Compare commits

...

17 Commits

Author SHA1 Message Date
Andrea Spacca
ffeba97f90 Merge branch 'master' into fix-workflows 2021-05-21 17:18:50 +02:00
Andrea Spacca
62349b51df tests on PR 2021-05-21 17:17:54 +02:00
Andrea Spacca
2e2c07c3a0 tests on PR (#375)
* tests on PR
2021-05-21 17:16:43 +02:00
Andrea Spacca
87b090af66 tests on PR 2021-05-21 17:14:14 +02:00
Andrea Spacca
e57b0314b3 tests on PR 2021-05-21 17:06:38 +02:00
Andrea Spacca
f25b3fb99d tests on PR 2021-05-21 17:05:49 +02:00
Andrea Spacca
9df18fdc69 fixes-20210521 (#373) 2021-05-21 15:49:48 +02:00
Stefan Benten
49c6d7ee4f Merge pull request #372 from dutchcoders/ISSUE-371
Add random-token-length config
2021-05-21 10:37:42 +02:00
Andrea Spacca
fdfd453222 Added random-token-length, Encode() refactored 2021-05-20 08:26:07 +02:00
Stefan Benten
b5ffdb5095 Merge pull request #370 from dutchcoders/ISSUE-38-WEB
Added UrlRandomToken in template data
2021-05-09 10:37:43 +02:00
Andrea Spacca
0512452111 Added UrlRandomToken in template data 2021-05-09 09:21:54 +02:00
Federico Marzocchi
16e94447f7 GitHub workflow for releases (#368)
* GitHub workflow for releases

* avoid renaming

* add workflow for tests

* fix workflow name

* one yaml for all

* fix yaml

* add missing pipe

* add conditional

* also rename that other var

* remove empty version

* print the go version

* add name to step

* add go version when building

* split files

* remove dependency

* delete travis and add badge

* CHANGE NAMES TO LOWER CASE
2021-05-01 19:53:15 +02:00
Stefan Benten
941ec1fe0f Merge pull request #365 from JustAnotherArchivist/disable-download-counter-without-limit
Only increment download counter on files that have a download limit
2021-04-25 21:00:49 +02:00
JustAnotherArchivist
6bd3e97186 Only increment download counter on files that have a download limit
For files that have no limit, there is no reason to track the download counter as its value will never be used anyway. This reduces costs on storage backends that have an operation fee (e.g. AWS S3) or a minimum retention (e.g. Wasabi S3).
2021-04-25 17:47:23 +00:00
Andrea Spacca
31ef712847 Merge pull request #362 from JustAnotherArchivist/fix-metadata-rewrite
Only rewrite metadata file when the download counter changed
2021-04-24 21:23:51 +02:00
JustAnotherArchivist
4daca97f89 Only rewrite metadata file when the download counter changed
Previously, the metadata file would be rewritten even if the download counter stayed the same (i.e. `increaseDownload = false`, previews and HEAD requests). Because the metadata doesn't change in that case, this would simply rewrite the exact same contents needlessly, which may also incur extra costs depending on the storage backend.
2021-04-19 18:37:47 +00:00
Andrea Spacca
69519d8fa4 Unwrap unnecessary bytes.NewReader 2021-03-19 08:25:40 +01:00
10 changed files with 190 additions and 98 deletions

View File

@@ -1,4 +1,4 @@
name: Deploy multi-architecture Docker images for transfer.sh with buildx name: deploy multi-architecture Docker images for transfer.sh with buildx
on: on:
schedule: schedule:

58
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: release
on:
push:
tags:
- v*
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
GOOS: [ darwin, linux, windows ]
GOARCH: [ amd64 ]
include:
- GOOS: darwin
GOARCH: amd64
suffix: darwin-amd64
- GOOS: windows
GOARCH: amd64
suffix: windows-amd64.exe
- GOOS: linux
GOARCH: amd64
suffix: linux-amd64
- GOOS: linux
GOARCH: arm
suffix: linux-armv7
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: 1.16.x
- name: Build ${{ matrix.suffix }} (GOOS=${{ matrix.GOOS }}, GOARCH=${{ matrix.GOARCH }})
env:
GOOS: ${{ matrix.GOOS }}
GOARCH: ${{ matrix.GOARCH }}
run: |
go version
go build -o ./artifacts/transfersh-${GITHUB_REF##*/}-${{ matrix.suffix }}
- uses: actions/upload-artifact@v2
name: Upload artifacts
with:
name: artifacts
path: ./artifacts
release:
runs-on: ubuntu-latest
needs: [ build ]
steps:
- uses: actions/download-artifact@v2
name: Download artifacts
with:
name: artifacts
path: ./artifacts
- name: Publish artifacts
uses: softprops/action-gh-release@v1
with:
files: './artifacts/*'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

30
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: test
on:
pull_request:
branches:
- "*"
push:
branches:
- "*"
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go_version:
- 1.13.x
- 1.14.x
- 1.15.x
- 1.16.x
name: Test with ${{ matrix.go_version }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v1
with:
go-version: ${{ matrix.go_version }}
- name: Vet and test
run: |
go version
go vet ./...
go test ./...

View File

@@ -1,49 +0,0 @@
language: go
sudo: false
os:
- linux
services:
- docker
go:
- 1.13.x
- 1.14.x
- 1.15.x
- 1.16.x
- tip
env:
global:
- GO111MODULE=on
install:
- go get -t -u -v ./...
- go build -v .
- go vet ./...
script:
- go test ./...
before_deploy:
- mkdir -p release
- "GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -tags netgo -ldflags '-a -s -w -extldflags -static' -o release/transfersh-$TRAVIS_TAG-linux-amd64"
- "GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 go build -tags netgo -ldflags '-a -s -w -extldflags -static' -o release/transfersh-$TRAVIS_TAG-linux-armv7"
- "GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -tags netgo -ldflags '-a -s -w -extldflags -static' -o release/transfersh-$TRAVIS_TAG-darwin-amd64"
- "GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -tags netgo -ldflags '-a -s -w -extldflags -static' -o release/transfersh-$TRAVIS_TAG-win-amd64.exe"
deploy:
provider: releases
api_key:
secure: cOuMGyvrl/9GX3TZFL+Vq++2Bv5Hlb3VfXSYONfeAj+1AXI3Y+tPruy/XnWpa1MUxkvFuIhea3sUAiKfwhHip9csCmMUhDJtaTU9apsxRkyF/OFrWb7/FlbnqYuAwnp91ImvtSlnubg2VHTjhBA6ycNQF7WZcJEMVMsAtC/nSY4=
file:
- "release/transfersh-$TRAVIS_TAG-linux-amd64"
- "release/transfersh-$TRAVIS_TAG-linux-armv7"
- "release/transfersh-$TRAVIS_TAG-darwin-amd64"
- "release/transfersh-$TRAVIS_TAG-win-amd64.exe"
skip_cleanup: true
on:
tags: true
go: 1.15.x
overwrite: true

View File

@@ -1,4 +1,4 @@
# transfer.sh [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dutchcoders/transfer.sh?utm_source=badge&utm_medium=badge&utm_campaign=&utm_campaign=pr-badge&utm_content=badge) [![Go Report Card](https://goreportcard.com/badge/github.com/dutchcoders/transfer.sh)](https://goreportcard.com/report/github.com/dutchcoders/transfer.sh) [![Docker pulls](https://img.shields.io/docker/pulls/dutchcoders/transfer.sh.svg)](https://hub.docker.com/r/dutchcoders/transfer.sh/) [![Build Status](https://travis-ci.com/dutchcoders/transfer.sh.svg?branch=master)](https://travis-ci.com/dutchcoders/transfer.sh) # transfer.sh [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dutchcoders/transfer.sh?utm_source=badge&utm_medium=badge&utm_campaign=&utm_campaign=pr-badge&utm_content=badge) [![Go Report Card](https://goreportcard.com/badge/github.com/dutchcoders/transfer.sh)](https://goreportcard.com/report/github.com/dutchcoders/transfer.sh) [![Docker pulls](https://img.shields.io/docker/pulls/dutchcoders/transfer.sh.svg)](https://hub.docker.com/r/dutchcoders/transfer.sh/) [![Build Status](https://github.com/dutchcoders/transfer.sh/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/dutchcoders/transfer.sh/actions/workflows/test.yml?query=branch%3Amaster)
Easy and fast file sharing from the command-line. This code contains the server with everything you need to create your own instance. Easy and fast file sharing from the command-line. This code contains the server with everything you need to create your own instance.
@@ -114,6 +114,7 @@ rate-limit | request per minute | | RATE_LIMIT |
max-upload-size | max upload size in kilobytes | | MAX_UPLOAD_SIZE | max-upload-size | max upload size in kilobytes | | MAX_UPLOAD_SIZE |
purge-days | number of days after the uploads are purged automatically | | PURGE_DAYS | purge-days | number of days after the uploads are purged automatically | | PURGE_DAYS |
purge-interval | interval in hours to run the automatic purge for (not applicable to S3 and Storj) | | PURGE_INTERVAL | purge-interval | interval in hours to run the automatic purge for (not applicable to S3 and Storj) | | PURGE_INTERVAL |
random-token-length | length of the random token for the upload path (double the size for delete path) | 6 | RANDOM_TOKEN_LENGTH |
If you want to use TLS using lets encrypt certificates, set lets-encrypt-hosts to your domain, set tls-listener to :443 and enable force-https. If you want to use TLS using lets encrypt certificates, set lets-encrypt-hosts to your domain, set tls-listener to :443 and enable force-https.

View File

@@ -12,7 +12,7 @@ import (
"google.golang.org/api/googleapi" "google.golang.org/api/googleapi"
) )
var Version = "1.2.2" var Version = "1.2.4"
var helpTemplate = `NAME: var helpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
@@ -274,6 +274,12 @@ var globalFlags = []cli.Flag{
Value: "", Value: "",
EnvVar: "CORS_DOMAINS", EnvVar: "CORS_DOMAINS",
}, },
cli.Int64Flag{
Name: "random-token-length",
Usage: "",
Value: 6,
EnvVar: "RANDOM_TOKEN_LENGTH",
},
} }
type Cmd struct { type Cmd struct {
@@ -377,6 +383,9 @@ func New() *Cmd {
options = append(options, server.RateLimit(v)) options = append(options, server.RateLimit(v))
} }
v := c.Int64("random-token-length")
options = append(options, server.RandomTokenLength(v))
purgeDays := c.Int("purge-days") purgeDays := c.Int("purge-days")
purgeInterval := c.Int("purge-interval") purgeInterval := c.Int("purge-interval")
if purgeDays > 0 && purgeInterval > 0 { if purgeDays > 0 && purgeInterval > 0 {

View File

@@ -26,6 +26,7 @@ package server
import ( import (
"math" "math"
"math/rand"
"strings" "strings"
) )
@@ -34,19 +35,31 @@ const (
SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
// someone set us up the bomb !! // someone set us up the bomb !!
BASE = int64(len(SYMBOLS)) BASE = float64(len(SYMBOLS))
// init seed encode number
INIT_SEED = float64(-1)
) )
// encodes a number into our *base* representation // encodes a number into our *base* representation
// TODO can this be made better with some bitshifting? // TODO can this be made better with some bitshifting?
func Encode(number int64) string { func Encode(number float64, length int64) string {
rest := number % BASE if number == INIT_SEED {
seed := math.Pow(float64(BASE), float64(length))
number = seed + (rand.Float64() * seed) // start with seed to enforce desired length
}
rest := int64(math.Mod(number, BASE))
// strings are a bit weird in go... // strings are a bit weird in go...
result := string(SYMBOLS[rest]) result := string(SYMBOLS[rest])
if number-rest != 0 { if rest > 0 && number-float64(rest) != 0 {
newnumber := (number - rest) / BASE newnumber := (number - float64(rest)) / BASE
result = Encode(newnumber) + result result = Encode(newnumber, length) + result
} else {
// it would always be 1 because of starting with seed and we want to skip
return ""
} }
return result return result
} }

15
server/codec_test.go Normal file
View File

@@ -0,0 +1,15 @@
package server
import "testing"
func BenchmarkEncodeConcat(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Encode(INIT_SEED, 5) + Encode(INIT_SEED, 5)
}
}
func BenchmarkEncodeLonger(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Encode(INIT_SEED, 10)
}
}

View File

@@ -41,7 +41,6 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"math/rand"
"mime" "mime"
"net/http" "net/http"
"net/url" "net/url"
@@ -95,6 +94,27 @@ func healthHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Approaching Neutral Zone, all systems normal and functioning.") fmt.Fprintf(w, "Approaching Neutral Zone, all systems normal and functioning.")
} }
func canContainsXSS(contentType string) bool {
switch {
case strings.Contains(contentType, "cache-manifest"):
fallthrough
case strings.Contains(contentType, "html"):
fallthrough
case strings.Contains(contentType, "rdf"):
fallthrough
case strings.Contains(contentType, "vtt"):
fallthrough
case strings.Contains(contentType, "xml"):
fallthrough
case strings.Contains(contentType, "xsl"):
return true
case strings.Contains(contentType, "x-mixed-replace"):
return true
}
return false
}
/* The preview handler will show a preview of the content for browsers (accept type text/html), and referer is not transfer.sh */ /* The preview handler will show a preview of the content for browsers (accept type text/html), and referer is not transfer.sh */
func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) { func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
@@ -174,23 +194,25 @@ func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) {
webAddress := resolveWebAddress(r, s.proxyPath, s.proxyPort) webAddress := resolveWebAddress(r, s.proxyPath, s.proxyPort)
data := struct { data := struct {
ContentType string ContentType string
Content html_template.HTML Content html_template.HTML
Filename string Filename string
Url string Url string
UrlGet string UrlGet string
Hostname string UrlRandomToken string
WebAddress string Hostname string
ContentLength uint64 WebAddress string
GAKey string ContentLength uint64
UserVoiceKey string GAKey string
QRCode string UserVoiceKey string
QRCode string
}{ }{
contentType, contentType,
content, content,
filename, filename,
resolvedURL, resolvedURL,
resolvedURLGet, resolvedURLGet,
token,
hostname, hostname,
webAddress, webAddress,
contentLength, contentLength,
@@ -255,18 +277,14 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
token := Encode(10000000 + int64(rand.Intn(1000000000))) token := Encode(INIT_SEED, s.randomTokenLength)
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
for _, fheaders := range r.MultipartForm.File { for _, fheaders := range r.MultipartForm.File {
for _, fheader := range fheaders { for _, fheader := range fheaders {
filename := sanitize(fheader.Filename) filename := sanitize(fheader.Filename)
contentType := fheader.Header.Get("Content-Type") contentType := mime.TypeByExtension(filepath.Ext(fheader.Filename))
if contentType == "" {
contentType = mime.TypeByExtension(filepath.Ext(fheader.Filename))
}
var f io.Reader var f io.Reader
var err error var err error
@@ -317,7 +335,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
metadata := MetadataForRequest(contentType, r) metadata := MetadataForRequest(contentType, 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 {
@@ -381,13 +399,13 @@ type Metadata struct {
DeletionToken string DeletionToken string
} }
func MetadataForRequest(contentType string, r *http.Request) Metadata { func MetadataForRequest(contentType string, randomTokenLength int64, r *http.Request) Metadata {
metadata := Metadata{ metadata := Metadata{
ContentType: strings.ToLower(contentType), ContentType: strings.ToLower(contentType),
MaxDate: time.Time{}, MaxDate: time.Time{},
Downloads: 0, Downloads: 0,
MaxDownloads: -1, MaxDownloads: -1,
DeletionToken: Encode(10000000+int64(rand.Intn(1000000000))) + Encode(10000000+int64(rand.Intn(1000000000))), DeletionToken: Encode(INIT_SEED, randomTokenLength) + Encode(INIT_SEED, randomTokenLength),
} }
if v := r.Header.Get("Max-Downloads"); v == "" { if v := r.Header.Get("Max-Downloads"); v == "" {
@@ -473,15 +491,11 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
contentType := r.Header.Get("Content-Type") contentType := mime.TypeByExtension(filepath.Ext(vars["filename"]))
if contentType == "" { token := Encode(INIT_SEED, s.randomTokenLength)
contentType = mime.TypeByExtension(filepath.Ext(vars["filename"]))
}
token := Encode(10000000 + int64(rand.Intn(1000000000))) metadata := MetadataForRequest(contentType, s.randomTokenLength, r)
metadata := MetadataForRequest(contentType, 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 {
@@ -661,13 +675,11 @@ func (s *Server) CheckMetadata(token, filename string, increaseDownload bool) (M
return metadata, errors.New("MaxDownloads expired.") return metadata, errors.New("MaxDownloads expired.")
} else if !metadata.MaxDate.IsZero() && time.Now().After(metadata.MaxDate) { } else if !metadata.MaxDate.IsZero() && time.Now().After(metadata.MaxDate) {
return metadata, errors.New("MaxDate expired.") return metadata, errors.New("MaxDate expired.")
} else { } else if metadata.MaxDownloads != -1 && increaseDownload {
// todo(nl5887): mutex? // todo(nl5887): mutex?
// update number of downloads // update number of downloads
if increaseDownload { metadata.Downloads++
metadata.Downloads++
}
buffer := &bytes.Buffer{} buffer := &bytes.Buffer{}
if err := json.NewEncoder(buffer).Encode(metadata); err != nil { if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
@@ -688,7 +700,7 @@ func (s *Server) CheckDeletionToken(deletionToken, token, filename string) error
r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename)) r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
if s.storage.IsNotExist(err) { if s.storage.IsNotExist(err) {
return nil return errors.New("Metadata doesn't exist")
} else if err != nil { } else if err != nil {
return err return err
} }
@@ -1009,13 +1021,8 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
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)
if disposition == "inline" && canContainsXSS(contentType) {
if disposition == "inline" && strings.Contains(contentType, "html") { reader = ioutil.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader))
reader = ioutil.NopCloser(
bytes.NewReader(
bluemonday.UGCPolicy().
SanitizeReader(reader).
Bytes()))
} }
if w.Header().Get("Range") == "" { if w.Header().Get("Range") == "" {

View File

@@ -187,6 +187,12 @@ func RateLimit(requests int) OptionFn {
} }
} }
func RandomTokenLength(length int64) OptionFn {
return func(srvr *Server) {
srvr.randomTokenLength = length
}
}
func Purge(days, interval int) OptionFn { func Purge(days, interval int) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.purgeDays = time.Duration(days) * time.Hour * 24 srvr.purgeDays = time.Duration(days) * time.Hour * 24
@@ -294,6 +300,8 @@ type Server struct {
forceHTTPs bool forceHTTPs bool
randomTokenLength int64
ipFilterOptions *IPFilterOptions ipFilterOptions *IPFilterOptions
VirusTotalKey string VirusTotalKey string