diff --git a/lib/std/Url.zig b/lib/std/Url.zig new file mode 100644 index 0000000000..8887f5de92 --- /dev/null +++ b/lib/std/Url.zig @@ -0,0 +1,98 @@ +scheme: []const u8, +host: []const u8, +path: []const u8, +port: ?u16, + +/// TODO: redo this implementation according to RFC 1738. This code is only a +/// placeholder for now. +pub fn parse(s: []const u8) !Url { + var scheme_end: usize = 0; + var host_start: usize = 0; + var host_end: usize = 0; + var path_start: usize = 0; + var port_start: usize = 0; + var port_end: usize = 0; + var state: enum { + scheme, + scheme_slash1, + scheme_slash2, + host, + port, + path, + } = .scheme; + + for (s) |b, i| switch (state) { + .scheme => switch (b) { + ':' => { + state = .scheme_slash1; + scheme_end = i; + }, + else => {}, + }, + .scheme_slash1 => switch (b) { + '/' => { + state = .scheme_slash2; + }, + else => return error.InvalidUrl, + }, + .scheme_slash2 => switch (b) { + '/' => { + state = .host; + host_start = i + 1; + }, + else => return error.InvalidUrl, + }, + .host => switch (b) { + ':' => { + state = .port; + host_end = i; + port_start = i + 1; + }, + '/' => { + state = .path; + host_end = i; + path_start = i; + }, + else => {}, + }, + .port => switch (b) { + '/' => { + port_end = i; + state = .path; + path_start = i; + }, + else => {}, + }, + .path => {}, + }; + + const port_slice = s[port_start..port_end]; + const port = if (port_slice.len == 0) null else try std.fmt.parseInt(u16, port_slice, 10); + + return .{ + .scheme = s[0..scheme_end], + .host = s[host_start..host_end], + .path = s[path_start..], + .port = port, + }; +} + +const Url = @This(); +const std = @import("std.zig"); +const testing = std.testing; + +test "basic" { + const parsed = try parse("https://ziglang.org/download"); + try testing.expectEqualStrings("https", parsed.scheme); + try testing.expectEqualStrings("ziglang.org", parsed.host); + try testing.expectEqualStrings("/download", parsed.path); + try testing.expectEqual(@as(?u16, null), parsed.port); +} + +test "with port" { + const parsed = try parse("http://example:1337/"); + try testing.expectEqualStrings("http", parsed.scheme); + try testing.expectEqualStrings("example", parsed.host); + try testing.expectEqualStrings("/", parsed.path); + try testing.expectEqual(@as(?u16, 1337), parsed.port); +} diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index 8c1a63cd46..4177676d96 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -105,7 +105,9 @@ pub fn addCertsFromFile( // This is possible by computing the decoded length and reserving the space // for the decoded bytes first. const decoded_size_upper_bound = size / 4 * 3; - try cb.bytes.ensureUnusedCapacity(gpa, decoded_size_upper_bound + size); + const needed_capacity = std.math.cast(u32, decoded_size_upper_bound + size) orelse + return error.CertificateAuthorityBundleTooBig; + try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity); const end_reserved = cb.bytes.items.len + decoded_size_upper_bound; const buffer = cb.bytes.allocatedSlice()[end_reserved..]; const end_index = try file.readAll(buffer); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 1d10870312..4e5bd3da0c 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -3,6 +3,7 @@ const assert = std.debug.assert; const http = std.http; const net = std.net; const Client = @This(); +const Url = std.Url; allocator: std.mem.Allocator, headers: std.ArrayListUnmanaged(u8) = .{}, @@ -19,14 +20,7 @@ pub const Request = struct { pub const Protocol = enum { http, https }; pub const Options = struct { - family: Family = .any, - protocol: Protocol = .https, method: http.Method = .GET, - host: []const u8 = "localhost", - path: []const u8 = "/", - port: u16 = 0, - - pub const Family = enum { any, ip4, ip6 }; }; pub fn deinit(req: *Request) void { @@ -90,20 +84,27 @@ pub fn deinit(client: *Client) void { client.* = undefined; } -pub fn request(client: *Client, options: Request.Options) !Request { +pub fn request(client: *Client, url: Url, options: Request.Options) !Request { + const protocol = std.meta.stringToEnum(Request.Protocol, url.scheme) orelse + return error.UnsupportedUrlScheme; + const port: u16 = url.port orelse switch (protocol) { + .http => 80, + .https => 443, + }; + var req: Request = .{ .client = client, - .stream = try net.tcpConnectToHost(client.allocator, options.host, options.port), - .protocol = options.protocol, + .stream = try net.tcpConnectToHost(client.allocator, url.host, port), + .protocol = protocol, .tls_client = undefined, }; client.active_requests += 1; errdefer req.deinit(); - switch (options.protocol) { + switch (protocol) { .http => {}, .https => { - req.tls_client = try std.crypto.tls.Client.init(req.stream, client.ca_bundle, options.host); + req.tls_client = try std.crypto.tls.Client.init(req.stream, client.ca_bundle, url.host); }, } @@ -111,19 +112,19 @@ pub fn request(client: *Client, options: Request.Options) !Request { client.allocator, @tagName(options.method).len + 1 + - options.path.len + + url.path.len + " HTTP/1.1\r\nHost: ".len + - options.host.len + + url.host.len + "\r\nUpgrade-Insecure-Requests: 1\r\n".len + client.headers.items.len + 2, // for the \r\n at the end of headers ); req.headers.appendSliceAssumeCapacity(@tagName(options.method)); req.headers.appendSliceAssumeCapacity(" "); - req.headers.appendSliceAssumeCapacity(options.path); + req.headers.appendSliceAssumeCapacity(url.path); req.headers.appendSliceAssumeCapacity(" HTTP/1.1\r\nHost: "); - req.headers.appendSliceAssumeCapacity(options.host); - switch (options.protocol) { + req.headers.appendSliceAssumeCapacity(url.host); + switch (protocol) { .https => req.headers.appendSliceAssumeCapacity("\r\nUpgrade-Insecure-Requests: 1\r\n"), .http => req.headers.appendSliceAssumeCapacity("\r\n"), } diff --git a/lib/std/std.zig b/lib/std/std.zig index 4bfb44d12f..1cbcd6bad7 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -42,6 +42,7 @@ pub const Target = @import("target.zig").Target; pub const Thread = @import("Thread.zig"); pub const Treap = @import("treap.zig").Treap; pub const Tz = tz.Tz; +pub const Url = @import("Url.zig"); pub const array_hash_map = @import("array_hash_map.zig"); pub const atomic = @import("atomic.zig");