Compare commits
17 Commits
v1.2.2
...
fix-workfl
Author | SHA1 | Date | |
---|---|---|---|
|
ffeba97f90 | ||
|
62349b51df | ||
|
2e2c07c3a0 | ||
|
87b090af66 | ||
|
e57b0314b3 | ||
|
f25b3fb99d | ||
|
9df18fdc69 | ||
|
49c6d7ee4f | ||
|
fdfd453222 | ||
|
b5ffdb5095 | ||
|
0512452111 | ||
|
16e94447f7 | ||
|
941ec1fe0f | ||
|
6bd3e97186 | ||
|
31ef712847 | ||
|
4daca97f89 | ||
|
69519d8fa4 |
2
.github/workflows/build-docker-images.yml
vendored
2
.github/workflows/build-docker-images.yml
vendored
@@ -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:
|
||||
schedule:
|
||||
|
58
.github/workflows/release.yml
vendored
Normal file
58
.github/workflows/release.yml
vendored
Normal 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
30
.github/workflows/test.yml
vendored
Normal 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 ./...
|
49
.travis.yml
49
.travis.yml
@@ -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
|
@@ -1,4 +1,4 @@
|
||||
# transfer.sh [](https://gitter.im/dutchcoders/transfer.sh?utm_source=badge&utm_medium=badge&utm_campaign=&utm_campaign=pr-badge&utm_content=badge) [](https://goreportcard.com/report/github.com/dutchcoders/transfer.sh) [](https://hub.docker.com/r/dutchcoders/transfer.sh/) [](https://travis-ci.com/dutchcoders/transfer.sh)
|
||||
# transfer.sh [](https://gitter.im/dutchcoders/transfer.sh?utm_source=badge&utm_medium=badge&utm_campaign=&utm_campaign=pr-badge&utm_content=badge) [](https://goreportcard.com/report/github.com/dutchcoders/transfer.sh) [](https://hub.docker.com/r/dutchcoders/transfer.sh/) [](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.
|
||||
|
||||
@@ -114,6 +114,7 @@ rate-limit | request per minute | | RATE_LIMIT |
|
||||
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-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.
|
||||
|
||||
|
11
cmd/cmd.go
11
cmd/cmd.go
@@ -12,7 +12,7 @@ import (
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
var Version = "1.2.2"
|
||||
var Version = "1.2.4"
|
||||
var helpTemplate = `NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
|
||||
@@ -274,6 +274,12 @@ var globalFlags = []cli.Flag{
|
||||
Value: "",
|
||||
EnvVar: "CORS_DOMAINS",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "random-token-length",
|
||||
Usage: "",
|
||||
Value: 6,
|
||||
EnvVar: "RANDOM_TOKEN_LENGTH",
|
||||
},
|
||||
}
|
||||
|
||||
type Cmd struct {
|
||||
@@ -377,6 +383,9 @@ func New() *Cmd {
|
||||
options = append(options, server.RateLimit(v))
|
||||
}
|
||||
|
||||
v := c.Int64("random-token-length")
|
||||
options = append(options, server.RandomTokenLength(v))
|
||||
|
||||
purgeDays := c.Int("purge-days")
|
||||
purgeInterval := c.Int("purge-interval")
|
||||
if purgeDays > 0 && purgeInterval > 0 {
|
||||
|
@@ -26,6 +26,7 @@ package server
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -34,19 +35,31 @@ const (
|
||||
SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
// 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
|
||||
// TODO can this be made better with some bitshifting?
|
||||
func Encode(number int64) string {
|
||||
rest := number % BASE
|
||||
func Encode(number float64, length int64) string {
|
||||
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...
|
||||
result := string(SYMBOLS[rest])
|
||||
if number-rest != 0 {
|
||||
newnumber := (number - rest) / BASE
|
||||
result = Encode(newnumber) + result
|
||||
if rest > 0 && number-float64(rest) != 0 {
|
||||
newnumber := (number - float64(rest)) / BASE
|
||||
result = Encode(newnumber, length) + result
|
||||
} else {
|
||||
// it would always be 1 because of starting with seed and we want to skip
|
||||
return ""
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
15
server/codec_test.go
Normal file
15
server/codec_test.go
Normal 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)
|
||||
}
|
||||
}
|
@@ -41,7 +41,6 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"mime"
|
||||
"net/http"
|
||||
"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.")
|
||||
}
|
||||
|
||||
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 */
|
||||
func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
|
||||
data := struct {
|
||||
ContentType string
|
||||
Content html_template.HTML
|
||||
Filename string
|
||||
Url string
|
||||
UrlGet string
|
||||
Hostname string
|
||||
WebAddress string
|
||||
ContentLength uint64
|
||||
GAKey string
|
||||
UserVoiceKey string
|
||||
QRCode string
|
||||
ContentType string
|
||||
Content html_template.HTML
|
||||
Filename string
|
||||
Url string
|
||||
UrlGet string
|
||||
UrlRandomToken string
|
||||
Hostname string
|
||||
WebAddress string
|
||||
ContentLength uint64
|
||||
GAKey string
|
||||
UserVoiceKey string
|
||||
QRCode string
|
||||
}{
|
||||
contentType,
|
||||
content,
|
||||
filename,
|
||||
resolvedURL,
|
||||
resolvedURLGet,
|
||||
token,
|
||||
hostname,
|
||||
webAddress,
|
||||
contentLength,
|
||||
@@ -255,18 +277,14 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
token := Encode(10000000 + int64(rand.Intn(1000000000)))
|
||||
token := Encode(INIT_SEED, s.randomTokenLength)
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
|
||||
for _, fheaders := range r.MultipartForm.File {
|
||||
for _, fheader := range fheaders {
|
||||
filename := sanitize(fheader.Filename)
|
||||
contentType := fheader.Header.Get("Content-Type")
|
||||
|
||||
if contentType == "" {
|
||||
contentType = mime.TypeByExtension(filepath.Ext(fheader.Filename))
|
||||
}
|
||||
contentType := mime.TypeByExtension(filepath.Ext(fheader.Filename))
|
||||
|
||||
var f io.Reader
|
||||
var err error
|
||||
@@ -317,7 +335,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
metadata := MetadataForRequest(contentType, r)
|
||||
metadata := MetadataForRequest(contentType, s.randomTokenLength, r)
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
||||
@@ -381,13 +399,13 @@ type Metadata struct {
|
||||
DeletionToken string
|
||||
}
|
||||
|
||||
func MetadataForRequest(contentType string, r *http.Request) Metadata {
|
||||
func MetadataForRequest(contentType string, randomTokenLength int64, r *http.Request) Metadata {
|
||||
metadata := Metadata{
|
||||
ContentType: strings.ToLower(contentType),
|
||||
MaxDate: time.Time{},
|
||||
Downloads: 0,
|
||||
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 == "" {
|
||||
@@ -473,15 +491,11 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
contentType := mime.TypeByExtension(filepath.Ext(vars["filename"]))
|
||||
|
||||
if contentType == "" {
|
||||
contentType = mime.TypeByExtension(filepath.Ext(vars["filename"]))
|
||||
}
|
||||
token := Encode(INIT_SEED, s.randomTokenLength)
|
||||
|
||||
token := Encode(10000000 + int64(rand.Intn(1000000000)))
|
||||
|
||||
metadata := MetadataForRequest(contentType, r)
|
||||
metadata := MetadataForRequest(contentType, s.randomTokenLength, r)
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
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.")
|
||||
} else if !metadata.MaxDate.IsZero() && time.Now().After(metadata.MaxDate) {
|
||||
return metadata, errors.New("MaxDate expired.")
|
||||
} else {
|
||||
} else if metadata.MaxDownloads != -1 && increaseDownload {
|
||||
// todo(nl5887): mutex?
|
||||
|
||||
// update number of downloads
|
||||
if increaseDownload {
|
||||
metadata.Downloads++
|
||||
}
|
||||
metadata.Downloads++
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
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))
|
||||
if s.storage.IsNotExist(err) {
|
||||
return nil
|
||||
return errors.New("Metadata doesn't exist")
|
||||
} else if err != nil {
|
||||
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-Days", remainingDays)
|
||||
|
||||
|
||||
if disposition == "inline" && strings.Contains(contentType, "html") {
|
||||
reader = ioutil.NopCloser(
|
||||
bytes.NewReader(
|
||||
bluemonday.UGCPolicy().
|
||||
SanitizeReader(reader).
|
||||
Bytes()))
|
||||
if disposition == "inline" && canContainsXSS(contentType) {
|
||||
reader = ioutil.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader))
|
||||
}
|
||||
|
||||
if w.Header().Get("Range") == "" {
|
||||
|
@@ -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 {
|
||||
return func(srvr *Server) {
|
||||
srvr.purgeDays = time.Duration(days) * time.Hour * 24
|
||||
@@ -294,6 +300,8 @@ type Server struct {
|
||||
|
||||
forceHTTPs bool
|
||||
|
||||
randomTokenLength int64
|
||||
|
||||
ipFilterOptions *IPFilterOptions
|
||||
|
||||
VirusTotalKey string
|
||||
|
Reference in New Issue
Block a user