Add Url type

This commit is contained in:
jaina heartles 2022-09-10 15:29:08 -07:00
parent 0a42274b27
commit 994c9e8cf2
2 changed files with 161 additions and 0 deletions

160
src/util/Url.zig Normal file
View file

@ -0,0 +1,160 @@
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;
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);
}

View file

@ -4,6 +4,7 @@ pub const ciutf8 = @import("./ciutf8.zig");
pub const Uuid = @import("./Uuid.zig");
pub const DateTime = @import("./DateTime.zig");
pub const PathIter = @import("./PathIter.zig");
pub const Url = @import("./Url.zig");
pub fn cloneStr(str: []const u8, alloc: std.mem.Allocator) ![]const u8 {
var new = try alloc.alloc(u8, str.len);