From 29b82dd80c46de16f5aaa405e51b2148831e2061 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 16 Aug 2026 14:03:18 -0700 Subject: [PATCH] config: preserve bytes in hex escapes Fixes #13855 Make the config string parser preserve hexadecimal escapes as bytes. Previously all escaped values were encoded as Unicode codepoints. --- src/config/string.zig | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/config/string.zig b/src/config/string.zig index 450799373..8d4fc7b45 100644 --- a/src/config/string.zig +++ b/src/config/string.zig @@ -20,16 +20,23 @@ pub fn parse(out: []u8, bytes: []const u8) ![]u8 { continue; } + // Zig's \xNN syntax represents a single byte, while the other + // escape sequences represent Unicode codepoints. + const is_byte_escape = src_i + 1 < bytes.len and + bytes[src_i + 1] == 'x'; + // Parse the escape sequence switch (std.zig.string_literal.parseEscapeSequence( bytes, &src_i, )) { .failure => return error.InvalidString, - .success => |cp| dst_i += try std.unicode.utf8Encode( - cp, - out[dst_i..], - ), + .success => |cp| if (is_byte_escape) { + out[dst_i] = @intCast(cp); + dst_i += 1; + } else { + dst_i += try std.unicode.utf8Encode(cp, out[dst_i..]); + }, } } @@ -100,6 +107,15 @@ test "parse: escapes" { } } +test "parse: hex escapes are bytes" { + var buf: [128]u8 = undefined; + const result = try parse( + &buf, + "\\xe6\\x97\\xa5\\xe6\\x9c\\xac\\xe8\\xaa\\x9e", + ); + try std.testing.expectEqualStrings("日本語", result); +} + test "codepointIterator: empty" { var it = codepointIterator(""); try std.testing.expectEqual(null, try it.next());