diff --git a/src/Config.zig b/src/Config.zig index b0059c32..c4099c97 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -1098,20 +1098,14 @@ fn parseCommonArg( return error.InvalidArgument; }; - for (str) |c| { - if (!std.ascii.isPrint(c)) { - log.fatal(.app, "not printable character", .{ .arg = opt }); - return error.InvalidArgument; - } - } - - if (std.ascii.indexOfIgnoreCase(str, "mozilla") != null) { + validateUserAgent(str) catch |err| { log.fatal(.app, "invalid value", .{ - .detail = "user-agent can't contain Mozilla", + .detail = "invalid user agent", .arg = opt, + .err = err, }); return error.InvalidArgument; - } + }; common.user_agent = try allocator.dupe(u8, str); return true; @@ -1194,98 +1188,60 @@ fn parseCommonArg( return false; } -test "HttpHeaders: default user agent" { - const allocator = std.testing.allocator; - var config = Config{ - .mode = .{ .serve = .{} }, - .exec_name = "test", - .http_headers = undefined, - }; - config.http_headers = try HttpHeaders.init(allocator, &config); - defer config.http_headers.deinit(allocator); +pub fn validateUserAgent(ua: []const u8) !void { + for (ua) |c| { + if (!std.ascii.isPrint(c)) { + return error.NonPrintable; + } + } - try std.testing.expectEqualStrings("Lightpanda/1.0", config.http_headers.user_agent); - try std.testing.expectEqualStrings("User-Agent: Lightpanda/1.0", config.http_headers.user_agent_header); - try std.testing.expect(config.http_headers.proxy_bearer_header == null); + if (std.ascii.indexOfIgnoreCase(ua, "mozilla") != null) { + return error.Reserved; + } } -test "HttpHeaders: custom user agent override" { - const allocator = std.testing.allocator; - const ua = try allocator.dupe(u8, "MyBot/2.0"); - var config = Config{ - .mode = .{ .serve = .{ .common = .{ .user_agent = ua } } }, - .exec_name = "test", - .http_headers = undefined, - }; - config.http_headers = try HttpHeaders.init(allocator, &config); - defer config.http_headers.deinit(allocator); - defer allocator.free(ua); +const testing = @import("testing.zig"); +test "Config: HttpHeaders - default user agent" { + var config = try Config.init(testing.allocator, "", .{ .serve = .{} }); + defer config.deinit(testing.allocator); - try std.testing.expectEqualStrings("MyBot/2.0", config.http_headers.user_agent); - try std.testing.expectEqualStrings("User-Agent: MyBot/2.0", config.http_headers.user_agent_header); + try testing.expectEqual("Lightpanda/1.0", config.http_headers.user_agent); + try testing.expectEqual("User-Agent: Lightpanda/1.0", config.http_headers.user_agent_header); + try testing.expect(config.http_headers.proxy_bearer_header == null); } -test "HttpHeaders: user agent suffix" { - const allocator = std.testing.allocator; - const suffix = try allocator.dupe(u8, "CustomSuffix/3.0"); - var config = Config{ - .mode = .{ .serve = .{ .common = .{ .user_agent_suffix = suffix } } }, - .exec_name = "test", - .http_headers = undefined, - }; - config.http_headers = try HttpHeaders.init(allocator, &config); - defer config.http_headers.deinit(allocator); - defer allocator.free(suffix); +test "Config: HttpHeaders - custom user agent override" { + var config = try Config.init(testing.allocator, "", .{ .serve = .{ .common = .{ .user_agent = "MyBot/2.0" } } }); + defer config.deinit(testing.allocator); - try std.testing.expectEqualStrings("Lightpanda/1.0 CustomSuffix/3.0", config.http_headers.user_agent); - try std.testing.expectEqualStrings("User-Agent: Lightpanda/1.0 CustomSuffix/3.0", config.http_headers.user_agent_header); + try testing.expectEqual("MyBot/2.0", config.http_headers.user_agent); + try testing.expectEqual("User-Agent: MyBot/2.0", config.http_headers.user_agent_header); } -test "HttpHeaders: fetch mode default user agent" { - const allocator = std.testing.allocator; - const url = try allocator.dupeZ(u8, "https://example.com"); - defer allocator.free(url); - var config = Config{ - .mode = .{ .fetch = .{ .url = url } }, - .exec_name = "test", - .http_headers = undefined, - }; - config.http_headers = try HttpHeaders.init(allocator, &config); - defer config.http_headers.deinit(allocator); +test "Config: HttpHeaders - user agent suffix" { + var config = try Config.init(testing.allocator, "", .{ .serve = .{ .common = .{ .user_agent_suffix = "CustomSuffix/3.0" } } }); + defer config.deinit(testing.allocator); - try std.testing.expectEqualStrings("Lightpanda/1.0", config.http_headers.user_agent); + try testing.expectEqual("Lightpanda/1.0 CustomSuffix/3.0", config.http_headers.user_agent); + try testing.expectEqual("User-Agent: Lightpanda/1.0 CustomSuffix/3.0", config.http_headers.user_agent_header); } -test "HttpHeaders: fetch mode custom user agent" { - const allocator = std.testing.allocator; - const url = try allocator.dupeZ(u8, "https://example.com"); - defer allocator.free(url); - const ua = try allocator.dupe(u8, "FetchBot/1.0"); - var config = Config{ - .mode = .{ .fetch = .{ .url = url, .common = .{ .user_agent = ua } } }, - .exec_name = "test", - .http_headers = undefined, - }; - config.http_headers = try HttpHeaders.init(allocator, &config); - defer config.http_headers.deinit(allocator); - defer allocator.free(ua); - - try std.testing.expectEqualStrings("FetchBot/1.0", config.http_headers.user_agent); - try std.testing.expectEqualStrings("User-Agent: FetchBot/1.0", config.http_headers.user_agent_header); +test "Config: HttpHeaders - fetch mode default user agent" { + var config = try Config.init(testing.allocator, "", .{ .fetch = .{ .url = "https://example.com" } }); + defer config.deinit(testing.allocator); + try testing.expectEqual("Lightpanda/1.0", config.http_headers.user_agent); } -test "HttpHeaders: proxy bearer header" { - const allocator = std.testing.allocator; - const token: [:0]const u8 = try allocator.dupeZ(u8, "secret-token"); - var config = Config{ - .mode = .{ .serve = .{ .common = .{ .proxy_bearer_token = token } } }, - .exec_name = "test", - .http_headers = undefined, - }; - config.http_headers = try HttpHeaders.init(allocator, &config); - defer config.http_headers.deinit(allocator); - defer allocator.free(token); - - try std.testing.expectEqualStrings("Lightpanda/1.0", config.http_headers.user_agent); - try std.testing.expectEqualStrings("Proxy-Authorization: Bearer secret-token", config.http_headers.proxy_bearer_header.?); +test "Config: HttpHeaders - fetch mode custom user agent" { + var config = try Config.init(testing.allocator, "", .{ .fetch = .{ .url = "https://example.com", .common = .{ .user_agent = "FetchBot/1.0" } } }); + defer config.deinit(testing.allocator); + try testing.expectEqual("FetchBot/1.0", config.http_headers.user_agent); + try testing.expectEqual("User-Agent: FetchBot/1.0", config.http_headers.user_agent_header); +} + +test "Config: HttpHeaders - proxy bearer header" { + var config = try Config.init(testing.allocator, "", .{ .serve = .{ .common = .{ .proxy_bearer_token = "secret-token" } } }); + defer config.deinit(testing.allocator); + try testing.expectEqual("Lightpanda/1.0", config.http_headers.user_agent); + try testing.expectEqual("Proxy-Authorization: Bearer secret-token", config.http_headers.proxy_bearer_header.?); } diff --git a/src/cdp/domains/emulation.zig b/src/cdp/domains/emulation.zig index 7248b76c..fc7d821d 100644 --- a/src/cdp/domains/emulation.zig +++ b/src/cdp/domains/emulation.zig @@ -19,6 +19,7 @@ const std = @import("std"); const CDP = @import("../CDP.zig"); const log = @import("../../log.zig"); +const Config = @import("../../Config.zig"); pub fn processMessage(cmd: *CDP.Command) !void { const action = std.meta.stringToEnum(enum { @@ -85,22 +86,13 @@ pub fn setUserAgentOverride(cmd: *CDP.Command) !void { } const ua = params.userAgent; - - // Validate: all characters must be printable ASCII - for (ua) |c| { - if (!std.ascii.isPrint(c)) { - return cmd.sendError(-32602, "User agent contains non-printable characters", .{}); - } - } - - // Reject user agents containing "mozilla" (case-insensitive) - if (std.ascii.indexOfIgnoreCase(ua, "mozilla") != null) { - // go-rod client automatically set a Mozilla/ user agent. - // Since we don't want to stop this client to work, let's ignore the - // new user-agent and add a log instead. - log.warn(.not_implemented, "Emulation.setUserAgentOverride", .{ .param = "userAgent", .value = ua, .info = "User agent must not contain Mozilla" }); - return cmd.sendResult(null, .{}); - } + Config.validateUserAgent(ua) catch |err| switch (err) { + error.NonPrintable => return cmd.sendError(-32602, "User agent contains non-printable characters", .{}), + error.Reserved => { + log.warn(.not_implemented, "Emulation.setUserAgentOverride", .{ .param = "userAgent", .value = ua, .info = "User agent must not contain Mozilla" }); + return cmd.sendResult(null, .{}); + }, + }; const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; const http_client = cmd.cdp.browser.http_client; @@ -127,6 +119,9 @@ test "cdp.Emulation: setUserAgentOverride with valid user agent" { } test "cdp.Emulation: setUserAgentOverride ignores mozilla" { + const filter: testing.LogFilter = .init(&.{.not_implemented}); + defer filter.deinit(); + var ctx = try testing.context(); defer ctx.deinit(); _ = try ctx.loadBrowserContext(.{ .id = "BID-UA2" }); @@ -138,10 +133,13 @@ test "cdp.Emulation: setUserAgentOverride ignores mozilla" { }); try ctx.expectSentResult(null, .{}); - try testing.expect(ctx.cdp().browser_context.?.user_agent_changed == false); + try testing.expectEqual(false, ctx.cdp().browser_context.?.user_agent_changed); } test "cdp.Emulation: setUserAgentOverride ignores mozilla case insensitive" { + const filter: testing.LogFilter = .init(&.{.not_implemented}); + defer filter.deinit(); + var ctx = try testing.context(); defer ctx.deinit(); _ = try ctx.loadBrowserContext(.{ .id = "BID-UA3" }); @@ -153,10 +151,13 @@ test "cdp.Emulation: setUserAgentOverride ignores mozilla case insensitive" { }); try ctx.expectSentResult(null, .{}); - try testing.expect(ctx.cdp().browser_context.?.user_agent_changed == false); + try testing.expectEqual(false, ctx.cdp().browser_context.?.user_agent_changed); } test "cdp.Emulation: setUserAgentOverride rejects non-printable characters" { + const filter: testing.LogFilter = .init(&.{.not_implemented}); + defer filter.deinit(); + var ctx = try testing.context(); defer ctx.deinit(); _ = try ctx.loadBrowserContext(.{ .id = "BID-UA4" }); @@ -171,6 +172,9 @@ test "cdp.Emulation: setUserAgentOverride rejects non-printable characters" { } test "cdp.Emulation: setUserAgentOverride with optional params" { + const filter: testing.LogFilter = .init(&.{.not_implemented}); + defer filter.deinit(); + var ctx = try testing.context(); defer ctx.deinit(); _ = try ctx.loadBrowserContext(.{ .id = "BID-UA5" }); diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig index a7c7317b..7392b873 100644 --- a/src/cdp/testing.zig +++ b/src/cdp/testing.zig @@ -32,6 +32,7 @@ pub const expectError = base.expectError; pub const expectEqualSlices = base.expectEqualSlices; pub const pageTest = base.pageTest; pub const newString = base.newString; +pub const LogFilter = base.LogFilter; const TestContext = struct { read_at: usize = 0,