mirror of
https://github.com/ziglang/zig.git
synced 2026-02-12 20:37:54 +00:00
add url parsing to the std lib
This commit is contained in:
parent
c71c562486
commit
5b8b5f2505
98
lib/std/Url.zig
Normal file
98
lib/std/Url.zig
Normal file
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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"),
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user