Compare commits

..

5 Commits

Author SHA1 Message Date
Andrea Spacca
3b777e1c19 gpg encryption support (#162)
* gpg encryption support

* gpg encryption support

* refinement

* refactor using protonmail opengpg

* warning

* remove exception in linting

* reduce I/O on decrypt

* fix typo

* streaming in encrypt

* Update README.md

do not reference server public hosting version in encrypt/decrypt headers example

* linting

* linting fix

* increase default random-token-length to 10
2023-03-11 10:08:55 +09:00
Andrea Spacca
28fc9c73f5 Lint accept range (#535)
* Support Range header for GET

* Adds 'Accept-Ranges: bytes' header to handlers

* proper param name in error

* bump gdrive dependecies

* refactor CloseCheck to avoid panic on nil, remove range/audio/video special handling on get handler

* refactor CloseCheck to avoid panic on nil, handle range with no limit

* refactor CloseCheck to avoid panic on nil, handle range in gdrive storage

* refactor CloseCheck to avoid panic on nil

* handle range with no limit

* lint

* bump deps

* go mod tidy

* gofmt

* cr fixes, replace deprecated ioutil.NopCloser

* linting

* do not write to file by default

* fix storj

---------

Co-authored-by: Vladislav Grubov <vladgrubov@gmail.com>
2023-03-11 01:41:43 +09:00
dependabot[bot]
c452bd4719 Bump golang.org/x/net from 0.0.0-20220513224357-95641704303c to 0.7.0 (#534)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20220513224357-95641704303c to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/commits/v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 16:39:47 +01:00
dependabot[bot]
a4475513bc Bump golang.org/x/crypto from 0.0.0-20220131195533-30dcbda58838 to 0.1.0 (#533)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20220131195533-30dcbda58838 to 0.1.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/commits/v0.1.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 16:07:10 +01:00
Andrea Spacca
9f1fe62e05 min go version 1.18, include tip for test (#532)
* min go version 1.18, include tip for test

* lint

* lint

* lint

* lint

* lint

* lint

* lint

* lint

* lint

* lint

* lint

* lint

* lint
2023-03-01 21:51:24 +09:00
11 changed files with 432 additions and 1316 deletions

View File

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

View File

@@ -13,16 +13,27 @@ jobs:
fail-fast: false
matrix:
go_version:
- 1.15.x
- 1.16.x
- 1.17.x
- 1.18.X
- '1.18'
- '1.19'
- '1.20'
- tip
name: Test with ${{ matrix.go_version }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v1
- name: Install Go ${{ matrix.go_version }}
if: ${{ matrix.go_version != 'tip' }}
uses: actions/setup-go@master
with:
go-version: ${{ matrix.go_version }}
check-latest: true
- name: Install Go ${{ matrix.go_version }}
if: ${{ matrix.go_version == 'tip' }}
run: |
curl -sL https://storage.googleapis.com/go-build-snap/go/linux-amd64/$(git ls-remote https://github.com/golang/go.git HEAD | awk '{print $1;}').tar.gz -o gotip.tar.gz
ls -lah gotip.tar.gz
mkdir -p ~/sdk/gotip
tar -C ~/sdk/gotip -xzf gotip.tar.gz
echo "PATH=$HOME/go/bin:$HOME/sdk/gotip/bin/:$PATH" >> $GITHUB_ENV
- name: Vet and test
run: |
go version
@@ -33,9 +44,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v1
- uses: actions/setup-go@master
with:
go-version: 1.18
go-version: '1.20'
check-latest: true
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:

View File

@@ -47,6 +47,18 @@ $ 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
#### Beware, use this feature only on your self-hosted server: trusting a third-party service for server side encryption is at your own risk
```bash
$ curl --upload-file ./hello.txt https://your-transfersh-instance.tld/hello.txt -H "X-Encrypt-Password: test" # Encrypt the content sever side with AES265 using "test" as password
```
### X-Decrypt-Password
#### Beware, use this feature only on your self-hosted server: trusting a third-party service for server side encryption is at your own risk
```bash
$ curl https://your-transfersh-instance.tld/BAYh0/hello.txt -H "X-Decrypt-Password: test" # Decrypt the content sever side with AES265 using "test" as password
```
## Response Headers
### X-Url-Delete

View File

@@ -2,11 +2,12 @@ package cmd
import (
"fmt"
"github.com/dutchcoders/transfer.sh/server/storage"
"log"
"os"
"strings"
"github.com/dutchcoders/transfer.sh/server/storage"
"github.com/dutchcoders/transfer.sh/server"
"github.com/fatih/color"
"github.com/urfave/cli"
@@ -290,7 +291,7 @@ var globalFlags = []cli.Flag{
cli.IntFlag{
Name: "random-token-length",
Usage: "",
Value: 6,
Value: 10,
EnvVar: "RANDOM_TOKEN_LENGTH",
},
}

91
go.mod
View File

@@ -1,40 +1,75 @@
module github.com/dutchcoders/transfer.sh
go 1.15
go 1.18
require (
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8
github.com/ProtonMail/gopenpgp/v2 v2.5.2
github.com/PuerkitoBio/ghost v0.0.0-20160324114900-206e6e460e14
github.com/VojtechVitek/ratelimit v0.0.0-20160722140851-dc172bc0f6d2
github.com/aws/aws-sdk-go v1.44.211
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
github.com/dutchcoders/go-virustotal v0.0.0-20140923143438-24cc8e6fa329
github.com/dutchcoders/transfer.sh-web v0.0.0-20221119114740-ca3a2621d2a6
github.com/elazarl/go-bindata-assetfs v1.0.1
github.com/fatih/color v1.14.1
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/microcosm-cc/bluemonday v1.0.23
github.com/russross/blackfriday/v2 v2.1.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
github.com/urfave/cli v1.22.12
golang.org/x/crypto v0.6.0
golang.org/x/net v0.8.0
golang.org/x/oauth2 v0.5.0
google.golang.org/api v0.111.0
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
storj.io/common v0.0.0-20230301105927-7f966760c100
storj.io/uplink v1.10.0
)
require (
cloud.google.com/go/compute v1.18.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/PuerkitoBio/ghost v0.0.0-20160324114900-206e6e460e14
github.com/VojtechVitek/ratelimit v0.0.0-20160722140851-dc172bc0f6d2
github.com/aws/aws-sdk-go v1.37.14
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
github.com/dutchcoders/go-virustotal v0.0.0-20140923143438-24cc8e6fa329
github.com/dutchcoders/transfer.sh-web v0.0.0-20220824020025-7240e75c3bb8
github.com/elazarl/go-bindata-assetfs v1.0.1
github.com/fatih/color v1.10.0
github.com/garyburd/redigo v1.6.2 // indirect
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f
github.com/aymerick/douceur v0.2.0 // indirect
github.com/calebcase/tmpfile v1.0.3 // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/flynn/noise v1.0.0 // indirect
github.com/garyburd/redigo v1.6.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.2 // indirect
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.16
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jtolio/eventkit v0.0.0-20230301123942-0cee1388f16f // indirect
github.com/jtolio/noiseconn v0.0.0-20230227223919-bddcd1327059 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/russross/blackfriday/v2 v2.1.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
github.com/urfave/cli v1.22.5
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
golang.org/x/net v0.6.0 // indirect
golang.org/x/oauth2 v0.5.0
google.golang.org/api v0.109.0
google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/spacemonkeygo/monkit/v3 v3.0.19 // indirect
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
github.com/zeebo/errs v1.3.0 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514 // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
storj.io/common v0.0.0-20220405183405-ffdc3ab808c6
storj.io/uplink v1.8.2
storj.io/drpc v0.0.33-0.20230204035225-c9649dee8f2a // indirect
storj.io/picobuf v0.0.1 // indirect
)

1293
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,6 @@ import (
"html"
htmlTemplate "html/template"
"io"
"io/ioutil"
"mime"
"net"
"net/http"
@@ -53,13 +52,17 @@ import (
textTemplate "text/template"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/dutchcoders/transfer.sh/server/storage"
web "github.com/dutchcoders/transfer.sh-web"
"github.com/gorilla/mux"
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday/v2"
"github.com/skip2/go-qrcode"
blackfriday "github.com/russross/blackfriday/v2"
qrcode "github.com/skip2/go-qrcode"
"golang.org/x/net/idna"
)
@@ -91,6 +94,128 @@ func initHTMLTemplates() *htmlTemplate.Template {
return templates
}
func attachEncryptionReader(reader io.ReadCloser, password string) (io.ReadCloser, error) {
if len(password) == 0 {
return reader, nil
}
return encrypt(reader, []byte(password))
}
func attachDecryptionReader(reader io.ReadCloser, password string) (io.ReadCloser, error) {
if len(password) == 0 {
return reader, nil
}
return decrypt(reader, []byte(password))
}
func decrypt(ciphertext io.ReadCloser, password []byte) (plaintext io.ReadCloser, err error) {
unarmored, err := armor.Decode(ciphertext)
if err != nil {
return
}
firstTimeCalled := true
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if firstTimeCalled {
firstTimeCalled = false
return password, nil
}
// Re-prompt still occurs if SKESK pasrsing fails (i.e. when decrypted cipher algo is invalid).
// For most (but not all) cases, inputting a wrong passwords is expected to trigger this error.
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
}
config := &packet.Config{
DefaultCipher: packet.CipherAES256,
}
var emptyKeyRing openpgp.EntityList
md, err := openpgp.ReadMessage(unarmored.Body, emptyKeyRing, prompt, config)
if err != nil {
// Parsing errors when reading the message are most likely caused by incorrect password, but we cannot know for sure
return
}
plaintext = io.NopCloser(md.UnverifiedBody)
return
}
type encryptWrapperReader struct {
plaintext io.Reader
encrypt io.WriteCloser
armored io.WriteCloser
buffer io.ReadWriter
plaintextReadZero bool
}
func (e *encryptWrapperReader) Read(p []byte) (n int, err error) {
p2 := make([]byte, len(p))
n, _ = e.plaintext.Read(p2)
if n == 0 {
if !e.plaintextReadZero {
err = e.encrypt.Close()
if err != nil {
return
}
err = e.armored.Close()
if err != nil {
return
}
e.plaintextReadZero = true
}
return e.buffer.Read(p)
}
return e.buffer.Read(p)
}
func (e *encryptWrapperReader) Close() error {
return nil
}
func NewEncryptWrapperReader(plaintext io.Reader, armored, encrypt io.WriteCloser, buffer io.ReadWriter) io.ReadCloser {
return &encryptWrapperReader{
plaintext: io.TeeReader(plaintext, encrypt),
encrypt: encrypt,
armored: armored,
buffer: buffer,
}
}
func encrypt(plaintext io.ReadCloser, password []byte) (ciphertext io.ReadCloser, err error) {
bufferReadWriter := new(bytes.Buffer)
armored, err := armor.Encode(bufferReadWriter, constants.PGPMessageHeader, nil)
if err != nil {
return
}
config := &packet.Config{
DefaultCipher: packet.CipherAES256,
Time: time.Now,
}
hints := &openpgp.FileHints{
IsBinary: true,
FileName: "",
ModTime: time.Unix(time.Now().Unix(), 0),
}
encryptWriter, err := openpgp.SymmetricallyEncrypt(armored, password, hints, config)
if err != nil {
return
}
ciphertext = NewEncryptWrapperReader(plaintext, armored, encryptWriter, bufferReadWriter)
return
}
func healthHandler(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("Approaching Neutral Zone, all systems normal and functioning."))
}
@@ -318,7 +443,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
return
}
file, err := ioutil.TempFile(s.tempPath, "transfer-")
file, err := os.CreateTemp(s.tempPath, "transfer-")
defer s.cleanTmpFile(file)
if err != nil {
@@ -363,7 +488,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 {
@@ -380,7 +505,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 := attachEncryptionReader(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
@@ -418,8 +549,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
@@ -428,11 +559,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,
@@ -451,6 +587,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
}
@@ -463,34 +607,53 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
defer storage.CloseCheck(r.Body)
file, err := ioutil.TempFile(s.tempPath, "transfer-")
defer s.cleanTmpFile(file)
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
reader := r.Body
// queue file to disk, because s3 needs content length
// and clamav prescan scans a file
n, err := io.Copy(file, r.Body)
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
if contentLength < 1 || s.performClamavPrescan {
file, err := os.CreateTemp(s.tempPath, "transfer-")
defer s.cleanTmpFile(file)
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
return
}
// queue file to disk, because s3 needs content length
// and clamav prescan scans a file
n, err := io.Copy(file, r.Body)
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
_, err = file.Seek(0, io.SeekStart)
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, "Cannot reset cache file", http.StatusInternalServerError)
return
}
return
}
_, err = file.Seek(0, io.SeekStart)
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, "Cannot reset cache file", http.StatusInternalServerError)
return
}
if contentLength < 1 {
contentLength = n
if s.performClamavPrescan {
status, err := s.performScan(file.Name())
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, "Could not perform prescan", http.StatusInternalServerError)
return
}
if status != clamavScanStatusOK {
s.logger.Printf("prescan positive: %s", status)
http.Error(w, "Clamav prescan found a virus", http.StatusPreconditionFailed)
return
}
}
reader = file
}
if s.maxUploadSize > 0 && contentLength > s.maxUploadSize {
@@ -505,26 +668,11 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
return
}
if s.performClamavPrescan {
status, err := s.performScan(file.Name())
if err != nil {
s.logger.Printf("%s", err.Error())
http.Error(w, "Could not perform prescan", http.StatusInternalServerError)
return
}
if status != clamavScanStatusOK {
s.logger.Printf("prescan positive: %s", status)
http.Error(w, "Clamav prescan found a virus", http.StatusPreconditionFailed)
return
}
}
contentType := mime.TypeByExtension(filepath.Ext(vars["filename"]))
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 {
@@ -543,7 +691,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 := attachEncryptionReader(reader, 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
@@ -1031,8 +1185,6 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
reader, contentLength, err := s.storage.Get(r.Context(), token, filename, rng)
defer storage.CloseCheck(reader)
rdr := io.Reader(reader)
if s.storage.IsNotExist(err) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
@@ -1047,18 +1199,15 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("Content-Range", cr)
if rng.Limit > 0 {
rdr = io.LimitReader(reader, int64(rng.Limit))
reader = io.NopCloser(io.LimitReader(reader, int64(rng.Limit)))
}
}
}
var disposition string
if action == "inline" {
disposition = "inline"
/*
metadata.ContentType is unable to determine the type of the content,
metadata.ContentType is unable to determine the type of the content,
metadata.ContentType is unable to determine the type of the content,
So add text/plain in this case to fix XSS related issues/
*/
@@ -1071,9 +1220,7 @@ 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)
@@ -1084,10 +1231,25 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
}
if disposition == "inline" && canContainsXSS(contentType) {
reader = ioutil.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader))
reader = io.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader))
}
if _, err = io.Copy(w, rdr); err != nil {
password := r.Header.Get("X-Decrypt-Password")
decryptionReader, err := attachDecryptionReader(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

View File

@@ -21,13 +21,13 @@ import (
"github.com/tomasen/realip"
)
//IPFilterOptions for ipFilter. Allowed takes precedence over Blocked.
//IPs can be IPv4 or IPv6 and can optionally contain subnet
//masks (/24). Note however, determining if a given IP is
//included in a subnet requires a linear scan so is less performant
//than looking up single IPs.
// IPFilterOptions for ipFilter. Allowed takes precedence over Blocked.
// IPs can be IPv4 or IPv6 and can optionally contain subnet
// masks (/24). Note however, determining if a given IP is
// included in a subnet requires a linear scan so is less performant
// than looking up single IPs.
//
//This could be improved with some algorithmic magic.
// This could be improved with some algorithmic magic.
type IPFilterOptions struct {
//explicity allowed IPs
AllowedIPs []string
@@ -127,19 +127,19 @@ func (f *ipFilter) ToggleIP(str string, allowed bool) bool {
return false
}
//ToggleDefault alters the default setting
// ToggleDefault alters the default setting
func (f *ipFilter) ToggleDefault(allowed bool) {
f.mut.Lock()
f.defaultAllowed = allowed
f.mut.Unlock()
}
//Allowed returns if a given IP can pass through the filter
// Allowed returns if a given IP can pass through the filter
func (f *ipFilter) Allowed(ipstr string) bool {
return f.NetAllowed(net.ParseIP(ipstr))
}
//NetAllowed returns if a given net.IP can pass through the filter
// NetAllowed returns if a given net.IP can pass through the filter
func (f *ipFilter) NetAllowed(ip net.IP) bool {
//invalid ip
if ip == nil {
@@ -172,23 +172,23 @@ func (f *ipFilter) NetAllowed(ip net.IP) bool {
return f.defaultAllowed
}
//Blocked returns if a given IP can NOT pass through the filter
// Blocked returns if a given IP can NOT pass through the filter
func (f *ipFilter) Blocked(ip string) bool {
return !f.Allowed(ip)
}
//NetBlocked returns if a given net.IP can NOT pass through the filter
// NetBlocked returns if a given net.IP can NOT pass through the filter
func (f *ipFilter) NetBlocked(ip net.IP) bool {
return !f.NetAllowed(ip)
}
//Wrap the provided handler with simple IP blocking middleware
//using this IP filter and its configuration
// Wrap the provided handler with simple IP blocking middleware
// using this IP filter and its configuration
func (f *ipFilter) Wrap(next http.Handler) http.Handler {
return &ipFilterMiddleware{ipFilter: f, next: next}
}
//WrapIPFilter is equivalent to newIPFilter(opts) then Wrap(next)
// WrapIPFilter is equivalent to newIPFilter(opts) then Wrap(next)
func WrapIPFilter(next http.Handler, opts IPFilterOptions) http.Handler {
return newIPFilter(opts).Wrap(next)
}

View File

@@ -466,8 +466,6 @@ func (s *Server) Run() {
r.HandleFunc("/{action:(?:download|get|inline)}/{token}/{filename}", s.headHandler).Methods("HEAD")
r.HandleFunc("/{token}/{filename}", s.previewHandler).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) (match bool) {
match = false
// The file will show a preview page when opening the link in browser directly or
// from external link. If the referer url path and current path are the same it will be
// downloaded.

View File

@@ -4,10 +4,9 @@ import (
"context"
"fmt"
"io"
"regexp"
"strconv"
"time"
"regexp"
)
type Range struct {

View File

@@ -83,15 +83,18 @@ func (s *StorjStorage) Get(ctx context.Context, token string, filename string, r
s.logger.Printf("Getting file %s from Storj Bucket", filename)
options := uplink.DownloadOptions{}
var options *uplink.DownloadOptions
if rng != nil {
options = new(uplink.DownloadOptions)
options.Offset = int64(rng.Start)
if rng.Limit > 0 {
options.Length = int64(rng.Limit)
} else {
options.Length = -1
}
}
download, err := s.project.DownloadObject(fpath.WithTempData(ctx, "", true), s.bucket.Name, key, &options)
download, err := s.project.DownloadObject(fpath.WithTempData(ctx, "", true), s.bucket.Name, key, options)
if err != nil {
return nil, 0, err
}