Compare commits
25 Commits
multiple-f
...
v1.3.1
Author | SHA1 | Date | |
---|---|---|---|
|
acd6fb084f | ||
|
6f49951bc0 | ||
|
d2a0e77814 | ||
|
014b95ff07 | ||
|
0eec27586d | ||
|
c7164856d2 | ||
|
96723b2685 | ||
|
4a56bad05f | ||
|
fa74be02d2 | ||
|
2959fc2992 | ||
|
694b3ce246 | ||
|
92948fab23 | ||
|
e5455d9713 | ||
|
6ab75b30e5 | ||
|
6b8eff8322 | ||
|
2da62eb235 | ||
|
156daa5a24 | ||
|
e97fdcb293 | ||
|
9fe5f9a5c9 | ||
|
788dfa203f | ||
|
440ccf3a57 | ||
|
224f8dca42 | ||
|
d16a6cc25e | ||
|
49a083d8a2 | ||
|
f38eb6fb77 |
33
.github/build/friendly-filenames.json
vendored
Normal file
33
.github/build/friendly-filenames.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"android-arm64": { "friendlyName": "android-arm64-v8a" },
|
||||
"darwin-amd64": { "friendlyName": "darwin-amd64" },
|
||||
"darwin-arm64": { "friendlyName": "darwin-arm64" },
|
||||
"dragonfly-amd64": { "friendlyName": "dragonfly-amd64" },
|
||||
"freebsd-386": { "friendlyName": "freebsd-386" },
|
||||
"freebsd-amd64": { "friendlyName": "freebsd-amd64" },
|
||||
"freebsd-arm64": { "friendlyName": "freebsd-arm64-v8a" },
|
||||
"freebsd-arm7": { "friendlyName": "freebsd-arm32-v7a" },
|
||||
"linux-386": { "friendlyName": "linux-386" },
|
||||
"linux-amd64": { "friendlyName": "linux-amd64" },
|
||||
"linux-arm5": { "friendlyName": "linux-arm32-v5" },
|
||||
"linux-arm64": { "friendlyName": "linux-arm64-v8a" },
|
||||
"linux-arm6": { "friendlyName": "linux-arm32-v6" },
|
||||
"linux-arm7": { "friendlyName": "linux-armv7" },
|
||||
"linux-mips64le": { "friendlyName": "linux-mips64le" },
|
||||
"linux-mips64": { "friendlyName": "linux-mips64" },
|
||||
"linux-mipslesoftfloat": { "friendlyName": "linux-mips32le-softfloat" },
|
||||
"linux-mipsle": { "friendlyName": "linux-mips32le" },
|
||||
"linux-mipssoftfloat": { "friendlyName": "linux-mips32-softfloat" },
|
||||
"linux-mips": { "friendlyName": "linux-mips32" },
|
||||
"linux-ppc64le": { "friendlyName": "linux-ppc64le" },
|
||||
"linux-ppc64": { "friendlyName": "linux-ppc64" },
|
||||
"linux-riscv64": { "friendlyName": "linux-riscv64" },
|
||||
"linux-s390x": { "friendlyName": "linux-s390x" },
|
||||
"openbsd-386": { "friendlyName": "openbsd-386" },
|
||||
"openbsd-amd64": { "friendlyName": "openbsd-amd64" },
|
||||
"openbsd-arm64": { "friendlyName": "openbsd-arm64-v8a" },
|
||||
"openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" },
|
||||
"windows-386": { "friendlyName": "windows-386" },
|
||||
"windows-amd64": { "friendlyName": "windows-amd64" },
|
||||
"windows-arm7": { "friendlyName": "windows-arm32-v7a" }
|
||||
}
|
4
.github/workflows/build-docker-images.yml
vendored
4
.github/workflows/build-docker-images.yml
vendored
@@ -4,9 +4,9 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # everyday at midnight UTC
|
||||
pull_request:
|
||||
branches: master
|
||||
branches: main
|
||||
push:
|
||||
branches: master
|
||||
branches: main
|
||||
tags:
|
||||
- v*
|
||||
|
||||
|
202
.github/workflows/release.yml
vendored
202
.github/workflows/release.yml
vendored
@@ -1,59 +1,171 @@
|
||||
name: release
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
GOOS: [ darwin, linux, windows ]
|
||||
GOARCH: [ amd64 ]
|
||||
# Include amd64 on all platforms.
|
||||
goos: [windows, freebsd, openbsd, linux, dragonfly, darwin]
|
||||
goarch: [amd64, 386]
|
||||
exclude:
|
||||
# Exclude i386 on darwin and dragonfly.
|
||||
- goarch: 386
|
||||
goos: dragonfly
|
||||
- goarch: 386
|
||||
goos: darwin
|
||||
include:
|
||||
- GOOS: darwin
|
||||
GOARCH: amd64
|
||||
suffix: darwin-amd64
|
||||
- GOOS: windows
|
||||
GOARCH: amd64
|
||||
suffix: windows-amd64.exe
|
||||
- GOOS: linux
|
||||
GOARCH: amd64
|
||||
suffix: linux-amd64
|
||||
- GOOS: linux
|
||||
GOARCH: arm
|
||||
suffix: linux-armv7
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16.x
|
||||
- name: Build ${{ matrix.suffix }} (GOOS=${{ matrix.GOOS }}, GOARCH=${{ matrix.GOARCH }})
|
||||
env:
|
||||
GOOS: ${{ matrix.GOOS }}
|
||||
GOARCH: ${{ matrix.GOARCH }}
|
||||
CGO_ENABLED: 0
|
||||
run: |
|
||||
go version
|
||||
go build -tags netgo -ldflags "-X github.com/dutchcoders/transfer.sh/cmd.Version=${GITHUB_REF##*/} -a -s -w -extldflags '-static'" -o ./artifacts/transfersh-${GITHUB_REF##*/}-${{ matrix.suffix }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
name: Upload artifacts
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./artifacts
|
||||
# BEIGIN MacOS ARM64
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
# END MacOS ARM64
|
||||
# BEGIN Linux ARM 5 6 7
|
||||
- goos: linux
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
- goos: linux
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
- goos: linux
|
||||
goarch: arm
|
||||
goarm: 5
|
||||
# END Linux ARM 5 6 7
|
||||
# BEGIN Android ARM 8
|
||||
- goos: android
|
||||
goarch: arm64
|
||||
# END Android ARM 8
|
||||
# Windows ARM 7
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
# BEGIN Other architectures
|
||||
# BEGIN riscv64 & ARM64
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
- goos: linux
|
||||
goarch: riscv64
|
||||
# END riscv64 & ARM64
|
||||
# BEGIN MIPS
|
||||
- goos: linux
|
||||
goarch: mips64
|
||||
- goos: linux
|
||||
goarch: mips64le
|
||||
- goos: linux
|
||||
goarch: mipsle
|
||||
- goos: linux
|
||||
goarch: mips
|
||||
# END MIPS
|
||||
# BEGIN PPC
|
||||
- goos: linux
|
||||
goarch: ppc64
|
||||
- goos: linux
|
||||
goarch: ppc64le
|
||||
# END PPC
|
||||
# BEGIN FreeBSD ARM
|
||||
- goos: freebsd
|
||||
goarch: arm64
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
# END FreeBSD ARM
|
||||
# BEGIN S390X
|
||||
- goos: linux
|
||||
goarch: s390x
|
||||
# END S390X
|
||||
# END Other architectures
|
||||
# BEGIN OPENBSD ARM
|
||||
- goos: openbsd
|
||||
goarch: arm64
|
||||
- goos: openbsd
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
# END OPENBSD ARM
|
||||
fail-fast: false
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ build ]
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- uses: actions/download-artifact@v2
|
||||
name: Download artifacts
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Show workflow information
|
||||
id: get_filename
|
||||
run: |
|
||||
export _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
|
||||
echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME"
|
||||
echo "::set-output name=ASSET_NAME::$_NAME"
|
||||
echo "::set-output name=GIT_TAG::${GITHUB_REF##*/}"
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./artifacts
|
||||
- name: Publish artifacts
|
||||
go-version: ^1.17
|
||||
|
||||
- name: Get project dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Build Transfersh
|
||||
run: |
|
||||
mkdir -p build_assets
|
||||
go build -tags netgo -ldflags "-X github.com/dutchcoders/transfer.sh/cmd.Version=${GITHUB_REF##*/} -a -s -w -extldflags '-static'" -o build_assets/transfersh-${GITHUB_REF##*/}-${ASSET_NAME}
|
||||
|
||||
- name: Build Mips softfloat Transfersh
|
||||
if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
|
||||
run: |
|
||||
GOMIPS=softfloat go build -tags netgo -ldflags "-X github.com/dutchcoders/transfer.sh/cmd.Version=${GITHUB_REF##*/} -a -s -w -extldflags '-static'" -o build_assets/transfersh-softfloat-${GITHUB_REF##*/}-${ASSET_NAME}
|
||||
|
||||
- name: Rename Windows Transfersh
|
||||
if: matrix.goos == 'windows'
|
||||
run: |
|
||||
cd ./build_assets || exit 1
|
||||
mv transfersh-${GITHUB_REF##*/}-${ASSET_NAME} transfersh-${GITHUB_REF##*/}-${ASSET_NAME}.exe
|
||||
|
||||
- name: Prepare to release
|
||||
run: |
|
||||
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
||||
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
||||
|
||||
- name: Create Gzip archive
|
||||
shell: bash
|
||||
run: |
|
||||
pushd build_assets || exit 1
|
||||
touch -mt $(date +%Y01010000) *
|
||||
tar zcvf transfersh-${GITHUB_REF##*/}-${ASSET_NAME}.tar.gz *
|
||||
mv transfersh-${GITHUB_REF##*/}-${ASSET_NAME}.tar.gz ../
|
||||
FILE=`find . -name "transfersh-${GITHUB_REF##*/}-${ASSET_NAME}*"`
|
||||
DGST=$FILE.sha256sum
|
||||
echo `sha256sum $FILE` > $DGST
|
||||
popd || exit 1
|
||||
FILE=./transfersh-${GITHUB_REF##*/}-${ASSET_NAME}.tar.gz
|
||||
DGST=$FILE.sha256sum
|
||||
echo `sha256sum $FILE` > $DGST
|
||||
|
||||
- name: Change the name
|
||||
run: |
|
||||
mv build_assets transfersh-${GITHUB_REF##*/}-${ASSET_NAME}
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: transfersh-${{ steps.get_filename.outputs.GIT_TAG }}-${{ steps.get_filename.outputs.ASSET_NAME }}
|
||||
path: |
|
||||
./transfersh-${{ steps.get_filename.outputs.GIT_TAG }}-${{ steps.get_filename.outputs.ASSET_NAME }}/*
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
files: './artifacts/*'
|
||||
files: |
|
||||
./transfersh-${{ steps.get_filename.outputs.GIT_TAG }}-${{ steps.get_filename.outputs.ASSET_NAME }}.tar.gz*
|
||||
./transfersh-${{ steps.get_filename.outputs.GIT_TAG }}-${{ steps.get_filename.outputs.ASSET_NAME }}/transfersh-${{ steps.get_filename.outputs.GIT_TAG }}-${{ steps.get_filename.outputs.ASSET_NAME }}*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@@ -17,6 +17,7 @@ jobs:
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
- 1.16.x
|
||||
- 1.17.x
|
||||
name: Test with ${{ matrix.go_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -20,3 +20,5 @@ transfersh-server/run.sh
|
||||
.elasticbeanstalk/*
|
||||
!.elasticbeanstalk/*.cfg.yml
|
||||
!.elasticbeanstalk/*.global.yml
|
||||
|
||||
!.github/build/
|
||||
|
@@ -13,6 +13,7 @@ Examples of unacceptable behavior by participants include:
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
* Use of harsh language
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
|
||||
|
||||
@@ -20,5 +21,5 @@ This code of conduct applies both within project spaces and in public spaces whe
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.2.0, available at https://www.contributor-covenant.org/version/1/2/0/code-of-conduct.html
|
||||
This Code of Conduct is adapted from the [Contributor Covenant] (https://www.contributor-covenant.org), version 1.2.0, available at https://www.contributor-covenant.org/version/1/2/0/code-of-conduct.html
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Default to Go 1.16
|
||||
ARG GO_VERSION=1.16
|
||||
# Default to Go 1.17
|
||||
ARG GO_VERSION=1.17
|
||||
FROM golang:${GO_VERSION}-alpine as build
|
||||
|
||||
# Necessary to run 'go get' and to compile the linked binary
|
||||
|
30
README.md
30
README.md
@@ -1,4 +1,4 @@
|
||||
# transfer.sh [](https://gitter.im/dutchcoders/transfer.sh?utm_source=badge&utm_medium=badge&utm_campaign=&utm_campaign=pr-badge&utm_content=badge) [](https://goreportcard.com/report/github.com/dutchcoders/transfer.sh) [](https://hub.docker.com/r/dutchcoders/transfer.sh/) [](https://github.com/dutchcoders/transfer.sh/actions/workflows/test.yml?query=branch%3Amaster)
|
||||
# transfer.sh [](https://goreportcard.com/report/github.com/dutchcoders/transfer.sh) [](https://hub.docker.com/r/dutchcoders/transfer.sh/) [](https://github.com/dutchcoders/transfer.sh/actions/workflows/test.yml?query=branch%3Amain)
|
||||
|
||||
Easy and fast file sharing from the command-line. This code contains the server with everything you need to create your own instance.
|
||||
|
||||
@@ -15,17 +15,17 @@ The service at transfersh.com is of unknown origin and reported as cloud malware
|
||||
$ curl --upload-file ./hello.txt https://transfer.sh/hello.txt
|
||||
```
|
||||
|
||||
### Encrypt & upload:
|
||||
### Encrypt & Upload:
|
||||
```bash
|
||||
$ cat /tmp/hello.txt|gpg -ac -o-|curl -X PUT --upload-file "-" https://transfer.sh/test.txt
|
||||
````
|
||||
|
||||
### Download & decrypt:
|
||||
### Download & Decrypt:
|
||||
```bash
|
||||
$ curl https://transfer.sh/1lDau/test.txt|gpg -o- > /tmp/hello.txt
|
||||
```
|
||||
|
||||
### Upload to virustotal:
|
||||
### Upload to Virustotal:
|
||||
```bash
|
||||
$ curl -X PUT --upload-file nhgbhhj https://transfer.sh/test.txt/virustotal
|
||||
```
|
||||
@@ -51,7 +51,7 @@ $ curl --upload-file ./hello.txt https://transfer.sh/hello.txt -H "Max-Days: 1"
|
||||
|
||||
### X-Url-Delete
|
||||
|
||||
The URL used to request the deletion of a file. Returned as a response header.
|
||||
The URL used to request the deletion of a file and returned as a response header.
|
||||
```bash
|
||||
curl -sD - --upload-file ./hello https://transfer.sh/hello.txt | grep 'X-Url-Delete'
|
||||
X-Url-Delete: https://transfer.sh/hello.txt/BAYh0/hello.txt/PDw0NHPcqU
|
||||
@@ -90,6 +90,7 @@ temp-path | path to temp folder | system temp | TEMP_PATH |
|
||||
web-path | path to static web files (for development or custom front end) | | WEB_PATH |
|
||||
proxy-path | path prefix when service is run behind a proxy | | PROXY_PATH |
|
||||
proxy-port | port of the proxy when the service is run behind a proxy | | PROXY_PORT |
|
||||
email-contact | email contact for the front end | | EMAIL_CONTACT |
|
||||
ga-key | google analytics key for the front end | | GA_KEY |
|
||||
provider | which storage provider to use | (s3, storj, gdrive or local) |
|
||||
uservoice-key | user voice key for the front end | | USERVOICE_KEY |
|
||||
@@ -161,23 +162,23 @@ To use a custom non-AWS S3 provider, you need to specify the endpoint as defined
|
||||
|
||||
## Storj Network Provider
|
||||
|
||||
To use the Storj Network as storage provider you need to specify the following flags:
|
||||
To use the Storj Network as a storage provider you need to specify the following flags:
|
||||
- provider `--provider storj`
|
||||
- storj-access _(either via flag or environment variable STORJ_ACCESS)_
|
||||
- storj-bucket _(either via flag or environment variable STORJ_BUCKET)_
|
||||
|
||||
### Creating Bucket and Scope
|
||||
|
||||
In preparation you need to create an access grant (or copy it from the uplink configuration) and a bucket.
|
||||
You need to create an access grant (or copy it from the uplink configuration) and a bucket in preparation.
|
||||
|
||||
To get started, login to your account and go to the Access Grant Menu and start the Wizard on the upper right.
|
||||
To get started, log in to your account and go to the Access Grant Menu and start the Wizard on the upper right.
|
||||
|
||||
Enter your access grant name of choice, hit *Next* and restrict it as necessary/preferred.
|
||||
Aftwards continue either in CLI or within the Browser. You'll be asked for a Passphrase used as Encryption Key.
|
||||
**Make sure to save it in a safe place, without it you will lose the ability to decrypt your files!**
|
||||
Afterwards continue either in CLI or within the Browser. Next, you'll be asked for a Passphrase used as Encryption Key.
|
||||
**Make sure to save it in a safe place. Without it, you will lose the ability to decrypt your files!**
|
||||
|
||||
Afterwards you can copy the access grant and then start the startup of the transfer.sh endpoint.
|
||||
For enhanced security its recommended to provide both the access grant and the bucket name as ENV Variables.
|
||||
Afterwards, you can copy the access grant and then start the startup of the transfer.sh endpoint.
|
||||
It is recommended to provide both the access grant and the bucket name as ENV Variables for enhanced security.
|
||||
|
||||
Example:
|
||||
```
|
||||
@@ -196,8 +197,7 @@ For the usage with Google drive, you need to specify the following options:
|
||||
|
||||
### Creating Gdrive Client Json
|
||||
|
||||
You need to create a Oauth Client id from console.cloud.google.com
|
||||
download the file and place into a safe directory
|
||||
You need to create an OAuth Client id from console.cloud.google.com, download the file, and place it into a safe directory.
|
||||
|
||||
### Usage example
|
||||
|
||||
@@ -221,7 +221,7 @@ Contributions are welcome.
|
||||
|
||||
**Stefan Benten**
|
||||
|
||||
## Copyright and license
|
||||
## Copyright and License
|
||||
|
||||
Code and documentation copyright 2011-2018 Remco Verhoef.
|
||||
Code and documentation copyright 2018-2020 Andrea Spacca.
|
||||
|
25
cmd/cmd.go
25
cmd/cmd.go
@@ -12,6 +12,7 @@ import (
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
// Version is inject at build time
|
||||
var Version = "0.0.0"
|
||||
var helpTemplate = `NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
@@ -97,6 +98,12 @@ var globalFlags = []cli.Flag{
|
||||
Value: "",
|
||||
EnvVar: "PROXY_PORT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "email-contact",
|
||||
Usage: "email address to link in Contact Us (front end)",
|
||||
Value: "",
|
||||
EnvVar: "EMAIL_CONTACT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "ga-key",
|
||||
Usage: "key for google analytics (front end)",
|
||||
@@ -282,14 +289,16 @@ var globalFlags = []cli.Flag{
|
||||
},
|
||||
}
|
||||
|
||||
// Cmd wraps cli.app
|
||||
type Cmd struct {
|
||||
*cli.App
|
||||
}
|
||||
|
||||
func VersionAction(c *cli.Context) {
|
||||
func versionAction(c *cli.Context) {
|
||||
fmt.Println(color.YellowString(fmt.Sprintf("transfer.sh %s: Easy file sharing from the command line", Version)))
|
||||
}
|
||||
|
||||
// New is the factory for transfer.sh
|
||||
func New() *Cmd {
|
||||
logger := log.New(os.Stdout, "[transfer.sh]", log.LstdFlags)
|
||||
|
||||
@@ -304,7 +313,7 @@ func New() *Cmd {
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "version",
|
||||
Action: VersionAction,
|
||||
Action: versionAction,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -345,6 +354,10 @@ func New() *Cmd {
|
||||
options = append(options, server.ProxyPort(v))
|
||||
}
|
||||
|
||||
if v := c.String("email-contact"); v != "" {
|
||||
options = append(options, server.EmailContact(v))
|
||||
}
|
||||
|
||||
if v := c.String("ga-key"); v != "" {
|
||||
options = append(options, server.GoogleAnalytics(v))
|
||||
}
|
||||
@@ -403,13 +416,13 @@ func New() *Cmd {
|
||||
}
|
||||
|
||||
if c.Bool("force-https") {
|
||||
options = append(options, server.ForceHTTPs())
|
||||
options = append(options, server.ForceHTTPS())
|
||||
}
|
||||
|
||||
if httpAuthUser := c.String("http-auth-user"); httpAuthUser == "" {
|
||||
} else if httpAuthPass := c.String("http-auth-pass"); httpAuthPass == "" {
|
||||
} else {
|
||||
options = append(options, server.HttpAuthCredentials(httpAuthUser, httpAuthPass))
|
||||
options = append(options, server.HTTPAuthCredentials(httpAuthUser, httpAuthPass))
|
||||
}
|
||||
|
||||
applyIPFilter := false
|
||||
@@ -445,13 +458,13 @@ func New() *Cmd {
|
||||
case "gdrive":
|
||||
chunkSize := c.Int("gdrive-chunk-size")
|
||||
|
||||
if clientJsonFilepath := c.String("gdrive-client-json-filepath"); clientJsonFilepath == "" {
|
||||
if clientJSONFilepath := c.String("gdrive-client-json-filepath"); clientJSONFilepath == "" {
|
||||
panic("client-json-filepath not set.")
|
||||
} else if localConfigPath := c.String("gdrive-local-config-path"); localConfigPath == "" {
|
||||
panic("local-config-path not set.")
|
||||
} else if basedir := c.String("basedir"); basedir == "" {
|
||||
panic("basedir not set.")
|
||||
} else if storage, err := server.NewGDriveStorage(clientJsonFilepath, localConfigPath, basedir, chunkSize, logger); err != nil {
|
||||
} else if storage, err := server.NewGDriveStorage(clientJSONFilepath, localConfigPath, basedir, chunkSize, logger); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
options = append(options, server.UseStorage(storage))
|
||||
|
89
examples.md
89
examples.md
@@ -5,6 +5,7 @@
|
||||
* [Archiving and backups](#archiving-and-backups)
|
||||
* [Encrypting and decrypting](#encrypting-and-decrypting)
|
||||
* [Scanning for viruses](#scanning-for-viruses)
|
||||
* [Uploading and copy download command](#uploading-and-copy-download-command)
|
||||
|
||||
## Aliases
|
||||
<a name="aliases"/>
|
||||
@@ -173,4 +174,90 @@ $ curl -X PUT --upload-file ./eicar.com https://transfer.sh/eicar.com/scan
|
||||
### Upload malware to VirusTotal, get a permalink in return
|
||||
```bash
|
||||
$ curl -X PUT --upload-file nhgbhhj https://transfer.sh/test.txt/virustotal
|
||||
```
|
||||
```
|
||||
## Uploading and copy download command
|
||||
|
||||
Download commands can be automatically copied to the clipboard after files are uploaded using transfer.sh.
|
||||
|
||||
It was designed for Linux or macOS.
|
||||
|
||||
### 1. Install xclip or xsel for Linux, macOS skips this step
|
||||
|
||||
- install xclip see https://command-not-found.com/xclip
|
||||
|
||||
- install xsel see https://command-not-found.com/xsel
|
||||
|
||||
Install later, add pbcopy and pbpaste to .bashrc or .zshrc or its equivalent.
|
||||
|
||||
- If use xclip, paste the following lines:
|
||||
|
||||
```sh
|
||||
alias pbcopy='xclip -selection clipboard'
|
||||
alias pbpaste='xclip -selection clipboard -o'
|
||||
```
|
||||
|
||||
- If use xsel, paste the following lines:
|
||||
|
||||
```sh
|
||||
alias pbcopy='xsel --clipboard --input'
|
||||
alias pbpaste='xsel --clipboard --output'
|
||||
```
|
||||
|
||||
### 2. Add Uploading and copy download command shell function
|
||||
|
||||
1. Open .bashrc or .zshrc or its equivalent.
|
||||
|
||||
2. Add the following shell script:
|
||||
|
||||
```sh
|
||||
transfer() {
|
||||
curl --progress-bar --upload-file "$1" https://transfer.sh/$(basename "$1") | pbcopy;
|
||||
echo "1) Download link:"
|
||||
echo "$(pbpaste)"
|
||||
|
||||
echo "\n2) Linux or macOS download command:"
|
||||
linux_macos_download_command="wget $(pbpaste)"
|
||||
echo $linux_macos_download_command
|
||||
|
||||
echo "\n3) Windows download command:"
|
||||
windows_download_command="Invoke-WebRequest -Uri "$(pbpaste)" -OutFile $(basename $1)"
|
||||
echo $windows_download_command
|
||||
|
||||
case $2 in
|
||||
l|m) echo $linux_macos_download_command | pbcopy
|
||||
;;
|
||||
w) echo $windows_download_command | pbcopy
|
||||
;;
|
||||
esac
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 3. Test
|
||||
|
||||
The transfer command has two parameters:
|
||||
|
||||
1. The first parameter is the path to upload the file.
|
||||
|
||||
2. The second parameter indicates which system's download command is copied. optional:
|
||||
|
||||
- This parameter is empty to copy the download link.
|
||||
|
||||
- `l` or `m` copy the Linux or macOS command that downloaded the file.
|
||||
|
||||
- `w` copy the Windows command that downloaded the file.
|
||||
|
||||
For example, The command to download the file on Windows will be copied:
|
||||
|
||||
```sh
|
||||
$ transfer ~/temp/a.log w
|
||||
######################################################################## 100.0%
|
||||
1) Download link:
|
||||
https://transfer.sh/y0qr2c/a.log
|
||||
|
||||
2) Linux or macOS download command:
|
||||
wget https://transfer.sh/y0qr2c/a.log
|
||||
|
||||
3) Windows download command:
|
||||
Invoke-WebRequest -Uri https://transfer.sh/y0qr2c/a.log -OutFile a.log
|
||||
```
|
||||
|
41
flake.lock
generated
Normal file
41
flake.lock
generated
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1631561581,
|
||||
"narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1632470817,
|
||||
"narHash": "sha256-tGyOesdpqQEVqlmVeElsC98OJ2GDy+LNaCThSby/GQM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "39e8ec2db68b863543bd377e44fbe02f8d05864e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
212
flake.nix
Normal file
212
flake.nix
Normal file
@@ -0,0 +1,212 @@
|
||||
{
|
||||
description = "Transfer.sh";
|
||||
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
let
|
||||
transfer-sh = pkgs: pkgs.buildGoModule {
|
||||
src = self;
|
||||
name = "transfer.sh";
|
||||
vendorSha256 = "sha256-bgQUMiC33yVorcKOWhegT1/YU+fvxsz2pkeRvjf3R7g=";
|
||||
};
|
||||
in
|
||||
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
rec {
|
||||
packages = flake-utils.lib.flattenTree {
|
||||
transfer-sh = transfer-sh pkgs;
|
||||
};
|
||||
defaultPackage = packages.transfer-sh;
|
||||
apps.transfer-sh = flake-utils.lib.mkApp { drv = packages.transfer-sh; };
|
||||
defaultApp = apps.transfer-sh;
|
||||
}
|
||||
) // rec {
|
||||
|
||||
nixosModules = {
|
||||
transfer-sh = { config, lib, pkgs, ... }: with lib; let
|
||||
RUNTIME_DIR = "/var/lib/transfer.sh";
|
||||
cfg = config.services.transfer-sh;
|
||||
|
||||
general_options = {
|
||||
|
||||
enable = mkEnableOption "Transfer.sh service";
|
||||
listener = mkOption { default = 80; type = types.int; description = "port to use for http (:80)"; };
|
||||
profile-listener = mkOption { default = 6060; type = types.int; description = "port to use for profiler (:6060)"; };
|
||||
force-https = mkOption { type = types.nullOr types.bool; description = "redirect to https"; };
|
||||
tls-listener = mkOption { default = 443; type = types.int; description = "port to use for https (:443)"; };
|
||||
tls-listener-only = mkOption { type = types.nullOr types.bool; description = "flag to enable tls listener only"; };
|
||||
tls-cert-file = mkOption { type = types.nullOr types.str; description = "path to tls certificate"; };
|
||||
tls-private-key = mkOption { type = types.nullOr types.str; description = "path to tls private key "; };
|
||||
http-auth-user = mkOption { type = types.nullOr types.str; description = "user for basic http auth on upload"; };
|
||||
http-auth-pass = mkOption { type = types.nullOr types.str; description = "pass for basic http auth on upload"; };
|
||||
ip-whitelist = mkOption { type = types.nullOr types.str; description = "comma separated list of ips allowed to connect to the service"; };
|
||||
ip-blacklist = mkOption { type = types.nullOr types.str; description = "comma separated list of ips not allowed to connect to the service"; };
|
||||
temp-path = mkOption { type = types.nullOr types.str; description = "path to temp folder"; };
|
||||
web-path = mkOption { type = types.nullOr types.str; description = "path to static web files (for development or custom front end)"; };
|
||||
proxy-path = mkOption { type = types.nullOr types.str; description = "path prefix when service is run behind a proxy"; };
|
||||
proxy-port = mkOption { type = types.nullOr types.str; description = "port of the proxy when the service is run behind a proxy"; };
|
||||
ga-key = mkOption { type = types.nullOr types.str; description = "google analytics key for the front end"; };
|
||||
email-contact = mkOption { type = types.nullOr types.str; description = "email contact for the front end"; };
|
||||
uservoice-key = mkOption { type = types.nullOr types.str; description = "user voice key for the front end"; };
|
||||
lets-encrypt-hosts = mkOption { type = types.nullOr (types.listOf types.str); description = "hosts to use for lets encrypt certificates"; };
|
||||
log = mkOption { type = types.nullOr types.str; description = "path to log file"; };
|
||||
cors-domains = mkOption { type = types.nullOr (types.listOf types.str); description = "comma separated list of domains for CORS, setting it enable CORS "; };
|
||||
clamav-host = mkOption { type = types.nullOr types.str; description = "host for clamav feature"; };
|
||||
rate-limit = mkOption { type = types.nullOr types.int; description = "request per minute"; };
|
||||
max-upload-size = mkOption { type = types.nullOr types.int; description = "max upload size in kilobytes "; };
|
||||
purge-days = mkOption { type = types.nullOr types.int; description = "number of days after the uploads are purged automatically "; };
|
||||
random-token-length = mkOption { type = types.nullOr types.int; description = "length of the random token for the upload path (double the size for delete path)"; };
|
||||
|
||||
};
|
||||
|
||||
provider_options = {
|
||||
|
||||
aws = {
|
||||
enable = mkEnableOption "Enable AWS backend";
|
||||
aws-access-key = mkOption { type = types.str; description = "aws access key"; };
|
||||
aws-secret-key = mkOption { type = types.str; description = "aws secret key"; };
|
||||
bucket = mkOption { type = types.str; description = "aws bucket "; };
|
||||
s3-endpoint = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
Custom S3 endpoint.
|
||||
If you specify the s3-region, you don't need to set the endpoint URL since the correct endpoint will used automatically.
|
||||
'';
|
||||
};
|
||||
s3-region = mkOption { type = types.str; description = "region of the s3 bucket eu-west-"; };
|
||||
s3-no-multipart = mkOption { type = types.nullOr types.bool; description = "disables s3 multipart upload "; };
|
||||
s3-path-style = mkOption { type = types.nullOr types.str; description = "Forces path style URLs, required for Minio. "; };
|
||||
};
|
||||
|
||||
storj = {
|
||||
enable = mkEnableOption "Enable storj backend";
|
||||
storj-access = mkOption { type = types.str; description = "Access for the project"; };
|
||||
storj-bucket = mkOption { type = types.str; description = "Bucket to use within the project"; };
|
||||
};
|
||||
|
||||
gdrive = {
|
||||
enable = mkEnableOption "Enable gdrive backend";
|
||||
gdrive-client-json = mkOption { type = types.str; description = "oauth client json config for gdrive provider"; };
|
||||
gdrive-chunk-size = mkOption { default = 8; type = types.nullOr types.int; description = "chunk size for gdrive upload in megabytes, must be lower than available memory (8 MB)"; };
|
||||
basedir = mkOption { type = types.str; description = "path storage for gdrive provider"; default = "${cfg.stateDir}/store"; };
|
||||
purge-interval = mkOption { type = types.nullOr types.int; description = "interval in hours to run the automatic purge for (not applicable to S3 and Storj)"; };
|
||||
|
||||
};
|
||||
|
||||
local = {
|
||||
enable = mkEnableOption "Enable local backend";
|
||||
basedir = mkOption { type = types.str; description = "path storage for local provider"; default = "${cfg.stateDir}/store"; };
|
||||
purge-interval = mkOption { type = types.nullOr types.int; description = "interval in hours to run the automatic purge for (not applicable to S3 and Storj)"; };
|
||||
};
|
||||
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.transfer-sh = fold recursiveUpdate {} [
|
||||
general_options
|
||||
{
|
||||
provider = provider_options;
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = "User to run the service under";
|
||||
default = "transfer.sh";
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
description = "Group to run the service under";
|
||||
default = "transfer.sh";
|
||||
};
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
description = "Variable state directory";
|
||||
default = RUNTIME_DIR;
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
config = let
|
||||
|
||||
mkFlags = cfg: options:
|
||||
let
|
||||
mkBoolFlag = option: if cfg.${option} then [ "--${option}" ] else [];
|
||||
mkFlag = option:
|
||||
if isBool cfg.${option}
|
||||
then mkBoolFlag option
|
||||
else [ "--${option}" "${cfg.${option}}" ];
|
||||
|
||||
in
|
||||
lists.flatten (map (mkFlag) (filter (option: cfg.${option} != null && option != "enable") options));
|
||||
|
||||
aws-config = (mkFlags cfg.provider.aws (attrNames provider_options)) ++ [ "--provider" "aws" ];
|
||||
gdrive-config = mkFlags cfg.provider.gdrive (attrNames provider_options.gdrive) ++ [ "--provider" "gdrive" ];
|
||||
storj-config = mkFlags cfg.provider.storj (attrNames provider_options.storj) ++ [ "--provider" "storj" ];
|
||||
local-config = mkFlags cfg.provider.local (attrNames provider_options.local) ++ [ "--provider" "local" ];
|
||||
|
||||
general-config = concatStringsSep " " (mkFlags cfg (attrNames general_options));
|
||||
provider-config = concatStringsSep " " (
|
||||
if cfg.provider.aws.enable && !cfg.provider.storj.enable && !cfg.provider.gdrive.enable && !cfg.provider.local.enable then aws-config
|
||||
else if !cfg.provider.aws.enable && cfg.provider.storj.enable && !cfg.provider.gdrive.enable && !cfg.provider.local.enable then storj-config
|
||||
else if !cfg.provider.aws.enable && !cfg.provider.storj.enable && cfg.provider.gdrive.enable && !cfg.provider.local.enable then gdrive-config
|
||||
else if !cfg.provider.aws.enable && !cfg.provider.storj.enable && !cfg.provider.gdrive.enable && cfg.provider.local.enable then local-config
|
||||
else throw "transfer.sh requires exactly one provider (aws, storj, gdrive, local)"
|
||||
);
|
||||
|
||||
in
|
||||
lib.mkIf cfg.enable
|
||||
{
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.stateDir} 0750 ${cfg.user} ${cfg.group} - -"
|
||||
] ++ optional cfg.provider.gdrive.enable cfg.provider.gdrive.basedir
|
||||
++ optional cfg.provider.local.enable cfg.provider.local.basedir;
|
||||
|
||||
systemd.services.transfer-sh = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = "${transfer-sh pkgs}/bin/transfer.sh ${general-config} ${provider-config} ";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ cfg.listener cfg.profile-listener cfg.tls-listener ];
|
||||
};
|
||||
};
|
||||
|
||||
default = { self, pkgs, ... }: {
|
||||
imports = [ nixosModules.transfer-sh ];
|
||||
# Network configuration.
|
||||
|
||||
# useDHCP is generally considered to better be turned off in favor
|
||||
# of <adapter>.useDHCP
|
||||
networking.useDHCP = false;
|
||||
networking.firewall.allowedTCPPorts = [];
|
||||
|
||||
# Enable the inventaire server.
|
||||
services.transfer-sh = {
|
||||
enable = true;
|
||||
provider.local = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
nixosConfigurations."container" = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
nixosModules.default
|
||||
({ ... }: { boot.isContainer = true; })
|
||||
];
|
||||
};
|
||||
|
||||
};
|
||||
}
|
7
go.mod
7
go.mod
@@ -8,10 +8,11 @@ require (
|
||||
github.com/VojtechVitek/ratelimit v0.0.0-20160722140851-dc172bc0f6d2
|
||||
github.com/aws/aws-sdk-go v1.37.14
|
||||
github.com/calebcase/tmpfile v1.0.2 // indirect
|
||||
github.com/chris-ramon/douceur v0.2.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
|
||||
github.com/dutchcoders/go-virustotal v0.0.0-20140923143438-24cc8e6fa329
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210723094506-f0946ebceb7a
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20211215083008-31e11925a9d3
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/garyburd/redigo v1.6.2 // indirect
|
||||
@@ -19,7 +20,7 @@ require (
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.4
|
||||
github.com/microcosm-cc/bluemonday v1.0.16
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
@@ -27,7 +28,7 @@ require (
|
||||
github.com/urfave/cli v1.22.5
|
||||
go.opencensus.io v0.22.6 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99
|
||||
google.golang.org/api v0.40.0
|
||||
google.golang.org/genproto v0.0.0-20210218151259-fe80b386bf06 // indirect
|
||||
|
75
go.sum
75
go.sum
@@ -1,17 +1,14 @@
|
||||
cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
@@ -61,7 +58,6 @@ github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVa
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/calebcase/tmpfile v1.0.2-0.20200602150926-3af473ef8439 h1:fqGdSbbWVbQqNCQXd/dyZ7Bl+u8R2X7QCiNzwgyUa/M=
|
||||
github.com/calebcase/tmpfile v1.0.2-0.20200602150926-3af473ef8439/go.mod h1:iErLeG/iqJr8LaQ/gYRv4GXdqssi3jg4iSzvrA06/lw=
|
||||
github.com/calebcase/tmpfile v1.0.2 h1:1AGuhKiUu4J6wxz6lxuF6ck3f8G2kaV6KSEny0RGCig=
|
||||
github.com/calebcase/tmpfile v1.0.2/go.mod h1:iErLeG/iqJr8LaQ/gYRv4GXdqssi3jg4iSzvrA06/lw=
|
||||
@@ -76,12 +72,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -90,16 +84,12 @@ github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e h1:rcHHSQqzCg
|
||||
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e/go.mod h1:Byz7q8MSzSPkouskHJhX0er2mZY/m0Vj5bMeMCkkyY4=
|
||||
github.com/dutchcoders/go-virustotal v0.0.0-20140923143438-24cc8e6fa329 h1:ERqCkG/uSyT74P1m/j9yR+so+7ynY4fbTvLY/Mr1ZMg=
|
||||
github.com/dutchcoders/go-virustotal v0.0.0-20140923143438-24cc8e6fa329/go.mod h1:G5qOfE5bQZ5scycLpB7fYWgN4y3xdfXo+pYWM8z2epY=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210212072623-ac7014a9c3a7 h1:zKJw+RxTDbypWVb0HfPs43bj/ee65Bl7rIDPO7HixrQ=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210212072623-ac7014a9c3a7/go.mod h1:jTzXZabwihvQgvmySgD4f4GNszimkXK3o8x1ucH1z5Q=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210717081259-8b8af59a0fae h1:JalbO1PKAsbSYQBW6Q4aXbgj2w4bWofdrHxRRwqRgDc=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210717081259-8b8af59a0fae/go.mod h1:F6Q37CxDh2MHr5KXkcZmNB3tdkK7v+bgE+OpBY+9ilI=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210723091746-c17d678a22f3 h1:CM9FGBPXLXhvKo0TuO4CKKdZLah6esP1SAfPzZDjEB0=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210723091746-c17d678a22f3/go.mod h1:F6Q37CxDh2MHr5KXkcZmNB3tdkK7v+bgE+OpBY+9ilI=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210723093538-596fe60a5dd5 h1:M6GI6DvsFgBGpp3+V+VB5okP1BGXISltz7acVYWWFOQ=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210723093538-596fe60a5dd5/go.mod h1:F6Q37CxDh2MHr5KXkcZmNB3tdkK7v+bgE+OpBY+9ilI=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210723094506-f0946ebceb7a h1:+N7J1NK7gxKZ+X4syY1HqafUudJiR8voJGcXWkxLgAw=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210723094506-f0946ebceb7a/go.mod h1:F6Q37CxDh2MHr5KXkcZmNB3tdkK7v+bgE+OpBY+9ilI=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210819203540-bbdd40be1311 h1:/Rwuhcp8ZLUauWajAgMyy6AiVbobvD52I+/OnzThK0A=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20210819203540-bbdd40be1311/go.mod h1:F6Q37CxDh2MHr5KXkcZmNB3tdkK7v+bgE+OpBY+9ilI=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20211215083008-31e11925a9d3 h1:HyfU90/8y9S5IkHTQgIfzs4dT3iagbJUJLncCsSwZCI=
|
||||
github.com/dutchcoders/transfer.sh-web v0.0.0-20211215083008-31e11925a9d3/go.mod h1:F6Q37CxDh2MHr5KXkcZmNB3tdkK7v+bgE+OpBY+9ilI=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -125,9 +115,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg=
|
||||
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f/go.mod h1:ijRvpgDJDI262hYq/IQVYgf8hd8IHUs93Ol0kvMBAx4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
@@ -141,13 +129,10 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -163,10 +148,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.1.1-0.20171103154506-982329095285/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
@@ -205,7 +188,6 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
@@ -224,7 +206,6 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@@ -238,8 +219,10 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
|
||||
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDEq1axMbGg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
||||
github.com/microcosm-cc/bluemonday v1.0.5 h1:cF59UCKMmmUgqN1baLvqU/B1ZsMori+duLVTLpgiG3w=
|
||||
github.com/microcosm-cc/bluemonday v1.0.5/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
||||
github.com/microcosm-cc/bluemonday v1.0.16 h1:kHmAq2t7WPWLjiGvzKa5o3HzSfahUKiOq7fAPUiMNIc=
|
||||
github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
@@ -247,28 +230,22 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shuLhan/go-bindata v4.0.0+incompatible/go.mod h1:pkcPAATLBDD2+SpAPnX5vEM90F7fcwHCvvLCMXcmw3g=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spacemonkeygo/monkit/v3 v3.0.4 h1:Ay+PZirv+qfd4sqcT+X/U3BnC7AcIaqp/IXh0oV36k8=
|
||||
github.com/spacemonkeygo/monkit/v3 v3.0.4/go.mod h1:JcK1pCbReQsOsMKF/POFSZCq7drXFybgGmbc27tuwes=
|
||||
github.com/spacemonkeygo/monkit/v3 v3.0.7 h1:LsGdIXl8mccqJrYEh4Uf4sLVGu/g0tjhNqQzdn9MzVk=
|
||||
github.com/spacemonkeygo/monkit/v3 v3.0.7/go.mod h1:kj1ViJhlyADa7DiA4xVnTuPA46lFKbM7mxQTrXCuJP4=
|
||||
github.com/spacemonkeygo/monkit/v3 v3.0.10 h1:jJPiF4EdetQAHAyHyX0wYIv9c/Vl0oN3KJKtnakfR+A=
|
||||
github.com/spacemonkeygo/monkit/v3 v3.0.10/go.mod h1:kj1ViJhlyADa7DiA4xVnTuPA46lFKbM7mxQTrXCuJP4=
|
||||
github.com/spacemonkeygo/monotime v0.0.0-20180824235756-e3f48a95f98a h1:8+cCjxhToanKmxLIbuyBNe2EnpgwhiivsIaRJstDRFA=
|
||||
github.com/spacemonkeygo/monotime v0.0.0-20180824235756-e3f48a95f98a/go.mod h1:ul4bvvnCOPZgq8w0nTkSmWVg/hauVpFS97Am1YM1XXo=
|
||||
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
@@ -276,9 +253,7 @@ github.com/spf13/jwalterweatherman v0.0.0-20170901151539-12bd96e66386/go.mod h1:
|
||||
github.com/spf13/pflag v1.0.1-0.20170901120850-7aff26db30c1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -297,15 +272,11 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
|
||||
github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
github.com/zeebo/float16 v0.1.0 h1:kRqxv5og6z1emEyz5FpW0/BVHe5VfxEAw6b1ljCZlUc=
|
||||
github.com/zeebo/float16 v0.1.0/go.mod h1:fssGvvXu+XS8MH57cKmyrLB/cqioYeYX/2mXCN3a5wo=
|
||||
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 h1:+cwNE5KJ3pika4HuzmDHkDlK5myo0G9Sv+eO7WWxnUQ=
|
||||
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54/go.mod h1:EI8LcOBDlSL3POyqwC1eJhOYlMBMidES+613EtmmT5w=
|
||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
@@ -317,7 +288,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA=
|
||||
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
@@ -374,7 +344,6 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
@@ -388,17 +357,16 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
@@ -411,16 +379,12 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -444,7 +408,6 @@ golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -462,19 +425,21 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe h1:WdX7u8s3yOigWAhHEaDl8r9G+4XwFQEQFtBMYyN+kXQ=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -527,7 +492,6 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -535,7 +499,6 @@ google.golang.org/api v0.0.0-20170921000349-586095a6e407/go.mod h1:4mhQ8q/RsB7i+
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
@@ -543,7 +506,6 @@ google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
@@ -556,10 +518,8 @@ google.golang.org/api v0.40.0 h1:uWrpz12dpVPn7cojP82mk02XDgTJLDPc2KbVTxrWb4A=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
@@ -569,7 +529,6 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
@@ -584,7 +543,6 @@ google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
@@ -606,17 +564,14 @@ google.golang.org/genproto v0.0.0-20210218151259-fe80b386bf06 h1:Px6YyLaNKEo5eon
|
||||
google.golang.org/genproto v0.0.0-20210218151259-fe80b386bf06/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.2.1-0.20170921194603-d4b75ebd4f9f/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
|
||||
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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
@@ -638,7 +593,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -646,7 +600,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
@@ -57,6 +57,7 @@ func (s *Server) scanHandler(w http.ResponseWriter, r *http.Request) {
|
||||
c := clamd.NewClamd(s.ClamAVDaemonHost)
|
||||
|
||||
abort := make(chan bool)
|
||||
defer close(abort)
|
||||
response, err := c.ScanStream(reader, abort)
|
||||
if err != nil {
|
||||
s.logger.Printf("%s", err.Error())
|
||||
@@ -70,6 +71,4 @@ func (s *Server) scanHandler(w http.ResponseWriter, r *http.Request) {
|
||||
case <-time.After(time.Second * 60):
|
||||
abort <- true
|
||||
}
|
||||
|
||||
close(abort)
|
||||
}
|
||||
|
@@ -37,7 +37,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
blackfriday "github.com/russross/blackfriday/v2"
|
||||
"html"
|
||||
html_template "html/template"
|
||||
"io"
|
||||
@@ -54,9 +53,12 @@ import (
|
||||
text_template "text/template"
|
||||
"time"
|
||||
|
||||
blackfriday "github.com/russross/blackfriday/v2"
|
||||
|
||||
"net"
|
||||
|
||||
"encoding/base64"
|
||||
|
||||
web "github.com/dutchcoders/transfer.sh-web"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
@@ -123,7 +125,7 @@ func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
token := vars["token"]
|
||||
filename := vars["filename"]
|
||||
|
||||
metadata, err := s.CheckMetadata(token, filename, false)
|
||||
metadata, err := s.checkMetadata(token, filename, false)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Printf("Error metadata: %s", err.Error())
|
||||
@@ -198,9 +200,9 @@ func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ContentType string
|
||||
Content html_template.HTML
|
||||
Filename string
|
||||
Url string
|
||||
UrlGet string
|
||||
UrlRandomToken string
|
||||
URL string
|
||||
URLGet string
|
||||
URLRandomToken string
|
||||
Hostname string
|
||||
WebAddress string
|
||||
ContentLength uint64
|
||||
@@ -251,6 +253,7 @@ func (s *Server) viewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := struct {
|
||||
Hostname string
|
||||
WebAddress string
|
||||
EmailContact string
|
||||
GAKey string
|
||||
UserVoiceKey string
|
||||
PurgeTime string
|
||||
@@ -260,12 +263,13 @@ func (s *Server) viewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}{
|
||||
hostname,
|
||||
webAddress,
|
||||
s.emailContact,
|
||||
s.gaKey,
|
||||
s.userVoiceKey,
|
||||
purgeTime,
|
||||
maxUploadSize,
|
||||
Token(s.randomTokenLength),
|
||||
Token(s.randomTokenLength),
|
||||
token(s.randomTokenLength),
|
||||
token(s.randomTokenLength),
|
||||
}
|
||||
|
||||
if acceptsHTML(r.Header) {
|
||||
@@ -286,7 +290,7 @@ func (s *Server) notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func sanitize(fileName string) string {
|
||||
return path.Clean(path.Base(fileName))
|
||||
return path.Base(fileName)
|
||||
}
|
||||
|
||||
func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -296,7 +300,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
token := Token(s.randomTokenLength)
|
||||
token := token(s.randomTokenLength)
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
|
||||
@@ -342,6 +346,11 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
reader, err = os.Open(file.Name())
|
||||
if err != nil {
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
reader = bytes.NewReader(b.Bytes())
|
||||
}
|
||||
@@ -354,7 +363,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
metadata := MetadataForRequest(contentType, s.randomTokenLength, r)
|
||||
metadata := metadataForRequest(contentType, s.randomTokenLength, r)
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
||||
@@ -403,7 +412,7 @@ func (s *Server) cleanTmpFile(f *os.File) {
|
||||
}
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
type metadata struct {
|
||||
// ContentType is the original uploading content type
|
||||
ContentType string
|
||||
// Secret as knowledge to delete file
|
||||
@@ -418,13 +427,13 @@ type Metadata struct {
|
||||
DeletionToken string
|
||||
}
|
||||
|
||||
func MetadataForRequest(contentType string, randomTokenLength int, r *http.Request) Metadata {
|
||||
metadata := Metadata{
|
||||
func metadataForRequest(contentType string, randomTokenLength int, r *http.Request) metadata {
|
||||
metadata := metadata{
|
||||
ContentType: strings.ToLower(contentType),
|
||||
MaxDate: time.Time{},
|
||||
Downloads: 0,
|
||||
MaxDownloads: -1,
|
||||
DeletionToken: Token(randomTokenLength) + Token(randomTokenLength),
|
||||
DeletionToken: token(randomTokenLength) + token(randomTokenLength),
|
||||
}
|
||||
|
||||
if v := r.Header.Get("Max-Downloads"); v == "" {
|
||||
@@ -491,6 +500,11 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
reader, err = os.Open(file.Name())
|
||||
if err != nil {
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
reader = bytes.NewReader(b.Bytes())
|
||||
}
|
||||
@@ -512,15 +526,19 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
contentType := mime.TypeByExtension(filepath.Ext(vars["filename"]))
|
||||
|
||||
token := Token(s.randomTokenLength)
|
||||
token := token(s.randomTokenLength)
|
||||
|
||||
metadata := MetadataForRequest(contentType, s.randomTokenLength, r)
|
||||
metadata := metadataForRequest(contentType, s.randomTokenLength, r)
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, errors.New("Could not encode metadata").Error(), 500)
|
||||
return
|
||||
} else if !metadata.MaxDate.IsZero() && time.Now().After(metadata.MaxDate) {
|
||||
s.logger.Print("Invalid MaxDate")
|
||||
http.Error(w, errors.New("Invalid MaxDate, make sure Max-Days is smaller than 290 years").Error(), 400)
|
||||
return
|
||||
} else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, errors.New("Could not save metadata").Error(), 500)
|
||||
@@ -614,32 +632,31 @@ func getURL(r *http.Request, proxyPort string) *url.URL {
|
||||
u.Scheme = "http"
|
||||
}
|
||||
|
||||
if u.Host == "" {
|
||||
host, port, err := net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
host = r.Host
|
||||
port = ""
|
||||
}
|
||||
if len(proxyPort) != 0 {
|
||||
port = proxyPort
|
||||
}
|
||||
if len(port) == 0 {
|
||||
host, port, err := net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
host = r.Host
|
||||
port = ""
|
||||
}
|
||||
if len(proxyPort) != 0 {
|
||||
port = proxyPort
|
||||
}
|
||||
|
||||
if len(port) == 0 {
|
||||
u.Host = host
|
||||
} else {
|
||||
if port == "80" && u.Scheme == "http" {
|
||||
u.Host = host
|
||||
} else if port == "443" && u.Scheme == "https" {
|
||||
u.Host = host
|
||||
} else {
|
||||
if port == "80" && u.Scheme == "http" {
|
||||
u.Host = host
|
||||
} else if port == "443" && u.Scheme == "https" {
|
||||
u.Host = host
|
||||
} else {
|
||||
u.Host = net.JoinHostPort(host, port)
|
||||
}
|
||||
u.Host = net.JoinHostPort(host, port)
|
||||
}
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func (metadata Metadata) remainingLimitHeaderValues() (remainingDownloads, remainingDays string) {
|
||||
func (metadata metadata) remainingLimitHeaderValues() (remainingDownloads, remainingDays string) {
|
||||
if metadata.MaxDate.IsZero() {
|
||||
remainingDays = "n/a"
|
||||
} else {
|
||||
@@ -656,7 +673,7 @@ func (metadata Metadata) remainingLimitHeaderValues() (remainingDownloads, remai
|
||||
return remainingDownloads, remainingDays
|
||||
}
|
||||
|
||||
func (s *Server) Lock(token, filename string) {
|
||||
func (s *Server) lock(token, filename string) {
|
||||
key := path.Join(token, filename)
|
||||
|
||||
lock, _ := s.locks.LoadOrStore(key, &sync.Mutex{})
|
||||
@@ -666,7 +683,7 @@ func (s *Server) Lock(token, filename string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Server) Unlock(token, filename string) {
|
||||
func (s *Server) unlock(token, filename string) {
|
||||
key := path.Join(token, filename)
|
||||
|
||||
lock, _ := s.locks.LoadOrStore(key, &sync.Mutex{})
|
||||
@@ -674,11 +691,11 @@ func (s *Server) Unlock(token, filename string) {
|
||||
lock.(*sync.Mutex).Unlock()
|
||||
}
|
||||
|
||||
func (s *Server) CheckMetadata(token, filename string, increaseDownload bool) (Metadata, error) {
|
||||
s.Lock(token, filename)
|
||||
defer s.Unlock(token, filename)
|
||||
func (s *Server) checkMetadata(token, filename string, increaseDownload bool) (metadata, error) {
|
||||
s.lock(token, filename)
|
||||
defer s.unlock(token, filename)
|
||||
|
||||
var metadata Metadata
|
||||
var metadata metadata
|
||||
|
||||
r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
|
||||
if err != nil {
|
||||
@@ -690,9 +707,9 @@ func (s *Server) CheckMetadata(token, filename string, increaseDownload bool) (M
|
||||
if err := json.NewDecoder(r).Decode(&metadata); err != nil {
|
||||
return metadata, err
|
||||
} else if metadata.MaxDownloads != -1 && metadata.Downloads >= metadata.MaxDownloads {
|
||||
return metadata, errors.New("MaxDownloads expired.")
|
||||
return metadata, errors.New("maxDownloads expired")
|
||||
} else if !metadata.MaxDate.IsZero() && time.Now().After(metadata.MaxDate) {
|
||||
return metadata, errors.New("MaxDate expired.")
|
||||
return metadata, errors.New("maxDate expired")
|
||||
} else if metadata.MaxDownloads != -1 && increaseDownload {
|
||||
// todo(nl5887): mutex?
|
||||
|
||||
@@ -710,15 +727,15 @@ func (s *Server) CheckMetadata(token, filename string, increaseDownload bool) (M
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (s *Server) CheckDeletionToken(deletionToken, token, filename string) error {
|
||||
s.Lock(token, filename)
|
||||
defer s.Unlock(token, filename)
|
||||
func (s *Server) checkDeletionToken(deletionToken, token, filename string) error {
|
||||
s.lock(token, filename)
|
||||
defer s.unlock(token, filename)
|
||||
|
||||
var metadata Metadata
|
||||
var metadata metadata
|
||||
|
||||
r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
|
||||
if s.storage.IsNotExist(err) {
|
||||
return errors.New("Metadata doesn't exist")
|
||||
return errors.New("metadata doesn't exist")
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -728,7 +745,7 @@ func (s *Server) CheckDeletionToken(deletionToken, token, filename string) error
|
||||
if err := json.NewDecoder(r).Decode(&metadata); err != nil {
|
||||
return err
|
||||
} else if metadata.DeletionToken != deletionToken {
|
||||
return errors.New("Deletion token doesn't match.")
|
||||
return errors.New("deletion token doesn't match")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -754,7 +771,7 @@ func (s *Server) deleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
filename := vars["filename"]
|
||||
deletionToken := vars["deletionToken"]
|
||||
|
||||
if err := s.CheckDeletionToken(deletionToken, token, filename); err != nil {
|
||||
if err := s.checkDeletionToken(deletionToken, token, filename); err != nil {
|
||||
s.logger.Printf("Error metadata: %s", err.Error())
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
@@ -790,7 +807,7 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
||||
token := strings.Split(key, "/")[0]
|
||||
filename := sanitize(strings.Split(key, "/")[1])
|
||||
|
||||
if _, err := s.CheckMetadata(token, filename, true); err != nil {
|
||||
if _, err := s.checkMetadata(token, filename, true); err != nil {
|
||||
s.logger.Printf("Error metadata: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
@@ -801,11 +818,11 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if s.storage.IsNotExist(err) {
|
||||
http.Error(w, "File not found", 404)
|
||||
return
|
||||
} else {
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, "Could not retrieve file.", 500)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, "Could not retrieve file.", 500)
|
||||
return
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
@@ -862,7 +879,7 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
token := strings.Split(key, "/")[0]
|
||||
filename := sanitize(strings.Split(key, "/")[1])
|
||||
|
||||
if _, err := s.CheckMetadata(token, filename, true); err != nil {
|
||||
if _, err := s.checkMetadata(token, filename, true); err != nil {
|
||||
s.logger.Printf("Error metadata: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
@@ -872,11 +889,11 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if s.storage.IsNotExist(err) {
|
||||
http.Error(w, "File not found", 404)
|
||||
return
|
||||
} else {
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, "Could not retrieve file.", 500)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, "Could not retrieve file.", 500)
|
||||
return
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
@@ -921,7 +938,7 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
|
||||
token := strings.Split(key, "/")[0]
|
||||
filename := strings.Split(key, "/")[1]
|
||||
|
||||
if _, err := s.CheckMetadata(token, filename, true); err != nil {
|
||||
if _, err := s.checkMetadata(token, filename, true); err != nil {
|
||||
s.logger.Printf("Error metadata: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
@@ -931,11 +948,11 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if s.storage.IsNotExist(err) {
|
||||
http.Error(w, "File not found", 404)
|
||||
return
|
||||
} else {
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, "Could not retrieve file.", 500)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, "Could not retrieve file.", 500)
|
||||
return
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
@@ -966,7 +983,7 @@ func (s *Server) headHandler(w http.ResponseWriter, r *http.Request) {
|
||||
token := vars["token"]
|
||||
filename := vars["filename"]
|
||||
|
||||
metadata, err := s.CheckMetadata(token, filename, false)
|
||||
metadata, err := s.checkMetadata(token, filename, false)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Printf("Error metadata: %s", err.Error())
|
||||
@@ -1001,7 +1018,7 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
||||
token := vars["token"]
|
||||
filename := vars["filename"]
|
||||
|
||||
metadata, err := s.CheckMetadata(token, filename, true)
|
||||
metadata, err := s.checkMetadata(token, filename, true)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Printf("Error metadata: %s", err.Error())
|
||||
@@ -1073,19 +1090,32 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// RedirectHandler handles redirect
|
||||
func (s *Server) RedirectHandler(h http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.forceHTTPs {
|
||||
if !s.forceHTTPS {
|
||||
// we don't want to enforce https
|
||||
} else if r.URL.Path == "/health.html" {
|
||||
// health check url won't redirect
|
||||
} else if strings.HasSuffix(ipAddrFromRemoteAddr(r.Host), ".onion") {
|
||||
// .onion addresses cannot get a valid certificate, so don't redirect
|
||||
} else if r.Header.Get("X-Forwarded-Proto") == "https" {
|
||||
} else if r.URL.Scheme == "https" {
|
||||
} else if r.TLS != nil {
|
||||
} else {
|
||||
u := getURL(r, s.proxyPort)
|
||||
u.Scheme = "https"
|
||||
if len(s.proxyPort) == 0 && len(s.TLSListenerString) > 0 {
|
||||
_, port, err := net.SplitHostPort(s.TLSListenerString)
|
||||
if err != nil || port == "443" {
|
||||
port = ""
|
||||
}
|
||||
|
||||
if len(port) > 0 {
|
||||
u.Host = net.JoinHostPort(u.Hostname(), port)
|
||||
} else {
|
||||
u.Host = u.Hostname()
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(w, r, u.String(), http.StatusPermanentRedirect)
|
||||
return
|
||||
@@ -1095,17 +1125,17 @@ func (s *Server) RedirectHandler(h http.Handler) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// Create a log handler for every request it receives.
|
||||
// LoveHandler Create a log handler for every request it receives.
|
||||
func LoveHandler(h http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("x-made-with", "<3 by DutchCoders")
|
||||
w.Header().Set("x-served-by", "Proudly served by DutchCoders")
|
||||
w.Header().Set("Server", "Transfer.sh HTTP Server 1.0")
|
||||
w.Header().Set("server", "Transfer.sh HTTP Server")
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func IPFilterHandler(h http.Handler, ipFilterOptions *IPFilterOptions) http.HandlerFunc {
|
||||
func ipFilterHandler(h http.Handler, ipFilterOptions *IPFilterOptions) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if ipFilterOptions == nil {
|
||||
h.ServeHTTP(w, r)
|
||||
@@ -1116,7 +1146,7 @@ func IPFilterHandler(h http.Handler, ipFilterOptions *IPFilterOptions) http.Hand
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) BasicAuthHandler(h http.Handler) http.HandlerFunc {
|
||||
func (s *Server) basicAuthHandler(h http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if s.AuthUser == "" || s.AuthPass == "" {
|
||||
h.ServeHTTP(w, r)
|
||||
|
@@ -13,16 +13,16 @@ import (
|
||||
func Test(t *testing.T) { TestingT(t) }
|
||||
|
||||
var (
|
||||
_ = Suite(&SuiteRedirectWithForceHTTPs{})
|
||||
_ = Suite(&SuiteRedirectWithoutForceHTTPs{})
|
||||
_ = Suite(&suiteRedirectWithForceHTTPS{})
|
||||
_ = Suite(&suiteRedirectWithoutForceHTTPS{})
|
||||
)
|
||||
|
||||
type SuiteRedirectWithForceHTTPs struct {
|
||||
type suiteRedirectWithForceHTTPS struct {
|
||||
handler http.HandlerFunc
|
||||
}
|
||||
|
||||
func (s *SuiteRedirectWithForceHTTPs) SetUpTest(c *C) {
|
||||
srvr, err := New(ForceHTTPs())
|
||||
func (s *suiteRedirectWithForceHTTPS) SetUpTest(c *C) {
|
||||
srvr, err := New(ForceHTTPS())
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -32,7 +32,7 @@ func (s *SuiteRedirectWithForceHTTPs) SetUpTest(c *C) {
|
||||
s.handler = srvr.RedirectHandler(handler)
|
||||
}
|
||||
|
||||
func (s *SuiteRedirectWithForceHTTPs) TestHTTPs(c *C) {
|
||||
func (s *suiteRedirectWithForceHTTPS) TestHTTPs(c *C) {
|
||||
req := httptest.NewRequest("GET", "https://test/test", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -42,7 +42,7 @@ func (s *SuiteRedirectWithForceHTTPs) TestHTTPs(c *C) {
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *SuiteRedirectWithForceHTTPs) TestOnion(c *C) {
|
||||
func (s *suiteRedirectWithForceHTTPS) TestOnion(c *C) {
|
||||
req := httptest.NewRequest("GET", "http://test.onion/test", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -52,7 +52,7 @@ func (s *SuiteRedirectWithForceHTTPs) TestOnion(c *C) {
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *SuiteRedirectWithForceHTTPs) TestXForwardedFor(c *C) {
|
||||
func (s *suiteRedirectWithForceHTTPS) TestXForwardedFor(c *C) {
|
||||
req := httptest.NewRequest("GET", "http://127.0.0.1/test", nil)
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
|
||||
@@ -63,7 +63,7 @@ func (s *SuiteRedirectWithForceHTTPs) TestXForwardedFor(c *C) {
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *SuiteRedirectWithForceHTTPs) TestHTTP(c *C) {
|
||||
func (s *suiteRedirectWithForceHTTPS) TestHTTP(c *C) {
|
||||
req := httptest.NewRequest("GET", "http://127.0.0.1/test", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -74,11 +74,11 @@ func (s *SuiteRedirectWithForceHTTPs) TestHTTP(c *C) {
|
||||
c.Assert(resp.Header.Get("Location"), Equals, "https://127.0.0.1/test")
|
||||
}
|
||||
|
||||
type SuiteRedirectWithoutForceHTTPs struct {
|
||||
type suiteRedirectWithoutForceHTTPS struct {
|
||||
handler http.HandlerFunc
|
||||
}
|
||||
|
||||
func (s *SuiteRedirectWithoutForceHTTPs) SetUpTest(c *C) {
|
||||
func (s *suiteRedirectWithoutForceHTTPS) SetUpTest(c *C) {
|
||||
srvr, err := New()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
@@ -89,7 +89,7 @@ func (s *SuiteRedirectWithoutForceHTTPs) SetUpTest(c *C) {
|
||||
s.handler = srvr.RedirectHandler(handler)
|
||||
}
|
||||
|
||||
func (s *SuiteRedirectWithoutForceHTTPs) TestHTTP(c *C) {
|
||||
func (s *suiteRedirectWithoutForceHTTPS) TestHTTP(c *C) {
|
||||
req := httptest.NewRequest("GET", "http://127.0.0.1/test", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -99,7 +99,7 @@ func (s *SuiteRedirectWithoutForceHTTPs) TestHTTP(c *C) {
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *SuiteRedirectWithoutForceHTTPs) TestHTTPs(c *C) {
|
||||
func (s *suiteRedirectWithoutForceHTTPS) TestHTTPs(c *C) {
|
||||
req := httptest.NewRequest("GET", "https://127.0.0.1/test", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
@@ -21,7 +21,7 @@ import (
|
||||
"github.com/tomasen/realip"
|
||||
)
|
||||
|
||||
//IPFilterOptions for IPFilter. Allowed takes precendence over Blocked.
|
||||
//IPFilterOptions for ipFilter. Allowed takes precedence over Blocked.
|
||||
//IPs can be IPv4 or IPv6 and can optionally contain subnet
|
||||
//masks (/24). Note however, determining if a given IP is
|
||||
//included in a subnet requires a linear scan so is less performant
|
||||
@@ -43,7 +43,8 @@ type IPFilterOptions struct {
|
||||
}
|
||||
}
|
||||
|
||||
type IPFilter struct {
|
||||
// ipFilter
|
||||
type ipFilter struct {
|
||||
opts IPFilterOptions
|
||||
//mut protects the below
|
||||
//rw since writes are rare
|
||||
@@ -59,13 +60,12 @@ type subnet struct {
|
||||
allowed bool
|
||||
}
|
||||
|
||||
//New constructs IPFilter instance.
|
||||
func NewIPFilter(opts IPFilterOptions) *IPFilter {
|
||||
func newIPFilter(opts IPFilterOptions) *ipFilter {
|
||||
if opts.Logger == nil {
|
||||
flags := log.LstdFlags
|
||||
opts.Logger = log.New(os.Stdout, "", flags)
|
||||
}
|
||||
f := &IPFilter{
|
||||
f := &ipFilter{
|
||||
opts: opts,
|
||||
ips: map[string]bool{},
|
||||
defaultAllowed: !opts.BlockByDefault,
|
||||
@@ -79,15 +79,15 @@ func NewIPFilter(opts IPFilterOptions) *IPFilter {
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *IPFilter) AllowIP(ip string) bool {
|
||||
func (f *ipFilter) AllowIP(ip string) bool {
|
||||
return f.ToggleIP(ip, true)
|
||||
}
|
||||
|
||||
func (f *IPFilter) BlockIP(ip string) bool {
|
||||
func (f *ipFilter) BlockIP(ip string) bool {
|
||||
return f.ToggleIP(ip, false)
|
||||
}
|
||||
|
||||
func (f *IPFilter) ToggleIP(str string, allowed bool) bool {
|
||||
func (f *ipFilter) ToggleIP(str string, allowed bool) bool {
|
||||
//check if has subnet
|
||||
if ip, net, err := net.ParseCIDR(str); err == nil {
|
||||
// containing only one ip?
|
||||
@@ -128,19 +128,19 @@ func (f *IPFilter) ToggleIP(str string, allowed bool) bool {
|
||||
}
|
||||
|
||||
//ToggleDefault alters the default setting
|
||||
func (f *IPFilter) ToggleDefault(allowed bool) {
|
||||
func (f *ipFilter) ToggleDefault(allowed bool) {
|
||||
f.mut.Lock()
|
||||
f.defaultAllowed = allowed
|
||||
f.mut.Unlock()
|
||||
}
|
||||
|
||||
//Allowed returns if a given IP can pass through the filter
|
||||
func (f *IPFilter) Allowed(ipstr string) bool {
|
||||
func (f *ipFilter) Allowed(ipstr string) bool {
|
||||
return f.NetAllowed(net.ParseIP(ipstr))
|
||||
}
|
||||
|
||||
//NetAllowed returns if a given net.IP can pass through the filter
|
||||
func (f *IPFilter) NetAllowed(ip net.IP) bool {
|
||||
func (f *ipFilter) NetAllowed(ip net.IP) bool {
|
||||
//invalid ip
|
||||
if ip == nil {
|
||||
return false
|
||||
@@ -173,35 +173,35 @@ func (f *IPFilter) NetAllowed(ip net.IP) bool {
|
||||
}
|
||||
|
||||
//Blocked returns if a given IP can NOT pass through the filter
|
||||
func (f *IPFilter) Blocked(ip string) bool {
|
||||
func (f *ipFilter) Blocked(ip string) bool {
|
||||
return !f.Allowed(ip)
|
||||
}
|
||||
|
||||
//NetBlocked returns if a given net.IP can NOT pass through the filter
|
||||
func (f *IPFilter) NetBlocked(ip net.IP) bool {
|
||||
func (f *ipFilter) NetBlocked(ip net.IP) bool {
|
||||
return !f.NetAllowed(ip)
|
||||
}
|
||||
|
||||
//WrapIPFilter the provided handler with simple IP blocking middleware
|
||||
//using this IP filter and its configuration
|
||||
func (f *IPFilter) Wrap(next http.Handler) http.Handler {
|
||||
return &ipFilterMiddleware{IPFilter: f, next: next}
|
||||
func (f *ipFilter) Wrap(next http.Handler) http.Handler {
|
||||
return &ipFilterMiddleware{ipFilter: f, next: next}
|
||||
}
|
||||
|
||||
//WrapIPFilter is equivalent to NewIPFilter(opts) then Wrap(next)
|
||||
//WrapIPFilter is equivalent to newIPFilter(opts) then Wrap(next)
|
||||
func WrapIPFilter(next http.Handler, opts IPFilterOptions) http.Handler {
|
||||
return NewIPFilter(opts).Wrap(next)
|
||||
return newIPFilter(opts).Wrap(next)
|
||||
}
|
||||
|
||||
type ipFilterMiddleware struct {
|
||||
*IPFilter
|
||||
*ipFilter
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
func (m *ipFilterMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
remoteIP := realip.FromRequest(r)
|
||||
|
||||
if !m.IPFilter.Allowed(remoteIP) {
|
||||
if !m.ipFilter.Allowed(remoteIP) {
|
||||
//show simple forbidden text
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
|
@@ -48,6 +48,7 @@ import (
|
||||
"github.com/VojtechVitek/ratelimit/memory"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
// import pprof
|
||||
_ "net/http/pprof"
|
||||
|
||||
"crypto/tls"
|
||||
@@ -59,28 +60,30 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const SERVER_INFO = "transfer.sh"
|
||||
|
||||
// parse request with maximum memory of _24Kilobits
|
||||
const _24K = (1 << 3) * 24
|
||||
|
||||
// parse request with maximum memory of _5Megabytes
|
||||
const _5M = (1 << 20) * 5
|
||||
|
||||
// OptionFn is the option function type
|
||||
type OptionFn func(*Server)
|
||||
|
||||
// ClamavHost sets clamav host
|
||||
func ClamavHost(s string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.ClamAVDaemonHost = s
|
||||
}
|
||||
}
|
||||
|
||||
// VirustotalKey sets virus total key
|
||||
func VirustotalKey(s string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.VirusTotalKey = s
|
||||
}
|
||||
}
|
||||
|
||||
// Listener set listener
|
||||
func Listener(s string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.ListenerString = s
|
||||
@@ -88,6 +91,7 @@ func Listener(s string) OptionFn {
|
||||
|
||||
}
|
||||
|
||||
// CorsDomains sets CORS domains
|
||||
func CorsDomains(s string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.CorsDomains = s
|
||||
@@ -95,18 +99,28 @@ func CorsDomains(s string) OptionFn {
|
||||
|
||||
}
|
||||
|
||||
// EmailContact sets email contact
|
||||
func EmailContact(emailContact string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.emailContact = emailContact
|
||||
}
|
||||
}
|
||||
|
||||
// GoogleAnalytics sets GA key
|
||||
func GoogleAnalytics(gaKey string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.gaKey = gaKey
|
||||
}
|
||||
}
|
||||
|
||||
// UserVoice sets UV key
|
||||
func UserVoice(userVoiceKey string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.userVoiceKey = userVoiceKey
|
||||
}
|
||||
}
|
||||
|
||||
// TLSListener sets TLS listener and option
|
||||
func TLSListener(s string, t bool) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.TLSListenerString = s
|
||||
@@ -115,12 +129,14 @@ func TLSListener(s string, t bool) OptionFn {
|
||||
|
||||
}
|
||||
|
||||
// ProfileListener sets profile listener
|
||||
func ProfileListener(s string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.ProfileListenerString = s
|
||||
}
|
||||
}
|
||||
|
||||
// WebPath sets web path
|
||||
func WebPath(s string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
if s[len(s)-1:] != "/" {
|
||||
@@ -131,6 +147,7 @@ func WebPath(s string) OptionFn {
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyPath sets proxy path
|
||||
func ProxyPath(s string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
if s[len(s)-1:] != "/" {
|
||||
@@ -141,12 +158,14 @@ func ProxyPath(s string) OptionFn {
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyPort sets proxy port
|
||||
func ProxyPort(s string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.proxyPort = s
|
||||
}
|
||||
}
|
||||
|
||||
// TempPath sets temp path
|
||||
func TempPath(s string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
if s[len(s)-1:] != "/" {
|
||||
@@ -157,6 +176,7 @@ func TempPath(s string) OptionFn {
|
||||
}
|
||||
}
|
||||
|
||||
// LogFile sets log file
|
||||
func LogFile(logger *log.Logger, s string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
f, err := os.OpenFile(s, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
@@ -169,30 +189,36 @@ func LogFile(logger *log.Logger, s string) OptionFn {
|
||||
}
|
||||
}
|
||||
|
||||
// Logger sets logger
|
||||
func Logger(logger *log.Logger) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// MaxUploadSize sets max upload size
|
||||
func MaxUploadSize(kbytes int64) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.maxUploadSize = kbytes * 1024
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// RateLimit set rate limit
|
||||
func RateLimit(requests int) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.rateLimitRequests = requests
|
||||
}
|
||||
}
|
||||
|
||||
// RandomTokenLength sets random token length
|
||||
func RandomTokenLength(length int) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.randomTokenLength = length
|
||||
}
|
||||
}
|
||||
|
||||
// Purge sets purge days and option
|
||||
func Purge(days, interval int) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.purgeDays = time.Duration(days) * time.Hour * 24
|
||||
@@ -200,24 +226,28 @@ func Purge(days, interval int) OptionFn {
|
||||
}
|
||||
}
|
||||
|
||||
func ForceHTTPs() OptionFn {
|
||||
// ForceHTTPS sets forcing https
|
||||
func ForceHTTPS() OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.forceHTTPs = true
|
||||
srvr.forceHTTPS = true
|
||||
}
|
||||
}
|
||||
|
||||
// EnableProfiler sets enable profiler
|
||||
func EnableProfiler() OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.profilerEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// UseStorage set storage to use
|
||||
func UseStorage(s Storage) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.storage = s
|
||||
}
|
||||
}
|
||||
|
||||
// UseLetsEncrypt set letsencrypt usage
|
||||
func UseLetsEncrypt(hosts []string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
cacheDir := "./cache/"
|
||||
@@ -246,6 +276,7 @@ func UseLetsEncrypt(hosts []string) OptionFn {
|
||||
}
|
||||
}
|
||||
|
||||
// TLSConfig sets TLS config
|
||||
func TLSConfig(cert, pk string) OptionFn {
|
||||
certificate, err := tls.LoadX509KeyPair(cert, pk)
|
||||
return func(srvr *Server) {
|
||||
@@ -257,13 +288,15 @@ func TLSConfig(cert, pk string) OptionFn {
|
||||
}
|
||||
}
|
||||
|
||||
func HttpAuthCredentials(user string, pass string) OptionFn {
|
||||
// HTTPAuthCredentials sets basic http auth credentials
|
||||
func HTTPAuthCredentials(user string, pass string) OptionFn {
|
||||
return func(srvr *Server) {
|
||||
srvr.AuthUser = user
|
||||
srvr.AuthPass = pass
|
||||
}
|
||||
}
|
||||
|
||||
// FilterOptions sets ip filtering
|
||||
func FilterOptions(options IPFilterOptions) OptionFn {
|
||||
for i, allowedIP := range options.AllowedIPs {
|
||||
options.AllowedIPs[i] = strings.TrimSpace(allowedIP)
|
||||
@@ -278,6 +311,7 @@ func FilterOptions(options IPFilterOptions) OptionFn {
|
||||
}
|
||||
}
|
||||
|
||||
// Server is the main application
|
||||
type Server struct {
|
||||
AuthUser string
|
||||
AuthPass string
|
||||
@@ -298,7 +332,7 @@ type Server struct {
|
||||
|
||||
storage Storage
|
||||
|
||||
forceHTTPs bool
|
||||
forceHTTPS bool
|
||||
|
||||
randomTokenLength int
|
||||
|
||||
@@ -312,6 +346,7 @@ type Server struct {
|
||||
webPath string
|
||||
proxyPath string
|
||||
proxyPort string
|
||||
emailContact string
|
||||
gaKey string
|
||||
userVoiceKey string
|
||||
|
||||
@@ -327,6 +362,7 @@ type Server struct {
|
||||
LetsEncryptCache string
|
||||
}
|
||||
|
||||
// New is the factory fot Server
|
||||
func New(options ...OptionFn) (*Server, error) {
|
||||
s := &Server{
|
||||
locks: sync.Map{},
|
||||
@@ -347,6 +383,7 @@ func init() {
|
||||
rand.Seed(int64(binary.LittleEndian.Uint64(seedBytes[:])))
|
||||
}
|
||||
|
||||
// Run starts Server
|
||||
func (s *Server) Run() {
|
||||
listening := false
|
||||
|
||||
@@ -402,7 +439,7 @@ func (s *Server) Run() {
|
||||
r.HandleFunc("/favicon.ico", staticHandler.ServeHTTP).Methods("GET")
|
||||
r.HandleFunc("/robots.txt", staticHandler.ServeHTTP).Methods("GET")
|
||||
|
||||
r.HandleFunc("/{filename:(?:favicon\\.ico|robots\\.txt|health\\.html)}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
|
||||
r.HandleFunc("/{filename:(?:favicon\\.ico|robots\\.txt|health\\.html)}", s.basicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
|
||||
|
||||
r.HandleFunc("/health.html", healthHandler).Methods("GET")
|
||||
r.HandleFunc("/", s.viewHandler).Methods("GET")
|
||||
@@ -446,10 +483,10 @@ func (s *Server) Run() {
|
||||
|
||||
r.HandleFunc("/{filename}/virustotal", s.virusTotalHandler).Methods("PUT")
|
||||
r.HandleFunc("/{filename}/scan", s.scanHandler).Methods("PUT")
|
||||
r.HandleFunc("/put/{filename}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
|
||||
r.HandleFunc("/upload/{filename}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
|
||||
r.HandleFunc("/{filename}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
|
||||
r.HandleFunc("/", s.BasicAuthHandler(http.HandlerFunc(s.postHandler))).Methods("POST")
|
||||
r.HandleFunc("/put/{filename}", s.basicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
|
||||
r.HandleFunc("/upload/{filename}", s.basicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
|
||||
r.HandleFunc("/{filename}", s.basicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
|
||||
r.HandleFunc("/", s.basicAuthHandler(http.HandlerFunc(s.postHandler))).Methods("POST")
|
||||
// r.HandleFunc("/{page}", viewHandler).Methods("GET")
|
||||
|
||||
r.HandleFunc("/{token}/{filename}/{deletionToken}", s.deleteHandler).Methods("DELETE")
|
||||
@@ -474,7 +511,7 @@ func (s *Server) Run() {
|
||||
}
|
||||
|
||||
h := handlers.PanicHandler(
|
||||
IPFilterHandler(
|
||||
ipFilterHandler(
|
||||
handlers.LogHandler(
|
||||
LoveHandler(
|
||||
s.RedirectHandler(cors(r))),
|
||||
|
@@ -27,31 +27,43 @@ import (
|
||||
"storj.io/uplink"
|
||||
)
|
||||
|
||||
// Storage is the interface for storage operation
|
||||
type Storage interface {
|
||||
// Get retrieves a file from storage
|
||||
Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error)
|
||||
// Head retrieves content length of a file from storage
|
||||
Head(token string, filename string) (contentLength uint64, err error)
|
||||
// Put saves a file on storage
|
||||
Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error
|
||||
// Delete removes a file from storage
|
||||
Delete(token string, filename string) error
|
||||
// IsNotExist indicates if a file doesn't exist on storage
|
||||
IsNotExist(err error) bool
|
||||
// Purge cleans up the storage
|
||||
Purge(days time.Duration) error
|
||||
|
||||
// Type returns the storage type
|
||||
Type() string
|
||||
}
|
||||
|
||||
// LocalStorage is a local storage
|
||||
type LocalStorage struct {
|
||||
Storage
|
||||
basedir string
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewLocalStorage is the factory for LocalStorage
|
||||
func NewLocalStorage(basedir string, logger *log.Logger) (*LocalStorage, error) {
|
||||
return &LocalStorage{basedir: basedir, logger: logger}, nil
|
||||
}
|
||||
|
||||
// Type returns the storage type
|
||||
func (s *LocalStorage) Type() string {
|
||||
return "local"
|
||||
}
|
||||
|
||||
// Head retrieves content length of a file from storage
|
||||
func (s *LocalStorage) Head(token string, filename string) (contentLength uint64, err error) {
|
||||
path := filepath.Join(s.basedir, token, filename)
|
||||
|
||||
@@ -65,6 +77,7 @@ func (s *LocalStorage) Head(token string, filename string) (contentLength uint64
|
||||
return
|
||||
}
|
||||
|
||||
// Get retrieves a file from storage
|
||||
func (s *LocalStorage) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) {
|
||||
path := filepath.Join(s.basedir, token, filename)
|
||||
|
||||
@@ -83,6 +96,7 @@ func (s *LocalStorage) Get(token string, filename string) (reader io.ReadCloser,
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes a file from storage
|
||||
func (s *LocalStorage) Delete(token string, filename string) (err error) {
|
||||
metadata := filepath.Join(s.basedir, token, fmt.Sprintf("%s.metadata", filename))
|
||||
os.Remove(metadata)
|
||||
@@ -92,6 +106,7 @@ func (s *LocalStorage) Delete(token string, filename string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Purge cleans up the storage
|
||||
func (s *LocalStorage) Purge(days time.Duration) (err error) {
|
||||
err = filepath.Walk(s.basedir,
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
@@ -113,6 +128,7 @@ func (s *LocalStorage) Purge(days time.Duration) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// IsNotExist indicates if a file doesn't exist on storage
|
||||
func (s *LocalStorage) IsNotExist(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
@@ -121,6 +137,7 @@ func (s *LocalStorage) IsNotExist(err error) bool {
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
|
||||
// Put saves a file on storage
|
||||
func (s *LocalStorage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error {
|
||||
var f io.WriteCloser
|
||||
var err error
|
||||
@@ -144,6 +161,7 @@ func (s *LocalStorage) Put(token string, filename string, reader io.Reader, cont
|
||||
return nil
|
||||
}
|
||||
|
||||
// S3Storage is a storage backed by AWS S3
|
||||
type S3Storage struct {
|
||||
Storage
|
||||
bucket string
|
||||
@@ -154,6 +172,7 @@ type S3Storage struct {
|
||||
noMultipart bool
|
||||
}
|
||||
|
||||
// NewS3Storage is the factory for S3Storage
|
||||
func NewS3Storage(accessKey, secretKey, bucketName string, purgeDays int, region, endpoint string, disableMultipart bool, forcePathStyle bool, logger *log.Logger) (*S3Storage, error) {
|
||||
sess := getAwsSession(accessKey, secretKey, region, endpoint, forcePathStyle)
|
||||
|
||||
@@ -167,10 +186,12 @@ func NewS3Storage(accessKey, secretKey, bucketName string, purgeDays int, region
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Type returns the storage type
|
||||
func (s *S3Storage) Type() string {
|
||||
return "s3"
|
||||
}
|
||||
|
||||
// Head retrieves content length of a file from storage
|
||||
func (s *S3Storage) Head(token string, filename string) (contentLength uint64, err error) {
|
||||
key := fmt.Sprintf("%s/%s", token, filename)
|
||||
|
||||
@@ -192,11 +213,13 @@ func (s *S3Storage) Head(token string, filename string) (contentLength uint64, e
|
||||
return
|
||||
}
|
||||
|
||||
// Purge cleans up the storage
|
||||
func (s *S3Storage) Purge(days time.Duration) (err error) {
|
||||
// NOOP expiration is set at upload time
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsNotExist indicates if a file doesn't exist on storage
|
||||
func (s *S3Storage) IsNotExist(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
@@ -212,6 +235,7 @@ func (s *S3Storage) IsNotExist(err error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get retrieves a file from storage
|
||||
func (s *S3Storage) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) {
|
||||
key := fmt.Sprintf("%s/%s", token, filename)
|
||||
|
||||
@@ -233,6 +257,7 @@ func (s *S3Storage) Get(token string, filename string) (reader io.ReadCloser, co
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes a file from storage
|
||||
func (s *S3Storage) Delete(token string, filename string) (err error) {
|
||||
metadata := fmt.Sprintf("%s/%s.metadata", token, filename)
|
||||
deleteRequest := &s3.DeleteObjectInput{
|
||||
@@ -256,6 +281,7 @@ func (s *S3Storage) Delete(token string, filename string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Put saves a file on storage
|
||||
func (s *S3Storage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) (err error) {
|
||||
key := fmt.Sprintf("%s/%s", token, filename)
|
||||
|
||||
@@ -273,27 +299,34 @@ func (s *S3Storage) Put(token string, filename string, reader io.Reader, content
|
||||
u.LeavePartsOnError = false
|
||||
})
|
||||
|
||||
var expire *time.Time
|
||||
if s.purgeDays.Hours() > 0 {
|
||||
expire = aws.Time(time.Now().Add(s.purgeDays))
|
||||
}
|
||||
|
||||
_, err = uploader.Upload(&s3manager.UploadInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(key),
|
||||
Body: reader,
|
||||
Expires: aws.Time(time.Now().Add(s.purgeDays)),
|
||||
Expires: expire,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GDrive is a storage backed by GDrive
|
||||
type GDrive struct {
|
||||
service *drive.Service
|
||||
rootId string
|
||||
rootID string
|
||||
basedir string
|
||||
localConfigPath string
|
||||
chunkSize int
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewGDriveStorage(clientJsonFilepath string, localConfigPath string, basedir string, chunkSize int, logger *log.Logger) (*GDrive, error) {
|
||||
b, err := ioutil.ReadFile(clientJsonFilepath)
|
||||
// NewGDriveStorage is the factory for GDrive
|
||||
func NewGDriveStorage(clientJSONFilepath string, localConfigPath string, basedir string, chunkSize int, logger *log.Logger) (*GDrive, error) {
|
||||
b, err := ioutil.ReadFile(clientJSONFilepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -310,7 +343,7 @@ func NewGDriveStorage(clientJsonFilepath string, localConfigPath string, basedir
|
||||
}
|
||||
|
||||
chunkSize = chunkSize * 1024 * 1024
|
||||
storage := &GDrive{service: srv, basedir: basedir, rootId: "", localConfigPath: localConfigPath, chunkSize: chunkSize, logger: logger}
|
||||
storage := &GDrive{service: srv, basedir: basedir, rootID: "", localConfigPath: localConfigPath, chunkSize: chunkSize, logger: logger}
|
||||
err = storage.setupRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -319,26 +352,26 @@ func NewGDriveStorage(clientJsonFilepath string, localConfigPath string, basedir
|
||||
return storage, nil
|
||||
}
|
||||
|
||||
const GDriveRootConfigFile = "root_id.conf"
|
||||
const GDriveTokenJsonFile = "token.json"
|
||||
const GDriveDirectoryMimeType = "application/vnd.google-apps.folder"
|
||||
const gdriveRootConfigFile = "root_id.conf"
|
||||
const gdriveTokenJSONFile = "token.json"
|
||||
const gdriveDirectoryMimeType = "application/vnd.google-apps.folder"
|
||||
|
||||
func (s *GDrive) setupRoot() error {
|
||||
rootFileConfig := filepath.Join(s.localConfigPath, GDriveRootConfigFile)
|
||||
rootFileConfig := filepath.Join(s.localConfigPath, gdriveRootConfigFile)
|
||||
|
||||
rootId, err := ioutil.ReadFile(rootFileConfig)
|
||||
rootID, err := ioutil.ReadFile(rootFileConfig)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if string(rootId) != "" {
|
||||
s.rootId = string(rootId)
|
||||
if string(rootID) != "" {
|
||||
s.rootID = string(rootID)
|
||||
return nil
|
||||
}
|
||||
|
||||
dir := &drive.File{
|
||||
Name: s.basedir,
|
||||
MimeType: GDriveDirectoryMimeType,
|
||||
MimeType: gdriveDirectoryMimeType,
|
||||
}
|
||||
|
||||
di, err := s.service.Files.Create(dir).Fields("id").Do()
|
||||
@@ -346,8 +379,8 @@ func (s *GDrive) setupRoot() error {
|
||||
return err
|
||||
}
|
||||
|
||||
s.rootId = di.Id
|
||||
err = ioutil.WriteFile(rootFileConfig, []byte(s.rootId), os.FileMode(0600))
|
||||
s.rootID = di.Id
|
||||
err = ioutil.WriteFile(rootFileConfig, []byte(s.rootID), os.FileMode(0600))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -363,13 +396,13 @@ func (s *GDrive) list(nextPageToken string, q string) (*drive.FileList, error) {
|
||||
return s.service.Files.List().Fields("nextPageToken, files(id, name, mimeType)").Q(q).PageToken(nextPageToken).Do()
|
||||
}
|
||||
|
||||
func (s *GDrive) findId(filename string, token string) (string, error) {
|
||||
func (s *GDrive) findID(filename string, token string) (string, error) {
|
||||
filename = strings.Replace(filename, `'`, `\'`, -1)
|
||||
filename = strings.Replace(filename, `"`, `\"`, -1)
|
||||
|
||||
fileId, tokenId, nextPageToken := "", "", ""
|
||||
fileID, tokenID, nextPageToken := "", "", ""
|
||||
|
||||
q := fmt.Sprintf("'%s' in parents and name='%s' and mimeType='%s' and trashed=false", s.rootId, token, GDriveDirectoryMimeType)
|
||||
q := fmt.Sprintf("'%s' in parents and name='%s' and mimeType='%s' and trashed=false", s.rootID, token, gdriveDirectoryMimeType)
|
||||
l, err := s.list(nextPageToken, q)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -377,7 +410,7 @@ func (s *GDrive) findId(filename string, token string) (string, error) {
|
||||
|
||||
for 0 < len(l.Files) {
|
||||
for _, fi := range l.Files {
|
||||
tokenId = fi.Id
|
||||
tokenID = fi.Id
|
||||
break
|
||||
}
|
||||
|
||||
@@ -386,15 +419,18 @@ func (s *GDrive) findId(filename string, token string) (string, error) {
|
||||
}
|
||||
|
||||
l, err = s.list(l.NextPageToken, q)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if filename == "" {
|
||||
return tokenId, nil
|
||||
} else if tokenId == "" {
|
||||
return tokenID, nil
|
||||
} else if tokenID == "" {
|
||||
return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
|
||||
}
|
||||
|
||||
q = fmt.Sprintf("'%s' in parents and name='%s' and mimeType!='%s' and trashed=false", tokenId, filename, GDriveDirectoryMimeType)
|
||||
q = fmt.Sprintf("'%s' in parents and name='%s' and mimeType!='%s' and trashed=false", tokenID, filename, gdriveDirectoryMimeType)
|
||||
l, err = s.list(nextPageToken, q)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -403,7 +439,7 @@ func (s *GDrive) findId(filename string, token string) (string, error) {
|
||||
for 0 < len(l.Files) {
|
||||
for _, fi := range l.Files {
|
||||
|
||||
fileId = fi.Id
|
||||
fileID = fi.Id
|
||||
break
|
||||
}
|
||||
|
||||
@@ -412,28 +448,33 @@ func (s *GDrive) findId(filename string, token string) (string, error) {
|
||||
}
|
||||
|
||||
l, err = s.list(l.NextPageToken, q)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if fileId == "" {
|
||||
if fileID == "" {
|
||||
return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
|
||||
}
|
||||
|
||||
return fileId, nil
|
||||
return fileID, nil
|
||||
}
|
||||
|
||||
// Type returns the storage type
|
||||
func (s *GDrive) Type() string {
|
||||
return "gdrive"
|
||||
}
|
||||
|
||||
// Head retrieves content length of a file from storage
|
||||
func (s *GDrive) Head(token string, filename string) (contentLength uint64, err error) {
|
||||
var fileId string
|
||||
fileId, err = s.findId(filename, token)
|
||||
var fileID string
|
||||
fileID, err = s.findID(filename, token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var fi *drive.File
|
||||
if fi, err = s.service.Files.Get(fileId).Fields("size").Do(); err != nil {
|
||||
if fi, err = s.service.Files.Get(fileID).Fields("size").Do(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -442,15 +483,16 @@ func (s *GDrive) Head(token string, filename string) (contentLength uint64, err
|
||||
return
|
||||
}
|
||||
|
||||
// Get retrieves a file from storage
|
||||
func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) {
|
||||
var fileId string
|
||||
fileId, err = s.findId(filename, token)
|
||||
var fileID string
|
||||
fileID, err = s.findID(filename, token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var fi *drive.File
|
||||
fi, err = s.service.Files.Get(fileId).Fields("size", "md5Checksum").Do()
|
||||
fi, err = s.service.Files.Get(fileID).Fields("size", "md5Checksum").Do()
|
||||
if !s.hasChecksum(fi) {
|
||||
err = fmt.Errorf("Cannot find file %s/%s", token, filename)
|
||||
return
|
||||
@@ -460,7 +502,7 @@ func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, conte
|
||||
|
||||
ctx := context.Background()
|
||||
var res *http.Response
|
||||
res, err = s.service.Files.Get(fileId).Context(ctx).Download()
|
||||
res, err = s.service.Files.Get(fileID).Context(ctx).Download()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -470,25 +512,27 @@ func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, conte
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes a file from storage
|
||||
func (s *GDrive) Delete(token string, filename string) (err error) {
|
||||
metadata, _ := s.findId(fmt.Sprintf("%s.metadata", filename), token)
|
||||
metadata, _ := s.findID(fmt.Sprintf("%s.metadata", filename), token)
|
||||
s.service.Files.Delete(metadata).Do()
|
||||
|
||||
var fileId string
|
||||
fileId, err = s.findId(filename, token)
|
||||
var fileID string
|
||||
fileID, err = s.findID(filename, token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = s.service.Files.Delete(fileId).Do()
|
||||
err = s.service.Files.Delete(fileID).Do()
|
||||
return
|
||||
}
|
||||
|
||||
// Purge cleans up the storage
|
||||
func (s *GDrive) Purge(days time.Duration) (err error) {
|
||||
nextPageToken := ""
|
||||
|
||||
expirationDate := time.Now().Add(-1 * days).Format(time.RFC3339)
|
||||
q := fmt.Sprintf("'%s' in parents and modifiedTime < '%s' and mimeType!='%s' and trashed=false", s.rootId, expirationDate, GDriveDirectoryMimeType)
|
||||
q := fmt.Sprintf("'%s' in parents and modifiedTime < '%s' and mimeType!='%s' and trashed=false", s.rootID, expirationDate, gdriveDirectoryMimeType)
|
||||
l, err := s.list(nextPageToken, q)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -507,32 +551,39 @@ func (s *GDrive) Purge(days time.Duration) (err error) {
|
||||
}
|
||||
|
||||
l, err = s.list(l.NextPageToken, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsNotExist indicates if a file doesn't exist on storage
|
||||
func (s *GDrive) IsNotExist(err error) bool {
|
||||
if err != nil {
|
||||
if e, ok := err.(*googleapi.Error); ok {
|
||||
return e.Code == http.StatusNotFound
|
||||
}
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if e, ok := err.(*googleapi.Error); ok {
|
||||
return e.Code == http.StatusNotFound
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Put saves a file on storage
|
||||
func (s *GDrive) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error {
|
||||
dirId, err := s.findId("", token)
|
||||
dirID, err := s.findID("", token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dirId == "" {
|
||||
if dirID == "" {
|
||||
dir := &drive.File{
|
||||
Name: token,
|
||||
Parents: []string{s.rootId},
|
||||
MimeType: GDriveDirectoryMimeType,
|
||||
Parents: []string{s.rootID},
|
||||
MimeType: gdriveDirectoryMimeType,
|
||||
}
|
||||
|
||||
di, err := s.service.Files.Create(dir).Fields("id").Do()
|
||||
@@ -540,13 +591,13 @@ func (s *GDrive) Put(token string, filename string, reader io.Reader, contentTyp
|
||||
return err
|
||||
}
|
||||
|
||||
dirId = di.Id
|
||||
dirID = di.Id
|
||||
}
|
||||
|
||||
// Instantiate empty drive file
|
||||
dst := &drive.File{
|
||||
Name: filename,
|
||||
Parents: []string{dirId},
|
||||
Parents: []string{dirID},
|
||||
MimeType: contentType,
|
||||
}
|
||||
|
||||
@@ -562,7 +613,7 @@ func (s *GDrive) Put(token string, filename string, reader io.Reader, contentTyp
|
||||
|
||||
// Retrieve a token, saves the token, then returns the generated client.
|
||||
func getGDriveClient(config *oauth2.Config, localConfigPath string, logger *log.Logger) *http.Client {
|
||||
tokenFile := filepath.Join(localConfigPath, GDriveTokenJsonFile)
|
||||
tokenFile := filepath.Join(localConfigPath, gdriveTokenJSONFile)
|
||||
tok, err := gDriveTokenFromFile(tokenFile)
|
||||
if err != nil {
|
||||
tok = getGDriveTokenFromWeb(config, logger)
|
||||
@@ -614,6 +665,7 @@ func saveGDriveToken(path string, token *oauth2.Token, logger *log.Logger) {
|
||||
json.NewEncoder(f).Encode(token)
|
||||
}
|
||||
|
||||
// StorjStorage is a storage backed by Storj
|
||||
type StorjStorage struct {
|
||||
Storage
|
||||
project *uplink.Project
|
||||
@@ -622,6 +674,7 @@ type StorjStorage struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewStorjStorage is the factory for StorjStorage
|
||||
func NewStorjStorage(access, bucket string, purgeDays int, logger *log.Logger) (*StorjStorage, error) {
|
||||
var instance StorjStorage
|
||||
var err error
|
||||
@@ -652,10 +705,12 @@ func NewStorjStorage(access, bucket string, purgeDays int, logger *log.Logger) (
|
||||
return &instance, nil
|
||||
}
|
||||
|
||||
// Type returns the storage type
|
||||
func (s *StorjStorage) Type() string {
|
||||
return "storj"
|
||||
}
|
||||
|
||||
// Head retrieves content length of a file from storage
|
||||
func (s *StorjStorage) Head(token string, filename string) (contentLength uint64, err error) {
|
||||
key := storj.JoinPaths(token, filename)
|
||||
|
||||
@@ -671,6 +726,7 @@ func (s *StorjStorage) Head(token string, filename string) (contentLength uint64
|
||||
return
|
||||
}
|
||||
|
||||
// Get retrieves a file from storage
|
||||
func (s *StorjStorage) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) {
|
||||
key := storj.JoinPaths(token, filename)
|
||||
|
||||
@@ -689,6 +745,7 @@ func (s *StorjStorage) Get(token string, filename string) (reader io.ReadCloser,
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes a file from storage
|
||||
func (s *StorjStorage) Delete(token string, filename string) (err error) {
|
||||
key := storj.JoinPaths(token, filename)
|
||||
|
||||
@@ -701,11 +758,13 @@ func (s *StorjStorage) Delete(token string, filename string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Purge cleans up the storage
|
||||
func (s *StorjStorage) Purge(days time.Duration) (err error) {
|
||||
// NOOP expiration is set at upload time
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put saves a file on storage
|
||||
func (s *StorjStorage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) (err error) {
|
||||
key := storj.JoinPaths(token, filename)
|
||||
|
||||
@@ -713,7 +772,12 @@ func (s *StorjStorage) Put(token string, filename string, reader io.Reader, cont
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
writer, err := s.project.UploadObject(ctx, s.bucket.Name, key, &uplink.UploadOptions{Expires: time.Now().Add(s.purgeDays)})
|
||||
var uploadOptions *uplink.UploadOptions
|
||||
if s.purgeDays.Hours() > 0 {
|
||||
uploadOptions = &uplink.UploadOptions{Expires: time.Now().Add(s.purgeDays)}
|
||||
}
|
||||
|
||||
writer, err := s.project.UploadObject(ctx, s.bucket.Name, key, uploadOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -735,6 +799,7 @@ func (s *StorjStorage) Put(token string, filename string, reader io.Reader, cont
|
||||
return err
|
||||
}
|
||||
|
||||
// IsNotExist indicates if a file doesn't exist on storage
|
||||
func (s *StorjStorage) IsNotExist(err error) bool {
|
||||
return errors.Is(err, uplink.ErrObjectNotFound)
|
||||
}
|
||||
|
@@ -29,12 +29,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// characters used for short-urls
|
||||
// SYMBOLS characters used for short-urls
|
||||
SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
)
|
||||
|
||||
// generate a token
|
||||
func Token(length int) string {
|
||||
func token(length int) string {
|
||||
result := ""
|
||||
for i := 0; i < length; i++ {
|
||||
x := rand.Intn(len(SYMBOLS) - 1)
|
||||
|
@@ -4,12 +4,12 @@ import "testing"
|
||||
|
||||
func BenchmarkTokenConcat(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Token(5) + Token(5)
|
||||
_ = token(5) + token(5)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTokenLonger(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Token(10)
|
||||
_ = token(10)
|
||||
}
|
||||
}
|
||||
|
@@ -50,7 +50,7 @@ func getAwsSession(accessKey, secretKey, region, endpoint string, forcePathStyle
|
||||
}
|
||||
|
||||
func formatNumber(format string, s uint64) string {
|
||||
return RenderFloat(format, float64(s))
|
||||
return renderFloat(format, float64(s))
|
||||
}
|
||||
|
||||
var renderFloatPrecisionMultipliers = [10]float64{
|
||||
@@ -79,7 +79,7 @@ var renderFloatPrecisionRounders = [10]float64{
|
||||
0.0000000005,
|
||||
}
|
||||
|
||||
func RenderFloat(format string, n float64) string {
|
||||
func renderFloat(format string, n float64) string {
|
||||
// Special cases:
|
||||
// NaN = "NaN"
|
||||
// +Inf = "+Infinity"
|
||||
@@ -127,7 +127,7 @@ func RenderFloat(format string, n float64) string {
|
||||
// +0000
|
||||
if formatDirectiveIndices[0] == 0 {
|
||||
if formatDirectiveChars[formatDirectiveIndices[0]] != '+' {
|
||||
panic("RenderFloat(): invalid positive sign directive")
|
||||
panic("renderFloat(): invalid positive sign directive")
|
||||
}
|
||||
positiveStr = "+"
|
||||
formatDirectiveIndices = formatDirectiveIndices[1:]
|
||||
@@ -141,7 +141,7 @@ func RenderFloat(format string, n float64) string {
|
||||
// 000,000.00
|
||||
if len(formatDirectiveIndices) == 2 {
|
||||
if (formatDirectiveIndices[1] - formatDirectiveIndices[0]) != 4 {
|
||||
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||||
panic("renderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||||
}
|
||||
thousandStr = string(formatDirectiveChars[formatDirectiveIndices[0]])
|
||||
formatDirectiveIndices = formatDirectiveIndices[1:]
|
||||
@@ -201,8 +201,8 @@ func RenderFloat(format string, n float64) string {
|
||||
return signStr + intStr + decimalStr + fracStr
|
||||
}
|
||||
|
||||
func RenderInteger(format string, n int) string {
|
||||
return RenderFloat(format, float64(n))
|
||||
func renderInteger(format string, n int) string {
|
||||
return renderFloat(format, float64(n))
|
||||
}
|
||||
|
||||
// Request.RemoteAddress contains port, which we want to remove i.e.:
|
||||
|
@@ -29,7 +29,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
_ "github.com/PuerkitoBio/ghost/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
virustotal "github.com/dutchcoders/go-virustotal"
|
||||
|
Reference in New Issue
Block a user