http actions: add multipart support

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-09-03 16:29:07 +02:00
parent 3267a50ae3
commit c2a65a9a74
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
20 changed files with 897 additions and 154 deletions

View file

@ -423,6 +423,12 @@ jobs:
gzip output/man/man1/* gzip output/man/man1/*
cp sftpgo output/ 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 - uses: uraimo/run-on-arch-action@v2
if: ${{ matrix.arch != 'amd64' }} if: ${{ matrix.arch != 'amd64' }}
name: Build for ${{ matrix.arch }} name: Build for ${{ matrix.arch }}
@ -437,7 +443,7 @@ jobs:
shell: /bin/bash shell: /bin/bash
install: | install: |
apt-get update -q -y apt-get update -q -y
apt-get install -q -y curl gcc git apt-get install -q -y curl gcc
if [ ${{ matrix.go }} == 'latest' ] if [ ${{ matrix.go }} == 'latest' ]
then then
GO_VERSION=$(curl -L https://go.dev/VERSION?m=text) GO_VERSION=$(curl -L https://go.dev/VERSION?m=text)
@ -457,7 +463,7 @@ jobs:
then then
export GOARM=7 export GOARM=7
fi 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} mkdir -p output/{init,bash_completion,zsh_completion}
cp sftpgo.json output/ cp sftpgo.json output/
cp -r templates output/ cp -r templates output/

View file

@ -95,6 +95,13 @@ jobs:
fi fi
TAGS="${TAGS},${DOCKER_IMAGE}:distroless" TAGS="${TAGS},${DOCKER_IMAGE}:distroless"
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:distroless-slim" 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 else
if [[ -n $MAJOR && -n $MINOR ]]; then if [[ -n $MAJOR && -n $MINOR ]]; then
TAGS="${TAGS},${DOCKER_IMAGE}:${MINOR}-alpine,${DOCKER_IMAGE}:${MAJOR}-alpine" TAGS="${TAGS},${DOCKER_IMAGE}:${MINOR}-alpine,${DOCKER_IMAGE}:${MAJOR}-alpine"

View file

@ -326,6 +326,12 @@ jobs:
env: env:
SFTPGO_VERSION: ${{ steps.get_version.outputs.SFTPGO_VERSION }} 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 - uses: uraimo/run-on-arch-action@v2
if: ${{ matrix.arch != 'amd64' }} if: ${{ matrix.arch != 'amd64' }}
name: Build for ${{ matrix.arch }} name: Build for ${{ matrix.arch }}
@ -340,7 +346,7 @@ jobs:
shell: /bin/bash shell: /bin/bash
install: | install: |
apt-get update -q -y 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 }} GO_DOWNLOAD_ARCH=${{ matrix.go-arch }}
if [ ${{ matrix.arch}} == 'armv7' ] if [ ${{ matrix.arch}} == 'armv7' ]
then then
@ -350,7 +356,7 @@ jobs:
tar -C /usr/local -xzf go.tar.gz tar -C /usr/local -xzf go.tar.gz
run: | run: |
export PATH=$PATH:/usr/local/go/bin 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} mkdir -p output/{init,sqlite,bash_completion,zsh_completion}
echo "For documentation please take a look here:" > output/README.txt echo "For documentation please take a look here:" > output/README.txt
echo "" >> output/README.txt echo "" >> output/README.txt

View file

@ -4,11 +4,12 @@ SFTPGo provides an official Docker image, it is available on both [Docker Hub](h
## Supported tags and respective Dockerfile links ## 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.4, v2.3, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile)
- [v2.3.3-alpine, v2.3-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile.alpine) - [v2.3.4-plugins, v2.3-plugins, v2-plugins, plugins](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile)
- [v2.3.3-slim, v2.3-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile) - [v2.3.4-alpine, v2.3-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile.alpine)
- [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.4-slim, v2.3-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile)
- [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-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](../Dockerfile)
- [edge-plugins](../Dockerfile) - [edge-plugins](../Dockerfile)
- [edge-alpine](../Dockerfile.alpine) - [edge-alpine](../Dockerfile.alpine)

53
go.mod
View file

@ -4,21 +4,21 @@ go 1.19
require ( require (
cloud.google.com/go/storage v1.26.0 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/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 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 v1.16.14
github.com/aws/aws-sdk-go-v2/config v1.17.3 github.com/aws/aws-sdk-go-v2/config v1.17.5
github.com/aws/aws-sdk-go-v2/credentials v1.12.16 github.com/aws/aws-sdk-go-v2/credentials v1.12.18
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.13 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.29 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.31
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.15 github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.17
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.7 github.com/aws/aws-sdk-go-v2/service/s3 v1.27.9
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.20 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.22
github.com/aws/aws-sdk-go-v2/service/sts v1.16.15 github.com/aws/aws-sdk-go-v2/service/sts v1.16.17
github.com/cockroachdb/cockroach-go/v2 v2.2.15 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/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
github.com/fclairamb/ftpserverlib v0.19.1 github.com/fclairamb/ftpserverlib v0.19.1
github.com/fclairamb/go-log v0.4.1 github.com/fclairamb/go-log v0.4.1
@ -44,7 +44,7 @@ require (
github.com/minio/sio v0.3.0 github.com/minio/sio v0.3.0
github.com/otiai10/copy v1.7.0 github.com/otiai10/copy v1.7.0
github.com/pires/go-proxyproto v0.6.2 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/pquerna/otp v1.3.0
github.com/prometheus/client_golang v1.13.0 github.com/prometheus/client_golang v1.13.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
@ -52,7 +52,7 @@ require (
github.com/rs/xid v1.4.0 github.com/rs/xid v1.4.0
github.com/rs/zerolog v1.28.0 github.com/rs/zerolog v1.28.0
github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657 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/afero v1.9.2
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.5.0
github.com/spf13/viper v1.12.0 github.com/spf13/viper v1.12.0
@ -80,18 +80,18 @@ require (
cloud.google.com/go/iam v0.3.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // indirect
github.com/ajg/form v1.5.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/aws/protocol/eventstream v1.4.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.19 // 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.13 // 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.20 // 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.10 // 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.6 // 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.14 // 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.13 // 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.13 // 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.19 // 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.1 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3 // indirect
github.com/aws/smithy-go v1.13.0 // indirect github.com/aws/smithy-go v1.13.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // 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/tools v0.1.12 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/appengine v1.6.7 // 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/grpc v1.49.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
@ -167,7 +167,6 @@ require (
replace ( replace (
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 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/crypto => github.com/drakkan/crypto v0.0.0-20220831070132-e3c36f2ab82b
golang.org/x/net => github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5 golang.org/x/net => github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5
) )

107
go.sum
View file

@ -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 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 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 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.3 h1:8LoU8N2lIUzkmstvwXvVfniMZlFbesfT2AmA1aqvRr8=
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/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 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/azidentity v1.0.0 h1:Yoicul8bnVdQrhDMTHxdEckRGX01XvwXDHUT9zYZ3k0=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= 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.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 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.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.14 h1:db6GvO4Z2UqHt5gvT0lr6J5x5P+oQ7bdRzczVaRekMU=
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/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.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.7 h1:/kxQjtZc7j67TMW/aFJfpsrlvFhsq3lNbX41qN5Tro4=
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/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.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.5 h1:+NS1BWvprx7nHcIk5o32LrZgifs/7Pm1V2nWjQgZ2H0=
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/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.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.18 h1:HF62tbhARhgLfvmfwUbL9qZ+dkbZYzbFdxBb3l5gr7Q=
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/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.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.15 h1:nkQ+aI0OCeYfzrBipL6ja/6VEbUnHQoZHBHtoK+Nzxw=
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/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.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.31 h1:Ggf7rvFS1s3/Nauv2mokAY+RfKsCAHvfiiZJoYd0lV0=
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/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.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.21 h1:gRIXnmAVNyoRQywdNtpAkgY+f30QNzgF53Q5OobNZZs=
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/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.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.15 h1:noAhOo2mMDyYhTx99aYPvQw16T3fQ/DiKAv9fzpIKH8=
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/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.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.22 h1:nF+E8HfYpOMw6M5oA9efB602VC00IHNQnB5CmFvZPvA=
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/ini v1.3.22/go.mod h1:tltHVGy977LrSOgRR5aV9+miyno/Gul/uJNPKS7FzP4=
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.12 h1:i0Tig01XGhXo/ki1BZUbRMhusGVCScEvaWdlFRWxAKk=
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/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.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.8 h1:NpixDFjwr1BZg2459mX07NZnVYGGp62Lb6AtVGOLNlo=
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/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.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.16 h1:kHc3TqW5kJ9Vfd9YEwywrNrL87DItpvAohlP+OuzABY=
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/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.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.15 h1:xlf0J6DUgAj/ocvKQxCmad8Bu1lJuRbt5Wu+4G1xw1g=
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/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.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.15 h1:v9f7NY7D19ssE2EM+m9yT1m5zdWHuRAsZaFh24GAkOk=
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/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/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.17 h1:b8nlmU7/7j+Tujr7X4YcJ0hb0hqQ/IeXCt8/CjJVO4A=
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/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.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.9 h1:imVonvre+AHMcDc3B9bPHHy5ZgjIkkYc/jyDBK8FHFw=
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/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.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.22 h1:ggHTCgbIivTM85PFjv/rkJbchrmLSNL+Vcj5hg54TyM=
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/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/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/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/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.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.21 h1:7jUFr+7F4MzIjCZzy7ygRtXFQcQ0kAbT0gUvtUeAdyU=
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/sso v1.11.21/go.mod h1:q8nYq51W3gpZempYsAD83fPRlrOTMCwN+Ahg4BKFTXQ=
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.3 h1:UTTPNP3/WzZa7hoHP3Szb/Yl0bM3NoBrf5ABy1OArUM=
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/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.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.17 h1:LVM2jzEQ8mhb2dhrFl4PJ3sa5+KcKT01dsMk2Ma9/FU=
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/go.mod h1:bQujK1n0V1D1Gz5uII1jaB1WDvhj4/T3tElsJnVXCR0=
github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= 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.2 h1:TBLKyeJfXTrTXRHmsv4qWt9IQGYyWThLYaJWSahTOGE=
github.com/aws/smithy-go v1.13.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= 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/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 v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 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/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 h1:6TeTC1JLSlHJWJCswWZ7mQyT16kY5mQSs53C2coQISI=
github.com/cockroachdb/cockroach-go/v2 v2.2.15/go.mod h1:xZ2VHjUEb/cySv0scXBx7YsBnHtLHkR1+w/w73b5i3M= 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.3.0 h1:Y1LV3mP+QT3MEycATZpAiwfyN+uxZLqVbAHJUuOJEe4=
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/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-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 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= 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/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 h1:+sVMXrU1DiQLNDgz1KvybqHEzRf8KuX5xQW8fpii6rI=
github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 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 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4=
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001/go.mod h1:kltMsfRMTHSFdMbK66XdS8mfMW77+FZA1fGY1xYMF84= 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= 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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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= 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/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 h1:UXTpae6d+G/VI3sVITl+58SK0F3ZULn9dlEPMXcyNKY=
github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657/go.mod h1:PTp1TfXa+95wHw9yuZu7BA3vmzLqbRkz3gBmMNnwFQg= 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.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y=
github.com/shirou/gopsutil/v3 v3.22.7/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI= 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 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.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1/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-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-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-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-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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/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-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-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-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-20220902135211-223410557253 h1:vXJMM8Shg7TGaYxZsQ++A/FOSlbDmDtWhS/o+3w/hj4=
google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= 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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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/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 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 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 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 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= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -20,7 +20,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"mime"
"mime/multipart"
"net/http" "net/http"
"net/textproto"
"net/url" "net/url"
"os" "os"
"os/exec" "os/exec"
@ -50,6 +53,7 @@ const (
var ( var (
// eventManager handle the supported event rules actions // eventManager handle the supported event rules actions
eventManager eventRulesContainer eventManager eventRulesContainer
multipartQuoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
) )
func init() { func init() {
@ -455,7 +459,12 @@ func (p *EventParams) getStatusString() string {
// getUsers returns users with group settings not applied // getUsers returns users with group settings not applied
func (p *EventParams) getUsers() ([]dataprovider.User, error) { func (p *EventParams) getUsers() ([]dataprovider.User, error) {
if p.sender == "" { 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() user, err := p.getUserFromSender()
if err != nil { if err != nil {
@ -467,7 +476,8 @@ func (p *EventParams) getUsers() ([]dataprovider.User, error) {
func (p *EventParams) getUserFromSender() (dataprovider.User, error) { func (p *EventParams) getUserFromSender() (dataprovider.User, error) {
user, err := dataprovider.UserExists(p.sender) user, err := dataprovider.UserExists(p.sender)
if err != nil { 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 return user, nil
} }
@ -515,26 +525,45 @@ func (p *EventParams) getStringReplacements(addObjectData bool) []string {
return replacements 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) fs, fsPath, err := conn.GetFsAndResolvedPath(virtualPath)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
f, r, cancelFn, err := fs.Open(fsPath, 0) f, r, cancelFn, err := fs.Open(fsPath, 0)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if cancelFn == nil { if cancelFn == nil {
cancelFn = func() {} cancelFn = func() {}
} }
defer cancelFn()
var reader io.ReadCloser
if f != nil { if f != nil {
reader = f return f, cancelFn, nil
} else {
reader = r
} }
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() defer reader.Close()
data := make([]byte, expectedSize) data := make([]byte, expectedSize)
@ -632,19 +661,103 @@ func getHTTPRuleActionEndpoint(c dataprovider.EventActionHTTPConfig, replacer *s
return c.Endpoint, nil return c.Endpoint, nil
} }
func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventParams) error { func writeHTTPPart(m *multipart.Writer, part dataprovider.HTTPPart, h textproto.MIMEHeader,
if !c.Password.IsEmpty() { conn *BaseConnection, replacer *strings.Replacer,
if err := c.Password.TryDecrypt(); err != nil { ) error {
return fmt.Errorf("unable to decrypt password: %w", err) 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 addObjectData := false
if params.Object != nil { if params.Object != nil {
if !addObjectData { addObjectData = c.HasObjectData()
if strings.Contains(c.Body, "{{ObjectData}}") {
addObjectData = true
}
}
} }
replacements := params.getStringReplacements(addObjectData) replacements := params.getStringReplacements(addObjectData)
@ -654,16 +767,32 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa
return err return err
} }
var body io.Reader ctx, cancel := c.GetContext()
if c.Body != "" && c.Method != http.MethodGet { defer cancel()
body = bytes.NewBufferString(replaceWithReplacer(c.Body, replacer))
} var user dataprovider.User
req, err := http.NewRequest(c.Method, endpoint, body) if c.HasMultipartFile() {
user, err = params.getUserFromSender()
if err != nil { if err != nil {
return err return err
} }
}
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 != "" { 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 { for _, keyVal := range c.Headers {
req.Header.Set(keyVal.Key, replaceWithReplacer(keyVal.Value, replacer)) req.Header.Set(keyVal.Key, replaceWithReplacer(keyVal.Value, replacer))
@ -676,11 +805,11 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa
if err != nil { if err != nil {
eventManagerLog(logger.LevelDebug, "unable to send http notification, endpoint: %s, elapsed: %s, err: %v", eventManagerLog(logger.LevelDebug, "unable to send http notification, endpoint: %s, elapsed: %s, err: %v",
endpoint, time.Since(startTime), err) endpoint, time.Since(startTime), err)
return err return fmt.Errorf("error sending HTTP request: %w", err)
} }
defer resp.Body.Close() 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) endpoint, time.Since(startTime), resp.StatusCode)
if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusNoContent { if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusNoContent {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode) 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) { func getUserForEventAction(user dataprovider.User) (dataprovider.User, error) {
err := user.LoadAndApplyGroupSettings() err := user.LoadAndApplyGroupSettings()
if err != nil { 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.DisableFsChecks = false
user.Filters.FilePatterns = nil user.Filters.FilePatterns = nil
for k := range user.Permissions { for k := range user.Permissions {
user.Permissions[k] = []string{dataprovider.PermAny} user.Permissions[k] = []string{dataprovider.PermAny}
} }
return user, err return user, nil
} }
func executeDeleteFileFsAction(conn *BaseConnection, item string, info os.FileInfo) error { func executeDeleteFileFsAction(conn *BaseConnection, item string, info os.FileInfo) error {

View file

@ -17,6 +17,8 @@ package common
import ( import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"io"
"mime/multipart"
"net/http" "net/http"
"os" "os"
"path" "path"
@ -349,6 +351,26 @@ func TestEventManagerErrors(t *testing.T) {
}}, []string{"/a", "/b"}, nil) }}, []string{"/a", "/b"}, nil)
assert.Error(t, err) 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{ dataRetentionAction := dataprovider.BaseEventAction{
Type: dataprovider.ActionTypeDataRetentionCheck, Type: dataprovider.ActionTypeDataRetentionCheck,
Options: dataprovider.BaseEventActionOptions{ Options: dataprovider.BaseEventActionOptions{
@ -469,6 +491,9 @@ func TestEventRuleActions(t *testing.T) {
} }
err = executeRuleAction(action, params, dataprovider.ConditionOptions{}) err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
assert.NoError(t, err) 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) action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v/404", httpAddr)
err = executeRuleAction(action, params, dataprovider.ConditionOptions{}) err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
if assert.Error(t, err) { 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") action.Options.HTTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusSecretBox, "payload", "key", "data")
err = executeRuleAction(action, params, dataprovider.ConditionOptions{}) err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
if assert.Error(t, err) { 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 // test disk and transfer quota reset
username1 := "user1" username1 := "user1"
username2 := "user2" username2 := "user2"
@ -849,6 +888,12 @@ func TestEventRuleActions(t *testing.T) {
assert.Contains(t, err.Error(), "no folder quota reset executed") 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) err = os.RemoveAll(folder1.MappedPath)
assert.NoError(t, err) assert.NoError(t, err)
err = dataprovider.DeleteFolder(foldername1, "", "") err = dataprovider.DeleteFolder(foldername1, "", "")
@ -979,7 +1024,19 @@ func TestFilesystemActionErrors(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
_, err = getFileContent(NewBaseConnection("", protocolEventAction, "", "", user), "/f.txt", 1234) _, err = getFileContent(NewBaseConnection("", protocolEventAction, "", "", user), "/f.txt", 1234)
assert.Error(t, err) 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.FsConfig.Provider = sdk.LocalFilesystemProvider
user.Permissions["/"] = []string{dataprovider.PermUpload} user.Permissions["/"] = []string{dataprovider.PermUpload}
err = dataprovider.DeleteUser(username, "", "") err = dataprovider.DeleteUser(username, "", "")
@ -1276,3 +1333,34 @@ func TestEventParamsStatusFromError(t *testing.T) {
params.AddError(os.ErrNotExist) params.AddError(os.ErrNotExist)
assert.Equal(t, 2, params.Status) 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)
}

View file

@ -169,6 +169,16 @@ func TestMain(m *testing.M) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Not found\n") 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 { if err := http.ListenAndServe(httpAddr, nil); err != nil {
logger.ErrorToConsole("could not start HTTP notification server: %v", err) logger.ErrorToConsole("could not start HTTP notification server: %v", err)
os.Exit(1) os.Exit(1)
@ -3815,6 +3825,94 @@ func TestEventRuleFsActions(t *testing.T) {
assert.NoError(t, err) 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) { func TestEventActionEmailAttachments(t *testing.T) {
smtpCfg := smtp.Config{ smtpCfg := smtp.Config{
Host: "127.0.0.1", Host: "127.0.0.1",

View file

@ -15,6 +15,7 @@
package dataprovider package dataprovider
import ( import (
"context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
@ -200,6 +201,39 @@ type KeyValue struct {
Value string `json:"value"` 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 // EventActionHTTPConfig defines the configuration for an HTTP event target
type EventActionHTTPConfig struct { type EventActionHTTPConfig struct {
Endpoint string `json:"endpoint,omitempty"` Endpoint string `json:"endpoint,omitempty"`
@ -210,7 +244,34 @@ type EventActionHTTPConfig struct {
SkipTLSVerify bool `json:"skip_tls_verify,omitempty"` SkipTLSVerify bool `json:"skip_tls_verify,omitempty"`
Method string `json:"method,omitempty"` Method string `json:"method,omitempty"`
QueryParameters []KeyValue `json:"query_parameters,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 { 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://"}) { if !util.IsStringPrefixInSlice(c.Endpoint, []string{"http://", "https://"}) {
return util.NewValidationError("invalid HTTP endpoint schema: http and https are supported") 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)) return util.NewValidationError(fmt.Sprintf("invalid HTTP timeout %d", c.Timeout))
} }
for _, kv := range c.Headers { for _, kv := range c.Headers {
if kv.Key == "" || kv.Value == "" { if kv.isNotValid() {
return util.NewValidationError("invalid HTTP headers") return util.NewValidationError("invalid HTTP headers")
} }
} }
if err := c.validateMultiparts(); err != nil {
return err
}
if c.Password.IsRedacted() { if c.Password.IsRedacted() {
return util.NewValidationError("cannot save HTTP configuration with a redacted secret") 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)) return util.NewValidationError(fmt.Sprintf("unsupported HTTP method: %s", c.Method))
} }
for _, kv := range c.QueryParameters { for _, kv := range c.QueryParameters {
if kv.Key == "" || kv.Value == "" { if kv.isNotValid() {
return util.NewValidationError("invalid HTTP query parameters") return util.NewValidationError("invalid HTTP query parameters")
} }
} }
return nil 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 // GetHTTPClient returns an HTTP client based on the config
func (c *EventActionHTTPConfig) GetHTTPClient() *http.Client { func (c *EventActionHTTPConfig) GetHTTPClient() *http.Client {
client := &http.Client{ client := &http.Client{}
Timeout: time.Duration(c.Timeout) * time.Second,
}
if c.SkipTLSVerify { if c.SkipTLSVerify {
transport := http.DefaultTransport.(*http.Transport).Clone() transport := http.DefaultTransport.(*http.Transport).Clone()
if transport.TLSClientConfig != nil { 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)) return util.NewValidationError(fmt.Sprintf("invalid command action timeout %d", c.Timeout))
} }
for _, kv := range c.EnvVars { for _, kv := range c.EnvVars {
if kv.Key == "" || kv.Value == "" { if kv.isNotValid() {
return util.NewValidationError("invalid command env vars") return util.NewValidationError("invalid command env vars")
} }
} }
@ -589,6 +692,15 @@ func (o *BaseEventActionOptions) getACopy() BaseEventActionOptions {
IgnoreUserPermissions: folder.IgnoreUserPermissions, 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{ return BaseEventActionOptions{
HTTPConfig: EventActionHTTPConfig{ HTTPConfig: EventActionHTTPConfig{
@ -601,6 +713,7 @@ func (o *BaseEventActionOptions) getACopy() BaseEventActionOptions {
Method: o.HTTPConfig.Method, Method: o.HTTPConfig.Method,
QueryParameters: cloneKeyValues(o.HTTPConfig.QueryParameters), QueryParameters: cloneKeyValues(o.HTTPConfig.QueryParameters),
Body: o.HTTPConfig.Body, Body: o.HTTPConfig.Body,
Parts: httpParts,
}, },
CmdConfig: EventActionCommandConfig{ CmdConfig: EventActionCommandConfig{
Cmd: o.CmdConfig.Cmd, 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") 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 return nil
} }

View file

@ -1490,7 +1490,7 @@ func TestEventActionValidation(t *testing.T) {
Value: "application/json", 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) _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, string(resp), "cannot save HTTP configuration with a redacted secret") 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) _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, string(resp), "invalid HTTP query parameters") 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 action.Type = dataprovider.ActionTypeCommand
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest) _, 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.NotEmpty(t, actionGet.Options.HTTPConfig.Password.GetPayload())
assert.Empty(t, actionGet.Options.HTTPConfig.Password.GetKey()) assert.Empty(t, actionGet.Options.HTTPConfig.Password.GetKey())
assert.Empty(t, actionGet.Options.HTTPConfig.Password.GetAdditionalData()) 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_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), req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
bytes.NewBuffer([]byte(form.Encode()))) bytes.NewBuffer([]byte(form.Encode())))
assert.NoError(t, err) assert.NoError(t, err)
@ -18913,6 +18965,20 @@ func TestWebEventAction(t *testing.T) {
err = dbAction.Options.HTTPConfig.Password.Decrypt() err = dbAction.Options.HTTPConfig.Password.Decrypt()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, defaultPassword, dbAction.Options.HTTPConfig.Password.GetPayload()) 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 // change action type
action.Type = dataprovider.ActionTypeCommand action.Type = dataprovider.ActionTypeCommand
action.Options.CmdConfig = dataprovider.EventActionCommandConfig{ action.Options.CmdConfig = dataprovider.EventActionCommandConfig{

View file

@ -23,6 +23,7 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -1877,6 +1878,46 @@ func getFoldersRetentionFromPostFields(r *http.Request) ([]dataprovider.FolderRe
return res, nil 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) { func getEventActionOptionsFromPostFields(r *http.Request) (dataprovider.BaseEventActionOptions, error) {
httpTimeout, err := strconv.Atoi(r.Form.Get("http_timeout")) httpTimeout, err := strconv.Atoi(r.Form.Get("http_timeout"))
if err != nil { if err != nil {
@ -1913,6 +1954,7 @@ func getEventActionOptionsFromPostFields(r *http.Request) (dataprovider.BaseEven
Method: r.Form.Get("http_method"), Method: r.Form.Get("http_method"),
QueryParameters: getKeyValsFromPostFields(r, "http_query_key", "http_query_val"), QueryParameters: getKeyValsFromPostFields(r, "http_query_key", "http_query_val"),
Body: r.Form.Get("http_body"), Body: r.Form.Get("http_body"),
Parts: getHTTPPartsFromPostFields(r),
}, },
CmdConfig: dataprovider.EventActionCommandConfig{ CmdConfig: dataprovider.EventActionCommandConfig{
Cmd: r.Form.Get("cmd_path"), Cmd: r.Form.Get("cmd_path"),

View file

@ -2208,6 +2208,27 @@ func compareKeyValues(expected, actual []dataprovider.KeyValue) error {
return nil 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 { func compareEventActionHTTPConfigFields(expected, actual dataprovider.EventActionHTTPConfig) error {
if expected.Endpoint != actual.Endpoint { if expected.Endpoint != actual.Endpoint {
return errors.New("http endpoint mismatch") return errors.New("http endpoint mismatch")
@ -2236,7 +2257,10 @@ func compareEventActionHTTPConfigFields(expected, actual dataprovider.EventActio
if expected.Body != actual.Body { if expected.Body != actual.Body {
return errors.New("http body mismatch") 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 { func compareEventActionEmailConfigFields(expected, actual dataprovider.EventActionEmailConfig) error {

View file

@ -17,7 +17,7 @@ package version
import "strings" import "strings"
const version = "2.3.3-dev" const version = "2.3.4-dev"
var ( var (
commit = "" commit = ""

View file

@ -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. 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. 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. 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: contact:
name: API support name: API support
url: 'https://github.com/drakkan/sftpgo' url: 'https://github.com/drakkan/sftpgo'
@ -6013,6 +6013,21 @@ components:
type: string type: string
value: value:
type: string 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: EventActionHTTPConfig:
type: object type: object
properties: properties:
@ -6033,6 +6048,7 @@ components:
type: integer type: integer
minimum: 1 minimum: 1
maximum: 120 maximum: 120
description: 'Ignored for multipart requests with files as attachments'
skip_tls_verify: skip_tls_verify:
type: boolean 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.' 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: body:
type: string type: string
description: HTTP POST/PUT body 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: EventActionCommandConfig:
type: object type: object
properties: properties:

View file

@ -3,17 +3,17 @@
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata> <metadata>
<id>sftpgo</id> <id>sftpgo</id>
<version>2.3.3</version> <version>2.3.4</version>
<packageSourceUrl>https://github.com/drakkan/sftpgo/tree/main/pkgs/choco</packageSourceUrl> <packageSourceUrl>https://github.com/drakkan/sftpgo/tree/main/pkgs/choco</packageSourceUrl>
<owners>asheroto</owners> <owners>asheroto</owners>
<title>SFTPGo</title> <title>SFTPGo</title>
<authors>Nicola Murino</authors> <authors>Nicola Murino</authors>
<projectUrl>https://github.com/drakkan/sftpgo</projectUrl> <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> <licenseUrl>https://github.com/drakkan/sftpgo/blob/main/LICENSE</licenseUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/drakkan/sftpgo</projectSourceUrl> <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> <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> <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> <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. * 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> * 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> </metadata>
<files> <files>
<file src="**" exclude="**\*.md;**\icon.png;**\icon.jpg;**\icon.svg" /> <file src="**" exclude="**\*.md;**\icon.png;**\icon.jpg;**\icon.svg" />

View file

@ -1,8 +1,8 @@
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$packageName = 'sftpgo' $packageName = 'sftpgo'
$softwareName = 'SFTPGo' $softwareName = 'SFTPGo'
$url = 'https://github.com/drakkan/sftpgo/releases/download/v2.3.3/sftpgo_v2.3.3_windows_x86_64.exe' $url = 'https://github.com/drakkan/sftpgo/releases/download/v2.3.4/sftpgo_v2.3.4_windows_x86_64.exe'
$checksum = '5A6E798BDD920D7DE6110C8478A993C9E5A691E48F74D86021DF4745CB8A5FDC' $checksum = '68428CECD98DB2F111BB5B1293CF7807BA8DA2CEFDD7F38ACDCF7B7D50C781DC'
$silentArgs = '/VERYSILENT' $silentArgs = '/VERYSILENT'
$validExitCodes = @(0) $validExitCodes = @(0)
@ -45,8 +45,8 @@ Write-Output ""
Write-Output "General information (README) location:" Write-Output "General information (README) location:"
Write-Output "`thttps://github.com/drakkan/sftpgo" Write-Output "`thttps://github.com/drakkan/sftpgo"
Write-Output "Getting start guide location:" 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 "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 ""
Write-Output "---------------------------" Write-Output "---------------------------"

View file

@ -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 sftpgo (2.3.3-1ppa1) bionic; urgency=medium
* New upstream release * New upstream release

View file

@ -213,22 +213,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div> </div>
</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"> <div class="form-group row action-type action-http">
<label for="idHTTPTimeout" class="col-sm-2 col-form-label">Timeout</label> <label for="idHTTPTimeout" class="col-sm-2 col-form-label">Timeout</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" min="1" max="120" class="form-control" id="idHTTPTimeout" name="http_timeout" placeholder="" <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>
</div> </div>
@ -240,6 +232,100 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div> </div>
</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}}&#010;{{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"> <div class="form-group row action-type action-cmd">
<label for="idCmdPath" class="col-sm-2 col-form-label">Command</label> <label for="idCmdPath" class="col-sm-2 col-form-label">Command</label>
<div class="col-sm-10"> <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(); $(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){ function onTypeChanged(val){
$('.action-type').hide(); $('.action-type').hide();
switch (val) { switch (val) {

View file

@ -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> <label for="idSFTPFingerprints" class="col-sm-2 col-form-label">Fingerprints</label>
<div class="col-sm-10"> <div class="col-sm-10">
<textarea class="form-control" id="idSFTPFingerprints" name="sftp_fingerprints" rows="3" <textarea class="form-control" id="idSFTPFingerprints" name="sftp_fingerprints" rows="3"
aria-describedby="SFTPFingerprintsHelpBlock">{{range .SFTPConfig.Fingerprints}}{{.}}&#10;{{end}}</textarea> aria-describedby="SFTPFingerprintsHelpBlock">{{range .SFTPConfig.Fingerprints}}{{.}}&#010;{{end}}</textarea>
<small id="SFTPFingerprintsHelpBlock" class="form-text text-muted"> <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! 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> </small>