authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-25 23:45:49-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
log5b8b5f2505ca63dd62f487dcd0357112f959dde7
treec9250a957238686e30c890371ef654bfc246466d
parentc71c562486c5b3e92a1ea936f3c7b848853b2d5c

add url parsing to the std lib


4 files changed, 120 insertions(+), 18 deletions(-)

lib/std/Url.zig created+98
...@@ -0,0 +1,98 @@
1scheme: []const u8,
2host: []const u8,
3path: []const u8,
4port: ?u16,
5
6/// TODO: redo this implementation according to RFC 1738. This code is only a
7/// placeholder for now.
8pub fn parse(s: []const u8) !Url {
9 var scheme_end: usize = 0;
10 var host_start: usize = 0;
11 var host_end: usize = 0;
12 var path_start: usize = 0;
13 var port_start: usize = 0;
14 var port_end: usize = 0;
15 var state: enum {
16 scheme,
17 scheme_slash1,
18 scheme_slash2,
19 host,
20 port,
21 path,
22 } = .scheme;
23
24 for (s) |b, i| switch (state) {
25 .scheme => switch (b) {
26 ':' => {
27 state = .scheme_slash1;
28 scheme_end = i;
29 },
30 else => {},
31 },
32 .scheme_slash1 => switch (b) {
33 '/' => {
34 state = .scheme_slash2;
35 },
36 else => return error.InvalidUrl,
37 },
38 .scheme_slash2 => switch (b) {
39 '/' => {
40 state = .host;
41 host_start = i + 1;
42 },
43 else => return error.InvalidUrl,
44 },
45 .host => switch (b) {
46 ':' => {
47 state = .port;
48 host_end = i;
49 port_start = i + 1;
50 },
51 '/' => {
52 state = .path;
53 host_end = i;
54 path_start = i;
55 },
56 else => {},
57 },
58 .port => switch (b) {
59 '/' => {
60 port_end = i;
61 state = .path;
62 path_start = i;
63 },
64 else => {},
65 },
66 .path => {},
67 };
68
69 const port_slice = s[port_start..port_end];
70 const port = if (port_slice.len == 0) null else try std.fmt.parseInt(u16, port_slice, 10);
71
72 return .{
73 .scheme = s[0..scheme_end],
74 .host = s[host_start..host_end],
75 .path = s[path_start..],
76 .port = port,
77 };
78}
79
80const Url = @This();
81const std = @import("std.zig");
82const testing = std.testing;
83
84test "basic" {
85 const parsed = try parse("https://ziglang.org/download");
86 try testing.expectEqualStrings("https", parsed.scheme);
87 try testing.expectEqualStrings("ziglang.org", parsed.host);
88 try testing.expectEqualStrings("/download", parsed.path);
89 try testing.expectEqual(@as(?u16, null), parsed.port);
90}
91
92test "with port" {
93 const parsed = try parse("http://example:1337/");
94 try testing.expectEqualStrings("http", parsed.scheme);
95 try testing.expectEqualStrings("example", parsed.host);
96 try testing.expectEqualStrings("/", parsed.path);
97 try testing.expectEqual(@as(?u16, 1337), parsed.port);
98}
lib/std/crypto/Certificate/Bundle.zig+3-1
...@@ -105,7 +105,9 @@ pub fn addCertsFromFile(...@@ -105,7 +105,9 @@ pub fn addCertsFromFile(
105 // This is possible by computing the decoded length and reserving the space105 // This is possible by computing the decoded length and reserving the space
106 // for the decoded bytes first.106 // for the decoded bytes first.
107 const decoded_size_upper_bound = size / 4 * 3;107 const decoded_size_upper_bound = size / 4 * 3;
108 try cb.bytes.ensureUnusedCapacity(gpa, decoded_size_upper_bound + size);108 const needed_capacity = std.math.cast(u32, decoded_size_upper_bound + size) orelse
109 return error.CertificateAuthorityBundleTooBig;
110 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);
109 const end_reserved = cb.bytes.items.len + decoded_size_upper_bound;111 const end_reserved = cb.bytes.items.len + decoded_size_upper_bound;
110 const buffer = cb.bytes.allocatedSlice()[end_reserved..];112 const buffer = cb.bytes.allocatedSlice()[end_reserved..];
111 const end_index = try file.readAll(buffer);113 const end_index = try file.readAll(buffer);
lib/std/http/Client.zig+18-17
...@@ -3,6 +3,7 @@ const assert = std.debug.assert;...@@ -3,6 +3,7 @@ const assert = std.debug.assert;
3const http = std.http;3const http = std.http;
4const net = std.net;4const net = std.net;
5const Client = @This();5const Client = @This();
6const Url = std.Url;
67
7allocator: std.mem.Allocator,8allocator: std.mem.Allocator,
8headers: std.ArrayListUnmanaged(u8) = .{},9headers: std.ArrayListUnmanaged(u8) = .{},
...@@ -19,14 +20,7 @@ pub const Request = struct {...@@ -19,14 +20,7 @@ pub const Request = struct {
19 pub const Protocol = enum { http, https };20 pub const Protocol = enum { http, https };
2021
21 pub const Options = struct {22 pub const Options = struct {
22 family: Family = .any,
23 protocol: Protocol = .https,
24 method: http.Method = .GET,23 method: http.Method = .GET,
25 host: []const u8 = "localhost",
26 path: []const u8 = "/",
27 port: u16 = 0,
28
29 pub const Family = enum { any, ip4, ip6 };
30 };24 };
3125
32 pub fn deinit(req: *Request) void {26 pub fn deinit(req: *Request) void {
...@@ -90,20 +84,27 @@ pub fn deinit(client: *Client) void {...@@ -90,20 +84,27 @@ pub fn deinit(client: *Client) void {
90 client.* = undefined;84 client.* = undefined;
91}85}
9286
93pub fn request(client: *Client, options: Request.Options) !Request {87pub fn request(client: *Client, url: Url, options: Request.Options) !Request {
88 const protocol = std.meta.stringToEnum(Request.Protocol, url.scheme) orelse
89 return error.UnsupportedUrlScheme;
90 const port: u16 = url.port orelse switch (protocol) {
91 .http => 80,
92 .https => 443,
93 };
94
94 var req: Request = .{95 var req: Request = .{
95 .client = client,96 .client = client,
96 .stream = try net.tcpConnectToHost(client.allocator, options.host, options.port),97 .stream = try net.tcpConnectToHost(client.allocator, url.host, port),
97 .protocol = options.protocol,98 .protocol = protocol,
98 .tls_client = undefined,99 .tls_client = undefined,
99 };100 };
100 client.active_requests += 1;101 client.active_requests += 1;
101 errdefer req.deinit();102 errdefer req.deinit();
102103
103 switch (options.protocol) {104 switch (protocol) {
104 .http => {},105 .http => {},
105 .https => {106 .https => {
106 req.tls_client = try std.crypto.tls.Client.init(req.stream, client.ca_bundle, options.host);107 req.tls_client = try std.crypto.tls.Client.init(req.stream, client.ca_bundle, url.host);
107 },108 },
108 }109 }
109110
...@@ -111,19 +112,19 @@ pub fn request(client: *Client, options: Request.Options) !Request {...@@ -111,19 +112,19 @@ pub fn request(client: *Client, options: Request.Options) !Request {
111 client.allocator,112 client.allocator,
112 @tagName(options.method).len +113 @tagName(options.method).len +
113 1 +114 1 +
114 options.path.len +115 url.path.len +
115 " HTTP/1.1\r\nHost: ".len +116 " HTTP/1.1\r\nHost: ".len +
116 options.host.len +117 url.host.len +
117 "\r\nUpgrade-Insecure-Requests: 1\r\n".len +118 "\r\nUpgrade-Insecure-Requests: 1\r\n".len +
118 client.headers.items.len +119 client.headers.items.len +
119 2, // for the \r\n at the end of headers120 2, // for the \r\n at the end of headers
120 );121 );
121 req.headers.appendSliceAssumeCapacity(@tagName(options.method));122 req.headers.appendSliceAssumeCapacity(@tagName(options.method));
122 req.headers.appendSliceAssumeCapacity(" ");123 req.headers.appendSliceAssumeCapacity(" ");
123 req.headers.appendSliceAssumeCapacity(options.path);124 req.headers.appendSliceAssumeCapacity(url.path);
124 req.headers.appendSliceAssumeCapacity(" HTTP/1.1\r\nHost: ");125 req.headers.appendSliceAssumeCapacity(" HTTP/1.1\r\nHost: ");
125 req.headers.appendSliceAssumeCapacity(options.host);126 req.headers.appendSliceAssumeCapacity(url.host);
126 switch (options.protocol) {127 switch (protocol) {
127 .https => req.headers.appendSliceAssumeCapacity("\r\nUpgrade-Insecure-Requests: 1\r\n"),128 .https => req.headers.appendSliceAssumeCapacity("\r\nUpgrade-Insecure-Requests: 1\r\n"),
128 .http => req.headers.appendSliceAssumeCapacity("\r\n"),129 .http => req.headers.appendSliceAssumeCapacity("\r\n"),
129 }130 }
lib/std/std.zig+1
...@@ -42,6 +42,7 @@ pub const Target = @import("target.zig").Target;...@@ -42,6 +42,7 @@ pub const Target = @import("target.zig").Target;
42pub const Thread = @import("Thread.zig");42pub const Thread = @import("Thread.zig");
43pub const Treap = @import("treap.zig").Treap;43pub const Treap = @import("treap.zig").Treap;
44pub const Tz = tz.Tz;44pub const Tz = tz.Tz;
45pub const Url = @import("Url.zig");
4546
46pub const array_hash_map = @import("array_hash_map.zig");47pub const array_hash_map = @import("array_hash_map.zig");
47pub const atomic = @import("atomic.zig");48pub const atomic = @import("atomic.zig");