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
*.egg-info
.vagrant
.git
.tmp
bower_components
node_modules

View File

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

View File

@@ -18,3 +18,7 @@ issues:
max-same-issues: 0
new: false
exclude-use-default: false
exclude-rules:
- linters:
- staticcheck
text: "SA1019:"

View File

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

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
```
### 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.

View File

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

View File

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

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

View File

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