Compare commits

...

43 Commits

Author SHA1 Message Date
Andrea Spacca
acd6fb084f remove tor, remove bitcoing, fix contact us (#447) 2021-12-15 21:06:54 +01:00
Andrea Spacca
6f49951bc0 master to main, what's left 2021-12-07 20:24:58 +01:00
Andrea Spacca
d2a0e77814 fix in force-https redirect (#441) 2021-12-07 19:41:42 +01:00
Andrea Spacca
014b95ff07 fix typo in flake.nix 2021-12-07 13:39:53 +01:00
干志雄
0eec27586d Add uploading and copy download command (#412)
* Add uploading and copy download command
2021-11-13 09:16:53 +01:00
kugiyasan
c7164856d2 issue #420 added MaxDate.IsZero() check (#427)
* issue #420 return 400 response when Max-Days is too big

* issue #420 moved the Max-Days check before saving the metadata

* issue #420 added a logging message when Max-Days is invalid

* issue #420 added MaxDate.IsZero() check

Co-authored-by: kugiyasan <kugiyasan@users.noreply.github.com>
2021-10-30 10:04:43 +02:00
Andrea Spacca
96723b2685 fix go1.16, add go1.17 2021-10-26 19:34:40 +02:00
Andrea Spacca
4a56bad05f bump bluemonday 2021-10-26 19:12:34 +02:00
Andrea Spacca
fa74be02d2 Revert "issue #420 return 400 response when Max-Days is too big (#422)" (#426)
This reverts commit 2959fc2992.
2021-10-26 19:03:51 +02:00
kugiyasan
2959fc2992 issue #420 return 400 response when Max-Days is too big (#422)
* issue #420 return 400 response when Max-Days is too big

Co-authored-by: kugiyasan <kugiyasan@users.noreply.github.com>
2021-10-25 18:33:18 +02:00
Yannik Sander
694b3ce246 Implement Nix Flake (#424)
* Implement flake
2021-10-24 09:42:38 +02:00
Masaya Watanabe
92948fab23 fix missed errors (#417)
Co-authored-by: Andrea Spacca <andrea.spacca@gmail.com>
2021-10-16 09:57:59 +02:00
mattn
e5455d9713 Fix path (#416)
* chan is leaked

* Remove path.Clean

Co-authored-by: Andrea Spacca <andrea.spacca@gmail.com>
2021-10-16 09:51:09 +02:00
Soumil Arora
6ab75b30e5 Edited code of condunct for more information and corrected a grammatical error (#421) 2021-10-16 09:36:02 +02:00
Christian Leo-Pernold
6b8eff8322 Update README.md (#415)
* Update README.md

Add minor grammar improvements.

* Update README.md

Fix grammar.
2021-10-08 10:55:56 +02:00
Stefan Benten
2da62eb235 Merge pull request #414 from modem7/patch-1 2021-10-03 12:51:02 +02:00
modem7
156daa5a24 Update README.md
Edited capitalization of some sections to better follow the rest of the readme.
2021-10-03 02:56:17 +01:00
Andrea Spacca
e97fdcb293 remove build on master 2021-08-20 08:07:12 +02:00
Andrea Spacca
9fe5f9a5c9 Golint (#404)
* golint
* remove gitter badge
2021-08-19 22:45:30 +02:00
wc7086
788dfa203f Build more binary releases (#395)
* Build more binary releases
2021-08-19 20:57:42 +02:00
Stefan Benten
440ccf3a57 Merge pull request #399 from dutchcoders/ISSUE-398
s3 and storj storage: use nil when purgeDays is 0
2021-08-15 11:50:26 +02:00
Andrea Spacca
224f8dca42 s3 and storj storage: use nill when purgeDays is 0 2021-08-15 10:40:58 +02:00
Stefan Benten
d16a6cc25e Merge pull request #393 from dutchcoders/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.5
Bump github.com/microcosm-cc/bluemonday from 1.0.4 to 1.0.5
2021-08-01 22:58:09 +02:00
dependabot[bot]
49a083d8a2 Bump github.com/microcosm-cc/bluemonday from 1.0.4 to 1.0.5
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.4...v1.0.5)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-26 09:07:59 +00:00
Stefan Benten
f38eb6fb77 Multiple fixes 2021-07-26 10:59:28 +02:00
Andrea Spacca
8f5a0a80f3 bump trasfer.sh-web 2021-07-23 11:46:00 +02:00
Andrea Spacca
a895846bbc bump trasfer.sh-web 2021-07-23 11:37:09 +02:00
Andrea Spacca
3990c3c5b7 gofmt 2021-07-23 11:21:05 +02:00
Andrea Spacca
3ea4ffd0e3 cr fixes and dynamic upload size in UI 2021-07-23 11:20:49 +02:00
Andrea Spacca
88003018e6 deletion token in UI 2021-07-19 21:03:41 +02:00
Andrea Spacca
9baefbc4bf purge time in ui, fix range regeression from videojs 2021-07-19 21:03:41 +02:00
Andrea Spacca
e9148e0c20 remove unused const 2021-07-19 21:03:41 +02:00
Andrea Spacca
2ecedb93c6 rename file 2021-07-19 21:03:41 +02:00
Andrea Spacca
1ff9ccd01e copyright update 2021-07-19 21:03:41 +02:00
Andrea Spacca
ca17555c22 forgot dockerfile 2021-07-19 21:03:41 +02:00
Andrea Spacca
a7a3e685fe gofmt 2021-07-19 21:03:41 +02:00
Andrea Spacca
94f94f27ff rename benchmark 2021-07-19 21:03:41 +02:00
Andrea Spacca
69d979ca48 issue 380 and simplier token generation 2021-07-19 21:03:41 +02:00
Andrea Spacca
d9198e78db always use logger 2021-07-19 21:03:41 +02:00
Andrea Spacca
811679f8fb make version on build time 2021-07-19 21:03:41 +02:00
Tom Byrer
ce0c9b8915 Un-hyperlink malware (#388)
Don't want people to accidently click on the malware site.  :)
2021-07-18 10:40:13 +02:00
Andrea Spacca
c93c585566 ISSUE-382-take2 (#385) 2021-07-06 19:50:42 +02:00
Andrea Spacca
0eb3ba9ff8 ISSUE-382 (#383) 2021-07-06 18:52:38 +02:00
28 changed files with 1094 additions and 489 deletions

View File

@@ -6,7 +6,6 @@ bin
*.pyc *.pyc
*.egg-info *.egg-info
.vagrant .vagrant
.git
.tmp .tmp
bower_components bower_components
node_modules node_modules

33
.github/build/friendly-filenames.json vendored Normal file
View 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" }
}

View File

@@ -4,9 +4,9 @@ on:
schedule: schedule:
- cron: '0 0 * * *' # everyday at midnight UTC - cron: '0 0 * * *' # everyday at midnight UTC
pull_request: pull_request:
branches: master branches: main
push: push:
branches: master branches: main
tags: tags:
- v* - v*

View File

@@ -1,58 +1,171 @@
name: release name: Build and Release
on: on:
push: workflow_dispatch:
tags: release:
- v* types: [published]
jobs: jobs:
build: build:
runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
GOOS: [ darwin, linux, windows ] # Include amd64 on all platforms.
GOARCH: [ amd64 ] 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: include:
- GOOS: darwin # BEIGIN MacOS ARM64
GOARCH: amd64 - goos: darwin
suffix: darwin-amd64 goarch: arm64
- GOOS: windows # END MacOS ARM64
GOARCH: amd64 # BEGIN Linux ARM 5 6 7
suffix: windows-amd64.exe - goos: linux
- GOOS: linux goarch: arm
GOARCH: amd64 goarm: 7
suffix: linux-amd64 - goos: linux
- GOOS: linux goarch: arm
GOARCH: arm goarm: 6
suffix: linux-armv7 - goos: linux
steps: goarch: arm
- uses: actions/checkout@v2 goarm: 5
- uses: actions/setup-go@v2 # END Linux ARM 5 6 7
with: # BEGIN Android ARM 8
go-version: 1.16.x - goos: android
- name: Build ${{ matrix.suffix }} (GOOS=${{ matrix.GOOS }}, GOARCH=${{ matrix.GOARCH }}) goarch: arm64
env: # END Android ARM 8
GOOS: ${{ matrix.GOOS }} # Windows ARM 7
GOARCH: ${{ matrix.GOARCH }} - goos: windows
run: | goarch: arm
go version goarm: 7
go build -o ./artifacts/transfersh-${GITHUB_REF##*/}-${{ matrix.suffix }} # BEGIN Other architectures
- uses: actions/upload-artifact@v2 # BEGIN riscv64 & ARM64
name: Upload artifacts - goos: linux
with: goarch: arm64
name: artifacts - goos: linux
path: ./artifacts 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 runs-on: ubuntu-latest
needs: [ build ] env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm }}
CGO_ENABLED: 0
steps: steps:
- uses: actions/download-artifact@v2 - name: Checkout codebase
name: Download artifacts 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: with:
name: artifacts go-version: ^1.17
path: ./artifacts
- name: Publish artifacts - 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 uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with: 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: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -17,6 +17,7 @@ jobs:
- 1.14.x - 1.14.x
- 1.15.x - 1.15.x
- 1.16.x - 1.16.x
- 1.17.x
name: Test with ${{ matrix.go_version }} name: Test with ${{ matrix.go_version }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

2
.gitignore vendored
View File

@@ -20,3 +20,5 @@ transfersh-server/run.sh
.elasticbeanstalk/* .elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml !.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml !.elasticbeanstalk/*.global.yml
!.github/build/

View File

@@ -13,6 +13,7 @@ Examples of unacceptable behavior by participants include:
* Public or private harassment * Public or private harassment
* Publishing other's private information, such as physical or electronic addresses, without explicit permission * Publishing other's private information, such as physical or electronic addresses, without explicit permission
* Other unethical or unprofessional conduct * 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. 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. 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

View File

@@ -1,5 +1,5 @@
# Default to Go 1.15 # Default to Go 1.17
ARG GO_VERSION=1.15 ARG GO_VERSION=1.17
FROM golang:${GO_VERSION}-alpine as build FROM golang:${GO_VERSION}-alpine as build
# Necessary to run 'go get' and to compile the linked binary # Necessary to run 'go get' and to compile the linked binary
@@ -12,7 +12,7 @@ WORKDIR /go/src/github.com/dutchcoders/transfer.sh
ENV GO111MODULE=on ENV GO111MODULE=on
# build & install server # build & install server
RUN go get -u ./... && CGO_ENABLED=0 go build -tags netgo -ldflags '-a -s -w -extldflags "-static"' -o /go/bin/transfersh github.com/dutchcoders/transfer.sh RUN CGO_ENABLED=0 go build -tags netgo -ldflags "-X github.com/dutchcoders/transfer.sh/cmd.Version=$(git describe --tags) -a -s -w -extldflags '-static'" -o /go/bin/transfersh
FROM scratch AS final FROM scratch AS final
LABEL maintainer="Andrea Spacca <andrea.spacca@gmail.com>" LABEL maintainer="Andrea Spacca <andrea.spacca@gmail.com>"

View File

@@ -1,6 +1,8 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014-2018 DutchCoders [https://github.com/dutchcoders/] Copyright (c) 2014-2018 DutchCoders [https://github.com/dutchcoders/]
Copyright (c) 2018-2020 Andrea Spacca.
Copyright (c) 2020- Andrea Spacca and Stefan Benten.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,4 +1,4 @@
# transfer.sh [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dutchcoders/transfer.sh?utm_source=badge&utm_medium=badge&utm_campaign=&utm_campaign=pr-badge&utm_content=badge) [![Go Report Card](https://goreportcard.com/badge/github.com/dutchcoders/transfer.sh)](https://goreportcard.com/report/github.com/dutchcoders/transfer.sh) [![Docker pulls](https://img.shields.io/docker/pulls/dutchcoders/transfer.sh.svg)](https://hub.docker.com/r/dutchcoders/transfer.sh/) [![Build Status](https://github.com/dutchcoders/transfer.sh/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/dutchcoders/transfer.sh/actions/workflows/test.yml?query=branch%3Amaster) # transfer.sh [![Go Report Card](https://goreportcard.com/badge/github.com/dutchcoders/transfer.sh)](https://goreportcard.com/report/github.com/dutchcoders/transfer.sh) [![Docker pulls](https://img.shields.io/docker/pulls/dutchcoders/transfer.sh.svg)](https://hub.docker.com/r/dutchcoders/transfer.sh/) [![Build Status](https://github.com/dutchcoders/transfer.sh/actions/workflows/test.yml/badge.svg?branch=main)](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. Easy and fast file sharing from the command-line. This code contains the server with everything you need to create your own instance.
@@ -6,7 +6,7 @@ Transfer.sh currently supports the s3 (Amazon S3), gdrive (Google Drive), storj
## Disclaimer ## Disclaimer
The service at https://transfersh.com is of unknown origin and reported as cloud malware. The service at transfersh.com is of unknown origin and reported as cloud malware.
## Usage ## Usage
@@ -15,17 +15,17 @@ The service at https://transfersh.com is of unknown origin and reported as cloud
$ curl --upload-file ./hello.txt https://transfer.sh/hello.txt $ curl --upload-file ./hello.txt https://transfer.sh/hello.txt
``` ```
### Encrypt & upload: ### Encrypt & Upload:
```bash ```bash
$ cat /tmp/hello.txt|gpg -ac -o-|curl -X PUT --upload-file "-" https://transfer.sh/test.txt $ cat /tmp/hello.txt|gpg -ac -o-|curl -X PUT --upload-file "-" https://transfer.sh/test.txt
```` ````
### Download & decrypt: ### Download & Decrypt:
```bash ```bash
$ curl https://transfer.sh/1lDau/test.txt|gpg -o- > /tmp/hello.txt $ curl https://transfer.sh/1lDau/test.txt|gpg -o- > /tmp/hello.txt
``` ```
### Upload to virustotal: ### Upload to Virustotal:
```bash ```bash
$ curl -X PUT --upload-file nhgbhhj https://transfer.sh/test.txt/virustotal $ 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 ### 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 ```bash
curl -sD - --upload-file ./hello https://transfer.sh/hello.txt | grep 'X-Url-Delete' 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 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 | 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-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 | 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 | ga-key | google analytics key for the front end | | GA_KEY |
provider | which storage provider to use | (s3, storj, gdrive or local) | provider | which storage provider to use | (s3, storj, gdrive or local) |
uservoice-key | user voice key for the front end | | USERVOICE_KEY | 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 ## 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` - provider `--provider storj`
- storj-access _(either via flag or environment variable STORJ_ACCESS)_ - storj-access _(either via flag or environment variable STORJ_ACCESS)_
- storj-bucket _(either via flag or environment variable STORJ_BUCKET)_ - storj-bucket _(either via flag or environment variable STORJ_BUCKET)_
### Creating Bucket and Scope ### 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. 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. 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!** **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. 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. It is recommended to provide both the access grant and the bucket name as ENV Variables for enhanced security.
Example: Example:
``` ```
@@ -196,8 +197,7 @@ For the usage with Google drive, you need to specify the following options:
### Creating Gdrive Client Json ### Creating Gdrive Client Json
You need to create a Oauth Client id from console.cloud.google.com You need to create an OAuth Client id from console.cloud.google.com, download the file, and place it into a safe directory.
download the file and place into a safe directory
### Usage example ### Usage example
@@ -221,7 +221,10 @@ Contributions are welcome.
**Stefan Benten** **Stefan Benten**
## Copyright and license ## Copyright and License
Code and documentation copyright 2011-2018 Remco Verhoef. Code and documentation copyright 2011-2018 Remco Verhoef.
Code and documentation copyright 2018-2020 Andrea Spacca.
Code and documentation copyright 2020- Andrea Spacca and Stefan Benten.
Code released under [the MIT license](LICENSE). Code released under [the MIT license](LICENSE).

View File

@@ -12,7 +12,8 @@ import (
"google.golang.org/api/googleapi" "google.golang.org/api/googleapi"
) )
var Version = "1.2.4" // Version is inject at build time
var Version = "0.0.0"
var helpTemplate = `NAME: var helpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
@@ -97,6 +98,12 @@ var globalFlags = []cli.Flag{
Value: "", Value: "",
EnvVar: "PROXY_PORT", EnvVar: "PROXY_PORT",
}, },
cli.StringFlag{
Name: "email-contact",
Usage: "email address to link in Contact Us (front end)",
Value: "",
EnvVar: "EMAIL_CONTACT",
},
cli.StringFlag{ cli.StringFlag{
Name: "ga-key", Name: "ga-key",
Usage: "key for google analytics (front end)", Usage: "key for google analytics (front end)",
@@ -274,7 +281,7 @@ var globalFlags = []cli.Flag{
Value: "", Value: "",
EnvVar: "CORS_DOMAINS", EnvVar: "CORS_DOMAINS",
}, },
cli.Int64Flag{ cli.IntFlag{
Name: "random-token-length", Name: "random-token-length",
Usage: "", Usage: "",
Value: 6, Value: 6,
@@ -282,14 +289,16 @@ var globalFlags = []cli.Flag{
}, },
} }
// Cmd wraps cli.app
type Cmd struct { type Cmd struct {
*cli.App *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))) 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 { func New() *Cmd {
logger := log.New(os.Stdout, "[transfer.sh]", log.LstdFlags) logger := log.New(os.Stdout, "[transfer.sh]", log.LstdFlags)
@@ -304,7 +313,7 @@ func New() *Cmd {
app.Commands = []cli.Command{ app.Commands = []cli.Command{
{ {
Name: "version", Name: "version",
Action: VersionAction, Action: versionAction,
}, },
} }
@@ -345,6 +354,10 @@ func New() *Cmd {
options = append(options, server.ProxyPort(v)) 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 != "" { if v := c.String("ga-key"); v != "" {
options = append(options, server.GoogleAnalytics(v)) options = append(options, server.GoogleAnalytics(v))
} }
@@ -383,7 +396,7 @@ func New() *Cmd {
options = append(options, server.RateLimit(v)) options = append(options, server.RateLimit(v))
} }
v := c.Int64("random-token-length") v := c.Int("random-token-length")
options = append(options, server.RandomTokenLength(v)) options = append(options, server.RandomTokenLength(v))
purgeDays := c.Int("purge-days") purgeDays := c.Int("purge-days")
@@ -403,13 +416,13 @@ func New() *Cmd {
} }
if c.Bool("force-https") { if c.Bool("force-https") {
options = append(options, server.ForceHTTPs()) options = append(options, server.ForceHTTPS())
} }
if httpAuthUser := c.String("http-auth-user"); httpAuthUser == "" { if httpAuthUser := c.String("http-auth-user"); httpAuthUser == "" {
} else if httpAuthPass := c.String("http-auth-pass"); httpAuthPass == "" { } else if httpAuthPass := c.String("http-auth-pass"); httpAuthPass == "" {
} else { } else {
options = append(options, server.HttpAuthCredentials(httpAuthUser, httpAuthPass)) options = append(options, server.HTTPAuthCredentials(httpAuthUser, httpAuthPass))
} }
applyIPFilter := false applyIPFilter := false
@@ -445,13 +458,13 @@ func New() *Cmd {
case "gdrive": case "gdrive":
chunkSize := c.Int("gdrive-chunk-size") 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.") panic("client-json-filepath not set.")
} else if localConfigPath := c.String("gdrive-local-config-path"); localConfigPath == "" { } else if localConfigPath := c.String("gdrive-local-config-path"); localConfigPath == "" {
panic("local-config-path not set.") panic("local-config-path not set.")
} else if basedir := c.String("basedir"); basedir == "" { } else if basedir := c.String("basedir"); basedir == "" {
panic("basedir not set.") 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) panic(err)
} else { } else {
options = append(options, server.UseStorage(storage)) options = append(options, server.UseStorage(storage))

View File

@@ -5,6 +5,7 @@
* [Archiving and backups](#archiving-and-backups) * [Archiving and backups](#archiving-and-backups)
* [Encrypting and decrypting](#encrypting-and-decrypting) * [Encrypting and decrypting](#encrypting-and-decrypting)
* [Scanning for viruses](#scanning-for-viruses) * [Scanning for viruses](#scanning-for-viruses)
* [Uploading and copy download command](#uploading-and-copy-download-command)
## Aliases ## Aliases
<a name="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 ### Upload malware to VirusTotal, get a permalink in return
```bash ```bash
$ curl -X PUT --upload-file nhgbhhj https://transfer.sh/test.txt/virustotal $ 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
View 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
View 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
View File

@@ -8,10 +8,11 @@ require (
github.com/VojtechVitek/ratelimit v0.0.0-20160722140851-dc172bc0f6d2 github.com/VojtechVitek/ratelimit v0.0.0-20160722140851-dc172bc0f6d2
github.com/aws/aws-sdk-go v1.37.14 github.com/aws/aws-sdk-go v1.37.14
github.com/calebcase/tmpfile v1.0.2 // indirect 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/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
github.com/dutchcoders/go-virustotal v0.0.0-20140923143438-24cc8e6fa329 github.com/dutchcoders/go-virustotal v0.0.0-20140923143438-24cc8e6fa329
github.com/dutchcoders/transfer.sh-web v0.0.0-20210212072623-ac7014a9c3a7 github.com/dutchcoders/transfer.sh-web v0.0.0-20211215083008-31e11925a9d3
github.com/elazarl/go-bindata-assetfs v1.0.1 github.com/elazarl/go-bindata-assetfs v1.0.1
github.com/fatih/color v1.10.0 github.com/fatih/color v1.10.0
github.com/garyburd/redigo v1.6.2 // indirect github.com/garyburd/redigo v1.6.2 // indirect
@@ -19,7 +20,7 @@ require (
github.com/gorilla/handlers v1.5.1 github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/securecookie v1.1.1 // indirect 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/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/russross/blackfriday/v2 v2.1.0 github.com/russross/blackfriday/v2 v2.1.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
@@ -27,7 +28,7 @@ require (
github.com/urfave/cli v1.22.5 github.com/urfave/cli v1.22.5
go.opencensus.io v0.22.6 // indirect go.opencensus.io v0.22.6 // indirect
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc 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 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99
google.golang.org/api v0.40.0 google.golang.org/api v0.40.0
google.golang.org/genproto v0.0.0-20210218151259-fe80b386bf06 // indirect google.golang.org/genproto v0.0.0-20210218151259-fe80b386bf06 // indirect

72
go.sum
View File

@@ -1,17 +1,14 @@
cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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.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.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.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.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 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.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.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 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.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.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.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.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 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/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/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/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-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 h1:1AGuhKiUu4J6wxz6lxuF6ck3f8G2kaV6KSEny0RGCig=
github.com/calebcase/tmpfile v1.0.2/go.mod h1:iErLeG/iqJr8LaQ/gYRv4GXdqssi3jg4iSzvrA06/lw= 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-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-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/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-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 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 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 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -90,8 +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-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 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/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-20210723094506-f0946ebceb7a h1:+N7J1NK7gxKZ+X4syY1HqafUudJiR8voJGcXWkxLgAw=
github.com/dutchcoders/transfer.sh-web v0.0.0-20210212072623-ac7014a9c3a7/go.mod h1:jTzXZabwihvQgvmySgD4f4GNszimkXK3o8x1ucH1z5Q= 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 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= 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= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -117,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/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 h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg=
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f/go.mod h1:ijRvpgDJDI262hYq/IQVYgf8hd8IHUs93Ol0kvMBAx4= 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/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-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-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
@@ -133,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.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 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.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.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.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.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.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.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/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -155,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/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.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.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.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.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.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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -197,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/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/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.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/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/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= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -216,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/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/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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -230,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.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 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 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.5 h1:cF59UCKMmmUgqN1baLvqU/B1ZsMori+duLVTLpgiG3w=
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= 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/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 h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
@@ -239,27 +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/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/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/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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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/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.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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shuLhan/go-bindata v4.0.0+incompatible/go.mod h1:pkcPAATLBDD2+SpAPnX5vEM90F7fcwHCvvLCMXcmw3g=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 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 h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= 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.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.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 h1:jJPiF4EdetQAHAyHyX0wYIv9c/Vl0oN3KJKtnakfR+A=
github.com/spacemonkeygo/monkit/v3 v3.0.10/go.mod h1:kj1ViJhlyADa7DiA4xVnTuPA46lFKbM7mxQTrXCuJP4= 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/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/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
@@ -267,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/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/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/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.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.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 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -288,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/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 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 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/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= 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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 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.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.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/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= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
@@ -308,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-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-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-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-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 h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA=
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
@@ -365,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-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-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-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-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-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -379,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-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-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-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-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 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-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-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-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-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-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-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-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
@@ -402,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-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-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-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-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-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-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-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-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-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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -435,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-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-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-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-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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -453,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-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 h1:WdX7u8s3yOigWAhHEaDl8r9G+4XwFQEQFtBMYyN+kXQ=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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/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.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.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.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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/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 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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-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-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-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/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= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -518,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/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-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-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-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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -526,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.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.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.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.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
@@ -534,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.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.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.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.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.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
@@ -547,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/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.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.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.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.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.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/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= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@@ -560,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-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-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-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-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-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@@ -575,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-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-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-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-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-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
@@ -597,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/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.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.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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 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.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.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.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 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= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
@@ -629,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 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 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 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-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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -637,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/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/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.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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -2,6 +2,8 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/] Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/]
Copyright (c) 2018-2020 Andrea Spacca.
Copyright (c) 2020- Andrea Spacca and Stefan Benten.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -30,7 +32,6 @@ import (
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"time" "time"
@@ -56,9 +57,10 @@ func (s *Server) scanHandler(w http.ResponseWriter, r *http.Request) {
c := clamd.NewClamd(s.ClamAVDaemonHost) c := clamd.NewClamd(s.ClamAVDaemonHost)
abort := make(chan bool) abort := make(chan bool)
defer close(abort)
response, err := c.ScanStream(reader, abort) response, err := c.ScanStream(reader, abort)
if err != nil { if err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
@@ -69,6 +71,4 @@ func (s *Server) scanHandler(w http.ResponseWriter, r *http.Request) {
case <-time.After(time.Second * 60): case <-time.After(time.Second * 60):
abort <- true abort <- true
} }
close(abort)
} }

View File

@@ -1,78 +0,0 @@
/*
https://github.com/fs111/kurz.go/blob/master/src/codec.go
Originally written and Copyright (c) 2011 André Kelpe
Modifications Copyright (c) 2015 John Ko
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package server
import (
"math"
"math/rand"
"strings"
)
const (
// characters used for short-urls
SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
// someone set us up the bomb !!
BASE = float64(len(SYMBOLS))
// init seed encode number
INIT_SEED = float64(-1)
)
// encodes a number into our *base* representation
// TODO can this be made better with some bitshifting?
func Encode(number float64, length int64) string {
if number == INIT_SEED {
seed := math.Pow(float64(BASE), float64(length))
number = seed + (rand.Float64() * seed) // start with seed to enforce desired length
}
rest := int64(math.Mod(number, BASE))
// strings are a bit weird in go...
result := string(SYMBOLS[rest])
if rest > 0 && number-float64(rest) != 0 {
newnumber := (number - float64(rest)) / BASE
result = Encode(newnumber, length) + result
} else {
// it would always be 1 because of starting with seed and we want to skip
return ""
}
return result
}
// Decodes a string given in our encoding and returns the decimal
// integer.
func Decode(input string) int64 {
const floatbase = float64(BASE)
l := len(input)
var sum int = 0
for index := l - 1; index > -1; index -= 1 {
current := string(input[index])
pos := strings.Index(SYMBOLS, current)
sum = sum + (pos * int(math.Pow(floatbase, float64((l-index-1)))))
}
return int64(sum)
}

View File

@@ -1,15 +0,0 @@
package server
import "testing"
func BenchmarkEncodeConcat(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Encode(INIT_SEED, 5) + Encode(INIT_SEED, 5)
}
}
func BenchmarkEncodeLonger(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Encode(INIT_SEED, 10)
}
}

View File

@@ -2,6 +2,8 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/] Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/]
Copyright (c) 2018-2020 Andrea Spacca.
Copyright (c) 2020- Andrea Spacca and Stefan Benten.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -35,12 +37,10 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
blackfriday "github.com/russross/blackfriday/v2"
"html" "html"
html_template "html/template" html_template "html/template"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"mime" "mime"
"net/http" "net/http"
"net/url" "net/url"
@@ -53,9 +53,12 @@ import (
text_template "text/template" text_template "text/template"
"time" "time"
blackfriday "github.com/russross/blackfriday/v2"
"net" "net"
"encoding/base64" "encoding/base64"
web "github.com/dutchcoders/transfer.sh-web" web "github.com/dutchcoders/transfer.sh-web"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/microcosm-cc/bluemonday" "github.com/microcosm-cc/bluemonday"
@@ -122,10 +125,10 @@ func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) {
token := vars["token"] token := vars["token"]
filename := vars["filename"] filename := vars["filename"]
metadata, err := s.CheckMetadata(token, filename, false) metadata, err := s.checkMetadata(token, filename, false)
if err != nil { if err != nil {
log.Printf("Error metadata: %s", err.Error()) s.logger.Printf("Error metadata: %s", err.Error())
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return return
} }
@@ -197,9 +200,9 @@ func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) {
ContentType string ContentType string
Content html_template.HTML Content html_template.HTML
Filename string Filename string
Url string URL string
UrlGet string URLGet string
UrlRandomToken string URLRandomToken string
Hostname string Hostname string
WebAddress string WebAddress string
ContentLength uint64 ContentLength uint64
@@ -237,16 +240,36 @@ func (s *Server) viewHandler(w http.ResponseWriter, r *http.Request) {
hostname := getURL(r, s.proxyPort).Host hostname := getURL(r, s.proxyPort).Host
webAddress := resolveWebAddress(r, s.proxyPath, s.proxyPort) webAddress := resolveWebAddress(r, s.proxyPath, s.proxyPort)
maxUploadSize := ""
if s.maxUploadSize > 0 {
maxUploadSize = formatSize(s.maxUploadSize)
}
purgeTime := ""
if s.purgeDays > 0 {
purgeTime = s.purgeDays.String()
}
data := struct { data := struct {
Hostname string Hostname string
WebAddress string WebAddress string
GAKey string EmailContact string
UserVoiceKey string GAKey string
UserVoiceKey string
PurgeTime string
MaxUploadSize string
SampleToken string
SampleToken2 string
}{ }{
hostname, hostname,
webAddress, webAddress,
s.emailContact,
s.gaKey, s.gaKey,
s.userVoiceKey, s.userVoiceKey,
purgeTime,
maxUploadSize,
token(s.randomTokenLength),
token(s.randomTokenLength),
} }
if acceptsHTML(r.Header) { if acceptsHTML(r.Header) {
@@ -267,17 +290,17 @@ func (s *Server) notFoundHandler(w http.ResponseWriter, r *http.Request) {
} }
func sanitize(fileName string) string { 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) { func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(_24K); nil != err { if err := r.ParseMultipartForm(_24K); nil != err {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Error occurred copying to output stream", 500) http.Error(w, "Error occurred copying to output stream", 500)
return return
} }
token := Encode(INIT_SEED, s.randomTokenLength) token := token(s.randomTokenLength)
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
@@ -290,7 +313,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
var err error var err error
if f, err = fheader.Open(); err != nil { if f, err = fheader.Open(); err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
@@ -299,7 +322,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
n, err := io.CopyN(&b, f, _24K+1) n, err := io.CopyN(&b, f, _24K+1)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
@@ -310,19 +333,24 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
if n > _24K { if n > _24K {
file, err = ioutil.TempFile(s.tempPath, "transfer-") file, err = ioutil.TempFile(s.tempPath, "transfer-")
if err != nil { if err != nil {
log.Fatal(err) s.logger.Fatal(err)
} }
n, err = io.Copy(file, io.MultiReader(&b, f)) n, err = io.Copy(file, io.MultiReader(&b, f))
if err != nil { if err != nil {
cleanTmpFile(file) s.cleanTmpFile(file)
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
reader, err = os.Open(file.Name()) reader, err = os.Open(file.Name())
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), 500)
return
}
} else { } else {
reader = bytes.NewReader(b.Bytes()) reader = bytes.NewReader(b.Bytes())
} }
@@ -330,32 +358,32 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
contentLength := n contentLength := n
if s.maxUploadSize > 0 && contentLength > s.maxUploadSize { if s.maxUploadSize > 0 && contentLength > s.maxUploadSize {
log.Print("Entity too large") s.logger.Print("Entity too large")
http.Error(w, http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge) http.Error(w, http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge)
return return
} }
metadata := MetadataForRequest(contentType, s.randomTokenLength, r) metadata := metadataForRequest(contentType, s.randomTokenLength, r)
buffer := &bytes.Buffer{} buffer := &bytes.Buffer{}
if err := json.NewEncoder(buffer).Encode(metadata); err != nil { if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, errors.New("Could not encode metadata").Error(), 500) http.Error(w, errors.New("Could not encode metadata").Error(), 500)
cleanTmpFile(file) s.cleanTmpFile(file)
return return
} else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil { } else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, errors.New("Could not save metadata").Error(), 500) http.Error(w, errors.New("Could not save metadata").Error(), 500)
cleanTmpFile(file) s.cleanTmpFile(file)
return return
} }
log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) s.logger.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType)
if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil {
log.Printf("Backend storage error: %s", err.Error()) s.logger.Printf("Backend storage error: %s", err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
@@ -365,26 +393,26 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
relativeURL, _ := url.Parse(path.Join(s.proxyPath, token, filename)) relativeURL, _ := url.Parse(path.Join(s.proxyPath, token, filename))
fmt.Fprintln(w, getURL(r, s.proxyPort).ResolveReference(relativeURL).String()) fmt.Fprintln(w, getURL(r, s.proxyPort).ResolveReference(relativeURL).String())
cleanTmpFile(file) s.cleanTmpFile(file)
} }
} }
} }
func cleanTmpFile(f *os.File) { func (s *Server) cleanTmpFile(f *os.File) {
if f != nil { if f != nil {
err := f.Close() err := f.Close()
if err != nil { if err != nil {
log.Printf("Error closing tmpfile: %s (%s)", err, f.Name()) s.logger.Printf("Error closing tmpfile: %s (%s)", err, f.Name())
} }
err = os.Remove(f.Name()) err = os.Remove(f.Name())
if err != nil { if err != nil {
log.Printf("Error removing tmpfile: %s (%s)", err, f.Name()) s.logger.Printf("Error removing tmpfile: %s (%s)", err, f.Name())
} }
} }
} }
type Metadata struct { type metadata struct {
// ContentType is the original uploading content type // ContentType is the original uploading content type
ContentType string ContentType string
// Secret as knowledge to delete file // Secret as knowledge to delete file
@@ -399,13 +427,13 @@ type Metadata struct {
DeletionToken string DeletionToken string
} }
func MetadataForRequest(contentType string, randomTokenLength int64, r *http.Request) Metadata { func metadataForRequest(contentType string, randomTokenLength int, r *http.Request) metadata {
metadata := Metadata{ metadata := metadata{
ContentType: strings.ToLower(contentType), ContentType: strings.ToLower(contentType),
MaxDate: time.Time{}, MaxDate: time.Time{},
Downloads: 0, Downloads: 0,
MaxDownloads: -1, MaxDownloads: -1,
DeletionToken: Encode(INIT_SEED, randomTokenLength) + Encode(INIT_SEED, randomTokenLength), DeletionToken: token(randomTokenLength) + token(randomTokenLength),
} }
if v := r.Header.Get("Max-Downloads"); v == "" { if v := r.Header.Get("Max-Downloads"); v == "" {
@@ -447,7 +475,7 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
n, err := io.CopyN(&b, f, _24K+1) n, err := io.CopyN(&b, f, _24K+1)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
log.Printf("Error putting new file: %s", err.Error()) s.logger.Printf("Error putting new file: %s", err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
@@ -457,21 +485,26 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
if n > _24K { if n > _24K {
file, err = ioutil.TempFile(s.tempPath, "transfer-") file, err = ioutil.TempFile(s.tempPath, "transfer-")
if err != nil { if err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
defer cleanTmpFile(file) defer s.cleanTmpFile(file)
n, err = io.Copy(file, io.MultiReader(&b, f)) n, err = io.Copy(file, io.MultiReader(&b, f))
if err != nil { if err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
reader, err = os.Open(file.Name()) reader, err = os.Open(file.Name())
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), 500)
return
}
} else { } else {
reader = bytes.NewReader(b.Bytes()) reader = bytes.NewReader(b.Bytes())
} }
@@ -480,40 +513,44 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
} }
if s.maxUploadSize > 0 && contentLength > s.maxUploadSize { if s.maxUploadSize > 0 && contentLength > s.maxUploadSize {
log.Print("Entity too large") s.logger.Print("Entity too large")
http.Error(w, http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge) http.Error(w, http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge)
return return
} }
if contentLength == 0 { if contentLength == 0 {
log.Print("Empty content-length") s.logger.Print("Empty content-length")
http.Error(w, errors.New("Could not upload empty file").Error(), 400) http.Error(w, errors.New("Could not upload empty file").Error(), 400)
return return
} }
contentType := mime.TypeByExtension(filepath.Ext(vars["filename"])) contentType := mime.TypeByExtension(filepath.Ext(vars["filename"]))
token := Encode(INIT_SEED, s.randomTokenLength) token := token(s.randomTokenLength)
metadata := MetadataForRequest(contentType, s.randomTokenLength, r) metadata := metadataForRequest(contentType, s.randomTokenLength, r)
buffer := &bytes.Buffer{} buffer := &bytes.Buffer{}
if err := json.NewEncoder(buffer).Encode(metadata); err != nil { if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, errors.New("Could not encode metadata").Error(), 500) http.Error(w, errors.New("Could not encode metadata").Error(), 500)
return 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 { } else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, errors.New("Could not save metadata").Error(), 500) http.Error(w, errors.New("Could not save metadata").Error(), 500)
return return
} }
log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) s.logger.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType)
var err error var err error
if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil {
log.Printf("Error putting new file: %s", err.Error()) s.logger.Printf("Error putting new file: %s", err.Error())
http.Error(w, errors.New("Could not save file").Error(), 500) http.Error(w, errors.New("Could not save file").Error(), 500)
return return
} }
@@ -595,32 +632,31 @@ func getURL(r *http.Request, proxyPort string) *url.URL {
u.Scheme = "http" u.Scheme = "http"
} }
if u.Host == "" { host, port, err := net.SplitHostPort(r.Host)
host, port, err := net.SplitHostPort(r.Host) if err != nil {
if err != nil { host = r.Host
host = r.Host port = ""
port = "" }
} if len(proxyPort) != 0 {
if len(proxyPort) != 0 { port = proxyPort
port = proxyPort }
}
if len(port) == 0 { 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 u.Host = host
} else { } else {
if port == "80" && u.Scheme == "http" { u.Host = net.JoinHostPort(host, port)
u.Host = host
} else if port == "443" && u.Scheme == "https" {
u.Host = host
} else {
u.Host = net.JoinHostPort(host, port)
}
} }
} }
return u return u
} }
func (metadata Metadata) remainingLimitHeaderValues() (remainingDownloads, remainingDays string) { func (metadata metadata) remainingLimitHeaderValues() (remainingDownloads, remainingDays string) {
if metadata.MaxDate.IsZero() { if metadata.MaxDate.IsZero() {
remainingDays = "n/a" remainingDays = "n/a"
} else { } else {
@@ -637,30 +673,29 @@ func (metadata Metadata) remainingLimitHeaderValues() (remainingDownloads, remai
return remainingDownloads, remainingDays return remainingDownloads, remainingDays
} }
func (s *Server) Lock(token, filename string) error { func (s *Server) lock(token, filename string) {
key := path.Join(token, filename) key := path.Join(token, filename)
if _, ok := s.locks[key]; !ok { lock, _ := s.locks.LoadOrStore(key, &sync.Mutex{})
s.locks[key] = &sync.Mutex{}
}
s.locks[key].Lock() lock.(*sync.Mutex).Lock()
return nil return
} }
func (s *Server) Unlock(token, filename string) error { func (s *Server) unlock(token, filename string) {
key := path.Join(token, filename) key := path.Join(token, filename)
s.locks[key].Unlock()
return nil lock, _ := s.locks.LoadOrStore(key, &sync.Mutex{})
lock.(*sync.Mutex).Unlock()
} }
func (s *Server) CheckMetadata(token, filename string, increaseDownload bool) (Metadata, error) { func (s *Server) checkMetadata(token, filename string, increaseDownload bool) (metadata, error) {
s.Lock(token, filename) s.lock(token, filename)
defer s.Unlock(token, filename) defer s.unlock(token, filename)
var metadata Metadata var metadata metadata
r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename)) r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
if err != nil { if err != nil {
@@ -672,9 +707,9 @@ func (s *Server) CheckMetadata(token, filename string, increaseDownload bool) (M
if err := json.NewDecoder(r).Decode(&metadata); err != nil { if err := json.NewDecoder(r).Decode(&metadata); err != nil {
return metadata, err return metadata, err
} else if metadata.MaxDownloads != -1 && metadata.Downloads >= metadata.MaxDownloads { } 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) { } 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 { } else if metadata.MaxDownloads != -1 && increaseDownload {
// todo(nl5887): mutex? // todo(nl5887): mutex?
@@ -692,15 +727,15 @@ func (s *Server) CheckMetadata(token, filename string, increaseDownload bool) (M
return metadata, nil return metadata, nil
} }
func (s *Server) CheckDeletionToken(deletionToken, token, filename string) error { func (s *Server) checkDeletionToken(deletionToken, token, filename string) error {
s.Lock(token, filename) s.lock(token, filename)
defer s.Unlock(token, filename) defer s.unlock(token, filename)
var metadata Metadata var metadata metadata
r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename)) r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
if s.storage.IsNotExist(err) { if s.storage.IsNotExist(err) {
return errors.New("Metadata doesn't exist") return errors.New("metadata doesn't exist")
} else if err != nil { } else if err != nil {
return err return err
} }
@@ -710,7 +745,7 @@ func (s *Server) CheckDeletionToken(deletionToken, token, filename string) error
if err := json.NewDecoder(r).Decode(&metadata); err != nil { if err := json.NewDecoder(r).Decode(&metadata); err != nil {
return err return err
} else if metadata.DeletionToken != deletionToken { } else if metadata.DeletionToken != deletionToken {
return errors.New("Deletion token doesn't match.") return errors.New("deletion token doesn't match")
} }
return nil return nil
@@ -723,7 +758,7 @@ func (s *Server) purgeHandler() {
select { select {
case <-ticker.C: case <-ticker.C:
err := s.storage.Purge(s.purgeDays) err := s.storage.Purge(s.purgeDays)
log.Printf("error cleaning up expired files: %v", err) s.logger.Printf("error cleaning up expired files: %v", err)
} }
} }
}() }()
@@ -736,8 +771,8 @@ func (s *Server) deleteHandler(w http.ResponseWriter, r *http.Request) {
filename := vars["filename"] filename := vars["filename"]
deletionToken := vars["deletionToken"] deletionToken := vars["deletionToken"]
if err := s.CheckDeletionToken(deletionToken, token, filename); err != nil { if err := s.checkDeletionToken(deletionToken, token, filename); err != nil {
log.Printf("Error metadata: %s", err.Error()) s.logger.Printf("Error metadata: %s", err.Error())
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return return
} }
@@ -747,7 +782,7 @@ func (s *Server) deleteHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return return
} else if err != nil { } else if err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Could not delete file.", 500) http.Error(w, "Could not delete file.", 500)
return return
} }
@@ -772,8 +807,8 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
token := strings.Split(key, "/")[0] token := strings.Split(key, "/")[0]
filename := sanitize(strings.Split(key, "/")[1]) filename := sanitize(strings.Split(key, "/")[1])
if _, err := s.CheckMetadata(token, filename, true); err != nil { if _, err := s.checkMetadata(token, filename, true); err != nil {
log.Printf("Error metadata: %s", err.Error()) s.logger.Printf("Error metadata: %s", err.Error())
continue continue
} }
@@ -783,11 +818,11 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
if s.storage.IsNotExist(err) { if s.storage.IsNotExist(err) {
http.Error(w, "File not found", 404) http.Error(w, "File not found", 404)
return return
} else {
log.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() defer reader.Close()
@@ -802,20 +837,20 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
fw, err := zw.CreateHeader(header) fw, err := zw.CreateHeader(header)
if err != nil { if err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Internal server error.", 500) http.Error(w, "Internal server error.", 500)
return return
} }
if _, err = io.Copy(fw, reader); err != nil { if _, err = io.Copy(fw, reader); err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Internal server error.", 500) http.Error(w, "Internal server error.", 500)
return return
} }
} }
if err := zw.Close(); err != nil { if err := zw.Close(); err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Internal server error.", 500) http.Error(w, "Internal server error.", 500)
return return
} }
@@ -844,8 +879,8 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
token := strings.Split(key, "/")[0] token := strings.Split(key, "/")[0]
filename := sanitize(strings.Split(key, "/")[1]) filename := sanitize(strings.Split(key, "/")[1])
if _, err := s.CheckMetadata(token, filename, true); err != nil { if _, err := s.checkMetadata(token, filename, true); err != nil {
log.Printf("Error metadata: %s", err.Error()) s.logger.Printf("Error metadata: %s", err.Error())
continue continue
} }
@@ -854,11 +889,11 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
if s.storage.IsNotExist(err) { if s.storage.IsNotExist(err) {
http.Error(w, "File not found", 404) http.Error(w, "File not found", 404)
return return
} else {
log.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() defer reader.Close()
@@ -870,13 +905,13 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
err = zw.WriteHeader(header) err = zw.WriteHeader(header)
if err != nil { if err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Internal server error.", 500) http.Error(w, "Internal server error.", 500)
return return
} }
if _, err = io.Copy(zw, reader); err != nil { if _, err = io.Copy(zw, reader); err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Internal server error.", 500) http.Error(w, "Internal server error.", 500)
return return
} }
@@ -903,8 +938,8 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
token := strings.Split(key, "/")[0] token := strings.Split(key, "/")[0]
filename := strings.Split(key, "/")[1] filename := strings.Split(key, "/")[1]
if _, err := s.CheckMetadata(token, filename, true); err != nil { if _, err := s.checkMetadata(token, filename, true); err != nil {
log.Printf("Error metadata: %s", err.Error()) s.logger.Printf("Error metadata: %s", err.Error())
continue continue
} }
@@ -913,11 +948,11 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
if s.storage.IsNotExist(err) { if s.storage.IsNotExist(err) {
http.Error(w, "File not found", 404) http.Error(w, "File not found", 404)
return return
} else {
log.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() defer reader.Close()
@@ -929,13 +964,13 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
err = zw.WriteHeader(header) err = zw.WriteHeader(header)
if err != nil { if err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Internal server error.", 500) http.Error(w, "Internal server error.", 500)
return return
} }
if _, err = io.Copy(zw, reader); err != nil { if _, err = io.Copy(zw, reader); err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Internal server error.", 500) http.Error(w, "Internal server error.", 500)
return return
} }
@@ -948,10 +983,10 @@ func (s *Server) headHandler(w http.ResponseWriter, r *http.Request) {
token := vars["token"] token := vars["token"]
filename := vars["filename"] filename := vars["filename"]
metadata, err := s.CheckMetadata(token, filename, false) metadata, err := s.checkMetadata(token, filename, false)
if err != nil { if err != nil {
log.Printf("Error metadata: %s", err.Error()) s.logger.Printf("Error metadata: %s", err.Error())
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return return
} }
@@ -962,7 +997,7 @@ func (s *Server) headHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return return
} else if err != nil { } else if err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Could not retrieve file.", 500) http.Error(w, "Could not retrieve file.", 500)
return return
} }
@@ -983,10 +1018,10 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
token := vars["token"] token := vars["token"]
filename := vars["filename"] filename := vars["filename"]
metadata, err := s.CheckMetadata(token, filename, true) metadata, err := s.checkMetadata(token, filename, true)
if err != nil { if err != nil {
log.Printf("Error metadata: %s", err.Error()) s.logger.Printf("Error metadata: %s", err.Error())
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return return
} }
@@ -997,7 +1032,7 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return return
} else if err != nil { } else if err != nil {
log.Printf("%s", err.Error()) s.logger.Printf("%s", err.Error())
http.Error(w, "Could not retrieve file.", 500) http.Error(w, "Could not retrieve file.", 500)
return return
} }
@@ -1025,56 +1060,62 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
reader = ioutil.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader)) reader = ioutil.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader))
} }
if w.Header().Get("Range") == "" { if w.Header().Get("Range") != "" || strings.HasPrefix(metadata.ContentType, "video") || strings.HasPrefix(metadata.ContentType, "audio") {
if _, err = io.Copy(w, reader); err != nil { file, err := ioutil.TempFile(s.tempPath, "range-")
log.Printf("%s", err.Error()) if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, "Error occurred copying to output stream", 500) http.Error(w, "Error occurred copying to output stream", 500)
return return
} }
defer s.cleanTmpFile(file)
_, err = io.Copy(file, reader)
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, "Error occurred copying to output stream", 500)
return
}
http.ServeContent(w, r, filename, time.Now(), file)
return return
} }
file, err := ioutil.TempFile(s.tempPath, "range-") if _, err = io.Copy(w, reader); err != nil {
if err != nil { s.logger.Printf("%s", err.Error())
log.Printf("%s", err.Error())
http.Error(w, "Error occurred copying to output stream", 500) http.Error(w, "Error occurred copying to output stream", 500)
return return
} }
defer cleanTmpFile(file) return
tee := io.TeeReader(reader, file)
for {
b := make([]byte, _5M)
_, err = tee.Read(b)
if err == io.EOF {
break
}
if err != nil {
log.Printf("%s", err.Error())
http.Error(w, "Error occurred copying to output stream", 500)
return
}
}
http.ServeContent(w, r, filename, time.Now(), file)
} }
// RedirectHandler handles redirect
func (s *Server) RedirectHandler(h http.Handler) http.HandlerFunc { func (s *Server) RedirectHandler(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if !s.forceHTTPs { if !s.forceHTTPS {
// we don't want to enforce https // we don't want to enforce https
} else if r.URL.Path == "/health.html" { } else if r.URL.Path == "/health.html" {
// health check url won't redirect // health check url won't redirect
} else if strings.HasSuffix(ipAddrFromRemoteAddr(r.Host), ".onion") { } else if strings.HasSuffix(ipAddrFromRemoteAddr(r.Host), ".onion") {
// .onion addresses cannot get a valid certificate, so don't redirect // .onion addresses cannot get a valid certificate, so don't redirect
} else if r.Header.Get("X-Forwarded-Proto") == "https" { } else if r.Header.Get("X-Forwarded-Proto") == "https" {
} else if r.URL.Scheme == "https" { } else if r.TLS != nil {
} else { } else {
u := getURL(r, s.proxyPort) u := getURL(r, s.proxyPort)
u.Scheme = "https" 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) http.Redirect(w, r, u.String(), http.StatusPermanentRedirect)
return return
@@ -1084,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 { func LoveHandler(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("x-made-with", "<3 by DutchCoders") w.Header().Set("x-made-with", "<3 by DutchCoders")
w.Header().Set("x-served-by", "Proudly served 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) 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) { return func(w http.ResponseWriter, r *http.Request) {
if ipFilterOptions == nil { if ipFilterOptions == nil {
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
@@ -1105,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) { return func(w http.ResponseWriter, r *http.Request) {
if s.AuthUser == "" || s.AuthPass == "" { if s.AuthUser == "" || s.AuthPass == "" {
h.ServeHTTP(w, r) h.ServeHTTP(w, r)

View File

@@ -13,16 +13,16 @@ import (
func Test(t *testing.T) { TestingT(t) } func Test(t *testing.T) { TestingT(t) }
var ( var (
_ = Suite(&SuiteRedirectWithForceHTTPs{}) _ = Suite(&suiteRedirectWithForceHTTPS{})
_ = Suite(&SuiteRedirectWithoutForceHTTPs{}) _ = Suite(&suiteRedirectWithoutForceHTTPS{})
) )
type SuiteRedirectWithForceHTTPs struct { type suiteRedirectWithForceHTTPS struct {
handler http.HandlerFunc handler http.HandlerFunc
} }
func (s *SuiteRedirectWithForceHTTPs) SetUpTest(c *C) { func (s *suiteRedirectWithForceHTTPS) SetUpTest(c *C) {
srvr, err := New(ForceHTTPs()) srvr, err := New(ForceHTTPS())
c.Assert(err, IsNil) c.Assert(err, IsNil)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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) 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) req := httptest.NewRequest("GET", "https://test/test", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
@@ -42,7 +42,7 @@ func (s *SuiteRedirectWithForceHTTPs) TestHTTPs(c *C) {
c.Assert(resp.StatusCode, Equals, http.StatusOK) 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) req := httptest.NewRequest("GET", "http://test.onion/test", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
@@ -52,7 +52,7 @@ func (s *SuiteRedirectWithForceHTTPs) TestOnion(c *C) {
c.Assert(resp.StatusCode, Equals, http.StatusOK) 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 := httptest.NewRequest("GET", "http://127.0.0.1/test", nil)
req.Header.Set("X-Forwarded-Proto", "https") req.Header.Set("X-Forwarded-Proto", "https")
@@ -63,7 +63,7 @@ func (s *SuiteRedirectWithForceHTTPs) TestXForwardedFor(c *C) {
c.Assert(resp.StatusCode, Equals, http.StatusOK) 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) req := httptest.NewRequest("GET", "http://127.0.0.1/test", nil)
w := httptest.NewRecorder() 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") c.Assert(resp.Header.Get("Location"), Equals, "https://127.0.0.1/test")
} }
type SuiteRedirectWithoutForceHTTPs struct { type suiteRedirectWithoutForceHTTPS struct {
handler http.HandlerFunc handler http.HandlerFunc
} }
func (s *SuiteRedirectWithoutForceHTTPs) SetUpTest(c *C) { func (s *suiteRedirectWithoutForceHTTPS) SetUpTest(c *C) {
srvr, err := New() srvr, err := New()
c.Assert(err, IsNil) c.Assert(err, IsNil)
@@ -89,7 +89,7 @@ func (s *SuiteRedirectWithoutForceHTTPs) SetUpTest(c *C) {
s.handler = srvr.RedirectHandler(handler) 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) req := httptest.NewRequest("GET", "http://127.0.0.1/test", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
@@ -99,7 +99,7 @@ func (s *SuiteRedirectWithoutForceHTTPs) TestHTTP(c *C) {
c.Assert(resp.StatusCode, Equals, http.StatusOK) 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) req := httptest.NewRequest("GET", "https://127.0.0.1/test", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()

View File

@@ -21,7 +21,7 @@ import (
"github.com/tomasen/realip" "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 //IPs can be IPv4 or IPv6 and can optionally contain subnet
//masks (/24). Note however, determining if a given IP is //masks (/24). Note however, determining if a given IP is
//included in a subnet requires a linear scan so is less performant //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 opts IPFilterOptions
//mut protects the below //mut protects the below
//rw since writes are rare //rw since writes are rare
@@ -59,13 +60,12 @@ type subnet struct {
allowed bool allowed bool
} }
//New constructs IPFilter instance. func newIPFilter(opts IPFilterOptions) *ipFilter {
func NewIPFilter(opts IPFilterOptions) *IPFilter {
if opts.Logger == nil { if opts.Logger == nil {
flags := log.LstdFlags flags := log.LstdFlags
opts.Logger = log.New(os.Stdout, "", flags) opts.Logger = log.New(os.Stdout, "", flags)
} }
f := &IPFilter{ f := &ipFilter{
opts: opts, opts: opts,
ips: map[string]bool{}, ips: map[string]bool{},
defaultAllowed: !opts.BlockByDefault, defaultAllowed: !opts.BlockByDefault,
@@ -79,15 +79,15 @@ func NewIPFilter(opts IPFilterOptions) *IPFilter {
return f return f
} }
func (f *IPFilter) AllowIP(ip string) bool { func (f *ipFilter) AllowIP(ip string) bool {
return f.ToggleIP(ip, true) return f.ToggleIP(ip, true)
} }
func (f *IPFilter) BlockIP(ip string) bool { func (f *ipFilter) BlockIP(ip string) bool {
return f.ToggleIP(ip, false) 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 //check if has subnet
if ip, net, err := net.ParseCIDR(str); err == nil { if ip, net, err := net.ParseCIDR(str); err == nil {
// containing only one ip? // containing only one ip?
@@ -128,19 +128,19 @@ func (f *IPFilter) ToggleIP(str string, allowed bool) bool {
} }
//ToggleDefault alters the default setting //ToggleDefault alters the default setting
func (f *IPFilter) ToggleDefault(allowed bool) { func (f *ipFilter) ToggleDefault(allowed bool) {
f.mut.Lock() f.mut.Lock()
f.defaultAllowed = allowed f.defaultAllowed = allowed
f.mut.Unlock() f.mut.Unlock()
} }
//Allowed returns if a given IP can pass through the filter //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)) return f.NetAllowed(net.ParseIP(ipstr))
} }
//NetAllowed returns if a given net.IP can pass through the filter //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 //invalid ip
if ip == nil { if ip == nil {
return false 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 //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) return !f.Allowed(ip)
} }
//NetBlocked returns if a given net.IP can NOT pass through the filter //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) return !f.NetAllowed(ip)
} }
//WrapIPFilter the provided handler with simple IP blocking middleware //WrapIPFilter the provided handler with simple IP blocking middleware
//using this IP filter and its configuration //using this IP filter and its configuration
func (f *IPFilter) Wrap(next http.Handler) http.Handler { func (f *ipFilter) Wrap(next http.Handler) http.Handler {
return &ipFilterMiddleware{IPFilter: f, next: next} 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 { func WrapIPFilter(next http.Handler, opts IPFilterOptions) http.Handler {
return NewIPFilter(opts).Wrap(next) return newIPFilter(opts).Wrap(next)
} }
type ipFilterMiddleware struct { type ipFilterMiddleware struct {
*IPFilter *ipFilter
next http.Handler next http.Handler
} }
func (m *ipFilterMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (m *ipFilterMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
remoteIP := realip.FromRequest(r) remoteIP := realip.FromRequest(r)
if !m.IPFilter.Allowed(remoteIP) { if !m.ipFilter.Allowed(remoteIP) {
//show simple forbidden text //show simple forbidden text
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return return

View File

@@ -48,6 +48,7 @@ import (
"github.com/VojtechVitek/ratelimit/memory" "github.com/VojtechVitek/ratelimit/memory"
"github.com/gorilla/mux" "github.com/gorilla/mux"
// import pprof
_ "net/http/pprof" _ "net/http/pprof"
"crypto/tls" "crypto/tls"
@@ -59,28 +60,30 @@ import (
"path/filepath" "path/filepath"
) )
const SERVER_INFO = "transfer.sh"
// parse request with maximum memory of _24Kilobits // parse request with maximum memory of _24Kilobits
const _24K = (1 << 3) * 24 const _24K = (1 << 3) * 24
// parse request with maximum memory of _5Megabytes // parse request with maximum memory of _5Megabytes
const _5M = (1 << 20) * 5 const _5M = (1 << 20) * 5
// OptionFn is the option function type
type OptionFn func(*Server) type OptionFn func(*Server)
// ClamavHost sets clamav host
func ClamavHost(s string) OptionFn { func ClamavHost(s string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.ClamAVDaemonHost = s srvr.ClamAVDaemonHost = s
} }
} }
// VirustotalKey sets virus total key
func VirustotalKey(s string) OptionFn { func VirustotalKey(s string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.VirusTotalKey = s srvr.VirusTotalKey = s
} }
} }
// Listener set listener
func Listener(s string) OptionFn { func Listener(s string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.ListenerString = s srvr.ListenerString = s
@@ -88,6 +91,7 @@ func Listener(s string) OptionFn {
} }
// CorsDomains sets CORS domains
func CorsDomains(s string) OptionFn { func CorsDomains(s string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.CorsDomains = s 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 { func GoogleAnalytics(gaKey string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.gaKey = gaKey srvr.gaKey = gaKey
} }
} }
// UserVoice sets UV key
func UserVoice(userVoiceKey string) OptionFn { func UserVoice(userVoiceKey string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.userVoiceKey = userVoiceKey srvr.userVoiceKey = userVoiceKey
} }
} }
// TLSListener sets TLS listener and option
func TLSListener(s string, t bool) OptionFn { func TLSListener(s string, t bool) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.TLSListenerString = s srvr.TLSListenerString = s
@@ -115,12 +129,14 @@ func TLSListener(s string, t bool) OptionFn {
} }
// ProfileListener sets profile listener
func ProfileListener(s string) OptionFn { func ProfileListener(s string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.ProfileListenerString = s srvr.ProfileListenerString = s
} }
} }
// WebPath sets web path
func WebPath(s string) OptionFn { func WebPath(s string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
if s[len(s)-1:] != "/" { if s[len(s)-1:] != "/" {
@@ -131,6 +147,7 @@ func WebPath(s string) OptionFn {
} }
} }
// ProxyPath sets proxy path
func ProxyPath(s string) OptionFn { func ProxyPath(s string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
if s[len(s)-1:] != "/" { if s[len(s)-1:] != "/" {
@@ -141,12 +158,14 @@ func ProxyPath(s string) OptionFn {
} }
} }
// ProxyPort sets proxy port
func ProxyPort(s string) OptionFn { func ProxyPort(s string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.proxyPort = s srvr.proxyPort = s
} }
} }
// TempPath sets temp path
func TempPath(s string) OptionFn { func TempPath(s string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
if s[len(s)-1:] != "/" { if s[len(s)-1:] != "/" {
@@ -157,11 +176,12 @@ func TempPath(s string) OptionFn {
} }
} }
// LogFile sets log file
func LogFile(logger *log.Logger, s string) OptionFn { func LogFile(logger *log.Logger, s string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
f, err := os.OpenFile(s, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) f, err := os.OpenFile(s, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil { if err != nil {
log.Fatalf("error opening file: %v", err) logger.Fatalf("error opening file: %v", err)
} }
logger.SetOutput(f) logger.SetOutput(f)
@@ -169,30 +189,36 @@ func LogFile(logger *log.Logger, s string) OptionFn {
} }
} }
// Logger sets logger
func Logger(logger *log.Logger) OptionFn { func Logger(logger *log.Logger) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.logger = logger srvr.logger = logger
} }
} }
// MaxUploadSize sets max upload size
func MaxUploadSize(kbytes int64) OptionFn { func MaxUploadSize(kbytes int64) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.maxUploadSize = kbytes * 1024 srvr.maxUploadSize = kbytes * 1024
} }
} }
// RateLimit set rate limit
func RateLimit(requests int) OptionFn { func RateLimit(requests int) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.rateLimitRequests = requests srvr.rateLimitRequests = requests
} }
} }
func RandomTokenLength(length int64) OptionFn { // RandomTokenLength sets random token length
func RandomTokenLength(length int) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.randomTokenLength = length srvr.randomTokenLength = length
} }
} }
// Purge sets purge days and option
func Purge(days, interval int) OptionFn { func Purge(days, interval int) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.purgeDays = time.Duration(days) * time.Hour * 24 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) { return func(srvr *Server) {
srvr.forceHTTPs = true srvr.forceHTTPS = true
} }
} }
// EnableProfiler sets enable profiler
func EnableProfiler() OptionFn { func EnableProfiler() OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.profilerEnabled = true srvr.profilerEnabled = true
} }
} }
// UseStorage set storage to use
func UseStorage(s Storage) OptionFn { func UseStorage(s Storage) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.storage = s srvr.storage = s
} }
} }
// UseLetsEncrypt set letsencrypt usage
func UseLetsEncrypt(hosts []string) OptionFn { func UseLetsEncrypt(hosts []string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
cacheDir := "./cache/" cacheDir := "./cache/"
@@ -246,6 +276,7 @@ func UseLetsEncrypt(hosts []string) OptionFn {
} }
} }
// TLSConfig sets TLS config
func TLSConfig(cert, pk string) OptionFn { func TLSConfig(cert, pk string) OptionFn {
certificate, err := tls.LoadX509KeyPair(cert, pk) certificate, err := tls.LoadX509KeyPair(cert, pk)
return func(srvr *Server) { 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) { return func(srvr *Server) {
srvr.AuthUser = user srvr.AuthUser = user
srvr.AuthPass = pass srvr.AuthPass = pass
} }
} }
// FilterOptions sets ip filtering
func FilterOptions(options IPFilterOptions) OptionFn { func FilterOptions(options IPFilterOptions) OptionFn {
for i, allowedIP := range options.AllowedIPs { for i, allowedIP := range options.AllowedIPs {
options.AllowedIPs[i] = strings.TrimSpace(allowedIP) options.AllowedIPs[i] = strings.TrimSpace(allowedIP)
@@ -278,6 +311,7 @@ func FilterOptions(options IPFilterOptions) OptionFn {
} }
} }
// Server is the main application
type Server struct { type Server struct {
AuthUser string AuthUser string
AuthPass string AuthPass string
@@ -288,7 +322,7 @@ type Server struct {
profilerEnabled bool profilerEnabled bool
locks map[string]*sync.Mutex locks sync.Map
maxUploadSize int64 maxUploadSize int64
rateLimitRequests int rateLimitRequests int
@@ -298,9 +332,9 @@ type Server struct {
storage Storage storage Storage
forceHTTPs bool forceHTTPS bool
randomTokenLength int64 randomTokenLength int
ipFilterOptions *IPFilterOptions ipFilterOptions *IPFilterOptions
@@ -312,6 +346,7 @@ type Server struct {
webPath string webPath string
proxyPath string proxyPath string
proxyPort string proxyPort string
emailContact string
gaKey string gaKey string
userVoiceKey string userVoiceKey string
@@ -327,9 +362,10 @@ type Server struct {
LetsEncryptCache string LetsEncryptCache string
} }
// New is the factory fot Server
func New(options ...OptionFn) (*Server, error) { func New(options ...OptionFn) (*Server, error) {
s := &Server{ s := &Server{
locks: map[string]*sync.Mutex{}, locks: sync.Map{},
} }
for _, optionFn := range options { for _, optionFn := range options {
@@ -347,6 +383,7 @@ func init() {
rand.Seed(int64(binary.LittleEndian.Uint64(seedBytes[:]))) rand.Seed(int64(binary.LittleEndian.Uint64(seedBytes[:])))
} }
// Run starts Server
func (s *Server) Run() { func (s *Server) Run() {
listening := false listening := false
@@ -402,7 +439,7 @@ func (s *Server) Run() {
r.HandleFunc("/favicon.ico", staticHandler.ServeHTTP).Methods("GET") r.HandleFunc("/favicon.ico", staticHandler.ServeHTTP).Methods("GET")
r.HandleFunc("/robots.txt", 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("/health.html", healthHandler).Methods("GET")
r.HandleFunc("/", s.viewHandler).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}/virustotal", s.virusTotalHandler).Methods("PUT")
r.HandleFunc("/{filename}/scan", s.scanHandler).Methods("PUT") r.HandleFunc("/{filename}/scan", s.scanHandler).Methods("PUT")
r.HandleFunc("/put/{filename}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).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("/upload/{filename}", s.basicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
r.HandleFunc("/{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("/", s.basicAuthHandler(http.HandlerFunc(s.postHandler))).Methods("POST")
// r.HandleFunc("/{page}", viewHandler).Methods("GET") // r.HandleFunc("/{page}", viewHandler).Methods("GET")
r.HandleFunc("/{token}/{filename}/{deletionToken}", s.deleteHandler).Methods("DELETE") r.HandleFunc("/{token}/{filename}/{deletionToken}", s.deleteHandler).Methods("DELETE")
@@ -474,7 +511,7 @@ func (s *Server) Run() {
} }
h := handlers.PanicHandler( h := handlers.PanicHandler(
IPFilterHandler( ipFilterHandler(
handlers.LogHandler( handlers.LogHandler(
LoveHandler( LoveHandler(
s.RedirectHandler(cors(r))), s.RedirectHandler(cors(r))),

View File

@@ -27,31 +27,43 @@ import (
"storj.io/uplink" "storj.io/uplink"
) )
// Storage is the interface for storage operation
type Storage interface { type Storage interface {
// Get retrieves a file from storage
Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) 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) 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 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 Delete(token string, filename string) error
// IsNotExist indicates if a file doesn't exist on storage
IsNotExist(err error) bool IsNotExist(err error) bool
// Purge cleans up the storage
Purge(days time.Duration) error Purge(days time.Duration) error
// Type returns the storage type
Type() string Type() string
} }
// LocalStorage is a local storage
type LocalStorage struct { type LocalStorage struct {
Storage Storage
basedir string basedir string
logger *log.Logger logger *log.Logger
} }
// NewLocalStorage is the factory for LocalStorage
func NewLocalStorage(basedir string, logger *log.Logger) (*LocalStorage, error) { func NewLocalStorage(basedir string, logger *log.Logger) (*LocalStorage, error) {
return &LocalStorage{basedir: basedir, logger: logger}, nil return &LocalStorage{basedir: basedir, logger: logger}, nil
} }
// Type returns the storage type
func (s *LocalStorage) Type() string { func (s *LocalStorage) Type() string {
return "local" return "local"
} }
// Head retrieves content length of a file from storage
func (s *LocalStorage) Head(token string, filename string) (contentLength uint64, err error) { func (s *LocalStorage) Head(token string, filename string) (contentLength uint64, err error) {
path := filepath.Join(s.basedir, token, filename) path := filepath.Join(s.basedir, token, filename)
@@ -65,6 +77,7 @@ func (s *LocalStorage) Head(token string, filename string) (contentLength uint64
return return
} }
// Get retrieves a file from storage
func (s *LocalStorage) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) { func (s *LocalStorage) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) {
path := filepath.Join(s.basedir, token, filename) path := filepath.Join(s.basedir, token, filename)
@@ -83,6 +96,7 @@ func (s *LocalStorage) Get(token string, filename string) (reader io.ReadCloser,
return return
} }
// Delete removes a file from storage
func (s *LocalStorage) Delete(token string, filename string) (err error) { func (s *LocalStorage) Delete(token string, filename string) (err error) {
metadata := filepath.Join(s.basedir, token, fmt.Sprintf("%s.metadata", filename)) metadata := filepath.Join(s.basedir, token, fmt.Sprintf("%s.metadata", filename))
os.Remove(metadata) os.Remove(metadata)
@@ -92,6 +106,7 @@ func (s *LocalStorage) Delete(token string, filename string) (err error) {
return return
} }
// Purge cleans up the storage
func (s *LocalStorage) Purge(days time.Duration) (err error) { func (s *LocalStorage) Purge(days time.Duration) (err error) {
err = filepath.Walk(s.basedir, err = filepath.Walk(s.basedir,
func(path string, info os.FileInfo, err error) error { func(path string, info os.FileInfo, err error) error {
@@ -113,6 +128,7 @@ func (s *LocalStorage) Purge(days time.Duration) (err error) {
return return
} }
// IsNotExist indicates if a file doesn't exist on storage
func (s *LocalStorage) IsNotExist(err error) bool { func (s *LocalStorage) IsNotExist(err error) bool {
if err == nil { if err == nil {
return false return false
@@ -121,6 +137,7 @@ func (s *LocalStorage) IsNotExist(err error) bool {
return os.IsNotExist(err) 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 { func (s *LocalStorage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error {
var f io.WriteCloser var f io.WriteCloser
var err error var err error
@@ -144,6 +161,7 @@ func (s *LocalStorage) Put(token string, filename string, reader io.Reader, cont
return nil return nil
} }
// S3Storage is a storage backed by AWS S3
type S3Storage struct { type S3Storage struct {
Storage Storage
bucket string bucket string
@@ -154,6 +172,7 @@ type S3Storage struct {
noMultipart bool 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) { 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) sess := getAwsSession(accessKey, secretKey, region, endpoint, forcePathStyle)
@@ -167,10 +186,12 @@ func NewS3Storage(accessKey, secretKey, bucketName string, purgeDays int, region
}, nil }, nil
} }
// Type returns the storage type
func (s *S3Storage) Type() string { func (s *S3Storage) Type() string {
return "s3" return "s3"
} }
// Head retrieves content length of a file from storage
func (s *S3Storage) Head(token string, filename string) (contentLength uint64, err error) { func (s *S3Storage) Head(token string, filename string) (contentLength uint64, err error) {
key := fmt.Sprintf("%s/%s", token, filename) key := fmt.Sprintf("%s/%s", token, filename)
@@ -192,11 +213,13 @@ func (s *S3Storage) Head(token string, filename string) (contentLength uint64, e
return return
} }
// Purge cleans up the storage
func (s *S3Storage) Purge(days time.Duration) (err error) { func (s *S3Storage) Purge(days time.Duration) (err error) {
// NOOP expiration is set at upload time // NOOP expiration is set at upload time
return nil return nil
} }
// IsNotExist indicates if a file doesn't exist on storage
func (s *S3Storage) IsNotExist(err error) bool { func (s *S3Storage) IsNotExist(err error) bool {
if err == nil { if err == nil {
return false return false
@@ -212,6 +235,7 @@ func (s *S3Storage) IsNotExist(err error) bool {
return false return false
} }
// Get retrieves a file from storage
func (s *S3Storage) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) { func (s *S3Storage) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) {
key := fmt.Sprintf("%s/%s", token, filename) key := fmt.Sprintf("%s/%s", token, filename)
@@ -233,6 +257,7 @@ func (s *S3Storage) Get(token string, filename string) (reader io.ReadCloser, co
return return
} }
// Delete removes a file from storage
func (s *S3Storage) Delete(token string, filename string) (err error) { func (s *S3Storage) Delete(token string, filename string) (err error) {
metadata := fmt.Sprintf("%s/%s.metadata", token, filename) metadata := fmt.Sprintf("%s/%s.metadata", token, filename)
deleteRequest := &s3.DeleteObjectInput{ deleteRequest := &s3.DeleteObjectInput{
@@ -256,6 +281,7 @@ func (s *S3Storage) Delete(token string, filename string) (err error) {
return return
} }
// Put saves a file on storage
func (s *S3Storage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) (err error) { func (s *S3Storage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) (err error) {
key := fmt.Sprintf("%s/%s", token, filename) 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 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{ _, err = uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String(s.bucket), Bucket: aws.String(s.bucket),
Key: aws.String(key), Key: aws.String(key),
Body: reader, Body: reader,
Expires: aws.Time(time.Now().Add(s.purgeDays)), Expires: expire,
}) })
return return
} }
// GDrive is a storage backed by GDrive
type GDrive struct { type GDrive struct {
service *drive.Service service *drive.Service
rootId string rootID string
basedir string basedir string
localConfigPath string localConfigPath string
chunkSize int chunkSize int
logger *log.Logger logger *log.Logger
} }
func NewGDriveStorage(clientJsonFilepath string, localConfigPath string, basedir string, chunkSize int, logger *log.Logger) (*GDrive, error) { // NewGDriveStorage is the factory for GDrive
b, err := ioutil.ReadFile(clientJsonFilepath) func NewGDriveStorage(clientJSONFilepath string, localConfigPath string, basedir string, chunkSize int, logger *log.Logger) (*GDrive, error) {
b, err := ioutil.ReadFile(clientJSONFilepath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -310,7 +343,7 @@ func NewGDriveStorage(clientJsonFilepath string, localConfigPath string, basedir
} }
chunkSize = chunkSize * 1024 * 1024 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() err = storage.setupRoot()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -319,26 +352,26 @@ func NewGDriveStorage(clientJsonFilepath string, localConfigPath string, basedir
return storage, nil return storage, nil
} }
const GDriveRootConfigFile = "root_id.conf" const gdriveRootConfigFile = "root_id.conf"
const GDriveTokenJsonFile = "token.json" const gdriveTokenJSONFile = "token.json"
const GDriveDirectoryMimeType = "application/vnd.google-apps.folder" const gdriveDirectoryMimeType = "application/vnd.google-apps.folder"
func (s *GDrive) setupRoot() error { 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) { if err != nil && !os.IsNotExist(err) {
return err return err
} }
if string(rootId) != "" { if string(rootID) != "" {
s.rootId = string(rootId) s.rootID = string(rootID)
return nil return nil
} }
dir := &drive.File{ dir := &drive.File{
Name: s.basedir, Name: s.basedir,
MimeType: GDriveDirectoryMimeType, MimeType: gdriveDirectoryMimeType,
} }
di, err := s.service.Files.Create(dir).Fields("id").Do() di, err := s.service.Files.Create(dir).Fields("id").Do()
@@ -346,8 +379,8 @@ func (s *GDrive) setupRoot() error {
return err return err
} }
s.rootId = di.Id s.rootID = di.Id
err = ioutil.WriteFile(rootFileConfig, []byte(s.rootId), os.FileMode(0600)) err = ioutil.WriteFile(rootFileConfig, []byte(s.rootID), os.FileMode(0600))
if err != nil { if err != nil {
return err 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() 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)
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) l, err := s.list(nextPageToken, q)
if err != nil { if err != nil {
return "", err return "", err
@@ -377,7 +410,7 @@ func (s *GDrive) findId(filename string, token string) (string, error) {
for 0 < len(l.Files) { for 0 < len(l.Files) {
for _, fi := range l.Files { for _, fi := range l.Files {
tokenId = fi.Id tokenID = fi.Id
break break
} }
@@ -386,15 +419,18 @@ func (s *GDrive) findId(filename string, token string) (string, error) {
} }
l, err = s.list(l.NextPageToken, q) l, err = s.list(l.NextPageToken, q)
if err != nil {
return "", err
}
} }
if filename == "" { if filename == "" {
return tokenId, nil return tokenID, nil
} else if tokenId == "" { } else if tokenID == "" {
return "", fmt.Errorf("Cannot find file %s/%s", token, filename) 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) l, err = s.list(nextPageToken, q)
if err != nil { if err != nil {
return "", err return "", err
@@ -403,7 +439,7 @@ func (s *GDrive) findId(filename string, token string) (string, error) {
for 0 < len(l.Files) { for 0 < len(l.Files) {
for _, fi := range l.Files { for _, fi := range l.Files {
fileId = fi.Id fileID = fi.Id
break break
} }
@@ -412,28 +448,33 @@ func (s *GDrive) findId(filename string, token string) (string, error) {
} }
l, err = s.list(l.NextPageToken, q) 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 "", 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 { func (s *GDrive) Type() string {
return "gdrive" return "gdrive"
} }
// Head retrieves content length of a file from storage
func (s *GDrive) Head(token string, filename string) (contentLength uint64, err error) { func (s *GDrive) Head(token string, filename string) (contentLength uint64, err error) {
var fileId string var fileID string
fileId, err = s.findId(filename, token) fileID, err = s.findID(filename, token)
if err != nil { if err != nil {
return return
} }
var fi *drive.File 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 return
} }
@@ -442,15 +483,16 @@ func (s *GDrive) Head(token string, filename string) (contentLength uint64, err
return return
} }
// Get retrieves a file from storage
func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) { func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) {
var fileId string var fileID string
fileId, err = s.findId(filename, token) fileID, err = s.findID(filename, token)
if err != nil { if err != nil {
return return
} }
var fi *drive.File 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) { if !s.hasChecksum(fi) {
err = fmt.Errorf("Cannot find file %s/%s", token, filename) err = fmt.Errorf("Cannot find file %s/%s", token, filename)
return return
@@ -460,7 +502,7 @@ func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, conte
ctx := context.Background() ctx := context.Background()
var res *http.Response 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 { if err != nil {
return return
} }
@@ -470,25 +512,27 @@ func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, conte
return return
} }
// Delete removes a file from storage
func (s *GDrive) Delete(token string, filename string) (err error) { 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() s.service.Files.Delete(metadata).Do()
var fileId string var fileID string
fileId, err = s.findId(filename, token) fileID, err = s.findID(filename, token)
if err != nil { if err != nil {
return return
} }
err = s.service.Files.Delete(fileId).Do() err = s.service.Files.Delete(fileID).Do()
return return
} }
// Purge cleans up the storage
func (s *GDrive) Purge(days time.Duration) (err error) { func (s *GDrive) Purge(days time.Duration) (err error) {
nextPageToken := "" nextPageToken := ""
expirationDate := time.Now().Add(-1 * days).Format(time.RFC3339) 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) l, err := s.list(nextPageToken, q)
if err != nil { if err != nil {
return err return err
@@ -507,32 +551,39 @@ func (s *GDrive) Purge(days time.Duration) (err error) {
} }
l, err = s.list(l.NextPageToken, q) l, err = s.list(l.NextPageToken, q)
if err != nil {
return
}
} }
return return
} }
// IsNotExist indicates if a file doesn't exist on storage
func (s *GDrive) IsNotExist(err error) bool { func (s *GDrive) IsNotExist(err error) bool {
if err != nil { if err == nil {
if e, ok := err.(*googleapi.Error); ok { return false
return e.Code == http.StatusNotFound }
}
if e, ok := err.(*googleapi.Error); ok {
return e.Code == http.StatusNotFound
} }
return false return false
} }
// Put saves a file on storage
func (s *GDrive) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error { 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 { if err != nil {
return err return err
} }
if dirId == "" { if dirID == "" {
dir := &drive.File{ dir := &drive.File{
Name: token, Name: token,
Parents: []string{s.rootId}, Parents: []string{s.rootID},
MimeType: GDriveDirectoryMimeType, MimeType: gdriveDirectoryMimeType,
} }
di, err := s.service.Files.Create(dir).Fields("id").Do() 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 return err
} }
dirId = di.Id dirID = di.Id
} }
// Instantiate empty drive file // Instantiate empty drive file
dst := &drive.File{ dst := &drive.File{
Name: filename, Name: filename,
Parents: []string{dirId}, Parents: []string{dirID},
MimeType: contentType, 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. // Retrieve a token, saves the token, then returns the generated client.
func getGDriveClient(config *oauth2.Config, localConfigPath string, logger *log.Logger) *http.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) tok, err := gDriveTokenFromFile(tokenFile)
if err != nil { if err != nil {
tok = getGDriveTokenFromWeb(config, logger) tok = getGDriveTokenFromWeb(config, logger)
@@ -614,6 +665,7 @@ func saveGDriveToken(path string, token *oauth2.Token, logger *log.Logger) {
json.NewEncoder(f).Encode(token) json.NewEncoder(f).Encode(token)
} }
// StorjStorage is a storage backed by Storj
type StorjStorage struct { type StorjStorage struct {
Storage Storage
project *uplink.Project project *uplink.Project
@@ -622,6 +674,7 @@ type StorjStorage struct {
logger *log.Logger logger *log.Logger
} }
// NewStorjStorage is the factory for StorjStorage
func NewStorjStorage(access, bucket string, purgeDays int, logger *log.Logger) (*StorjStorage, error) { func NewStorjStorage(access, bucket string, purgeDays int, logger *log.Logger) (*StorjStorage, error) {
var instance StorjStorage var instance StorjStorage
var err error var err error
@@ -652,10 +705,12 @@ func NewStorjStorage(access, bucket string, purgeDays int, logger *log.Logger) (
return &instance, nil return &instance, nil
} }
// Type returns the storage type
func (s *StorjStorage) Type() string { func (s *StorjStorage) Type() string {
return "storj" return "storj"
} }
// Head retrieves content length of a file from storage
func (s *StorjStorage) Head(token string, filename string) (contentLength uint64, err error) { func (s *StorjStorage) Head(token string, filename string) (contentLength uint64, err error) {
key := storj.JoinPaths(token, filename) key := storj.JoinPaths(token, filename)
@@ -671,6 +726,7 @@ func (s *StorjStorage) Head(token string, filename string) (contentLength uint64
return return
} }
// Get retrieves a file from storage
func (s *StorjStorage) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) { func (s *StorjStorage) Get(token string, filename string) (reader io.ReadCloser, contentLength uint64, err error) {
key := storj.JoinPaths(token, filename) key := storj.JoinPaths(token, filename)
@@ -689,6 +745,7 @@ func (s *StorjStorage) Get(token string, filename string) (reader io.ReadCloser,
return return
} }
// Delete removes a file from storage
func (s *StorjStorage) Delete(token string, filename string) (err error) { func (s *StorjStorage) Delete(token string, filename string) (err error) {
key := storj.JoinPaths(token, filename) key := storj.JoinPaths(token, filename)
@@ -701,11 +758,13 @@ func (s *StorjStorage) Delete(token string, filename string) (err error) {
return return
} }
// Purge cleans up the storage
func (s *StorjStorage) Purge(days time.Duration) (err error) { func (s *StorjStorage) Purge(days time.Duration) (err error) {
// NOOP expiration is set at upload time // NOOP expiration is set at upload time
return nil 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) { func (s *StorjStorage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) (err error) {
key := storj.JoinPaths(token, filename) key := storj.JoinPaths(token, filename)
@@ -713,7 +772,12 @@ func (s *StorjStorage) Put(token string, filename string, reader io.Reader, cont
ctx := context.TODO() 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 { if err != nil {
return err return err
} }
@@ -735,6 +799,7 @@ func (s *StorjStorage) Put(token string, filename string, reader io.Reader, cont
return err return err
} }
// IsNotExist indicates if a file doesn't exist on storage
func (s *StorjStorage) IsNotExist(err error) bool { func (s *StorjStorage) IsNotExist(err error) bool {
return errors.Is(err, uplink.ErrObjectNotFound) return errors.Is(err, uplink.ErrObjectNotFound)
} }

45
server/token.go Normal file
View File

@@ -0,0 +1,45 @@
/*
The MIT License (MIT)
Copyright (c) 2020- Andrea Spacca and Stefan Benten.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package server
import (
"math/rand"
)
const (
// SYMBOLS characters used for short-urls
SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
// generate a token
func token(length int) string {
result := ""
for i := 0; i < length; i++ {
x := rand.Intn(len(SYMBOLS) - 1)
result = string(SYMBOLS[x]) + result
}
return result
}

15
server/token_test.go Normal file
View File

@@ -0,0 +1,15 @@
package server
import "testing"
func BenchmarkTokenConcat(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = token(5) + token(5)
}
}
func BenchmarkTokenLonger(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = token(10)
}
}

View File

@@ -2,6 +2,8 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/] Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/]
Copyright (c) 2018-2020 Andrea Spacca.
Copyright (c) 2020- Andrea Spacca and Stefan Benten.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -25,6 +27,7 @@ THE SOFTWARE.
package server package server
import ( import (
"fmt"
"math" "math"
"net/http" "net/http"
"net/mail" "net/mail"
@@ -47,8 +50,7 @@ func getAwsSession(accessKey, secretKey, region, endpoint string, forcePathStyle
} }
func formatNumber(format string, s uint64) string { func formatNumber(format string, s uint64) string {
return renderFloat(format, float64(s))
return RenderFloat(format, float64(s))
} }
var renderFloatPrecisionMultipliers = [10]float64{ var renderFloatPrecisionMultipliers = [10]float64{
@@ -77,7 +79,7 @@ var renderFloatPrecisionRounders = [10]float64{
0.0000000005, 0.0000000005,
} }
func RenderFloat(format string, n float64) string { func renderFloat(format string, n float64) string {
// Special cases: // Special cases:
// NaN = "NaN" // NaN = "NaN"
// +Inf = "+Infinity" // +Inf = "+Infinity"
@@ -125,7 +127,7 @@ func RenderFloat(format string, n float64) string {
// +0000 // +0000
if formatDirectiveIndices[0] == 0 { if formatDirectiveIndices[0] == 0 {
if formatDirectiveChars[formatDirectiveIndices[0]] != '+' { if formatDirectiveChars[formatDirectiveIndices[0]] != '+' {
panic("RenderFloat(): invalid positive sign directive") panic("renderFloat(): invalid positive sign directive")
} }
positiveStr = "+" positiveStr = "+"
formatDirectiveIndices = formatDirectiveIndices[1:] formatDirectiveIndices = formatDirectiveIndices[1:]
@@ -139,7 +141,7 @@ func RenderFloat(format string, n float64) string {
// 000,000.00 // 000,000.00
if len(formatDirectiveIndices) == 2 { if len(formatDirectiveIndices) == 2 {
if (formatDirectiveIndices[1] - formatDirectiveIndices[0]) != 4 { 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]]) thousandStr = string(formatDirectiveChars[formatDirectiveIndices[0]])
formatDirectiveIndices = formatDirectiveIndices[1:] formatDirectiveIndices = formatDirectiveIndices[1:]
@@ -199,8 +201,8 @@ func RenderFloat(format string, n float64) string {
return signStr + intStr + decimalStr + fracStr return signStr + intStr + decimalStr + fracStr
} }
func RenderInteger(format string, n int) string { func renderInteger(format string, n int) string {
return RenderFloat(format, float64(n)) return renderFloat(format, float64(n))
} }
// Request.RemoteAddress contains port, which we want to remove i.e.: // Request.RemoteAddress contains port, which we want to remove i.e.:
@@ -253,3 +255,27 @@ func acceptsHTML(hdr http.Header) bool {
return (false) return (false)
} }
func formatSize(size int64) string {
sizeFloat := float64(size)
base := math.Log(sizeFloat) / math.Log(1024)
sizeOn := math.Pow(1024, base-math.Floor(base))
var round float64
pow := math.Pow(10, float64(2))
digit := pow * sizeOn
round = math.Floor(digit)
newVal := round / pow
var suffixes [5]string
suffixes[0] = "B"
suffixes[1] = "KB"
suffixes[2] = "MB"
suffixes[3] = "GB"
suffixes[4] = "TB"
getSuffix := suffixes[int(math.Floor(base))]
return fmt.Sprintf("%s %s", strconv.FormatFloat(newVal, 'f', -1, 64), getSuffix)
}

View File

@@ -29,7 +29,6 @@ import (
"io" "io"
"net/http" "net/http"
_ "github.com/PuerkitoBio/ghost/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
virustotal "github.com/dutchcoders/go-virustotal" virustotal "github.com/dutchcoders/go-virustotal"