mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 00:50:31 +00:00
http actions: add multipart support
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
3267a50ae3
commit
c2a65a9a74
20 changed files with 897 additions and 154 deletions
10
.github/workflows/development.yml
vendored
10
.github/workflows/development.yml
vendored
|
@ -423,6 +423,12 @@ jobs:
|
|||
gzip output/man/man1/*
|
||||
cp sftpgo output/
|
||||
|
||||
- name: Get commit SHA
|
||||
if: ${{ matrix.arch != 'amd64' }}
|
||||
id: get_commit
|
||||
run: echo ::set-output name=COMMIT::${GITHUB_SHA::8}
|
||||
shell: bash
|
||||
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
if: ${{ matrix.arch != 'amd64' }}
|
||||
name: Build for ${{ matrix.arch }}
|
||||
|
@ -437,7 +443,7 @@ jobs:
|
|||
shell: /bin/bash
|
||||
install: |
|
||||
apt-get update -q -y
|
||||
apt-get install -q -y curl gcc git
|
||||
apt-get install -q -y curl gcc
|
||||
if [ ${{ matrix.go }} == 'latest' ]
|
||||
then
|
||||
GO_VERSION=$(curl -L https://go.dev/VERSION?m=text)
|
||||
|
@ -457,7 +463,7 @@ jobs:
|
|||
then
|
||||
export GOARM=7
|
||||
fi
|
||||
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
mkdir -p output/{init,bash_completion,zsh_completion}
|
||||
cp sftpgo.json output/
|
||||
cp -r templates output/
|
||||
|
|
7
.github/workflows/docker.yml
vendored
7
.github/workflows/docker.yml
vendored
|
@ -95,6 +95,13 @@ jobs:
|
|||
fi
|
||||
TAGS="${TAGS},${DOCKER_IMAGE}:distroless"
|
||||
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:distroless-slim"
|
||||
elif [[ $DOCKER_PKG == debian-plugins ]]; then
|
||||
if [[ -n $MAJOR && -n $MINOR ]]; then
|
||||
TAGS="${TAGS},${DOCKER_IMAGE}:${MINOR}-plugins,${DOCKER_IMAGE}:${MAJOR}-plugins"
|
||||
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:${MINOR}-plugins-slim,${DOCKER_IMAGE}:${MAJOR}-plugins-slim"
|
||||
fi
|
||||
TAGS="${TAGS},${DOCKER_IMAGE}:plugins"
|
||||
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:plugins-slim"
|
||||
else
|
||||
if [[ -n $MAJOR && -n $MINOR ]]; then
|
||||
TAGS="${TAGS},${DOCKER_IMAGE}:${MINOR}-alpine,${DOCKER_IMAGE}:${MAJOR}-alpine"
|
||||
|
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
|
@ -326,6 +326,12 @@ jobs:
|
|||
env:
|
||||
SFTPGO_VERSION: ${{ steps.get_version.outputs.SFTPGO_VERSION }}
|
||||
|
||||
- name: Get commit SHA
|
||||
if: ${{ matrix.arch != 'amd64' }}
|
||||
id: get_commit
|
||||
run: echo ::set-output name=COMMIT::${GITHUB_SHA::8}
|
||||
shell: bash
|
||||
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
if: ${{ matrix.arch != 'amd64' }}
|
||||
name: Build for ${{ matrix.arch }}
|
||||
|
@ -340,7 +346,7 @@ jobs:
|
|||
shell: /bin/bash
|
||||
install: |
|
||||
apt-get update -q -y
|
||||
apt-get install -q -y curl gcc git xz-utils
|
||||
apt-get install -q -y curl gcc xz-utils
|
||||
GO_DOWNLOAD_ARCH=${{ matrix.go-arch }}
|
||||
if [ ${{ matrix.arch}} == 'armv7' ]
|
||||
then
|
||||
|
@ -350,7 +356,7 @@ jobs:
|
|||
tar -C /usr/local -xzf go.tar.gz
|
||||
run: |
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
mkdir -p output/{init,sqlite,bash_completion,zsh_completion}
|
||||
echo "For documentation please take a look here:" > output/README.txt
|
||||
echo "" >> output/README.txt
|
||||
|
|
|
@ -4,11 +4,12 @@ SFTPGo provides an official Docker image, it is available on both [Docker Hub](h
|
|||
|
||||
## Supported tags and respective Dockerfile links
|
||||
|
||||
- [v2.3.3, v2.3, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile)
|
||||
- [v2.3.3-alpine, v2.3-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile.alpine)
|
||||
- [v2.3.3-slim, v2.3-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile)
|
||||
- [v2.3.3-alpine-slim, v2.3-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile.alpine)
|
||||
- [v2.3.3-distroless-slim, v2.3-distroless-slim, v2-distroless-slim, distroless-slim](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile.distroless)
|
||||
- [v2.3.4, v2.3, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile)
|
||||
- [v2.3.4-plugins, v2.3-plugins, v2-plugins, plugins](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile)
|
||||
- [v2.3.4-alpine, v2.3-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile.alpine)
|
||||
- [v2.3.4-slim, v2.3-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile)
|
||||
- [v2.3.4-alpine-slim, v2.3-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile.alpine)
|
||||
- [v2.3.4-distroless-slim, v2.3-distroless-slim, v2-distroless-slim, distroless-slim](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile.distroless)
|
||||
- [edge](../Dockerfile)
|
||||
- [edge-plugins](../Dockerfile)
|
||||
- [edge-alpine](../Dockerfile.alpine)
|
||||
|
|
53
go.mod
53
go.mod
|
@ -4,21 +4,21 @@ go 1.19
|
|||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.26.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.3
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.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.16.12
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.3
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.16
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.13
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.29
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.15
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.7
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.20
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.15
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.14
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.5
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.18
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.31
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.17
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.9
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.22
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.17
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.15
|
||||
github.com/coreos/go-oidc/v3 v3.2.0
|
||||
github.com/coreos/go-oidc/v3 v3.3.0
|
||||
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
|
||||
github.com/fclairamb/ftpserverlib v0.19.1
|
||||
github.com/fclairamb/go-log v0.4.1
|
||||
|
@ -44,7 +44,7 @@ require (
|
|||
github.com/minio/sio v0.3.0
|
||||
github.com/otiai10/copy v1.7.0
|
||||
github.com/pires/go-proxyproto v0.6.2
|
||||
github.com/pkg/sftp v1.13.5
|
||||
github.com/pkg/sftp v1.13.6-0.20220831160757-628507938ec6
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
|
@ -52,7 +52,7 @@ require (
|
|||
github.com/rs/xid v1.4.0
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657
|
||||
github.com/shirou/gopsutil/v3 v3.22.7
|
||||
github.com/shirou/gopsutil/v3 v3.22.8
|
||||
github.com/spf13/afero v1.9.2
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/viper v1.12.0
|
||||
|
@ -80,18 +80,18 @@ require (
|
|||
cloud.google.com/go/iam v0.3.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.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.1 // indirect
|
||||
github.com/aws/smithy-go v1.13.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3 // indirect
|
||||
github.com/aws/smithy-go v1.13.2 // 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
|
||||
|
@ -156,7 +156,7 @@ require (
|
|||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf // indirect
|
||||
google.golang.org/genproto v0.0.0-20220902135211-223410557253 // indirect
|
||||
google.golang.org/grpc v1.49.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
|
@ -167,7 +167,6 @@ require (
|
|||
|
||||
replace (
|
||||
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
|
||||
github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d
|
||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220831070132-e3c36f2ab82b
|
||||
golang.org/x/net => github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5
|
||||
)
|
||||
|
|
107
go.sum
107
go.sum
|
@ -90,8 +90,8 @@ github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
|
|||
github.com/Azure/azure-sdk-for-go v59.3.0+incompatible h1:dPIm0BO4jsMXFcCI/sLTPkBtE7mk8WMuRHA0JeWhlcQ=
|
||||
github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2 h1:lneMk5qtUMulXa/eVxjVd+/bDYMEDIqYpLzLa2/EsNI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.3 h1:8LoU8N2lIUzkmstvwXvVfniMZlFbesfT2AmA1aqvRr8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.3/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0 h1:Yoicul8bnVdQrhDMTHxdEckRGX01XvwXDHUT9zYZ3k0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
|
||||
|
@ -142,69 +142,69 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
|
|||
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.12 h1:wbMYa2PlFysFx2GLIQojr6FJV5+OWCM/BwyHXARxETA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.12/go.mod h1:C+Ym0ag2LIghJbXhfXZ0YEEp49rBWowxKzJLUoob0ts=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.14 h1:db6GvO4Z2UqHt5gvT0lr6J5x5P+oQ7bdRzczVaRekMU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.14/go.mod h1:s/G+UV29dECbF5rf+RNj1xhlmvoNurGSr+McVSRj59w=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.5 h1:7A1nDFvkVlBmMa69QMLkw/m/DDHm6PUluIYK61aQoOY=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.5/go.mod h1:DnlOnWR2YuzMXNSHHNuoklObUE3SwWlcRTGL/zL+Aj8=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.7 h1:/kxQjtZc7j67TMW/aFJfpsrlvFhsq3lNbX41qN5Tro4=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.7/go.mod h1:KvHyNlxCjo9Y1Fsz+6Ex9OaN2jKijvMxzROxpW5Vctc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.3 h1:s1As/fiVMmM3CObC4GcSaSbkhm88S6a5qn8St3wgal0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.3/go.mod h1:tRGUOfk9Rrf6UCJm5qDlL9AizSsgvteuKX4qajAV3pU=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.5 h1:+NS1BWvprx7nHcIk5o32LrZgifs/7Pm1V2nWjQgZ2H0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.5/go.mod h1:H0cvPNDO3uExWts/9PDhD/0ne2esu1uaIulwn1vkwxM=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.16 h1:HXczS88Pg36j8dq0KSjtHBPFs8gdRyBSS1hueeG/rxA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.16/go.mod h1:eLJ+j1lwQdHJ0c56tRoDWcgss1e/laVmvW2AaOicuAw=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.18 h1:HF62tbhARhgLfvmfwUbL9qZ+dkbZYzbFdxBb3l5gr7Q=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.18/go.mod h1:O7n/CPagQ33rfG6h7vR/W02ammuc5CrsSM22cNZp9so=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.13 h1:+uferi8SUDZtMloCDt24Zenyy/i71C/ua5mjUCpbpN0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.13/go.mod h1:y0eXmsNBFIVjUE8ZBjES8myOHlMsXDz7qGT93+MVdjk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15 h1:nkQ+aI0OCeYfzrBipL6ja/6VEbUnHQoZHBHtoK+Nzxw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15/go.mod h1:Oz2/qWINxIgSmoZT9adpxJy2UhpcOAI3TIyWgYMVSz0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.29 h1:VKi/79iKGaZ9pJTSuj/gNlzJdFczcGcsw9NDAT7I+hY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.29/go.mod h1:ge60sLiMug/7ubLIbRyM9zNv5fR99ZzR+staDaM7+Tw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.31 h1:Ggf7rvFS1s3/Nauv2mokAY+RfKsCAHvfiiZJoYd0lV0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.31/go.mod h1:Iv2xOFdy8aFIxVKEdzo9puLXFaGNnjx5xzGYIlGzhuY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.19 h1:gC5mudiFrWGhzcdoWj1iCGUfrzCpQG0MQIQf0CXFFQQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.19/go.mod h1:llxE6bwUZhuCas0K7qGiu5OgMis3N7kdWtFSxoHmJ7E=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.21 h1:gRIXnmAVNyoRQywdNtpAkgY+f30QNzgF53Q5OobNZZs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.21/go.mod h1:XsmHMV9c512xgsW01q7H0ut+UQQQpWX8QsFbdLHDwaU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.13 h1:qezY57na06d6kSE7uuB0N7XEflu914AXx/hg2L8Ykcw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.13/go.mod h1:lB12mkZqCSo5PsdBFLNqc2M/OOYgNAy8UtaktyuWvE8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.15 h1:noAhOo2mMDyYhTx99aYPvQw16T3fQ/DiKAv9fzpIKH8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.15/go.mod h1:kjJ4CyD9M3Wq88GYg3IPfj67Rs0Uvz8aXK7MJ8BvE4I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.20 h1:GvszACAU8GSV3+Tant5GutW6smY8WavrP8ZuRS9Ku4Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.20/go.mod h1:bfTcsThj5a9P5pIGRy0QudJ8k4+issxXX+O6Djnd5Cs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.10 h1:233xgzn4lsBeN7qgG+k2kLquzBk35WB+nIhPMeK0h/Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.10/go.mod h1:1nl/nuVB6+UOpiyYJBfyhCzsX8fJAL6fCVcbtPIIV4w=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22 h1:nF+E8HfYpOMw6M5oA9efB602VC00IHNQnB5CmFvZPvA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22/go.mod h1:tltHVGy977LrSOgRR5aV9+miyno/Gul/uJNPKS7FzP4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.12 h1:i0Tig01XGhXo/ki1BZUbRMhusGVCScEvaWdlFRWxAKk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.12/go.mod h1:QPoxYMISvteeDH4A89gGWWlCA/Bz6oUDF7hGdPdOPuE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.6 h1:Z0Yw2qkgPZVGbOR70snGRAlBR0QIGPLkHoNhR4+7hbY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.6/go.mod h1:Slj62rcu4BKdMAH0wqeP0fUkW1b1bkCxcSP+ZY5cevE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.8 h1:NpixDFjwr1BZg2459mX07NZnVYGGp62Lb6AtVGOLNlo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.8/go.mod h1:MJUgrBPfGB4yk2uWoImVqd9cklry1hATyJV/7gJ6JTk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.14 h1:NWR21daQBDyY4WChz4Gd78QuCPorUJiSHg7r1OWvfgA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.14/go.mod h1:Yz4G3rD1LtBcg6gIYtJtpoEjts9IZMHiamdm3F1xtNA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.16 h1:kHc3TqW5kJ9Vfd9YEwywrNrL87DItpvAohlP+OuzABY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.16/go.mod h1:U/9ZCgIx6x6NTdFRt60qO3gxUxBx4gRi+S/Yc/n+7vc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.13 h1:ObfthqDyhe7rMAOa7pqft6974VHIk8BAJB7kYdoIfTA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.13/go.mod h1:V390DK4MQxLpDdXxFqizyz8KUxuWImkW/xzgXMz0yyk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15 h1:xlf0J6DUgAj/ocvKQxCmad8Bu1lJuRbt5Wu+4G1xw1g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15/go.mod h1:ZVJ7ejRl4+tkWMuCwjXoy0jd8fF5u3RCyWjSVjUIvQE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.13 h1:h1equp9qdWANft5cmtDUditRlALvE7tuaHs2RdSbsQg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.13/go.mod h1:3RA7cs1uHkbV3f6tMYy7u0OfkyVckZBM70wUS4h1MDk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.15 h1:v9f7NY7D19ssE2EM+m9yT1m5zdWHuRAsZaFh24GAkOk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.15/go.mod h1:gXfPo3nMoCbJKTZKDxv3rUhcYJjYT/K++jEqcWHjD/Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g=
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.15 h1:ek8ACOAGvDWRm1kFCcj22soNkkLFh4WPBFv7BdWqebs=
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.15/go.mod h1:Zf+Tf40dskiGdwVJU2HIgln1vtnQF8QpsguBsbI5Uq8=
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.17 h1:b8nlmU7/7j+Tujr7X4YcJ0hb0hqQ/IeXCt8/CjJVO4A=
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.17/go.mod h1:kJoiz0fTRMsFZp4BICG6nC++aet5gG9jyjxcGlxxMUs=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.7 h1:BlxqVULzNS7udJIwZBJdL8NNcLbSwgXv/WRJCVUaMm8=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.7/go.mod h1:orjy5IRgBQnh9EI/lMW7YGF6eYk6re8HPFbL66a2DSo=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.9 h1:imVonvre+AHMcDc3B9bPHHy5ZgjIkkYc/jyDBK8FHFw=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.9/go.mod h1:0Gfmg8gjPhVPy/IXkLAmyKZbAue+2s11BWKH+oXggmg=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.20 h1:j41VjMJNc5T9AWkLf/FdVtR46st2PZYB/6xoBBY2/8Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.20/go.mod h1:F2AUfGEOcxpOTzo/+Bur5PrtsvnhVQQbd4CGfPicOpw=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.22 h1:ggHTCgbIivTM85PFjv/rkJbchrmLSNL+Vcj5hg54TyM=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.22/go.mod h1:zT2j7Ndi+FcBX+zfYLDppqODSgSdKlquB3LPLPVDAts=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.19 h1:WdCwfJmu23XiIDeZwclSyAorQe916M3LeHd53xqBjfA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.19/go.mod h1:ytmEi5+qwcSNcV2pVA8PIb1DnKT/0Bu/K4nfJHwoM6c=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.1 h1:p48IfndYbRk3iDsoQAmVXdCKEM5+7Y50JAPikjwk8gI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.1/go.mod h1:NY+G+8PW0ISyJ7/6t5mgOe6qpJiwZa9Jix05WPscJjg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.21 h1:7jUFr+7F4MzIjCZzy7ygRtXFQcQ0kAbT0gUvtUeAdyU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.21/go.mod h1:q8nYq51W3gpZempYsAD83fPRlrOTMCwN+Ahg4BKFTXQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3 h1:UTTPNP3/WzZa7hoHP3Szb/Yl0bM3NoBrf5ABy1OArUM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3/go.mod h1:+IF75RMJh0+zqTGXGshyEGRsU2ImqWv6UuHGkHl6kEo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.15 h1:ApuR2BK9vf5/XXsImHBBsYJ6aUhmUhBHnZMPyhJo1jQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.15/go.mod h1:Y+BUV19q3OmQVqNUlbZ40zVi3NM6Biuxwkx/qdSD/CY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.17 h1:LVM2jzEQ8mhb2dhrFl4PJ3sa5+KcKT01dsMk2Ma9/FU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.17/go.mod h1:bQujK1n0V1D1Gz5uII1jaB1WDvhj4/T3tElsJnVXCR0=
|
||||
github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
|
||||
github.com/aws/smithy-go v1.13.0 h1:YfyEmSJLo7fAv8FbuDK4R8F9aAmi9DZ88Zb/KJJmUl0=
|
||||
github.com/aws/smithy-go v1.13.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/smithy-go v1.13.2 h1:TBLKyeJfXTrTXRHmsv4qWt9IQGYyWThLYaJWSahTOGE=
|
||||
github.com/aws/smithy-go v1.13.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
|
@ -239,8 +239,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
|
|||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.15 h1:6TeTC1JLSlHJWJCswWZ7mQyT16kY5mQSs53C2coQISI=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.15/go.mod h1:xZ2VHjUEb/cySv0scXBx7YsBnHtLHkR1+w/w73b5i3M=
|
||||
github.com/coreos/go-oidc/v3 v3.2.0 h1:2eR2MGR7thBXSQ2YbODlF0fcmgtliLCfr9iX6RW11fc=
|
||||
github.com/coreos/go-oidc/v3 v3.2.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
|
||||
github.com/coreos/go-oidc/v3 v3.3.0 h1:Y1LV3mP+QT3MEycATZpAiwfyN+uxZLqVbAHJUuOJEe4=
|
||||
github.com/coreos/go-oidc/v3 v3.3.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
|
@ -268,8 +268,6 @@ github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHP
|
|||
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
||||
github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5 h1:+sVMXrU1DiQLNDgz1KvybqHEzRf8KuX5xQW8fpii6rI=
|
||||
github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d h1:kNk/KRhszPJASp7WvjagNW254aKK643Lu8/fr4/ukiM=
|
||||
github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
|
||||
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4=
|
||||
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001/go.mod h1:kltMsfRMTHSFdMbK66XdS8mfMW77+FZA1fGY1xYMF84=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
@ -662,6 +660,9 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pkg/sftp v1.13.6-0.20220831160757-628507938ec6 h1:hxLT9qX4jw+GjGuPA6XHtooT1+nf/hr5anQtACaXZmY=
|
||||
github.com/pkg/sftp v1.13.6-0.20220831160757-628507938ec6/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
|
@ -715,8 +716,8 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
|
|||
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
|
||||
github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657 h1:UXTpae6d+G/VI3sVITl+58SK0F3ZULn9dlEPMXcyNKY=
|
||||
github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657/go.mod h1:PTp1TfXa+95wHw9yuZu7BA3vmzLqbRkz3gBmMNnwFQg=
|
||||
github.com/shirou/gopsutil/v3 v3.22.7 h1:flKnuCMfUUrO+oAvwAd6GKZgnPzr098VA/UJ14nhJd4=
|
||||
github.com/shirou/gopsutil/v3 v3.22.7/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
|
||||
github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y=
|
||||
github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
|
@ -938,6 +939,7 @@ golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -1222,8 +1224,8 @@ google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP
|
|||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf h1:Q5xNKbTSFwkuaaGaR7CMcXEM5sy19KYdUU8iF8/iRC0=
|
||||
google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
|
||||
google.golang.org/genproto v0.0.0-20220902135211-223410557253 h1:vXJMM8Shg7TGaYxZsQ++A/FOSlbDmDtWhS/o+3w/hj4=
|
||||
google.golang.org/genproto v0.0.0-20220902135211-223410557253/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
@ -1286,7 +1288,6 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
|||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -20,7 +20,10 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -49,7 +52,8 @@ const (
|
|||
|
||||
var (
|
||||
// eventManager handle the supported event rules actions
|
||||
eventManager eventRulesContainer
|
||||
eventManager eventRulesContainer
|
||||
multipartQuoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -455,7 +459,12 @@ func (p *EventParams) getStatusString() string {
|
|||
// getUsers returns users with group settings not applied
|
||||
func (p *EventParams) getUsers() ([]dataprovider.User, error) {
|
||||
if p.sender == "" {
|
||||
return dataprovider.DumpUsers()
|
||||
users, err := dataprovider.DumpUsers()
|
||||
if err != nil {
|
||||
eventManagerLog(logger.LevelError, "unable to get users: %+v", err)
|
||||
return users, errors.New("unable to get users")
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
user, err := p.getUserFromSender()
|
||||
if err != nil {
|
||||
|
@ -467,7 +476,8 @@ func (p *EventParams) getUsers() ([]dataprovider.User, error) {
|
|||
func (p *EventParams) getUserFromSender() (dataprovider.User, error) {
|
||||
user, err := dataprovider.UserExists(p.sender)
|
||||
if err != nil {
|
||||
return user, fmt.Errorf("error getting user %q: %w", p.sender, err)
|
||||
eventManagerLog(logger.LevelError, "unable to get user %q: %+v", p.sender, err)
|
||||
return user, fmt.Errorf("error getting user %q", p.sender)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
@ -515,26 +525,45 @@ func (p *EventParams) getStringReplacements(addObjectData bool) []string {
|
|||
return replacements
|
||||
}
|
||||
|
||||
func getFileContent(conn *BaseConnection, virtualPath string, expectedSize int) ([]byte, error) {
|
||||
func getFileReader(conn *BaseConnection, virtualPath string) (io.ReadCloser, func(), error) {
|
||||
fs, fsPath, err := conn.GetFsAndResolvedPath(virtualPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
f, r, cancelFn, err := fs.Open(fsPath, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if cancelFn == nil {
|
||||
cancelFn = func() {}
|
||||
}
|
||||
defer cancelFn()
|
||||
|
||||
var reader io.ReadCloser
|
||||
if f != nil {
|
||||
reader = f
|
||||
} else {
|
||||
reader = r
|
||||
return f, cancelFn, nil
|
||||
}
|
||||
return r, cancelFn, nil
|
||||
}
|
||||
|
||||
func writeFileContent(conn *BaseConnection, virtualPath string, w io.Writer) error {
|
||||
reader, cancelFn, err := getFileReader(conn, virtualPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer cancelFn()
|
||||
defer reader.Close()
|
||||
|
||||
_, err = io.Copy(w, reader)
|
||||
return err
|
||||
}
|
||||
|
||||
func getFileContent(conn *BaseConnection, virtualPath string, expectedSize int) ([]byte, error) {
|
||||
reader, cancelFn, err := getFileReader(conn, virtualPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer cancelFn()
|
||||
defer reader.Close()
|
||||
|
||||
data := make([]byte, expectedSize)
|
||||
|
@ -632,19 +661,103 @@ func getHTTPRuleActionEndpoint(c dataprovider.EventActionHTTPConfig, replacer *s
|
|||
return c.Endpoint, nil
|
||||
}
|
||||
|
||||
func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventParams) error {
|
||||
if !c.Password.IsEmpty() {
|
||||
if err := c.Password.TryDecrypt(); err != nil {
|
||||
return fmt.Errorf("unable to decrypt password: %w", err)
|
||||
func writeHTTPPart(m *multipart.Writer, part dataprovider.HTTPPart, h textproto.MIMEHeader,
|
||||
conn *BaseConnection, replacer *strings.Replacer,
|
||||
) error {
|
||||
partWriter, err := m.CreatePart(h)
|
||||
if err != nil {
|
||||
eventManagerLog(logger.LevelError, "unable to create part %q, err: %v", part.Name, err)
|
||||
return err
|
||||
}
|
||||
if part.Body != "" {
|
||||
_, err = partWriter.Write([]byte(replaceWithReplacer(part.Body, replacer)))
|
||||
if err != nil {
|
||||
eventManagerLog(logger.LevelError, "unable to write part %q, err: %v", part.Name, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err = writeFileContent(conn, util.CleanPath(replacer.Replace(part.Filepath)), partWriter)
|
||||
if err != nil {
|
||||
eventManagerLog(logger.LevelError, "unable to write file part %q, err: %v", part.Name, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHTTPRuleActionBody(c dataprovider.EventActionHTTPConfig, replacer *strings.Replacer,
|
||||
cancel context.CancelFunc, user dataprovider.User,
|
||||
) (io.ReadCloser, string, error) {
|
||||
var body io.ReadCloser
|
||||
if c.Method == http.MethodGet {
|
||||
return body, "", nil
|
||||
}
|
||||
if c.Body != "" {
|
||||
return io.NopCloser(bytes.NewBufferString(replaceWithReplacer(c.Body, replacer))), "", nil
|
||||
}
|
||||
if len(c.Parts) > 0 {
|
||||
r, w := io.Pipe()
|
||||
m := multipart.NewWriter(w)
|
||||
|
||||
var conn *BaseConnection
|
||||
if user.Username != "" {
|
||||
var err error
|
||||
user, err = getUserForEventAction(user)
|
||||
if err != nil {
|
||||
return body, "", err
|
||||
}
|
||||
connectionID := fmt.Sprintf("%s_%s", protocolEventAction, xid.New().String())
|
||||
err = user.CheckFsRoot(connectionID)
|
||||
if err != nil {
|
||||
user.CloseFs() //nolint:errcheck
|
||||
return body, "", fmt.Errorf("error getting multipart file/s, unable to check root fs for user %q: %w",
|
||||
user.Username, err)
|
||||
}
|
||||
conn = NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer w.Close()
|
||||
defer user.CloseFs() //nolint:errcheck
|
||||
|
||||
for _, part := range c.Parts {
|
||||
h := make(textproto.MIMEHeader)
|
||||
if part.Body != "" {
|
||||
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"`, multipartQuoteEscaper.Replace(part.Name)))
|
||||
} else {
|
||||
filePath := util.CleanPath(replacer.Replace(part.Filepath))
|
||||
h.Set("Content-Disposition",
|
||||
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
|
||||
multipartQuoteEscaper.Replace(part.Name), multipartQuoteEscaper.Replace(path.Base(filePath))))
|
||||
contentType := mime.TypeByExtension(path.Ext(filePath))
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
h.Set("Content-Type", contentType)
|
||||
}
|
||||
for _, keyVal := range part.Headers {
|
||||
h.Set(keyVal.Key, replaceWithReplacer(keyVal.Value, replacer))
|
||||
}
|
||||
if err := writeHTTPPart(m, part, h, conn, replacer); err != nil {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
m.Close()
|
||||
}()
|
||||
|
||||
return r, m.FormDataContentType(), nil
|
||||
}
|
||||
return body, "", nil
|
||||
}
|
||||
|
||||
func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventParams) error {
|
||||
if err := c.TryDecryptPassword(); err != nil {
|
||||
return err
|
||||
}
|
||||
addObjectData := false
|
||||
if params.Object != nil {
|
||||
if !addObjectData {
|
||||
if strings.Contains(c.Body, "{{ObjectData}}") {
|
||||
addObjectData = true
|
||||
}
|
||||
}
|
||||
addObjectData = c.HasObjectData()
|
||||
}
|
||||
|
||||
replacements := params.getStringReplacements(addObjectData)
|
||||
|
@ -654,16 +767,32 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa
|
|||
return err
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
if c.Body != "" && c.Method != http.MethodGet {
|
||||
body = bytes.NewBufferString(replaceWithReplacer(c.Body, replacer))
|
||||
ctx, cancel := c.GetContext()
|
||||
defer cancel()
|
||||
|
||||
var user dataprovider.User
|
||||
if c.HasMultipartFile() {
|
||||
user, err = params.getUserFromSender()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
req, err := http.NewRequest(c.Method, endpoint, body)
|
||||
body, contentType, err := getHTTPRuleActionBody(c, replacer, cancel, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if body != nil {
|
||||
defer body.Close()
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, c.Method, endpoint, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
if c.Username != "" {
|
||||
req.SetBasicAuth(replaceWithReplacer(c.Username, replacer), c.Password.GetAdditionalData())
|
||||
req.SetBasicAuth(replaceWithReplacer(c.Username, replacer), c.Password.GetPayload())
|
||||
}
|
||||
for _, keyVal := range c.Headers {
|
||||
req.Header.Set(keyVal.Key, replaceWithReplacer(keyVal.Value, replacer))
|
||||
|
@ -676,11 +805,11 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa
|
|||
if err != nil {
|
||||
eventManagerLog(logger.LevelDebug, "unable to send http notification, endpoint: %s, elapsed: %s, err: %v",
|
||||
endpoint, time.Since(startTime), err)
|
||||
return err
|
||||
return fmt.Errorf("error sending HTTP request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
eventManagerLog(logger.LevelDebug, "http notification sent, endopoint: %s, elapsed: %s, status code: %d",
|
||||
eventManagerLog(logger.LevelDebug, "http notification sent, endpoint: %s, elapsed: %s, status code: %d",
|
||||
endpoint, time.Since(startTime), resp.StatusCode)
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusNoContent {
|
||||
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
|
@ -761,14 +890,15 @@ func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params *Event
|
|||
func getUserForEventAction(user dataprovider.User) (dataprovider.User, error) {
|
||||
err := user.LoadAndApplyGroupSettings()
|
||||
if err != nil {
|
||||
return dataprovider.User{}, err
|
||||
eventManagerLog(logger.LevelError, "unable to get group for user %q: %+v", user.Username, err)
|
||||
return dataprovider.User{}, fmt.Errorf("unable to get groups for user %q", user.Username)
|
||||
}
|
||||
user.Filters.DisableFsChecks = false
|
||||
user.Filters.FilePatterns = nil
|
||||
for k := range user.Permissions {
|
||||
user.Permissions[k] = []string{dataprovider.PermAny}
|
||||
}
|
||||
return user, err
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func executeDeleteFileFsAction(conn *BaseConnection, item string, info os.FileInfo) error {
|
||||
|
|
|
@ -17,6 +17,8 @@ package common
|
|||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -349,6 +351,26 @@ func TestEventManagerErrors(t *testing.T) {
|
|||
}}, []string{"/a", "/b"}, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
_, _, err = getHTTPRuleActionBody(dataprovider.EventActionHTTPConfig{
|
||||
Method: http.MethodPost,
|
||||
Parts: []dataprovider.HTTPPart{
|
||||
{
|
||||
Name: "p1",
|
||||
},
|
||||
},
|
||||
}, nil, nil, dataprovider.User{
|
||||
BaseUser: sdk.BaseUser{
|
||||
Username: "u",
|
||||
},
|
||||
Groups: []sdk.GroupMapping{
|
||||
{
|
||||
Name: groupName,
|
||||
Type: sdk.GroupTypePrimary,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
|
||||
dataRetentionAction := dataprovider.BaseEventAction{
|
||||
Type: dataprovider.ActionTypeDataRetentionCheck,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
|
@ -469,6 +491,9 @@ func TestEventRuleActions(t *testing.T) {
|
|||
}
|
||||
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
||||
assert.NoError(t, err)
|
||||
action.Options.HTTPConfig.Method = http.MethodGet
|
||||
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
||||
assert.NoError(t, err)
|
||||
action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v/404", httpAddr)
|
||||
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
||||
if assert.Error(t, err) {
|
||||
|
@ -484,8 +509,22 @@ func TestEventRuleActions(t *testing.T) {
|
|||
action.Options.HTTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusSecretBox, "payload", "key", "data")
|
||||
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "unable to decrypt password")
|
||||
assert.Contains(t, err.Error(), "unable to decrypt HTTP password")
|
||||
}
|
||||
action.Options.HTTPConfig.Password = kms.NewEmptySecret()
|
||||
action.Options.HTTPConfig.Body = ""
|
||||
action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
|
||||
{
|
||||
Name: "p1",
|
||||
Filepath: "path",
|
||||
},
|
||||
}
|
||||
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "error getting user")
|
||||
}
|
||||
action.Options.HTTPConfig.Parts = nil
|
||||
action.Options.HTTPConfig.Body = "{{ObjectData}}"
|
||||
// test disk and transfer quota reset
|
||||
username1 := "user1"
|
||||
username2 := "user2"
|
||||
|
@ -849,6 +888,12 @@ func TestEventRuleActions(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "no folder quota reset executed")
|
||||
}
|
||||
|
||||
body, _, err := getHTTPRuleActionBody(dataprovider.EventActionHTTPConfig{
|
||||
Method: http.MethodPost,
|
||||
}, nil, nil, dataprovider.User{})
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, body)
|
||||
|
||||
err = os.RemoveAll(folder1.MappedPath)
|
||||
assert.NoError(t, err)
|
||||
err = dataprovider.DeleteFolder(foldername1, "", "")
|
||||
|
@ -979,7 +1024,19 @@ func TestFilesystemActionErrors(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
_, err = getFileContent(NewBaseConnection("", protocolEventAction, "", "", user), "/f.txt", 1234)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = executeHTTPRuleAction(dataprovider.EventActionHTTPConfig{
|
||||
Endpoint: "http://127.0.0.1:9999/",
|
||||
Method: http.MethodPost,
|
||||
Parts: []dataprovider.HTTPPart{
|
||||
{
|
||||
Name: "p1",
|
||||
Filepath: "/filepath",
|
||||
},
|
||||
},
|
||||
}, &EventParams{
|
||||
sender: username,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
user.FsConfig.Provider = sdk.LocalFilesystemProvider
|
||||
user.Permissions["/"] = []string{dataprovider.PermUpload}
|
||||
err = dataprovider.DeleteUser(username, "", "")
|
||||
|
@ -1276,3 +1333,34 @@ func TestEventParamsStatusFromError(t *testing.T) {
|
|||
params.AddError(os.ErrNotExist)
|
||||
assert.Equal(t, 2, params.Status)
|
||||
}
|
||||
|
||||
type testWriter struct {
|
||||
errTest error
|
||||
sentinel string
|
||||
}
|
||||
|
||||
func (w *testWriter) Write(p []byte) (int, error) {
|
||||
if w.errTest != nil {
|
||||
return 0, w.errTest
|
||||
}
|
||||
if w.sentinel == string(p) {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func TestWriteHTTPPartsError(t *testing.T) {
|
||||
m := multipart.NewWriter(&testWriter{
|
||||
errTest: io.ErrShortWrite,
|
||||
})
|
||||
|
||||
err := writeHTTPPart(m, dataprovider.HTTPPart{}, nil, nil, nil)
|
||||
assert.ErrorIs(t, err, io.ErrShortWrite)
|
||||
|
||||
body := "test body"
|
||||
m = multipart.NewWriter(&testWriter{sentinel: body})
|
||||
err = writeHTTPPart(m, dataprovider.HTTPPart{
|
||||
Body: body,
|
||||
}, nil, nil, nil)
|
||||
assert.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
||||
}
|
||||
|
|
|
@ -169,6 +169,16 @@ func TestMain(m *testing.M) {
|
|||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Not found\n")
|
||||
})
|
||||
http.HandleFunc("/multipart", func(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseMultipartForm(1048576)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "KO\n")
|
||||
return
|
||||
}
|
||||
defer r.MultipartForm.RemoveAll() //nolint:errcheck
|
||||
fmt.Fprintf(w, "OK\n")
|
||||
})
|
||||
if err := http.ListenAndServe(httpAddr, nil); err != nil {
|
||||
logger.ErrorToConsole("could not start HTTP notification server: %v", err)
|
||||
os.Exit(1)
|
||||
|
@ -3815,6 +3825,94 @@ func TestEventRuleFsActions(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventActionHTTPMultipart(t *testing.T) {
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
Name: "action1",
|
||||
Type: dataprovider.ActionTypeHTTP,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
HTTPConfig: dataprovider.EventActionHTTPConfig{
|
||||
Endpoint: fmt.Sprintf("http://%s/multipart", httpAddr),
|
||||
Method: http.MethodPut,
|
||||
Parts: []dataprovider.HTTPPart{
|
||||
{
|
||||
Name: "part1",
|
||||
Headers: []dataprovider.KeyValue{
|
||||
{
|
||||
Key: "Content-Type",
|
||||
Value: "application/json",
|
||||
},
|
||||
},
|
||||
Body: `{"FilePath": "{{VirtualPath}}"}`,
|
||||
},
|
||||
{
|
||||
Name: "file",
|
||||
Filepath: "/{{VirtualPath}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
||||
assert.NoError(t, err, string(resp))
|
||||
r1 := dataprovider.EventRule{
|
||||
Name: "test http multipart",
|
||||
Trigger: dataprovider.EventTriggerFsEvent,
|
||||
Conditions: dataprovider.EventConditions{
|
||||
FsEvents: []string{"upload"},
|
||||
},
|
||||
Actions: []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action1.Name,
|
||||
},
|
||||
Options: dataprovider.EventActionOptions{
|
||||
ExecuteSync: true,
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
conn, client, err := getSftpClient(user)
|
||||
if assert.NoError(t, err) {
|
||||
defer conn.Close()
|
||||
defer client.Close()
|
||||
|
||||
f, err := client.Create(testFileName)
|
||||
assert.NoError(t, err)
|
||||
_, err = f.Write(testFileContent)
|
||||
assert.NoError(t, err)
|
||||
err = f.Close()
|
||||
assert.NoError(t, err)
|
||||
// now add an missing file to the http multipart action
|
||||
action1.Options.HTTPConfig.Parts = append(action1.Options.HTTPConfig.Parts, dataprovider.HTTPPart{
|
||||
Name: "file1",
|
||||
Filepath: "/missing",
|
||||
})
|
||||
_, resp, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
|
||||
assert.NoError(t, err, string(resp))
|
||||
|
||||
f, err = client.Create("testfile.txt")
|
||||
assert.NoError(t, err)
|
||||
_, err = f.Write(testFileContent)
|
||||
assert.NoError(t, err)
|
||||
err = f.Close()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventActionEmailAttachments(t *testing.T) {
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
package dataprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
@ -200,6 +201,39 @@ type KeyValue struct {
|
|||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (k *KeyValue) isNotValid() bool {
|
||||
return k.Key == "" || k.Value == ""
|
||||
}
|
||||
|
||||
// HTTPPart defines a part for HTTP multipart requests
|
||||
type HTTPPart struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Filepath string `json:"filepath,omitempty"`
|
||||
Headers []KeyValue `json:"headers,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Order int `json:"-"`
|
||||
}
|
||||
|
||||
func (p *HTTPPart) validate() error {
|
||||
if p.Name == "" {
|
||||
return util.NewValidationError("HTTP part name is required")
|
||||
}
|
||||
for _, kv := range p.Headers {
|
||||
if kv.isNotValid() {
|
||||
return util.NewValidationError("invalid HTTP part headers")
|
||||
}
|
||||
}
|
||||
if p.Filepath == "" {
|
||||
if p.Body == "" {
|
||||
return util.NewValidationError("HTTP part body is required if no file path is provided")
|
||||
}
|
||||
} else {
|
||||
p.Body = ""
|
||||
p.Filepath = util.CleanPath(p.Filepath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EventActionHTTPConfig defines the configuration for an HTTP event target
|
||||
type EventActionHTTPConfig struct {
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
|
@ -210,7 +244,34 @@ type EventActionHTTPConfig struct {
|
|||
SkipTLSVerify bool `json:"skip_tls_verify,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
QueryParameters []KeyValue `json:"query_parameters,omitempty"`
|
||||
Body string `json:"post_body,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Parts []HTTPPart `json:"parts,omitempty"`
|
||||
}
|
||||
|
||||
func (c *EventActionHTTPConfig) isTimeoutNotValid() bool {
|
||||
if c.HasMultipartFile() {
|
||||
return false
|
||||
}
|
||||
return c.Timeout < 1 || c.Timeout > 120
|
||||
}
|
||||
|
||||
func (c *EventActionHTTPConfig) validateMultiparts() error {
|
||||
for idx := range c.Parts {
|
||||
if err := c.Parts[idx].validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(c.Parts) > 0 {
|
||||
if c.Body != "" {
|
||||
return util.NewValidationError("multipart requests require no body. The request body is build from the specified parts")
|
||||
}
|
||||
for _, k := range c.Headers {
|
||||
if strings.ToLower(k.Key) == "content-type" {
|
||||
return util.NewValidationError("content type is automatically set for multipart requests")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *EventActionHTTPConfig) validate(additionalData string) error {
|
||||
|
@ -220,14 +281,17 @@ func (c *EventActionHTTPConfig) validate(additionalData string) error {
|
|||
if !util.IsStringPrefixInSlice(c.Endpoint, []string{"http://", "https://"}) {
|
||||
return util.NewValidationError("invalid HTTP endpoint schema: http and https are supported")
|
||||
}
|
||||
if c.Timeout < 1 || c.Timeout > 120 {
|
||||
if c.isTimeoutNotValid() {
|
||||
return util.NewValidationError(fmt.Sprintf("invalid HTTP timeout %d", c.Timeout))
|
||||
}
|
||||
for _, kv := range c.Headers {
|
||||
if kv.Key == "" || kv.Value == "" {
|
||||
if kv.isNotValid() {
|
||||
return util.NewValidationError("invalid HTTP headers")
|
||||
}
|
||||
}
|
||||
if err := c.validateMultiparts(); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.Password.IsRedacted() {
|
||||
return util.NewValidationError("cannot save HTTP configuration with a redacted secret")
|
||||
}
|
||||
|
@ -242,18 +306,57 @@ func (c *EventActionHTTPConfig) validate(additionalData string) error {
|
|||
return util.NewValidationError(fmt.Sprintf("unsupported HTTP method: %s", c.Method))
|
||||
}
|
||||
for _, kv := range c.QueryParameters {
|
||||
if kv.Key == "" || kv.Value == "" {
|
||||
if kv.isNotValid() {
|
||||
return util.NewValidationError("invalid HTTP query parameters")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetContext returns the context and the cancel func to use for the HTTP request
|
||||
func (c *EventActionHTTPConfig) GetContext() (context.Context, context.CancelFunc) {
|
||||
if c.HasMultipartFile() {
|
||||
return context.WithCancel(context.Background())
|
||||
}
|
||||
return context.WithTimeout(context.Background(), time.Duration(c.Timeout)*time.Second)
|
||||
}
|
||||
|
||||
// HasObjectData returns true if the {{ObjectData}} placeholder is defined
|
||||
func (c *EventActionHTTPConfig) HasObjectData() bool {
|
||||
if strings.Contains(c.Body, "{{ObjectData}}") {
|
||||
return true
|
||||
}
|
||||
for _, part := range c.Parts {
|
||||
if strings.Contains(part.Body, "{{ObjectData}}") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasMultipartFile returns true if a file must be uploaded via a multipart request
|
||||
func (c *EventActionHTTPConfig) HasMultipartFile() bool {
|
||||
for _, part := range c.Parts {
|
||||
if part.Filepath != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TryDecryptPassword decrypts the password if encryptet
|
||||
func (c *EventActionHTTPConfig) TryDecryptPassword() error {
|
||||
if c.Password != nil && !c.Password.IsEmpty() {
|
||||
if err := c.Password.TryDecrypt(); err != nil {
|
||||
return fmt.Errorf("unable to decrypt HTTP password: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHTTPClient returns an HTTP client based on the config
|
||||
func (c *EventActionHTTPConfig) GetHTTPClient() *http.Client {
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(c.Timeout) * time.Second,
|
||||
}
|
||||
client := &http.Client{}
|
||||
if c.SkipTLSVerify {
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
if transport.TLSClientConfig != nil {
|
||||
|
@ -288,7 +391,7 @@ func (c *EventActionCommandConfig) validate() error {
|
|||
return util.NewValidationError(fmt.Sprintf("invalid command action timeout %d", c.Timeout))
|
||||
}
|
||||
for _, kv := range c.EnvVars {
|
||||
if kv.Key == "" || kv.Value == "" {
|
||||
if kv.isNotValid() {
|
||||
return util.NewValidationError("invalid command env vars")
|
||||
}
|
||||
}
|
||||
|
@ -589,6 +692,15 @@ func (o *BaseEventActionOptions) getACopy() BaseEventActionOptions {
|
|||
IgnoreUserPermissions: folder.IgnoreUserPermissions,
|
||||
})
|
||||
}
|
||||
httpParts := make([]HTTPPart, 0, len(o.HTTPConfig.Parts))
|
||||
for _, part := range o.HTTPConfig.Parts {
|
||||
httpParts = append(httpParts, HTTPPart{
|
||||
Name: part.Name,
|
||||
Filepath: part.Filepath,
|
||||
Headers: cloneKeyValues(part.Headers),
|
||||
Body: part.Body,
|
||||
})
|
||||
}
|
||||
|
||||
return BaseEventActionOptions{
|
||||
HTTPConfig: EventActionHTTPConfig{
|
||||
|
@ -601,6 +713,7 @@ func (o *BaseEventActionOptions) getACopy() BaseEventActionOptions {
|
|||
Method: o.HTTPConfig.Method,
|
||||
QueryParameters: cloneKeyValues(o.HTTPConfig.QueryParameters),
|
||||
Body: o.HTTPConfig.Body,
|
||||
Parts: httpParts,
|
||||
},
|
||||
CmdConfig: EventActionCommandConfig{
|
||||
Cmd: o.CmdConfig.Cmd,
|
||||
|
@ -1171,6 +1284,11 @@ func (r *EventRule) CheckActionsConsistency(providerObjectType string) error {
|
|||
return errors.New("cannot send an email with attachments for a rule with no user associated")
|
||||
}
|
||||
}
|
||||
if action.Type == ActionTypeHTTP && action.BaseEventAction.Options.HTTPConfig.HasMultipartFile() {
|
||||
if !r.hasUserAssociated(providerObjectType) {
|
||||
return errors.New("cannot upload file/s for a rule with no user associated")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1490,7 +1490,7 @@ func TestEventActionValidation(t *testing.T) {
|
|||
Value: "application/json",
|
||||
},
|
||||
}
|
||||
action.Options.HTTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusRedacted, "paylod", "", "")
|
||||
action.Options.HTTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusRedacted, "payload", "", "")
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "cannot save HTTP configuration with a redacted secret")
|
||||
|
@ -1509,6 +1509,43 @@ func TestEventActionValidation(t *testing.T) {
|
|||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "invalid HTTP query parameters")
|
||||
action.Options.HTTPConfig.QueryParameters = nil
|
||||
action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
|
||||
{
|
||||
Name: "",
|
||||
},
|
||||
}
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "HTTP part name is required")
|
||||
action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
|
||||
{
|
||||
Name: "p1",
|
||||
},
|
||||
}
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "HTTP part body is required if no file path is provided")
|
||||
action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
|
||||
{
|
||||
Name: "p1",
|
||||
Filepath: "p",
|
||||
},
|
||||
}
|
||||
action.Options.HTTPConfig.Body = "b"
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "multipart requests require no body")
|
||||
action.Options.HTTPConfig.Body = ""
|
||||
action.Options.HTTPConfig.Headers = []dataprovider.KeyValue{
|
||||
{
|
||||
Key: "Content-Type",
|
||||
Value: "application/json",
|
||||
},
|
||||
}
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "content type is automatically set for multipart requests")
|
||||
|
||||
action.Type = dataprovider.ActionTypeCommand
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
|
@ -18899,8 +18936,23 @@ func TestWebEventAction(t *testing.T) {
|
|||
assert.NotEmpty(t, actionGet.Options.HTTPConfig.Password.GetPayload())
|
||||
assert.Empty(t, actionGet.Options.HTTPConfig.Password.GetKey())
|
||||
assert.Empty(t, actionGet.Options.HTTPConfig.Password.GetAdditionalData())
|
||||
// update and check that the password is preserved
|
||||
// update and check that the password is preserved and the multipart fields
|
||||
form.Set("http_password", redactedSecret)
|
||||
form.Set("http_body", "")
|
||||
form.Set("http_timeout", "0")
|
||||
form.Del("http_header_key0")
|
||||
form.Del("http_header_val0")
|
||||
form.Set("http_part_name0", "part1")
|
||||
form.Set("http_part_file0", "{{VirtualPath}}")
|
||||
form.Set("http_part_headers0", "X-MyHeader: a:b,c")
|
||||
form.Set("http_part_body0", "")
|
||||
form.Set("http_part_namea", "ignored")
|
||||
form.Set("http_part_filea", "{{VirtualPath}}")
|
||||
form.Set("http_part_headersa", "X-MyHeader: a:b,c")
|
||||
form.Set("http_part_bodya", "")
|
||||
form.Set("http_part_name12", "part2")
|
||||
form.Set("http_part_headers12", "Content-Type:application/json \r\n")
|
||||
form.Set("http_part_body12", "{{ObjectData}}")
|
||||
req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
|
||||
bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
|
@ -18913,6 +18965,20 @@ func TestWebEventAction(t *testing.T) {
|
|||
err = dbAction.Options.HTTPConfig.Password.Decrypt()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, defaultPassword, dbAction.Options.HTTPConfig.Password.GetPayload())
|
||||
assert.Empty(t, dbAction.Options.HTTPConfig.Body)
|
||||
assert.Equal(t, 0, dbAction.Options.HTTPConfig.Timeout)
|
||||
if assert.Len(t, dbAction.Options.HTTPConfig.Parts, 2) {
|
||||
assert.Equal(t, "part1", dbAction.Options.HTTPConfig.Parts[0].Name)
|
||||
assert.Equal(t, "/{{VirtualPath}}", dbAction.Options.HTTPConfig.Parts[0].Filepath)
|
||||
assert.Empty(t, dbAction.Options.HTTPConfig.Parts[0].Body)
|
||||
assert.Equal(t, "X-MyHeader", dbAction.Options.HTTPConfig.Parts[0].Headers[0].Key)
|
||||
assert.Equal(t, "a:b,c", dbAction.Options.HTTPConfig.Parts[0].Headers[0].Value)
|
||||
assert.Equal(t, "part2", dbAction.Options.HTTPConfig.Parts[1].Name)
|
||||
assert.Equal(t, "{{ObjectData}}", dbAction.Options.HTTPConfig.Parts[1].Body)
|
||||
assert.Empty(t, dbAction.Options.HTTPConfig.Parts[1].Filepath)
|
||||
assert.Equal(t, "Content-Type", dbAction.Options.HTTPConfig.Parts[1].Headers[0].Key)
|
||||
assert.Equal(t, "application/json", dbAction.Options.HTTPConfig.Parts[1].Headers[0].Value)
|
||||
}
|
||||
// change action type
|
||||
action.Type = dataprovider.ActionTypeCommand
|
||||
action.Options.CmdConfig = dataprovider.EventActionCommandConfig{
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -1877,6 +1878,46 @@ func getFoldersRetentionFromPostFields(r *http.Request) ([]dataprovider.FolderRe
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func getHTTPPartsFromPostFields(r *http.Request) []dataprovider.HTTPPart {
|
||||
var result []dataprovider.HTTPPart
|
||||
for k := range r.Form {
|
||||
if strings.HasPrefix(k, "http_part_name") {
|
||||
partName := r.Form.Get(k)
|
||||
if partName != "" {
|
||||
idx := strings.TrimPrefix(k, "http_part_name")
|
||||
order, err := strconv.Atoi(idx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
filePath := r.Form.Get(fmt.Sprintf("http_part_file%s", idx))
|
||||
body := r.Form.Get(fmt.Sprintf("http_part_body%s", idx))
|
||||
concatHeaders := getSliceFromDelimitedValues(r.Form.Get(fmt.Sprintf("http_part_headers%s", idx)), "\n")
|
||||
var headers []dataprovider.KeyValue
|
||||
for _, h := range concatHeaders {
|
||||
values := strings.SplitN(h, ":", 2)
|
||||
if len(values) > 1 {
|
||||
headers = append(headers, dataprovider.KeyValue{
|
||||
Key: strings.TrimSpace(values[0]),
|
||||
Value: strings.TrimSpace(values[1]),
|
||||
})
|
||||
}
|
||||
}
|
||||
result = append(result, dataprovider.HTTPPart{
|
||||
Name: partName,
|
||||
Filepath: filePath,
|
||||
Headers: headers,
|
||||
Body: body,
|
||||
Order: order,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Order < result[j].Order
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
func getEventActionOptionsFromPostFields(r *http.Request) (dataprovider.BaseEventActionOptions, error) {
|
||||
httpTimeout, err := strconv.Atoi(r.Form.Get("http_timeout"))
|
||||
if err != nil {
|
||||
|
@ -1913,6 +1954,7 @@ func getEventActionOptionsFromPostFields(r *http.Request) (dataprovider.BaseEven
|
|||
Method: r.Form.Get("http_method"),
|
||||
QueryParameters: getKeyValsFromPostFields(r, "http_query_key", "http_query_val"),
|
||||
Body: r.Form.Get("http_body"),
|
||||
Parts: getHTTPPartsFromPostFields(r),
|
||||
},
|
||||
CmdConfig: dataprovider.EventActionCommandConfig{
|
||||
Cmd: r.Form.Get("cmd_path"),
|
||||
|
|
|
@ -2208,6 +2208,27 @@ func compareKeyValues(expected, actual []dataprovider.KeyValue) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func compareHTTPparts(expected, actual []dataprovider.HTTPPart) error {
|
||||
for _, p1 := range expected {
|
||||
found := false
|
||||
for _, p2 := range actual {
|
||||
if p1.Name == p2.Name {
|
||||
found = true
|
||||
if err := compareKeyValues(p1.Headers, p2.Headers); err != nil {
|
||||
return fmt.Errorf("http headers mismatch for part %q", p1.Name)
|
||||
}
|
||||
if p1.Body != p2.Body || p1.Filepath != p2.Filepath {
|
||||
return fmt.Errorf("http part %q mismatch", p1.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("expected http part %q not found", p1.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareEventActionHTTPConfigFields(expected, actual dataprovider.EventActionHTTPConfig) error {
|
||||
if expected.Endpoint != actual.Endpoint {
|
||||
return errors.New("http endpoint mismatch")
|
||||
|
@ -2236,7 +2257,10 @@ func compareEventActionHTTPConfigFields(expected, actual dataprovider.EventActio
|
|||
if expected.Body != actual.Body {
|
||||
return errors.New("http body mismatch")
|
||||
}
|
||||
return nil
|
||||
if len(expected.Parts) != len(actual.Parts) {
|
||||
return errors.New("http parts mismatch")
|
||||
}
|
||||
return compareHTTPparts(expected.Parts, actual.Parts)
|
||||
}
|
||||
|
||||
func compareEventActionEmailConfigFields(expected, actual dataprovider.EventActionEmailConfig) error {
|
||||
|
|
|
@ -17,7 +17,7 @@ package version
|
|||
|
||||
import "strings"
|
||||
|
||||
const version = "2.3.3-dev"
|
||||
const version = "2.3.4-dev"
|
||||
|
||||
var (
|
||||
commit = ""
|
||||
|
|
|
@ -27,7 +27,7 @@ info:
|
|||
SFTPGo supports groups to simplify the administration of multiple accounts by letting you assign settings once to a group, instead of multiple times to each individual user.
|
||||
The SFTPGo WebClient allows end users to change their credentials, browse and manage their files in the browser and setup two-factor authentication which works with Authy, Google Authenticator and other compatible apps.
|
||||
From the WebClient each authorized user can also create HTTP/S links to externally share files and folders securely, by setting limits to the number of downloads/uploads, protecting the share with a password, limiting access by source IP address, setting an automatic expiration date.
|
||||
version: 2.3.3-dev
|
||||
version: 2.3.4-dev
|
||||
contact:
|
||||
name: API support
|
||||
url: 'https://github.com/drakkan/sftpgo'
|
||||
|
@ -6013,6 +6013,21 @@ components:
|
|||
type: string
|
||||
value:
|
||||
type: string
|
||||
HTTPPart:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
headers:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/KeyValue'
|
||||
description: 'Additional headers. Content-Disposition header is automatically set. Content-Type header is automatically detect for files to attach'
|
||||
filepath:
|
||||
type: string
|
||||
description: 'path to the file to be sent as an attachment'
|
||||
body:
|
||||
type: string
|
||||
EventActionHTTPConfig:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -6033,6 +6048,7 @@ components:
|
|||
type: integer
|
||||
minimum: 1
|
||||
maximum: 120
|
||||
description: 'Ignored for multipart requests with files as attachments'
|
||||
skip_tls_verify:
|
||||
type: boolean
|
||||
description: 'if enabled the HTTP client accepts any TLS certificate presented by the server and any host name in that certificate. In this mode, TLS is susceptible to man-in-the-middle attacks. This should be used only for testing.'
|
||||
|
@ -6049,6 +6065,11 @@ components:
|
|||
body:
|
||||
type: string
|
||||
description: HTTP POST/PUT body
|
||||
parts:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/HTTPPart'
|
||||
description: 'Multipart requests allow to combine one or more sets of data into a single body. For each part, you can set a file path or a body as text. Placeholders are supported in file path, body, header values.'
|
||||
EventActionCommandConfig:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>sftpgo</id>
|
||||
<version>2.3.3</version>
|
||||
<version>2.3.4</version>
|
||||
<packageSourceUrl>https://github.com/drakkan/sftpgo/tree/main/pkgs/choco</packageSourceUrl>
|
||||
<owners>asheroto</owners>
|
||||
<title>SFTPGo</title>
|
||||
<authors>Nicola Murino</authors>
|
||||
<projectUrl>https://github.com/drakkan/sftpgo</projectUrl>
|
||||
<iconUrl>https://cdn.statically.io/gh/drakkan/sftpgo/v2.3.3/static/img/logo.png</iconUrl>
|
||||
<iconUrl>https://cdn.statically.io/gh/drakkan/sftpgo/v2.3.4/static/img/logo.png</iconUrl>
|
||||
<licenseUrl>https://github.com/drakkan/sftpgo/blob/main/LICENSE</licenseUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<projectSourceUrl>https://github.com/drakkan/sftpgo</projectSourceUrl>
|
||||
<docsUrl>https://github.com/drakkan/sftpgo/tree/v2.3.3/docs</docsUrl>
|
||||
<docsUrl>https://github.com/drakkan/sftpgo/tree/v2.3.4/docs</docsUrl>
|
||||
<bugTrackerUrl>https://github.com/drakkan/sftpgo/issues</bugTrackerUrl>
|
||||
<tags>sftp sftp-server ftp webdav s3 azure-blob google-cloud-storage cloud-storage scp data-at-rest-encryption multi-factor-authentication multi-step-authentication</tags>
|
||||
<summary>Fully featured and highly configurable SFTP server with optional HTTP/S,FTP/S and WebDAV support.</summary>
|
||||
|
@ -32,7 +32,7 @@ You can find more info [here](https://github.com/drakkan/sftpgo).
|
|||
|
||||
* This package installs SFTPGo as Windows Service.
|
||||
* After the first installation please take a look at the [Getting Started Guide](https://github.com/drakkan/sftpgo/blob/main/docs/howto/getting-started.md).</description>
|
||||
<releaseNotes>https://github.com/drakkan/sftpgo/releases/tag/v2.3.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/drakkan/sftpgo/releases/tag/v2.3.4</releaseNotes>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="**" exclude="**\*.md;**\icon.png;**\icon.jpg;**\icon.svg" />
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$ErrorActionPreference = 'Stop'
|
||||
$packageName = 'sftpgo'
|
||||
$softwareName = 'SFTPGo'
|
||||
$url = 'https://github.com/drakkan/sftpgo/releases/download/v2.3.3/sftpgo_v2.3.3_windows_x86_64.exe'
|
||||
$checksum = '5A6E798BDD920D7DE6110C8478A993C9E5A691E48F74D86021DF4745CB8A5FDC'
|
||||
$url = 'https://github.com/drakkan/sftpgo/releases/download/v2.3.4/sftpgo_v2.3.4_windows_x86_64.exe'
|
||||
$checksum = '68428CECD98DB2F111BB5B1293CF7807BA8DA2CEFDD7F38ACDCF7B7D50C781DC'
|
||||
$silentArgs = '/VERYSILENT'
|
||||
$validExitCodes = @(0)
|
||||
|
||||
|
@ -45,8 +45,8 @@ Write-Output ""
|
|||
Write-Output "General information (README) location:"
|
||||
Write-Output "`thttps://github.com/drakkan/sftpgo"
|
||||
Write-Output "Getting start guide location:"
|
||||
Write-Output "`thttps://github.com/drakkan/sftpgo/blob/v2.3.3/docs/howto/getting-started.md"
|
||||
Write-Output "`thttps://github.com/drakkan/sftpgo/blob/v2.3.4/docs/howto/getting-started.md"
|
||||
Write-Output "Detailed information (docs folder) location:"
|
||||
Write-Output "`thttps://github.com/drakkan/sftpgo/tree/v2.3.3/docs"
|
||||
Write-Output "`thttps://github.com/drakkan/sftpgo/tree/v2.3.4/docs"
|
||||
Write-Output ""
|
||||
Write-Output "---------------------------"
|
|
@ -1,3 +1,9 @@
|
|||
sftpgo (2.3.4-1ppa1) bionic; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Nicola Murino <nicola.murino@gmail.com> Thu, 01 Sep 2022 16:56:20 +0200
|
||||
|
||||
sftpgo (2.3.3-1ppa1) bionic; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
|
|
@ -213,22 +213,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row action-type action-http">
|
||||
<label for="idHTTPBody" class="col-sm-2 col-form-label">Body</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" id="idHTTPBody" name="http_body" rows="4" placeholder=""
|
||||
aria-describedby="httpBodyHelpBlock">{{.Action.Options.HTTPConfig.Body}}</textarea>
|
||||
<small id="httpBodyHelpBlock" class="form-text text-muted">
|
||||
Placeholders are supported
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row action-type action-http">
|
||||
<label for="idHTTPTimeout" class="col-sm-2 col-form-label">Timeout</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" min="1" max="120" class="form-control" id="idHTTPTimeout" name="http_timeout" placeholder=""
|
||||
value="{{.Action.Options.HTTPConfig.Timeout}}">
|
||||
aria-describedby="httpTimeoutHelpBlock" value="{{.Action.Options.HTTPConfig.Timeout}}">
|
||||
<small id="httpTimeoutHelpBlock" class="form-text text-muted">
|
||||
Ignored for multipart requests with files as attachments.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -240,6 +232,100 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row action-type action-http">
|
||||
<label for="idHTTPBody" class="col-sm-2 col-form-label">Body</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" id="idHTTPBody" name="http_body" rows="4" placeholder=""
|
||||
aria-describedby="httpBodyHelpBlock">{{.Action.Options.HTTPConfig.Body}}</textarea>
|
||||
<small id="httpBodyHelpBlock" class="form-text text-muted">
|
||||
Placeholders are supported. Ignored for HTTP get requested. Leave empty for multipart requests.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3 action-type action-http">
|
||||
<div class="card-header">
|
||||
<b>Multipart body</b>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6 class="card-title mb-4">Multipart requests allow to combine one or more sets of data into a single body. For each part, you can set a file path or a body as text. Placeholders are supported in file path, body, header values.</h6>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12 form_field_http_part_outer">
|
||||
{{range $idx, $val := .Action.Options.HTTPConfig.Parts}}
|
||||
<div class="row form_field_http_part_outer_row">
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-2">
|
||||
<input type="text" class="form-control" id="idHTTPPartName{{$idx}}" name="http_part_name{{$idx}}" placeholder="Part name" value="{{$val.Name}}">
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<input type="text" class="form-control" id="idHTTPPartFile{{$idx}}" name="http_part_file{{$idx}}" placeholder="File path" value="{{$val.Filepath}}">
|
||||
</div>
|
||||
<div class="form-group col-md-5">
|
||||
<textarea class="form-control" id="idHTTPPartHeaders{{$idx}}" name="http_part_headers{{$idx}}" rows="2" placeholder="Additional part headers"
|
||||
aria-describedby="httpPartHeadersHelpBlock{{$idx}}">{{range $val.Headers}}{{.Key}}: {{.Value}}
{{end}}</textarea>
|
||||
<small id="httpPartHeadersHelpBlock{{$idx}}" class="form-text text-muted">
|
||||
One header per line as "key: value", example: "Content-Type: application/json", without quotes. Content type for files is automatically detected
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group col-md-1"></div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_http_part_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-10">
|
||||
<textarea class="form-control" id="idHTTPPartBody{{$idx}}" name="http_part_body{{$idx}}" rows="3" placeholder="Part body">{{$val.Body}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="row form_field_http_part_outer_row">
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-2">
|
||||
<input type="text" class="form-control" id="idHTTPPartName0" name="http_part_name0" placeholder="Part name" value="">
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<input type="text" class="form-control" id="idHTTPPartFile0" name="http_part_file0" placeholder="File path" value="">
|
||||
</div>
|
||||
<div class="form-group col-md-5">
|
||||
<textarea class="form-control" id="idHTTPPartHeaders0" name="http_part_headers0" rows="2" placeholder="Additional part headers"
|
||||
aria-describedby="httpPartHeadersHelpBlock0"></textarea>
|
||||
<small id="httpPartHeadersHelpBlock0" class="form-text text-muted">
|
||||
One header per line as "key: value", example: "Content-Type: application/json", without quotes. Content type for files is automatically detected
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group col-md-1"></div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_http_part_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-10">
|
||||
<textarea class="form-control" id="idHTTPPartBody0" name="http_part_body0" rows="3" placeholder="Part body"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mx-1">
|
||||
<button type="button" class="btn btn-secondary add_new_http_part_field_btn">
|
||||
<i class="fas fa-plus"></i> Add new HTTP part
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row action-type action-cmd">
|
||||
<label for="idCmdPath" class="col-sm-2 col-form-label">Command</label>
|
||||
<div class="col-sm-10">
|
||||
|
@ -745,6 +831,50 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
$(this).closest(".form_field_fs_rename_outer_row").remove();
|
||||
});
|
||||
|
||||
$("body").on("click", ".add_new_http_part_field_btn", function () {
|
||||
var index = $(".form_field_http_part_outer").find(".form_field_http_part_outer_row").length;
|
||||
while (document.getElementById("idHTTPPartName"+index) != null){
|
||||
index++;
|
||||
}
|
||||
$(".form_field_http_part_outer").append(`
|
||||
<div class="row form_field_http_part_outer_row">
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-2">
|
||||
<input type="text" class="form-control" id="idHTTPPartName${index}" name="http_part_name${index}" placeholder="Part name" value="">
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<input type="text" class="form-control" id="idHTTPPartFile${index}" name="http_part_file${index}" placeholder="File path" value="">
|
||||
</div>
|
||||
<div class="form-group col-md-5">
|
||||
<textarea class="form-control" id="idHTTPPartHeaders${index}" name="http_part_headers${index}" rows="2" placeholder="Additional part headers"
|
||||
aria-describedby="httpPartHeadersHelpBlock${index}"></textarea>
|
||||
<small id="httpPartHeadersHelpBlock${index}" class="form-text text-muted">
|
||||
One header per line as "key: value", example: "Content-Type: application/json", without quotes. Content type for files is automatically detected
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group col-md-1"></div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_http_part_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-10">
|
||||
<textarea class="form-control" id="idHTTPPartBody${index}" name="http_part_body${index}" rows="3" placeholder="Part body"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
$("body").on("click", ".remove_http_part_btn_frm_field", function () {
|
||||
$(this).closest(".form_field_http_part_outer_row").remove();
|
||||
});
|
||||
|
||||
function onTypeChanged(val){
|
||||
$('.action-type').hide();
|
||||
switch (val) {
|
||||
|
|
|
@ -455,7 +455,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
<label for="idSFTPFingerprints" class="col-sm-2 col-form-label">Fingerprints</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" id="idSFTPFingerprints" name="sftp_fingerprints" rows="3"
|
||||
aria-describedby="SFTPFingerprintsHelpBlock">{{range .SFTPConfig.Fingerprints}}{{.}} {{end}}</textarea>
|
||||
aria-describedby="SFTPFingerprintsHelpBlock">{{range .SFTPConfig.Fingerprints}}{{.}}
{{end}}</textarea>
|
||||
<small id="SFTPFingerprintsHelpBlock" class="form-text text-muted">
|
||||
SHA256 fingerprints to validate when connecting to the external SFTP server, one per line. If empty any host key will be accepted: this is a security risk!
|
||||
</small>
|
||||
|
|
Loading…
Reference in a new issue