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; std.log.debug("query: {s}", .{self.query}); 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); }