2022-09-10 22:29:08 +00:00
|
|
|
const Url = @This();
|
|
|
|
const std = @import("std");
|
|
|
|
|
|
|
|
scheme: []const u8,
|
|
|
|
hostport: []const u8,
|
|
|
|
path: []const u8,
|
|
|
|
query: []const u8,
|
|
|
|
fragment: []const u8,
|
|
|
|
|
|
|
|
pub fn parse(url: []const u8) !Url {
|
|
|
|
const scheme_end = for (url) |ch, i| {
|
|
|
|
if (ch == ':') break i;
|
|
|
|
} else return error.InvalidUrl;
|
|
|
|
|
|
|
|
if (url.len < scheme_end + 3 or url[scheme_end + 1] != '/' or url[scheme_end + 1] != '/') return error.InvalidUrl;
|
|
|
|
|
|
|
|
const hostport_start = scheme_end + 3;
|
|
|
|
const hostport_end = for (url[hostport_start..]) |ch, i| {
|
|
|
|
if (ch == '/' or ch == '?' or ch == '#') break i + hostport_start;
|
|
|
|
} else url.len;
|
|
|
|
|
|
|
|
const path_end = for (url[hostport_end..]) |ch, i| {
|
|
|
|
if (ch == '?' or ch == '#') break i + hostport_end;
|
|
|
|
} else url.len;
|
|
|
|
|
|
|
|
const query_end = if (!(url.len > path_end and url[path_end] == '?'))
|
|
|
|
path_end
|
|
|
|
else for (url[path_end..]) |ch, i| {
|
|
|
|
if (ch == '#') break i + path_end;
|
|
|
|
} else url.len;
|
|
|
|
|
|
|
|
const query = url[path_end..query_end];
|
|
|
|
const fragment = url[query_end..];
|
|
|
|
|
|
|
|
return Url{
|
|
|
|
.scheme = url[0..scheme_end],
|
|
|
|
.hostport = url[hostport_start..hostport_end],
|
|
|
|
.path = url[hostport_end..path_end],
|
|
|
|
.query = if (query.len > 0) query[1..] else query,
|
|
|
|
.fragment = if (fragment.len > 0) fragment[1..] else fragment,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn getQuery(self: Url, param: []const u8) ?[]const u8 {
|
|
|
|
var key_start: usize = 0;
|
2022-10-08 07:51:57 +00:00
|
|
|
std.log.debug("query: {s}", .{self.query});
|
2022-09-10 22:29:08 +00:00
|
|
|
while (key_start < self.query.len) {
|
|
|
|
const key_end = for (self.query[key_start..]) |ch, i| {
|
|
|
|
if (ch == '=') break key_start + i;
|
|
|
|
} else return null;
|
|
|
|
|
|
|
|
const val_start = key_end + 1;
|
|
|
|
const val_end = for (self.query[val_start..]) |ch, i| {
|
|
|
|
if (ch == '&') break val_start + i;
|
|
|
|
} else self.query.len;
|
|
|
|
|
|
|
|
const key = self.query[key_start..key_end];
|
|
|
|
if (std.mem.eql(u8, key, param)) return self.query[val_start..val_end];
|
|
|
|
|
|
|
|
key_start = val_end + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn strDecode(buf: []u8, str: []const u8) ![]u8 {
|
|
|
|
var str_i: usize = 0;
|
|
|
|
var buf_i: usize = 0;
|
|
|
|
while (str_i < str.len) : ({
|
|
|
|
str_i += 1;
|
|
|
|
buf_i += 1;
|
|
|
|
}) {
|
|
|
|
if (buf_i >= buf.len) return error.NoSpaceLeft;
|
|
|
|
const ch = str[str_i];
|
|
|
|
if (ch == '%') {
|
|
|
|
if (str.len < str_i + 2) return error.BadEscape;
|
|
|
|
|
|
|
|
const hi = try std.fmt.charToDigit(str[str_i + 1], 16);
|
|
|
|
const lo = try std.fmt.charToDIgit(str[str_i + 2], 16);
|
|
|
|
str_i += 2;
|
|
|
|
|
|
|
|
buf[buf_i] = (hi << 4) | lo;
|
|
|
|
} else {
|
|
|
|
buf[buf_i] = str[str_i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf[0..buf_i];
|
|
|
|
}
|
|
|
|
|
|
|
|
fn expectEqualUrl(expected: Url, actual: Url) !void {
|
|
|
|
const t = @import("std").testing;
|
|
|
|
try t.expectEqualStrings(expected.scheme, actual.scheme);
|
|
|
|
try t.expectEqualStrings(expected.hostport, actual.hostport);
|
|
|
|
try t.expectEqualStrings(expected.path, actual.path);
|
|
|
|
try t.expectEqualStrings(expected.query, actual.query);
|
|
|
|
try t.expectEqualStrings(expected.fragment, actual.fragment);
|
|
|
|
}
|
|
|
|
test "Url" {
|
|
|
|
try expectEqualUrl(.{
|
|
|
|
.scheme = "https",
|
|
|
|
.hostport = "example.com",
|
|
|
|
.path = "",
|
|
|
|
.query = "",
|
|
|
|
.fragment = "",
|
|
|
|
}, try Url.parse("https://example.com"));
|
|
|
|
|
|
|
|
try expectEqualUrl(.{
|
|
|
|
.scheme = "https",
|
|
|
|
.hostport = "example.com:1234",
|
|
|
|
.path = "",
|
|
|
|
.query = "",
|
|
|
|
.fragment = "",
|
|
|
|
}, try Url.parse("https://example.com:1234"));
|
|
|
|
|
|
|
|
try expectEqualUrl(.{
|
|
|
|
.scheme = "http",
|
|
|
|
.hostport = "example.com",
|
|
|
|
.path = "/home",
|
|
|
|
.query = "",
|
|
|
|
.fragment = "",
|
|
|
|
}, try Url.parse("http://example.com/home"));
|
|
|
|
|
|
|
|
try expectEqualUrl(.{
|
|
|
|
.scheme = "https",
|
|
|
|
.hostport = "example.com",
|
|
|
|
.path = "",
|
|
|
|
.query = "query=abc",
|
|
|
|
.fragment = "",
|
|
|
|
}, try Url.parse("https://example.com?query=abc"));
|
|
|
|
|
|
|
|
try expectEqualUrl(.{
|
|
|
|
.scheme = "https",
|
|
|
|
.hostport = "example.com",
|
|
|
|
.path = "",
|
|
|
|
.query = "query=abc",
|
|
|
|
.fragment = "",
|
|
|
|
}, try Url.parse("https://example.com?query=abc"));
|
|
|
|
|
|
|
|
try expectEqualUrl(.{
|
|
|
|
.scheme = "https",
|
|
|
|
.hostport = "example.com",
|
|
|
|
.path = "/path/to/resource",
|
|
|
|
.query = "query=abc",
|
|
|
|
.fragment = "123",
|
|
|
|
}, try Url.parse("https://example.com/path/to/resource?query=abc#123"));
|
|
|
|
|
|
|
|
const t = @import("std").testing;
|
|
|
|
try t.expectError(error.InvalidUrl, Url.parse("https:example.com"));
|
|
|
|
try t.expectError(error.InvalidUrl, Url.parse("example.com"));
|
|
|
|
}
|
|
|
|
|
|
|
|
test "Url.getQuery" {
|
|
|
|
const url = try Url.parse("https://example.com?a=xyz&b=jkl");
|
|
|
|
const t = @import("std").testing;
|
|
|
|
|
|
|
|
try t.expectEqualStrings("xyz", url.getQuery("a").?);
|
|
|
|
try t.expectEqualStrings("jkl", url.getQuery("b").?);
|
|
|
|
try t.expect(url.getQuery("c") == null);
|
|
|
|
try t.expect(url.getQuery("xyz") == null);
|
|
|
|
}
|