diff --git a/README.md b/README.md index 7bdf4fd..0947a53 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/cmd/cmd.go b/cmd/cmd.go index be87eae..1987d84 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -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 { diff --git a/server/codec.go b/server/codec.go index fda3682..a33f3c0 100644 --- a/server/codec.go +++ b/server/codec.go @@ -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 } diff --git a/server/codec_test.go b/server/codec_test.go new file mode 100644 index 0000000..aebd8ab --- /dev/null +++ b/server/codec_test.go @@ -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) + } +} diff --git a/server/handlers.go b/server/handlers.go index 39bf2b7..4cf4238 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -41,7 +41,6 @@ import ( "io" "io/ioutil" "log" - "math/rand" "mime" "net/http" "net/url" @@ -257,7 +256,7 @@ 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") @@ -319,7 +318,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 { @@ -383,13 +382,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 == "" { @@ -481,9 +480,9 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) { contentType = mime.TypeByExtension(filepath.Ext(vars["filename"])) } - token := Encode(10000000 + int64(rand.Intn(1000000000))) + token := Encode(INIT_SEED, s.randomTokenLength) - metadata := MetadataForRequest(contentType, r) + metadata := MetadataForRequest(contentType, s.randomTokenLength, r) buffer := &bytes.Buffer{} if err := json.NewEncoder(buffer).Encode(metadata); err != nil { diff --git a/server/server.go b/server/server.go index bff1ee9..6985162 100644 --- a/server/server.go +++ b/server/server.go @@ -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