@@ -46,6 +46,34 @@ pub const ProxyType = enum {
4646 connect ,
4747};
4848
49+ pub const ProxyAuth = union (enum ) {
50+ basic : struct { user_pass : []const u8 },
51+ bearer : struct { token : []const u8 },
52+
53+ pub fn header_value (self : ProxyAuth , allocator : Allocator ) ! []const u8 {
54+ switch (self ) {
55+ .basic = > | * auth | {
56+ if (std .mem .indexOfScalar (u8 , auth .user_pass , ':' ) == null ) return error .InvalidProxyAuth ;
57+
58+ const prefix = "Basic " ;
59+ var encoder = std .base64 .standard .Encoder ;
60+ const size = encoder .calcSize (auth .user_pass .len );
61+ var buffer = try allocator .alloc (u8 , size + prefix .len );
62+ @memcpy (buffer [0.. prefix .len ], prefix );
63+ _ = std .base64 .standard .Encoder .encode (buffer [prefix .len .. ], auth .user_pass );
64+ return buffer ;
65+ },
66+ .bearer = > | * auth | {
67+ const prefix = "Bearer " ;
68+ var buffer = try allocator .alloc (u8 , auth .token .len + prefix .len );
69+ @memcpy (buffer [0.. prefix .len ], prefix );
70+ @memcpy (buffer [prefix .len .. ], auth .token );
71+ return buffer ;
72+ },
73+ }
74+ }
75+ };
76+
4977// Thread-safe. Holds our root certificate, connection pool and state pool
5078// Used to create Requests.
5179pub const Client = struct {
@@ -54,6 +82,7 @@ pub const Client = struct {
5482 state_pool : StatePool ,
5583 http_proxy : ? Uri ,
5684 proxy_type : ? ProxyType ,
85+ proxy_auth : ? []const u8 , // Basic <user:pass; base64> or Bearer <token>
5786 root_ca : tls.config.CertBundle ,
5887 tls_verify_host : bool = true ,
5988 connection_manager : ConnectionManager ,
@@ -63,6 +92,7 @@ pub const Client = struct {
6392 max_concurrent : usize = 3 ,
6493 http_proxy : ? std.Uri = null ,
6594 proxy_type : ? ProxyType = null ,
95+ proxy_auth : ? ProxyAuth = null ,
6696 tls_verify_host : bool = true ,
6797 max_idle_connection : usize = 10 ,
6898 };
@@ -71,10 +101,10 @@ pub const Client = struct {
71101 var root_ca : tls.config.CertBundle = if (builtin .is_test ) .{} else try tls .config .CertBundle .fromSystem (allocator );
72102 errdefer root_ca .deinit (allocator );
73103
74- const state_pool = try StatePool .init (allocator , opts .max_concurrent );
104+ var state_pool = try StatePool .init (allocator , opts .max_concurrent );
75105 errdefer state_pool .deinit (allocator );
76106
77- const connection_manager = ConnectionManager .init (allocator , opts .max_idle_connection );
107+ var connection_manager = ConnectionManager .init (allocator , opts .max_idle_connection );
78108 errdefer connection_manager .deinit ();
79109
80110 return .{
@@ -84,6 +114,7 @@ pub const Client = struct {
84114 .state_pool = state_pool ,
85115 .http_proxy = opts .http_proxy ,
86116 .proxy_type = if (opts .http_proxy == null ) null else (opts .proxy_type orelse .connect ),
117+ .proxy_auth = if (opts .proxy_auth ) | * auth | try auth .header_value (allocator ) else null ,
87118 .tls_verify_host = opts .tls_verify_host ,
88119 .connection_manager = connection_manager ,
89120 .request_pool = std .heap .MemoryPool (Request ).init (allocator ),
@@ -98,6 +129,10 @@ pub const Client = struct {
98129 self .state_pool .deinit (allocator );
99130 self .connection_manager .deinit ();
100131 self .request_pool .deinit ();
132+
133+ if (self .proxy_auth ) | auth | {
134+ allocator .free (auth );
135+ }
101136 }
102137
103138 pub fn request (self : * Client , method : Request.Method , uri : * const Uri ) ! * Request {
@@ -763,6 +798,13 @@ pub const Request = struct {
763798
764799 try self .headers .append (arena , .{ .name = "User-Agent" , .value = "Lightpanda/1.0" });
765800 try self .headers .append (arena , .{ .name = "Accept" , .value = "*/*" });
801+
802+ if (self ._client .isSimpleProxy ()) {
803+ if (self ._client .proxy_auth ) | proxy_auth | {
804+ try self .headers .append (arena , .{ .name = "Proxy-Authorization" , .value = proxy_auth });
805+ }
806+ }
807+
766808 self .requestStarting ();
767809 }
768810
@@ -887,7 +929,13 @@ pub const Request = struct {
887929 var writer = fbs .writer ();
888930
889931 try writer .print ("CONNECT {s}:{d} HTTP/1.1\r \n " , .{ self ._request_host , self ._request_port });
890- try writer .print ("Host: {s}:{d}\r \n \r \n " , .{ self ._request_host , self ._request_port });
932+ try writer .print ("Host: {s}:{d}\r \n " , .{ self ._request_host , self ._request_port });
933+
934+ if (self ._client .proxy_auth ) | proxy_auth | {
935+ try writer .print ("Proxy-Authorization: {s}\r \n " , .{proxy_auth });
936+ }
937+
938+ _ = try writer .write ("\r \n " );
891939 return buf [0.. fbs .pos ];
892940 }
893941
@@ -3030,15 +3078,56 @@ test "HttpClient: sync with body proxy CONNECT" {
30303078 }
30313079 try testing .expectEqual ("over 9000!" , try res .next ());
30323080 try testing .expectEqual (201 , res .header .status );
3033- try testing .expectEqual (5 , res .header .count ());
3081+ try testing .expectEqual (6 , res .header .count ());
30343082 try testing .expectEqual ("Close" , res .header .get ("connection" ));
30353083 try testing .expectEqual ("10" , res .header .get ("content-length" ));
30363084 try testing .expectEqual ("127.0.0.1" , res .header .get ("_host" ));
30373085 try testing .expectEqual ("Lightpanda/1.0" , res .header .get ("_user-agent" ));
30383086 try testing .expectEqual ("*/*" , res .header .get ("_accept" ));
3087+ // Proxy headers
3088+ try testing .expectEqual ("127.0.0.1:9582" , res .header .get ("__host" ));
30393089 }
30403090}
30413091
3092+ test "HttpClient: basic authentication CONNECT" {
3093+ const proxy_uri = try Uri .parse ("http://127.0.0.1:9582/" );
3094+ var client = try testClient (.{ .proxy_type = .connect , .http_proxy = proxy_uri , .proxy_auth = .{ .basic = .{ .user_pass = "user:pass" } } });
3095+ defer client .deinit ();
3096+
3097+ const uri = try Uri .parse ("http://127.0.0.1:9582/http_client/echo" );
3098+ var req = try client .request (.GET , & uri );
3099+ defer req .deinit ();
3100+
3101+ var res = try req .sendSync (.{});
3102+
3103+ try testing .expectEqual (201 , res .header .status );
3104+ // Destination headers
3105+ try testing .expectEqual (null , res .header .get ("_authorization" ));
3106+ try testing .expectEqual (null , res .header .get ("_proxy-authorization" ));
3107+ // Proxy headers
3108+ try testing .expectEqual (null , res .header .get ("__authorization" ));
3109+ try testing .expectEqual ("Basic dXNlcjpwYXNz" , res .header .get ("__proxy-authorization" ));
3110+ }
3111+ test "HttpClient: bearer authentication CONNECT" {
3112+ const proxy_uri = try Uri .parse ("http://127.0.0.1:9582/" );
3113+ var client = try testClient (.{ .proxy_type = .connect , .http_proxy = proxy_uri , .proxy_auth = .{ .bearer = .{ .token = "fruitsalad" } } });
3114+ defer client .deinit ();
3115+
3116+ const uri = try Uri .parse ("http://127.0.0.1:9582/http_client/echo" );
3117+ var req = try client .request (.GET , & uri );
3118+ defer req .deinit ();
3119+
3120+ var res = try req .sendSync (.{});
3121+
3122+ try testing .expectEqual (201 , res .header .status );
3123+ // Destination headers
3124+ try testing .expectEqual (null , res .header .get ("_authorization" ));
3125+ try testing .expectEqual (null , res .header .get ("_proxy-authorization" ));
3126+ // Proxy headers
3127+ try testing .expectEqual (null , res .header .get ("__authorization" ));
3128+ try testing .expectEqual ("Bearer fruitsalad" , res .header .get ("__proxy-authorization" ));
3129+ }
3130+
30423131test "HttpClient: sync with gzip body" {
30433132 for (0.. 2) | i | {
30443133 var client = try testClient (.{});
0 commit comments