diff --git a/.dockerignore b/.dockerignore index e4722dc..21934c1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,7 +6,6 @@ bin *.pyc *.egg-info .vagrant -.git .tmp bower_components node_modules diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 366d218..f2f7ed8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: CGO_ENABLED: 0 run: | go version - go build -tags netgo -ldflags '-a -s -w -extldflags "-static"' -o ./artifacts/transfersh-${GITHUB_REF##*/}-${{ matrix.suffix }} + go build -tags netgo -ldflags "-X github.com/dutchcoders/transfer.sh/cmd.Version=${GITHUB_REF##*/} -a -s -w -extldflags '-static'" -o ./artifacts/transfersh-${GITHUB_REF##*/}-${{ matrix.suffix }} - uses: actions/upload-artifact@v2 name: Upload artifacts with: diff --git a/Dockerfile b/Dockerfile index 99ada23..25d08c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -# Default to Go 1.15 -ARG GO_VERSION=1.15 +# Default to Go 1.16 +ARG GO_VERSION=1.16 FROM golang:${GO_VERSION}-alpine as build # Necessary to run 'go get' and to compile the linked binary @@ -12,7 +12,7 @@ WORKDIR /go/src/github.com/dutchcoders/transfer.sh ENV GO111MODULE=on # build & install server -RUN go get -u ./... && CGO_ENABLED=0 go build -tags netgo -ldflags '-a -s -w -extldflags "-static"' -o /go/bin/transfersh github.com/dutchcoders/transfer.sh +RUN CGO_ENABLED=0 go build -tags netgo -ldflags "-X github.com/dutchcoders/transfer.sh/cmd.Version=$(git describe --tags) -a -s -w -extldflags '-static'" -o /go/bin/transfersh FROM scratch AS final LABEL maintainer="Andrea Spacca " diff --git a/LICENSE b/LICENSE index bc796f9..8af1a8e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,8 @@ The MIT License (MIT) Copyright (c) 2014-2018 DutchCoders [https://github.com/dutchcoders/] +Copyright (c) 2018-2020 Andrea Spacca. +Copyright (c) 2020- Andrea Spacca and Stefan Benten. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 8e385c4..01dfdd2 100644 --- a/README.md +++ b/README.md @@ -224,4 +224,7 @@ Contributions are welcome. ## Copyright and license Code and documentation copyright 2011-2018 Remco Verhoef. +Code and documentation copyright 2018-2020 Andrea Spacca. +Code and documentation copyright 2020- Andrea Spacca and Stefan Benten. + Code released under [the MIT license](LICENSE). diff --git a/cmd/cmd.go b/cmd/cmd.go index e840e32..759cb73 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -12,7 +12,7 @@ import ( "google.golang.org/api/googleapi" ) -var Version = "1.2.4" +var Version = "0.0.0" var helpTemplate = `NAME: {{.Name}} - {{.Usage}} @@ -274,7 +274,7 @@ var globalFlags = []cli.Flag{ Value: "", EnvVar: "CORS_DOMAINS", }, - cli.Int64Flag{ + cli.IntFlag{ Name: "random-token-length", Usage: "", Value: 6, @@ -383,7 +383,7 @@ func New() *Cmd { options = append(options, server.RateLimit(v)) } - v := c.Int64("random-token-length") + v := c.Int("random-token-length") options = append(options, server.RandomTokenLength(v)) purgeDays := c.Int("purge-days") diff --git a/go.mod b/go.mod index 0b34fc4..e0d90d9 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( 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-20210212072623-ac7014a9c3a7 + github.com/dutchcoders/transfer.sh-web v0.0.0-20210723094506-f0946ebceb7a github.com/elazarl/go-bindata-assetfs v1.0.1 github.com/fatih/color v1.10.0 github.com/garyburd/redigo v1.6.2 // indirect diff --git a/go.sum b/go.sum index dbc1261..263ad3b 100644 --- a/go.sum +++ b/go.sum @@ -92,6 +92,14 @@ github.com/dutchcoders/go-virustotal v0.0.0-20140923143438-24cc8e6fa329 h1:ERqCk github.com/dutchcoders/go-virustotal v0.0.0-20140923143438-24cc8e6fa329/go.mod h1:G5qOfE5bQZ5scycLpB7fYWgN4y3xdfXo+pYWM8z2epY= github.com/dutchcoders/transfer.sh-web v0.0.0-20210212072623-ac7014a9c3a7 h1:zKJw+RxTDbypWVb0HfPs43bj/ee65Bl7rIDPO7HixrQ= github.com/dutchcoders/transfer.sh-web v0.0.0-20210212072623-ac7014a9c3a7/go.mod h1:jTzXZabwihvQgvmySgD4f4GNszimkXK3o8x1ucH1z5Q= +github.com/dutchcoders/transfer.sh-web v0.0.0-20210717081259-8b8af59a0fae h1:JalbO1PKAsbSYQBW6Q4aXbgj2w4bWofdrHxRRwqRgDc= +github.com/dutchcoders/transfer.sh-web v0.0.0-20210717081259-8b8af59a0fae/go.mod h1:F6Q37CxDh2MHr5KXkcZmNB3tdkK7v+bgE+OpBY+9ilI= +github.com/dutchcoders/transfer.sh-web v0.0.0-20210723091746-c17d678a22f3 h1:CM9FGBPXLXhvKo0TuO4CKKdZLah6esP1SAfPzZDjEB0= +github.com/dutchcoders/transfer.sh-web v0.0.0-20210723091746-c17d678a22f3/go.mod h1:F6Q37CxDh2MHr5KXkcZmNB3tdkK7v+bgE+OpBY+9ilI= +github.com/dutchcoders/transfer.sh-web v0.0.0-20210723093538-596fe60a5dd5 h1:M6GI6DvsFgBGpp3+V+VB5okP1BGXISltz7acVYWWFOQ= +github.com/dutchcoders/transfer.sh-web v0.0.0-20210723093538-596fe60a5dd5/go.mod h1:F6Q37CxDh2MHr5KXkcZmNB3tdkK7v+bgE+OpBY+9ilI= +github.com/dutchcoders/transfer.sh-web v0.0.0-20210723094506-f0946ebceb7a h1:+N7J1NK7gxKZ+X4syY1HqafUudJiR8voJGcXWkxLgAw= +github.com/dutchcoders/transfer.sh-web v0.0.0-20210723094506-f0946ebceb7a/go.mod h1:F6Q37CxDh2MHr5KXkcZmNB3tdkK7v+bgE+OpBY+9ilI= github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -249,6 +257,7 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shuLhan/go-bindata v4.0.0+incompatible/go.mod h1:pkcPAATLBDD2+SpAPnX5vEM90F7fcwHCvvLCMXcmw3g= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= diff --git a/server/clamav.go b/server/clamav.go index 4352fbc..e16a28c 100644 --- a/server/clamav.go +++ b/server/clamav.go @@ -2,6 +2,8 @@ The MIT License (MIT) Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/] +Copyright (c) 2018-2020 Andrea Spacca. +Copyright (c) 2020- Andrea Spacca and Stefan Benten. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -30,7 +32,6 @@ import ( "fmt" "io" - "log" "net/http" "time" @@ -58,7 +59,7 @@ func (s *Server) scanHandler(w http.ResponseWriter, r *http.Request) { abort := make(chan bool) response, err := c.ScanStream(reader, abort) if err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } diff --git a/server/codec.go b/server/codec.go deleted file mode 100644 index a33f3c0..0000000 --- a/server/codec.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -https://github.com/fs111/kurz.go/blob/master/src/codec.go - -Originally written and Copyright (c) 2011 André Kelpe -Modifications Copyright (c) 2015 John Ko - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -package server - -import ( - "math" - "math/rand" - "strings" -) - -const ( - // characters used for short-urls - SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - - // someone set us up the bomb !! - 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 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 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 -} - -// Decodes a string given in our encoding and returns the decimal -// integer. -func Decode(input string) int64 { - const floatbase = float64(BASE) - l := len(input) - var sum int = 0 - for index := l - 1; index > -1; index -= 1 { - current := string(input[index]) - pos := strings.Index(SYMBOLS, current) - sum = sum + (pos * int(math.Pow(floatbase, float64((l-index-1))))) - } - return int64(sum) -} diff --git a/server/codec_test.go b/server/codec_test.go deleted file mode 100644 index aebd8ab..0000000 --- a/server/codec_test.go +++ /dev/null @@ -1,15 +0,0 @@ -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 dbecb1e..4947b77 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -2,6 +2,8 @@ The MIT License (MIT) Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/] +Copyright (c) 2018-2020 Andrea Spacca. +Copyright (c) 2020- Andrea Spacca and Stefan Benten. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -40,7 +42,6 @@ import ( html_template "html/template" "io" "io/ioutil" - "log" "mime" "net/http" "net/url" @@ -125,7 +126,7 @@ func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) { metadata, err := s.CheckMetadata(token, filename, false) if err != nil { - log.Printf("Error metadata: %s", err.Error()) + s.logger.Printf("Error metadata: %s", err.Error()) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } @@ -237,16 +238,34 @@ func (s *Server) viewHandler(w http.ResponseWriter, r *http.Request) { hostname := getURL(r, s.proxyPort).Host webAddress := resolveWebAddress(r, s.proxyPath, s.proxyPort) + maxUploadSize := "" + if s.maxUploadSize > 0 { + maxUploadSize = formatSize(s.maxUploadSize) + } + + purgeTime := "" + if s.purgeDays > 0 { + purgeTime = s.purgeDays.String() + } + data := struct { - Hostname string - WebAddress string - GAKey string - UserVoiceKey string + Hostname string + WebAddress string + GAKey string + UserVoiceKey string + PurgeTime string + MaxUploadSize string + SampleToken string + SampleToken2 string }{ hostname, webAddress, s.gaKey, s.userVoiceKey, + purgeTime, + maxUploadSize, + Token(s.randomTokenLength), + Token(s.randomTokenLength), } if acceptsHTML(r.Header) { @@ -272,12 +291,12 @@ func sanitize(fileName string) string { func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(_24K); nil != err { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Error occurred copying to output stream", 500) return } - token := Encode(INIT_SEED, s.randomTokenLength) + token := Token(s.randomTokenLength) w.Header().Set("Content-Type", "text/plain") @@ -290,7 +309,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) { var err error if f, err = fheader.Open(); err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -299,7 +318,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) { n, err := io.CopyN(&b, f, _24K+1) if err != nil && err != io.EOF { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -310,14 +329,14 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) { if n > _24K { file, err = ioutil.TempFile(s.tempPath, "transfer-") if err != nil { - log.Fatal(err) + s.logger.Fatal(err) } n, err = io.Copy(file, io.MultiReader(&b, f)) if err != nil { - cleanTmpFile(file) + s.cleanTmpFile(file) - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -330,7 +349,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) { contentLength := n if s.maxUploadSize > 0 && contentLength > s.maxUploadSize { - log.Print("Entity too large") + s.logger.Print("Entity too large") http.Error(w, http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge) return } @@ -339,23 +358,23 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) { buffer := &bytes.Buffer{} if err := json.NewEncoder(buffer).Encode(metadata); err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, errors.New("Could not encode metadata").Error(), 500) - cleanTmpFile(file) + s.cleanTmpFile(file) return } else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, errors.New("Could not save metadata").Error(), 500) - cleanTmpFile(file) + s.cleanTmpFile(file) return } - log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) + s.logger.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { - log.Printf("Backend storage error: %s", err.Error()) + s.logger.Printf("Backend storage error: %s", err.Error()) http.Error(w, err.Error(), 500) return @@ -365,21 +384,21 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) { relativeURL, _ := url.Parse(path.Join(s.proxyPath, token, filename)) fmt.Fprintln(w, getURL(r, s.proxyPort).ResolveReference(relativeURL).String()) - cleanTmpFile(file) + s.cleanTmpFile(file) } } } -func cleanTmpFile(f *os.File) { +func (s *Server) cleanTmpFile(f *os.File) { if f != nil { err := f.Close() if err != nil { - log.Printf("Error closing tmpfile: %s (%s)", err, f.Name()) + s.logger.Printf("Error closing tmpfile: %s (%s)", err, f.Name()) } err = os.Remove(f.Name()) if err != nil { - log.Printf("Error removing tmpfile: %s (%s)", err, f.Name()) + s.logger.Printf("Error removing tmpfile: %s (%s)", err, f.Name()) } } } @@ -399,13 +418,13 @@ type Metadata struct { DeletionToken string } -func MetadataForRequest(contentType string, randomTokenLength int64, r *http.Request) Metadata { +func MetadataForRequest(contentType string, randomTokenLength int, r *http.Request) Metadata { metadata := Metadata{ ContentType: strings.ToLower(contentType), MaxDate: time.Time{}, Downloads: 0, MaxDownloads: -1, - DeletionToken: Encode(INIT_SEED, randomTokenLength) + Encode(INIT_SEED, randomTokenLength), + DeletionToken: Token(randomTokenLength) + Token(randomTokenLength), } if v := r.Header.Get("Max-Downloads"); v == "" { @@ -447,7 +466,7 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) { n, err := io.CopyN(&b, f, _24K+1) if err != nil && err != io.EOF { - log.Printf("Error putting new file: %s", err.Error()) + s.logger.Printf("Error putting new file: %s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -457,16 +476,16 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) { if n > _24K { file, err = ioutil.TempFile(s.tempPath, "transfer-") if err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } - defer cleanTmpFile(file) + defer s.cleanTmpFile(file) n, err = io.Copy(file, io.MultiReader(&b, f)) if err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -480,40 +499,40 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) { } if s.maxUploadSize > 0 && contentLength > s.maxUploadSize { - log.Print("Entity too large") + s.logger.Print("Entity too large") http.Error(w, http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge) return } if contentLength == 0 { - log.Print("Empty content-length") + s.logger.Print("Empty content-length") http.Error(w, errors.New("Could not upload empty file").Error(), 400) return } contentType := mime.TypeByExtension(filepath.Ext(vars["filename"])) - token := Encode(INIT_SEED, s.randomTokenLength) + token := Token(s.randomTokenLength) metadata := MetadataForRequest(contentType, s.randomTokenLength, r) buffer := &bytes.Buffer{} if err := json.NewEncoder(buffer).Encode(metadata); err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, errors.New("Could not encode metadata").Error(), 500) return } else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, errors.New("Could not save metadata").Error(), 500) return } - log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) + s.logger.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) var err error if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { - log.Printf("Error putting new file: %s", err.Error()) + s.logger.Printf("Error putting new file: %s", err.Error()) http.Error(w, errors.New("Could not save file").Error(), 500) return } @@ -637,23 +656,22 @@ func (metadata Metadata) remainingLimitHeaderValues() (remainingDownloads, remai return remainingDownloads, remainingDays } -func (s *Server) Lock(token, filename string) error { +func (s *Server) Lock(token, filename string) { key := path.Join(token, filename) - if _, ok := s.locks[key]; !ok { - s.locks[key] = &sync.Mutex{} - } + lock, _ := s.locks.LoadOrStore(key, &sync.Mutex{}) - s.locks[key].Lock() + lock.(*sync.Mutex).Lock() - return nil + return } -func (s *Server) Unlock(token, filename string) error { +func (s *Server) Unlock(token, filename string) { key := path.Join(token, filename) - s.locks[key].Unlock() - return nil + lock, _ := s.locks.LoadOrStore(key, &sync.Mutex{}) + + lock.(*sync.Mutex).Unlock() } func (s *Server) CheckMetadata(token, filename string, increaseDownload bool) (Metadata, error) { @@ -723,7 +741,7 @@ func (s *Server) purgeHandler() { select { case <-ticker.C: err := s.storage.Purge(s.purgeDays) - log.Printf("error cleaning up expired files: %v", err) + s.logger.Printf("error cleaning up expired files: %v", err) } } }() @@ -737,7 +755,7 @@ func (s *Server) deleteHandler(w http.ResponseWriter, r *http.Request) { deletionToken := vars["deletionToken"] if err := s.CheckDeletionToken(deletionToken, token, filename); err != nil { - log.Printf("Error metadata: %s", err.Error()) + s.logger.Printf("Error metadata: %s", err.Error()) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } @@ -747,7 +765,7 @@ func (s *Server) deleteHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } else if err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Could not delete file.", 500) return } @@ -773,7 +791,7 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) { filename := sanitize(strings.Split(key, "/")[1]) if _, err := s.CheckMetadata(token, filename, true); err != nil { - log.Printf("Error metadata: %s", err.Error()) + s.logger.Printf("Error metadata: %s", err.Error()) continue } @@ -784,7 +802,7 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "File not found", 404) return } else { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Could not retrieve file.", 500) return } @@ -802,20 +820,20 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) { fw, err := zw.CreateHeader(header) if err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Internal server error.", 500) return } if _, err = io.Copy(fw, reader); err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Internal server error.", 500) return } } if err := zw.Close(); err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Internal server error.", 500) return } @@ -845,7 +863,7 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) { filename := sanitize(strings.Split(key, "/")[1]) if _, err := s.CheckMetadata(token, filename, true); err != nil { - log.Printf("Error metadata: %s", err.Error()) + s.logger.Printf("Error metadata: %s", err.Error()) continue } @@ -855,7 +873,7 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "File not found", 404) return } else { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Could not retrieve file.", 500) return } @@ -870,13 +888,13 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) { err = zw.WriteHeader(header) if err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Internal server error.", 500) return } if _, err = io.Copy(zw, reader); err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Internal server error.", 500) return } @@ -904,7 +922,7 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) { filename := strings.Split(key, "/")[1] if _, err := s.CheckMetadata(token, filename, true); err != nil { - log.Printf("Error metadata: %s", err.Error()) + s.logger.Printf("Error metadata: %s", err.Error()) continue } @@ -914,7 +932,7 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "File not found", 404) return } else { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Could not retrieve file.", 500) return } @@ -929,13 +947,13 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) { err = zw.WriteHeader(header) if err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Internal server error.", 500) return } if _, err = io.Copy(zw, reader); err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Internal server error.", 500) return } @@ -951,7 +969,7 @@ func (s *Server) headHandler(w http.ResponseWriter, r *http.Request) { metadata, err := s.CheckMetadata(token, filename, false) if err != nil { - log.Printf("Error metadata: %s", err.Error()) + s.logger.Printf("Error metadata: %s", err.Error()) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } @@ -962,7 +980,7 @@ func (s *Server) headHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } else if err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Could not retrieve file.", 500) return } @@ -986,7 +1004,7 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) { metadata, err := s.CheckMetadata(token, filename, true) if err != nil { - log.Printf("Error metadata: %s", err.Error()) + s.logger.Printf("Error metadata: %s", err.Error()) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } @@ -997,7 +1015,7 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } else if err != nil { - log.Printf("%s", err.Error()) + s.logger.Printf("%s", err.Error()) http.Error(w, "Could not retrieve file.", 500) return } @@ -1025,41 +1043,34 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) { reader = ioutil.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader)) } - if w.Header().Get("Range") == "" { - if _, err = io.Copy(w, reader); err != nil { - log.Printf("%s", err.Error()) + if w.Header().Get("Range") != "" || strings.HasPrefix(metadata.ContentType, "video") || strings.HasPrefix(metadata.ContentType, "audio") { + file, err := ioutil.TempFile(s.tempPath, "range-") + if err != nil { + s.logger.Printf("%s", err.Error()) http.Error(w, "Error occurred copying to output stream", 500) return } + defer s.cleanTmpFile(file) + + _, err = io.Copy(file, reader) + if err != nil { + s.logger.Printf("%s", err.Error()) + http.Error(w, "Error occurred copying to output stream", 500) + return + } + + http.ServeContent(w, r, filename, time.Now(), file) return } - file, err := ioutil.TempFile(s.tempPath, "range-") - if err != nil { - log.Printf("%s", err.Error()) + if _, err = io.Copy(w, reader); err != nil { + s.logger.Printf("%s", err.Error()) http.Error(w, "Error occurred copying to output stream", 500) return } - defer cleanTmpFile(file) - - tee := io.TeeReader(reader, file) - for { - b := make([]byte, _5M) - _, err = tee.Read(b) - if err == io.EOF { - break - } - - if err != nil { - log.Printf("%s", err.Error()) - http.Error(w, "Error occurred copying to output stream", 500) - return - } - } - - http.ServeContent(w, r, filename, time.Now(), file) + return } func (s *Server) RedirectHandler(h http.Handler) http.HandlerFunc { diff --git a/server/server.go b/server/server.go index 6985162..ea0a0de 100644 --- a/server/server.go +++ b/server/server.go @@ -161,7 +161,7 @@ func LogFile(logger *log.Logger, s string) OptionFn { return func(srvr *Server) { f, err := os.OpenFile(s, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { - log.Fatalf("error opening file: %v", err) + logger.Fatalf("error opening file: %v", err) } logger.SetOutput(f) @@ -187,7 +187,7 @@ func RateLimit(requests int) OptionFn { } } -func RandomTokenLength(length int64) OptionFn { +func RandomTokenLength(length int) OptionFn { return func(srvr *Server) { srvr.randomTokenLength = length } @@ -288,7 +288,7 @@ type Server struct { profilerEnabled bool - locks map[string]*sync.Mutex + locks sync.Map maxUploadSize int64 rateLimitRequests int @@ -300,7 +300,7 @@ type Server struct { forceHTTPs bool - randomTokenLength int64 + randomTokenLength int ipFilterOptions *IPFilterOptions @@ -329,7 +329,7 @@ type Server struct { func New(options ...OptionFn) (*Server, error) { s := &Server{ - locks: map[string]*sync.Mutex{}, + locks: sync.Map{}, } for _, optionFn := range options { diff --git a/server/token.go b/server/token.go new file mode 100644 index 0000000..b5f0247 --- /dev/null +++ b/server/token.go @@ -0,0 +1,45 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020- Andrea Spacca and Stefan Benten. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package server + +import ( + "math/rand" +) + +const ( + // characters used for short-urls + SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +) + +// generate a token +func Token(length int) string { + result := "" + for i := 0; i < length; i++ { + x := rand.Intn(len(SYMBOLS) - 1) + result = string(SYMBOLS[x]) + result + } + + return result +} diff --git a/server/token_test.go b/server/token_test.go new file mode 100644 index 0000000..ae9afa0 --- /dev/null +++ b/server/token_test.go @@ -0,0 +1,15 @@ +package server + +import "testing" + +func BenchmarkTokenConcat(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = Token(5) + Token(5) + } +} + +func BenchmarkTokenLonger(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = Token(10) + } +} diff --git a/server/utils.go b/server/utils.go index ff8973d..3a53adc 100644 --- a/server/utils.go +++ b/server/utils.go @@ -2,6 +2,8 @@ The MIT License (MIT) Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/] +Copyright (c) 2018-2020 Andrea Spacca. +Copyright (c) 2020- Andrea Spacca and Stefan Benten. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -25,6 +27,7 @@ THE SOFTWARE. package server import ( + "fmt" "math" "net/http" "net/mail" @@ -47,7 +50,6 @@ func getAwsSession(accessKey, secretKey, region, endpoint string, forcePathStyle } func formatNumber(format string, s uint64) string { - return RenderFloat(format, float64(s)) } @@ -253,3 +255,27 @@ func acceptsHTML(hdr http.Header) bool { return (false) } + +func formatSize(size int64) string { + sizeFloat := float64(size) + base := math.Log(sizeFloat) / math.Log(1024) + + sizeOn := math.Pow(1024, base-math.Floor(base)) + + var round float64 + pow := math.Pow(10, float64(2)) + digit := pow * sizeOn + round = math.Floor(digit) + + newVal := round / pow + + var suffixes [5]string + suffixes[0] = "B" + suffixes[1] = "KB" + suffixes[2] = "MB" + suffixes[3] = "GB" + suffixes[4] = "TB" + + getSuffix := suffixes[int(math.Floor(base))] + return fmt.Sprintf("%s %s", strconv.FormatFloat(newVal, 'f', -1, 64), getSuffix) +}