From 0634acdac4ea30abb5c6940d12bdd219715dde90 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 9 Oct 2025 12:03:14 +0300 Subject: [PATCH] add a fast path for validating cookie strings This prefers `suggestVectorLength` in order to pick a vector size; for cookie strings shorter than, say 64, this might cause it to fallback to slow path on architectures that support larger vector sizes (like AVX-512). We may also add checks for smaller vector sizes if desired in the future. --- src/browser/storage/cookie.zig | 61 ++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/src/browser/storage/cookie.zig b/src/browser/storage/cookie.zig index cd95fec6..3304ddcf 100644 --- a/src/browser/storage/cookie.zig +++ b/src/browser/storage/cookie.zig @@ -207,16 +207,7 @@ pub const Cookie = struct { // Duplicate attributes - use the last valid // Value-less attributes with a value? Ignore the value pub fn parse(allocator: Allocator, uri: *const std.Uri, str: []const u8) !Cookie { - if (str.len == 0) { - // this check is necessary, `std.mem.minMax` asserts len > 0 - return error.Empty; - } - { - const min, const max = std.mem.minMax(u8, str); - if (min < 32 or max > 126) { - return error.InvalidByteSequence; - } - } + try validateCookieString(str); const cookie_name, const cookie_value, const rest = parseNameValue(str) catch { return error.InvalidNameValue; @@ -321,6 +312,56 @@ pub const Cookie = struct { }; } + const ValidateCookieError = error{ Empty, InvalidByteSequence }; + + /// Returns an error if cookie str length is 0 + /// or contains characters between 32...126. + fn validateCookieString(str: []const u8) ValidateCookieError!void { + if (str.len == 0) { + return error.Empty; + } + + const vec_size_suggestion = std.simd.suggestVectorLength(u8); + var offset: usize = 0; + + // Fast path if possible. + if (comptime vec_size_suggestion) |size| { + while (str.len - offset >= size) : (offset += size) { + const Vec = @Vector(size, u8); + const space: Vec = @splat(32); + const tilde: Vec = @splat(126); + const chunk: Vec = str[offset..][0..size].*; + + // This creates a mask where invalid characters represented + // as ones and valid characters as zeros. We then bitCast this + // into an unsigned integer. If the integer is not equal to 0, + // we know that we've invalid characters in this chunk. + // @popCount can also be used but using integers are simpler. + const mask = (@intFromBool(chunk < space) | @intFromBool(chunk > tilde)); + const reduced: std.meta.Int(.unsigned, size) = @bitCast(mask); + + // Got match. + if (reduced != 0) { + return error.InvalidByteSequence; + } + } + + // Means str.len % size == 0; we also know str.len != 0. + // Cookie is valid. + if (offset == str.len) { + return; + } + } + + // Either remaining slice or the original if fast path not taken. + const slice = str[offset..]; + // Slow path. + const min, const max = std.mem.minMax(u8, slice); + if (min < 32 or max > 126) { + return error.InvalidByteSequence; + } + } + pub fn parsePath(arena: Allocator, uri: ?*const std.Uri, explicit_path: ?[]const u8) ![]const u8 { // path attribute value either begins with a '/' or we // ignore it and use the "default-path" algorithm