carlos / todomvc-bakeoff Public
fix(zig): never hand utf8Decode a malformed sequence when trimming titles
The backward scan in trimTitle passed whatever bytes followed a lead byte to std.unicode.utf8Decode, which asserts a whole sequence; a title like a%80%80%80%80 hit unreachable and aborted the server. Check the lead byte's length matches before decoding; add regression tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+11
−0
apps/zig/src/app.zig+11 −0
diff --git a/apps/zig/src/app.zig b/apps/zig/src/app.zig| index 579194f..26eed99 100644 |
| --- a/apps/zig/src/app.zig |
| +++ b/apps/zig/src/app.zig |
| @@ -215,6 +215,11 @@ fn trimTitle(s: []const u8) []const u8 { |
| while (end > start) { |
| var i = end - 1; |
| while (i > start and s[i] & 0xC0 == 0x80) i -= 1; |
| + // utf8Decode asserts its input is one whole sequence, so check the |
| + // lead byte claims exactly the bytes found; anything else is |
| + // invalid UTF-8 and ends the trim. |
| + const len = std.unicode.utf8ByteSequenceLength(s[i]) catch break; |
| + if (len != end - i) break; |
| const cp = std.unicode.utf8Decode(s[i..end]) catch break; |
| if (!isSpace(cp)) break; |
| end = i; |
| @@ -340,6 +345,12 @@ test trimTitle { |
| try std.testing.expectEqualStrings("a\u{a0}b", trimTitle("a\u{a0}b")); |
| try std.testing.expectEqualStrings("日本語", trimTitle("日本語")); |
| try std.testing.expectEqualStrings("\xff x", trimTitle("\xff x")); |
| + // Malformed UTF-8 is left alone, never decoded. |
| + try std.testing.expectEqualStrings("a\x80\x80\x80\x80", trimTitle("a\x80\x80\x80\x80")); |
| + try std.testing.expectEqualStrings("\x80\x80", trimTitle(" \x80\x80 ")); |
| + try std.testing.expectEqualStrings("x\xe2\x80", trimTitle("x\xe2\x80")); |
| + try std.testing.expectEqualStrings("x\xc3", trimTitle("x\xc3")); |
| + try std.testing.expectEqualStrings("\xf0\x80\x80\x80\x80", trimTitle("\xf0\x80\x80\x80\x80")); |
| } |
| test itemAction { |