Skip to content

Commit 0110ac6

Browse files
Merge pull request #490 from karlseguin/cookies
Add Cookie support to browser & xhr requests
2 parents 5bfa44b + d21821a commit 0110ac6

File tree

8 files changed

+351
-247
lines changed

8 files changed

+351
-247
lines changed

src/browser/browser.zig

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const Location = @import("../html/location.zig").Location;
4343

4444
const storage = @import("../storage/storage.zig");
4545

46-
const HttpClient = @import("../http/client.zig").Client;
46+
const http = @import("../http/client.zig");
4747
const UserContext = @import("../user_context.zig").UserContext;
4848

4949
const polyfill = @import("../polyfill/polyfill.zig");
@@ -60,7 +60,7 @@ pub const Browser = struct {
6060
app: *App,
6161
session: ?*Session,
6262
allocator: Allocator,
63-
http_client: *HttpClient,
63+
http_client: *http.Client,
6464
session_pool: SessionPool,
6565
page_arena: std.heap.ArenaAllocator,
6666

@@ -130,10 +130,12 @@ pub const Session = struct {
130130

131131
window: Window,
132132

133-
// TODO move the shed to the browser?
133+
// TODO move the shed/jar to the browser?
134134
storage_shed: storage.Shed,
135+
cookie_jar: storage.CookieJar,
136+
135137
page: ?Page = null,
136-
http_client: *HttpClient,
138+
http_client: *http.Client,
137139

138140
jstypes: [Types.len]usize = undefined,
139141

@@ -148,6 +150,7 @@ pub const Session = struct {
148150
.http_client = browser.http_client,
149151
.storage_shed = storage.Shed.init(allocator),
150152
.arena = std.heap.ArenaAllocator.init(allocator),
153+
.cookie_jar = storage.CookieJar.init(allocator),
151154
.window = Window.create(null, .{ .agent = user_agent }),
152155
};
153156

@@ -183,6 +186,7 @@ pub const Session = struct {
183186
}
184187
self.env.deinit();
185188
self.arena.deinit();
189+
self.cookie_jar.deinit();
186190
self.storage_shed.deinit();
187191
}
188192

@@ -371,14 +375,16 @@ pub const Page = struct {
371375
} });
372376

373377
// load the data
374-
var request = try self.session.http_client.request(.GET, self.uri);
378+
var request = try self.newHTTPRequest(.GET, self.uri, .{ .navigation = true });
375379
defer request.deinit();
376-
var response = try request.sendSync(.{});
377380

381+
var response = try request.sendSync(.{});
378382
const header = response.header;
383+
try self.session.cookie_jar.populateFromResponse(self.uri, &header);
384+
379385
log.info("GET {any} {d}", .{ self.uri, header.status });
380386

