Merge pull request #102 from mutantmonkey/maxexpiry
Add option for maximum expiration time (fixes #99)
This commit is contained in:
commit
29d3157a03
|
@ -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;")
|
||||||
|
|
53
expiry.go
53
expiry.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
5
pages.go
5
pages.go
|
@ -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, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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", "",
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue