update tests

This commit is contained in:
Halil Durak
2026-07-29 12:50:55 +03:00
parent dff92a6fb5
commit 844c8d0846
4 changed files with 61 additions and 6 deletions

View File

@@ -274,6 +274,7 @@
const src = new FormData();
src.append('username', 'alice');
src.append('note', 'two\r\nlines');
src.append('a\tb', 'tabbed');
src.append('username', 'bob');
const req2 = new Request('https://example.com', { method: 'POST', body: src });
const fd2 = await req2.formData();
@@ -340,6 +341,7 @@
testing.expectEqual('alice', fd2.getAll('username')[0]);
testing.expectEqual('bob', fd2.getAll('username')[1]);
testing.expectEqual('two\r\nlines', fd2.get('note'));
testing.expectEqual('tabbed', fd2.get('a\tb'));
testing.expectEqual('y z', fd3.get('x'));

View File

@@ -99,6 +99,7 @@
const src = new FormData();
src.append('username', 'alice');
src.append('note', 'two\r\nlines');
src.append('a\tb', 'tabbed');
src.append('username', 'bob');
const res2 = new Response(src);
const fd2 = await res2.formData();
@@ -123,6 +124,28 @@
const promised6 = p6 instanceof Promise;
try { await p6; } catch (e) { rejected6 = e instanceof TypeError; }
// A null body with a form Content-Type acts as an empty byte sequence and
// resolves with an empty FormData.
const res7 = new Response(null, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
const fd7 = await res7.formData();
// A multipart Content-Type without a boundary rejects with a TypeError.
let rejected8 = false;
const res8 = new Response('x', {
headers: { 'Content-Type': 'multipart/form-data' },
});
try { await res8.formData(); } catch (e) { rejected8 = e instanceof TypeError; }
// A part with an unquoted RFC 5987 filename* parameter — which real
// servers emit — parses; the parameter itself is skipped.
const res9 = new Response(
'--B\r\nContent-Disposition: form-data; filename*=UTF-8\'\'a.jpg; name="n"\r\n\r\nv\r\n--B--\r\n',
{ headers: { 'Content-Type': 'multipart/form-data; boundary=B' } },
);
const fd9 = await res9.formData();
state.resolve();
await state.done(() => {
testing.expectEqual(true, fd1 instanceof FormData);
@@ -136,6 +159,7 @@
testing.expectEqual('alice', fd2.getAll('username')[0]);
testing.expectEqual('bob', fd2.getAll('username')[1]);
testing.expectEqual('two\r\nlines', fd2.get('note'));
testing.expectEqual('tabbed', fd2.get('a\tb'));
testing.expectEqual('y z', fd3.get('x'));
@@ -144,6 +168,11 @@
testing.expectEqual(true, promised6);
testing.expectEqual(true, rejected6);
testing.expectEqual(true, fd7 instanceof FormData);
testing.expectEqual(0, [...fd7].length);
testing.expectEqual(true, rejected8);
testing.expectEqual('v', fd9.get('n'));
});
</script>

View File

@@ -1314,6 +1314,9 @@ test "FormData: multipart round-trip" {
try src.appendText("username", "alice");
try src.appendText("username", "bob");
try src.appendText("a\"b\r\nc", "quoted \"value\"");
// HTAB in a name survives the trip: the encoder writes it raw into the
// Content-Disposition value and the parser accepts it (RFC 9110).
try src.appendText("a\tb", "tabbed");
var buf = std.Io.Writer.Allocating.init(allocator);
try src.write(.{
@@ -1328,10 +1331,11 @@ test "FormData: multipart round-trip" {
};
try fd.parseMultipart(frame._page, buf.written(), "BOUNDARY");
try testing.expectEqual(3, fd._entries.items.len);
try testing.expectEqual(4, fd._entries.items.len);
try testing.expectString("username", fd._entries.items[0].name.str());
try testing.expectString("alice", fd._entries.items[0].value.asString());
try testing.expectString("username", fd._entries.items[1].name.str());
try testing.expectString("bob", fd._entries.items[1].value.asString());
try testing.expectString("quoted \"value\"", fd.get(.wrap("a\"b\r\nc")).?);
try testing.expectString("tabbed", fd.get(.wrap("a\tb")).?);
}

View File

@@ -390,16 +390,22 @@ test "header_parser: parse HTTP header" {
try testing.expectString("def", header.value);
try testing.expectEqual(true, cursor.reachedEnd());
// Leading whitespaces of the value are skipped, trailing ones are kept.
cursor = initCursor("Key: value \r\n");
// Leading whitespaces of the value — spaces and HTABs — are skipped,
// trailing ones are kept.
cursor = initCursor("Key: \t value \t\r\n");
try header.parse(&cursor);
try testing.expectString("value ", header.value);
try testing.expectString("value \t", header.value);
// 0 length values are fine.
cursor = initCursor("Key:\r\n");
try header.parse(&cursor);
try testing.expectString("Key", header.key);
try testing.expectString("", header.value);
// HTAB is legal field content, including next to spaces.
cursor = initCursor("Key: a\tb\t c\r\n");
try header.parse(&cursor);
try testing.expectString("a\tb\t c", header.value);
}
test "header_parser: parse HTTP header incomplete" {
@@ -490,8 +496,8 @@ test "header_parser: cursor" {
try testing.expectEqual(true, cursor.reachedEnd());
try testing.expectString("", cursor.remaining());
// Skipping spaces stops at the end of the buffer.
cursor = initCursor(" ");
// Skipping spaces covers HTABs too and stops at the end of the buffer.
cursor = initCursor(" \t \t");
cursor.skipSpaces();
try testing.expectEqual(true, cursor.reachedEnd());
}
@@ -525,6 +531,20 @@ test "header_parser: match functions against scalar reference" {
}
}
// HTAB is the one valid byte below space; pair it with every byte at
// every position to prove it never disturbs its neighbor's verdict
// (a cross-lane borrow in the SWAR path would).
for (2..buf.len + 1) |len| {
for (0..len - 1) |pos| {
for (0..256) |c| {
@memset(buf[0..len], 'a');
buf[pos] = '\t';
buf[pos + 1] = @intCast(c);
try expectMatchesReference(buf[0..len]);
}
}
}
// Randomized: fully random buffers to exercise multiple invalid bytes per
// chunk at once.
var prng = std.Random.DefaultPrng.init(0x5eed);