mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
url: implement query parsing
This commit is contained in:
@@ -11,6 +11,7 @@ const Window = @import("html/window.zig").Window;
|
|||||||
const xhr = @import("xhr/xhr.zig");
|
const xhr = @import("xhr/xhr.zig");
|
||||||
const storage = @import("storage/storage.zig");
|
const storage = @import("storage/storage.zig");
|
||||||
const url = @import("url/url.zig");
|
const url = @import("url/url.zig");
|
||||||
|
const urlquery = @import("url/query.zig");
|
||||||
|
|
||||||
const documentTestExecFn = @import("dom/document.zig").testExecFn;
|
const documentTestExecFn = @import("dom/document.zig").testExecFn;
|
||||||
const HTMLDocumentTestExecFn = @import("html/document.zig").testExecFn;
|
const HTMLDocumentTestExecFn = @import("html/document.zig").testExecFn;
|
||||||
@@ -278,6 +279,9 @@ test {
|
|||||||
|
|
||||||
const cssLibdomTest = @import("css/libdom_test.zig");
|
const cssLibdomTest = @import("css/libdom_test.zig");
|
||||||
std.testing.refAllDecls(cssLibdomTest);
|
std.testing.refAllDecls(cssLibdomTest);
|
||||||
|
|
||||||
|
const queryTest = @import("url/query.zig");
|
||||||
|
std.testing.refAllDecls(queryTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testJSRuntime(alloc: std.mem.Allocator) !void {
|
fn testJSRuntime(alloc: std.mem.Allocator) !void {
|
||||||
|
|||||||
159
src/url/query.zig
Normal file
159
src/url/query.zig
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Reader = @import("../str/parser.zig").Reader;
|
||||||
|
|
||||||
|
// Values is a map with string key of string values.
|
||||||
|
pub const Values = struct {
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
map: std.StringArrayHashMapUnmanaged(List),
|
||||||
|
|
||||||
|
const List = std.ArrayListUnmanaged([]const u8);
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) Values {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.map = .{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Values) void {
|
||||||
|
var it = self.map.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
for (entry.value_ptr.items) |v| self.alloc.free(v);
|
||||||
|
entry.value_ptr.deinit(self.alloc);
|
||||||
|
self.alloc.free(entry.key_ptr.*);
|
||||||
|
}
|
||||||
|
self.map.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the key value couple to the values.
|
||||||
|
// the key and the value are duplicated.
|
||||||
|
pub fn append(self: *Values, k: []const u8, v: []const u8) !void {
|
||||||
|
const vv = try self.alloc.dupe(u8, v);
|
||||||
|
|
||||||
|
if (self.map.getPtr(k)) |list| {
|
||||||
|
return try list.append(self.alloc, vv);
|
||||||
|
}
|
||||||
|
|
||||||
|
const kk = try self.alloc.dupe(u8, k);
|
||||||
|
var list = List{};
|
||||||
|
try list.append(self.alloc, vv);
|
||||||
|
try self.map.put(self.alloc, kk, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *Values, k: []const u8) [][]const u8 {
|
||||||
|
if (self.map.get(k)) |list| {
|
||||||
|
return list.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &[_][]const u8{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn first(self: *Values, k: []const u8) []const u8 {
|
||||||
|
if (self.map.getPtr(k)) |list| {
|
||||||
|
if (list.items.len == 0) return "";
|
||||||
|
return list.items[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(self: *Values, k: []const u8) void {
|
||||||
|
if (self.map.getPtr(k)) |list| {
|
||||||
|
list.deinit(self.alloc);
|
||||||
|
_ = self.map.fetchSwapRemove(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deleteValue(self: *Values, k: []const u8, v: []const u8) void {
|
||||||
|
const list = self.map.getPtr(k) orelse return;
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < list.items.len) {
|
||||||
|
if (std.mem.eql(u8, v, list.items[i])) {
|
||||||
|
_ = list.swapRemove(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count(self: *Values) usize {
|
||||||
|
return self.map.count();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse the given query.
|
||||||
|
pub fn parseQuery(alloc: std.mem.Allocator, s: []const u8) !Values {
|
||||||
|
var values = Values.init(alloc);
|
||||||
|
errdefer values.deinit();
|
||||||
|
|
||||||
|
const ln = s.len;
|
||||||
|
if (ln == 0) return values;
|
||||||
|
|
||||||
|
var r = Reader{ .s = s };
|
||||||
|
while (true) {
|
||||||
|
const param = r.until('&');
|
||||||
|
if (param.len == 0) break;
|
||||||
|
|
||||||
|
var rr = Reader{ .s = param };
|
||||||
|
const k = rr.until('=');
|
||||||
|
if (k.len == 0) continue;
|
||||||
|
|
||||||
|
_ = rr.skip();
|
||||||
|
const v = rr.tail();
|
||||||
|
|
||||||
|
// TODO decode k and v
|
||||||
|
|
||||||
|
try values.append(k, v);
|
||||||
|
|
||||||
|
if (!r.skip()) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse empty query" {
|
||||||
|
var values = try parseQuery(std.testing.allocator, "");
|
||||||
|
defer values.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(values.count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse empty query &" {
|
||||||
|
var values = try parseQuery(std.testing.allocator, "&");
|
||||||
|
defer values.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(values.count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse query" {
|
||||||
|
var values = try parseQuery(std.testing.allocator, "a=b&b=c");
|
||||||
|
defer values.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(values.count() == 2);
|
||||||
|
try std.testing.expect(values.get("a").len == 1);
|
||||||
|
try std.testing.expect(std.mem.eql(u8, values.get("a")[0], "b"));
|
||||||
|
try std.testing.expect(std.mem.eql(u8, values.first("a"), "b"));
|
||||||
|
|
||||||
|
try std.testing.expect(values.get("b").len == 1);
|
||||||
|
try std.testing.expect(std.mem.eql(u8, values.get("b")[0], "c"));
|
||||||
|
try std.testing.expect(std.mem.eql(u8, values.first("b"), "c"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse query no value" {
|
||||||
|
var values = try parseQuery(std.testing.allocator, "a");
|
||||||
|
defer values.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(values.count() == 1);
|
||||||
|
try std.testing.expect(std.mem.eql(u8, values.first("a"), ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse query dup" {
|
||||||
|
var values = try parseQuery(std.testing.allocator, "a=b&a=c");
|
||||||
|
defer values.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(values.count() == 1);
|
||||||
|
try std.testing.expect(std.mem.eql(u8, values.first("a"), "b"));
|
||||||
|
try std.testing.expect(values.get("a").len == 2);
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ const Case = jsruntime.test_utils.Case;
|
|||||||
const checkCases = jsruntime.test_utils.checkCases;
|
const checkCases = jsruntime.test_utils.checkCases;
|
||||||
const generate = @import("../generate.zig");
|
const generate = @import("../generate.zig");
|
||||||
|
|
||||||
|
const query = @import("query.zig");
|
||||||
|
|
||||||
pub const Interfaces = generate.Tuple(.{
|
pub const Interfaces = generate.Tuple(.{
|
||||||
URL,
|
URL,
|
||||||
URLSearchParams,
|
URLSearchParams,
|
||||||
@@ -27,6 +29,7 @@ pub const Interfaces = generate.Tuple(.{
|
|||||||
pub const URL = struct {
|
pub const URL = struct {
|
||||||
rawuri: []const u8,
|
rawuri: []const u8,
|
||||||
uri: std.Uri,
|
uri: std.Uri,
|
||||||
|
search_params: URLSearchParams,
|
||||||
|
|
||||||
pub const mem_guarantied = true;
|
pub const mem_guarantied = true;
|
||||||
|
|
||||||
@@ -41,10 +44,12 @@ pub const URL = struct {
|
|||||||
return .{
|
return .{
|
||||||
.rawuri = raw,
|
.rawuri = raw,
|
||||||
.uri = uri,
|
.uri = uri,
|
||||||
|
.search_params = try URLSearchParams.constructor(alloc, uri.query),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *URL, alloc: std.mem.Allocator) void {
|
pub fn deinit(self: *URL, alloc: std.mem.Allocator) void {
|
||||||
|
self.search_params.deinit();
|
||||||
alloc.free(self.rawuri);
|
alloc.free(self.rawuri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,14 +130,57 @@ pub const URL = struct {
|
|||||||
return try std.mem.concat(alloc, u8, &[_][]const u8{ "#", self.uri.fragment.? });
|
return try std.mem.concat(alloc, u8, &[_][]const u8{ "#", self.uri.fragment.? });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_searchParams(self: *URL) *URLSearchParams {
|
||||||
|
return &self.search_params;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn _toJSON(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn _toJSON(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
||||||
return try self.get_href(alloc);
|
return try self.get_href(alloc);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://url.spec.whatwg.org/#interface-urlsearchparams
|
// https://url.spec.whatwg.org/#interface-urlsearchparams
|
||||||
|
// TODO array like
|
||||||
pub const URLSearchParams = struct {
|
pub const URLSearchParams = struct {
|
||||||
|
values: query.Values,
|
||||||
|
|
||||||
pub const mem_guarantied = true;
|
pub const mem_guarantied = true;
|
||||||
|
|
||||||
|
pub fn constructor(alloc: std.mem.Allocator, init: ?[]const u8) !URLSearchParams {
|
||||||
|
return .{
|
||||||
|
.values = try query.parseQuery(alloc, init orelse ""),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *URLSearchParams, _: std.mem.Allocator) void {
|
||||||
|
self.values.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_size(self: *URLSearchParams) u32 {
|
||||||
|
return @intCast(self.values.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _append(self: *URLSearchParams, name: []const u8, value: []const u8) !void {
|
||||||
|
try self.values.append(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _delete(self: *URLSearchParams, name: []const u8, value: ?[]const u8) !void {
|
||||||
|
if (value) |v| return self.values.deleteValue(name, v);
|
||||||
|
|
||||||
|
self.values.delete(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _get(self: *URLSearchParams, name: []const u8) ?[]const u8 {
|
||||||
|
return self.values.first(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO return generates an error: caught unexpected error 'TypeLookup'
|
||||||
|
// pub fn _getAll(self: *URLSearchParams, name: []const u8) [][]const u8 {
|
||||||
|
// try self.values.get(name);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
pub fn _sort(_: *URLSearchParams) void {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
@@ -154,6 +202,21 @@ pub fn testExecFn(
|
|||||||
.{ .src = "url.pathname", .ex = "/path" },
|
.{ .src = "url.pathname", .ex = "/path" },
|
||||||
.{ .src = "url.search", .ex = "?query" },
|
.{ .src = "url.search", .ex = "?query" },
|
||||||
.{ .src = "url.hash", .ex = "#fragment" },
|
.{ .src = "url.hash", .ex = "#fragment" },
|
||||||
|
.{ .src = "url.searchParams.get('query')", .ex = "" },
|
||||||
};
|
};
|
||||||
try checkCases(js_env, &url);
|
try checkCases(js_env, &url);
|
||||||
|
|
||||||
|
var qs = [_]Case{
|
||||||
|
.{ .src = "var url = new URL('https://foo.bar/path?a=~&b=%7E')", .ex = "undefined" },
|
||||||
|
.{ .src = "url.searchParams.get('a')", .ex = "~" },
|
||||||
|
.{ .src = "url.searchParams.get('b')", .ex = "~" },
|
||||||
|
.{ .src = "url.searchParams.append('c', 'foo')", .ex = "undefined" },
|
||||||
|
.{ .src = "url.searchParams.get('c')", .ex = "foo" },
|
||||||
|
.{ .src = "url.searchParams.size", .ex = "3" },
|
||||||
|
.{ .src = "url.searchParams.delete('c', 'foo')", .ex = "undefined" },
|
||||||
|
.{ .src = "url.searchParams.get('c')", .ex = "" },
|
||||||
|
.{ .src = "url.searchParams.delete('a')", .ex = "undefined" },
|
||||||
|
.{ .src = "url.searchParams.get('a')", .ex = "" },
|
||||||
|
};
|
||||||
|
try checkCases(js_env, &qs);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user