authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-03-03 01:42:15 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-03-03 01:42:15 -08:00
log2e9575197c909d8acf128ed5627db2a53765abd5
treea9e5dfadea90b150e0fe50964f2981a86aad8601
parente09157c647f08a087a832cf1da70e0de3f8c0076
signaturebadge-check Signed by SSH key SHA256:4hHJbtBRU58AYXwjL7fkz2fnQHdiye8x1QpTCQ0sHNw

add URLSearchParams


3 files changed, 780 insertions(+), 14 deletions(-)

test.SearchParams.zig created+546
......@@ -0,0 +1,546 @@
1const std = @import("std");
2const url = @import("url");
3const expect = @import("expect").expect;
4
5test {
6 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
7 defer arena.deinit();
8 const alloc = arena.allocator();
9
10 var x = try url.SearchParams.initFromString(alloc, "a=b&c=d");
11 try expect(x.get("a")).toEqualString("b");
12 try expect(x.get("c")).toEqualString("d");
13 try expect(x.get("e")).toBeNull();
14
15 try expect(try x.encode()).toEqualString("a=b&c=d");
16}
17
18test {
19 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
20 defer arena.deinit();
21 const alloc = arena.allocator();
22
23 var x = try url.SearchParams.initFromString(alloc, "e=f&g=h");
24 try expect(try x.encode()).toEqualString("e=f&g=h");
25
26 try x.set("i", " j ");
27 try expect(x.get("i")).toEqualString(" j ");
28 try expect(try x.encode()).toEqualString("e=f&g=h&i=+j+");
29
30 try x.set("e", "updated");
31 try expect(x.get("e")).toEqualString("updated");
32 try expect(try x.encode()).toEqualString("e=updated&g=h&i=+j+");
33}
34
35test {
36 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
37 defer arena.deinit();
38 const alloc = arena.allocator();
39
40 var x = url.SearchParams.init(alloc);
41 try x.append("a", "b");
42 try expect(try x.encode()).toEqualString("a=b");
43 try x.append("a", "b");
44 try expect(try x.encode()).toEqualString("a=b&a=b");
45 try x.append("a", "c");
46 try expect(try x.encode()).toEqualString("a=b&a=b&a=c");
47}
48
49test {
50 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
51 defer arena.deinit();
52 const alloc = arena.allocator();
53
54 var x = url.SearchParams.init(alloc);
55 try x.append("", "");
56 try expect(try x.encode()).toEqualString("=");
57 try x.append("", "");
58 try expect(try x.encode()).toEqualString("=&=");
59}
60
61test {
62 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
63 defer arena.deinit();
64 const alloc = arena.allocator();
65
66 var x = url.SearchParams.init(alloc);
67 try x.append("first", "1");
68 try x.append("second", "2");
69 try x.append("third", "");
70 try x.append("first", "10");
71 try expect(x.get("first")).not().toBeNull();
72 try expect(x.get("first")).toEqualString("1");
73 try expect(x.get("second")).toEqualString("2");
74 try expect(x.get("third")).toEqualString("");
75 try x.append("first", "10");
76 try expect(x.get("first")).toEqualString("1");
77}
78
79test {
80 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
81 defer arena.deinit();
82 const alloc = arena.allocator();
83
84 var x = url.SearchParams.init(alloc);
85 try expect(try x.encode()).toEqualString("");
86 try x.append("a", "b");
87 try expect(try x.encode()).toEqualString("a=b");
88 try x.append("a", "c");
89 try expect(try x.encode()).toEqualString("a=b&a=c");
90 try expect(x.take("a")).toEqualString("b");
91 try expect(try x.encode()).toEqualString("a=c");
92 try expect(x.take("a")).toEqualString("c");
93 try expect(try x.encode()).toEqualString("");
94}
95
96test {
97 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
98 defer arena.deinit();
99 const alloc = arena.allocator();
100
101 var x = try url.SearchParams.initFromString(alloc, "a=b&c=d");
102 x.delete("a", null);
103 try expect(try x.encode()).toEqualString("c=d");
104 x = try url.SearchParams.initFromString(alloc, "a=a&b=b&a=a&c=c");
105 x.delete("a", null);
106 try expect(try x.encode()).toEqualString("b=b&c=c");
107 x = try url.SearchParams.initFromString(alloc, "a=a&=&b=b&c=c");
108 x.delete("", null);
109 try expect(try x.encode()).toEqualString("a=a&b=b&c=c");
110 x = try url.SearchParams.initFromString(alloc, "a=a&null=null&b=b");
111 x.delete("null", null);
112 try expect(try x.encode()).toEqualString("a=a&b=b");
113 x = try url.SearchParams.initFromString(alloc, "a=a&undefined=undefined&b=b");
114 x.delete("undefined", null);
115 try expect(try x.encode()).toEqualString("a=a&b=b");
116}
117
118test {
119 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
120 defer arena.deinit();
121 const alloc = arena.allocator();
122
123 var x = url.SearchParams.init(alloc);
124 try x.append("first", "1");
125 try expect(x.has("first", null)).toEqual(true);
126 try expect(x.get("first")).toEqualSlice("1");
127 x.delete("first", null);
128 try expect(x.has("first", null)).toEqual(false);
129 try x.append("first", "1");
130 try x.append("first", "10");
131 x.delete("first", null);
132 try expect(x.has("first", null)).toEqual(false);
133}
134
135test {
136 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
137 defer arena.deinit();
138 const alloc = arena.allocator();
139
140 var x = url.SearchParams.init(alloc);
141 try x.append("a", "b");
142 try x.append("a", "c");
143 try x.append("a", "d");
144 x.delete("a", "c");
145 try expect(try x.encode()).toEqualString("a=b&a=d");
146}
147
148test {
149 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
150 defer arena.deinit();
151 const alloc = arena.allocator();
152
153 var x = url.SearchParams.init(alloc);
154 try x.append("a", "b");
155 try x.append("a", "c");
156 try x.append("b", "c");
157 try x.append("b", "d");
158 x.delete("b", "c");
159 x.delete("a", null);
160 try expect(try x.encode()).toEqualString("b=d");
161}
162
163test {
164 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
165 defer arena.deinit();
166 const alloc = arena.allocator();
167
168 var x = try url.SearchParams.initFromString(alloc, "a=b&c=d");
169 try expect(x.get("a")).toEqualString("b");
170 try expect(x.get("c")).toEqualString("d");
171 try expect(x.get("e")).toBeNull();
172 x = try url.SearchParams.initFromString(alloc, "a=b&c=d&a=e");
173 try expect(x.get("a")).toEqualString("b");
174 x = try url.SearchParams.initFromString(alloc, "=b&c=d");
175 try expect(x.get("")).toEqualString("b");
176 x = try url.SearchParams.initFromString(alloc, "a=&c=d&a=e");
177 try expect(x.get("a")).toEqualString("");
178}
179
180test {
181 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
182 defer arena.deinit();
183 const alloc = arena.allocator();
184
185 var x = try url.SearchParams.initFromString(alloc, "first=second&third&&");
186 try expect(x.has("first", null)).toEqual(true);
187 try expect(x.get("first")).toEqualString("second");
188 try expect(x.get("third")).toEqualString("");
189 try expect(x.get("fourth")).toBeNull();
190}
191
192test {
193 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
194 defer arena.deinit();
195 const alloc = arena.allocator();
196
197 var x = try url.SearchParams.initFromString(alloc, "a=b&c=d");
198 try expect(try x.getAll("a")).toEqualStringSlice(&.{"b"});
199 try expect(try x.getAll("c")).toEqualStringSlice(&.{"d"});
200 try expect(try x.getAll("e")).toEqualStringSlice(&.{});
201 x = try url.SearchParams.initFromString(alloc, "a=b&c=d&a=e");
202 try expect(try x.getAll("a")).toEqualStringSlice(&.{ "b", "e" });
203 x = try url.SearchParams.initFromString(alloc, "=b&c=d");
204 try expect(try x.getAll("")).toEqualStringSlice(&.{"b"});
205 x = try url.SearchParams.initFromString(alloc, "a=&c=d&a=e");
206 try expect(try x.getAll("a")).toEqualStringSlice(&.{ "", "e" });
207}
208
209test {
210 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
211 defer arena.deinit();
212 const alloc = arena.allocator();
213
214 var x = try url.SearchParams.initFromString(alloc, "a=1&a=2&a=3&a");
215 try expect(x.has("a", null)).toEqual(true);
216 var matches = try x.getAll("a");
217 try expect(matches).not().toBeNull();
218 try expect(matches.?.len).toEqual(4);
219 try expect(matches).toEqualStringSlice(&.{ "1", "2", "3", "" });
220 try x.set("a", "one");
221 try expect(x.get("a")).toEqualString("one");
222 try expect(try x.encode()).toEqualString("a=one");
223 matches = try x.getAll("a");
224 try expect(matches).not().toBeNull();
225 try expect(matches.?.len).toEqual(1);
226 try expect(matches).toEqualStringSlice(&.{"one"});
227}
228
229test {
230 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
231 defer arena.deinit();
232 const alloc = arena.allocator();
233
234 var x = try url.SearchParams.initFromString(alloc, "a=b&c=d");
235 try expect(x.has("a", null)).toEqual(true);
236 try expect(x.has("c", null)).toEqual(true);
237 try expect(x.has("e", null)).toEqual(false);
238 x = try url.SearchParams.initFromString(alloc, "a=b&c=d&a=e");
239 try expect(x.has("a", null)).toEqual(true);
240 x = try url.SearchParams.initFromString(alloc, "=b&c=d");
241 try expect(x.has("", null)).toEqual(true);
242 x = try url.SearchParams.initFromString(alloc, "null=a");
243 try expect(x.has("null", null)).toEqual(true);
244}
245
246test {
247 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
248 defer arena.deinit();
249 const alloc = arena.allocator();
250
251 var x = try url.SearchParams.initFromString(alloc, "a=b&c=d&&");
252 try x.append("first", "1");
253 try x.append("first", "2");
254 try expect(x.has("a", null)).toEqual(true);
255 try expect(x.has("c", null)).toEqual(true);
256 try expect(x.has("first", null)).toEqual(true);
257 try expect(x.has("d", null)).toEqual(false);
258 x.delete("first", null);
259 try expect(x.has("first", null)).toEqual(false);
260}
261
262test {
263 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
264 defer arena.deinit();
265 const alloc = arena.allocator();
266
267 var x = try url.SearchParams.initFromString(alloc, "a=b&a=d&c&e&");
268 try expect(x.has("a", "b")).toEqual(true);
269 try expect(x.has("a", "c")).toEqual(false);
270 try expect(x.has("a", "d")).toEqual(true);
271 try expect(x.has("e", "")).toEqual(true);
272 try x.append("first", "null");
273 try expect(x.has("first", "")).toEqual(false);
274 try expect(x.has("first", "null")).toEqual(true);
275 x.delete("a", "b");
276 try expect(x.has("a", "d")).toEqual(true);
277}
278
279test {
280 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
281 defer arena.deinit();
282 const alloc = arena.allocator();
283
284 var x = try url.SearchParams.initFromString(alloc, "a=b&a=d&c&e&");
285 try expect(x.has("a", "b")).toEqual(true);
286 try expect(x.has("a", "c")).toEqual(false);
287 try expect(x.has("a", "d")).toEqual(true);
288 try expect(x.has("a", null)).toEqual(true);
289}
290
291test {
292 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
293 defer arena.deinit();
294 const alloc = arena.allocator();
295
296 var x = try url.SearchParams.initFromString(alloc, "a=b&c=d");
297 try x.set("a", "B");
298 try expect(try x.encode()).toEqualString("a=B&c=d");
299 x = try url.SearchParams.initFromString(alloc, "a=b&c=d&a=e");
300 try x.set("a", "B");
301 try expect(try x.encode()).toEqualString("a=B&c=d");
302 try x.set("e", "f");
303 try expect(try x.encode()).toEqualString("a=B&c=d&e=f");
304}
305
306test {
307 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
308 defer arena.deinit();
309 const alloc = arena.allocator();
310
311 var x = try url.SearchParams.initFromString(alloc, "a=1&a=2&a=3");
312 try expect(x.has("a", null)).toEqual(true);
313 try expect(x.get("a")).toEqualString("1");
314 try x.set("first", "4");
315 try expect(x.has("a", null)).toEqual(true);
316 try expect(x.get("a")).toEqualString("1");
317 try x.set("a", "4");
318 try expect(x.has("a", null)).toEqual(true);
319 try expect(x.get("a")).toEqualString("4");
320}
321
322test {
323 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
324 defer arena.deinit();
325 const alloc = arena.allocator();
326
327 var x = try url.SearchParams.initFromString(alloc, "a=1&b=2&a=3");
328 try expect(x.size()).toEqual(3);
329 x.delete("a", null);
330 try expect(x.size()).toEqual(1);
331}
332
333test {
334 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
335 defer arena.deinit();
336 const alloc = arena.allocator();
337
338 var x = try url.SearchParams.initFromString(alloc, "a=1&b=2&a=3");
339 try expect(x.size()).toEqual(3);
340 try x.append("b", "4");
341 try expect(x.size()).toEqual(4);
342}
343
344test {
345 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
346 defer arena.deinit();
347 const alloc = arena.allocator();
348
349 var x = try url.SearchParams.initFromString(alloc, "a=1&b=2&a=3");
350 try expect(x.size()).toEqual(3);
351 x.delete("a", null);
352 try expect(x.size()).toEqual(1);
353 try x.append("b", "4");
354 try expect(x.size()).toEqual(2);
355}
356
357test {
358 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
359 defer arena.deinit();
360 const alloc = arena.allocator();
361
362 var x = url.SearchParams.init(alloc);
363 try x.append("a", "b c");
364 try expect(try x.encode()).toEqualString("a=b+c");
365 x.delete("a", null);
366 try x.append("a b", "c");
367 try expect(try x.encode()).toEqualString("a+b=c");
368}
369
370test {
371 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
372 defer arena.deinit();
373 const alloc = arena.allocator();
374
375 var x = url.SearchParams.init(alloc);
376 try x.append("a", "");
377 try expect(try x.encode()).toEqualString("a=");
378 try x.append("a", "");
379 try expect(try x.encode()).toEqualString("a=&a=");
380 try x.append("", "b");
381 try expect(try x.encode()).toEqualString("a=&a=&=b");
382 try x.append("", "");
383 try expect(try x.encode()).toEqualString("a=&a=&=b&=");
384 try x.append("", "");
385 try expect(try x.encode()).toEqualString("a=&a=&=b&=&=");
386}
387
388test {
389 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
390 defer arena.deinit();
391 const alloc = arena.allocator();
392
393 var x = url.SearchParams.init(alloc);
394 try x.append("", "b");
395 try expect(try x.encode()).toEqualString("=b");
396 try x.append("", "b");
397 try expect(try x.encode()).toEqualString("=b&=b");
398}
399
400test {
401 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
402 defer arena.deinit();
403 const alloc = arena.allocator();
404
405 var x = url.SearchParams.init(alloc);
406 try x.append("", "");
407 try expect(try x.encode()).toEqualString("=");
408 try x.append("", "");
409 try expect(try x.encode()).toEqualString("=&=");
410}
411
412test {
413 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
414 defer arena.deinit();
415 const alloc = arena.allocator();
416
417 var x = url.SearchParams.init(alloc);
418 try x.append("a", "b+c");
419 try expect(try x.encode()).toEqualString("a=b%2Bc");
420 x.delete("a", null);
421 try x.append("a+b", "c");
422 try expect(try x.encode()).toEqualString("a%2Bb=c");
423}
424
425test {
426 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
427 defer arena.deinit();
428 const alloc = arena.allocator();
429
430 var x = url.SearchParams.init(alloc);
431 try x.append("=", "a");
432 try expect(try x.encode()).toEqualString("%3D=a");
433 try x.append("b", "=");
434 try expect(try x.encode()).toEqualString("%3D=a&b=%3D");
435}
436
437test {
438 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
439 defer arena.deinit();
440 const alloc = arena.allocator();
441
442 var x = url.SearchParams.init(alloc);
443 try x.append("&", "a");
444 try expect(try x.encode()).toEqualString("%26=a");
445 try x.append("b", "&");
446 try expect(try x.encode()).toEqualString("%26=a&b=%26");
447}
448
449test {
450 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
451 defer arena.deinit();
452 const alloc = arena.allocator();
453
454 var x = url.SearchParams.init(alloc);
455 try x.append("a", "*-._");
456 try expect(try x.encode()).toEqualString("a=*-._");
457 x.delete("a", null);
458 try x.append("*-._", "c");
459 try expect(try x.encode()).toEqualString("*-._=c");
460}
461
462test {
463 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
464 defer arena.deinit();
465 const alloc = arena.allocator();
466
467 var x = url.SearchParams.init(alloc);
468 try x.append("a", "b%c");
469 try expect(try x.encode()).toEqualString("a=b%25c");
470 x.delete("a", null);
471 try x.append("a%b", "c");
472 try expect(try x.encode()).toEqualString("a%25b=c");
473 x = try url.SearchParams.initFromString(alloc, "id=0&value=%");
474 try expect(try x.encode()).toEqualString("id=0&value=%25");
475}
476
477test {
478 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
479 defer arena.deinit();
480 const alloc = arena.allocator();
481
482 var x = url.SearchParams.init(alloc);
483 try x.append("a", "b\x00c");
484 try expect(try x.encode()).toEqualString("a=b%00c");
485 x.delete("a", null);
486 try x.append("a\x00b", "c");
487 try expect(try x.encode()).toEqualString("a%00b=c");
488}
489
490test {
491 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
492 defer arena.deinit();
493 const alloc = arena.allocator();
494
495 var x = url.SearchParams.init(alloc);
496 // try x.append("a", "b\uD83D\uDCA9c");
497 try x.append("a", "b\xf0\x9f\x92\xa9c");
498 try expect(try x.encode()).toEqualString("a=b%F0%9F%92%A9c");
499 x.delete("a", null);
500 // try x.append("a\uD83D\uDCA9b", "c");
501 try x.append("a\xf0\x9f\x92\xa9b", "c");
502 try expect(try x.encode()).toEqualString("a%F0%9F%92%A9b=c");
503}
504
505test {
506 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
507 defer arena.deinit();
508 const alloc = arena.allocator();
509
510 var x = url.SearchParams.init(alloc);
511 x = try url.SearchParams.initFromString(alloc, "a=b&c=d&&e&&");
512 try expect(try x.encode()).toEqualString("a=b&c=d&e=");
513 x = try url.SearchParams.initFromString(alloc, "a = b &a=b&c=d%20");
514 try expect(try x.encode()).toEqualString("a+=+b+&a=b&c=d+");
515 // The lone "=" _does_ survive the roundtrip.
516 x = try url.SearchParams.initFromString(alloc, "a=&a=b");
517 try expect(try x.encode()).toEqualString("a=&a=b");
518 x = try url.SearchParams.initFromString(alloc, "b=%2sf%2a");
519 try expect(try x.encode()).toEqualString("b=%252sf*");
520 x = try url.SearchParams.initFromString(alloc, "b=%2%2af%2a");
521 try expect(try x.encode()).toEqualString("b=%252*f*");
522 x = try url.SearchParams.initFromString(alloc, "b=%%2a");
523 try expect(try x.encode()).toEqualString("b=%25*");
524}
525
526test {
527 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
528 defer arena.deinit();
529 const alloc = arena.allocator();
530
531 var x = try url.SearchParams.initFromString(alloc, "a=b,c");
532 try expect(try x.encode()).toEqualString("a=b%2Cc");
533 try x.append("x", "y");
534 try expect(try x.encode()).toEqualString("a=b%2Cc&x=y");
535}
536
537test {
538 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
539 defer arena.deinit();
540 const alloc = arena.allocator();
541
542 var x = url.SearchParams.init(alloc);
543 try x.append("a\nb", "c\rd");
544 try x.append("e\n\rf", "g\r\nh");
545 try expect(try x.encode()).toEqualString("a%0Ab=c%0Dd&e%0A%0Df=g%0D%0Ah");
546}
test.zig+4
......@@ -4,3 +4,7 @@ const url = @import("url");
44test {
55 _ = @import("./zig-out/test.zig");
66}
7
8test {
9 _ = @import("./test.SearchParams.zig");
10}
url.zig+230-14
......@@ -973,6 +973,222 @@ pub const URL = struct {
973973 }
974974};
975975
976pub const SearchParams = struct {
977 const string = []const u8;
978 const KV = struct { key: string, value: string };
979
980 allocator: std.mem.Allocator,
981 inner: std.MultiArrayList(KV),
982
983 pub fn init(alloc: std.mem.Allocator) SearchParams {
984 return .{
985 .allocator = alloc,
986 .inner = std.MultiArrayList(KV){},
987 };
988 }
989
990 pub fn initFromString(alloc: std.mem.Allocator, input: string) !SearchParams {
991 var uv = SearchParams.init(alloc);
992 var iter = RawIterator.init(input, alloc);
993 while (try iter.next()) |piece| {
994 const k = piece[0];
995 const v = piece[1];
996 try uv.append(k, v);
997 }
998 return uv;
999 }
1000
1001 const RawIterator = struct {
1002 input: string,
1003 iter: std.mem.SplitIterator(u8, .scalar),
1004 alloc: std.mem.Allocator,
1005
1006 fn init(input: string, alloc: std.mem.Allocator) RawIterator {
1007 return .{
1008 .input = input,
1009 .iter = std.mem.splitScalar(u8, input, '&'),
1010 .alloc = alloc,
1011 };
1012 }
1013
1014 fn next(ri: *RawIterator) !?struct { string, string } {
1015 while (ri.iter.next()) |piece| {
1016 if (piece.len == 0) continue;
1017 var jter = std.mem.splitScalar(u8, piece, '=');
1018 const k = jter.next().?;
1019 var v = jter.rest();
1020 std.mem.replaceScalar(u8, @constCast(v), '+', ' ');
1021 v = std.Uri.percentDecodeInPlace(try ri.alloc.dupe(u8, v));
1022 return .{ k, v };
1023 }
1024 return null;
1025 }
1026 };
1027
1028 pub fn set(self: *SearchParams, key: string, value: string) !void {
1029 const keys = self.inner.items(.key);
1030 const values = self.inner.items(.value);
1031 var idx = extras.indexOfSlice(u8, keys, key) orelse {
1032 return try self.append(key, value);
1033 };
1034 values[idx] = value;
1035 idx += 1;
1036 while (true) {
1037 if (idx >= self.inner.len) break;
1038 if (!std.mem.eql(u8, keys[idx], key)) {
1039 idx += 1;
1040 continue;
1041 }
1042 self.inner.orderedRemove(idx);
1043 }
1044 }
1045
1046 pub fn append(self: *SearchParams, key: string, value: string) !void {
1047 try self.inner.append(self.allocator, .{ .key = key, .value = value });
1048 }
1049
1050 pub fn get(self: *const SearchParams, key: string) ?string {
1051 const keys = self.inner.items(.key);
1052 const idx = extras.indexOfSlice(u8, keys, key) orelse return null;
1053 const values = self.inner.items(.value);
1054 return values[idx];
1055 }
1056
1057 pub fn getAll(self: *const SearchParams, key: string) !?[]const string {
1058 const keys = self.inner.items(.key);
1059 const values = self.inner.items(.value);
1060 var backer: [256]usize = undefined;
1061 @memset(&backer, 0);
1062 var bset = std.bit_set.DynamicBitSetUnmanaged{ .bit_length = self.inner.len, .masks = &backer };
1063 var prev: bool = false;
1064 var phase_count: usize = 0;
1065 for (keys, 0..) |k, i| {
1066 const this = std.mem.eql(u8, k, key);
1067 defer prev = this;
1068 if (this != prev) phase_count += 1;
1069 if (this == true) bset.set(i);
1070 }
1071 if (prev == true) phase_count += 1;
1072 const count = bset.count();
1073 if (count == 0) return null;
1074 if (count == 1) return values[bset.findFirstSet().?..][0..1];
1075 if (phase_count == 2) return values[bset.findFirstSet().? .. findLastSet(bset).? + 1];
1076 const items = try self.allocator.alloc(string, count);
1077 var iter = bset.iterator(.{});
1078 var i: usize = 0;
1079 while (iter.next()) |idx| : (i += 1) items[i] = values[idx];
1080 return items;
1081 }
1082
1083 pub fn take(self: *SearchParams, key: string) ?string {
1084 const keys = self.inner.items(.key);
1085 const idx = extras.indexOfSlice(u8, keys, key) orelse return null;
1086 const values = self.inner.items(.value);
1087 defer self.inner.orderedRemove(idx);
1088 return values[idx];
1089 }
1090
1091 /// if 'value' is null, will delete all the values with key 'key'
1092 pub fn delete(self: *SearchParams, key: string, value: ?string) void {
1093 var offset: usize = 0;
1094 while (true) {
1095 const keys = self.inner.items(.key);
1096 const values = self.inner.items(.value);
1097 var idx = extras.indexOfSlice(u8, keys[offset..], key) orelse break;
1098 idx += offset;
1099 if (idx >= self.inner.len) break;
1100 if (!std.mem.eql(u8, keys[idx], key)) break;
1101 if (value) |v| {
1102 if (!std.mem.eql(u8, values[idx], v)) {
1103 offset += 1;
1104 continue;
1105 }
1106 }
1107 self.inner.orderedRemove(idx);
1108 }
1109 }
1110
1111 pub fn has(self: *const SearchParams, key: string, value: ?string) bool {
1112 const keys = self.inner.items(.key);
1113 if (value == null) {
1114 return extras.indexOfSlice(u8, keys, key) != null;
1115 }
1116 const values = self.inner.items(.value);
1117 for (keys, 0..) |k, i| {
1118 if (std.mem.eql(u8, key, k)) {
1119 if (std.mem.eql(u8, value.?, values[i])) {
1120 return true;
1121 }
1122 }
1123 }
1124 return false;
1125 }
1126
1127 pub fn size(self: *const SearchParams) usize {
1128 return self.inner.len;
1129 }
1130
1131 pub fn encode(self: *const SearchParams) !string {
1132 const alloc = self.allocator;
1133 var list = std.ArrayList(u8).init(alloc);
1134 errdefer list.deinit();
1135 for (self.inner.items(.key), self.inner.items(.value), 0..) |k, v, i| {
1136 if (i > 0) try list.writer().writeAll("&");
1137 try percentEncodeAL(&list, k, is_formurlencoded_percent_char);
1138 try list.append('=');
1139 try percentEncodeAL(&list, v, is_formurlencoded_percent_char);
1140 }
1141 list.items.len -= std.mem.replace(u8, list.items, "%20", "+", list.items) * 2;
1142 return list.toOwnedSlice();
1143 }
1144
1145 fn IteratorFor(comptime field: string) type {
1146 return struct {
1147 raw: RawIterator,
1148
1149 const Self = @This();
1150
1151 pub fn init(alloc: std.mem.Allocator, input: string) Self {
1152 return .{
1153 .raw = .{
1154 .input = input,
1155 .iter = std.mem.splitScalar(u8, input, '&'),
1156 .alloc = alloc,
1157 },
1158 };
1159 }
1160
1161 pub fn next(self: *Self) !?string {
1162 while (try self.raw.next()) |piece| {
1163 const k = piece[0];
1164 const v = piece[1];
1165 if (!std.mem.eql(u8, k, field)) continue;
1166 return v;
1167 }
1168 return null;
1169 }
1170 };
1171 }
1172
1173 /// Finds the index of the last set bit.
1174 /// If no bits are set, returns null.
1175 fn findLastSet(self: std.bit_set.DynamicBitSetUnmanaged) ?usize {
1176 if (self.bit_length == 0) return null;
1177 const bs = @bitSizeOf(usize);
1178 var len = self.bit_length / bs;
1179 if (self.bit_length % bs != 0) len += 1;
1180 var offset: usize = len * bs;
1181 var idx: usize = len - 1;
1182 while (self.masks[idx] == 0) : (idx -= 1) {
1183 offset -= bs;
1184 if (idx == 0) return null;
1185 }
1186 offset -= @clz(self.masks[idx]);
1187 offset -= 1;
1188 return offset;
1189 }
1190};
1191
9761192/// https://url.spec.whatwg.org/#special-scheme
9771193fn isSchemeSpecial(scheme: []const u8) bool {
9781194 if (std.mem.eql(u8, scheme, "ftp")) return true;
......@@ -1539,20 +1755,20 @@ pub fn is_userinfo_percent_char(c: u8) bool {
15391755 if (c == '|') return true;
15401756 return is_path_percent_char(c);
15411757}
1542// /// https://url.spec.whatwg.org/#component-percent-encode-set
1543// fn is_component_percent_char(c: u8) bool {
1544// if (c >= '$' and c <= '&') return true;
1545// if (c == '+') return true;
1546// if (c == ',') return true;
1547// return is_userinfo_percent_char(c);
1548// }
1549// /// https://url.spec.whatwg.org/#application-x-www-form-urlencoded-percent-encode-set
1550// fn is_formurlencoded_percent_char(c: u8) bool {
1551// if (c == '!') return true;
1552// if (c >= '\'' and c <= ')') return true;
1553// if (c == '~') return true;
1554// return is_component_percent_char(c);
1555// }
1758/// https://url.spec.whatwg.org/#component-percent-encode-set
1759pub fn is_component_percent_char(c: u8) bool {
1760 if (c >= '$' and c <= '&') return true;
1761 if (c == '+') return true;
1762 if (c == ',') return true;
1763 return is_userinfo_percent_char(c);
1764}
1765/// https://url.spec.whatwg.org/#application-x-www-form-urlencoded-percent-encode-set
1766pub fn is_formurlencoded_percent_char(c: u8) bool {
1767 if (c == '!') return true;
1768 if (c >= '\'' and c <= ')') return true;
1769 if (c == '~') return true;
1770 return is_component_percent_char(c);
1771}
15561772
15571773/// Asserts b is a valid UTF-8 codepoint
15581774fn l(b: u8) u3 {