@@ -207,9 +207,6 @@ pub const Cookie = struct {
207
207
// this check is necessary, `std.mem.minMax` asserts len > 0
208
208
return error .Empty ;
209
209
}
210
-
211
- const host = (uri .host orelse return error .InvalidURI ).percent_encoded ;
212
-
213
210
{
214
211
const min , const max = std .mem .minMax (u8 , str );
215
212
if (min < 32 or max > 126 ) {
@@ -254,34 +251,10 @@ pub const Cookie = struct {
254
251
samesite ,
255
252
}, std .ascii .lowerString (& scrap , key_string )) orelse continue ;
256
253
257
- var value = if (sep == attribute .len ) "" else trim (attribute [sep + 1 .. ]);
254
+ const value = if (sep == attribute .len ) "" else trim (attribute [sep + 1 .. ]);
258
255
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 ,
285
258
.secure = > secure = true ,
286
259
.@"max-age" = > max_age = std .fmt .parseInt (i64 , value , 10 ) catch continue ,
287
260
.expires = > expires = DateTime .parse (value , .rfc822 ) catch continue ,
@@ -301,20 +274,9 @@ pub const Cookie = struct {
301
274
const aa = arena .allocator ();
302
275
const owned_name = try aa .dupe (u8 , cookie_name );
303
276
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 );
318
280
319
281
var normalized_expires : ? i64 = null ;
320
282
if (max_age ) | ma | {
@@ -339,6 +301,92 @@ pub const Cookie = struct {
339
301
};
340
302
}
341
303
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 .indexOfScalarPos (u8 , owned_domain , 1 , '.' ) == null and 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
+
342
390
fn parseNameValue (str : []const u8 ) ! struct { []const u8 , []const u8 , []const u8 } {
343
391
const key_value_end = std .mem .indexOfScalarPos (u8 , str , 0 , ';' ) orelse str .len ;
344
392
const rest = if (key_value_end == str .len ) "" else str [key_value_end + 1 .. ];
@@ -422,16 +470,6 @@ pub const PreparedUri = struct {
422
470
secure : bool , // True if scheme is https
423
471
};
424
472
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
-
435
473
fn trim (str : []const u8 ) []const u8 {
436
474
return std .mem .trim (u8 , str , & std .ascii .whitespace );
437
475
}
0 commit comments