Files: a sweep of the edges
Found by walking every handler and the UI with odd inputs, then a battery of requests against the live hub. Fixed: - A save over the 8 MiB cap was cut short in place: the file was truncated, then the first 8 MiB written, and the caller told 204. Now the body is read whole first and a body over the cap is a 413 with the file untouched. - A link to a file showed the link's own time; now the file's, so the row reads right and the preview key follows the file. - Reading a link to nothing answered 200 with an empty body (the open failed after the headers in root mode); now a 404 that names the target. - A play request with an unknown codec or size was answered by inspecting the file first, so an audio-only file said "no video stream" instead of what was wrong; the choice is checked before any work, and is a 400. - A stream that ended in failure was handed back to the next identical request for ten minutes; a failed one is now dropped and a fresh one made. - A player that left while its stream was still finding a first segment made the loop try the next recipe; it stops. A stream closed mid-start is not started again. - Ending a root-mode ffmpeg could wait forever: the daemon cannot signal root's processes, so the wait is bounded and the helper's own grace does the rest. - A page going away (a phone closing the tab) releases its stream by a beacon, rather than ten minutes later by the sweeper. - The path box resolves like a shell — relative to the folder shown, . and .. applied, trailing slashes dropped — in the tab and in the Move picker, with a test. A typed path that is a file opens it; a remembered folder that is gone falls back to / once. - A dropped folder arrived as a nameless empty file; it is refused with a word. The upload toast counts what was actually uploaded. - The viewer names a device, pipe or socket, and a dangling link, instead of calling them binary files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
d200e5dc2e
commit
1038c010fa
+13
-1
@@ -317,6 +317,7 @@ func (s *Server) routes() *http.ServeMux {
|
||||
m.HandleFunc("POST /api/files/play", s.auth(s.playStart))
|
||||
m.HandleFunc("GET /api/files/play/{id}", s.auth(s.playStatus))
|
||||
m.HandleFunc("DELETE /api/files/play/{id}", s.auth(s.playStop))
|
||||
m.HandleFunc("POST /api/files/play/{id}/close", s.auth(s.playStop))
|
||||
m.HandleFunc("GET /api/files/play/{id}/{file}", s.auth(s.playFile))
|
||||
m.HandleFunc("GET /api/hardware", s.auth(s.hardwareReport))
|
||||
m.HandleFunc("GET /api/network", s.auth(s.networkOverview))
|
||||
@@ -1931,6 +1932,10 @@ func (s *Server) filesRead(w http.ResponseWriter, r *http.Request) {
|
||||
jsonErr(w, 400, "not a regular file (device, pipe or socket)")
|
||||
return
|
||||
}
|
||||
if in.Broken {
|
||||
jsonErr(w, 404, "a link to nothing: "+in.Target)
|
||||
return
|
||||
}
|
||||
mx, _ := strconv.ParseInt(r.URL.Query().Get("max"), 10, 64)
|
||||
if mx > 0 && in.Size > mx {
|
||||
jsonErr(w, 413, "file too large")
|
||||
@@ -1989,7 +1994,14 @@ func (s *Server) filesWrite(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := fsys.Write(r.Context(), p, io.LimitReader(r.Body, 8<<20)); err != nil {
|
||||
// Whole in memory first: the write truncates the file, so a body over the cap must be
|
||||
// refused before that, not cut short after it.
|
||||
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 8<<20))
|
||||
if err != nil {
|
||||
jsonErr(w, 413, "too large to save here (8 MiB at most)")
|
||||
return
|
||||
}
|
||||
if err := fsys.Write(r.Context(), p, bytes.NewReader(body)); err != nil {
|
||||
fsErr(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user