381-
const ct = response.header.get("content-type") orelse {
387+
const ct = header.get("content-type") orelse {
382388
// no content type in HTTP headers.
383389
// TODO try to sniff mime type from the body.
384390
log.info("no content-type HTTP header", .{});
@@ -439,7 +445,9 @@ pub const Page = struct {
439445

440446
// replace the user context document with the new one.
441447
try session.env.setUserContext(.{
448+
.uri = self.uri,
442449
.document = html_doc,
450+
.cookie_jar = @ptrCast(&self.session.cookie_jar),
443451
.http_client = @ptrCast(self.session.http_client),
444452
});
445453

@@ -614,13 +622,19 @@ pub const Page = struct {
614622
}
615623
const u = try std.Uri.resolve_inplace(self.uri, res_src, &b);
616624

617-
var request = try self.session.http_client.request(.GET, u);
625+
var request = try self.newHTTPRequest(.GET, u, .{
626+
.origin_uri = self.uri,
627+
.navigation = false,
628+
});
618629
defer request.deinit();
630+
619631
var response = try request.sendSync(.{});
632+
var header = response.header;
633+
try self.session.cookie_jar.populateFromResponse(u, &header);
620634

621-
log.info("fetch {any}: {d}", .{ u, response.header.status });
635+
log.info("fetch {any}: {d}", .{ u, header.status });
622636

623-
if (response.header.status != 200) {
637+
if (header.status != 200) {
624638
return FetchError.BadStatusCode;
625639
}
626640

@@ -645,6 +659,21 @@ pub const Page = struct {
645659
try s.eval(arena, &self.session.env, body);
646660
}
647661

662+
fn newHTTPRequest(self: *const Page, method: http.Request.Method, uri: std.Uri, opts: storage.cookie.LookupOpts) !http.Request {
663+
const session = self.session;
664+
var request = try session.http_client.request(method, uri);
665+
errdefer request.deinit();
666+
667+
var arr: std.ArrayListUnmanaged(u8) = .{};
668+
try session.cookie_jar.forRequest(uri, arr.writer(self.arena), opts);
669+
670+
if (arr.items.len > 0) {
671+
try request.addHeader("Cookie", arr.items, .{});
672+
}
673+
674+
return request;
675+
}
676+
648677
const Script = struct {
649678
element: *parser.Element,
650679
kind: Kind,

src/http/client.zig

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,33 @@ pub const ResponseHeader = struct {
14431443
pub fn count(self: *const ResponseHeader) usize {
14441444
return self.headers.items.len;
14451445
}
1446+
1447+
pub fn iterate(self: *const ResponseHeader, name: []const u8) HeaderIterator {
1448+
return .{
1449+
.index = 0,
1450+
.name = name,
1451+
.headers = self.headers,
1452+
};
1453+
}
1454+
};
1455+
1456+
const HeaderIterator = struct {
1457+
index: usize,
1458+
name: []const u8,
1459+
headers: HeaderList,
1460+
1461+
pub fn next(self: *HeaderIterator) ?[]const u8 {
1462+
const name = self.name;
1463+
const index = self.index;
1464+
for (self.headers.items[index..], index..) |h, i| {
1465+
if (std.mem.eql(u8, name, h.name)) {
1466+
self.index = i + 1;
1467+
return h.value;
1468+
}
1469+
}
1470+
self.index = self.headers.items.len;
1471+
return null;
1472+
}
14461473
};
14471474

14481475
// What we emit from the AsyncHandler
@@ -2044,6 +2071,52 @@ test "HttpClient: async redirect plaintext to TLS" {
20442071
}
20452072
}
20462073

2074+
test "HttpClient: HeaderIterator" {
2075+
var header = ResponseHeader{};
2076+
defer header.headers.deinit(testing.allocator);
2077+
2078+
{
2079+
var it = header.iterate("nope");
2080+
try testing.expectEqual(null, it.next());
2081+
try testing.expectEqual(null, it.next());
2082+
}
2083+
2084+
try header.headers.append(testing.allocator, .{ .name = "h1", .value = "value1" });
2085+
try header.headers.append(testing.allocator, .{ .name = "h2", .value = "value2" });
2086+
try header.headers.append(testing.allocator, .{ .name = "h3", .value = "value3" });
2087+
try header.headers.append(testing.allocator, .{ .name = "h1", .value = "value4" });
2088+
try header.headers.append(testing.allocator, .{ .name = "h1", .value = "value5" });
2089+
2090+
{
2091+
var it = header.iterate("nope");
2092+
try testing.expectEqual(null, it.next());
2093+
try testing.expectEqual(null, it.next());
2094+
}
2095+
2096+
{
2097+
var it = header.iterate("h2");
2098+
try testing.expectEqual("value2", it.next());
2099+
try testing.expectEqual(null, it.next());
2100+
try testing.expectEqual(null, it.next());
2101+
}
2102+
2103+
{
2104+
var it = header.iterate("h3");
2105+
try testing.expectEqual("value3", it.next());
2106+
try testing.expectEqual(null, it.next());
2107+
try testing.expectEqual(null, it.next());
2108+
}
2109+
2110+
{
2111+
var it = header.iterate("h1");
2112+
try testing.expectEqual("value1", it.next());
2113+
try testing.expectEqual("value4", it.next());
2114+
try testing.expectEqual("value5", it.next());
2115+
try testing.expectEqual(null, it.next());
2116+
try testing.expectEqual(null, it.next());
2117+
}
2118+
}
2119+
20472120
const TestResponse = struct {
20482121
status: u16,
20492122
keepalive: ?bool,

src/main_tests.zig

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ const apiweb = @import("apiweb.zig");
2828
const Window = @import("html/window.zig").Window;
2929
const xhr = @import("xhr/xhr.zig");
3030
const storage = @import("storage/storage.zig");
31-
const url = @import("url/url.zig");
32-
const URL = url.URL;
31+
const URL = @import("url/url.zig").URL;
3332
const urlquery = @import("url/query.zig");
3433
const Location = @import("html/location.zig").Location;
3534

@@ -54,7 +53,7 @@ const EventTestExecFn = @import("events/event.zig").testExecFn;
5453
const XHRTestExecFn = xhr.testExecFn;
5554
const ProgressEventTestExecFn = @import("xhr/progress_event.zig").testExecFn;
5655
const StorageTestExecFn = storage.testExecFn;
57-
const URLTestExecFn = url.testExecFn;
56+
const URLTestExecFn = @import("url/url.zig").testExecFn;
5857
const HTMLElementTestExecFn = @import("html/elements.zig").testExecFn;
5958
const MutationObserverTestExecFn = @import("dom/mutation_observer.zig").testExecFn;
6059

@@ -91,16 +90,23 @@ fn testExecFn(
9190
var http_client = try @import("http/client.zig").Client.init(alloc, 5, .{});
9291
defer http_client.deinit();
9392

93+
// alias global as self and window
94+
var window = Window.create(null, null);
95+
96+
const url = "https://lightpanda.io/opensource-browser/";
97+
var u = try URL.constructor(alloc, url, null);
98+
defer u.deinit(alloc);
99+
100+
var cookie_jar = storage.CookieJar.init(alloc);
101+
defer cookie_jar.deinit();
102+
94103
try js_env.setUserContext(.{
104+
.uri = try std.Uri.parse(url),
95105
.document = doc,
106+
.cookie_jar = &cookie_jar,
96107
.http_client = &http_client,
97108
});
98109

99-
// alias global as self and window
100-
var window = Window.create(null, null);
101-
102-
var u = try URL.constructor(alloc, "https://lightpanda.io/opensource-browser/", null);
103-
defer u.deinit(alloc);
104110
var location = Location{ .url = &u };
105111
try window.replaceLocation(&location);
106112

0 commit comments

Comments
 (0)