Add Url type
This commit is contained in:
parent
0a42274b27
commit
994c9e8cf2
2 changed files with 161 additions and 0 deletions
160
src/util/Url.zig
Normal file
160
src/util/Url.zig
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue