diff --git a/internal/web/uploads_badinput_handler_test.go b/internal/web/uploads_badinput_handler_test.go index f82b6ac..376d935 100644 --- a/internal/web/uploads_badinput_handler_test.go +++ b/internal/web/uploads_badinput_handler_test.go @@ -3,6 +3,7 @@ package web_test import ( "bytes" "context" + "fmt" "io" "log/slog" "net/http" @@ -63,37 +64,22 @@ func TestUploadPost_AMalformedMultipartHeaderIsA400AndItsTextIsNotLogged(t *test assert.NotContains(t, buf.String(), "zebrakaka", "a file name is private content") } -func TestUploadPost_ABodyCutOffInsideTheFilePartIsA400NotA500(t *testing.T) { - app, sub := newUploadApp(t, audioOK, 200<<20) - req := uploadReq(t, "t", "a.m4a", bytes.Repeat([]byte("a"), 4096)) - raw, err := io.ReadAll(req.Body) - require.NoError(t, err) - req.Body = io.NopCloser(bytes.NewReader(raw[:len(raw)-2000])) // the sender hung up mid-file - buf := &lockedBuf{} - app.Log = slog.New(slog.NewTextHandler(buf, nil)) - - rec := do(t, app, req) - - assert.Equal(t, http.StatusBadRequest, rec.Code, "a connection that drops mid-upload is the sender's to retry, not a server fault") - assert.Empty(t, sub.jobs) - assert.NotContains(t, buf.String(), "level=ERROR") -} - -func TestUploadPost_BidirectionalControlCharactersAreStrippedFromATitle(t *testing.T) { +func TestUploadPost_EveryBidirectionalControlCharacterIsStrippedFromATitle(t *testing.T) { // U+202E reverses the text that follows it, so a title could render reversed on its - // owner's own page (second adversarial review). - app, sub := newUploadApp(t, audioOK, 200<<20) + // owner's own page (second adversarial review). Every one of the twelve is pinned. + controls := []rune{0x202A, 0x202B, 0x202C, 0x202D, 0x202E, 0x2066, 0x2067, 0x2068, 0x2069, 0x200E, 0x200F, 0x061C} + for _, c := range controls { + t.Run(fmt.Sprintf("U+%04X", c), func(t *testing.T) { + app, sub := newUploadApp(t, audioOK, 200<<20) - rec := do(t, app, uploadReq(t, "Sprint \u202Ereview \u2066x\u2069 \u200Fok", "a.m4a", []byte("x"))) + rec := do(t, app, uploadReq(t, "Sprint "+string(c)+"review", "a.m4a", []byte("x"))) - require.Equal(t, http.StatusSeeOther, rec.Code) - var title string - require.NoError(t, rawPool(t).QueryRow(context.Background(), `SELECT title FROM uploaded_transcripts WHERE id = $1`, sub.jobs[0].UploadID).Scan(&title)) - for _, r := range []string{"\u202E", "\u2066", "\u2069", "\u200F"} { - assert.NotContains(t, title, r) + require.Equal(t, http.StatusSeeOther, rec.Code) + var title string + require.NoError(t, rawPool(t).QueryRow(context.Background(), `SELECT title FROM uploaded_transcripts WHERE id = $1`, sub.jobs[0].UploadID).Scan(&title)) + assert.Equal(t, "Sprintreview", title, "dropped, not replaced by a space: a space would leave a visible gap") + }) } - assert.Contains(t, title, "Sprint") - assert.Contains(t, title, "review") } func TestUploadPost_SwedishLettersAndEmojiSurviveTheTitleCleaning(t *testing.T) {