From aea34264a99a58a3bfbb6ff352bb9d8f9ffa9e66 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Wed, 25 Jun 2025 11:34:32 +0200 Subject: [PATCH] basic/bearer testing --- src/http/client.zig | 49 +++++++++++++++++++++++++++++++++++++++++---- src/main.zig | 16 +++++++++++++++ src/testing.zig | 5 ++++- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/http/client.zig b/src/http/client.zig index bc210553..b45f2b19 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -59,15 +59,15 @@ pub const ProxyAuth = union(enum) { var encoder = std.base64.standard.Encoder; const size = encoder.calcSize(auth.user_pass.len); var buffer = try allocator.alloc(u8, size + prefix.len); - std.mem.copyForwards(u8, buffer, prefix); + @memcpy(buffer[0..prefix.len], prefix); _ = std.base64.standard.Encoder.encode(buffer[prefix.len..], auth.user_pass); return buffer; }, .bearer => |*auth| { const prefix = "Bearer "; var buffer = try allocator.alloc(u8, auth.token.len + prefix.len); - std.mem.copyForwards(u8, buffer, prefix); - std.mem.copyForwards(u8, buffer[prefix.len..], auth.token); + @memcpy(buffer[0..prefix.len], prefix); + @memcpy(buffer[prefix.len..], auth.token); return buffer; }, } @@ -3078,15 +3078,56 @@ test "HttpClient: sync with body proxy CONNECT" { } try testing.expectEqual("over 9000!", try res.next()); try testing.expectEqual(201, res.header.status); - try testing.expectEqual(5, res.header.count()); + try testing.expectEqual(6, res.header.count()); try testing.expectEqual("Close", res.header.get("connection")); try testing.expectEqual("10", res.header.get("content-length")); try testing.expectEqual("127.0.0.1", res.header.get("_host")); try testing.expectEqual("Lightpanda/1.0", res.header.get("_user-agent")); try testing.expectEqual("*/*", res.header.get("_accept")); + // Proxy headers + try testing.expectEqual("127.0.0.1:9582", res.header.get("__host")); } } +test "HttpClient: basic authentication CONNECT" { + const proxy_uri = try Uri.parse("http://127.0.0.1:9582/"); + var client = try testClient(.{ .proxy_type = .connect, .http_proxy = proxy_uri, .proxy_auth = .{ .basic = .{ .user_pass = "user:pass" } } }); + defer client.deinit(); + + const uri = try Uri.parse("http://127.0.0.1:9582/http_client/echo"); + var req = try client.request(.GET, &uri); + defer req.deinit(); + + var res = try req.sendSync(.{}); + + try testing.expectEqual(201, res.header.status); + // Destination headers + try testing.expectEqual(null, res.header.get("_authorization")); + try testing.expectEqual(null, res.header.get("_proxy-authorization")); + // Proxy headers + try testing.expectEqual(null, res.header.get("__authorization")); + try testing.expectEqual("Basic dXNlcjpwYXNz", res.header.get("__proxy-authorization")); +} +test "HttpClient: bearer authentication CONNECT" { + const proxy_uri = try Uri.parse("http://127.0.0.1:9582/"); + var client = try testClient(.{ .proxy_type = .connect, .http_proxy = proxy_uri, .proxy_auth = .{ .bearer = .{ .token = "fruitsalad" } } }); + defer client.deinit(); + + const uri = try Uri.parse("http://127.0.0.1:9582/http_client/echo"); + var req = try client.request(.GET, &uri); + defer req.deinit(); + + var res = try req.sendSync(.{}); + + try testing.expectEqual(201, res.header.status); + // Destination headers + try testing.expectEqual(null, res.header.get("_authorization")); + try testing.expectEqual(null, res.header.get("_proxy-authorization")); + // Proxy headers + try testing.expectEqual(null, res.header.get("__authorization")); + try testing.expectEqual("Bearer fruitsalad", res.header.get("__proxy-authorization")); +} + test "HttpClient: sync with gzip body" { for (0..2) |i| { var client = try testClient(.{}); diff --git a/src/main.zig b/src/main.zig index 327be9ca..5e3a26de 100644 --- a/src/main.zig +++ b/src/main.zig @@ -648,6 +648,7 @@ fn serveHTTP(address: std.net.Address) !void { var conn = try listener.accept(); defer conn.stream.close(); var http_server = std.http.Server.init(conn, &read_buffer); + var connect_headers: std.ArrayListUnmanaged(std.http.Header) = .{}; REQUEST: while (true) { var request = http_server.receiveHead() catch |err| switch (err) { error.HttpConnectionClosing => continue :ACCEPT, @@ -659,6 +660,16 @@ fn serveHTTP(address: std.net.Address) !void { if (request.head.method == .CONNECT) { try request.respond("", .{ .status = .ok }); + + // Proxy headers and destination headers are separated in the case of a CONNECT proxy + // We store the CONNECT headers, then continue with the request for the destination + var it = request.iterateHeaders(); + while (it.next()) |hdr| { + try connect_headers.append(aa, .{ + .name = try std.fmt.allocPrint(aa, "__{s}", .{hdr.name}), + .value = try aa.dupe(u8, hdr.value), + }); + } continue :REQUEST; } @@ -699,6 +710,11 @@ fn serveHTTP(address: std.net.Address) !void { .value = hdr.value, }); } + + if (connect_headers.items.len > 0) { + try headers.appendSlice(aa, connect_headers.items); + connect_headers.clearRetainingCapacity(); + } try headers.append(aa, .{ .name = "Connection", .value = "Close" }); try request.respond("over 9000!", .{ diff --git a/src/testing.zig b/src/testing.zig index 843240bb..f5e9c20b 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -66,7 +66,10 @@ pub fn expectEqual(expected: anytype, actual: anytype) !void { if (@typeInfo(@TypeOf(expected)) == .null) { return std.testing.expectEqual(null, actual); } - return expectEqual(expected, actual.?); + if (actual) |_actual| { + return expectEqual(expected, _actual); + } + return std.testing.expectEqual(expected, null); }, .@"union" => |union_info| { if (union_info.tag_type == null) {