@@ -66,87 +66,33 @@ pub const Jar = struct {
6666 }
6767 }
6868
69- pub fn forRequest (self : * Jar , target_uri : * const Uri , writer : anytype , opts : LookupOpts ) ! void {
70- const target_path = target_uri .path .percent_encoded ;
71- const target_host = (target_uri .host orelse return error .InvalidURI ).percent_encoded ;
72-
73- const same_site = try areSameSite (opts .origin_uri , target_host );
74- const is_secure = std .mem .eql (u8 , target_uri .scheme , "https" );
75-
76- var i : usize = 0 ;
77- var cookies = self .cookies .items ;
78- const navigation = opts .navigation ;
79- const request_time = opts .request_time orelse std .time .timestamp ();
80-
81- var first = true ;
82- while (i < cookies .len ) {
83- const cookie = & cookies [i ];
84-
85- if (isCookieExpired (cookie , request_time )) {
86- cookie .deinit ();
87- _ = self .cookies .swapRemove (i );
88- // don't increment i !
89- continue ;
69+ pub fn removeExpired (self : * Jar , request_time : ? i64 ) void {
70+ if (self .cookies .items .len == 0 ) return ;
71+ const time = request_time orelse std .time .timestamp ();
72+ var i : usize = self .cookies .items .len - 1 ;
73+ while (i > 0 ) {
74+ defer i -= 1 ;
75+ const cookie = & self .cookies .items [i ];
76+ if (isCookieExpired (cookie , time )) {
77+ self .cookies .swapRemove (i ).deinit ();
9078 }
91- i += 1 ;
79+ }
80+ }
9281
93- if (is_secure == false and cookie .secure ) {
94- // secure cookie can only be sent over HTTPs
95- continue ;
96- }
82+ pub fn forRequest (self : * Jar , target_uri : * const Uri , writer : anytype , opts : LookupOpts ) ! void {
83+ const target = PreparedUri {
84+ .host = (target_uri .host orelse return error .InvalidURI ).percent_encoded ,
85+ .path = target_uri .path .percent_encoded ,
86+ .secure = std .mem .eql (u8 , target_uri .scheme , "https" ),
87+ };
88+ const same_site = try areSameSite (opts .origin_uri , target .host );
9789
98- if (same_site == false ) {
99- // If we aren't on the "same site" (matching 2nd level domain
100- // taking into account public suffix list), then the cookie
101- // can only be sent if cookie.same_site == .none, or if
102- // we're navigating to (as opposed to, say, loading an image)
103- // and cookie.same_site == .lax
104- switch (cookie .same_site ) {
105- .strict = > continue ,
106- .lax = > if (navigation == false ) continue ,
107- .none = > {},
108- }
109- }
90+ removeExpired (self , opts .request_time );
11091
111- {
112- const domain = cookie .domain ;
113- if (domain [0 ] == '.' ) {
114- // When a Set-Cookie header has a Domain attribute
115- // Then we will _always_ prefix it with a dot, extending its
116- // availability to all subdomains (yes, setting the Domain
117- // attributes EXPANDS the domains which the cookie will be
118- // sent to, to always include all subdomains).
119- if (std .mem .eql (u8 , target_host , domain [1.. ]) == false and std .mem .endsWith (u8 , target_host , domain ) == false ) {
120- continue ;
121- }
122- } else if (std .mem .eql (u8 , target_host , domain ) == false ) {
123- // When the Domain attribute isn't specific, then the cookie
124- // is only sent on an exact match.
125- continue ;
126- }
127- }
92+ var first = true ;
93+ for (self .cookies .items ) | * cookie | {
94+ if (! cookie .appliesTo (& target , same_site , opts .navigation )) continue ;
12895
129- {
130- const path = cookie .path ;
131- if (path [path .len - 1 ] == '/' ) {
132- // If our cookie has a trailing slash, we can only match is
133- // the target path is a perfix. I.e., if our path is
134- // /doc/ we can only match /doc/*
135- if (std .mem .startsWith (u8 , target_path , path ) == false ) {
136- continue ;
137- }
138- } else {
139- // Our cookie path is something like /hello
140- if (std .mem .startsWith (u8 , target_path , path ) == false ) {
141- // The target path has to either be /hello (it isn't)
142- continue ;
143- } else if (target_path .len < path .len or (target_path .len > path .len and target_path [path .len ] != '/' )) {
144- // Or it has to be something like /hello/* (it isn't)
145- // it isn't!
146- continue ;
147- }
148- }
149- }
15096 // we have a match!
15197 if (first ) {
15298 first = false ;
@@ -180,44 +126,6 @@ pub const Jar = struct {
180126 }
181127};
182128
183- // pub const CookieList = struct {
184- // _cookies: std.ArrayListUnmanaged(*const Cookie) = .{},
185-
186- // pub fn deinit(self: *CookieList, allocator: Allocator) void {
187- // self._cookies.deinit(allocator);
188- // }
189-
190- // pub fn cookies(self: *const CookieList) []*const Cookie {
191- // return self._cookies.items;
192- // }
193-
194- // pub fn len(self: *const CookieList) usize {
195- // return self._cookies.items.len;
196- // }
197-
198- // pub fn write(self: *const CookieList, writer: anytype) !void {
199- // const all = self._cookies.items;
200- // if (all.len == 0) {
201- // return;
202- // }
203- // try writeCookie(all[0], writer);
204- // for (all[1..]) |cookie| {
205- // try writer.writeAll("; ");
206- // try writeCookie(cookie, writer);
207- // }
208- // }
209-
210- // fn writeCookie(cookie: *const Cookie, writer: anytype) !void {
211- // if (cookie.name.len > 0) {
212- // try writer.writeAll(cookie.name);
213- // try writer.writeByte('=');
214- // }
215- // if (cookie.value.len > 0) {
216- // try writer.writeAll(cookie.value);
217- // }
218- // }
219- // };
220-
221129fn isCookieExpired (cookie : * const Cookie , now : i64 ) bool {
222130 const ce = cookie .expires orelse return false ;
223131 return ce <= now ;
@@ -447,6 +355,71 @@ pub const Cookie = struct {
447355 const value = trim (str [sep + 1 .. key_value_end ]);
448356 return .{ name , value , rest };
449357 }
358+
359+ pub fn appliesTo (self : * const Cookie , url : * const PreparedUri , same_site : bool , navigation : bool ) bool {
360+ if (url .secure == false and self .secure ) {
361+ // secure cookie can only be sent over HTTPs
362+ return false ;
363+ }
364+
365+ if (same_site == false ) {
366+ // If we aren't on the "same site" (matching 2nd level domain
367+ // taking into account public suffix list), then the cookie
368+ // can only be sent if cookie.same_site == .none, or if
369+ // we're navigating to (as opposed to, say, loading an image)
370+ // and cookie.same_site == .lax
371+ switch (self .same_site ) {
372+ .strict = > return false ,
373+ .lax = > if (navigation == false ) return false ,
374+ .none = > {},
375+ }
376+ }
377+
378+ {
379+ if (self .domain [0 ] == '.' ) {
380+ // When a Set-Cookie header has a Domain attribute
381+ // Then we will _always_ prefix it with a dot, extending its
382+ // availability to all subdomains (yes, setting the Domain
383+ // attributes EXPANDS the domains which the cookie will be
384+ // sent to, to always include all subdomains).
385+ if (std .mem .eql (u8 , url .host , self .domain [1.. ]) == false and std .mem .endsWith (u8 , url .host , self .domain ) == false ) {
386+ return false ;
387+ }
388+ } else if (std .mem .eql (u8 , url .host , self .domain ) == false ) {
389+ // When the Domain attribute isn't specific, then the cookie
390+ // is only sent on an exact match.
391+ return false ;
392+ }
393+ }
394+
395+ {
396+ if (self .path [self .path .len - 1 ] == '/' ) {
397+ // If our cookie has a trailing slash, we can only match is
398+ // the target path is a perfix. I.e., if our path is
399+ // /doc/ we can only match /doc/*
400+ if (std .mem .startsWith (u8 , url .path , self .path ) == false ) {
401+ return false ;
402+ }
403+ } else {
404+ // Our cookie path is something like /hello
405+ if (std .mem .startsWith (u8 , url .path , self .path ) == false ) {
406+ // The target path has to either be /hello (it isn't)
407+ return false ;
408+ } else if (url .path .len < self .path .len or (url .path .len > self .path .len and url .path [self .path .len ] != '/' )) {
409+ // Or it has to be something like /hello/* (it isn't)
410+ // it isn't!
411+ return false ;
412+ }
413+ }
414+ }
415+ return true ;
416+ }
417+ };
418+
419+ pub const PreparedUri = struct {
420+ host : []const u8 , // Percent encoded, lower case
421+ path : []const u8 , // Percent encoded
422+ secure : bool , // True if scheme is https
450423};
451424
452425fn defaultPath (allocator : Allocator , document_path : []const u8 ) ! []const u8 {
@@ -675,40 +648,6 @@ test "Jar: forRequest" {
675648 // the 'global2' cookie
676649}
677650
678- // test "CookieList: write" {
679- // var arr: std.ArrayListUnmanaged(u8) = .{};
680- // defer arr.deinit(testing.allocator);
681-
682- // var cookie_list = CookieList{};
683- // defer cookie_list.deinit(testing.allocator);
684-
685- // const c1 = try Cookie.parse(testing.allocator, &test_uri, "cookie_name=cookie_value");
686- // defer c1.deinit();
687- // {
688- // try cookie_list._cookies.append(testing.allocator, &c1);
689- // try cookie_list.write(arr.writer(testing.allocator));
690- // try testing.expectEqual("cookie_name=cookie_value", arr.items);
691- // }
692-
693- // const c2 = try Cookie.parse(testing.allocator, &test_uri, "x84");
694- // defer c2.deinit();
695- // {
696- // arr.clearRetainingCapacity();
697- // try cookie_list._cookies.append(testing.allocator, &c2);
698- // try cookie_list.write(arr.writer(testing.allocator));
699- // try testing.expectEqual("cookie_name=cookie_value; x84", arr.items);
700- // }
701-
702- // const c3 = try Cookie.parse(testing.allocator, &test_uri, "nope=");
703- // defer c3.deinit();
704- // {
705- // arr.clearRetainingCapacity();
706- // try cookie_list._cookies.append(testing.allocator, &c3);
707- // try cookie_list.write(arr.writer(testing.allocator));
708- // try testing.expectEqual("cookie_name=cookie_value; x84; nope=", arr.items);
709- // }
710- // }
711-
712651test "Cookie: parse key=value" {
713652 try expectError (error .Empty , null , "" );
714653 try expectError (error .InvalidByteSequence , null , &.{ 'a' , 30 , '=' , 'b' });
0 commit comments