authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-10-05 12:19:06-05:00
committergravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-10-21 20:52:58-05:00
log0eef21d8ec290564ab503e5ad25f4c0c86f04d45
tree0875fc925e2686dcbfd0187e415cea6b0e12d1fb
parente1c37f70d4ae9a7bfa6de92dcb26e7cfdffc17c2
signaturelock-open Commit is signed but in an unrecognized format.

std.http.Client: add option to disable https

std_options.http_connection_pool_size removed in favor of ``` client.connection_pool.resize(client.allocator, size); ``` std_options.http_disable_tls will remove all https capability from std.http when true. Any https request will error with `error.TlsInitializationFailed`. Solves #17051.

3 files changed, 85 insertions(+), 32 deletions(-)

lib/std/http/Client.zig+73-29
......@@ -12,8 +12,7 @@ const assert = std.debug.assert;
1212const Client = @This();
1313const proto = @import("protocol.zig");
1414
15pub const default_connection_pool_size = 32;
16pub const connection_pool_size = std.options.http_connection_pool_size;
15pub const disable_tls = std.options.http_disable_tls;
1716
1817allocator: Allocator,
1918ca_bundle: std.crypto.Certificate.Bundle = .{},
......@@ -50,7 +49,7 @@ pub const ConnectionPool = struct {
5049 /// Open connections that are not currently in use.
5150 free: Queue = .{},
5251 free_len: usize = 0,
53 free_size: usize = connection_pool_size,
52 free_size: usize = 32,
5453
5554 /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe.
5655 /// If no connection is found, null is returned.
......@@ -127,23 +126,43 @@ pub const ConnectionPool = struct {
127126 pool.used.append(node);
128127 }
129128
130 pub fn deinit(pool: *ConnectionPool, client: *Client) void {
129 /// Resizes the connection pool. This function is threadsafe.
130 ///
131 /// If the new size is smaller than the current size, then idle connections will be closed until the pool is the new size.
132 pub fn resize(pool: *ConnectionPool, allocator: Allocator, new_size: usize) void {
133 pool.mutex.lock();
134 defer pool.mutex.unlock();
135
136 var next = pool.free.first;
137 _ = next;
138 while (pool.free_len > new_size) {
139 const popped = pool.free.popFirst() orelse unreachable;
140 pool.free_len -= 1;
141
142 popped.data.close(allocator);
143 allocator.destroy(popped);
144 }
145
146 pool.free_size = new_size;
147 }
148
149 pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void {
131150 pool.mutex.lock();
132151
133152 var next = pool.free.first;
134153 while (next) |node| {
135 defer client.allocator.destroy(node);
154 defer allocator.destroy(node);
136155 next = node.next;
137156
138 node.data.close(client.allocator);
157 node.data.close(allocator);
139158 }
140159
141160 next = pool.used.first;
142161 while (next) |node| {
143 defer client.allocator.destroy(node);
162 defer allocator.destroy(node);
144163 next = node.next;
145164
146 node.data.close(client.allocator);
165 node.data.close(allocator);
147166 }
148167
149168 pool.* = undefined;
......@@ -159,7 +178,7 @@ pub const Connection = struct {
159178
160179 stream: net.Stream,
161180 /// undefined unless protocol is tls.
162 tls_client: *std.crypto.tls.Client,
181 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,
163182
164183 protocol: Protocol,
165184 host: []u8,
......@@ -174,11 +193,8 @@ pub const Connection = struct {
174193 read_buf: [buffer_size]u8 = undefined,
175194 write_buf: [buffer_size]u8 = undefined,
176195
177 pub fn readvDirect(conn: *Connection, buffers: []std.os.iovec) ReadError!usize {
178 return switch (conn.protocol) {
179 .plain => conn.stream.readv(buffers),
180 .tls => conn.tls_client.readv(conn.stream, buffers),
181 } catch |err| {
196 pub fn readvDirectTls(conn: *Connection, buffers: []std.os.iovec) ReadError!usize {
197 return conn.tls_client.readv(conn.stream, buffers) catch |err| {
182198 // TODO: https://github.com/ziglang/zig/issues/2473
183199 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;
184200
......@@ -191,6 +207,20 @@ pub const Connection = struct {
191207 };
192208 }
193209
210 pub fn readvDirect(conn: *Connection, buffers: []std.os.iovec) ReadError!usize {
211 if (conn.protocol == .tls) {
212 if (disable_tls) unreachable;
213
214 return conn.readvDirectTls(buffers);
215 }
216
217 return conn.stream.readv(buffers) catch |err| switch (err) {
218 error.ConnectionTimedOut => return error.ConnectionTimedOut,
219 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
220 else => return error.UnexpectedReadFailure,
221 };
222 }
223
194224 pub fn fill(conn: *Connection) ReadError!void {
195225 if (conn.read_end != conn.read_start) return;
196226
......@@ -257,11 +287,21 @@ pub const Connection = struct {
257287 return Reader{ .context = conn };
258288 }
259289
290 pub fn writeAllDirectTls(conn: *Connection, buffer: []const u8) WriteError!void {
291 return conn.tls_client.writeAll(conn.stream, buffer) catch |err| switch (err) {
292 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
293 else => return error.UnexpectedWriteFailure,
294 };
295 }
296
260297 pub fn writeAllDirect(conn: *Connection, buffer: []const u8) WriteError!void {
261 return switch (conn.protocol) {
262 .plain => conn.stream.writeAll(buffer),
263 .tls => conn.tls_client.writeAll(conn.stream, buffer),
264 } catch |err| switch (err) {
298 if (conn.protocol == .tls) {
299 if (disable_tls) unreachable;
300
301 return conn.writeAllDirectTls(buffer);
302 }
303
304 return conn.stream.writeAll(buffer) catch |err| switch (err) {
265305 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
266306 else => return error.UnexpectedWriteFailure,
267307 };
......@@ -303,6 +343,8 @@ pub const Connection = struct {
303343
304344 pub fn close(conn: *Connection, allocator: Allocator) void {
305345 if (conn.protocol == .tls) {
346 if (disable_tls) unreachable;
347
306348 // try to cleanly close the TLS connection, for any server that cares.
307349 _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {};
308350 allocator.destroy(conn.tls_client);
......@@ -932,7 +974,7 @@ pub const ProxyInformation = struct {
932974/// Release all associated resources with the client.
933975/// TODO: currently leaks all request allocated data
934976pub fn deinit(client: *Client) void {
935 client.connection_pool.deinit(client);
977 client.connection_pool.deinit(client.allocator);
936978
937979 if (client.http_proxy) |*proxy| {
938980 proxy.allocator.free(proxy.host);
......@@ -1046,6 +1088,9 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
10461088 })) |node|
10471089 return node;
10481090
1091 if (disable_tls and protocol == .tls)
1092 return error.TlsInitializationFailed;
1093
10491094 const conn = try client.allocator.create(ConnectionPool.Node);
10501095 errdefer client.allocator.destroy(conn);
10511096 conn.* = .{ .data = undefined };
......@@ -1073,17 +1118,16 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
10731118 };
10741119 errdefer client.allocator.free(conn.data.host);
10751120
1076 switch (protocol) {
1077 .plain => {},
1078 .tls => {
1079 conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);
1080 errdefer client.allocator.destroy(conn.data.tls_client);
1121 if (protocol == .tls) {
1122 if (disable_tls) unreachable;
10811123
1082 conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed;
1083 // This is appropriate for HTTPS because the HTTP headers contain
1084 // the content length which is used to detect truncation attacks.
1085 conn.data.tls_client.allow_truncation_attacks = true;
1086 },
1124 conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);
1125 errdefer client.allocator.destroy(conn.data.tls_client);
1126
1127 conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed;
1128 // This is appropriate for HTTPS because the HTTP headers contain
1129 // the content length which is used to detect truncation attacks.
1130 conn.data.tls_client.allow_truncation_attacks = true;
10871131 }
10881132
10891133 client.connection_pool.addUsed(conn);
lib/std/std.zig+8-3
......@@ -283,10 +283,15 @@ pub const options = struct {
283283 else
284284 false;
285285
286 pub const http_connection_pool_size = if (@hasDecl(options_override, "http_connection_pool_size"))
287 options_override.http_connection_pool_size
286 /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to
287 /// disable TLS support.
288 ///
289 /// This will likely reduce the size of the binary, but it will also make it impossible to
290 /// make a HTTPS connection.
291 pub const http_disable_tls = if (@hasDecl(options_override, "http_disable_tls"))
292 options_override.http_disable_tls
288293 else
289 http.Client.default_connection_pool_size;
294 false;
290295
291296 pub const side_channels_mitigations: crypto.SideChannelsMitigations = if (@hasDecl(options_override, "side_channels_mitigations"))
292297 options_override.side_channels_mitigations
test/standalone/http.zig+4
......@@ -7,6 +7,10 @@ const Client = http.Client;
77const mem = std.mem;
88const testing = std.testing;
99
10pub const std_options = struct {
11 pub const http_disable_tls = true;
12};
13
1014const max_header_size = 8192;
1115
1216var gpa_server = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){};