From 5b02ed7faedc30177ac5c7cb038c5f6b11f6f434 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:27:55 +0200 Subject: [PATCH 1/7] TLS connect proxy WIP --- src/http/client.zig | 80 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/src/http/client.zig b/src/http/client.zig index 1cb078525..99661ec6c 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -322,11 +322,19 @@ const Connection = struct { const TLSClient = union(enum) { blocking: tls.Connection(std.net.Stream), + blocking_tls_in_tls: struct { + proxy: tls.Connection(std.net.Stream), + destination: tls.Connection(*tls.Connection(std.net.Stream)), + }, nonblocking: tls.nonblock.Connection, fn close(self: *TLSClient) void { switch (self.*) { .blocking => |*tls_client| tls_client.close() catch {}, + .blocking_tls_in_tls => {}, // |*tls_in_tls| { + // tls_in_tls.destination.close() catch {}; // Crashes + // tls_in_tls.proxy.close() catch {}; + // }, .nonblocking => {}, } } @@ -657,18 +665,32 @@ pub const Request = struct { const is_connect_proxy = self._client.isConnectProxy(); if (is_connect_proxy) { - try SyncHandler.connect(self); - } - - if (self._secure) { - self._connection.?.tls = .{ - .blocking = try tls.client(std.net.Stream{ .handle = socket }, .{ - .host = if (is_connect_proxy) self._request_host else self._connect_host, + var connect_connection = try SyncHandler.connect(self); + if (self._secure) { // TODO separate _secure for proxy and desination + const tls_in_tls = try tls.client(&connect_connection, .{ + .host = self._request_host, .root_ca = self._client.root_ca, .insecure_skip_verify = self._tls_verify_host == false, // .key_log_callback = tls.config.key_log.callback, - }), - }; + }); + self._connection.?.tls = .{ + .blocking_tls_in_tls = .{ + .proxy = connect_connection, + .destination = tls_in_tls, + }, + }; + } + } else { + if (self._secure) { + self._connection.?.tls = .{ + .blocking = try tls.client(std.net.Stream{ .handle = socket }, .{ + .host = if (is_connect_proxy) self._request_host else self._connect_host, + .root_ca = self._client.root_ca, + .insecure_skip_verify = self._tls_verify_host == false, + // .key_log_callback = tls.config.key_log.callback, + }), + }; + } } self._connection_from_keepalive = false; @@ -1721,7 +1743,15 @@ const SyncHandler = struct { var conn: Conn = blk: { const c = request._connection.?; if (c.tls) |*tls_client| { - break :blk .{ .tls = &tls_client.blocking }; + switch (tls_client.*) { + .nonblocking => unreachable, + .blocking => |*blocking| { + break :blk .{ .tls = blocking }; + }, + .blocking_tls_in_tls => |*blocking_tls_in_tls| { + break :blk .{ .tls_in_tls = &blocking_tls_in_tls.destination }; + }, + } } break :blk .{ .plain = c.socket }; }; @@ -1804,11 +1834,18 @@ const SyncHandler = struct { // Unfortunately, this is called from the Request doSendSync since we need // to do this before setting up our TLS connection. - fn connect(request: *Request) !void { + fn connect(request: *Request) !tls.Connection(std.net.Stream) { const socket = request._connection.?.socket; const header = try request.buildConnectHeader(); - try Conn.writeAll(socket, header); + // try Conn.writeAll(socket, header); + var tls_client = try tls.client(std.net.Stream{ .handle = socket }, .{ + .host = request._connect_host, + .root_ca = request._client.root_ca, + .insecure_skip_verify = request._tls_verify_host == false, + .key_log_callback = tls.config.key_log.callback, + }); + try tls_client.writeAll(header); var pos: usize = 0; var reader = request.newReader(); @@ -1819,18 +1856,24 @@ const SyncHandler = struct { // we only send CONNECT requests on newly established connections // and maybeRetryOrErr is only for connections that might have been // closed while being kept-alive - const n = try posix.read(socket, read_buf[pos..]); + // const n = try posix.read(socket, read_buf[pos..]); + // const n = switch (self.*) { + // .tls => |tls_client| try tls_client.read(buf), + // .plain => |socket| try posix.read(socket, buf), + // }; + const n = try tls_client.read(read_buf[pos..]); if (n == 0) { return error.ConnectionResetByPeer; } pos += n; if (try reader.connectResponse(read_buf[0..pos])) { // returns true if we have a successful connect response - return; + return tls_client; } // we don't have enough data yet. } + return tls_client; } fn maybeRetryOrErr(self: *SyncHandler, err: anyerror) !Response { @@ -1880,11 +1923,18 @@ const SyncHandler = struct { } const Conn = union(enum) { + tls_in_tls: *tls.Connection(*tls.Connection(std.net.Stream)), tls: *tls.Connection(std.net.Stream), plain: posix.socket_t, fn sendRequest(self: *Conn, header: []const u8, body: ?[]const u8) !void { switch (self.*) { + .tls_in_tls => |tls_client| { + try tls_client.writeAll(header); + if (body) |b| { + try tls_client.writeAll(b); + } + }, .tls => |tls_client| { try tls_client.writeAll(header); if (body) |b| { @@ -1906,6 +1956,7 @@ const SyncHandler = struct { fn read(self: *Conn, buf: []u8) !usize { const n = switch (self.*) { + .tls_in_tls => |tls_client| try tls_client.read(buf), .tls => |tls_client| try tls_client.read(buf), .plain => |socket| try posix.read(socket, buf), }; @@ -2081,6 +2132,7 @@ const Reader = struct { if (result.done == false) { // CONNECT responses should not have a body. If the header is // done, then the entire response should be done. + log.err(.http_client, "InvalidConnectResponse", .{ .unprocessed = result.unprocessed.? }); return error.InvalidConnectResponse; } From aa9be8f2e693f7ba62b669292805e4cc844ab9a5 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Sun, 29 Jun 2025 17:58:19 -0700 Subject: [PATCH 2/7] Handle TLS proxy, both for HTTP and HTTPS (tls in tls) endpoints --- src/http/client.zig | 150 +++++++++++++++++++++++++------------------- 1 file changed, 87 insertions(+), 63 deletions(-) diff --git a/src/http/client.zig b/src/http/client.zig index 99661ec6c..82f5f8ae6 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -239,6 +239,11 @@ pub const Client = struct { const proxy_type = self.proxy_type orelse return false; return proxy_type == .forward; } + + fn isProxyTLS(self: *const Client) bool { + const proxy = self.http_proxy orelse return false; + return std.mem.eql(u8, proxy.scheme, "https"); + } }; const RequestOpts = struct { @@ -331,10 +336,10 @@ const Connection = struct { fn close(self: *TLSClient) void { switch (self.*) { .blocking => |*tls_client| tls_client.close() catch {}, - .blocking_tls_in_tls => {}, // |*tls_in_tls| { - // tls_in_tls.destination.close() catch {}; // Crashes - // tls_in_tls.proxy.close() catch {}; - // }, + .blocking_tls_in_tls => |*tls_in_tls| { + tls_in_tls.destination.close() catch {}; + tls_in_tls.proxy.close() catch {}; + }, .nonblocking => {}, } } @@ -535,6 +540,7 @@ pub const Request = struct { } const is_connect_proxy = client.isConnectProxy(); + const is_proxy_tls = client.isProxyTLS(); var secure: bool = undefined; const scheme = if (is_connect_proxy) uri.scheme else connect_uri.scheme; @@ -546,7 +552,11 @@ pub const Request = struct { return error.UnsupportedUriScheme; } const request_port: u16 = uri.port orelse if (secure) 443 else 80; - const connect_port: u16 = connect_uri.port orelse (if (is_connect_proxy) 80 else request_port); + const connect_port: u16 = connect_uri.port orelse blk: { + if (is_connect_proxy) { + if (is_proxy_tls) break :blk 443 else break :blk 80; + } else break :blk request_port; + }; return .{ .secure = secure, @@ -663,36 +673,58 @@ pub const Request = struct { }; self._connection = connection; + const tls_config = tls.config.Client{ + .host = self._request_host, + .root_ca = self._client.root_ca, + .insecure_skip_verify = self._tls_verify_host == false, + // .key_log_callback = tls.config.key_log.callback, + }; + + // proxy const is_connect_proxy = self._client.isConnectProxy(); + const is_proxy_tls = self._client.isProxyTLS(); + if (is_connect_proxy) { - var connect_connection = try SyncHandler.connect(self); - if (self._secure) { // TODO separate _secure for proxy and desination - const tls_in_tls = try tls.client(&connect_connection, .{ - .host = self._request_host, - .root_ca = self._client.root_ca, - .insecure_skip_verify = self._tls_verify_host == false, - // .key_log_callback = tls.config.key_log.callback, - }); - self._connection.?.tls = .{ - .blocking_tls_in_tls = .{ - .proxy = connect_connection, - .destination = tls_in_tls, - }, - }; + var proxy_conn: SyncHandler.Conn = .{ .plain = self._connection.?.socket }; + + if (is_proxy_tls) { + + // create an underlying TLS stream with the proxy + var proxy_tls_config = tls_config; + proxy_tls_config.host = self._connect_host; + var proxy_conn_tls = try tls.client(std.net.Stream{ .handle = socket }, proxy_tls_config); + proxy_conn = .{ .tls = &proxy_conn_tls }; } - } else { - if (self._secure) { - self._connection.?.tls = .{ - .blocking = try tls.client(std.net.Stream{ .handle = socket }, .{ - .host = if (is_connect_proxy) self._request_host else self._connect_host, - .root_ca = self._client.root_ca, - .insecure_skip_verify = self._tls_verify_host == false, - // .key_log_callback = tls.config.key_log.callback, - }), - }; + + // connect to the proxy + try SyncHandler.connect(self, &proxy_conn); + + if (is_proxy_tls) { + if (self._secure) { + + // if secure endpoint, create the main TLS stream + // encapsulated into the TLS stream proxy + const tls_in_tls = try tls.client(proxy_conn.tls, tls_config); + self._connection.?.tls = .{ + .blocking_tls_in_tls = .{ + .proxy = proxy_conn.tls.*, + .destination = tls_in_tls, + }, + }; + } else { + + // otherwise, just use the TLS stream proxy + self._connection.?.tls = .{ .blocking = proxy_conn.tls.* }; + } } } + if (self._secure and !is_proxy_tls) { + self._connection.?.tls = .{ + .blocking = try tls.client(std.net.Stream{ .handle = socket }, tls_config), + }; + } + self._connection_from_keepalive = false; } @@ -1834,18 +1866,9 @@ const SyncHandler = struct { // Unfortunately, this is called from the Request doSendSync since we need // to do this before setting up our TLS connection. - fn connect(request: *Request) !tls.Connection(std.net.Stream) { - const socket = request._connection.?.socket; - + fn connect(request: *Request, conn: *Conn) !void { const header = try request.buildConnectHeader(); - // try Conn.writeAll(socket, header); - var tls_client = try tls.client(std.net.Stream{ .handle = socket }, .{ - .host = request._connect_host, - .root_ca = request._client.root_ca, - .insecure_skip_verify = request._tls_verify_host == false, - .key_log_callback = tls.config.key_log.callback, - }); - try tls_client.writeAll(header); + try conn.writeAll(header); var pos: usize = 0; var reader = request.newReader(); @@ -1856,24 +1879,19 @@ const SyncHandler = struct { // we only send CONNECT requests on newly established connections // and maybeRetryOrErr is only for connections that might have been // closed while being kept-alive - // const n = try posix.read(socket, read_buf[pos..]); - // const n = switch (self.*) { - // .tls => |tls_client| try tls_client.read(buf), - // .plain => |socket| try posix.read(socket, buf), - // }; - const n = try tls_client.read(read_buf[pos..]); + const n = try conn.read(read_buf[pos..]); if (n == 0) { return error.ConnectionResetByPeer; } pos += n; if (try reader.connectResponse(read_buf[0..pos])) { // returns true if we have a successful connect response - return tls_client; + return; } // we don't have enough data yet. } - return tls_client; + return; } fn maybeRetryOrErr(self: *SyncHandler, err: anyerror) !Response { @@ -1929,16 +1947,16 @@ const SyncHandler = struct { fn sendRequest(self: *Conn, header: []const u8, body: ?[]const u8) !void { switch (self.*) { - .tls_in_tls => |tls_client| { - try tls_client.writeAll(header); + .tls => |_| { + try self.writeAll(header); if (body) |b| { - try tls_client.writeAll(b); + try self.writeAll(b); } }, - .tls => |tls_client| { - try tls_client.writeAll(header); + .tls_in_tls => |_| { + try self.writeAll(header); if (body) |b| { - try tls_client.writeAll(b); + try self.writeAll(b); } }, .plain => |socket| { @@ -1949,15 +1967,15 @@ const SyncHandler = struct { }; return writeAllIOVec(socket, &vec); } - return writeAll(socket, header); + return self.writeAll(header); }, } } fn read(self: *Conn, buf: []u8) !usize { const n = switch (self.*) { - .tls_in_tls => |tls_client| try tls_client.read(buf), .tls => |tls_client| try tls_client.read(buf), + .tls_in_tls => |tls_client| try tls_client.read(buf), .plain => |socket| try posix.read(socket, buf), }; if (n == 0) { @@ -1966,6 +1984,19 @@ const SyncHandler = struct { return n; } + fn writeAll(self: *Conn, data: []const u8) !void { + switch (self.*) { + .tls => |tls_client| try tls_client.writeAll(data), + .tls_in_tls => |tls_client| try tls_client.writeAll(data), + .plain => |socket| { + var i: usize = 0; + while (i < data.len) { + i += try posix.write(socket, data[i..]); + } + }, + } + } + fn writeAllIOVec(socket: posix.socket_t, vec: []posix.iovec_const) !void { var i: usize = 0; while (true) { @@ -1981,13 +2012,6 @@ const SyncHandler = struct { vec[i].len -= n; } } - - fn writeAll(socket: posix.socket_t, data: []const u8) !void { - var i: usize = 0; - while (i < data.len) { - i += try posix.write(socket, data[i..]); - } - } }; // We don't ask for encoding, but some providers (CloudFront!!) From 395c1467e8584710a272ca2bb9ac2ce0e304d286 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Sun, 29 Jun 2025 21:31:37 -0700 Subject: [PATCH 3/7] https-proxy: update tls.zig --- build.zig.zon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index f0ede0087..79181b0b0 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,8 +5,8 @@ .fingerprint = 0xda130f3af836cea0, .dependencies = .{ .tls = .{ - .url = "https://github.com/ianic/tls.zig/archive/8250aa9184fbad99983b32411bbe1a5d2fd6f4b7.tar.gz", - .hash = "tls-0.1.0-ER2e0pU3BQB-UD2_s90uvppceH_h4KZxtHCrCct8L054", + .url = "https://github.com/lightpanda-io/tls.zig/archive/bcf8f3982b3aaab950d47a9e0e2d1d96b1b9df22.tar.gz", + .hash = "tls-0.1.0-ER2e0mA4BQD2zfshj2kF83371dgVvhdr4dJ-TxPX5mmG", }, .tigerbeetle_io = .{ .url = "https://github.com/lightpanda-io/tigerbeetle-io/archive/61d9652f1a957b7f4db723ea6aa0ce9635e840ce.tar.gz", From b1186b5434ed74fb25eea2f3ac157fe71c24c432 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Wed, 2 Jul 2025 12:00:20 +0200 Subject: [PATCH 4/7] tls proxy tweaks --- src/http/client.zig | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/src/http/client.zig b/src/http/client.zig index 82f5f8ae6..e94dff687 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -242,7 +242,7 @@ pub const Client = struct { fn isProxyTLS(self: *const Client) bool { const proxy = self.http_proxy orelse return false; - return std.mem.eql(u8, proxy.scheme, "https"); + return std.ascii.eqlIgnoreCase(proxy.scheme, "https"); } }; @@ -328,7 +328,7 @@ const Connection = struct { const TLSClient = union(enum) { blocking: tls.Connection(std.net.Stream), blocking_tls_in_tls: struct { - proxy: tls.Connection(std.net.Stream), + proxy: tls.Connection(std.net.Stream), // Note, self-referential field. Proxy should be pinned in memory. destination: tls.Connection(*tls.Connection(std.net.Stream)), }, nonblocking: tls.nonblock.Connection, @@ -688,37 +688,33 @@ pub const Request = struct { var proxy_conn: SyncHandler.Conn = .{ .plain = self._connection.?.socket }; if (is_proxy_tls) { - - // create an underlying TLS stream with the proxy + // Create an underlying TLS stream with the proxy var proxy_tls_config = tls_config; proxy_tls_config.host = self._connect_host; var proxy_conn_tls = try tls.client(std.net.Stream{ .handle = socket }, proxy_tls_config); proxy_conn = .{ .tls = &proxy_conn_tls }; } - // connect to the proxy + // Connect to the proxy try SyncHandler.connect(self, &proxy_conn); if (is_proxy_tls) { if (self._secure) { - - // if secure endpoint, create the main TLS stream - // encapsulated into the TLS stream proxy - const tls_in_tls = try tls.client(proxy_conn.tls, tls_config); + // If secure endpoint, create the main TLS stream encapsulated into the TLS stream proxy self._connection.?.tls = .{ .blocking_tls_in_tls = .{ .proxy = proxy_conn.tls.*, - .destination = tls_in_tls, + .destination = undefined, }, }; + const proxy = &self._connection.?.tls.?.blocking_tls_in_tls.proxy; + self._connection.?.tls.?.blocking_tls_in_tls.destination = try tls.client(proxy, tls_config); } else { - - // otherwise, just use the TLS stream proxy + // Otherwise, just use the TLS stream proxy self._connection.?.tls = .{ .blocking = proxy_conn.tls.* }; } } } - if (self._secure and !is_proxy_tls) { self._connection.?.tls = .{ .blocking = try tls.client(std.net.Stream{ .handle = socket }, tls_config), @@ -1947,16 +1943,10 @@ const SyncHandler = struct { fn sendRequest(self: *Conn, header: []const u8, body: ?[]const u8) !void { switch (self.*) { - .tls => |_| { - try self.writeAll(header); - if (body) |b| { - try self.writeAll(b); - } - }, - .tls_in_tls => |_| { - try self.writeAll(header); + inline .tls, .tls_in_tls => |tls_client| { + try tls_client.writeAll(header); if (body) |b| { - try self.writeAll(b); + try tls_client.writeAll(b); } }, .plain => |socket| { @@ -2156,7 +2146,7 @@ const Reader = struct { if (result.done == false) { // CONNECT responses should not have a body. If the header is // done, then the entire response should be done. - log.err(.http_client, "InvalidConnectResponse", .{ .unprocessed = result.unprocessed.? }); + log.info(.http_client, "InvalidConnectResponse", .{ .status = self.response.status, .unprocessed = result.unprocessed }); return error.InvalidConnectResponse; } From 7a6dbc25c3ffc23dea1c62a6ae202d6726aad639 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 2 Jul 2025 10:39:26 -0700 Subject: [PATCH 5/7] https-proxy: update upstream tls.zig --- build.zig.zon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 79181b0b0..8e8bb137a 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,8 +5,8 @@ .fingerprint = 0xda130f3af836cea0, .dependencies = .{ .tls = .{ - .url = "https://github.com/lightpanda-io/tls.zig/archive/bcf8f3982b3aaab950d47a9e0e2d1d96b1b9df22.tar.gz", - .hash = "tls-0.1.0-ER2e0mA4BQD2zfshj2kF83371dgVvhdr4dJ-TxPX5mmG", + .url = "https://github.com/ianic/tls.zig/archive/55845f755d9e2e821458ea55693f85c737cd0c7a.tar.gz", + .hash = "tls-0.1.0-ER2e0m43BQAshi8ixj1qf3w2u2lqKtXtkrxUJ4AGZDcl", }, .tigerbeetle_io = .{ .url = "https://github.com/lightpanda-io/tigerbeetle-io/archive/61d9652f1a957b7f4db723ea6aa0ce9635e840ce.tar.gz", From 44ed39f366f7204bf61a0180292926d96bfd6a65 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:57:27 +0200 Subject: [PATCH 6/7] secure changes --- src/http/client.zig | 68 ++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/http/client.zig b/src/http/client.zig index e94dff687..91e772f22 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -239,11 +239,6 @@ pub const Client = struct { const proxy_type = self.proxy_type orelse return false; return proxy_type == .forward; } - - fn isProxyTLS(self: *const Client) bool { - const proxy = self.http_proxy orelse return false; - return std.ascii.eqlIgnoreCase(proxy.scheme, "https"); - } }; const RequestOpts = struct { @@ -388,9 +383,6 @@ pub const Request = struct { // List of request headers headers: std.ArrayListUnmanaged(std.http.Header), - // whether or not we expect this connection to be secure - _secure: bool, - // whether or not we should keep the underlying socket open and and usable // for other requests _keepalive: bool, @@ -398,6 +390,10 @@ pub const Request = struct { // extracted from request_uri _request_port: u16, _request_host: []const u8, + // Whether or not we expect this connection to be secure, connection may still be secure due to proxy + _request_secure: bool, + // Whether or not we expect the SIMPLE/CONNECT proxy connection to be secure + _proxy_secure: bool, // extracted from connect_uri _connect_port: u16, @@ -483,11 +479,12 @@ pub const Request = struct { .method = method, .notification = null, .arena = state.arena.allocator(), - ._secure = decomposed.secure, ._connect_host = decomposed.connect_host, ._connect_port = decomposed.connect_port, + ._proxy_secure = decomposed.proxy_secure, ._request_host = decomposed.request_host, ._request_port = decomposed.request_port, + ._request_secure = decomposed.request_secure, ._state = state, ._client = client, ._aborter = null, @@ -519,12 +516,13 @@ pub const Request = struct { } const DecomposedURL = struct { - secure: bool, connect_port: u16, connect_host: []const u8, connect_uri: *const std.Uri, + proxy_secure: bool, request_port: u16, request_host: []const u8, + request_secure: bool, }; fn decomposeURL(client: *const Client, uri: *const Uri) !DecomposedURL { if (uri.host == null) { @@ -539,32 +537,31 @@ pub const Request = struct { connect_host = proxy.host.?.percent_encoded; } - const is_connect_proxy = client.isConnectProxy(); - const is_proxy_tls = client.isProxyTLS(); - - var secure: bool = undefined; - const scheme = if (is_connect_proxy) uri.scheme else connect_uri.scheme; - if (std.ascii.eqlIgnoreCase(scheme, "https")) { - secure = true; - } else if (std.ascii.eqlIgnoreCase(scheme, "http")) { - secure = false; + var request_secure: bool = undefined; + if (std.ascii.eqlIgnoreCase(uri.scheme, "https")) { + request_secure = true; + } else if (std.ascii.eqlIgnoreCase(uri.scheme, "http")) { + request_secure = false; } else { return error.UnsupportedUriScheme; } - const request_port: u16 = uri.port orelse if (secure) 443 else 80; + const proxy_secure = client.http_proxy != null and std.ascii.eqlIgnoreCase(client.http_proxy.?.scheme, "https"); + + const request_port: u16 = uri.port orelse if (request_secure) 443 else 80; const connect_port: u16 = connect_uri.port orelse blk: { - if (is_connect_proxy) { - if (is_proxy_tls) break :blk 443 else break :blk 80; + if (client.isConnectProxy()) { + if (proxy_secure) break :blk 443 else break :blk 80; } else break :blk request_port; }; return .{ - .secure = secure, .connect_port = connect_port, .connect_host = connect_host, .connect_uri = connect_uri, + .proxy_secure = proxy_secure, .request_port = request_port, .request_host = request_host, + .request_secure = request_secure, }; } @@ -682,12 +679,11 @@ pub const Request = struct { // proxy const is_connect_proxy = self._client.isConnectProxy(); - const is_proxy_tls = self._client.isProxyTLS(); if (is_connect_proxy) { var proxy_conn: SyncHandler.Conn = .{ .plain = self._connection.?.socket }; - if (is_proxy_tls) { + if (self._proxy_secure) { // Create an underlying TLS stream with the proxy var proxy_tls_config = tls_config; proxy_tls_config.host = self._connect_host; @@ -698,8 +694,8 @@ pub const Request = struct { // Connect to the proxy try SyncHandler.connect(self, &proxy_conn); - if (is_proxy_tls) { - if (self._secure) { + if (self._proxy_secure) { + if (self._request_secure) { // If secure endpoint, create the main TLS stream encapsulated into the TLS stream proxy self._connection.?.tls = .{ .blocking_tls_in_tls = .{ @@ -715,7 +711,7 @@ pub const Request = struct { } } } - if (self._secure and !is_proxy_tls) { + if (self._request_secure and !self._proxy_secure) { self._connection.?.tls = .{ .blocking = try tls.client(std.net.Stream{ .handle = socket }, tls_config), }; @@ -794,7 +790,8 @@ pub const Request = struct { .conn = .{ .handler = async_handler, .protocol = .{ .plain = {} } }, }; - if (self._secure) { + if (self._client.isConnectProxy() and self._proxy_secure) log.warn(.http, "ASYNC TLS CONNECT no impl.", .{}); + if (self._request_secure) { if (self._connection_from_keepalive) { // If the connection came from the keepalive pool, than we already // have a TLS Connection. @@ -803,7 +800,7 @@ pub const Request = struct { std.debug.assert(connection.tls == null); async_handler.conn.protocol = .{ .handshake = tls.nonblock.Client.init(.{ - .host = if (self._client.isConnectProxy()) self._request_host else self._connect_host, + .host = if (self._client.isConnectProxy()) self._request_host else self._connect_host, // looks wrong .root_ca = self._client.root_ca, .insecure_skip_verify = self._tls_verify_host == false, .key_log_callback = tls.config.key_log.callback, @@ -883,9 +880,10 @@ pub const Request = struct { const decomposed = try decomposeURL(self._client, self.request_uri); self.connect_uri = decomposed.connect_uri; self._request_host = decomposed.request_host; + self._request_secure = decomposed.request_secure; self._connect_host = decomposed.connect_host; self._connect_port = decomposed.connect_port; - self._secure = decomposed.secure; + self._proxy_secure = decomposed.proxy_secure; self._keepalive = false; self._redirect_count = redirect_count + 1; @@ -933,7 +931,9 @@ pub const Request = struct { return null; } - return self._client.connection_manager.get(self._secure, self._connect_host, self._connect_port, blocking); + // A simple http proxy to an https destination is made into tls by the proxy, we see it as a plain connection + const expect_tls = self._proxy_secure or (self._request_secure and !self._client.isSimpleProxy()); + return self._client.connection_manager.get(expect_tls, self._connect_host, self._connect_port, blocking); } fn createSocket(self: *Request, blocking: bool) !struct { posix.socket_t, std.net.Address } { @@ -2973,14 +2973,14 @@ const ConnectionManager = struct { self.connection_pool.deinit(); } - fn get(self: *ConnectionManager, secure: bool, host: []const u8, port: u16, blocking: bool) ?*Connection { + fn get(self: *ConnectionManager, expect_tls: bool, host: []const u8, port: u16, blocking: bool) ?*Connection { self.mutex.lock(); defer self.mutex.unlock(); var node = self.idle.first; while (node) |n| { const connection = n.data; - if (std.ascii.eqlIgnoreCase(connection.host, host) and connection.port == port and connection.blocking == blocking and ((connection.tls == null) == !secure)) { + if (std.ascii.eqlIgnoreCase(connection.host, host) and connection.port == port and connection.blocking == blocking and ((connection.tls == null) == !expect_tls)) { self.count -= 1; self.idle.remove(n); self.node_pool.destroy(n); From bd37a8c5b7306a1339b94b706ce4a30114ed7c33 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Fri, 4 Jul 2025 09:53:13 +0200 Subject: [PATCH 7/7] rename tls_in_tls to tlsproxy --- src/http/client.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/http/client.zig b/src/http/client.zig index 91e772f22..368e7d0ea 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -322,7 +322,7 @@ const Connection = struct { const TLSClient = union(enum) { blocking: tls.Connection(std.net.Stream), - blocking_tls_in_tls: struct { + blocking_tlsproxy: struct { proxy: tls.Connection(std.net.Stream), // Note, self-referential field. Proxy should be pinned in memory. destination: tls.Connection(*tls.Connection(std.net.Stream)), }, @@ -331,7 +331,7 @@ const Connection = struct { fn close(self: *TLSClient) void { switch (self.*) { .blocking => |*tls_client| tls_client.close() catch {}, - .blocking_tls_in_tls => |*tls_in_tls| { + .blocking_tlsproxy => |*tls_in_tls| { tls_in_tls.destination.close() catch {}; tls_in_tls.proxy.close() catch {}; }, @@ -698,13 +698,13 @@ pub const Request = struct { if (self._request_secure) { // If secure endpoint, create the main TLS stream encapsulated into the TLS stream proxy self._connection.?.tls = .{ - .blocking_tls_in_tls = .{ + .blocking_tlsproxy = .{ .proxy = proxy_conn.tls.*, .destination = undefined, }, }; - const proxy = &self._connection.?.tls.?.blocking_tls_in_tls.proxy; - self._connection.?.tls.?.blocking_tls_in_tls.destination = try tls.client(proxy, tls_config); + const proxy = &self._connection.?.tls.?.blocking_tlsproxy.proxy; + self._connection.?.tls.?.blocking_tlsproxy.destination = try tls.client(proxy, tls_config); } else { // Otherwise, just use the TLS stream proxy self._connection.?.tls = .{ .blocking = proxy_conn.tls.* }; @@ -1776,8 +1776,8 @@ const SyncHandler = struct { .blocking => |*blocking| { break :blk .{ .tls = blocking }; }, - .blocking_tls_in_tls => |*blocking_tls_in_tls| { - break :blk .{ .tls_in_tls = &blocking_tls_in_tls.destination }; + .blocking_tlsproxy => |*blocking_tlsproxy| { + break :blk .{ .tls_in_tls = &blocking_tlsproxy.destination }; }, } }