fix(web): a body cut off mid-file is a 400; bidirectional controls are stripped from a title (second review)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+14
-1
@@ -152,6 +152,10 @@ func (a *App) uploadFailure(w http.ResponseWriter, r *http.Request, userID strin
|
|||||||
a.logger().Info("upload rejected", "reason", string(uploads.ReasonTooLarge))
|
a.logger().Info("upload rejected", "reason", string(uploads.ReasonTooLarge))
|
||||||
a.renderUploadsPage(w, r, userID, http.StatusRequestEntityTooLarge,
|
a.renderUploadsPage(w, r, userID, http.StatusRequestEntityTooLarge,
|
||||||
"This file is larger than the upload limit.")
|
"This file is larger than the upload limit.")
|
||||||
|
case errors.Is(err, io.ErrUnexpectedEOF):
|
||||||
|
// The sender hung up mid-file: theirs to retry, not a server fault.
|
||||||
|
a.logger().Info("upload rejected", "reason", "truncated")
|
||||||
|
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "The upload was cut off before it finished. Please try again.")
|
||||||
default:
|
default:
|
||||||
// Not err: it can carry the file's name, a spool path or a database value.
|
// Not err: it can carry the file's name, a spool path or a database value.
|
||||||
a.logger().Error("upload failed", "kind", errkind.Of(err))
|
a.logger().Error("upload failed", "kind", errkind.Of(err))
|
||||||
@@ -218,6 +222,12 @@ func titleFromFilename(name string) string {
|
|||||||
// uuidShape is the form of an upload id. Anything else cannot be one, so it is a 404.
|
// uuidShape is the form of an upload id. Anything else cannot be one, so it is a 404.
|
||||||
var uuidShape = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
var uuidShape = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
||||||
|
|
||||||
|
// isBidiControl reports the invisible characters that change text direction. Emoji joiners
|
||||||
|
// (ZWJ, ZWNJ) and variation selectors are not among them and are kept.
|
||||||
|
func isBidiControl(r rune) bool {
|
||||||
|
return r >= 0x202A && r <= 0x202E || r >= 0x2066 && r <= 0x2069 || r == 0x200E || r == 0x200F || r == 0x061C
|
||||||
|
}
|
||||||
|
|
||||||
// cleanTitle makes a title safe to store and show: invalid UTF-8 dropped, control
|
// cleanTitle makes a title safe to store and show: invalid UTF-8 dropped, control
|
||||||
// characters (NUL, newlines, escapes) turned into spaces, runs of space collapsed, trimmed
|
// characters (NUL, newlines, escapes) turned into spaces, runs of space collapsed, trimmed
|
||||||
// and clipped. A title is cosmetic; bad bytes in it must never fail an upload, and Postgres
|
// and clipped. A title is cosmetic; bad bytes in it must never fail an upload, and Postgres
|
||||||
@@ -225,7 +235,10 @@ var uuidShape = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4
|
|||||||
func cleanTitle(s string) string {
|
func cleanTitle(s string) string {
|
||||||
s = strings.ToValidUTF8(s, "")
|
s = strings.ToValidUTF8(s, "")
|
||||||
s = strings.Map(func(r rune) rune {
|
s = strings.Map(func(r rune) rune {
|
||||||
if unicode.IsControl(r) {
|
switch {
|
||||||
|
case isBidiControl(r):
|
||||||
|
return -1 // dropped: U+202E would reverse the rest of the title on its owner's page
|
||||||
|
case unicode.IsControl(r):
|
||||||
return ' '
|
return ' '
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
|
|||||||
Reference in New Issue
Block a user