@@ -207,9 +207,6 @@ pub const Cookie = struct {
207207 // this check is necessary, `std.mem.minMax` asserts len > 0
208208 return error .Empty ;
209209 }
210-
211- const host = (uri .host orelse return error .InvalidURI ).percent_encoded ;
212-
213210 {
214211 const min , const max = std .mem .minMax (u8 , str );
215212 if (min < 32 or max > 126 ) {
@@ -254,34 +251,10 @@ pub const Cookie = struct {
254251 samesite ,
255252 }, std .ascii .lowerString (& scrap , key_string )) orelse continue ;
256253
257- var value = if (sep == attribute .len ) "" else trim (attribute [sep + 1 .. ]);
254+ const value = if (sep == attribute .len ) "" else trim (attribute [sep + 1 .. ]);
258255 switch (key ) {
259- .path = > {
260- // path attribute value either begins with a '/' or we
261- // ignore it and use the "default-path" algorithm
262- if (value .len > 0 and value [0 ] == '/' ) {
263- path = value ;
264- }
265- },
266- .domain = > {
267- if (value .len == 0 ) {
268- continue ;
269- }
270- if (value [0 ] == '.' ) {
271- // leading dot is ignored
272- value = value [1.. ];
273- }
274-
275- if (std .mem .indexOfScalarPos (u8 , value , 0 , '.' ) == null and std .ascii .eqlIgnoreCase ("localhost" , value ) == false ) {
276- // can't set a cookie for a TLD
277- return error .InvalidDomain ;
278- }
279-
280- if (std .mem .endsWith (u8 , host , value ) == false ) {
281- return error .InvalidDomain ;
282- }
283- domain = value ; // Domain is made lower case after it has relocated to the arena
284- },
256+ .path = > path = value ,
257+ .domain = > domain = value ,
285258 .secure = > secure = true ,
286259 .@"max-age" = > max_age = std .fmt .parseInt (i64 , value , 10 ) catch continue ,
287260 .expires = > expires = DateTime .parse (value , .rfc822 ) catch continue ,
@@ -301,20 +274,9 @@ pub const Cookie = struct {
301274 const aa = arena .allocator ();
302275 const owned_name = try aa .dupe (u8 , cookie_name );
303276 const owned_value = try aa .dupe (u8 , cookie_value );
304- const owned_path = if (path ) | p |
305- try aa .dupe (u8 , p )
306- else
307- try defaultPath (aa , uri .path .percent_encoded );
308-
309- const owned_domain = if (domain ) | d | blk : {
310- const s = try aa .alloc (u8 , d .len + 1 );
311- s [0 ] = '.' ;
312- @memcpy (s [1.. ], d );
313- break :blk s ;
314- } else blk : {
315- break :blk try aa .dupe (u8 , host ); // Sjors: Should subdomains be removed from host?
316- };
317- _ = toLower (owned_domain );
277+ const owned_path = try parse_path (aa , uri .path , path );
278+ const host = uri .host orelse return error .InvalidURI ;
279+ const owned_domain = try parse_domain (aa , host , domain );
318280
319281 var normalized_expires : ? i64 = null ;
320282 if (max_age ) | ma | {
@@ -339,6 +301,92 @@ pub const Cookie = struct {
339301 };
340302 }
341303
304+ pub fn parse_path (arena : Allocator , url_path : std.Uri.Component , explicit_path : ? []const u8 ) ! []const u8 {
305+ // path attribute value either begins with a '/' or we
306+ // ignore it and use the "default-path" algorithm
307+ if (explicit_path ) | path | {
308+ if (path .len > 0 and path [0 ] == '/' ) {
309+ return try arena .dupe (u8 , path );
310+ }
311+ }
312+
313+ // default-path
314+ const either = url_path .percent_encoded ;
315+ if (either .len == 0 or (either .len == 1 and either [0 ] == '/' )) {
316+ return "/" ;
317+ }
318+
319+ var owned_path : []const u8 = try percentEncode (arena , url_path , isPathChar );
320+ const last = std .mem .lastIndexOfScalar (u8 , owned_path [1.. ], '/' ) orelse {
321+ return "/" ;
322+ };
323+ return try arena .dupe (u8 , owned_path [0 .. last + 1 ]);
324+ }
325+
326+ pub fn parse_domain (arena : Allocator , url_host : std.Uri.Component , explicit_domain : ? []const u8 ) ! []const u8 {
327+ const encoded_host = try percentEncode (arena , url_host , isHostChar );
328+ _ = toLower (encoded_host );
329+
330+ if (explicit_domain ) | domain | {
331+ if (domain .len == 0 ) {
332+ const no_leading_dot = if (domain [0 ] == '.' ) domain [1.. ] else domain ;
333+
334+ var list = std .ArrayList (u8 ).init (arena );
335+ try list .ensureTotalCapacity (no_leading_dot .len + 1 ); // Expect no precents needed
336+ list .appendAssumeCapacity ('.' );
337+ try std .Uri .Component .percentEncode (list .writer (), no_leading_dot , isHostChar );
338+ var owned_domain : []u8 = list .items ; // @memory retains memory used before growing
339+ _ = toLower (owned_domain );
340+
341+ if (std .mem .eql (u8 , "localhost" , owned_domain [1.. ]) == false ) {
342+ // can't set a cookie for a TLD
343+ return error .InvalidDomain ;
344+ }
345+ if (std .mem .endsWith (u8 , encoded_host , owned_domain [1.. ]) == false ) {
346+ return error .InvalidDomain ;
347+ }
348+ return owned_domain ;
349+ }
350+ }
351+
352+ return encoded_host ; // default-domain
353+ }
354+
355+ // TODO when getting cookeis Note: Chrome does not apply rules like removing a leading `.` from the domain.
356+
357+ pub fn percentEncode (arena : Allocator , component : std.Uri.Component , comptime isValidChar : fn (u8 ) bool ) ! []u8 {
358+ switch (component ) {
359+ .raw = > | str | {
360+ var list = std .ArrayList (u8 ).init (arena );
361+ try list .ensureTotalCapacity (str .len ); // Expect no precents needed
362+ try std .Uri .Component .percentEncode (list .writer (), str , isValidChar );
363+ return list .items ; // @memory retains memory used before growing
364+ },
365+ .percent_encoded = > | str | {
366+ return try arena .dupe (u8 , str );
367+ },
368+ }
369+ }
370+
371+ pub fn isHostChar (c : u8 ) bool {
372+ return switch (c ) {
373+ 'A' ... 'Z' , 'a' ... 'z' , '0' ... '9' , '-' , '.' , '_' , '~' = > true ,
374+ '!' , '$' , '&' , '\' ' , '(' , ')' , '*' , '+' , ',' , ';' , '=' = > true ,
375+ ':' = > true ,
376+ '[' , ']' = > true ,
377+ else = > false ,
378+ };
379+ }
380+
381+ pub fn isPathChar (c : u8 ) bool {
382+ return switch (c ) {
383+ 'A' ... 'Z' , 'a' ... 'z' , '0' ... '9' , '-' , '.' , '_' , '~' = > true ,
384+ '!' , '$' , '&' , '\' ' , '(' , ')' , '*' , '+' , ',' , ';' , '=' = > true ,
385+ '/' , ':' , '@' = > true ,
386+ else = > false ,
387+ };
388+ }
389+
342390 fn parseNameValue (str : []const u8 ) ! struct { []const u8 , []const u8 , []const u8 } {
343391 const key_value_end = std .mem .indexOfScalarPos (u8 , str , 0 , ';' ) orelse str .len ;
344392 const rest = if (key_value_end == str .len ) "" else str [key_value_end + 1 .. ];
@@ -422,16 +470,6 @@ pub const PreparedUri = struct {
422470 secure : bool , // True if scheme is https
423471};
424472
425- fn defaultPath (allocator : Allocator , document_path : []const u8 ) ! []const u8 {
426- if (document_path .len == 0 or (document_path .len == 1 and document_path [0 ] == '/' )) {
427- return "/" ;
428- }
429- const last = std .mem .lastIndexOfScalar (u8 , document_path [1.. ], '/' ) orelse {
430- return "/" ;
431- };
432- return try allocator .dupe (u8 , document_path [0 .. last + 1 ]);
433- }
434-
435473fn trim (str : []const u8 ) []const u8 {
436474 return std .mem .trim (u8 , str , & std .ascii .whitespace );
437475}
0 commit comments