From fef43d856e115dda758a822964d7596e66640d71 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sun, 18 Sep 2016 21:45:00 -0700 Subject: [PATCH] Add option for maximum expiration time (fixes #99) --- README.md | 1 + expiry.go | 53 +++++++++++++++++++++++++ pages.go | 7 +++- server.go | 3 ++ server_test.go | 94 ++++++++++++++++++++++++++++++++++++++++++++ templates/index.html | 11 ++---- templates/paste.html | 14 ++----- upload.go | 9 +++-- 8 files changed, 169 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 780384d..59b978b 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ allowhotlink = true - ```-filespath files/"``` -- Path to store uploads (default is files/) - ```-metapath meta/``` -- Path to store information about uploads (default is meta/) - ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) +- ```-maxexpiry 86400``` -- maximum expiration time in seconds (default is 0, which is no expiry) - ```-allowhotlink``` -- Allow file hotlinking - ```-contentsecuritypolicy "..."``` -- Content-Security-Policy header for pages (default is "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;") - ```-filecontentsecuritypolicy "..."``` -- Content-Security-Policy header for files (default is "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;") diff --git a/expiry.go b/expiry.go index 9deaf72..4172f51 100644 --- a/expiry.go +++ b/expiry.go @@ -2,8 +2,25 @@ package main import ( "time" + + "github.com/dustin/go-humanize" ) +var defaultExpiryList = []uint64{ + 60, + 300, + 3600, + 86400, + 604800, + 2419200, + 31536000, +} + +type ExpirationTime struct { + Seconds uint64 + Human string +} + var neverExpire = time.Unix(0, 0) // Determine if a file with expiry set to "ts" has expired yet @@ -21,3 +38,39 @@ func isFileExpired(filename string) (bool, error) { return isTsExpired(metadata.Expiry), nil } + +// Return a list of expiration times and their humanized versions +func listExpirationTimes() []ExpirationTime { + epoch := time.Now() + actualExpiryInList := false + var expiryList []ExpirationTime + + for _, expiry := range defaultExpiryList { + if Config.maxExpiry == 0 || expiry <= Config.maxExpiry { + if expiry == Config.maxExpiry { + actualExpiryInList = true + } + + duration := time.Duration(expiry) * time.Second + expiryList = append(expiryList, ExpirationTime{ + expiry, + humanize.RelTime(epoch, epoch.Add(duration), "", ""), + }) + } + } + + if Config.maxExpiry == 0 { + expiryList = append(expiryList, ExpirationTime{ + 0, + "never", + }) + } else if actualExpiryInList == false { + duration := time.Duration(Config.maxExpiry) * time.Second + expiryList = append(expiryList, ExpirationTime{ + Config.maxExpiry, + humanize.RelTime(epoch, epoch.Add(duration), "", ""), + }) + } + + return expiryList +} diff --git a/pages.go b/pages.go index abd2cbd..f58fa88 100644 --- a/pages.go +++ b/pages.go @@ -21,7 +21,8 @@ const ( func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) { err := renderTemplate(Templates["index.html"], pongo2.Context{ - "maxsize": Config.maxSize, + "maxsize": Config.maxSize, + "expirylist": listExpirationTimes(), }, r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -29,7 +30,9 @@ func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) { } func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) { - err := renderTemplate(Templates["paste.html"], pongo2.Context{}, r, w) + err := renderTemplate(Templates["paste.html"], pongo2.Context{ + "expirylist": listExpirationTimes(), + }, r, w) if err != nil { oopsHandler(c, w, r, RespHTML, "") } diff --git a/server.go b/server.go index 3ff5c7f..ac229fe 100644 --- a/server.go +++ b/server.go @@ -47,6 +47,7 @@ var Config struct { fileContentSecurityPolicy string xFrameOptions string maxSize int64 + maxExpiry uint64 realIp bool noLogs bool allowHotlink bool @@ -211,6 +212,8 @@ func main() { "site base url (including trailing slash)") flag.Int64Var(&Config.maxSize, "maxsize", 4*1024*1024*1024, "maximum upload file size in bytes (default 4GB)") + flag.Uint64Var(&Config.maxExpiry, "maxexpiry", 0, + "maximum expiration time in seconds (default is 0, which is no expiry)") flag.StringVar(&Config.certFile, "certfile", "", "path to ssl certificate (for https)") flag.StringVar(&Config.keyFile, "keyfile", "", diff --git a/server_test.go b/server_test.go index ed45105..af7abe0 100644 --- a/server_test.go +++ b/server_test.go @@ -54,6 +54,44 @@ func TestIndex(t *testing.T) { } } +func TestIndexStandardMaxExpiry(t *testing.T) { + mux := setup() + Config.maxExpiry = 60 + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if strings.Contains(w.Body.String(), ">1 hour") { + t.Fatal("String '>1 hour' found in index response") + } + + Config.maxExpiry = 0 +} + +func TestIndexWeirdMaxExpiry(t *testing.T) { + mux := setup() + Config.maxExpiry = 1500 + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if strings.Contains(w.Body.String(), ">never") { + t.Fatal("String '>never' found in index response") + } + + Config.maxExpiry = 0 +} + func TestAddHeader(t *testing.T) { Config.addHeaders = []string{"Linx-Test: It works!"} @@ -408,6 +446,62 @@ func TestPostJSONUpload(t *testing.T) { } } +func TestPostJSONUploadMaxExpiry(t *testing.T) { + mux := setup() + Config.maxExpiry = 300 + + testExpiries := []string{"86400", "-150"} + for _, expiry := range testExpiries { + w := httptest.NewRecorder() + + filename := generateBarename() + ".txt" + + var b bytes.Buffer + mw := multipart.NewWriter(&b) + fw, err := mw.CreateFormFile("file", filename) + if err != nil { + t.Fatal(err) + } + + fw.Write([]byte("File content")) + mw.Close() + + req, err := http.NewRequest("POST", "/upload/", &b) + req.Header.Set("Content-Type", mw.FormDataContentType()) + req.Header.Set("Accept", "application/json") + req.Header.Set("Linx-Expiry", expiry) + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if w.Code != 200 { + t.Log(w.Body.String()) + t.Fatalf("Status code is not 200, but %d", w.Code) + } + + var myjson RespOkJSON + err = json.Unmarshal([]byte(w.Body.String()), &myjson) + if err != nil { + fmt.Println(w.Body.String()) + t.Fatal(err) + } + + myExp, err := strconv.ParseInt(myjson.Expiry, 10, 64) + if err != nil { + t.Fatal(err) + } + + expected := time.Now().Add(time.Duration(Config.maxExpiry) * time.Second).Unix() + if myExp != expected { + t.Fatalf("File expiry is not %d but %s", expected, myjson.Expiry) + } + } + + Config.maxExpiry = 0 +} + func TestPostExpiresJSONUpload(t *testing.T) { mux := setup() w := httptest.NewRecorder() diff --git a/templates/index.html b/templates/index.html index 240a17d..5e95d01 100644 --- a/templates/index.html +++ b/templates/index.html @@ -20,14 +20,9 @@
diff --git a/templates/paste.html b/templates/paste.html index 1294b5e..9178ba4 100644 --- a/templates/paste.html +++ b/templates/paste.html @@ -8,19 +8,13 @@
-
diff --git a/upload.go b/upload.go index b0bbd9f..e67a20a 100644 --- a/upload.go +++ b/upload.go @@ -343,12 +343,15 @@ func barePlusExt(filename string) (barename, extension string) { func parseExpiry(expStr string) time.Duration { if expStr == "" { - return 0 + return time.Duration(Config.maxExpiry) * time.Second } else { - expiry, err := strconv.ParseInt(expStr, 10, 64) + expiry, err := strconv.ParseUint(expStr, 10, 64) if err != nil { - return 0 + return time.Duration(Config.maxExpiry) * time.Second } else { + if Config.maxExpiry > 0 && expiry > Config.maxExpiry { + expiry = Config.maxExpiry + } return time.Duration(expiry) * time.Second } }