From c6538e1038f717d2474a43e90c83285f2c8da395 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 27 Mar 2025 18:02:30 +0800 Subject: [PATCH 1/3] Add an `insecure_disable_tls_host_verification` command line option When set, this disables the host verification of all HTTP requests. Available for both the fetch and serve mode. Also introduced an App.Config, for future command line options which need to be passed more deeply into the code. --- src/app.zig | 17 +++++++++--- src/cdp/testing.zig | 2 +- src/http/client.zig | 26 ++++++++++++------ src/main.zig | 60 +++++++++++++++++++++++++++++++---------- src/main_tests.zig | 2 +- src/main_unit_tests.zig | 2 +- src/testing.zig | 2 +- 7 files changed, 81 insertions(+), 30 deletions(-) diff --git a/src/app.zig b/src/app.zig index 7cce8bbe..5c90b546 100644 --- a/src/app.zig +++ b/src/app.zig @@ -17,11 +17,18 @@ pub const App = struct { app_dir_path: ?[]const u8, pub const RunMode = enum { - serve, + help, fetch, + serve, + version, }; - pub fn init(allocator: Allocator, run_mode: RunMode) !*App { + pub const Config = struct { + tls_verify_host: bool = true, + run_mode: RunMode, + }; + + pub fn init(allocator: Allocator, config: Config) !*App { const app = try allocator.create(App); errdefer allocator.destroy(app); @@ -38,9 +45,11 @@ pub const App = struct { .allocator = allocator, .telemetry = undefined, .app_dir_path = app_dir_path, - .http_client = try HttpClient.init(allocator, 5), + .http_client = try HttpClient.init(allocator, 5, .{ + .tls_verify_host = config.tls_verify_host, + }), }; - app.telemetry = Telemetry.init(app, run_mode); + app.telemetry = Telemetry.init(app, config.run_mode); return app; } diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig index 68f26014..891fc67f 100644 --- a/src/cdp/testing.zig +++ b/src/cdp/testing.zig @@ -281,7 +281,7 @@ const TestContext = struct { pub fn context() TestContext { return .{ - .app = App.init(std.testing.allocator, .serve) catch unreachable, + .app = App.init(std.testing.allocator, .{ .run_mode = .serve }) catch unreachable, .arena = std.heap.ArenaAllocator.init(std.testing.allocator), }; } diff --git a/src/http/client.zig b/src/http/client.zig index b0192287..4669a3e0 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -45,9 +45,13 @@ pub const Client = struct { allocator: Allocator, state_pool: StatePool, root_ca: tls.config.CertBundle, + tls_verify_host: bool = true, - // we only allow passing in a root_ca for testing - pub fn init(allocator: Allocator, max_concurrent: usize) !Client { + const Opts = struct { + tls_verify_host: bool = true, + }; + + pub fn init(allocator: Allocator, max_concurrent: usize, opts: Opts) !Client { var root_ca = try tls.config.CertBundle.fromSystem(allocator); errdefer root_ca.deinit(allocator); @@ -58,6 +62,7 @@ pub const Client = struct { .root_ca = root_ca, .allocator = allocator, .state_pool = state_pool, + .tls_verify_host = opts.tls_verify_host, }; } @@ -123,7 +128,7 @@ pub const Request = struct { _has_host_header: bool, // Whether or not we should verify that the host matches the certificate CN - _tls_verify_host: bool = true, + _tls_verify_host: bool, pub const Method = enum { GET, @@ -167,6 +172,7 @@ pub const Request = struct { ._client = client, ._redirect_count = 0, ._has_host_header = false, + ._tls_verify_host = client.tls_verify_host, }; } @@ -205,11 +211,13 @@ pub const Request = struct { // TODO timeout const SendSyncOpts = struct { - tls_verify_host: bool = true, + tls_verify_host: ?bool = null, }; // Makes an synchronous request pub fn sendSync(self: *Request, opts: SendSyncOpts) anyerror!Response { - self._tls_verify_host = opts.tls_verify_host; + if (opts.tls_verify_host) |override| { + self._tls_verify_host = override; + } try self.prepareInitialSend(); return self.doSendSync(); } @@ -230,11 +238,13 @@ pub const Request = struct { } const SendAsyncOpts = struct { - tls_verify_host: bool = true, + tls_verify_host: ?bool = null, }; // Makes an asynchronous request pub fn sendAsync(self: *Request, loop: anytype, handler: anytype, opts: SendAsyncOpts) !void { - self._tls_verify_host = opts.tls_verify_host; + if (opts.tls_verify_host) |override| { + self._tls_verify_host = override; + } try self.prepareInitialSend(); return self.doSendAsync(loop, handler); } @@ -2176,5 +2186,5 @@ fn testReader(state: *State, res: *TestResponse, data: []const u8) !void { } fn testClient() !Client { - return try Client.init(testing.allocator, 1); + return try Client.init(testing.allocator, 1, .{}); } diff --git a/src/main.zig b/src/main.zig index f18ec2a5..6e7a6526 100644 --- a/src/main.zig +++ b/src/main.zig @@ -22,6 +22,7 @@ const Allocator = std.mem.Allocator; const jsruntime = @import("jsruntime"); +const App = @import("app.zig").App; const Browser = @import("browser/browser.zig").Browser; const server = @import("server.zig"); @@ -69,9 +70,12 @@ pub fn main() !void { log.err("address (host:port) {any}\n", .{err}); return args.printUsageAndExit(false); }; - - var app = try @import("app.zig").App.init(alloc, .serve); + var app = try App.init(alloc, .{ + .run_mode = args.mode, + .tls_verify_host = opts.tls_verify_host, + }); defer app.deinit(); + app.telemetry.record(.{ .run = {} }); const timeout = std.time.ns_per_s * @as(u64, opts.timeout); @@ -83,7 +87,10 @@ pub fn main() !void { .fetch => |opts| { log.debug("Fetch mode: url {s}, dump {any}", .{ opts.url, opts.dump }); - var app = try @import("app.zig").App.init(alloc, .fetch); + var app = try App.init(alloc, .{ + .run_mode = args.mode, + .tls_verify_host = opts.tls_verify_host, + }); defer app.deinit(); app.telemetry.record(.{ .run = {} }); @@ -125,14 +132,7 @@ const Command = struct { mode: Mode, exec_name: []const u8, - const ModeType = enum { - help, - fetch, - serve, - version, - }; - - const Mode = union(ModeType) { + const Mode = union(App.RunMode) { help: bool, // false when being printed because of an error fetch: Fetch, serve: Serve, @@ -143,11 +143,13 @@ const Command = struct { host: []const u8, port: u16, timeout: u16, + tls_verify_host: bool, }; const Fetch = struct { url: []const u8, dump: bool = false, + tls_verify_host: bool, }; fn printUsageAndExit(self: *const Command, success: bool) void { @@ -164,6 +166,12 @@ const Command = struct { \\--dump Dumps document to stdout. \\ Defaults to false. \\ + \\--insecure_disable_tls_host_verification + \\ Disables host verification on all HTTP requests. + \\ This is an advanced option which should only be + \\ set if you understand and accept the risk of + \\ disabling host verification. + \\ \\serve command \\Starts a websocket CDP server \\Example: {s} serve --host 127.0.0.1 --port 9222 @@ -178,11 +186,18 @@ const Command = struct { \\--timeout Inactivity timeout in seconds before disconnecting clients \\ Defaults to 3 (seconds) \\ + \\--insecure_disable_tls_host_verification + \\ Disables host verification on all HTTP requests. + \\ This is an advanced option which should only be + \\ set if you understand and accept the risk of + \\ disabling host verification. + \\ \\version command \\Displays the version of {s} \\ \\help command \\Displays this message + \\ ; std.debug.print(usage, .{ self.exec_name, self.exec_name, self.exec_name, self.exec_name }); if (success) { @@ -204,7 +219,7 @@ fn parseArgs(allocator: Allocator) !Command { }; const mode_string = args.next() orelse ""; - const mode = std.meta.stringToEnum(Command.ModeType, mode_string) orelse blk: { + const mode = std.meta.stringToEnum(App.RunMode, mode_string) orelse blk: { const inferred_mode = inferMode(mode_string) orelse return cmd; // "command" wasn't a command but an option. We can't reset args, but // we can create a new one. Not great, but this fallback is temporary @@ -227,7 +242,7 @@ fn parseArgs(allocator: Allocator) !Command { return cmd; } -fn inferMode(opt: []const u8) ?Command.ModeType { +fn inferMode(opt: []const u8) ?App.RunMode { if (opt.len == 0) { return .serve; } @@ -260,6 +275,7 @@ fn parseServeArgs( var host: []const u8 = "127.0.0.1"; var port: u16 = 9222; var timeout: u16 = 3; + var tls_verify_host = true; while (args.next()) |opt| { if (std.mem.eql(u8, "--host", opt)) { @@ -297,6 +313,11 @@ fn parseServeArgs( continue; } + if (std.mem.eql(u8, "--insecure_tls_verify_host", opt)) { + tls_verify_host = false; + continue; + } + log.err("Unknown option to serve command: '{s}'", .{opt}); return error.UnkownOption; } @@ -305,6 +326,7 @@ fn parseServeArgs( .host = host, .port = port, .timeout = timeout, + .tls_verify_host = tls_verify_host, }; } @@ -314,6 +336,7 @@ fn parseFetchArgs( ) !Command.Fetch { var dump: bool = false; var url: ?[]const u8 = null; + var tls_verify_host = true; while (args.next()) |opt| { if (std.mem.eql(u8, "--dump", opt)) { @@ -321,6 +344,11 @@ fn parseFetchArgs( continue; } + if (std.mem.eql(u8, "--insecure_disable_tls_host_verification", opt)) { + tls_verify_host = false; + continue; + } + if (std.mem.startsWith(u8, opt, "--")) { log.err("Unknown option to serve command: '{s}'", .{opt}); return error.UnkownOption; @@ -338,7 +366,11 @@ fn parseFetchArgs( return error.MissingURL; } - return .{ .url = url.?, .dump = dump }; + return .{ + .url = url.?, + .dump = dump, + .tls_verify_host = tls_verify_host, + }; } var verbose: bool = builtin.mode == .Debug; // In debug mode, force verbose. diff --git a/src/main_tests.zig b/src/main_tests.zig index 41560b99..29bba51c 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); + 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 caecc188..75c5986f 100644 --- a/src/main_unit_tests.zig +++ b/src/main_unit_tests.zig @@ -213,7 +213,7 @@ fn serveHTTPS(address: std.net.Address) !void { fn serveCDP(address: std.net.Address) !void { const App = @import("app.zig").App; - var app = try App.init(gpa.allocator(), .serve); + var app = try App.init(gpa.allocator(), .{.run_mode = .serve}); defer app.deinit(); const server = @import("server.zig"); diff --git a/src/testing.zig b/src/testing.zig index 8df7b31a..13d1c181 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -157,7 +157,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { // dummy opts incase we want to add something, and not have to break all the callers pub fn app(_: anytype) *App { - return App.init(allocator, .serve) catch unreachable; + return App.init(allocator, .{.run_mode = .serve}) catch unreachable; } pub const Random = struct { From fba9cb071dfc69936b87a53f57f31bcee7acf675 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 27 Mar 2025 18:15:27 +0800 Subject: [PATCH 2/3] zig fmt :| --- src/main_unit_tests.zig | 2 +- src/testing.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main_unit_tests.zig b/src/main_unit_tests.zig index 75c5986f..9937f0b8 100644 --- a/src/main_unit_tests.zig +++ b/src/main_unit_tests.zig @@ -213,7 +213,7 @@ fn serveHTTPS(address: std.net.Address) !void { fn serveCDP(address: std.net.Address) !void { const App = @import("app.zig").App; - var app = try App.init(gpa.allocator(), .{.run_mode = .serve}); + var app = try App.init(gpa.allocator(), .{ .run_mode = .serve }); defer app.deinit(); const server = @import("server.zig"); diff --git a/src/testing.zig b/src/testing.zig index 13d1c181..7e157f7e 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -157,7 +157,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { // dummy opts incase we want to add something, and not have to break all the callers pub fn app(_: anytype) *App { - return App.init(allocator, .{.run_mode = .serve}) catch unreachable; + return App.init(allocator, .{ .run_mode = .serve }) catch unreachable; } pub const Random = struct { From 3e7f07374ce46917706c9da3c4b636cb9caec4cc Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 27 Mar 2025 18:18:29 +0800 Subject: [PATCH 3/3] Pass HttpClient options in wpt --- src/wpt/run.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wpt/run.zig b/src/wpt/run.zig index 8677188b..9989e36b 100644 --- a/src/wpt/run.zig +++ b/src/wpt/run.zig @@ -55,7 +55,7 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const var loop = try Loop.init(alloc); defer loop.deinit(); - var http_client = try HttpClient.init(alloc, 2); + var http_client = try HttpClient.init(alloc, 2, .{}); defer http_client.deinit(); var js_env: Env = undefined;