Skip to content

Commit 1b462da

Browse files
authored
Merge pull request #1133 from lightpanda-io/nikneym/cookie-validation
Add a fast path for validating cookie strings
2 parents dc85c65 + 0794830 commit 1b462da

File tree

1 file changed

+51
-10
lines changed

1 file changed

+51
-10
lines changed

src/browser/storage/cookie.zig

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -207,16 +207,7 @@ pub const Cookie = struct {
207207
// Duplicate attributes - use the last valid
208208
// Value-less attributes with a value? Ignore the value
209209
pub fn parse(allocator: Allocator, uri: *const std.Uri, str: []const u8) !Cookie {
210-
if (str.len == 0) {
211-
// this check is necessary, `std.mem.minMax` asserts len > 0
212-
return error.Empty;
213-
}
214-
{
215-
const min, const max = std.mem.minMax(u8, str);
216-
if (min < 32 or max > 126) {
217-
return error.InvalidByteSequence;
218-
}
219-
}
210+
try validateCookieString(str);
220211

221212
const cookie_name, const cookie_value, const rest = parseNameValue(str) catch {
222213
return error.InvalidNameValue;
@@ -321,6 +312,56 @@ pub const Cookie = struct {
321312
};
322313
}
323314

315+
const ValidateCookieError = error{ Empty, InvalidByteSequence };
316+
317+
/// Returns an error if cookie str length is 0
318+
/// or contains characters outside of the ascii range 32...126.
319+
fn validateCookieString(str: []const u8) ValidateCookieError!void {
320+
if (str.len == 0) {
321+
return error.Empty;
322+
}
323+
324+
const vec_size_suggestion = std.simd.suggestVectorLength(u8);
325+
var offset: usize = 0;
326+
327+
// Fast path if possible.
328+
if (comptime vec_size_suggestion) |size| {
329+
while (str.len - offset >= size) : (offset += size) {
330+
const Vec = @Vector(size, u8);
331+
const space: Vec = @splat(32);
332+
const tilde: Vec = @splat(126);
333+
const chunk: Vec = str[offset..][0..size].*;
334+
335+
// This creates a mask where invalid characters represented
336+
// as ones and valid characters as zeros. We then bitCast this
337+
// into an unsigned integer. If the integer is not equal to 0,
338+
// we know that we've invalid characters in this chunk.
339+
// @popCount can also be used but using integers are simpler.
340+
const mask = (@intFromBool(chunk < space) | @intFromBool(chunk > tilde));
341+
const reduced: std.meta.Int(.unsigned, size) = @bitCast(mask);
342+
343+
// Got match.
344+
if (reduced != 0) {
345+
return error.InvalidByteSequence;
346+
}
347+
}
348+
349+
// Means str.len % size == 0; we also know str.len != 0.
350+
// Cookie is valid.
351+
if (offset == str.len) {
352+
return;
353+
}
354+
}
355+
356+
// Either remaining slice or the original if fast path not taken.
357+
const slice = str[offset..];
358+
// Slow path.
359+
const min, const max = std.mem.minMax(u8, slice);
360+
if (min < 32 or max > 126) {
361+
return error.InvalidByteSequence;
362+
}
363+
}
364+
324365
pub fn parsePath(arena: Allocator, uri: ?*const std.Uri, explicit_path: ?[]const u8) ![]const u8 {
325366
// path attribute value either begins with a '/' or we
326367
// ignore it and use the "default-path" algorithm

0 commit comments

Comments
 (0)