Skip to content

Commit d0c741f

Browse files
committed
url: search query dynamic and encoded
1 parent a9842fd commit d0c741f

File tree

2 files changed

+77
-8
lines changed

2 files changed

+77
-8
lines changed

src/url/query.zig

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,19 +79,42 @@ pub const Values = struct {
7979
pub fn deleteValue(self: *Values, k: []const u8, v: []const u8) void {
8080
const list = self.map.getPtr(k) orelse return;
8181

82-
var i: usize = 0;
83-
while (i < list.items.len) {
84-
if (std.mem.eql(u8, v, list.items[i])) {
82+
for (list.items, 0..) |vv, i| {
83+
if (std.mem.eql(u8, v, vv)) {
8584
_ = list.swapRemove(i);
8685
return;
8786
}
88-
i += 1;
8987
}
9088
}
9189

9290
pub fn count(self: *Values) usize {
9391
return self.map.count();
9492
}
93+
94+
// the caller owned the returned string.
95+
pub fn encode(self: *Values, writer: anytype) !void {
96+
var i: usize = 0;
97+
var it = self.map.iterator();
98+
while (it.next()) |entry| {
99+
defer i += 1;
100+
if (i > 0) try writer.writeByte('&');
101+
102+
if (entry.value_ptr.items.len == 0) {
103+
try escape(writer, entry.key_ptr.*);
104+
continue;
105+
}
106+
107+
const start = i;
108+
for (entry.value_ptr.items) |v| {
109+
defer i += 1;
110+
if (start < i) try writer.writeByte('&');
111+
112+
try escape(writer, entry.key_ptr.*);
113+
if (v.len > 0) try writer.writeByte('=');
114+
try escape(writer, v);
115+
}
116+
}
117+
}
95118
};
96119

97120
fn unhex(c: u8) u8 {
@@ -137,6 +160,19 @@ test "unescape" {
137160
alloc.free(v);
138161
}
139162

163+
pub fn escape(writer: anytype, raw: []const u8) !void {
164+
var start: usize = 0;
165+
for (raw, 0..) |char, index| {
166+
if ('a' <= char and char <= 'z' or 'A' <= char and char <= 'Z' or '0' <= char and char <= '9') {
167+
continue;
168+
}
169+
170+
try writer.print("{s}%{X:0>2}", .{ raw[start..index], char });
171+
start = index + 1;
172+
}
173+
try writer.writeAll(raw[start..]);
174+
}
175+
140176
// Parse the given query.
141177
pub fn parseQuery(alloc: std.mem.Allocator, s: []const u8) !Values {
142178
var values = Values.init(alloc);
@@ -213,3 +249,17 @@ test "parse query dup" {
213249
try std.testing.expect(std.mem.eql(u8, values.first("a"), "b"));
214250
try std.testing.expect(values.get("a").len == 2);
215251
}
252+
253+
test "encode query" {
254+
var values = try parseQuery(std.testing.allocator, "a=b&b=c");
255+
defer values.deinit();
256+
257+
try values.append("a", "~");
258+
259+
var buf: std.ArrayListUnmanaged(u8) = .{};
260+
defer buf.deinit(std.testing.allocator);
261+
262+
try values.encode(buf.writer(std.testing.allocator));
263+
264+
try std.testing.expect(std.mem.eql(u8, buf.items, "a=b&a=%7E&b=c"));
265+
}

src/url/url.zig

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,18 @@ pub const URL = struct {
5656
// the caller must free the returned string.
5757
// TODO return a disposable string
5858
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
59-
pub fn get_href(self: URL, alloc: std.mem.Allocator) ![]const u8 {
59+
pub fn get_href(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
6060
var buf = std.ArrayList(u8).init(alloc);
6161
defer buf.deinit();
6262

63+
// retrieve the query search from search_params.
64+
const cur = self.uri.query;
65+
defer self.uri.query = cur;
66+
var q = std.ArrayList(u8).init(alloc);
67+
defer q.deinit();
68+
try self.search_params.values.encode(q.writer());
69+
self.uri.query = q.items;
70+
6371
try self.uri.writeToStream(.{
6472
.scheme = true,
6573
.authentication = true,
@@ -116,9 +124,14 @@ pub const URL = struct {
116124
// TODO return a disposable string
117125
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
118126
pub fn get_search(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
119-
if (self.uri.query == null) return try alloc.dupe(u8, "");
127+
if (self.search_params.get_size() == 0) return try alloc.dupe(u8, "");
128+
129+
var buf: std.ArrayListUnmanaged(u8) = .{};
130+
defer buf.deinit(alloc);
120131

121-
return try std.mem.concat(alloc, u8, &[_][]const u8{ "?", self.uri.query.? });
132+
try buf.append(alloc, '?');
133+
try self.search_params.values.encode(buf.writer(alloc));
134+
return buf.toOwnedSlice(alloc);
122135
}
123136

124137
// the caller must free the returned string.
@@ -207,12 +220,18 @@ pub fn testExecFn(
207220
try checkCases(js_env, &url);
208221

209222
var qs = [_]Case{
210-
.{ .src = "var url = new URL('https://foo.bar/path?a=~&b=%7E')", .ex = "undefined" },
223+
.{ .src = "var url = new URL('https://foo.bar/path?a=~&b=%7E#fragment')", .ex = "undefined" },
211224
.{ .src = "url.searchParams.get('a')", .ex = "~" },
212225
.{ .src = "url.searchParams.get('b')", .ex = "~" },
213226
.{ .src = "url.searchParams.append('c', 'foo')", .ex = "undefined" },
214227
.{ .src = "url.searchParams.get('c')", .ex = "foo" },
215228
.{ .src = "url.searchParams.size", .ex = "3" },
229+
230+
// search is dynamic
231+
.{ .src = "url.search", .ex = "?a=%7E&b=%7E&c=foo" },
232+
// href is dynamic
233+
.{ .src = "url.href", .ex = "https://foo.bar/path?a=%7E&b=%7E&c=foo#fragment" },
234+
216235
.{ .src = "url.searchParams.delete('c', 'foo')", .ex = "undefined" },
217236
.{ .src = "url.searchParams.get('c')", .ex = "" },
218237
.{ .src = "url.searchParams.delete('a')", .ex = "undefined" },

0 commit comments

Comments
 (0)