Compare commits
11 Commits
v1.2.3
...
fix-workfl
Author | SHA1 | Date | |
---|---|---|---|
|
ffeba97f90 | ||
|
62349b51df | ||
|
2e2c07c3a0 | ||
|
87b090af66 | ||
|
e57b0314b3 | ||
|
f25b3fb99d | ||
|
9df18fdc69 | ||
|
49c6d7ee4f | ||
|
fdfd453222 | ||
|
b5ffdb5095 | ||
|
0512452111 |
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@@ -1,5 +1,8 @@
|
|||||||
name: test
|
name: test
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "*"
|
- "*"
|
||||||
|
@@ -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.
|
||||||
|
|
||||||
|
11
cmd/cmd.go
11
cmd/cmd.go
@@ -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 {
|
||||||
|
@@ -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
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"
|
||||||
"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)
|
||||||
@@ -179,6 +199,7 @@ func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Filename string
|
Filename string
|
||||||
Url string
|
Url string
|
||||||
UrlGet string
|
UrlGet string
|
||||||
|
UrlRandomToken string
|
||||||
Hostname string
|
Hostname string
|
||||||
WebAddress string
|
WebAddress string
|
||||||
ContentLength uint64
|
ContentLength uint64
|
||||||
@@ -191,6 +212,7 @@ func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
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 {
|
||||||
@@ -686,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
|
||||||
}
|
}
|
||||||
@@ -1007,8 +1021,7 @@ 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(bluemonday.UGCPolicy().SanitizeReader(reader))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user