Compare commits
4 Commits
main
...
gpg-encryp
Author | SHA1 | Date | |
---|---|---|---|
|
a7b600c562 | ||
|
715f2b4cd9 | ||
|
fbf9a4facc | ||
|
0c844c1f11 |
@@ -6,6 +6,7 @@ bin
|
||||
*.pyc
|
||||
*.egg-info
|
||||
.vagrant
|
||||
.git
|
||||
.tmp
|
||||
bower_components
|
||||
node_modules
|
||||
|
11
.github/workflows/build-docker-images.yml
vendored
11
.github/workflows/build-docker-images.yml
vendored
@@ -34,11 +34,9 @@ jobs:
|
||||
fi
|
||||
|
||||
TAGS="--tag ${DOCKER_IMAGE}:${VERSION}"
|
||||
TAGS_NOROOT="--tag ${DOCKER_IMAGE}:${VERSION}-noroot"
|
||||
|
||||
if [ $VERSION = edge -o $VERSION = nightly ]; then
|
||||
TAGS="$TAGS --tag ${DOCKER_IMAGE}:latest"
|
||||
TAGS_NOROOT="$TAGS_NOROOT --tag ${DOCKER_IMAGE}:latest-noroot"
|
||||
fi
|
||||
|
||||
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 VCS_REF=${GITHUB_SHA::8} \
|
||||
${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
|
||||
uses: docker/setup-qemu-action@v1
|
||||
@@ -72,7 +64,6 @@ jobs:
|
||||
name: Docker Buildx (build)
|
||||
run: |
|
||||
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
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
@@ -86,13 +77,11 @@ jobs:
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
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_noroot }}
|
||||
-
|
||||
name: Docker Check Manifest
|
||||
if: always() && github.event_name != 'pull_request'
|
||||
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 }}-noroot
|
||||
-
|
||||
name: Clear
|
||||
if: always() && github.event_name != 'pull_request'
|
||||
|
@@ -18,3 +18,7 @@ issues:
|
||||
max-same-issues: 0
|
||||
new: false
|
||||
exclude-use-default: false
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA1019:"
|
||||
|
17
Dockerfile
17
Dockerfile
@@ -14,27 +14,12 @@ ENV GO111MODULE=on
|
||||
# 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
|
||||
|
||||
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
|
||||
LABEL maintainer="Andrea Spacca <andrea.spacca@gmail.com>"
|
||||
ARG RUNAS
|
||||
|
||||
COPY --from=build /tmp/useradd/* /etc/
|
||||
COPY --from=build --chown=${RUNAS} /go/bin/transfersh /go/bin/transfersh
|
||||
COPY --from=build /go/bin/transfersh /go/bin/transfersh
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
USER ${RUNAS}
|
||||
|
||||
ENTRYPOINT ["/go/bin/transfersh", "--listener", ":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
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
### X-Url-Delete
|
||||
@@ -140,31 +150,12 @@ $ go build -o transfersh main.go
|
||||
|
||||
## Docker
|
||||
|
||||
For easy deployment, we've created an official Docker container. There are two variants, differing only by which user runs the process.
|
||||
|
||||
The default one will run as `root`:
|
||||
For easy deployment, we've created a Docker container.
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
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] ```
|
||||
|
||||
## 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 are welcome.
|
||||
|
@@ -58,6 +58,12 @@ import (
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
blackfriday "github.com/russross/blackfriday/v2"
|
||||
"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"
|
||||
@@ -65,6 +71,10 @@ const getPathPart = "get"
|
||||
var (
|
||||
htmlTemplates = initHTMLTemplates()
|
||||
textTemplates = initTextTemplates()
|
||||
|
||||
packetConfig = &packet.Config{
|
||||
DefaultCipher: packet.CipherAES256,
|
||||
}
|
||||
)
|
||||
|
||||
func stripPrefix(path string) string {
|
||||
@@ -88,6 +98,96 @@ func initHTMLTemplates() *html_template.Template {
|
||||
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) {
|
||||
_, _ = 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{}
|
||||
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)
|
||||
|
||||
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())
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -415,8 +521,8 @@ func (s *Server) cleanTmpFile(f *os.File) {
|
||||
type metadata struct {
|
||||
// ContentType is the original uploading content type
|
||||
ContentType string
|
||||
// Secret as knowledge to delete file
|
||||
// Secret string
|
||||
// ContentLength is is the original uploading content length
|
||||
ContentLength int64
|
||||
// Downloads is the actual number of downloads
|
||||
Downloads int
|
||||
// MaxDownloads contains the maximum numbers of downloads
|
||||
@@ -425,11 +531,16 @@ type metadata struct {
|
||||
MaxDate time.Time
|
||||
// DeletionToken contains the token to match against for deletion
|
||||
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{
|
||||
ContentType: strings.ToLower(contentType),
|
||||
ContentLength: contentLength,
|
||||
MaxDate: time.Time{},
|
||||
Downloads: 0,
|
||||
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))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -458,7 +577,7 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
contentLength := r.ContentLength
|
||||
|
||||
defer CloseCheck(r.Body.Close)
|
||||
defer safeClose(r.Body)
|
||||
|
||||
file, err := ioutil.TempFile(s.tempPath, "transfer-")
|
||||
defer s.cleanTmpFile(file)
|
||||
@@ -521,7 +640,7 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
token := token(s.randomTokenLength)
|
||||
|
||||
metadata := metadataForRequest(contentType, s.randomTokenLength, r)
|
||||
metadata := metadataForRequest(contentType, contentLength, s.randomTokenLength, r)
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
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)
|
||||
|
||||
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())
|
||||
http.Error(w, "Could not save file", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -651,6 +776,7 @@ func (metadata metadata) remainingLimitHeaderValues() (remainingDownloads, remai
|
||||
remainingDays = strconv.Itoa(int(timeDifference.Hours()/24) + 1)
|
||||
}
|
||||
|
||||
|
||||
if metadata.MaxDownloads == -1 {
|
||||
remainingDownloads = "n/a"
|
||||
} else {
|
||||
@@ -683,7 +809,7 @@ func (s *Server) checkMetadata(ctx context.Context, token, filename string, incr
|
||||
var metadata metadata
|
||||
|
||||
r, _, err := s.storage.Get(ctx, token, fmt.Sprintf("%s.metadata", filename))
|
||||
defer CloseCheck(r.Close)
|
||||
defer safeClose(r)
|
||||
|
||||
if err != nil {
|
||||
return metadata, err
|
||||
@@ -719,7 +845,7 @@ func (s *Server) checkDeletionToken(ctx context.Context, deletionToken, token, f
|
||||
var metadata metadata
|
||||
|
||||
r, _, err := s.storage.Get(ctx, token, fmt.Sprintf("%s.metadata", filename))
|
||||
defer CloseCheck(r.Close)
|
||||
defer safeClose(r)
|
||||
|
||||
if s.storage.IsNotExist(err) {
|
||||
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()))
|
||||
|
||||
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)
|
||||
|
||||
@@ -797,7 +924,7 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
reader, _, err := s.storage.Get(r.Context(), token, filename)
|
||||
defer CloseCheck(reader.Close)
|
||||
defer safeClose(reader)
|
||||
|
||||
if err != nil {
|
||||
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()))
|
||||
|
||||
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)
|
||||
defer CloseCheck(gw.Close)
|
||||
defer safeClose(gw)
|
||||
|
||||
zw := tar.NewWriter(gw)
|
||||
defer CloseCheck(zw.Close)
|
||||
defer safeClose(zw)
|
||||
|
||||
for _, key := range strings.Split(files, ",") {
|
||||
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)
|
||||
defer CloseCheck(reader.Close)
|
||||
defer safeClose(reader)
|
||||
|
||||
if err != nil {
|
||||
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()))
|
||||
|
||||
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)
|
||||
defer CloseCheck(zw.Close)
|
||||
defer safeClose(zw)
|
||||
|
||||
for _, key := range strings.Split(files, ",") {
|
||||
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)
|
||||
defer CloseCheck(reader.Close)
|
||||
defer safeClose(reader)
|
||||
|
||||
if err != nil {
|
||||
if s.storage.IsNotExist(err) {
|
||||
@@ -1009,7 +1138,7 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
contentType := metadata.ContentType
|
||||
reader, contentLength, err := s.storage.Get(r.Context(), token, filename)
|
||||
defer CloseCheck(reader.Close)
|
||||
defer safeClose(reader)
|
||||
|
||||
if s.storage.IsNotExist(err) {
|
||||
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
|
||||
|
||||
if action == "inline" {
|
||||
disposition = "inline"
|
||||
} else {
|
||||
@@ -1030,11 +1158,8 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
remainingDownloads, remainingDays := metadata.remainingLimitHeaderValues()
|
||||
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10))
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"", disposition, filename))
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"`, disposition, filename))
|
||||
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-Days", remainingDays)
|
||||
|
||||
@@ -1063,19 +1188,28 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
||||
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())
|
||||
http.Error(w, "Error occurred copying to output stream", http.StatusInternalServerError)
|
||||
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
|
||||
func (s *Server) RedirectHandler(h http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@@ -25,34 +25,39 @@ THE SOFTWARE.
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
crypto_rand "crypto/rand"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
gorillaHandlers "github.com/gorilla/handlers"
|
||||
"log"
|
||||
"math/rand"
|
||||
"mime"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
context "golang.org/x/net/context"
|
||||
|
||||
"github.com/PuerkitoBio/ghost/handlers"
|
||||
"github.com/VojtechVitek/ratelimit"
|
||||
"github.com/VojtechVitek/ratelimit/memory"
|
||||
gorillaHandlers "github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
|
||||
// import pprof
|
||||
_ "net/http/pprof"
|
||||
|
||||
"crypto/tls"
|
||||
|
||||
web "github.com/dutchcoders/transfer.sh-web"
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
|
||||
autocert "golang.org/x/crypto/acme/autocert"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// 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)
|
||||
defer CloseCheck(f.Close)
|
||||
defer safeClose(f)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -650,7 +650,7 @@ func getGDriveTokenFromWeb(ctx context.Context, config *oauth2.Config, logger *l
|
||||
// Retrieves a token from a local file.
|
||||
func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
|
||||
f, err := os.Open(file)
|
||||
defer CloseCheck(f.Close)
|
||||
defer safeClose(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -663,7 +663,7 @@ func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
|
||||
func saveGDriveToken(path string, token *oauth2.Token, logger *log.Logger) {
|
||||
logger.Printf("Saving credential file to: %s\n", path)
|
||||
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 {
|
||||
logger.Fatalf("Unable to cache oauth token: %v", err)
|
||||
}
|
||||
|
@@ -28,6 +28,7 @@ package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
@@ -280,8 +281,12 @@ func formatSize(size int64) string {
|
||||
return fmt.Sprintf("%s %s", strconv.FormatFloat(newVal, 'f', -1, 64), getSuffix)
|
||||
}
|
||||
|
||||
func CloseCheck(f func() error) {
|
||||
if err := f(); err != nil {
|
||||
func safeClose(c io.Closer) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Close(); err != nil {
|
||||
fmt.Println("Received close error:", err)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user