Compare commits

..

4 Commits

Author SHA1 Message Date
Andrea Spacca
a7b600c562 refinement 2022-03-02 22:49:20 +09:00
Andrea Spacca
715f2b4cd9 Merge branch 'main' into gpg-encryption-support 2022-03-02 22:34:40 +09:00
Andrea Spacca
fbf9a4facc gpg encryption support 2018-10-06 19:24:40 +02:00
Andrea Spacca
0c844c1f11 gpg encryption support 2018-10-06 19:04:46 +02:00
10 changed files with 205 additions and 126 deletions

View File

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

View File

@@ -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'

View File

@@ -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:"

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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

View File

@@ -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)
} }

View File

@@ -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)
} }
} }