Skip to content

Commit 37505f3

Browse files
authored
Merge pull request #83 from drduh/20dec25
sanitize filename and expiry inputs
2 parents 3d1f4ad + f575411 commit 37505f3

File tree

16 files changed

+307
-92
lines changed

16 files changed

+307
-92
lines changed

handlers/clear.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@ func Clear(app *config.App) http.HandlerFunc {
1313
if req == nil {
1414
return
1515
}
16-
1716
app.ClearStorage()
1817
app.Log.Info("storage cleared", "user", req)
19-
2018
toRoot(w, r, app.Root)
2119
}
2220
}

handlers/download.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@ func Download(app *config.App) http.HandlerFunc {
1414
return
1515
}
1616

17-
fileName := getRequestParameter(r, len(app.Download), "name")
18-
if fileName == "" {
17+
filename := getRequestParameter(r, len(app.Download), "name")
18+
if filename == "" {
1919
writeJSON(w, http.StatusNotFound, errorJSON(app.NoFilename))
2020
app.Log.Error(app.NoFilename, "user", req)
2121
return
2222
}
23-
app.Log.Debug("file requested", "filename", fileName, "user", req)
23+
app.Log.Debug("file requested", "filename", filename, "user", req)
2424

25-
file := app.FindFile(fileName)
25+
file := app.FindFile(filename)
2626
if file == nil {
2727
writeJSON(w, http.StatusNotFound, errorJSON(app.NotFound))
28-
app.Log.Error(app.NotFound, "filename", fileName, "user", req)
28+
app.Log.Error(app.NotFound, "filename", filename, "user", req)
2929
return
3030
}
3131

handlers/form.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,43 @@ package handlers
33
import (
44
"net/http"
55
"strconv"
6+
"strings"
67
"time"
8+
9+
"github.com/drduh/gone/settings"
710
)
811

912
// parseFormInt reads an integer form value or returns the default.
10-
func parseFormInt(r *http.Request, field string, def int) int {
13+
func parseFormInt(r *http.Request, field string, def, maximum int) int {
1114
input := r.FormValue(field)
1215
if input != "" {
16+
input = strings.TrimSpace(input)
1317
if v, err := strconv.Atoi(input); err == nil {
18+
if v <= 0 {
19+
return def
20+
}
21+
if v > maximum {
22+
return maximum
23+
}
1424
return v
1525
}
1626
}
1727
return def
1828
}
1929

2030
// parseFormDuration reads a duration form value or returns the default.
21-
func parseFormDuration(r *http.Request, field string, def time.Duration) time.Duration {
31+
func parseFormDuration(r *http.Request, field string,
32+
def time.Duration, maximum settings.Duration) time.Duration {
2233
input := r.FormValue(field)
2334
if input != "" {
35+
input = strings.TrimSpace(input)
2436
if d, err := time.ParseDuration(input); err == nil {
37+
if d < time.Second {
38+
return def
39+
}
40+
if d > maximum.GetDuration() {
41+
return maximum.GetDuration()
42+
}
2543
return d
2644
}
2745
}

handlers/form_test.go

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,40 @@ import (
44
"net/http"
55
"testing"
66
"time"
7+
8+
"github.com/drduh/gone/settings"
79
)
810

911
// TestParseFormInt tests integer form values are parsed.
1012
func TestParseFormInt(t *testing.T) {
13+
def := 1
14+
maximum := 100
1115
tests := []struct {
12-
name string
13-
query string
14-
field string
15-
def int
16-
want int
16+
name string
17+
query string
18+
field string
19+
def int
20+
want int
21+
maximum int
1722
}{
18-
{"valid", "/?downloads=5", "downloads", 10, 5},
19-
{"missing", "/", "downloads", 10, 10},
20-
{"invalid", "/?downloads=none", "downloads", 10, 10},
23+
{"valid", "/?downloads=5", "downloads",
24+
def, 5, maximum},
25+
{"space", "/?downloads= 5 ", "downloads",
26+
def, 5, maximum},
27+
{"missing", "/", "downloads",
28+
def, 1, maximum},
29+
{"invalid", "/?downloads=none", "downloads",
30+
def, 1, maximum},
31+
{"zero", "/?downloads=0", "downloads",
32+
def, def, maximum},
33+
{"negative", "/?downloads=-1", "downloads",
34+
def, def, maximum},
35+
{"fraction", "/?downloads=3.5", "downloads",
36+
def, def, maximum},
37+
{"large", "/?downloads=101", "downloads",
38+
def, maximum, maximum},
39+
{"xlarge", "/?downloads=999999999999999999999",
40+
"downloads", def, def, maximum}, // overflows int64
2141
}
2242
for _, tc := range tests {
2343
tc := tc
@@ -27,7 +47,7 @@ func TestParseFormInt(t *testing.T) {
2747
if err != nil {
2848
t.Fatal(err)
2949
}
30-
got := parseFormInt(req, tc.field, tc.def)
50+
got := parseFormInt(req, tc.field, tc.def, tc.maximum)
3151
if got != tc.want {
3252
t.Fatalf("parseFormInt(%q) = %d; want %d",
3353
tc.query, got, tc.want)
@@ -38,21 +58,34 @@ func TestParseFormInt(t *testing.T) {
3858

3959
// TestParseFormDuration tests duration form values are parsed.
4060
func TestParseFormDuration(t *testing.T) {
61+
def := 1 * time.Hour
62+
maximum := 8 * 24 * time.Hour
4163
tests := []struct {
42-
name string
43-
query string
44-
field string
45-
def time.Duration
46-
want time.Duration
64+
name string
65+
query string
66+
field string
67+
def time.Duration
68+
want time.Duration
69+
maximum time.Duration
4770
}{
4871
{"valid", "/?duration=1h30m", "duration",
49-
2 * time.Hour, 90 * time.Minute},
72+
def, 90 * time.Minute, maximum},
73+
{"space", "/?duration= 15m ", "duration",
74+
def, 15 * time.Minute, maximum},
5075
{"missing", "/", "duration",
51-
2 * time.Hour, 2 * time.Hour},
76+
def, def, maximum},
5277
{"invalid", "/?duration=none", "duration",
53-
2 * time.Hour, 2 * time.Hour},
54-
{"seconds", "/?duration=123s", "duration",
55-
2 * time.Hour, 2*time.Minute + 3*time.Second},
78+
def, def, maximum},
79+
{"zero", "/?duration=0s", "duration",
80+
def, def, maximum},
81+
{"negative", "/?duration=-1h", "duration",
82+
def, def, maximum},
83+
{"fraction", "/?duration=1.5h", "duration",
84+
def, 90 * time.Minute, maximum},
85+
{"large", "/?duration=9999h", "duration",
86+
def, maximum, maximum},
87+
{"xlarge", "/?duration=99999999999h", "duration",
88+
def, def, maximum}, // overflows int64
5689
}
5790
for _, tc := range tests {
5891
tc := tc
@@ -62,7 +95,8 @@ func TestParseFormDuration(t *testing.T) {
6295
if err != nil {
6396
t.Fatal(err)
6497
}
65-
got := parseFormDuration(req, tc.field, tc.def)
98+
got := parseFormDuration(req, tc.field, tc.def,
99+
settings.Duration{Duration: tc.maximum})
66100
if got != tc.want {
67101
t.Fatalf("parseFormDuration(%q) = %v; want %v",
68102
tc.query, got, tc.want)

handlers/index.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,42 +8,39 @@ import (
88
"github.com/drduh/gone/templates"
99
)
1010

11-
// Index handles requests for the main index page
12-
// with all available application features.
11+
const templateData = "data/*.tmpl"
12+
13+
// Index handles requests to load and render the index page.
1314
func Index(app *config.App) http.HandlerFunc {
1415
return func(w http.ResponseWriter, r *http.Request) {
1516
req := authRequest(w, r, app)
1617
if req == nil {
1718
return
1819
}
19-
2020
app.Log.Info("serving index", "user", req)
2121

2222
theme := getDefaultTheme(app.Style.Theme)
23-
app.Log.Debug("got theme", "default", theme)
2423
if app.Style.AllowPick {
2524
theme = getTheme(w, r, theme,
2625
app.Cookie.Id,
2726
app.Cookie.Time.GetDuration(),
2827
app.Style.Available)
29-
app.Log.Debug("got theme", "selected", theme)
3028
}
3129

32-
tmpl, err := template.New("index").ParseFS(templates.All, "data/*.tmpl")
30+
tmpl, err := template.New("index").ParseFS(templates.All, templateData)
3331
if err != nil {
3432
writeJSON(w, http.StatusInternalServerError, errorJSON(app.TmplParse))
3533
app.Log.Error(app.TmplParse, "error", err.Error(), "user", req)
3634
return
3735
}
3836

3937
app.UpdateTimeRemaining()
40-
4138
response := templates.Index{
4239
Auth: app.Auth,
40+
Default: app.Default,
4341
DefaultDuration: app.Expiration.String(),
4442
Hostname: app.Hostname,
4543
Index: app.Index,
46-
Default: app.Default,
4744
Limit: app.Limit,
4845
NoFiles: app.NoFiles,
4946
Paths: app.Paths,

handlers/list.go

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"net/http"
55

66
"github.com/drduh/gone/config"
7-
"github.com/drduh/gone/storage"
87
)
98

109
// List handles requests to list Files in Storage.
@@ -15,49 +14,9 @@ func List(app *config.App) http.HandlerFunc {
1514
return
1615
}
1716
app.UpdateTimeRemaining()
18-
files := getFiles(app)
17+
files := app.ListFiles()
1918
app.Log.Info("serving file list",
2019
"files", len(files), "user", req)
2120
writeJSON(w, http.StatusOK, files)
2221
}
2322
}
24-
25-
// getFiles returns a list of non-expired Files in Storage,
26-
// and removes expired Files.
27-
func getFiles(app *config.App) []storage.File {
28-
files := make([]storage.File, 0, len(app.Files))
29-
for _, file := range app.Files {
30-
reason := file.IsExpired()
31-
if reason != "" {
32-
app.Expire(file)
33-
app.Log.Info("removed file", "reason", reason,
34-
"id", file.Id, "name", file.Name,
35-
"downloads", file.Total)
36-
break
37-
}
38-
39-
f := storage.File{
40-
Id: file.Id,
41-
Name: file.Name,
42-
Size: file.Size,
43-
Sum: file.Sum,
44-
Type: file.Type,
45-
Owner: storage.Owner{
46-
Agent: file.Agent,
47-
Mask: file.Mask,
48-
},
49-
Time: storage.Time{
50-
Remain: file.Time.Remain,
51-
Upload: file.Upload,
52-
},
53-
Downloads: storage.Downloads{
54-
Allow: file.Downloads.Allow,
55-
Remain: file.NumRemaining(),
56-
Total: file.Total,
57-
},
58-
}
59-
files = append(files, f)
60-
}
61-
62-
return files
63-
}

handlers/random.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/drduh/gone/util"
88
)
99

10-
// Random serves a random string of specified type.
10+
// Random handles requests to generate a random string.
1111
func Random(app *config.App) http.HandlerFunc {
1212
return func(w http.ResponseWriter, r *http.Request) {
1313
req := authRequest(w, r, app)

handlers/upload.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ func Upload(app *config.App) http.HandlerFunc {
3737
}
3838

3939
downloadLimit := parseFormInt(r,
40-
formFieldDownloads, app.Downloads)
40+
formFieldDownloads, app.Downloads, app.MaxDownloads)
4141
app.Log.Debug("got form value", formFieldDownloads, downloadLimit)
4242

4343
durationLimit := parseFormDuration(r,
44-
formFieldDuration, app.Expiration.Duration)
44+
formFieldDuration, app.Expiration.Duration, app.MaxDuration)
4545
app.Log.Debug("got form value", formFieldDuration, durationLimit)
4646

4747
var upload storage.File
@@ -77,8 +77,10 @@ func Upload(app *config.App) http.HandlerFunc {
7777
return
7878
}
7979

80+
filename := storage.SanitizeName(fileHeader.Filename,
81+
app.MaxSizeName, app.FilenameExtraChars)
8082
f := &storage.File{
81-
Name: fileHeader.Filename,
83+
Name: filename,
8284
Data: buf.Bytes(),
8385
Owner: storage.Owner{
8486
Address: req.Address,

settings/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,21 @@ type Default struct {
185185
// Content represents limits on content sharing.
186186
type Limit struct {
187187

188+
// Extra characters allowed in file names
189+
FilenameExtraChars string `json:"filenameChars,omitempty"`
190+
191+
// Maximum number of allowed downloads
192+
MaxDownloads int `json:"maxDownloads,omitempty"`
193+
194+
// Maximum expiration duration
195+
MaxDuration Duration `json:"maxDuration,omitempty"`
196+
188197
// Maximum text message size
189198
MaxSizeMsg int `json:"maxSizeMsg,omitempty"`
190199

200+
// Maximum file name length
201+
MaxSizeName int `json:"maxSizeName,omitempty"`
202+
191203
// Maximum wall content size
192204
MaxSizeWall int `json:"maxSizeWall,omitempty"`
193205

settings/defaultSettings.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,15 @@
7373
}
7474
},
7575
"limit": {
76+
"expiryTicker": "30s",
77+
"filenameChars": "_.-",
78+
"maxDownloads": 100,
79+
"maxDuration": "192h",
7680
"maxSizeMsg": 128,
81+
"maxSizeName": 64,
7782
"maxSizeWall": 1280,
7883
"maxSizeFileMb": 256,
79-
"reqsPerMinute": 30,
80-
"expiryTicker": "30s"
84+
"reqsPerMinute": 30
8185
},
8286
"paths": {
8387
"assets": "/assets/",

0 commit comments

Comments
 (0)