Procházet zdrojové kódy

WebClient: make folder deletion recursive

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Nicola Murino před 2 roky
rodič
revize
6f422c3d8b

+ 34 - 33
go.mod

@@ -8,15 +8,15 @@ require (
 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.5.1
 	github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
 	github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
-	github.com/aws/aws-sdk-go-v2 v1.17.0
-	github.com/aws/aws-sdk-go-v2/config v1.17.9
-	github.com/aws/aws-sdk-go-v2/credentials v1.12.22
-	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.18
-	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.36
-	github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.20
-	github.com/aws/aws-sdk-go-v2/service/s3 v1.29.0
-	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.3
-	github.com/aws/aws-sdk-go-v2/service/sts v1.17.0
+	github.com/aws/aws-sdk-go-v2 v1.17.1
+	github.com/aws/aws-sdk-go-v2/config v1.17.10
+	github.com/aws/aws-sdk-go-v2/credentials v1.12.23
+	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19
+	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.37
+	github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.21
+	github.com/aws/aws-sdk-go-v2/service/s3 v1.29.1
+	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.4
+	github.com/aws/aws-sdk-go-v2/service/sts v1.17.1
 	github.com/cockroachdb/cockroach-go/v2 v2.2.16
 	github.com/coreos/go-oidc/v3 v3.4.0
 	github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
@@ -34,12 +34,12 @@ require (
 	github.com/hashicorp/go-hclog v1.3.1
 	github.com/hashicorp/go-plugin v1.4.5
 	github.com/hashicorp/go-retryablehttp v0.7.1
-	github.com/jackc/pgx/v5 v5.0.4-0.20221022150232-3e825ec8982f
+	github.com/jackc/pgx/v5 v5.0.4
 	github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126
-	github.com/klauspost/compress v1.15.11
+	github.com/klauspost/compress v1.15.12
 	github.com/lestrrat-go/jwx v1.2.25
 	github.com/lithammer/shortuuid/v3 v3.0.7
-	github.com/mattn/go-sqlite3 v1.14.15
+	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/mhale/smtpd v0.8.0
 	github.com/minio/sio v0.3.0
 	github.com/otiai10/copy v1.7.0
@@ -54,9 +54,9 @@ require (
 	github.com/sftpgo/sdk v0.1.2
 	github.com/shirou/gopsutil/v3 v3.22.9
 	github.com/spf13/afero v1.9.2
-	github.com/spf13/cobra v1.6.0
+	github.com/spf13/cobra v1.6.1
 	github.com/spf13/viper v1.13.0
-	github.com/stretchr/testify v1.8.0
+	github.com/stretchr/testify v1.8.1
 	github.com/studio-b12/gowebdav v0.0.0-20221015232716-17255f2e7423
 	github.com/subosito/gotenv v1.4.1
 	github.com/unrolled/secure v1.13.0
@@ -71,28 +71,29 @@ require (
 	golang.org/x/oauth2 v0.1.0
 	golang.org/x/sys v0.1.0
 	golang.org/x/time v0.1.0
-	google.golang.org/api v0.100.0
+	google.golang.org/api v0.101.0
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 )
 
 require (
-	cloud.google.com/go v0.104.0 // indirect
-	cloud.google.com/go/compute v1.10.0 // indirect
-	cloud.google.com/go/iam v0.5.0 // indirect
+	cloud.google.com/go v0.105.0 // indirect
+	cloud.google.com/go/compute v1.12.1 // indirect
+	cloud.google.com/go/compute/metadata v0.1.1 // indirect
+	cloud.google.com/go/iam v0.6.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // indirect
 	github.com/ajg/form v1.5.1 // indirect
-	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.24 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.18 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/ini v1.3.25 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.15 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.19 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.18 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.18 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sso v1.11.24 // indirect
-	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.7 // indirect
-	github.com/aws/smithy-go v1.13.3 // indirect
+	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 // indirect
+	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 // indirect
+	github.com/aws/smithy-go v1.13.4 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/boombuler/barcode v1.0.1 // indirect
 	github.com/cenkalti/backoff v2.2.1+incompatible // indirect
@@ -131,7 +132,7 @@ require (
 	github.com/magiconair/properties v1.8.6 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.16 // indirect
-	github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 	github.com/miekg/dns v1.1.50 // indirect
 	github.com/minio/sha256-simd v1.0.0 // indirect
 	github.com/mitchellh/go-testing-interface v1.14.1 // indirect
@@ -159,7 +160,7 @@ require (
 	golang.org/x/tools v0.2.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 // indirect
+	google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect
 	google.golang.org/grpc v1.50.1 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
@@ -171,5 +172,5 @@ require (
 replace (
 	github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
 	golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20221020054403-a265c1cba3cb
-	golang.org/x/net => github.com/drakkan/net v0.0.0-20221020052826-79457e688bf9
+	golang.org/x/net => github.com/drakkan/net v0.0.0-20221026175805-eaebd725b308
 )

+ 70 - 66
go.sum

@@ -36,8 +36,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9
 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
 cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
 cloud.google.com/go v0.103.0/go.mod h1:vwLx1nqLrzLX/fpwSMOXmFIqBOyHsvHbnAdbGSJ+mKk=
-cloud.google.com/go v0.104.0 h1:gSmWO7DY1vOm0MVU6DNXM11BWHHsTUmsC5cv1fuW5X8=
-cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
+cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=
+cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -50,16 +50,18 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m
 cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
 cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
 cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
-cloud.google.com/go/compute v1.10.0 h1:aoLIYaA1fX3ywihqpBk2APQKOo20nXsp1GEZQbx5Jk4=
-cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
+cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
+cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
+cloud.google.com/go/compute/metadata v0.1.1 h1:/sxEbyrm6cw+XOUw1YxBHlatV71z4vpnmO7z2IZ0h3I=
+cloud.google.com/go/compute/metadata v0.1.1/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
 cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
 cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
 cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
-cloud.google.com/go/iam v0.5.0 h1:fz9X5zyTWBmamZsqvqZqD7khbifcZF/q+Z1J8pfhIUg=
-cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=
+cloud.google.com/go/iam v0.6.0 h1:nsqQC88kT5Iwlm4MeNGTpfMWddp6NB/UOLFTH6m1QfQ=
+cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=
 cloud.google.com/go/kms v1.4.0 h1:iElbfoE61VeLhnZcGOltqL8HIly8Nhbe5t6JlH9GXjo=
 cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
 cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4=
@@ -224,70 +226,70 @@ github.com/aws/aws-sdk-go v1.44.45/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4
 github.com/aws/aws-sdk-go v1.44.68/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aws/aws-sdk-go-v2 v1.16.8/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw=
-github.com/aws/aws-sdk-go-v2 v1.17.0 h1:kWm8OZGx0Zvd6PsOfjFtwbw7+uWYp65DK8suo7WVznw=
-github.com/aws/aws-sdk-go-v2 v1.17.0/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k=
+github.com/aws/aws-sdk-go-v2 v1.17.1 h1:02c72fDJr87N8RAC2s3Qu0YuvMRZKNZJ9F+lAehCazk=
+github.com/aws/aws-sdk-go-v2 v1.17.1/go.mod h1:JLnGeGONAyi2lWXI1p0PCIOIy333JMVK1U7Hf0aRFLw=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.3/go.mod h1:gNsR5CaXKmQSSzrmGxmwmct/r+ZBfbxorAuXYsj/M5Y=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 h1:tcFliCWne+zOuUfKNRn8JdFBuWPDuISDH08wD2ULkhk=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8/go.mod h1:JTnlBSot91steJeti4ryyu/tLd4Sk84O5W22L7O2EQU=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9 h1:RKci2D7tMwpvGpDNZnGQw9wk6v7o/xSwFcUAuNPoB8k=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9/go.mod h1:vCmV1q1VK8eoQJ5+aYE7PkK1K6v41qJ5pJdK3ggCDvg=
 github.com/aws/aws-sdk-go-v2/config v1.15.15/go.mod h1:A1Lzyy/o21I5/s2FbyX5AevQfSVXpvvIDCoVFD0BC4E=
-github.com/aws/aws-sdk-go-v2/config v1.17.9 h1:PyqFD7DTmOx5gdvjFwZH2Tx0vivy+cJdM3SE3NVoWZc=
-github.com/aws/aws-sdk-go-v2/config v1.17.9/go.mod h1:NGC2Ut1x1Gl+qBdh4uGdqRTDtk6f3qS8VQ45kEoyAvM=
+github.com/aws/aws-sdk-go-v2/config v1.17.10 h1:zBy5QQ/mkvHElM1rygHPAzuH+sl8nsdSaxSWj0+rpdE=
+github.com/aws/aws-sdk-go-v2/config v1.17.10/go.mod h1:/4np+UiJJKpWHN7Q+LZvqXYgyjgeXm5+lLfDI6TPZao=
 github.com/aws/aws-sdk-go-v2/credentials v1.12.10/go.mod h1:g5eIM5XRs/OzIIK81QMBl+dAuDyoLN0VYaLP+tBqEOk=
-github.com/aws/aws-sdk-go-v2/credentials v1.12.22 h1:HPig9ugqH7Eyf2aqNVAPOCp3L/N2vlQ/IiaTxwcrH8U=
-github.com/aws/aws-sdk-go-v2/credentials v1.12.22/go.mod h1:XfHZqa+J1j2Am2GHrsWtg24tnkFkKxmWbWWel+W1zp0=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.23 h1:LctvcJMIb8pxvk5hQhChpCu0WlU6oKQmcYb1HA4IZSA=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.23/go.mod h1:0awX9iRr/+UO7OwRQFpV1hNtXxOVuehpjVEzrIAYNcA=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.9/go.mod h1:KDCCm4ONIdHtUloDcFvK2+vshZvx4Zmj7UMDfusuz5s=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.18 h1:63dqlW4EI4nfhmXJOUqP0zIaGEHoRPn1ahLz8hUOWrQ=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.18/go.mod h1:O3tSoDcot3jy62HNmq7ms16dPHQMR6nqQxooj8T53tI=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 h1:E3PXZSI3F2bzyj6XxUXdTIfvp425HHhwKsFvmzBwHgs=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19/go.mod h1:VihW95zQpeKQWVPGkwT+2+WJNQV8UXFfMTWdU6VErL8=
 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.21/go.mod h1:iIYPrQ2rYfZiB/iADYlhj9HHZ9TTi6PqKQPAqygohbE=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.36 h1:DYIvpSIM9YTdid6yRZk/w2kJhJJIbFnL/76NfzmfaTs=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.36/go.mod h1:1vzWYwKGRitVzk7xD3y8Ko7lg26qX+Pxwb5uRaOPSlM=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.37 h1:e1VtTBo+cLNjres0wTlMkmwCGGRjDEkkrz3frxxcaCs=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.37/go.mod h1:kdAV1UMnCkyG6tZJUC4mHbPoRjPA3dIK0L8mnsHERiM=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.15/go.mod h1:pWrr2OoHlT7M/Pd2y4HV3gJyPb3qj5qMmnPkKSNPYK4=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.24 h1:WFIoN2kiF95/4z4HNcJ9F9B0xFV0vrPlUOf3+uNIujM=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.24/go.mod h1:ghMzB/j2wRbPx5/4jPYxJdOtCG2ggrtY01j8K7FMBDA=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25 h1:nBO/RFxeq/IS5G9Of+ZrgucRciie2qpLy++3UGZ+q2E=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25/go.mod h1:Zb29PYkf42vVYQY6pvSyJCJcFHlPIiY+YKdPtwnvMkY=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.9/go.mod h1:08tUpeSGN33QKSO7fwxXczNfiwCpbj+GxK6XKwqWVv0=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.18 h1:c2RKF0UvfdVI6epHtFjDujlbiK+VeY85dP1i4gmYc5w=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.18/go.mod h1:fkQKYK/jUhCL/wNS1tOPrlYhr9vqutjCz4zZC1wBE1s=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 h1:oRHDrwCTVT8ZXi4sr9Ld+EXk7N/KGssOr2ygNeojEhw=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19/go.mod h1:6Q0546uHDp421okhmmGfbxzq2hBqbXFNpi4k+Q1JnQA=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.3.16/go.mod h1:CYmI+7x03jjJih8kBEEFKRQc40UjUokT0k7GbvrhhTc=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.3.25 h1:q4TXoep+lPTJneYxlIdcBrlGmTrhfNwrfkdBt1+HqzA=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.3.25/go.mod h1:9uX0Ksj6Zmsd3iQIyVkwkPWUqhPF6TxT/t8zYwUiQEU=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 h1:Mza+vlnZr+fPKFKRq/lKGVvM6B/8ZZmNdEopOwSQLms=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26/go.mod h1:Y2OJ+P+MC1u1VKnavT+PshiEuGPyh/7DqxoDNij4/bg=
 github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.6/go.mod h1:O7Oc4peGZDEKlddivslfYFvAbgzvl/GH3J8j3JIGBXc=
-github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.15 h1:15q0OjFjny5qjCC8nI+4DH+MZFDC2/BtXxONBNnVZR8=
-github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.15/go.mod h1:t7/Pw0mlxveHXyfzEkGjzQ59Xu9xUmzOfxe1S52TJ8Q=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 h1:2EXB7dtGwRYIN3XQ9qwIW504DVbKIw3r89xQnonGdsQ=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16/go.mod h1:XH+3h395e3WVdd6T2Z3mPxuI+x/HVtdqVOREkTiyubs=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.3/go.mod h1:gkb2qADY+OHaGLKNTYxMaQNacfeyQpZ4csDTQMeFmcw=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 h1:Lh1AShsuIJTwMkoxVCAYPJgNG5H+eN6SmoUn8nOZ5wE=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10 h1:dpiPHgmFstgkLG07KaYAewvuptq5kvo52xn7tVSrtrQ=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10/go.mod h1:9cBNUHI2aW4ho0A5T87O294iPDuuUOSIEDjnd1Lq/z0=
 github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.10/go.mod h1:Qks+dxK3O+Z2deAhNo6cJ8ls1bam3tUGUAcgxQP1c70=
-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.19 h1:jrV+VRNrUuzcwTZxdZMi1JtKMk71FN1H7VaF8XjGl44=
-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.19/go.mod h1:HGDDjLf/IyINXk4PcEZSEviZulqnePG76iq9/rC5qqo=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20 h1:KSvtm1+fPXE0swe9GPjc6msyrdTT0LB/BP8eLugL1FI=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20/go.mod h1:Mp4XI/CkWGD79AQxZ5lIFlgvC0A+gl+4BmyG1F+SfNc=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.9/go.mod h1:yQowTpvdZkFVuHrLBXmczat4W+WJKg/PafBZnGBLga0=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.18 h1:5oiCDEOHnYkk7uTVI8Wv6ftdFfb6YlUUNzkeePVIPjY=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.18/go.mod h1:QtCDHDOXunxeihz7iU15e09u9gRIeaa5WeE6FZVnGUo=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 h1:GE25AWCdNUPh9AOJzI9KIJnja7IwUc1WyUqz/JTyJ/I=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19/go.mod h1:02CP6iuYP+IVnBX5HULVdSAku/85eHB2Y9EsFhrkEwU=
 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.9/go.mod h1:Rc5+wn2k8gFSi3V1Ch4mhxOzjMh+bYSXVFfVaqowQOY=
-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.18 h1:sk9Z5ZwZpLGq3q8ZhOsw8bORT2t8raWPsFrq/yMMbZ0=
-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.18/go.mod h1:O1mfO/JzWKUNujOAqD39r7BXqlvhjh/JiPnQ97tvQMc=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19 h1:piDBAaWkaxkkVV3xJJbTehXCZRXYs49kvpi/LG6LR2o=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19/go.mod h1:BmQWRVkLTmyNzYPFAZgon53qKLWBNSvonugD1MrSWUs=
 github.com/aws/aws-sdk-go-v2/service/kms v1.18.1/go.mod h1:4PZMUkc9rXHWGVB5J9vKaZy3D7Nai79ORworQ3ASMiM=
-github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.20 h1:jOpM3C6a/W4cd31hj3qok1NZKu3pWYLEg5IwUharV+o=
-github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.20/go.mod h1:pvYIQ3quYKA9wXvn5oY6Suu4RqjURwN1tERJssL57nQ=
+github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.21 h1:LpIut7TpOhp8RuTD72PBj8ksPy3+RelT3LPwGgQ8+Hg=
+github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.21/go.mod h1:mRGY+k3s1yt7yQA3AfzJhnr68OCs1xDfQfIABFUk+ek=
 github.com/aws/aws-sdk-go-v2/service/s3 v1.27.2/go.mod h1:u+566cosFI+d+motIz3USXEh6sN8Nq4GrNXSg2RXVMo=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.29.0 h1:wmROdhyusq7m7HJgSB9Jm955XU4Kvz0FknIbr1dJTjA=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.29.0/go.mod h1:syhASH3D6eA1PCga49mGfvISJh/E2QYaooSIqir3pIM=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.29.1 h1:/EMdFPW/Ppieh0WUtQf1+qCGNLdsq5UWUyevBQ6vMVc=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.29.1/go.mod h1:/NHbqPRiwxSPVOB2Xr+StDEH+GWV/64WwnUjv4KYzV0=
 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.14/go.mod h1:xakbH8KMsQQKqzX87uyyzTHshc/0/Df8bsTneTS5pFU=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.3 h1:d5S+OhXne5O3cIo999RARy/N1dgXW2ldWgD53qbEAP4=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.3/go.mod h1:+X/VSQcuvHPWPRlM64HoWUJAPwsD86KpU9Z52lrsodM=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.4 h1:Hx79EGrkKNJya2iz2U5A7nyr7DjOu/TGTRefThfBZ1w=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.4/go.mod h1:k6CPuxyzO247nYEM1baEwHH1kRtosRCvgahAepaaShw=
 github.com/aws/aws-sdk-go-v2/service/sns v1.17.10/go.mod h1:uITsRNVMeCB3MkWpXxXw0eDz8pW4TYLzj+eyQtbhSxM=
 github.com/aws/aws-sdk-go-v2/service/sqs v1.19.1/go.mod h1:A94o564Gj+Yn+7QO1eLFeI7UVv3riy/YBFOfICVqFvU=
 github.com/aws/aws-sdk-go-v2/service/ssm v1.27.6/go.mod h1:fiFzQgj4xNOg4/wqmAiPvzgDMXPD+cUEplX/CYn+0j0=
 github.com/aws/aws-sdk-go-v2/service/sso v1.11.13/go.mod h1:d7ptRksDDgvXaUvxyHZ9SYh+iMDymm94JbVcgvSYSzU=
-github.com/aws/aws-sdk-go-v2/service/sso v1.11.24 h1:tNfD0JI7VKcIcEzYeIAXCIr8qnoq6DACg3QRt50ofOY=
-github.com/aws/aws-sdk-go-v2/service/sso v1.11.24/go.mod h1:7ZC+G3rX2IsGKIhiGDFiul7rgZPApvFy3dDJO7wKtno=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.7 h1:q2FDE8cl8rTPqgrTT0dF7xzIfGAwLMh2P+nU7F2CqVs=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.7/go.mod h1:sPh8yf7vmBOI/L9fqP55uq+T9WVoxnqrHMqyvgYC/gA=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 h1:GFZitO48N/7EsFDt8fMa5iYdmWqkUDDB3Eje6z3kbG0=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.25/go.mod h1:IARHuzTXmj1C0KS35vboR0FeJ89OkEy1M9mWbK2ifCI=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 h1:jcw6kKZrtNfBPJkaHrscDOZoe5gvi9wjudnxvozYFJo=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8/go.mod h1:er2JHN+kBY6FcMfcBBKNGCT3CarImmdFzishsqBmSRI=
 github.com/aws/aws-sdk-go-v2/service/sts v1.16.10/go.mod h1:cftkHYN6tCDNfkSasAmclSfl4l7cySoay8vz7p/ce0E=
-github.com/aws/aws-sdk-go-v2/service/sts v1.17.0 h1:9S0HcZUxKcU3HdN+M6GgLIvdbg9as5aOoHrvwRsPNYU=
-github.com/aws/aws-sdk-go-v2/service/sts v1.17.0/go.mod h1:9pZN58zQc5a4Dkdnhu/rI1lNBui1vP5B0giGCuUt2b0=
+github.com/aws/aws-sdk-go-v2/service/sts v1.17.1 h1:KRAix/KHvjGODaHAMXnxRk9t0D+4IJVUuS/uwXxngXk=
+github.com/aws/aws-sdk-go-v2/service/sts v1.17.1/go.mod h1:bXcN3koeVYiJcdDU89n3kCYILob7Y34AeLopUbZgLT4=
 github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
-github.com/aws/smithy-go v1.13.3 h1:l7LYxGuzK6/K+NzJ2mC+VvLUbae0sL3bXU//04MkmnA=
-github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
+github.com/aws/smithy-go v1.13.4 h1:/RN2z1txIJWeXeOkzX+Hk/4Uuvv7dWtCjbmVJcrskyk=
+github.com/aws/smithy-go v1.13.4/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
 github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -539,8 +541,8 @@ github.com/drakkan/crypto v0.0.0-20221020054403-a265c1cba3cb h1:ex3x8ir969oV6bQ8
 github.com/drakkan/crypto v0.0.0-20221020054403-a265c1cba3cb/go.mod h1:IBSs4ri4rdTqz2QKcpTKpwKMdM+WJ7atZeL9lCu2swQ=
 github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA=
 github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
-github.com/drakkan/net v0.0.0-20221020052826-79457e688bf9 h1:vU78OwgLgNqWDqhsk9So0XtQrPxgMDs85L+A3YotXIA=
-github.com/drakkan/net v0.0.0-20221020052826-79457e688bf9/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+github.com/drakkan/net v0.0.0-20221026175805-eaebd725b308 h1:F7OUb3MgSa2SuY5mdmseihGXhVhxv1OCuKHrona2eh0=
+github.com/drakkan/net v0.0.0-20221026175805-eaebd725b308/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@@ -1013,8 +1015,8 @@ github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQ
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
 github.com/jackc/pgx/v4 v4.16.0/go.mod h1:N0A9sFdWzkw/Jy1lwoiB64F2+ugFZi987zRxcPez/wI=
 github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
-github.com/jackc/pgx/v5 v5.0.4-0.20221022150232-3e825ec8982f h1:DEdjdZkMfuJ9Y9sELu1idpOg7101O6YlpdnSXlh0Et8=
-github.com/jackc/pgx/v5 v5.0.4-0.20221022150232-3e825ec8982f/go.mod h1:U0ynklHtgg43fue9Ly30w3OCSTDPlXjig9ghrNGaguQ=
+github.com/jackc/pgx/v5 v5.0.4 h1:r5O6y84qHX/z/HZV40JBdx2obsHz7/uRj5b+CcYEdeY=
+github.com/jackc/pgx/v5 v5.0.4/go.mod h1:U0ynklHtgg43fue9Ly30w3OCSTDPlXjig9ghrNGaguQ=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
@@ -1060,8 +1062,8 @@ github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
 github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
-github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
-github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
+github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
+github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
 github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.1.2 h1:XhdX4fqAJUA0yj+kUwMavO0hHrSPAecYdYf1ZmxHvak=
 github.com/klauspost/cpuid/v2 v2.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
@@ -1161,12 +1163,12 @@ github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vq
 github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
 github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
-github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
-github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM=
-github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
 github.com/mhale/smtpd v0.8.0 h1:5JvdsehCg33PQrZBvFyDMMUDQmvbzVpZgKob7eYBJc0=
 github.com/mhale/smtpd v0.8.0/go.mod h1:MQl+y2hwIEQCXtNhe5+55n0GZOjSmeqORDIXbqUL3x4=
@@ -1494,8 +1496,8 @@ github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKv
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
-github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
-github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
+github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
+github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@@ -1519,6 +1521,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -1529,8 +1532,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
 github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/studio-b12/gowebdav v0.0.0-20221015232716-17255f2e7423 h1:Wd8WDEEusB5+En4PiRWJp1cP59QLNsQun+mOTW8+s6s=
 github.com/studio-b12/gowebdav v0.0.0-20221015232716-17255f2e7423/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@@ -1783,7 +1787,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -2104,8 +2108,8 @@ google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6F
 google.golang.org/api v0.86.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
 google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
 google.golang.org/api v0.91.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
-google.golang.org/api v0.100.0 h1:LGUYIrbW9pzYQQ8NWXlaIVkgnfubVBZbMFb9P8TK374=
-google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
+google.golang.org/api v0.101.0 h1:lJPPeEBIRxGpGLwnBTam1NPEM8Z2BmmXEd3z812pjwM=
+google.golang.org/api v0.101.0/go.mod h1:CjxAAWWt3A3VrUE2IGDY2bgK5qhoG/OkyWVlYcP05MY=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -2216,8 +2220,8 @@ google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljW
 google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
 google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
 google.golang.org/genproto v0.0.0-20220802133213-ce4fa296bf78/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
-google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 h1:U1u4KB2kx6KR/aJDjQ97hZ15wQs8ZPvDcGcRynBhkvg=
-google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55/go.mod h1:45EK0dUbEZ2NHjCeAd2LXmyjAgGUGrpGROgjhC3ADck=
+google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 h1:GEgb2jF5zxsFJpJfg9RoDDWm7tiwc/DDSTE2BtLUkXU=
+google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
 google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

+ 134 - 4
internal/common/connection.go

@@ -462,7 +462,7 @@ func (c *BaseConnection) RemoveDir(virtualPath string) error {
 		return c.GetFsError(fs, err)
 	}
 	if !fi.IsDir() || fi.Mode()&os.ModeSymlink != 0 {
-		c.Log(logger.LevelError, "cannot remove %#v is not a directory", fsPath)
+		c.Log(logger.LevelError, "cannot remove %q is not a directory", fsPath)
 		return c.GetGenericError(nil)
 	}
 
@@ -477,6 +477,132 @@ func (c *BaseConnection) RemoveDir(virtualPath string) error {
 	return nil
 }
 
+type objectToRemoveMapping struct {
+	fsPath      string
+	virtualPath string
+	info        os.FileInfo
+}
+
+// orderDirsToRemove orders directories so that the empty ones will be at slice start
+func orderDirsToRemove(fs vfs.Fs, dirsToRemove []objectToRemoveMapping) []objectToRemoveMapping {
+	orderedDirs := make([]objectToRemoveMapping, 0, len(dirsToRemove))
+	removedDirs := make([]string, 0, len(dirsToRemove))
+
+	pathSeparator := "/"
+	if vfs.IsLocalOsFs(fs) {
+		pathSeparator = string(os.PathSeparator)
+	}
+
+	for len(orderedDirs) < len(dirsToRemove) {
+		for idx, d := range dirsToRemove {
+			if util.Contains(removedDirs, d.fsPath) {
+				continue
+			}
+			isEmpty := true
+			for idx1, d1 := range dirsToRemove {
+				if idx == idx1 {
+					continue
+				}
+				if util.Contains(removedDirs, d1.fsPath) {
+					continue
+				}
+				if strings.HasPrefix(d1.fsPath, d.fsPath+pathSeparator) {
+					isEmpty = false
+					break
+				}
+			}
+			if isEmpty {
+				orderedDirs = append(orderedDirs, d)
+				removedDirs = append(removedDirs, d.fsPath)
+			}
+		}
+	}
+
+	return orderedDirs
+}
+
+func (c *BaseConnection) removeDirTree(fs vfs.Fs, fsPath, virtualPath string) error {
+	var dirsToRemove []objectToRemoveMapping
+	var filesToRemove []objectToRemoveMapping
+
+	err := fs.Walk(fsPath, func(walkedPath string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		obj := objectToRemoveMapping{
+			fsPath:      walkedPath,
+			virtualPath: fs.GetRelativePath(walkedPath),
+			info:        info,
+		}
+		if info.IsDir() {
+			err = c.IsRemoveDirAllowed(fs, obj.fsPath, obj.virtualPath)
+			isDuplicated := false
+			for _, d := range dirsToRemove {
+				if d.fsPath == obj.fsPath {
+					isDuplicated = true
+					break
+				}
+			}
+			if !isDuplicated {
+				dirsToRemove = append(dirsToRemove, obj)
+			}
+		} else {
+			err = c.IsRemoveFileAllowed(obj.virtualPath)
+			filesToRemove = append(filesToRemove, obj)
+		}
+		if err != nil {
+			c.Log(logger.LevelError, "unable to remove dir tree, object %q->%q cannot be removed: %v",
+				virtualPath, fsPath, err)
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		c.Log(logger.LevelError, "failed to remove dir tree %q->%q: error: %+v", virtualPath, fsPath, err)
+		return c.GetFsError(fs, err)
+	}
+
+	for _, fileObj := range filesToRemove {
+		err = c.RemoveFile(fs, fileObj.fsPath, fileObj.virtualPath, fileObj.info)
+		if err != nil {
+			c.Log(logger.LevelError, "unable to remove dir tree, error removing file %q->%q: %v",
+				fileObj.virtualPath, fileObj.fsPath, err)
+			return err
+		}
+	}
+
+	for _, dirObj := range orderDirsToRemove(fs, dirsToRemove) {
+		err = c.RemoveDir(dirObj.virtualPath)
+		if err != nil {
+			c.Log(logger.LevelDebug, "unable to remove dir tree, error removing directory %q->%q: %v",
+				dirObj.virtualPath, dirObj.fsPath, err)
+			return err
+		}
+	}
+
+	return err
+}
+
+// RemoveAll removes the specified path and any children it contains
+func (c *BaseConnection) RemoveAll(virtualPath string) error {
+	fs, fsPath, err := c.GetFsAndResolvedPath(virtualPath)
+	if err != nil {
+		return err
+	}
+
+	fi, err := fs.Lstat(fsPath)
+	if err != nil {
+		c.Log(logger.LevelDebug, "failed to remove path %q: stat error: %+v", fsPath, err)
+		return c.GetFsError(fs, err)
+	}
+	if fi.IsDir() && fi.Mode()&os.ModeSymlink == 0 {
+		return c.removeDirTree(fs, fsPath, virtualPath)
+	}
+	return c.RemoveFile(fs, fsPath, virtualPath, fi)
+}
+
 // Rename renames (moves) virtualSourcePath to virtualTargetPath
 func (c *BaseConnection) Rename(virtualSourcePath, virtualTargetPath string) error {
 	if virtualSourcePath == virtualTargetPath {
@@ -509,7 +635,7 @@ func (c *BaseConnection) Rename(virtualSourcePath, virtualTargetPath string) err
 			initialSize = dstInfo.Size()
 		}
 		if !c.User.HasPerm(dataprovider.PermOverwrite, path.Dir(virtualTargetPath)) {
-			c.Log(logger.LevelDebug, "renaming %#v -> %#v is not allowed. Target exists but the user %#v"+
+			c.Log(logger.LevelDebug, "renaming %q -> %q is not allowed. Target exists but the user %q"+
 				"has no overwrite permission", virtualSourcePath, virtualTargetPath, c.User.Username)
 			return c.GetPermissionDeniedError()
 		}
@@ -824,11 +950,15 @@ func (c *BaseConnection) checkRecursiveRenameDirPermissions(fsSrc, fsDst vfs.Fs,
 		if err != nil {
 			return c.GetFsError(fsSrc, err)
 		}
+		if walkedPath != sourcePath && vfs.HasImplicitAtomicUploads(fsSrc) {
+			c.Log(logger.LevelInfo, "cannot rename non empty directory %q on this filesystem", virtualSourcePath)
+			return c.GetOpUnsupportedError()
+		}
 		dstPath := strings.Replace(walkedPath, sourcePath, targetPath, 1)
 		virtualSrcPath := fsSrc.GetRelativePath(walkedPath)
 		virtualDstPath := fsDst.GetRelativePath(dstPath)
 		if !c.isRenamePermitted(fsSrc, fsDst, walkedPath, dstPath, virtualSrcPath, virtualDstPath, info) {
-			c.Log(logger.LevelInfo, "rename %#v -> %#v is not allowed, virtual destination path: %#v",
+			c.Log(logger.LevelInfo, "rename %q -> %q is not allowed, virtual destination path: %q",
 				walkedPath, dstPath, virtualDstPath)
 			return c.GetPermissionDeniedError()
 		}
@@ -866,7 +996,7 @@ func (c *BaseConnection) isRenamePermitted(fsSrc, fsDst vfs.Fs, fsSourcePath, fs
 	virtualTargetPath string, fi os.FileInfo,
 ) bool {
 	if !c.isSameResourceRename(virtualSourcePath, virtualTargetPath) {
-		c.Log(logger.LevelInfo, "rename %#v->%#v is not allowed: the paths must be on the same resource",
+		c.Log(logger.LevelInfo, "rename %#q->%q is not allowed: the paths must be on the same resource",
 			virtualSourcePath, virtualTargetPath)
 		return false
 	}

+ 125 - 3
internal/common/connection_test.go

@@ -15,6 +15,7 @@
 package common
 
 import (
+	"errors"
 	"os"
 	"path"
 	"path/filepath"
@@ -33,11 +34,17 @@ import (
 	"github.com/drakkan/sftpgo/v2/internal/vfs"
 )
 
+var (
+	errWalkDir  = errors.New("err walk dir")
+	errWalkFile = errors.New("err walk file")
+)
+
 // MockOsFs mockable OsFs
 type MockOsFs struct {
 	vfs.Fs
 	hasVirtualFolders bool
 	name              string
+	err               error
 }
 
 // Name returns the name for the Fs implementation
@@ -61,11 +68,22 @@ func (fs *MockOsFs) Chtimes(name string, atime, mtime time.Time, isUploading boo
 	return vfs.ErrVfsUnsupported
 }
 
-func newMockOsFs(hasVirtualFolders bool, connectionID, rootDir, name string) vfs.Fs {
+// Walk returns a duplicate path for testing
+func (fs *MockOsFs) Walk(root string, walkFn filepath.WalkFunc) error {
+	if fs.err == errWalkDir {
+		walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil)        //nolint:errcheck
+		return walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck
+	}
+	walkFn("fsfpath", vfs.NewFileInfo("fpath", false, 0, time.Now(), false), nil) //nolint:errcheck
+	return fs.err
+}
+
+func newMockOsFs(hasVirtualFolders bool, connectionID, rootDir, name string, err error) vfs.Fs {
 	return &MockOsFs{
 		Fs:                vfs.NewOsFs(connectionID, rootDir, ""),
 		name:              name,
 		hasVirtualFolders: hasVirtualFolders,
+		err:               err,
 	}
 }
 
@@ -113,7 +131,7 @@ func TestSetStatMode(t *testing.T) {
 	}
 	user.Permissions = make(map[string][]string)
 	user.Permissions["/"] = []string{dataprovider.PermAny}
-	fs := newMockOsFs(true, "", user.GetHomeDir(), "")
+	fs := newMockOsFs(true, "", user.GetHomeDir(), "", nil)
 	conn := NewBaseConnection("", ProtocolWebDAV, "", "", user)
 	err := conn.handleChmod(fs, fakePath, fakePath, nil)
 	assert.NoError(t, err)
@@ -148,6 +166,15 @@ func TestRecursiveRenameWalkError(t *testing.T) {
 		filepath.Join(os.TempDir(), "/target"), "/source", "/target",
 		vfs.NewFileInfo("source", true, 0, time.Now(), false))
 	assert.ErrorIs(t, err, os.ErrNotExist)
+
+	fs = newMockOsFs(false, "mockID", filepath.Clean(os.TempDir()), "S3Fs", errWalkDir)
+	err = conn.checkRecursiveRenameDirPermissions(fs, fs, filepath.Join(os.TempDir(), "/source"),
+		filepath.Join(os.TempDir(), "/target"), "/source", "/target",
+		vfs.NewFileInfo("source", true, 0, time.Now(), false))
+	if assert.Error(t, err) {
+		assert.Equal(t, err.Error(), conn.GetOpUnsupportedError().Error())
+	}
+
 	conn.User.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
 		dataprovider.PermDownload, dataprovider.PermRenameFiles}
 	// no dir rename permission, the quick check path returns permission error without walking
@@ -441,7 +468,7 @@ func TestMaxWriteSize(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, int64(90), size)
 
-	fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), "")
+	fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), "", nil)
 	size, err = conn.GetMaxWriteSize(quotaResult, true, 100, fs.IsUploadResumeSupported())
 	assert.EqualError(t, err, ErrOpUnsupported.Error())
 	assert.Equal(t, int64(0), size)
@@ -519,3 +546,98 @@ func TestCheckParentDirsErrors(t *testing.T) {
 	err = os.RemoveAll(filepath.Join(os.TempDir(), "sub-dir"))
 	assert.NoError(t, err)
 }
+
+func TestRemoveDirTree(t *testing.T) {
+	user := dataprovider.User{
+		BaseUser: sdk.BaseUser{
+			HomeDir: filepath.Clean(os.TempDir()),
+		},
+	}
+	user.Permissions = make(map[string][]string)
+	user.Permissions["/"] = []string{dataprovider.PermAny}
+	fs := vfs.NewOsFs("connID", user.HomeDir, "")
+	connection := NewBaseConnection(fs.ConnectionID(), ProtocolWebDAV, "", "", user)
+
+	vpath := path.Join("adir", "missing")
+	p := filepath.Join(user.HomeDir, "adir", "missing")
+	err := connection.removeDirTree(fs, p, vpath)
+	if assert.Error(t, err) {
+		assert.True(t, fs.IsNotExist(err))
+	}
+
+	fs = newMockOsFs(false, "mockID", user.HomeDir, "", nil)
+	err = connection.removeDirTree(fs, p, vpath)
+	if assert.Error(t, err) {
+		assert.True(t, fs.IsNotExist(err), "unexpected error: %v", err)
+	}
+
+	errFake := errors.New("fake err")
+	fs = newMockOsFs(false, "mockID", user.HomeDir, "", errFake)
+	err = connection.removeDirTree(fs, p, vpath)
+	if assert.Error(t, err) {
+		assert.EqualError(t, err, ErrGenericFailure.Error())
+	}
+
+	fs = newMockOsFs(true, "mockID", user.HomeDir, "", errWalkDir)
+	err = connection.removeDirTree(fs, p, vpath)
+	if assert.Error(t, err) {
+		assert.True(t, fs.IsPermission(err), "unexpected error: %v", err)
+	}
+
+	fs = newMockOsFs(false, "mockID", user.HomeDir, "", errWalkFile)
+	err = connection.removeDirTree(fs, p, vpath)
+	if assert.Error(t, err) {
+		assert.EqualError(t, err, ErrGenericFailure.Error())
+	}
+
+	connection.User.Permissions["/"] = []string{dataprovider.PermListItems}
+	fs = newMockOsFs(false, "mockID", user.HomeDir, "", nil)
+	err = connection.removeDirTree(fs, p, vpath)
+	if assert.Error(t, err) {
+		assert.EqualError(t, err, ErrPermissionDenied.Error())
+	}
+}
+
+func TestOrderDirsToRemove(t *testing.T) {
+	fs := vfs.NewOsFs("id", os.TempDir(), "")
+	dirsToRemove := []objectToRemoveMapping{}
+
+	orderedDirs := orderDirsToRemove(fs, dirsToRemove)
+	assert.Equal(t, len(dirsToRemove), len(orderedDirs))
+
+	dirsToRemove = []objectToRemoveMapping{
+		{
+			fsPath:      "dir1",
+			virtualPath: "",
+		},
+	}
+	orderedDirs = orderDirsToRemove(fs, dirsToRemove)
+	assert.Equal(t, len(dirsToRemove), len(orderedDirs))
+
+	dirsToRemove = []objectToRemoveMapping{
+		{
+			fsPath:      "dir1",
+			virtualPath: "",
+		},
+		{
+			fsPath:      "dir12",
+			virtualPath: "",
+		},
+		{
+			fsPath:      filepath.Join("dir1", "a", "b"),
+			virtualPath: "",
+		},
+		{
+			fsPath:      filepath.Join("dir1", "a"),
+			virtualPath: "",
+		},
+	}
+
+	orderedDirs = orderDirsToRemove(fs, dirsToRemove)
+	if assert.Equal(t, len(dirsToRemove), len(orderedDirs)) {
+		assert.Equal(t, "dir12", orderedDirs[0].fsPath)
+		assert.Equal(t, filepath.Join("dir1", "a", "b"), orderedDirs[1].fsPath)
+		assert.Equal(t, filepath.Join("dir1", "a"), orderedDirs[2].fsPath)
+		assert.Equal(t, "dir1", orderedDirs[3].fsPath)
+	}
+}

+ 75 - 0
internal/common/protocol_test.go

@@ -47,6 +47,7 @@ import (
 	"github.com/sftpgo/sdk"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"github.com/studio-b12/gowebdav"
 	"golang.org/x/crypto/ssh"
 
 	"github.com/drakkan/sftpgo/v2/internal/common"
@@ -61,6 +62,7 @@ import (
 	"github.com/drakkan/sftpgo/v2/internal/smtp"
 	"github.com/drakkan/sftpgo/v2/internal/util"
 	"github.com/drakkan/sftpgo/v2/internal/vfs"
+	"github.com/drakkan/sftpgo/v2/internal/webdavd"
 )
 
 const (
@@ -68,6 +70,7 @@ const (
 	httpProxyAddr       = "127.0.0.1:7777"
 	sftpServerAddr      = "127.0.0.1:4022"
 	smtpServerAddr      = "127.0.0.1:2525"
+	webDavServerPort    = 9191
 	defaultUsername     = "test_common_sftp"
 	defaultPassword     = "test_password"
 	defaultSFTPUsername = "test_common_sftpfs_user"
@@ -145,6 +148,13 @@ func TestMain(m *testing.M) {
 	httpdConf.Bindings[0].Port = 4080
 	httpdtest.SetBaseURL("http://127.0.0.1:4080")
 
+	webDavConf := config.GetWebDAVDConfig()
+	webDavConf.Bindings = []webdavd.Binding{
+		{
+			Port: webDavServerPort,
+		},
+	}
+
 	go func() {
 		if err := sftpdConf.Initialize(configDir); err != nil {
 			logger.ErrorToConsole("could not start SFTP server: %v", err)
@@ -159,8 +169,16 @@ func TestMain(m *testing.M) {
 		}
 	}()
 
+	go func() {
+		if err := webDavConf.Initialize(configDir); err != nil {
+			logger.ErrorToConsole("could not start WebDAV server: %v", err)
+			os.Exit(1)
+		}
+	}()
+
 	waitTCPListening(sftpdConf.Bindings[0].GetAddress())
 	waitTCPListening(httpdConf.Bindings[0].GetAddress())
+	waitTCPListening(webDavConf.Bindings[0].GetAddress())
 
 	go func() {
 		// start a test HTTP server to receive action notifications
@@ -315,6 +333,50 @@ func TestBaseConnection(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func TestRemoveAll(t *testing.T) {
+	u := getTestUser()
+	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
+	assert.NoError(t, err)
+
+	webDavClient := getWebDavClient(user)
+	err = webDavClient.RemoveAll("/")
+	if assert.Error(t, err) {
+		assert.True(t, gowebdav.IsErrCode(err, http.StatusForbidden))
+	}
+
+	testDir := "baseDir"
+	err = webDavClient.RemoveAll(testDir)
+	assert.NoError(t, err)
+
+	conn, client, err := getSftpClient(user)
+	if assert.NoError(t, err) {
+		defer conn.Close()
+		defer client.Close()
+
+		err = client.Mkdir(testDir)
+		assert.NoError(t, err)
+		err = writeSFTPFile(path.Join(testDir, testFileName), 1234, client)
+		assert.NoError(t, err)
+
+		err = webDavClient.RemoveAll(path.Join(testDir, testFileName))
+		assert.NoError(t, err)
+		_, err = client.Stat(path.Join(testDir, testFileName))
+		assert.Error(t, err)
+
+		err = writeSFTPFile(path.Join(testDir, testFileName), 1234, client)
+		assert.NoError(t, err)
+		err = webDavClient.RemoveAll(testDir)
+		assert.NoError(t, err)
+		_, err = client.Stat(testDir)
+		assert.Error(t, err)
+	}
+
+	_, err = httpdtest.RemoveUser(user, http.StatusOK)
+	assert.NoError(t, err)
+	err = os.RemoveAll(user.GetHomeDir())
+	assert.NoError(t, err)
+}
+
 func TestRelativeSymlinks(t *testing.T) {
 	u := getTestUser()
 	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
@@ -2951,6 +3013,8 @@ func TestResolvePathError(t *testing.T) {
 	assert.Error(t, err)
 	_, err = conn.DoStat(testPath, 0, false)
 	assert.Error(t, err)
+	err = conn.RemoveAll(testPath)
+	assert.Error(t, err)
 	err = conn.SetStat(testPath, &common.StatAttributes{
 		Atime: time.Now(),
 		Mtime: time.Now(),
@@ -6409,6 +6473,17 @@ func getSftpClient(user dataprovider.User) (*ssh.Client, *sftp.Client, error) {
 	return conn, sftpClient, err
 }
 
+func getWebDavClient(user dataprovider.User) *gowebdav.Client {
+	rootPath := fmt.Sprintf("http://localhost:%d/", webDavServerPort)
+	pwd := defaultPassword
+	if user.Password != "" {
+		pwd = user.Password
+	}
+	client := gowebdav.NewClient(rootPath, user.Username, pwd)
+	client.SetTimeout(10 * time.Second)
+	return client
+}
+
 func getTestUser() dataprovider.User {
 	user := dataprovider.User{
 		BaseUser: sdk.BaseUser{

+ 1 - 1
internal/common/transfer_test.go

@@ -62,7 +62,7 @@ func TestTransferUpdateQuota(t *testing.T) {
 	assert.NoError(t, err)
 
 	transfer.ErrTransfer = errFake
-	transfer.Fs = newMockOsFs(true, "", "", "S3Fs fake")
+	transfer.Fs = newMockOsFs(true, "", "", "S3Fs fake", nil)
 	assert.False(t, transfer.updateQuota(1, 0))
 }
 

+ 3 - 3
internal/httpd/api_http_user.go

@@ -119,12 +119,12 @@ func deleteUserDir(w http.ResponseWriter, r *http.Request) {
 	defer common.Connections.Remove(connection.GetID())
 
 	name := connection.User.GetCleanedPath(r.URL.Query().Get("path"))
-	err = connection.RemoveDir(name)
+	err = connection.RemoveAll(name)
 	if err != nil {
-		sendAPIResponse(w, r, err, fmt.Sprintf("Unable to delete directory %#v", name), getMappedStatusCode(err))
+		sendAPIResponse(w, r, err, fmt.Sprintf("Unable to delete directory %q", name), getMappedStatusCode(err))
 		return
 	}
-	sendAPIResponse(w, r, nil, fmt.Sprintf("Directory %#v deleted", name), http.StatusOK)
+	sendAPIResponse(w, r, nil, fmt.Sprintf("Directory %q deleted", name), http.StatusOK)
 }
 
 func getUserFile(w http.ResponseWriter, r *http.Request) {

+ 1 - 123
internal/webdavd/handler.go

@@ -112,21 +112,7 @@ func (c *Connection) RemoveAll(ctx context.Context, name string) error {
 	c.UpdateLastActivity()
 
 	name = util.CleanPath(name)
-	fs, p, err := c.GetFsAndResolvedPath(name)
-	if err != nil {
-		return err
-	}
-
-	var fi os.FileInfo
-	if fi, err = fs.Lstat(p); err != nil {
-		c.Log(logger.LevelDebug, "failed to remove file %#v: stat error: %+v", p, err)
-		return c.GetFsError(fs, err)
-	}
-
-	if fi.IsDir() && fi.Mode()&os.ModeSymlink == 0 {
-		return c.removeDirTree(fs, p, name)
-	}
-	return c.RemoveFile(fs, p, name, fi)
+	return c.BaseConnection.RemoveAll(name)
 }
 
 // OpenFile opens the named file with specified flag.
@@ -290,111 +276,3 @@ func (c *Connection) handleUploadToExistingFile(fs vfs.Fs, resolvedPath, filePat
 
 	return newWebDavFile(baseTransfer, w, nil), nil
 }
-
-type objectMapping struct {
-	fsPath      string
-	virtualPath string
-	info        os.FileInfo
-}
-
-func (c *Connection) removeDirTree(fs vfs.Fs, fsPath, virtualPath string) error {
-	var dirsToRemove []objectMapping
-	var filesToRemove []objectMapping
-
-	err := fs.Walk(fsPath, func(walkedPath string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-
-		obj := objectMapping{
-			fsPath:      walkedPath,
-			virtualPath: fs.GetRelativePath(walkedPath),
-			info:        info,
-		}
-		if info.IsDir() {
-			err = c.IsRemoveDirAllowed(fs, obj.fsPath, obj.virtualPath)
-			isDuplicated := false
-			for _, d := range dirsToRemove {
-				if d.fsPath == obj.fsPath {
-					isDuplicated = true
-					break
-				}
-			}
-			if !isDuplicated {
-				dirsToRemove = append(dirsToRemove, obj)
-			}
-		} else {
-			err = c.IsRemoveFileAllowed(obj.virtualPath)
-			filesToRemove = append(filesToRemove, obj)
-		}
-		if err != nil {
-			c.Log(logger.LevelDebug, "unable to remove dir tree, object %#v->%#v cannot be removed: %v",
-				virtualPath, fsPath, err)
-			return err
-		}
-
-		return nil
-	})
-	if err != nil {
-		c.Log(logger.LevelError, "failed to remove dir tree %#v->%#v: error: %+v", virtualPath, fsPath, err)
-		return err
-	}
-
-	for _, fileObj := range filesToRemove {
-		err = c.RemoveFile(fs, fileObj.fsPath, fileObj.virtualPath, fileObj.info)
-		if err != nil {
-			c.Log(logger.LevelDebug, "unable to remove dir tree, error removing file %#v->%#v: %v",
-				fileObj.virtualPath, fileObj.fsPath, err)
-			return err
-		}
-	}
-
-	for _, dirObj := range c.orderDirsToRemove(fs, dirsToRemove) {
-		err = c.RemoveDir(dirObj.virtualPath)
-		if err != nil {
-			c.Log(logger.LevelDebug, "unable to remove dir tree, error removing directory %#v->%#v: %v",
-				dirObj.virtualPath, dirObj.fsPath, err)
-			return err
-		}
-	}
-
-	return err
-}
-
-// order directories so that the empty ones will be at slice start
-func (c *Connection) orderDirsToRemove(fs vfs.Fs, dirsToRemove []objectMapping) []objectMapping {
-	orderedDirs := make([]objectMapping, 0, len(dirsToRemove))
-	removedDirs := make([]string, 0, len(dirsToRemove))
-
-	pathSeparator := "/"
-	if vfs.IsLocalOsFs(fs) {
-		pathSeparator = string(os.PathSeparator)
-	}
-
-	for len(orderedDirs) < len(dirsToRemove) {
-		for idx, d := range dirsToRemove {
-			if util.Contains(removedDirs, d.fsPath) {
-				continue
-			}
-			isEmpty := true
-			for idx1, d1 := range dirsToRemove {
-				if idx == idx1 {
-					continue
-				}
-				if util.Contains(removedDirs, d1.fsPath) {
-					continue
-				}
-				if strings.HasPrefix(d1.fsPath, d.fsPath+pathSeparator) {
-					isEmpty = false
-					break
-				}
-			}
-			if isEmpty {
-				orderedDirs = append(orderedDirs, d)
-				removedDirs = append(removedDirs, d.fsPath)
-			}
-		}
-	}
-
-	return orderedDirs
-}

+ 7 - 130
internal/webdavd/internal_test.go

@@ -19,7 +19,6 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"encoding/xml"
-	"errors"
 	"fmt"
 	"io"
 	"net/http"
@@ -272,11 +271,6 @@ xr5cb9VBRBtB9aOKVfuRhpatAfS2Pzm2Htae9lFn7slGPUmu2hkjDw==
 -----END RSA PRIVATE KEY-----`
 )
 
-var (
-	errWalkDir  = errors.New("err walk dir")
-	errWalkFile = errors.New("err walk file")
-)
-
 // MockOsFs mockable OsFs
 type MockOsFs struct {
 	vfs.Fs
@@ -315,86 +309,22 @@ func (fs *MockOsFs) Remove(name string, isDir bool) error {
 
 // Rename renames (moves) source to target
 func (fs *MockOsFs) Rename(source, target string) error {
-	if fs.err != nil {
-		return fs.err
-	}
 	return os.Rename(source, target)
 }
 
-// Walk returns a duplicate path for testing
-func (fs *MockOsFs) Walk(root string, walkFn filepath.WalkFunc) error {
-	if fs.err == errWalkDir {
-		walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck
-		walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck
-		return nil
-	}
-	walkFn("fsfpath", vfs.NewFileInfo("fpath", false, 0, time.Now(), false), nil) //nolint:errcheck
-	return fs.err
-}
-
 // GetMimeType returns the content type
 func (fs *MockOsFs) GetMimeType(name string) (string, error) {
 	return "application/custom-mime", nil
 }
 
-func newMockOsFs(err error, atomicUpload bool, connectionID, rootDir string, reader *pipeat.PipeReaderAt) vfs.Fs {
+func newMockOsFs(atomicUpload bool, connectionID, rootDir string, reader *pipeat.PipeReaderAt) vfs.Fs {
 	return &MockOsFs{
 		Fs:                      vfs.NewOsFs(connectionID, rootDir, ""),
-		err:                     err,
 		isAtomicUploadSupported: atomicUpload,
 		reader:                  reader,
 	}
 }
 
-func TestOrderDirsToRemove(t *testing.T) {
-	user := dataprovider.User{}
-	fs := vfs.NewOsFs("id", os.TempDir(), "")
-	connection := &Connection{
-		BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, "", "", user),
-		request:        nil,
-	}
-	dirsToRemove := []objectMapping{}
-
-	orderedDirs := connection.orderDirsToRemove(fs, dirsToRemove)
-	assert.Equal(t, len(dirsToRemove), len(orderedDirs))
-
-	dirsToRemove = []objectMapping{
-		{
-			fsPath:      "dir1",
-			virtualPath: "",
-		},
-	}
-	orderedDirs = connection.orderDirsToRemove(fs, dirsToRemove)
-	assert.Equal(t, len(dirsToRemove), len(orderedDirs))
-
-	dirsToRemove = []objectMapping{
-		{
-			fsPath:      "dir1",
-			virtualPath: "",
-		},
-		{
-			fsPath:      "dir12",
-			virtualPath: "",
-		},
-		{
-			fsPath:      filepath.Join("dir1", "a", "b"),
-			virtualPath: "",
-		},
-		{
-			fsPath:      filepath.Join("dir1", "a"),
-			virtualPath: "",
-		},
-	}
-
-	orderedDirs = connection.orderDirsToRemove(fs, dirsToRemove)
-	if assert.Equal(t, len(dirsToRemove), len(orderedDirs)) {
-		assert.Equal(t, "dir12", orderedDirs[0].fsPath)
-		assert.Equal(t, filepath.Join("dir1", "a", "b"), orderedDirs[1].fsPath)
-		assert.Equal(t, filepath.Join("dir1", "a"), orderedDirs[2].fsPath)
-		assert.Equal(t, "dir1", orderedDirs[3].fsPath)
-	}
-}
-
 func TestUserInvalidParams(t *testing.T) {
 	u := &dataprovider.User{
 		BaseUser: sdk.BaseUser{
@@ -643,7 +573,7 @@ func TestFileAccessErrors(t *testing.T) {
 		assert.ErrorIs(t, err, os.ErrNotExist)
 	}
 
-	fs = newMockOsFs(nil, false, fs.ConnectionID(), user.HomeDir, nil)
+	fs = newMockOsFs(false, fs.ConnectionID(), user.HomeDir, nil)
 	_, err = connection.handleUploadToExistingFile(fs, p, p, 0, path.Join("adir", missingPath))
 	if assert.Error(t, err) {
 		assert.ErrorIs(t, err, os.ErrNotExist)
@@ -706,59 +636,6 @@ func TestFileAccessErrors(t *testing.T) {
 	}
 }
 
-func TestRemoveDirTree(t *testing.T) {
-	user := dataprovider.User{
-		BaseUser: sdk.BaseUser{
-			HomeDir: filepath.Clean(os.TempDir()),
-		},
-	}
-	user.Permissions = make(map[string][]string)
-	user.Permissions["/"] = []string{dataprovider.PermAny}
-	fs := vfs.NewOsFs("connID", user.HomeDir, "")
-	connection := &Connection{
-		BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, "", "", user),
-	}
-
-	vpath := path.Join("adir", "missing")
-	p := filepath.Join(user.HomeDir, "adir", "missing")
-	err := connection.removeDirTree(fs, p, vpath)
-	if assert.Error(t, err) {
-		assert.True(t, fs.IsNotExist(err))
-	}
-
-	fs = newMockOsFs(nil, false, "mockID", user.HomeDir, nil)
-	err = connection.removeDirTree(fs, p, vpath)
-	if assert.Error(t, err) {
-		assert.True(t, fs.IsNotExist(err), "unexpected error: %v", err)
-	}
-
-	errFake := errors.New("fake err")
-	fs = newMockOsFs(errFake, false, "mockID", user.HomeDir, nil)
-	err = connection.removeDirTree(fs, p, vpath)
-	if assert.Error(t, err) {
-		assert.EqualError(t, err, errFake.Error())
-	}
-
-	fs = newMockOsFs(errWalkDir, true, "mockID", user.HomeDir, nil)
-	err = connection.removeDirTree(fs, p, vpath)
-	if assert.Error(t, err) {
-		assert.True(t, fs.IsPermission(err), "unexpected error: %v", err)
-	}
-
-	fs = newMockOsFs(errWalkFile, false, "mockID", user.HomeDir, nil)
-	err = connection.removeDirTree(fs, p, vpath)
-	if assert.Error(t, err) {
-		assert.EqualError(t, err, errWalkFile.Error())
-	}
-
-	connection.User.Permissions["/"] = []string{dataprovider.PermListItems}
-	fs = newMockOsFs(nil, false, "mockID", user.HomeDir, nil)
-	err = connection.removeDirTree(fs, p, vpath)
-	if assert.Error(t, err) {
-		assert.EqualError(t, err, common.ErrPermissionDenied.Error())
-	}
-}
-
 func TestContentType(t *testing.T) {
 	user := dataprovider.User{
 		BaseUser: sdk.BaseUser{
@@ -775,7 +652,7 @@ func TestContentType(t *testing.T) {
 	ctx := context.Background()
 	baseTransfer := common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile,
 		common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{})
-	fs = newMockOsFs(nil, false, fs.ConnectionID(), user.GetHomeDir(), nil)
+	fs = newMockOsFs(false, fs.ConnectionID(), user.GetHomeDir(), nil)
 	err := os.WriteFile(testFilePath, []byte(""), os.ModePerm)
 	assert.NoError(t, err)
 	davFile := newWebDavFile(baseTransfer, nil, nil)
@@ -873,7 +750,7 @@ func TestTransferReadWriteErrors(t *testing.T) {
 
 	r, w, err = pipeat.Pipe()
 	assert.NoError(t, err)
-	mockFs := newMockOsFs(nil, false, fs.ConnectionID(), user.HomeDir, r)
+	mockFs := newMockOsFs(false, fs.ConnectionID(), user.HomeDir, r)
 	baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile,
 		common.TransferDownload, 0, 0, 0, 0, false, mockFs, dataprovider.TransferQuota{})
 	davFile = newWebDavFile(baseTransfer, nil, nil)
@@ -974,13 +851,13 @@ func TestTransferSeek(t *testing.T) {
 		common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{AllowedTotalSize: 100})
 	davFile = newWebDavFile(baseTransfer, nil, nil)
 	davFile.reader = f
-	davFile.Fs = newMockOsFs(nil, true, fs.ConnectionID(), user.GetHomeDir(), nil)
+	davFile.Fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), nil)
 	res, err = davFile.Seek(2, io.SeekStart)
 	assert.NoError(t, err)
 	assert.Equal(t, int64(2), res)
 
 	davFile = newWebDavFile(baseTransfer, nil, nil)
-	davFile.Fs = newMockOsFs(nil, true, fs.ConnectionID(), user.GetHomeDir(), nil)
+	davFile.Fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), nil)
 	res, err = davFile.Seek(2, io.SeekEnd)
 	assert.NoError(t, err)
 	assert.Equal(t, int64(5), res)
@@ -989,7 +866,7 @@ func TestTransferSeek(t *testing.T) {
 		common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{AllowedTotalSize: 100})
 
 	davFile = newWebDavFile(baseTransfer, nil, nil)
-	davFile.Fs = newMockOsFs(nil, true, fs.ConnectionID(), user.GetHomeDir(), nil)
+	davFile.Fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), nil)
 	res, err = davFile.Seek(2, io.SeekEnd)
 	assert.True(t, fs.IsNotExist(err))
 	assert.Equal(t, int64(0), res)

+ 3 - 1
templates/webclient/files.html

@@ -594,8 +594,10 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
             var itemType = getTypeFromMeta(selected);
             var itemName = getNameFromMeta(selected);
             var path;
+            var reqTimeout = 15000;
             if (itemType == "1"){
                 path = '{{.DirsURL}}';
+                reqTimeout = 90000
             } else {
                 path = '{{.FilesURL}}';
             }
@@ -606,7 +608,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
                 type: 'DELETE',
                 dataType: 'json',
                 headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
-                timeout: 60000,
+                timeout: reqTimeout,
                 success: function (result) {
                     index++;
                     success++;