@@ -23,39 +23,68 @@ const Page = @import("../page.zig").Page;
2323// https://developer.mozilla.org/en-US/docs/Web/API/Headers
2424const Headers = @This ();
2525
26- headers : std .StringHashMapUnmanaged ([]const u8 ),
26+ // Case-Insensitive String HashMap.
27+ // This allows us to avoid having to allocate lowercase keys all the time.
28+ const HeaderHashMap = std .HashMapUnmanaged ([]const u8 , []const u8 , struct {
29+ pub fn hash (_ : @This (), s : []const u8 ) u64 {
30+ var hasher = std .hash .Wyhash .init (s .len );
31+ for (s ) | c | {
32+ hasher .update (&.{std .ascii .toLower (c )});
33+ }
34+
35+ return hasher .final ();
36+ }
37+ pub fn eql (_ : @This (), a : []const u8 , b : []const u8 ) bool {
38+ if (a .len != b .len ) return false ;
39+
40+ for (a , b ) | c1 , c2 | {
41+ if (std .ascii .toLower (c1 ) != std .ascii .toLower (c2 )) return false ;
42+ }
43+
44+ return true ;
45+ }
46+ }, 80 );
47+
48+ headers : HeaderHashMap = .empty ,
2749
2850// They can either be:
2951//
3052// 1. An array of string pairs.
3153// 2. An object with string keys to string values.
3254// 3. Another Headers object.
33- const HeadersInit = union (enum ) {
34- strings : []const []const u8 ,
35- // headers: Headers,
55+ pub const HeadersInit = union (enum ) {
56+ // List of Pairs of []const u8
57+ strings : []const []const []const u8 ,
58+ headers : * Headers ,
3659};
3760
38- pub fn constructor (_init : ? [] const HeadersInit , page : * Page ) ! Headers {
61+ pub fn constructor (_init : ? HeadersInit , page : * Page ) ! Headers {
3962 const arena = page .arena ;
40- var headers = std . StringHashMapUnmanaged ([] const u8 ) .empty ;
63+ var headers : HeaderHashMap = .empty ;
4164
4265 if (_init ) | init | {
43- for (init ) | item | {
44- switch ( item ) {
45- .strings = > | pair | {
66+ switch (init ) {
67+ .strings = > | kvs | {
68+ for ( kvs ) | pair | {
4669 // Can only have two string elements if in a pair.
4770 if (pair .len != 2 ) {
4871 return error .TypeError ;
4972 }
5073
51- const raw_key = pair [0 ];
52- const value = pair [1 ];
53- const key = try std .ascii .allocLowerString (arena , raw_key );
74+ const key = try page .arena .dupe (u8 , pair [0 ]);
75+ const value = try page .arena .dupe (u8 , pair [1 ]);
5476
5577 try headers .put (arena , key , value );
56- },
57- // .headers => |_| {},
58- }
78+ }
79+ },
80+ .headers = > | hdrs | {
81+ var iter = hdrs .headers .iterator ();
82+ while (iter .next ()) | entry | {
83+ const key = try page .arena .dupe (u8 , entry .key_ptr .* );
84+ const value = try page .arena .dupe (u8 , entry .value_ptr .* );
85+ try headers .put (arena , key , value );
86+ }
87+ },
5988 }
6089 }
6190
@@ -64,14 +93,70 @@ pub fn constructor(_init: ?[]const HeadersInit, page: *Page) !Headers {
6493 };
6594}
6695
67- pub fn _get (self : * const Headers , header : []const u8 , page : * Page ) ! ? []const u8 {
96+ pub fn clone (self : * Headers , allocator : std.mem.Allocator ) ! Headers {
97+ return Headers {
98+ .headers = try self .headers .clone (allocator ),
99+ };
100+ }
101+
102+ pub fn _append (self : * Headers , name : []const u8 , value : []const u8 , page : * Page ) ! void {
68103 const arena = page .arena ;
69- const key = try std .ascii .allocLowerString (arena , header );
70104
71- const value = (self .headers .getEntry (key ) orelse return null ).value_ptr .* ;
105+ if (self .headers .getEntry (name )) | entry | {
106+ // If we found it, append the value.
107+ const new_value = try std .fmt .allocPrint (arena , "{s}, {s}" , .{ entry .value_ptr .* , value });
108+ entry .value_ptr .* = new_value ;
109+ } else {
110+ // Otherwise, we should just put it in.
111+ try self .headers .putNoClobber (
112+ arena ,
113+ try arena .dupe (u8 , name ),
114+ try arena .dupe (u8 , value ),
115+ );
116+ }
117+ }
118+
119+ pub fn _delete (self : * Headers , name : []const u8 ) void {
120+ _ = self .headers .remove (name );
121+ }
122+
123+ // TODO: entries iterator
124+ // They should be:
125+ // 1. Sorted in lexicographical order.
126+ // 2. Duplicate header names should be combined.
127+
128+ // TODO: header for each
129+
130+ pub fn _get (self : * const Headers , name : []const u8 , page : * Page ) ! ? []const u8 {
131+ const arena = page .arena ;
132+ const value = (self .headers .getEntry (name ) orelse return null ).value_ptr .* ;
72133 return try arena .dupe (u8 , value );
73134}
74135
136+ pub fn _has (self : * const Headers , name : []const u8 ) bool {
137+ return self .headers .contains (name );
138+ }
139+
140+ // TODO: keys iterator
141+
142+ pub fn _set (self : * Headers , name : []const u8 , value : []const u8 , page : * Page ) ! void {
143+ const arena = page .arena ;
144+
145+ if (self .headers .getEntry (name )) | entry | {
146+ // If we found it, set the value.
147+ entry .value_ptr .* = try arena .dupe (u8 , value );
148+ } else {
149+ // Otherwise, we should just put it in.
150+ try self .headers .putNoClobber (
151+ arena ,
152+ try arena .dupe (u8 , name ),
153+ try arena .dupe (u8 , value ),
154+ );
155+ }
156+ }
157+
158+ // TODO: values iterator
159+
75160const testing = @import ("../../testing.zig" );
76161test "fetch: headers" {
77162 var runner = try testing .jsRunner (testing .tracking_allocator , .{ .url = "https://lightpanda.io" });
@@ -85,4 +170,19 @@ test "fetch: headers" {
85170 .{ "let headers = new Headers([['Set-Cookie', 'name=world']])" , "undefined" },
86171 .{ "headers.get('set-cookie')" , "name=world" },
87172 }, .{});
173+
174+ // adapted from the mdn examples
175+ try runner .testCases (&.{
176+ .{ "const myHeaders = new Headers();" , "undefined" },
177+ .{ "myHeaders.append('Content-Type', 'image/jpeg')" , "undefined" },
178+ .{ "myHeaders.has('Picture-Type')" , "false" },
179+ .{ "myHeaders.get('Content-Type')" , "image/jpeg" },
180+ .{ "myHeaders.append('Content-Type', 'image/png')" , "undefined" },
181+ .{ "myHeaders.get('Content-Type')" , "image/jpeg, image/png" },
182+ .{ "myHeaders.delete('Content-Type')" , "undefined" },
183+ .{ "myHeaders.get('Content-Type')" , "null" },
184+ .{ "myHeaders.set('Picture-Type', 'image/svg')" , "undefined" },
185+ .{ "myHeaders.get('Picture-Type')" , "image/svg" },
186+ .{ "myHeaders.has('Picture-Type')" , "true" },
187+ }, .{});
88188}
0 commit comments