@@ -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 between 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