diff --git a/src/Config.zig b/src/Config.zig index ed987214..fc4ebcdd 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -32,9 +32,24 @@ pub const RunMode = enum { mode: Mode, exec_name: []const u8, +http_headers: HttpHeaders, const Config = @This(); +pub fn init(allocator: Allocator, exec_name: []const u8, mode: Mode) !Config { + var config = Config{ + .mode = mode, + .exec_name = exec_name, + .http_headers = undefined, + }; + config.http_headers = try HttpHeaders.init(allocator, &config); + return config; +} + +pub fn deinit(self: *const Config, allocator: Allocator) void { + self.http_headers.deinit(allocator); +} + pub fn tlsVerifyHost(self: *const Config) bool { return switch (self.mode) { inline .serve, .fetch => |opts| opts.common.tls_verify_host, @@ -52,7 +67,7 @@ pub fn httpProxy(self: *const Config) ?[:0]const u8 { pub fn proxyBearerToken(self: *const Config) ?[:0]const u8 { return switch (self.mode) { inline .serve, .fetch => |opts| opts.common.proxy_bearer_token, - else => unreachable, + .help, .version => null, }; } @@ -112,18 +127,10 @@ pub fn logFilterScopes(self: *const Config) ?[]const log.Scope { pub fn userAgentSuffix(self: *const Config) ?[]const u8 { return switch (self.mode) { inline .serve, .fetch => |opts| opts.common.user_agent_suffix, - else => unreachable, + .help, .version => null, }; } -pub fn userAgent(self: *const Config, allocator: Allocator) ![:0]const u8 { - const base = "User-Agent: Lightpanda/1.0"; - if (self.userAgentSuffix()) |suffix| { - return try std.fmt.allocPrintSentinel(allocator, "{s} {s}", .{ base, suffix }, 0); - } - return base; -} - pub const Mode = union(RunMode) { help: bool, // false when being printed because of an error fetch: Fetch, @@ -164,6 +171,49 @@ pub const Common = struct { user_agent_suffix: ?[]const u8 = null, }; +/// Pre-formatted HTTP headers for reuse across Http and Client. +/// Must be initialized with an allocator that outlives all HTTP connections. +pub const HttpHeaders = struct { + const user_agent_base: [:0]const u8 = "Lightpanda/1.0"; + + user_agent: [:0]const u8, // User agent value (e.g. "Lightpanda/1.0") + user_agent_header: [:0]const u8, + + proxy_bearer_header: ?[:0]const u8, + + pub fn init(allocator: Allocator, config: *const Config) !HttpHeaders { + const user_agent: [:0]const u8 = if (config.userAgentSuffix()) |suffix| + try std.fmt.allocPrintSentinel(allocator, "{s} {s}", .{ user_agent_base, suffix }, 0) + else + user_agent_base; + errdefer if (config.userAgentSuffix() != null) allocator.free(user_agent); + + const user_agent_header = try std.fmt.allocPrintSentinel(allocator, "User-Agent: {s}", .{user_agent}, 0); + errdefer allocator.free(user_agent_header); + + const proxy_bearer_header: ?[:0]const u8 = if (config.proxyBearerToken()) |token| + try std.fmt.allocPrintSentinel(allocator, "Proxy-Authorization: Bearer {s}", .{token}, 0) + else + null; + + return .{ + .user_agent = user_agent, + .user_agent_header = user_agent_header, + .proxy_bearer_header = proxy_bearer_header, + }; + } + + pub fn deinit(self: *const HttpHeaders, allocator: Allocator) void { + if (self.proxy_bearer_header) |hdr| { + allocator.free(hdr); + } + allocator.free(self.user_agent_header); + if (self.user_agent.ptr != user_agent_base.ptr) { + allocator.free(self.user_agent); + } + } +}; + pub fn printUsageAndExit(self: *const Config, success: bool) void { // MAX_HELP_LEN| const common_options = @@ -292,16 +342,12 @@ pub fn parseArgs(allocator: Allocator) !Config { var args = try std.process.argsWithAllocator(allocator); defer args.deinit(); - const exec_name = std.fs.path.basename(args.next().?); - - var config = Config{ - .mode = .{ .help = false }, - .exec_name = try allocator.dupe(u8, exec_name), - }; + const exec_name = try allocator.dupe(u8, std.fs.path.basename(args.next().?)); const mode_string = args.next() orelse ""; - const mode = std.meta.stringToEnum(RunMode, mode_string) orelse blk: { - const inferred_mode = inferMode(mode_string) orelse return config; + const run_mode = std.meta.stringToEnum(RunMode, mode_string) orelse blk: { + const inferred_mode = inferMode(mode_string) orelse + return init(allocator, exec_name, .{ .help = false }); // "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 // as we transition to this command mode approach. @@ -314,13 +360,15 @@ pub fn parseArgs(allocator: Allocator) !Config { break :blk inferred_mode; }; - config.mode = switch (mode) { + const mode: Mode = switch (run_mode) { .help => .{ .help = true }, - .serve => .{ .serve = parseServeArgs(allocator, &args) catch return config }, - .fetch => .{ .fetch = parseFetchArgs(allocator, &args) catch return config }, + .serve => .{ .serve = parseServeArgs(allocator, &args) catch + return init(allocator, exec_name, .{ .help = false }) }, + .fetch => .{ .fetch = parseFetchArgs(allocator, &args) catch + return init(allocator, exec_name, .{ .help = false }) }, .version => .{ .version = {} }, }; - return config; + return init(allocator, exec_name, mode); } fn inferMode(opt: []const u8) ?RunMode { diff --git a/src/browser/webapi/Navigator.zig b/src/browser/webapi/Navigator.zig index 55603b61..185c9551 100644 --- a/src/browser/webapi/Navigator.zig +++ b/src/browser/webapi/Navigator.zig @@ -29,7 +29,7 @@ _plugins: PluginArray = .{}, pub const init: Navigator = .{}; pub fn getUserAgent(_: *const Navigator, page: *Page) []const u8 { - return page._session.browser.app.http.user_agent; + return page._session.browser.app.config.http_headers.user_agent; } pub fn getAppName(_: *const Navigator) []const u8 { diff --git a/src/http/Client.zig b/src/http/Client.zig index 46ce2116..f5f33993 100644 --- a/src/http/Client.zig +++ b/src/http/Client.zig @@ -97,8 +97,7 @@ http_proxy: ?[:0]const u8 = null, // CDP. use_proxy: bool, -// The complete user-agent header line -user_agent: [:0]const u8, +config: *const Config, cdp_client: ?CDPClient = null, @@ -134,13 +133,7 @@ pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, config: *const Config) try errorMCheck(c.curl_multi_setopt(multi, c.CURLMOPT_MAX_HOST_CONNECTIONS, @as(c_long, config.httpMaxHostOpen()))); - const user_agent = try config.userAgent(allocator); - var proxy_bearer_header: ?[:0]const u8 = null; - if (config.proxyBearerToken()) |bt| { - proxy_bearer_header = try std.fmt.allocPrintSentinel(allocator, "Proxy-Authorization: Bearer {s}", .{bt}, 0); - } - - var handles = try Handles.init(allocator, client, ca_blob, config, user_agent, proxy_bearer_header); + var handles = try Handles.init(allocator, client, ca_blob, config); errdefer handles.deinit(allocator); const http_proxy = config.httpProxy(); @@ -154,7 +147,7 @@ pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, config: *const Config) .allocator = allocator, .http_proxy = http_proxy, .use_proxy = http_proxy != null, - .user_agent = user_agent, + .config = config, .transfer_pool = transfer_pool, }; @@ -172,7 +165,7 @@ pub fn deinit(self: *Client) void { } pub fn newHeaders(self: *const Client) !Http.Headers { - return Http.Headers.init(self.user_agent); + return Http.Headers.init(self.config.http_headers.user_agent_header); } pub fn abort(self: *Client) void { @@ -660,8 +653,6 @@ const Handles = struct { client: *Client, ca_blob: ?c.curl_blob, config: *const Config, - user_agent: [:0]const u8, - proxy_bearer_header: ?[:0]const u8, ) !Handles { const count: usize = config.httpMaxConcurrent(); if (count == 0) return error.InvalidMaxConcurrent; @@ -671,7 +662,7 @@ const Handles = struct { var available: HandleList = .{}; for (0..count) |i| { - handles[i] = try Handle.init(client, ca_blob, config, user_agent, proxy_bearer_header); + handles[i] = try Handle.init(client, ca_blob, config); available.append(&handles[i].node); } @@ -722,10 +713,8 @@ pub const Handle = struct { client: *Client, ca_blob: ?c.curl_blob, config: *const Config, - user_agent: [:0]const u8, - proxy_bearer_header: ?[:0]const u8, ) !Handle { - const conn = try Http.Connection.init(ca_blob, config, user_agent, proxy_bearer_header); + const conn = try Http.Connection.init(ca_blob, config); errdefer conn.deinit(); const easy = conn.easy; diff --git a/src/http/Http.zig b/src/http/Http.zig index b619b93f..3d488f95 100644 --- a/src/http/Http.zig +++ b/src/http/Http.zig @@ -45,8 +45,6 @@ config: *const Config, client: *Client, ca_blob: ?c.curl_blob, arena: ArenaAllocator, -user_agent: [:0]const u8, -proxy_bearer_header: ?[:0]const u8, pub fn init(allocator: Allocator, config: *const Config) !Http { try errorCheck(c.curl_global_init(c.CURL_GLOBAL_SSL)); @@ -59,13 +57,6 @@ pub fn init(allocator: Allocator, config: *const Config) !Http { var arena = ArenaAllocator.init(allocator); errdefer arena.deinit(); - const user_agent = try config.userAgent(arena.allocator()); - - var proxy_bearer_header: ?[:0]const u8 = null; - if (config.proxyBearerToken()) |bt| { - proxy_bearer_header = try std.fmt.allocPrintSentinel(arena.allocator(), "Proxy-Authorization: Bearer {s}", .{bt}, 0); - } - var ca_blob: ?c.curl_blob = null; if (config.tlsVerifyHost()) { ca_blob = try loadCerts(allocator, arena.allocator()); @@ -79,8 +70,6 @@ pub fn init(allocator: Allocator, config: *const Config) !Http { .client = client, .ca_blob = ca_blob, .config = config, - .user_agent = user_agent, - .proxy_bearer_header = proxy_bearer_header, }; } @@ -107,23 +96,16 @@ pub fn removeCDPClient(self: *Http) void { } pub fn newConnection(self: *Http) !Connection { - return Connection.init(self.ca_blob, self.config, self.user_agent, self.proxy_bearer_header); -} - -pub fn newHeaders(self: *const Http) Headers { - return Headers.init(self.user_agent); + return Connection.init(self.ca_blob, self.config); } pub const Connection = struct { easy: *c.CURL, - user_agent: [:0]const u8, - proxy_bearer_header: ?[:0]const u8, + http_headers: *const Config.HttpHeaders, pub fn init( ca_blob_: ?c.curl_blob, config: *const Config, - user_agent: [:0]const u8, - proxy_bearer_header: ?[:0]const u8, ) !Connection { const easy = c.curl_easy_init() orelse return error.FailedToInitializeEasy; errdefer _ = c.curl_easy_cleanup(easy); @@ -188,8 +170,7 @@ pub const Connection = struct { return .{ .easy = easy, - .user_agent = user_agent, - .proxy_bearer_header = proxy_bearer_header, + .http_headers = &config.http_headers, }; } @@ -243,7 +224,7 @@ pub const Connection = struct { // These are headers that may not be send to the users for inteception. pub fn secretHeaders(self: *const Connection, headers: *Headers) !void { - if (self.proxy_bearer_header) |hdr| { + if (self.http_headers.proxy_bearer_header) |hdr| { try headers.add(hdr); } } @@ -251,7 +232,7 @@ pub const Connection = struct { pub fn request(self: *const Connection) !u16 { const easy = self.easy; - var header_list = try Headers.init(self.user_agent); + var header_list = try Headers.init(self.http_headers.user_agent_header); defer header_list.deinit(); try self.secretHeaders(&header_list); try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_HTTPHEADER, header_list.headers)); diff --git a/src/main.zig b/src/main.zig index 9b849dc4..76c23d74 100644 --- a/src/main.zig +++ b/src/main.zig @@ -54,6 +54,7 @@ pub fn main() !void { fn run(allocator: Allocator, main_arena: Allocator, sighandler: *SigHandler) !void { const args = try Config.parseArgs(main_arena); + defer args.deinit(main_arena); switch (args.mode) { .help => { diff --git a/src/main_legacy_test.zig b/src/main_legacy_test.zig index e8180272..bc59492a 100644 --- a/src/main_legacy_test.zig +++ b/src/main_legacy_test.zig @@ -32,15 +32,12 @@ pub fn main() !void { wg.wait(); } lp.log.opts.level = .warn; - const config = lp.Config{ - .mode = .{ .serve = .{ - .common = .{ - .tls_verify_host = false, - .user_agent_suffix = "internal-tester", - }, - } }, - .exec_name = "legacy-test", - }; + const config = try lp.Config.init(allocator, "legacy-test", .{ .serve = .{ + .common = .{ + .tls_verify_host = false, + .user_agent_suffix = "internal-tester", + }, + } }); var app = try lp.App.init(allocator, &config); defer app.deinit(); diff --git a/src/main_wpt.zig b/src/main_wpt.zig index ce4a82bd..8647df96 100644 --- a/src/main_wpt.zig +++ b/src/main_wpt.zig @@ -58,15 +58,12 @@ pub fn main() !void { defer writer.deinit(); lp.log.opts.level = .warn; - const config = lp.Config{ - .mode = .{ .serve = .{ - .common = .{ - .tls_verify_host = false, - .user_agent_suffix = "internal-tester", - }, - } }, - .exec_name = "lightpanda-wpt", - }; + const config = try lp.Config.init(allocator, "lightpanda-wpt", .{ .serve = .{ + .common = .{ + .tls_verify_host = false, + .user_agent_suffix = "internal-tester", + }, + } }); var app = try lp.App.init(allocator, &config); defer app.deinit(); diff --git a/src/testing.zig b/src/testing.zig index 1a782d93..05c7efbc 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -452,21 +452,22 @@ const Server = @import("Server.zig"); var test_cdp_server: ?Server = null; var test_http_server: ?TestHTTPServer = null; -const test_config = Config{ - .mode = .{ .serve = .{ - .common = .{ - .tls_verify_host = false, - .user_agent_suffix = "internal-tester", - }, - } }, - .exec_name = "test", -}; +var test_config: Config = undefined; test "tests:beforeAll" { log.opts.level = .warn; log.opts.format = .pretty; - test_app = try App.init(@import("root").tracking_allocator, &test_config); + const test_allocator = @import("root").tracking_allocator; + + test_config = try Config.init(test_allocator, "test", .{ .serve = .{ + .common = .{ + .tls_verify_host = false, + .user_agent_suffix = "internal-tester", + }, + } }); + + test_app = try App.init(test_allocator, &test_config); errdefer test_app.deinit(); test_browser = try Browser.init(test_app, .{}); @@ -510,6 +511,7 @@ test "tests:afterAll" { test_notification.deinit(); test_browser.deinit(); test_app.deinit(); + test_config.deinit(@import("root").tracking_allocator); } fn serveCDP(wg: *std.Thread.WaitGroup) !void {