Skip to content

Commit 6a32e9d

Browse files
committed
buff and test sanitization
1 parent aa2f62c commit 6a32e9d

6 files changed

Lines changed: 125 additions & 29 deletions

File tree

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/upload.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func Upload(app *config.App) http.HandlerFunc {
7878
}
7979

8080
filename := storage.SanitizeName(fileHeader.Filename,
81-
app.MaxSizeName, app.AllowedSpecialChars)
81+
app.MaxSizeName, app.FilenameExtraChars)
8282
f := &storage.File{
8383
Name: filename,
8484
Data: buf.Bytes(),

settings/config.go

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

188-
// Special characters allowed in file names
189-
AllowedSpecialChars string `json:"specialChars,omitempty"`
188+
// Extra characters allowed in file names
189+
FilenameExtraChars string `json:"filenameChars,omitempty"`
190190

191191
// Maximum number of allowed downloads
192192
MaxDownloads int `json:"maxDownloads,omitempty"`

settings/defaultSettings.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,14 @@
7474
},
7575
"limit": {
7676
"expiryTicker": "30s",
77+
"filenameChars": "_.-",
7778
"maxDownloads": 100,
7879
"maxDuration": "192h",
7980
"maxSizeMsg": 128,
80-
"maxSizeName": 60,
81+
"maxSizeName": 64,
8182
"maxSizeWall": 1280,
8283
"maxSizeFileMb": 256,
83-
"reqsPerMinute": 30,
84-
"specialChars": "_.-"
84+
"reqsPerMinute": 30
8585
},
8686
"paths": {
8787
"assets": "/assets/",

storage/sanitize.go

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,26 @@ import (
66
"unicode"
77
)
88

9+
const defaultName = "default"
10+
911
// SanitizeName validates strings for use as filename.
10-
func SanitizeName(input string, maxLength int, specialChars string) string {
12+
func SanitizeName(input string, maxLength int, extraChars string) string {
1113
f := filepath.Base(input)
12-
f = removeInvalidChars(f, specialChars)
13-
f = truncateName(f, maxLength)
14-
return f
15-
}
16-
17-
// isAllowedChar returns true if a character is allowed.
18-
func isAllowedChar(r rune, allowed string) bool {
19-
return strings.ContainsRune(allowed, r)
14+
f = removeInvalidChars(f, extraChars)
15+
ext := filepath.Ext(f)
16+
base := strings.TrimSuffix(f, ext)
17+
if base == "" {
18+
base = defaultName
19+
}
20+
return truncateName(base, ext, maxLength)
2021
}
2122

2223
// removeInvalidChars removes all invalid characters.
2324
func removeInvalidChars(filename string, allowed string) string {
2425
var result strings.Builder
2526
for _, char := range filename {
26-
if unicode.IsLetter(char) ||
27-
unicode.IsDigit(char) ||
27+
if unicode.IsDigit(char) ||
28+
unicode.IsLetter(char) ||
2829
isAllowedChar(char, allowed) {
2930
result.WriteRune(char)
3031
}
@@ -33,16 +34,24 @@ func removeInvalidChars(filename string, allowed string) string {
3334
}
3435

3536
// truncateName trims a filename string to max size,
36-
// preserving the original file extension.
37-
func truncateName(filename string, length int) string {
38-
if len(filename) <= length {
39-
return filename
37+
// preserving reasonably-sized original file extensions.
38+
func truncateName(base string, ext string, maxLength int) string {
39+
const maxExtensionLength = 5
40+
if len(ext) > maxExtensionLength {
41+
ext = ext[:maxExtensionLength]
4042
}
41-
ext := filepath.Ext(filename)
42-
base := strings.TrimSuffix(filename, ext)
43-
maxBaseLength := length - len(ext)
44-
if len(base) > maxBaseLength {
45-
base = base[:maxBaseLength]
43+
totalLength := len(base) + len(ext)
44+
if totalLength <= maxLength {
45+
return base + ext
4646
}
47-
return base + ext
47+
allowedBaseLength := maxLength - len(ext)
48+
if allowedBaseLength > 0 {
49+
return base[:allowedBaseLength] + ext
50+
}
51+
return ext[:maxLength]
52+
}
53+
54+
// isAllowedChar returns true if a character is allowed.
55+
func isAllowedChar(r rune, allowed string) bool {
56+
return strings.ContainsRune(allowed, r)
4857
}

storage/sanitize_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package storage
2+
3+
import "testing"
4+
5+
const extraChars = "_.-"
6+
7+
// TestRemoveInvalidChars validates invalid characters
8+
// are removed from filename strings.
9+
func TestRemoveInvalidChars(t *testing.T) {
10+
tests := []struct {
11+
input string
12+
allow string
13+
want string
14+
}{
15+
{"filename.txt", extraChars, "filename.txt"},
16+
{"file@name!.txt", extraChars, "filename.txt"},
17+
{"_file-name.txt", extraChars, "_file-name.txt"},
18+
{"123-abc_ABC.txt", extraChars, "123-abc_ABC.txt"},
19+
{"chars@#$name.txt", extraChars, "charsname.txt"},
20+
{"afile", extraChars, "afile"},
21+
{".....", extraChars, "....."},
22+
{"!@#$%^&()", extraChars, ""},
23+
{"", extraChars, ""},
24+
}
25+
for _, test := range tests {
26+
result := removeInvalidChars(test.input, test.allow)
27+
if result != test.want {
28+
t.Fatalf("name: '%s' ('%s' allowed): '%s'; want: '%s'",
29+
test.input, test.allow, result, test.want)
30+
}
31+
}
32+
}
33+
34+
// TestTruncateName validates filenames are truncated
35+
// to the desired length.
36+
func TestTruncateName(t *testing.T) {
37+
tests := []struct {
38+
base string
39+
ext string
40+
length int
41+
want string
42+
}{
43+
{"shrt", ".exceedinglylongext", 10, "shrt.exce"},
44+
{"base", ".longextension", 12, "base.long"},
45+
{"exactfit", ".jpeg", 13, "exactfit.jpeg"},
46+
{"longfilename", ".txt", 10, "longfi.txt"},
47+
{"truncatebase", ".png", 8, "trun.png"},
48+
{"onlybase", ".toolong", 8, "onl.tool"},
49+
{"short", ".dat", 9, "short.dat"},
50+
{"example", "", 5, "examp"},
51+
{"", ".zip", 5, ".zip"},
52+
{"", "", 0, ""},
53+
}
54+
for _, test := range tests {
55+
result := truncateName(test.base, test.ext, test.length)
56+
if result != test.want {
57+
t.Fatalf("base: '%s', ext: '%s' (%d); got: '%s', want: '%s'",
58+
test.base, test.ext, test.length, result, test.want)
59+
}
60+
}
61+
}
62+
63+
// TestSanitizeName validates filenames do not exceed
64+
// length nor contain disallowed special characters.
65+
func TestSanitizeName(t *testing.T) {
66+
tests := []struct {
67+
input string
68+
maxLength int
69+
extraChars string
70+
want string
71+
}{
72+
{"my@invalid#name?.txt", 20, extraChars, "myinvalidname.txt"},
73+
{"averylongfilename.png", 15, extraChars, "averylongfi.png"},
74+
{"my.file.name.txt", 20, extraChars, "my.file.name.txt"},
75+
{".hiddenfile", 15, extraChars, defaultName + ".hidd"},
76+
{"myfilename.png", 20, extraChars, "myfilename.png"},
77+
{"/path/to/file.txt", 20, extraChars, "file.txt"},
78+
{"myfilenames", 10, extraChars, "myfilename"},
79+
{".@#$%^&*.png", 15, extraChars, "..png"},
80+
{"@#$%^&*", 10, extraChars, defaultName},
81+
}
82+
for _, test := range tests {
83+
result := SanitizeName(test.input, test.maxLength, test.extraChars)
84+
if result != test.want {
85+
t.Fatalf("name: '%s', length: %d, special: '%s', got: '%s', want: '%s'",
86+
test.input, test.maxLength, test.extraChars, result, test.want)
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)