Skip to content

Commit 5bc9497

Browse files
committed
fix: enforce max file size
Signed-off-by: skidoodle <contact@albert.lol>
1 parent 956dff4 commit 5bc9497

File tree

3 files changed

+173
-26
lines changed

3 files changed

+173
-26
lines changed

internal/app/handlers.go

Lines changed: 158 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ func (app *App) HandleHome(w http.ResponseWriter, r *http.Request) {
2121
"MaxMB": app.Conf.MaxMB,
2222
"Host": r.Host,
2323
})
24-
if err != nil {
25-
app.Logger.Error("Template error", "err", err)
26-
}
24+
if err != nil {
25+
app.Logger.Error("Template error", "err", err)
26+
}
2727
}
2828

2929
func (app *App) HandleUpload(w http.ResponseWriter, r *http.Request) {
@@ -32,17 +32,27 @@ func (app *App) HandleUpload(w http.ResponseWriter, r *http.Request) {
3232

3333
file, header, err := r.FormFile("file")
3434
if err != nil {
35+
if err.Error() == "http: request body too large" {
36+
app.SendError(w, r, http.StatusRequestEntityTooLarge)
37+
return
38+
}
3539
app.SendError(w, r, http.StatusBadRequest)
3640
return
3741
}
3842
defer file.Close()
3943

4044
tmpPath := filepath.Join(app.Conf.StorageDir, "tmp", fmt.Sprintf("up_%d", os.Getpid()))
41-
tmp, _ := os.Create(tmpPath)
45+
tmp, err := os.Create(tmpPath)
46+
if err != nil {
47+
app.Logger.Error("Failed to create temp file", "err", err)
48+
app.SendError(w, r, http.StatusInternalServerError)
49+
return
50+
}
4251
defer os.Remove(tmpPath)
4352
defer tmp.Close()
4453

4554
if _, err := io.Copy(tmp, file); err != nil {
55+
app.Logger.Error("Failed to write temp file", "err", err)
4656
app.SendError(w, r, http.StatusRequestEntityTooLarge)
4757
return
4858
}
@@ -51,53 +61,124 @@ func (app *App) HandleUpload(w http.ResponseWriter, r *http.Request) {
5161
}
5262

5363
func (app *App) HandleChunk(w http.ResponseWriter, r *http.Request) {
64+
r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
65+
5466
uid := r.FormValue("upload_id")
55-
idx, _ := strconv.Atoi(r.FormValue("index"))
67+
idx, err := strconv.Atoi(r.FormValue("index"))
68+
if err != nil {
69+
app.SendError(w, r, http.StatusBadRequest)
70+
return
71+
}
5672

57-
if !reUploadID.MatchString(uid) || idx > 1000 {
73+
const chunkSize = 8 << 20
74+
maxChunks := int((app.Conf.MaxMB<<20)/chunkSize) + 2
75+
76+
if !reUploadID.MatchString(uid) || idx > maxChunks || idx < 0 {
5877
app.SendError(w, r, http.StatusBadRequest)
5978
return
6079
}
6180

6281
file, _, err := r.FormFile("chunk")
6382
if err != nil {
83+
if err.Error() == "http: request body too large" {
84+
app.SendError(w, r, http.StatusRequestEntityTooLarge)
85+
return
86+
}
87+
app.SendError(w, r, http.StatusBadRequest)
6488
return
6589
}
6690
defer file.Close()
6791

6892
dir := filepath.Join(app.Conf.StorageDir, "tmp", uid)
69-
os.MkdirAll(dir, 0700)
93+
if err := os.MkdirAll(dir, 0700); err != nil {
94+
app.Logger.Error("Failed to create chunk dir", "err", err)
95+
app.SendError(w, r, http.StatusInternalServerError)
96+
return
97+
}
7098

71-
dest, _ := os.Create(filepath.Join(dir, strconv.Itoa(idx)))
99+
dest, err := os.Create(filepath.Join(dir, strconv.Itoa(idx)))
100+
if err != nil {
101+
app.Logger.Error("Failed to create chunk file", "err", err)
102+
app.SendError(w, r, http.StatusInternalServerError)
103+
return
104+
}
72105
defer dest.Close()
73-
io.Copy(dest, file)
106+
107+
if _, err := io.Copy(dest, file); err != nil {
108+
app.Logger.Error("Failed to save chunk", "err", err)
109+
app.SendError(w, r, http.StatusInternalServerError)
110+
return
111+
}
74112
}
75113

76114
func (app *App) HandleFinish(w http.ResponseWriter, r *http.Request) {
77115
uid := r.FormValue("upload_id")
78-
total, _ := strconv.Atoi(r.FormValue("total"))
116+
total, err := strconv.Atoi(r.FormValue("total"))
117+
if err != nil {
118+
app.SendError(w, r, http.StatusBadRequest)
119+
return
120+
}
121+
122+
const chunkSize = 8 << 20
123+
maxChunks := int((app.Conf.MaxMB<<20)/chunkSize) + 2
79124

80-
if !reUploadID.MatchString(uid) || total > 1000 {
125+
if !reUploadID.MatchString(uid) || total > maxChunks || total <= 0 {
81126
app.SendError(w, r, http.StatusBadRequest)
82127
return
83128
}
84129

85130
tmpPath := filepath.Join(app.Conf.StorageDir, "tmp", "m_"+uid)
86-
merged, _ := os.Create(tmpPath)
131+
merged, err := os.Create(tmpPath)
132+
if err != nil {
133+
app.Logger.Error("Failed to create merge file", "err", err)
134+
app.SendError(w, r, http.StatusInternalServerError)
135+
return
136+
}
87137
defer os.Remove(tmpPath)
88138
defer merged.Close()
89139

140+
limit := app.Conf.MaxMB << 20
141+
var written int64
142+
90143
for i := range total {
91144
partPath := filepath.Join(app.Conf.StorageDir, "tmp", uid, strconv.Itoa(i))
92145
part, err := os.Open(partPath)
93146
if err != nil {
94-
continue
147+
app.Logger.Error("Missing chunk during merge", "uid", uid, "index", i, "err", err)
148+
app.SendError(w, r, http.StatusBadRequest)
149+
return
95150
}
96-
io.Copy(merged, part)
151+
152+
n, err := io.Copy(merged, part)
97153
part.Close()
154+
if err != nil {
155+
app.Logger.Error("Failed to append chunk", "err", err)
156+
app.SendError(w, r, http.StatusInternalServerError)
157+
return
158+
}
159+
160+
written += n
161+
if written > limit {
162+
app.SendError(w, r, http.StatusRequestEntityTooLarge)
163+
return
164+
}
165+
}
166+
167+
if err := merged.Close(); err != nil {
168+
app.Logger.Error("Failed to close merged file", "err", err)
169+
app.SendError(w, r, http.StatusInternalServerError)
170+
return
171+
}
172+
173+
mergedRead, err := os.Open(tmpPath)
174+
if err != nil {
175+
app.Logger.Error("Failed to open merged file", "err", err)
176+
app.SendError(w, r, http.StatusInternalServerError)
177+
return
98178
}
179+
defer mergedRead.Close()
99180

100-
app.FinalizeFile(w, r, merged, r.FormValue("filename"))
181+
app.FinalizeFile(w, r, mergedRead, r.FormValue("filename"))
101182
os.RemoveAll(filepath.Join(app.Conf.StorageDir, "tmp", uid))
102183
}
103184

@@ -126,10 +207,21 @@ func (app *App) HandleGetFile(w http.ResponseWriter, r *http.Request) {
126207
return
127208
}
128209

129-
f, _ := os.Open(path)
210+
f, err := os.Open(path)
211+
if err != nil {
212+
app.Logger.Error("Failed to open file", "path", path, "err", err)
213+
app.SendError(w, r, http.StatusInternalServerError)
214+
return
215+
}
130216
defer f.Close()
131217

132-
streamer, _ := crypto.NewGCMStreamer(key)
218+
streamer, err := crypto.NewGCMStreamer(key)
219+
if err != nil {
220+
app.Logger.Error("Failed to create crypto streamer", "err", err)
221+
app.SendError(w, r, http.StatusInternalServerError)
222+
return
223+
}
224+
133225
decryptor := crypto.NewDecryptor(f, streamer.AEAD, info.Size())
134226

135227
contentType := mime.TypeByExtension(ext)
@@ -146,29 +238,70 @@ func (app *App) HandleGetFile(w http.ResponseWriter, r *http.Request) {
146238
}
147239

148240
func (app *App) FinalizeFile(w http.ResponseWriter, r *http.Request, src *os.File, filename string) {
149-
src.Seek(0, 0)
150-
key, _ := crypto.DeriveKey(src)
241+
if _, err := src.Seek(0, 0); err != nil {
242+
app.Logger.Error("Seek failed", "err", err)
243+
app.SendError(w, r, http.StatusInternalServerError)
244+
return
245+
}
246+
247+
key, err := crypto.DeriveKey(src)
248+
if err != nil {
249+
app.Logger.Error("Key derivation failed", "err", err)
250+
app.SendError(w, r, http.StatusInternalServerError)
251+
return
252+
}
151253

152254
ext := filepath.Ext(filename)
153255
id := crypto.GetID(key, ext)
154256

155-
src.Seek(0, 0)
257+
if _, err := src.Seek(0, 0); err != nil {
258+
app.Logger.Error("Seek failed", "err", err)
259+
app.SendError(w, r, http.StatusInternalServerError)
260+
return
261+
}
262+
156263
finalPath := filepath.Join(app.Conf.StorageDir, id)
157264

158265
if _, err := os.Stat(finalPath); err == nil {
159266
app.RespondWithLink(w, r, key, filename)
160267
return
161268
}
162269

163-
out, _ := os.Create(finalPath + ".tmp")
164-
streamer, _ := crypto.NewGCMStreamer(key)
165-
if err := streamer.EncryptStream(out, src); err != nil {
270+
out, err := os.Create(finalPath + ".tmp")
271+
if err != nil {
272+
app.Logger.Error("Failed to create final file", "err", err)
273+
app.SendError(w, r, http.StatusInternalServerError)
274+
return
275+
}
276+
defer func() {
166277
out.Close()
167278
os.Remove(finalPath + ".tmp")
279+
}()
280+
281+
streamer, err := crypto.NewGCMStreamer(key)
282+
if err != nil {
283+
app.Logger.Error("Failed to create streamer", "err", err)
168284
app.SendError(w, r, http.StatusInternalServerError)
169285
return
170286
}
171-
out.Close()
172-
os.Rename(finalPath+".tmp", finalPath)
287+
288+
if err := streamer.EncryptStream(out, src); err != nil {
289+
app.Logger.Error("Encryption failed", "err", err)
290+
app.SendError(w, r, http.StatusInternalServerError)
291+
return
292+
}
293+
294+
if err := out.Close(); err != nil {
295+
app.Logger.Error("Failed to close final file", "err", err)
296+
app.SendError(w, r, http.StatusInternalServerError)
297+
return
298+
}
299+
300+
if err := os.Rename(finalPath+".tmp", finalPath); err != nil {
301+
app.Logger.Error("Failed to rename final file", "err", err)
302+
app.SendError(w, r, http.StatusInternalServerError)
303+
return
304+
}
305+
173306
app.RespondWithLink(w, r, key, filename)
174307
}

web/static/js/app.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ if (dropZone) {
3232
}
3333

3434
async function handleUpload(file) {
35+
const maxMB = parseInt(dropZone.dataset.maxMb);
36+
if (file.size > maxMB * 1024 * 1024) {
37+
$("idle-state").classList.add("hidden");
38+
$("result-state").classList.remove("hidden");
39+
$("result-state").innerHTML = `
40+
<div class="result-container">
41+
<div class="error-text">File too large (Max ${maxMB}MB)</div>
42+
<div class="reset-wrapper">
43+
<button class="reset-btn" onclick="resetUI()">Try again</button>
44+
</div>
45+
</div>`;
46+
return;
47+
}
48+
3549
$("idle-state").classList.add("hidden");
3650
$("busy-state").classList.remove("hidden");
3751
$("p-bar-container").classList.add("visible");

web/templates/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{{define "content"}}
2-
<main class="upload-area" id="drop-zone">
2+
<main class="upload-area" id="drop-zone" data-max-mb="{{.MaxMB}}">
33
<div id="idle-state">
44
<div class="upload-icon"></div>
55
<div class="upload-text">Click or drag to upload</div>

0 commit comments

Comments
 (0)