Merge pull request #102 from mutantmonkey/maxexpiry

Add option for maximum expiration time (fixes #99)
This commit is contained in:
Andrei Marcu 2016-09-18 22:09:05 -07:00 committed by GitHub
commit 29d3157a03
8 changed files with 169 additions and 23 deletions

View File

@ -45,6 +45,7 @@ allowhotlink = true
- ```-filespath files/"``` -- Path to store uploads (default is files/) - ```-filespath files/"``` -- Path to store uploads (default is files/)
- ```-metapath meta/``` -- Path to store information about uploads (default is meta/) - ```-metapath meta/``` -- Path to store information about uploads (default is meta/)
- ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) - ```-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 - ```-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;") - ```-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;") - ```-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;")

View File

@ -2,8 +2,25 @@ package main
import ( import (
"time" "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) var neverExpire = time.Unix(0, 0)
// Determine if a file with expiry set to "ts" has expired yet // 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 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
}

View File

@ -22,6 +22,7 @@ const (
func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) { func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) {
err := renderTemplate(Templates["index.html"], pongo2.Context{ err := renderTemplate(Templates["index.html"], pongo2.Context{
"maxsize": Config.maxSize, "maxsize": Config.maxSize,
"expirylist": listExpirationTimes(),
}, r, w) }, r, w)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) 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) { 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 { if err != nil {
oopsHandler(c, w, r, RespHTML, "") oopsHandler(c, w, r, RespHTML, "")
} }

View File

@ -47,6 +47,7 @@ var Config struct {
fileContentSecurityPolicy string fileContentSecurityPolicy string
xFrameOptions string xFrameOptions string
maxSize int64 maxSize int64
maxExpiry uint64
realIp bool realIp bool
noLogs bool noLogs bool
allowHotlink bool allowHotlink bool
@ -211,6 +212,8 @@ func main() {
"site base url (including trailing slash)") "site base url (including trailing slash)")
flag.Int64Var(&Config.maxSize, "maxsize", 4*1024*1024*1024, flag.Int64Var(&Config.maxSize, "maxsize", 4*1024*1024*1024,
"maximum upload file size in bytes (default 4GB)") "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", "", flag.StringVar(&Config.certFile, "certfile", "",
"path to ssl certificate (for https)") "path to ssl certificate (for https)")
flag.StringVar(&Config.keyFile, "keyfile", "", flag.StringVar(&Config.keyFile, "keyfile", "",

View File

@ -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</object>") {
t.Fatal("String '>1 hour</object>' 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</object>") {
t.Fatal("String '>never</object>' found in index response")
}
Config.maxExpiry = 0
}
func TestAddHeader(t *testing.T) { func TestAddHeader(t *testing.T) {
Config.addHeaders = []string{"Linx-Test: It works!"} 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) { func TestPostExpiresJSONUpload(t *testing.T) {
mux := setup() mux := setup()
w := httptest.NewRecorder() w := httptest.NewRecorder()

View File

@ -20,14 +20,9 @@
<div id="expiry"> <div id="expiry">
<label>File expiry: <label>File expiry:
<select name="expires" id="expires"> <select name="expires" id="expires">
<option value="0">never</option> {% for expiry in expirylist %}
<option value="60">a minute</option> <option value="{{ expiry.Seconds }}"{% if forloop.Last %} selected{% endif %}>{{ expiry.Human }}</option>
<option value="300">5 minutes</option> {% endfor %}
<option value="3600">an hour</option>
<option value="86400">a day</option>
<option value="604800">a week</option>
<option value="2419200">a month</option>
<option value="29030400">a year</option>
</select> </select>
</label> </label>
</div> </div>

View File

@ -8,19 +8,13 @@
<div class="right"> <div class="right">
<select id="expiry" name="expires"> <select id="expiry" name="expires">
<option disabled=disabled>Expires:</option> <option disabled="disabled">Expires:</option>
<option value="0">never</option> {% for expiry in expirylist %}
<option value="60">a minute</option> <option value="{{ expiry.Seconds }}"{% if forloop.Last %} selected{% endif %}>{{ expiry.Human }}</option>
<option value="300">5 minutes</option> {% endfor %}
<option value="3600">an hour</option>
<option value="86400">a day</option>
<option value="604800">a week</option>
<option value="2419200">a month</option>
<option value="29030400">a year</option>
</select> </select>
<input type="submit" value="Paste"> <input type="submit" value="Paste">
</div> </div>
</div> </div>

View File

@ -343,12 +343,15 @@ func barePlusExt(filename string) (barename, extension string) {
func parseExpiry(expStr string) time.Duration { func parseExpiry(expStr string) time.Duration {
if expStr == "" { if expStr == "" {
return 0 return time.Duration(Config.maxExpiry) * time.Second
} else { } else {
expiry, err := strconv.ParseInt(expStr, 10, 64) expiry, err := strconv.ParseUint(expStr, 10, 64)
if err != nil { if err != nil {
return 0 return time.Duration(Config.maxExpiry) * time.Second
} else { } else {
if Config.maxExpiry > 0 && expiry > Config.maxExpiry {
expiry = Config.maxExpiry
}
return time.Duration(expiry) * time.Second return time.Duration(expiry) * time.Second
} }
} }