Compare commits
4 Commits
main
...
gpg-encryp
Author | SHA1 | Date | |
---|---|---|---|
|
a7b600c562 | ||
|
715f2b4cd9 | ||
|
fbf9a4facc | ||
|
0c844c1f11 |
@@ -6,6 +6,7 @@ bin
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.egg-info
|
*.egg-info
|
||||||
.vagrant
|
.vagrant
|
||||||
|
.git
|
||||||
.tmp
|
.tmp
|
||||||
bower_components
|
bower_components
|
||||||
node_modules
|
node_modules
|
||||||
|
11
.github/workflows/build-docker-images.yml
vendored
11
.github/workflows/build-docker-images.yml
vendored
@@ -34,11 +34,9 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
TAGS="--tag ${DOCKER_IMAGE}:${VERSION}"
|
TAGS="--tag ${DOCKER_IMAGE}:${VERSION}"
|
||||||
TAGS_NOROOT="--tag ${DOCKER_IMAGE}:${VERSION}-noroot"
|
|
||||||
|
|
||||||
if [ $VERSION = edge -o $VERSION = nightly ]; then
|
if [ $VERSION = edge -o $VERSION = nightly ]; then
|
||||||
TAGS="$TAGS --tag ${DOCKER_IMAGE}:latest"
|
TAGS="$TAGS --tag ${DOCKER_IMAGE}:latest"
|
||||||
TAGS_NOROOT="$TAGS_NOROOT --tag ${DOCKER_IMAGE}:latest-noroot"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
||||||
@@ -48,12 +46,6 @@ jobs:
|
|||||||
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
|
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
|
||||||
--build-arg VCS_REF=${GITHUB_SHA::8} \
|
--build-arg VCS_REF=${GITHUB_SHA::8} \
|
||||||
${TAGS} .
|
${TAGS} .
|
||||||
echo ::set-output name=buildx_args_noroot::--platform ${DOCKER_PLATFORMS} \
|
|
||||||
--build-arg VERSION=${VERSION} \
|
|
||||||
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
|
|
||||||
--build-arg VCS_REF=${GITHUB_SHA::8} \
|
|
||||||
--build-arg RUNAS=noroot \
|
|
||||||
${TAGS_NOROOT} .
|
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
@@ -72,7 +64,6 @@ jobs:
|
|||||||
name: Docker Buildx (build)
|
name: Docker Buildx (build)
|
||||||
run: |
|
run: |
|
||||||
docker buildx build --no-cache --pull --output "type=image,push=false" ${{ steps.prepare.outputs.buildx_args }}
|
docker buildx build --no-cache --pull --output "type=image,push=false" ${{ steps.prepare.outputs.buildx_args }}
|
||||||
docker buildx build --output "type=image,push=false" ${{ steps.prepare.outputs.buildx_args_noroot }}
|
|
||||||
-
|
-
|
||||||
name: Docker Login
|
name: Docker Login
|
||||||
if: success() && github.event_name != 'pull_request'
|
if: success() && github.event_name != 'pull_request'
|
||||||
@@ -86,13 +77,11 @@ jobs:
|
|||||||
if: success() && github.event_name != 'pull_request'
|
if: success() && github.event_name != 'pull_request'
|
||||||
run: |
|
run: |
|
||||||
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }}
|
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }}
|
||||||
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args_noroot }}
|
|
||||||
-
|
-
|
||||||
name: Docker Check Manifest
|
name: Docker Check Manifest
|
||||||
if: always() && github.event_name != 'pull_request'
|
if: always() && github.event_name != 'pull_request'
|
||||||
run: |
|
run: |
|
||||||
docker run --rm mplatform/mquery ${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }}
|
docker run --rm mplatform/mquery ${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }}
|
||||||
docker run --rm mplatform/mquery ${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }}-noroot
|
|
||||||
-
|
-
|
||||||
name: Clear
|
name: Clear
|
||||||
if: always() && github.event_name != 'pull_request'
|
if: always() && github.event_name != 'pull_request'
|
||||||
|
@@ -18,3 +18,7 @@ issues:
|
|||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
new: false
|
new: false
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
|
exclude-rules:
|
||||||
|
- linters:
|
||||||
|
- staticcheck
|
||||||
|
text: "SA1019:"
|
||||||
|
@@ -21,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
|
||||||
|
|
||||||
|
17
Dockerfile
17
Dockerfile
@@ -14,27 +14,12 @@ ENV GO111MODULE=on
|
|||||||
# build & install server
|
# build & install server
|
||||||
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
|
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
|
||||||
|
|
||||||
ARG PUID=5000 \
|
|
||||||
PGID=5000 \
|
|
||||||
RUNAS
|
|
||||||
|
|
||||||
RUN mkdir -p /tmp/useradd && \
|
|
||||||
if [ ! -z "$RUNAS" ]; then \
|
|
||||||
echo "${RUNAS}:x:${PUID}:${PGID}::/nonexistent:/sbin/nologin" >> /tmp/useradd/passwd && \
|
|
||||||
echo "${RUNAS}:!:::::::" >> /tmp/useradd/shadow && \
|
|
||||||
echo "${RUNAS}:x:${PGID}:" >> /tmp/useradd/group && \
|
|
||||||
echo "${RUNAS}:!::" >> /tmp/useradd/groupshadow; else touch /tmp/useradd/unused; fi
|
|
||||||
|
|
||||||
FROM scratch AS final
|
FROM scratch AS final
|
||||||
LABEL maintainer="Andrea Spacca <andrea.spacca@gmail.com>"
|
LABEL maintainer="Andrea Spacca <andrea.spacca@gmail.com>"
|
||||||
ARG RUNAS
|
|
||||||
|
|
||||||
COPY --from=build /tmp/useradd/* /etc/
|
COPY --from=build /go/bin/transfersh /go/bin/transfersh
|
||||||
COPY --from=build --chown=${RUNAS} /go/bin/transfersh /go/bin/transfersh
|
|
||||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
|
||||||
USER ${RUNAS}
|
|
||||||
|
|
||||||
ENTRYPOINT ["/go/bin/transfersh", "--listener", ":8080"]
|
ENTRYPOINT ["/go/bin/transfersh", "--listener", ":8080"]
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
66
README.md
66
README.md
@@ -47,6 +47,16 @@ $ curl --upload-file ./hello.txt https://transfer.sh/hello.txt -H "Max-Downloads
|
|||||||
$ curl --upload-file ./hello.txt https://transfer.sh/hello.txt -H "Max-Days: 1" # Set the number of days before deletion
|
$ curl --upload-file ./hello.txt https://transfer.sh/hello.txt -H "Max-Days: 1" # Set the number of days before deletion
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### X-Encrypt-Password
|
||||||
|
```bash
|
||||||
|
$ curl --upload-file ./hello.txt https://transfer.sh/hello.txt -H "X-Encrypt-Password: test" # Encrypt the content sever side with AES265 using "test" as password
|
||||||
|
```
|
||||||
|
|
||||||
|
### X-Decrypt-Password
|
||||||
|
```bash
|
||||||
|
$ curl https://transfer.sh/BAYh0/hello.txt -H "X-Decrypt-Password: test" # Decrypt the content sever side with AES265 using "test" as password
|
||||||
|
```
|
||||||
|
|
||||||
## Response Headers
|
## Response Headers
|
||||||
|
|
||||||
### X-Url-Delete
|
### X-Url-Delete
|
||||||
@@ -140,31 +150,12 @@ $ go build -o transfersh main.go
|
|||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
For easy deployment, we've created an official Docker container. There are two variants, differing only by which user runs the process.
|
For easy deployment, we've created a Docker container.
|
||||||
|
|
||||||
The default one will run as `root`:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run --publish 8080:8080 dutchcoders/transfer.sh:latest --provider local --basedir /tmp/
|
docker run --publish 8080:8080 dutchcoders/transfer.sh:latest --provider local --basedir /tmp/
|
||||||
```
|
```
|
||||||
|
|
||||||
The one tagged with the suffix `-noroot` will use `5000` as both UID and GID:
|
|
||||||
```bash
|
|
||||||
docker run --publish 8080:8080 dutchcoders/transfer.sh:latest-noroot --provider local --basedir /tmp/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building the Container
|
|
||||||
You can also build the container yourself. This allows you to choose which UID/GID will be used, e.g. when using NFS mounts:
|
|
||||||
```bash
|
|
||||||
# Build arguments:
|
|
||||||
# * RUNAS: If empty, the container will run as root.
|
|
||||||
# Set this to anything to enable UID/GID selection.
|
|
||||||
# * PUID: UID of the process. Needs RUNAS != "". Defaults to 5000.
|
|
||||||
# * PGID: GID of the process. Needs RUNAS != "". Defaults to 5000.
|
|
||||||
|
|
||||||
docker build -t transfer.sh-noroot --build-arg RUNAS=doesntmatter --build-arg PUID=1337 --build-arg PGID=1338 .
|
|
||||||
```
|
|
||||||
|
|
||||||
## S3 Usage
|
## S3 Usage
|
||||||
|
|
||||||
For the usage with a AWS S3 Bucket, you just need to specify the following options:
|
For the usage with a AWS S3 Bucket, you just need to specify the following options:
|
||||||
@@ -223,41 +214,6 @@ You need to create an OAuth Client id from console.cloud.google.com, download th
|
|||||||
|
|
||||||
```go run main.go --provider gdrive --basedir /tmp/ --gdrive-client-json-filepath /[credential_dir] --gdrive-local-config-path [directory_to_save_config] ```
|
```go run main.go --provider gdrive --basedir /tmp/ --gdrive-client-json-filepath /[credential_dir] --gdrive-local-config-path [directory_to_save_config] ```
|
||||||
|
|
||||||
## Shell functions
|
|
||||||
|
|
||||||
### Bash and zsh (multiple files uploaded as zip archive)
|
|
||||||
##### Add this to .bashrc or .zshrc or its equivalent
|
|
||||||
```bash
|
|
||||||
transfer(){ if [ $# -eq 0 ];then echo "No arguments specified.\nUsage:\n transfer <file|directory>\n ... | transfer <file_name>">&2;return 1;fi;if tty -s;then file="$1";file_name=$(basename "$file");if [ ! -e "$file" ];then echo "$file: No such file or directory">&2;return 1;fi;if [ -d "$file" ];then file_name="$file_name.zip" ,;(cd "$file"&&zip -r -q - .)|curl --progress-bar --upload-file "-" "https://transfer.sh/$file_name"|tee /dev/null,;else cat "$file"|curl --progress-bar --upload-file "-" "https://transfer.sh/$file_name"|tee /dev/null;fi;else file_name=$1;curl --progress-bar --upload-file "-" "https://transfer.sh/$file_name"|tee /dev/null;fi;}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Now you can use transfer function
|
|
||||||
```
|
|
||||||
$ transfer hello.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Zsh (with delete url outpu)
|
|
||||||
##### Add this to .zshrc or its equivalent
|
|
||||||
```bash
|
|
||||||
transfer()
|
|
||||||
{
|
|
||||||
local file="${1}"
|
|
||||||
local filename="${file##*/}"
|
|
||||||
# show delete link from the response header after upload. the command "sed" is necessary to clean up the output, "gsub()" in "awk" does not work.
|
|
||||||
curl --request PUT --progress-bar --dump-header - --upload-file "${file}" "https://transfer.sh/${filename}" | sed "s/#//g" | awk '/x-url-delete/ { print "Delete command: curl --request DELETE " $2 } END{ print "Download link: " $1 }'
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Sample ouput
|
|
||||||
```bash
|
|
||||||
$ transfer image.img
|
|
||||||
######################################################################################################################################################################################################################################## 100.0%
|
|
||||||
Delete command: curl --request DELETE https://transfer.sh/Ge9cuW/image.img/<some_delete_token>
|
|
||||||
Download link: https://transfer.sh/Ge9cuW/image.img
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
Contributions are welcome.
|
Contributions are welcome.
|
||||||
|
@@ -58,6 +58,12 @@ import (
|
|||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
blackfriday "github.com/russross/blackfriday/v2"
|
blackfriday "github.com/russross/blackfriday/v2"
|
||||||
"github.com/skip2/go-qrcode"
|
"github.com/skip2/go-qrcode"
|
||||||
|
//lint:ignore SA1019 Ignore the deprecation warnings
|
||||||
|
"golang.org/x/crypto/openpgp"
|
||||||
|
//lint:ignore SA1019 Ignore the deprecation warnings
|
||||||
|
"golang.org/x/crypto/openpgp/armor"
|
||||||
|
//lint:ignore SA1019 Ignore the deprecation warnings
|
||||||
|
"golang.org/x/crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
const getPathPart = "get"
|
const getPathPart = "get"
|
||||||
@@ -65,6 +71,10 @@ const getPathPart = "get"
|
|||||||
var (
|
var (
|
||||||
htmlTemplates = initHTMLTemplates()
|
htmlTemplates = initHTMLTemplates()
|
||||||
textTemplates = initTextTemplates()
|
textTemplates = initTextTemplates()
|
||||||
|
|
||||||
|
packetConfig = &packet.Config{
|
||||||
|
DefaultCipher: packet.CipherAES256,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func stripPrefix(path string) string {
|
func stripPrefix(path string) string {
|
||||||
@@ -88,6 +98,96 @@ func initHTMLTemplates() *html_template.Template {
|
|||||||
return templates
|
return templates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transformEncryptionReader(reader io.ReadCloser, password string) (io.Reader, error) {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return encrypt(reader, password, packetConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformDecryptionReader(reader io.ReadCloser, password string) (io.Reader, error) {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return decrypt(reader, password, packetConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrypt(ciphertext io.ReadCloser, password string, packetConfig *packet.Config) (plaintext io.Reader, err error) {
|
||||||
|
content, err := ioutil.ReadAll(ciphertext)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decbuf := bytes.NewBuffer(content)
|
||||||
|
|
||||||
|
armorBlock, err := armor.Decode(decbuf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
failed := false
|
||||||
|
prompt := func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
||||||
|
// If the given passphrase isn't correct, the function will be called again, forever.
|
||||||
|
// This method will fail fast.
|
||||||
|
// Ref: https://godoc.org/golang.org/x/crypto/openpgp#PromptFunction
|
||||||
|
if failed {
|
||||||
|
return nil, errors.New("decryption failed")
|
||||||
|
}
|
||||||
|
failed = true
|
||||||
|
return []byte(password), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := openpgp.ReadMessage(armorBlock.Body, nil, prompt, packetConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext = md.UnverifiedBody
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func encrypt(plaintext io.ReadCloser, password string, packetConfig *packet.Config) (ciphertext io.Reader, err error) {
|
||||||
|
encbuf := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
w, err := armor.Encode(encbuf, "PGP MESSAGE", nil)
|
||||||
|
if err != nil {
|
||||||
|
safeClose(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pt, err := openpgp.SymmetricallyEncrypt(w, []byte(password), nil, packetConfig)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
safeClose(pt)
|
||||||
|
safeClose(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(plaintext)
|
||||||
|
if err != nil {
|
||||||
|
safeClose(pt)
|
||||||
|
safeClose(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pt.Write(content)
|
||||||
|
if err != nil {
|
||||||
|
safeClose(pt)
|
||||||
|
safeClose(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close writers to force-flush their buffer
|
||||||
|
safeClose(pt)
|
||||||
|
safeClose(w)
|
||||||
|
ciphertext = bytes.NewReader(encbuf.Bytes())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func healthHandler(w http.ResponseWriter, r *http.Request) {
|
func healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
_, _ = w.Write([]byte("Approaching Neutral Zone, all systems normal and functioning."))
|
_, _ = w.Write([]byte("Approaching Neutral Zone, all systems normal and functioning."))
|
||||||
}
|
}
|
||||||
@@ -360,7 +460,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata := metadataForRequest(contentType, s.randomTokenLength, r)
|
metadata := metadataForRequest(contentType, contentLength, 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 {
|
||||||
@@ -377,7 +477,13 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
s.logger.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(r.Context(), token, filename, file, contentType, uint64(contentLength)); err != nil {
|
reader, err := transformEncryptionReader(file, r.Header.Get("X-Encrypt-Password"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Could not crypt file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.storage.Put(r.Context(), token, filename, reader, contentType, uint64(contentLength)); err != nil {
|
||||||
s.logger.Printf("Backend storage error: %s", err.Error())
|
s.logger.Printf("Backend storage error: %s", err.Error())
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -415,8 +521,8 @@ func (s *Server) cleanTmpFile(f *os.File) {
|
|||||||
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
|
// ContentLength is is the original uploading content length
|
||||||
// Secret string
|
ContentLength int64
|
||||||
// Downloads is the actual number of downloads
|
// Downloads is the actual number of downloads
|
||||||
Downloads int
|
Downloads int
|
||||||
// MaxDownloads contains the maximum numbers of downloads
|
// MaxDownloads contains the maximum numbers of downloads
|
||||||
@@ -425,11 +531,16 @@ type metadata struct {
|
|||||||
MaxDate time.Time
|
MaxDate time.Time
|
||||||
// DeletionToken contains the token to match against for deletion
|
// DeletionToken contains the token to match against for deletion
|
||||||
DeletionToken string
|
DeletionToken string
|
||||||
|
// Encrypted contains if the file was encrypted
|
||||||
|
Encrypted bool
|
||||||
|
// DecryptedContentType is the original uploading content type
|
||||||
|
DecryptedContentType string
|
||||||
}
|
}
|
||||||
|
|
||||||
func metadataForRequest(contentType string, randomTokenLength int, r *http.Request) metadata {
|
func metadataForRequest(contentType string, contentLength int64, randomTokenLength int, r *http.Request) metadata {
|
||||||
metadata := metadata{
|
metadata := metadata{
|
||||||
ContentType: strings.ToLower(contentType),
|
ContentType: strings.ToLower(contentType),
|
||||||
|
ContentLength: contentLength,
|
||||||
MaxDate: time.Time{},
|
MaxDate: time.Time{},
|
||||||
Downloads: 0,
|
Downloads: 0,
|
||||||
MaxDownloads: -1,
|
MaxDownloads: -1,
|
||||||
@@ -448,6 +559,14 @@ func metadataForRequest(contentType string, randomTokenLength int, r *http.Reque
|
|||||||
metadata.MaxDate = time.Now().Add(time.Hour * 24 * time.Duration(v))
|
metadata.MaxDate = time.Now().Add(time.Hour * 24 * time.Duration(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if password := r.Header.Get("X-Encrypt-Password"); password != "" {
|
||||||
|
metadata.Encrypted = true
|
||||||
|
metadata.ContentType = "text/plain; charset=utf-8"
|
||||||
|
metadata.DecryptedContentType = contentType
|
||||||
|
} else {
|
||||||
|
metadata.Encrypted = false
|
||||||
|
}
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,7 +577,7 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
contentLength := r.ContentLength
|
contentLength := r.ContentLength
|
||||||
|
|
||||||
defer CloseCheck(r.Body.Close)
|
defer safeClose(r.Body)
|
||||||
|
|
||||||
file, err := ioutil.TempFile(s.tempPath, "transfer-")
|
file, err := ioutil.TempFile(s.tempPath, "transfer-")
|
||||||
defer s.cleanTmpFile(file)
|
defer s.cleanTmpFile(file)
|
||||||
@@ -521,7 +640,7 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
token := token(s.randomTokenLength)
|
token := token(s.randomTokenLength)
|
||||||
|
|
||||||
metadata := metadataForRequest(contentType, s.randomTokenLength, r)
|
metadata := metadataForRequest(contentType, contentLength, 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 {
|
||||||
@@ -540,7 +659,13 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
s.logger.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(r.Context(), token, filename, file, contentType, uint64(contentLength)); err != nil {
|
reader, err := transformEncryptionReader(file, r.Header.Get("X-Encrypt-Password"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Could not crypt file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.storage.Put(r.Context(), token, filename, reader, contentType, uint64(contentLength)); err != nil {
|
||||||
s.logger.Printf("Error putting new file: %s", err.Error())
|
s.logger.Printf("Error putting new file: %s", err.Error())
|
||||||
http.Error(w, "Could not save file", http.StatusInternalServerError)
|
http.Error(w, "Could not save file", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -651,6 +776,7 @@ func (metadata metadata) remainingLimitHeaderValues() (remainingDownloads, remai
|
|||||||
remainingDays = strconv.Itoa(int(timeDifference.Hours()/24) + 1)
|
remainingDays = strconv.Itoa(int(timeDifference.Hours()/24) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if metadata.MaxDownloads == -1 {
|
if metadata.MaxDownloads == -1 {
|
||||||
remainingDownloads = "n/a"
|
remainingDownloads = "n/a"
|
||||||
} else {
|
} else {
|
||||||
@@ -683,7 +809,7 @@ func (s *Server) checkMetadata(ctx context.Context, token, filename string, incr
|
|||||||
var metadata metadata
|
var metadata metadata
|
||||||
|
|
||||||
r, _, err := s.storage.Get(ctx, token, fmt.Sprintf("%s.metadata", filename))
|
r, _, err := s.storage.Get(ctx, token, fmt.Sprintf("%s.metadata", filename))
|
||||||
defer CloseCheck(r.Close)
|
defer safeClose(r)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return metadata, err
|
return metadata, err
|
||||||
@@ -719,7 +845,7 @@ func (s *Server) checkDeletionToken(ctx context.Context, deletionToken, token, f
|
|||||||
var metadata metadata
|
var metadata metadata
|
||||||
|
|
||||||
r, _, err := s.storage.Get(ctx, token, fmt.Sprintf("%s.metadata", filename))
|
r, _, err := s.storage.Get(ctx, token, fmt.Sprintf("%s.metadata", filename))
|
||||||
defer CloseCheck(r.Close)
|
defer safeClose(r)
|
||||||
|
|
||||||
if s.storage.IsNotExist(err) {
|
if s.storage.IsNotExist(err) {
|
||||||
return errors.New("metadata doesn't exist")
|
return errors.New("metadata doesn't exist")
|
||||||
@@ -781,7 +907,8 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
zipfilename := fmt.Sprintf("transfersh-%d.zip", uint16(time.Now().UnixNano()))
|
zipfilename := fmt.Sprintf("transfersh-%d.zip", uint16(time.Now().UnixNano()))
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/zip")
|
w.Header().Set("Content-Type", "application/zip")
|
||||||
commonHeader(w, zipfilename)
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", zipfilename))
|
||||||
|
w.Header().Set("Connection", "close")
|
||||||
|
|
||||||
zw := zip.NewWriter(w)
|
zw := zip.NewWriter(w)
|
||||||
|
|
||||||
@@ -797,7 +924,7 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reader, _, err := s.storage.Get(r.Context(), token, filename)
|
reader, _, err := s.storage.Get(r.Context(), token, filename)
|
||||||
defer CloseCheck(reader.Close)
|
defer safeClose(reader)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s.storage.IsNotExist(err) {
|
if s.storage.IsNotExist(err) {
|
||||||
@@ -847,13 +974,14 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
tarfilename := fmt.Sprintf("transfersh-%d.tar.gz", uint16(time.Now().UnixNano()))
|
tarfilename := fmt.Sprintf("transfersh-%d.tar.gz", uint16(time.Now().UnixNano()))
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/x-gzip")
|
w.Header().Set("Content-Type", "application/x-gzip")
|
||||||
commonHeader(w, tarfilename)
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", tarfilename))
|
||||||
|
w.Header().Set("Connection", "close")
|
||||||
|
|
||||||
gw := gzip.NewWriter(w)
|
gw := gzip.NewWriter(w)
|
||||||
defer CloseCheck(gw.Close)
|
defer safeClose(gw)
|
||||||
|
|
||||||
zw := tar.NewWriter(gw)
|
zw := tar.NewWriter(gw)
|
||||||
defer CloseCheck(zw.Close)
|
defer safeClose(zw)
|
||||||
|
|
||||||
for _, key := range strings.Split(files, ",") {
|
for _, key := range strings.Split(files, ",") {
|
||||||
key = resolveKey(key, s.proxyPath)
|
key = resolveKey(key, s.proxyPath)
|
||||||
@@ -867,7 +995,7 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reader, contentLength, err := s.storage.Get(r.Context(), token, filename)
|
reader, contentLength, err := s.storage.Get(r.Context(), token, filename)
|
||||||
defer CloseCheck(reader.Close)
|
defer safeClose(reader)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s.storage.IsNotExist(err) {
|
if s.storage.IsNotExist(err) {
|
||||||
@@ -908,10 +1036,11 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
tarfilename := fmt.Sprintf("transfersh-%d.tar", uint16(time.Now().UnixNano()))
|
tarfilename := fmt.Sprintf("transfersh-%d.tar", uint16(time.Now().UnixNano()))
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/x-tar")
|
w.Header().Set("Content-Type", "application/x-tar")
|
||||||
commonHeader(w, tarfilename)
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", tarfilename))
|
||||||
|
w.Header().Set("Connection", "close")
|
||||||
|
|
||||||
zw := tar.NewWriter(w)
|
zw := tar.NewWriter(w)
|
||||||
defer CloseCheck(zw.Close)
|
defer safeClose(zw)
|
||||||
|
|
||||||
for _, key := range strings.Split(files, ",") {
|
for _, key := range strings.Split(files, ",") {
|
||||||
key = resolveKey(key, s.proxyPath)
|
key = resolveKey(key, s.proxyPath)
|
||||||
@@ -925,7 +1054,7 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reader, contentLength, err := s.storage.Get(r.Context(), token, filename)
|
reader, contentLength, err := s.storage.Get(r.Context(), token, filename)
|
||||||
defer CloseCheck(reader.Close)
|
defer safeClose(reader)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s.storage.IsNotExist(err) {
|
if s.storage.IsNotExist(err) {
|
||||||
@@ -1009,7 +1138,7 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
contentType := metadata.ContentType
|
contentType := metadata.ContentType
|
||||||
reader, contentLength, err := s.storage.Get(r.Context(), token, filename)
|
reader, contentLength, err := s.storage.Get(r.Context(), token, filename)
|
||||||
defer CloseCheck(reader.Close)
|
defer safeClose(reader)
|
||||||
|
|
||||||
if s.storage.IsNotExist(err) {
|
if s.storage.IsNotExist(err) {
|
||||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
@@ -1021,7 +1150,6 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var disposition string
|
var disposition string
|
||||||
|
|
||||||
if action == "inline" {
|
if action == "inline" {
|
||||||
disposition = "inline"
|
disposition = "inline"
|
||||||
} else {
|
} else {
|
||||||
@@ -1030,11 +1158,8 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
remainingDownloads, remainingDays := metadata.remainingLimitHeaderValues()
|
remainingDownloads, remainingDays := metadata.remainingLimitHeaderValues()
|
||||||
|
|
||||||
w.Header().Set("Content-Type", contentType)
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"`, disposition, filename))
|
||||||
w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10))
|
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"", disposition, filename))
|
|
||||||
w.Header().Set("Connection", "keep-alive")
|
w.Header().Set("Connection", "keep-alive")
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
|
||||||
w.Header().Set("X-Remaining-Downloads", remainingDownloads)
|
w.Header().Set("X-Remaining-Downloads", remainingDownloads)
|
||||||
w.Header().Set("X-Remaining-Days", remainingDays)
|
w.Header().Set("X-Remaining-Days", remainingDays)
|
||||||
|
|
||||||
@@ -1063,19 +1188,28 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = io.Copy(w, reader); err != nil {
|
password := r.Header.Get("X-Decrypt-Password");
|
||||||
|
decryptionReader, err := transformDecryptionReader(reader, password)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Could not decrypt file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.Encrypted && len(password) > 0 {
|
||||||
|
contentType = metadata.DecryptedContentType
|
||||||
|
contentLength = uint64(metadata.ContentLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10))
|
||||||
|
|
||||||
|
if _, err = io.Copy(w, decryptionReader); err != nil {
|
||||||
s.logger.Printf("%s", err.Error())
|
s.logger.Printf("%s", err.Error())
|
||||||
http.Error(w, "Error occurred copying to output stream", http.StatusInternalServerError)
|
http.Error(w, "Error occurred copying to output stream", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func commonHeader(w http.ResponseWriter, filename string) {
|
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
|
||||||
w.Header().Set("Connection", "close")
|
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RedirectHandler handles redirect
|
// 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) {
|
||||||
|
@@ -25,34 +25,39 @@ THE SOFTWARE.
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
crypto_rand "crypto/rand"
|
crypto_rand "crypto/rand"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
gorillaHandlers "github.com/gorilla/handlers"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/ghost/handlers"
|
"github.com/PuerkitoBio/ghost/handlers"
|
||||||
"github.com/VojtechVitek/ratelimit"
|
"github.com/VojtechVitek/ratelimit"
|
||||||
"github.com/VojtechVitek/ratelimit/memory"
|
"github.com/VojtechVitek/ratelimit/memory"
|
||||||
gorillaHandlers "github.com/gorilla/handlers"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
|
// import pprof
|
||||||
|
_ "net/http/pprof"
|
||||||
|
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
web "github.com/dutchcoders/transfer.sh-web"
|
web "github.com/dutchcoders/transfer.sh-web"
|
||||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||||
|
|
||||||
|
autocert "golang.org/x/crypto/acme/autocert"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parse request with maximum memory of _24Kilobits
|
// parse request with maximum memory of _24Kilobits
|
||||||
|
@@ -151,7 +151,7 @@ func (s *LocalStorage) Put(ctx context.Context, token string, filename string, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
f, err = os.OpenFile(filepath.Join(path, filename), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
f, err = os.OpenFile(filepath.Join(path, filename), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
defer CloseCheck(f.Close)
|
defer safeClose(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -650,7 +650,7 @@ func getGDriveTokenFromWeb(ctx context.Context, config *oauth2.Config, logger *l
|
|||||||
// Retrieves a token from a local file.
|
// Retrieves a token from a local file.
|
||||||
func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
|
func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
|
||||||
f, err := os.Open(file)
|
f, err := os.Open(file)
|
||||||
defer CloseCheck(f.Close)
|
defer safeClose(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -663,7 +663,7 @@ func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
|
|||||||
func saveGDriveToken(path string, token *oauth2.Token, logger *log.Logger) {
|
func saveGDriveToken(path string, token *oauth2.Token, logger *log.Logger) {
|
||||||
logger.Printf("Saving credential file to: %s\n", path)
|
logger.Printf("Saving credential file to: %s\n", path)
|
||||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
defer CloseCheck(f.Close)
|
defer safeClose(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("Unable to cache oauth token: %v", err)
|
logger.Fatalf("Unable to cache oauth token: %v", err)
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
@@ -280,8 +281,12 @@ func formatSize(size int64) string {
|
|||||||
return fmt.Sprintf("%s %s", strconv.FormatFloat(newVal, 'f', -1, 64), getSuffix)
|
return fmt.Sprintf("%s %s", strconv.FormatFloat(newVal, 'f', -1, 64), getSuffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CloseCheck(f func() error) {
|
func safeClose(c io.Closer) {
|
||||||
if err := f(); err != nil {
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Close(); err != nil {
|
||||||
fmt.Println("Received close error:", err)
|
fmt.Println("Received close error:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user