diff --git a/src/app.zig b/src/app.zig index 7a214c3d..7cce8bbe 100644 --- a/src/app.zig +++ b/src/app.zig @@ -38,7 +38,7 @@ pub const App = struct { .allocator = allocator, .telemetry = undefined, .app_dir_path = app_dir_path, - .http_client = try HttpClient.init(allocator, 5, null), + .http_client = try HttpClient.init(allocator, 5), }; app.telemetry = Telemetry.init(app, run_mode); diff --git a/src/http/client.zig b/src/http/client.zig index ddc188f9..cf30c57f 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -48,8 +48,8 @@ pub const Client = struct { root_ca: tls.config.CertBundle, // we only allow passing in a root_ca for testing - pub fn init(allocator: Allocator, max_concurrent: usize, root_ca_: ?tls.config.CertBundle) !Client { - var root_ca = root_ca_ orelse try tls.config.CertBundle.fromSystem(allocator); + pub fn init(allocator: Allocator, max_concurrent: usize) !Client { + var root_ca = try tls.config.CertBundle.fromSystem(allocator); errdefer root_ca.deinit(allocator); const state_pool = try StatePool.init(allocator, max_concurrent); @@ -123,6 +123,9 @@ pub const Request = struct { // we'll set it based on `uri` before issuing the request. _has_host_header: bool, + // Whether or not we should verify that the host matches the certificate CN + _tls_verify_host: bool = true, + pub const Method = enum { GET, PUT, @@ -202,9 +205,12 @@ pub const Request = struct { } // TODO timeout - const SendSyncOpts = struct {}; + const SendSyncOpts = struct { + tls_verify_host: bool = true, + }; // Makes an synchronous request - pub fn sendSync(self: *Request, _: SendSyncOpts) anyerror!Response { + pub fn sendSync(self: *Request, opts: SendSyncOpts) anyerror!Response { + self._tls_verify_host = opts.tls_verify_host; try self.prepareInitialSend(); return self.doSendSync(); } @@ -224,9 +230,12 @@ pub const Request = struct { }; } - const SendAsyncOpts = struct {}; + const SendAsyncOpts = struct { + tls_verify_host: bool = true, + }; // Makes an asynchronous request - pub fn sendAsync(self: *Request, loop: anytype, handler: anytype, _: SendAsyncOpts) !void { + pub fn sendAsync(self: *Request, loop: anytype, handler: anytype, opts: SendAsyncOpts) !void { + self._tls_verify_host = opts.tls_verify_host; try self.prepareInitialSend(); return self.doSendAsync(loop, handler); } @@ -236,8 +245,7 @@ pub const Request = struct { } fn doSendAsync(self: *Request, loop: anytype, handler: anytype) !void { - // TODO: change this to nonblocking (false) when we have promise resolution - const socket, const address = try self.createSocket(true); + const socket, const address = try self.createSocket(false); const AsyncHandlerT = AsyncHandler(@TypeOf(handler), @TypeOf(loop)); const async_handler = try self.arena.create(AsyncHandlerT); @@ -256,6 +264,7 @@ pub const Request = struct { .tls_client = try tls.asyn.Client(AsyncHandlerT.TLSHandler).init(self.arena, .{ .handler = async_handler }, .{ .host = self.host(), .root_ca = self._client.root_ca, + .insecure_skip_verify = self._tls_verify_host == false, // .key_log_callback = tls.config.key_log.callback }), }; @@ -367,6 +376,7 @@ pub const Request = struct { if (@hasDecl(posix.TCP, "NODELAY")) { try posix.setsockopt(socket, posix.IPPROTO.TCP, posix.TCP.NODELAY, &std.mem.toBytes(@as(c_int, 1))); } + self._socket = socket; return .{ socket, address }; } @@ -544,7 +554,6 @@ fn AsyncHandler(comptime H: type, comptime L: type) type { const n = n_ catch |err| { return self.handleError("Read error", err); }; - if (n == 0) { return self.handleError("Connection closed", error.ConnectionResetByPeer); } @@ -662,12 +671,12 @@ fn AsyncHandler(comptime H: type, comptime L: type) type { switch (self.protocol) { .tls_client => |*tls_client| { + handler.receive(); try tls_client.onConnect(); // when TLS is active, from a network point of view // it's no longer a strict REQ->RES. We pretty much // have to constantly receive data (e.g. to process // the handshake) - handler.receive(); }, .plain => { // queue everything up @@ -847,6 +856,7 @@ const SyncHandler = struct { .tls = try tls.client(std.net.Stream{ .handle = socket }, .{ .host = request.host(), .root_ca = request._client.root_ca, + .insecure_skip_verify = request._tls_verify_host == false, // .key_log_callback = tls.config.key_log.callback, }), }; @@ -1704,13 +1714,12 @@ test "HttpClient: sync no body" { } test "HttpClient: sync tls no body" { - // https://github.com/ianic/tls.zig/issues/10 - for (0..1) |_| { + for (0..5) |_| { var client = try testClient(); defer client.deinit(); var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/simple"); - var res = try req.sendSync(.{}); + var res = try req.sendSync(.{ .tls_verify_host = false }); try testing.expectEqual(null, try res.next()); try testing.expectEqual(200, res.header.status); @@ -1741,14 +1750,13 @@ test "HttpClient: sync tls with body" { defer arr.deinit(testing.allocator); try arr.ensureTotalCapacity(testing.allocator, 20); - // https://github.com/ianic/tls.zig/issues/10 - for (0..1) |_| { + for (0..5) |_| { defer arr.clearRetainingCapacity(); var client = try testClient(); defer client.deinit(); var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/body"); - var res = try req.sendSync(.{}); + var res = try req.sendSync(.{ .tls_verify_host = false }); while (try res.next()) |data| { arr.appendSliceAssumeCapacity(data); @@ -1766,14 +1774,13 @@ test "HttpClient: sync redirect from TLS to Plaintext" { defer arr.deinit(testing.allocator); try arr.ensureTotalCapacity(testing.allocator, 20); - // https://github.com/ianic/tls.zig/issues/10 - for (0..1) |_| { + for (0..5) |_| { defer arr.clearRetainingCapacity(); var client = try testClient(); defer client.deinit(); var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/redirect/insecure"); - var res = try req.sendSync(.{}); + var res = try req.sendSync(.{ .tls_verify_host = false }); while (try res.next()) |data| { arr.appendSliceAssumeCapacity(data); @@ -1793,14 +1800,13 @@ test "HttpClient: sync redirect plaintext to TLS" { defer arr.deinit(testing.allocator); try arr.ensureTotalCapacity(testing.allocator, 20); - // https://github.com/ianic/tls.zig/issues/10 - for (0..1) |_| { + for (0..5) |_| { defer arr.clearRetainingCapacity(); var client = try testClient(); defer client.deinit(); var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/redirect/secure"); - var res = try req.sendSync(.{}); + var res = try req.sendSync(.{ .tls_verify_host = false }); while (try res.next()) |data| { arr.appendSliceAssumeCapacity(data); @@ -1818,7 +1824,7 @@ test "HttpClient: sync GET redirect" { defer client.deinit(); var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/redirect"); - var res = try req.sendSync(.{}); + var res = try req.sendSync(.{ .tls_verify_host = false }); try testing.expectEqual("over 9000!", try res.next()); try testing.expectEqual(201, res.header.status); @@ -1866,9 +1872,6 @@ test "HttpClient: async no body" { var handler = try CaptureHandler.init(); defer handler.deinit(); - var loop = try jsruntime.Loop.init(testing.allocator); - defer loop.deinit(); - var req = try client.request(.GET, "HTTP://127.0.0.1:9582/http_client/simple"); try req.sendAsync(&handler.loop, &handler, .{}); try handler.waitUntilDone(); @@ -1886,11 +1889,8 @@ test "HttpClient: async no body" { // var handler = try CaptureHandler.init(); // defer handler.deinit(); -// var loop = try jsruntime.Loop.init(testing.allocator); -// defer loop.deinit(); - // var req = try client.request(.GET, "HTTPs://127.0.0.1:9581/http_client/simple"); -// try req.sendAsync(&handler.loop, &handler, .{}); +// try req.sendAsync(&handler.loop, &handler, .{ .tls_verify_host = false }); // try handler.waitUntilDone(); // const res = handler.response; @@ -1929,9 +1929,6 @@ test "HttpClient: async redirect" { var handler = try CaptureHandler.init(); defer handler.deinit(); - var loop = try jsruntime.Loop.init(testing.allocator); - defer loop.deinit(); - var req = try client.request(.GET, "HTTP://127.0.0.1:9582/http_client/redirect"); try req.sendAsync(&handler.loop, &handler, .{}); @@ -2094,7 +2091,5 @@ fn testReader(state: *State, res: *TestResponse, data: []const u8) !void { } fn testClient() !Client { - const test_dir = try std.fs.cwd().openDir("tests", .{}); - const root_ca = try tls.config.CertBundle.fromFile(testing.allocator, test_dir, "test_cert.pem"); - return try Client.init(testing.allocator, 1, root_ca); + return try Client.init(testing.allocator, 1); } diff --git a/src/main_tests.zig b/src/main_tests.zig index 37963b2d..41560b99 100644 --- a/src/main_tests.zig +++ b/src/main_tests.zig @@ -88,7 +88,7 @@ fn testExecFn( std.debug.print("documentHTMLClose error: {s}\n", .{@errorName(err)}); }; - var http_client = try @import("http/client.zig").Client.init(alloc, 5, null ); + var http_client = try @import("http/client.zig").Client.init(alloc, 5); defer http_client.deinit(); try js_env.setUserContext(.{ diff --git a/src/main_unit_tests.zig b/src/main_unit_tests.zig index 94199346..caecc188 100644 --- a/src/main_unit_tests.zig +++ b/src/main_unit_tests.zig @@ -52,13 +52,13 @@ test "tests:beforeAll" { { const address = try std.net.Address.parseIp("127.0.0.1", 9582); - const thread = try std.Thread.spawn(.{}, serveHTTP, .{ address }); + const thread = try std.Thread.spawn(.{}, serveHTTP, .{address}); thread.detach(); } { const address = try std.net.Address.parseIp("127.0.0.1", 9581); - const thread = try std.Thread.spawn(.{}, serveHTTPS, .{ address }); + const thread = try std.Thread.spawn(.{}, serveHTTPS, .{address}); thread.detach(); } @@ -67,6 +67,7 @@ test "tests:beforeAll" { const thread = try std.Thread.spawn(.{}, serveCDP, .{address}); thread.detach(); } + // need to wait for the servers to be listening, else tests will fail because // they aren't able to connect. wg.wait(); @@ -144,11 +145,6 @@ fn serveHTTP(address: std.net.Address) !void { // isn't a jerk. fn serveHTTPS(address: std.net.Address) !void { const allocator = gpa.allocator(); - const test_dir = try std.fs.cwd().openDir("tests", .{}); - - // openssl req -x509 -newkey rsa:4096 -keyout tests/test_key.pem -out tests/test_cert.pem -sha256 -days 3650 -nodes -subj "/CN=127.0.0.1" - var auth = try tls.config.CertKeyPair.load(allocator, test_dir, "test_cert.pem", "test_key.pem"); - defer auth.deinit(allocator); var listener = try address.listen(.{ .reuse_address = true }); defer listener.deinit(); @@ -174,7 +170,7 @@ fn serveHTTPS(address: std.net.Address) !void { }; defer stream.close(); - var conn = try tls.server(stream, .{ .auth = &auth }); + var conn = try tls.server(stream, .{ .auth = null }); defer conn.close() catch {}; var pos: usize = 0; @@ -208,7 +204,7 @@ fn serveHTTPS(address: std.net.Address) !void { const to_send = rand.intRangeAtMost(usize, 1, unsent.len); const sent = try conn.write(unsent[0..to_send]); unsent = unsent[sent..]; - // std.time.sleep(std.time.ns_per_us * 5); + std.time.sleep(std.time.ns_per_us * 5); } break; } diff --git a/src/wpt/run.zig b/src/wpt/run.zig index ee7e9840..8677188b 100644 --- a/src/wpt/run.zig +++ b/src/wpt/run.zig @@ -28,7 +28,7 @@ const Loop = jsruntime.Loop; const Env = jsruntime.Env; const Window = @import("../html/window.zig").Window; const storage = @import("../storage/storage.zig"); -const Client = @import("asyncio").Client; +const HttpClient = @import("../http/client.zig").Client; const Types = @import("../main_wpt.zig").Types; const UserContext = @import("../main_wpt.zig").UserContext; @@ -55,13 +55,13 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const var loop = try Loop.init(alloc); defer loop.deinit(); - var cli = Client{ .allocator = alloc }; - defer cli.deinit(); + var http_client = try HttpClient.init(alloc, 2); + defer http_client.deinit(); var js_env: Env = undefined; Env.init(&js_env, alloc, &loop, UserContext{ .document = html_doc, - .httpClient = &cli, + .http_client = &http_client, }); defer js_env.deinit(); diff --git a/tests/test_cert.pem b/tests/test_cert.pem deleted file mode 100644 index 343d0afc..00000000 --- a/tests/test_cert.pem +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFCTCCAvGgAwIBAgIUQizPG9ybhujB5BEDeapY//wFwgwwDQYJKoZIhvcNAQEL -BQAwFDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTI1MDMxOTEyMjYzNFoXDTM1MDMx -NzEyMjYzNFowFDESMBAGA1UEAwwJMTI3LjAuMC4xMIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEAzDHVQ7R3OxOxhoDILfEBkvSGcE6aeI+kEV52qmFzlFM9 -HCT+m4pSaMhtlsqwUA1N5FgkPD9wmka1EqrExnGdFbpZ6uWe0nTUMhNag5pHBT2/ -aYjEWcs/6C+lyve04w12jgkzvQcgbrQZTwVe2cxA2GTnggtiMrwhIuDjKpO12Yy5 -Fn+SrbhLtD7gYM3EOqrP7UukA/BKjYCEBKYkjBXjdODU7+xp8AYUwMQcOgIpMVAR -yJIGbKZ8PfLrmt8EaSHNe0d44YeLgRlOd9raw9aSKxdNDNYWYfAIDtV92gRhH24q -mi+THG8MUeAM7OpssQNNcH0En5ZmOGcKhwaTYM41jNZvPosVfrRTVGQimjrz42IZ -QUx5V+JxY5VMWrwxWxjPallKVN+2LCLdBV6I+zCrwghvQgc4UL8F6hTVg7aIez7E -oNamG77ooHc53eHBmIFGokhuPhFs0HVRIQdFO7Sm1Y1Lk6kek0SlZPge1x0nODRS -tPBpYJlnfzIzu7k8c9u7u7VhUqkRphxKz5xanKslozCjzk5UnmKbtQDJKQOcv+OO -pAm1InDDlw84GKw77aXfQQIJmej0sbDg0NadTMlaOpKnbh7VZBs2VYFmf42B1r1m -4yOdqdnDS/q0m+tkFZbBhDXuSOAkutNT7MUJXdL+/S68k+oHkGXMG/xkYNqTIuUC -AwEAAaNTMFEwHQYDVR0OBBYEFC0RmoMUNOMdBwdVnq2IjDG0B4HLMB8GA1UdIwQY -MBaAFC0RmoMUNOMdBwdVnq2IjDG0B4HLMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI -hvcNAQELBQADggIBAB/2OZOz/V3Qz9RG/XQfPtfDTzV2dzEszaha8CRat5TmEcg+ -s0AcqEyQd2Zn6DOcKeykRx6H6LtP0o+LyNmvRghoWbaz8zknikPotIu7Zw2cKfIq -2gjAq+sZCGkNsMoEVDurVtGmhgtnP1QVMUhW+2gsFXZQhtFjX2jYtMbuJ6A0or7q -Zf1ESCBemTFpgGvLZGiKwc4Re1vrNguyLhln2lYRfSh5UwSyL+KhnB67CpIV6rkc -jYOnN1VffxigqwBBeFWAZG12z8/5cwzmGno9f7w89s4rk1vo3YzZYq/jVs54lkS+ -JlVr8Guws4w1cb7fMRPf+Z28bBcPya4dPFrxbmi/9TK/AEEF2Yc7NgBCa3UI4B8j -2hdLKGq3Tj9Pi2XGIgJ4eejP2F1S2gOq2S10ix/kRyRTIcRaq1TvZt1WzaeLCZEk -gZQH5wS0BY/RvqqQf24APmzsHSFHFW7njpIEXJW4dvnVLJDZvk9r4cJXoTKTXjTb -wyOa4zHkrXW6a3OO+dehFyII6byqOz0oD3LVFF/Cxm3cUKaCBs7QrEDDri3bDu1w -Z/KJsOUV/Rv7YhgonfnAbKfmXAabKeGBSDA1jZG/cXE2qAgW49PUGsjhXLn0ENz4 -nOwCbUGCpnX8m5Myxx5rbJDliG3h+F937o7kyCDPqYaGRjGXbBjP4oaMhaHR ------END CERTIFICATE----- diff --git a/tests/test_key.pem b/tests/test_key.pem deleted file mode 100644 index 6fa8703c..00000000 --- a/tests/test_key.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -Z31M1oPIcKZ4ayKKiG5GYX/+juUlMA== ------END PRIVATE KEY-----