From f7f95ad76306f57282bcb6ceb42b3b576c71350a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Wed, 5 Aug 2026 23:40:57 +0200 Subject: [PATCH 1/4] Add percent_encode_test to test percent --- tests/core/net/test_core_net.odin | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index fbca15bb1..6a6111a29 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -304,7 +304,7 @@ client_sends_server_data :: proc(t: ^testing.T) { r.length, r.err = net.recv_tcp(client, r.data[:]) return } - + thread_data := [2]Thread_Data{} wg: sync.Wait_Group @@ -313,7 +313,7 @@ client_sends_server_data :: proc(t: ^testing.T) { thread_data[0].t = t thread_data[0].wg = &wg thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context) - + sync.wait_group_wait(&wg) sync.wait_group_add(&wg, 2) @@ -525,6 +525,31 @@ join_url_test :: proc(t: ^testing.T) { } } +@test +percent_encode_test :: proc(t: ^testing.T) { + test_cases := []struct{input, expected: string} { + // Bytes < 0x10 must be zero-padded to two hex digits + {"\n", "%0A"}, + {"\t", "%09"}, + {"\r", "%0D"}, + {"a\nb", "a%0Ab"}, + {"\x00", "%00"}, + + // Bytes >= 0x10 + {" ", "%20"}, + {"๐Ÿ˜ƒ", "%F0%9F%98%83"}, + + // Unreserved characters pass through unescaped + {"AZaz09-_.~", "AZaz09-_.~"}, + } + + for test in test_cases { + encoded := net.percent_encode(test.input) + defer delete(encoded) + testing.expectf(t, encoded == test.expected, "Expected `net.percent_encode(%q)` to return %q, got %q", test.input, test.expected, encoded) + } +} + @test test_udp_echo :: proc(t: ^testing.T) { endpoint := net.Endpoint{address=net.IP4_Address{127, 0, 0, 1}, port=0} From c7af2576f56ee31730fb41c96c6711c35c06321e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Wed, 5 Aug 2026 23:45:34 +0200 Subject: [PATCH 2/4] Zero-pad percent_encode escapes to two uppercase hex digits --- core/net/url.odin | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/net/url.odin b/core/net/url.odin index 29028b16c..1a8028ad0 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -114,6 +114,8 @@ join_url :: proc(scheme, host, path: string, queries: map[string]string, fragmen } percent_encode :: proc(s: string, allocator := context.allocator) -> string { + HEX_DIGITS_UPPER := "0123456789ABCDEF" // NOTE(michtesar): RFC 3986 ยง2.1 + b := strings.builder_make(allocator) strings.builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape. @@ -124,10 +126,9 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string { case: bytes, n := utf8.encode_rune(ch) for byte in bytes[:n] { - buf: [2]u8 = --- - t := strconv.write_int(buf[:], i64(byte), 16) - strings.write_rune(&b, '%') - strings.write_string(&b, t) + strings.write_byte(&b, '%') + strings.write_byte(&b, HEX_DIGITS_UPPER[byte >> 4]) + strings.write_byte(&b, HEX_DIGITS_UPPER[byte & 0xF]) } } } From 56d8293ae0798fea7538a2c52b2ee7e49632c54b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Wed, 5 Aug 2026 23:58:33 +0200 Subject: [PATCH 3/4] Restore formatting and remove unused import --- core/net/url.odin | 1 - tests/core/net/test_core_net.odin | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/net/url.odin b/core/net/url.odin index 1a8028ad0..b5bd6d917 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -19,7 +19,6 @@ package net */ import "core:strings" -import "core:strconv" import "core:unicode/utf8" import "core:encoding/hex" diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 6a6111a29..f360f95cd 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -304,7 +304,7 @@ client_sends_server_data :: proc(t: ^testing.T) { r.length, r.err = net.recv_tcp(client, r.data[:]) return } - + thread_data := [2]Thread_Data{} wg: sync.Wait_Group @@ -313,7 +313,7 @@ client_sends_server_data :: proc(t: ^testing.T) { thread_data[0].t = t thread_data[0].wg = &wg thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context) - + sync.wait_group_wait(&wg) sync.wait_group_add(&wg, 2) From a1cb87c1c4369d057943118a884fced687db1f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Thu, 6 Aug 2026 07:14:50 +0200 Subject: [PATCH 4/4] Add decode roundtrip test --- tests/core/net/test_core_net.odin | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index f360f95cd..b0cf57cad 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -547,6 +547,11 @@ percent_encode_test :: proc(t: ^testing.T) { encoded := net.percent_encode(test.input) defer delete(encoded) testing.expectf(t, encoded == test.expected, "Expected `net.percent_encode(%q)` to return %q, got %q", test.input, test.expected, encoded) + + decoded, ok := net.percent_decode(encoded) + defer delete(decoded) + testing.expectf(t, ok, "Expected `net.percent_decode(%q)` to succeed", encoded) + testing.expectf(t, decoded == test.input, "Expected percent-encoding roundtrip for %q, got %q", test.input, decoded) } }