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
signature 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;...@@ -12,8 +12,7 @@ const assert = std.debug.assert;
12const Client = @This();12const Client = @This();
13const proto = @import("protocol.zig");13const proto = @import("protocol.zig");
1414
15pub const default_connection_pool_size = 32;15pub const disable_tls = std.options.http_disable_tls;
16pub const connection_pool_size = std.options.http_connection_pool_size;
1716
18allocator: Allocator,17allocator: Allocator,
19ca_bundle: std.crypto.Certificate.Bundle = .{},18ca_bundle: std.crypto.Certificate.Bundle = .{},
...@@ -50,7 +49,7 @@ pub const ConnectionPool = struct {...@@ -50,7 +49,7 @@ pub const ConnectionPool = struct {
50 /// Open connections that are not currently in use.49 /// Open connections that are not currently in use.
51 free: Queue = .{},50 free: Queue = .{},
52 free_len: usize = 0,51 free_len: usize = 0,
53 free_size: usize = connection_pool_size,52 free_size: usize = 32,
5453
55 /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe.54 /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe.
56 /// If no connection is found, null is returned.55 /// If no connection is found, null is returned.
...@@ -127,23 +126,43 @@ pub const ConnectionPool = struct {...@@ -127,23 +126,43 @@ pub const ConnectionPool = struct {
127 pool.used.append(node);126 pool.used.append(node);
128 }127 }
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 {
131 pool.mutex.lock();150 pool.mutex.lock();
132151
133 var next = pool.free.first;152 var next = pool.free.first;
134 while (next) |node| {153 while (next) |node| {
135 defer client.allocator.destroy(node);154 defer allocator.destroy(node);
136 next = node.next;155 next = node.next;
137156
138 node.data.close(client.allocator);157 node.data.close(allocator);
139 }158 }
140159
141 next = pool.used.first;160 next = pool.used.first;
142 while (next) |node| {161 while (next) |node| {
143 defer client.allocator.destroy(node);162 defer allocator.destroy(node);
144 next = node.next;163 next = node.next;
145164
146 node.data.close(client.allocator);165 node.data.close(allocator);
147 }166 }
148167
149 pool.* = undefined;168 pool.* = undefined;
...@@ -159,7 +178,7 @@ pub const Connection = struct {...@@ -159,7 +178,7 @@ pub const Connection = struct {
159178
160 stream: net.Stream,179 stream: net.Stream,
161 /// undefined unless protocol is tls.180 /// 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
164 protocol: Protocol,183 protocol: Protocol,
165 host: []u8,184 host: []u8,
...@@ -174,11 +193,8 @@ pub const Connection = struct {...@@ -174,11 +193,8 @@ pub const Connection = struct {
174 read_buf: [buffer_size]u8 = undefined,193 read_buf: [buffer_size]u8 = undefined,
175 write_buf: [buffer_size]u8 = undefined,194 write_buf: [buffer_size]u8 = undefined,
176195
177 pub fn readvDirect(conn: *Connection, buffers: []std.os.iovec) ReadError!usize {196 pub fn readvDirectTls(conn: *Connection, buffers: []std.os.iovec) ReadError!usize {
178 return switch (conn.protocol) {197 return conn.tls_client.readv(conn.stream, buffers) catch |err| {
179 .plain => conn.stream.readv(buffers),
180 .tls => conn.tls_client.readv(conn.stream, buffers),
181 } catch |err| {
182 // TODO: https://github.com/ziglang/zig/issues/2473198 // TODO: https://github.com/ziglang/zig/issues/2473
183 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;199 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;
184200
...@@ -191,6 +207,20 @@ pub const Connection = struct {...@@ -191,6 +207,20 @@ pub const Connection = struct {
191 };207 };
192 }208 }
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
194 pub fn fill(conn: *Connection) ReadError!void {224 pub fn fill(conn: *Connection) ReadError!void {
195 if (conn.read_end != conn.read_start) return;225 if (conn.read_end != conn.read_start) return;
196226
...@@ -257,11 +287,21 @@ pub const Connection = struct {...@@ -257,11 +287,21 @@ pub const Connection = struct {
257 return Reader{ .context = conn };287 return Reader{ .context = conn };
258 }288 }
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
260 pub fn writeAllDirect(conn: *Connection, buffer: []const u8) WriteError!void {297 pub fn writeAllDirect(conn: *Connection, buffer: []const u8) WriteError!void {
261 return switch (conn.protocol) {298 if (conn.protocol == .tls) {
262 .plain => conn.stream.writeAll(buffer),299 if (disable_tls) unreachable;
263 .tls => conn.tls_client.writeAll(conn.stream, buffer),300
264 } catch |err| switch (err) {301 return conn.writeAllDirectTls(buffer);
302 }
303
304 return conn.stream.writeAll(buffer) catch |err| switch (err) {
265 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,305 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
266 else => return error.UnexpectedWriteFailure,306 else => return error.UnexpectedWriteFailure,
267 };307 };
...@@ -303,6 +343,8 @@ pub const Connection = struct {...@@ -303,6 +343,8 @@ pub const Connection = struct {
303343
304 pub fn close(conn: *Connection, allocator: Allocator) void {344 pub fn close(conn: *Connection, allocator: Allocator) void {
305 if (conn.protocol == .tls) {345 if (conn.protocol == .tls) {
346 if (disable_tls) unreachable;
347
306 // try to cleanly close the TLS connection, for any server that cares.348 // try to cleanly close the TLS connection, for any server that cares.
307 _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {};349 _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {};
308 allocator.destroy(conn.tls_client);350 allocator.destroy(conn.tls_client);
...@@ -932,7 +974,7 @@ pub const ProxyInformation = struct {...@@ -932,7 +974,7 @@ pub const ProxyInformation = struct {
932/// Release all associated resources with the client.974/// Release all associated resources with the client.
933/// TODO: currently leaks all request allocated data975/// TODO: currently leaks all request allocated data
934pub fn deinit(client: *Client) void {976pub fn deinit(client: *Client) void {
935 client.connection_pool.deinit(client);977 client.connection_pool.deinit(client.allocator);
936978
937 if (client.http_proxy) |*proxy| {979 if (client.http_proxy) |*proxy| {
938 proxy.allocator.free(proxy.host);980 proxy.allocator.free(proxy.host);
...@@ -1046,6 +1088,9 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1046,6 +1088,9 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1046 })) |node|1088 })) |node|
1047 return node;1089 return node;
10481090
1091 if (disable_tls and protocol == .tls)
1092 return error.TlsInitializationFailed;
1093
1049 const conn = try client.allocator.create(ConnectionPool.Node);1094 const conn = try client.allocator.create(ConnectionPool.Node);
1050 errdefer client.allocator.destroy(conn);1095 errdefer client.allocator.destroy(conn);
1051 conn.* = .{ .data = undefined };1096 conn.* = .{ .data = undefined };
...@@ -1073,17 +1118,16 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1073,17 +1118,16 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1073 };1118 };
1074 errdefer client.allocator.free(conn.data.host);1119 errdefer client.allocator.free(conn.data.host);
10751120
1076 switch (protocol) {1121 if (protocol == .tls) {
1077 .plain => {},1122 if (disable_tls) unreachable;
1078 .tls => {
1079 conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);
1080 errdefer client.allocator.destroy(conn.data.tls_client);
10811123
1082 conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed;1124 conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);
1083 // This is appropriate for HTTPS because the HTTP headers contain1125 errdefer client.allocator.destroy(conn.data.tls_client);
1084 // the content length which is used to detect truncation attacks.1126
1085 conn.data.tls_client.allow_truncation_attacks = true;1127 conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed;
1086 },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;
1087 }1131 }
10881132
1089 client.connection_pool.addUsed(conn);1133 client.connection_pool.addUsed(conn);
lib/std/std.zig+8-3
...@@ -283,10 +283,15 @@ pub const options = struct {...@@ -283,10 +283,15 @@ pub const options = struct {
283 else283 else
284 false;284 false;
285285
286 pub const http_connection_pool_size = if (@hasDecl(options_override, "http_connection_pool_size"))286 /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to
287 options_override.http_connection_pool_size287 /// 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
288 else293 else
289 http.Client.default_connection_pool_size;294 false;
290295
291 pub const side_channels_mitigations: crypto.SideChannelsMitigations = if (@hasDecl(options_override, "side_channels_mitigations"))296 pub const side_channels_mitigations: crypto.SideChannelsMitigations = if (@hasDecl(options_override, "side_channels_mitigations"))
292 options_override.side_channels_mitigations297 options_override.side_channels_mitigations
test/standalone/http.zig+4
...@@ -7,6 +7,10 @@ const Client = http.Client;...@@ -7,6 +7,10 @@ const Client = http.Client;
7const mem = std.mem;7const mem = std.mem;
8const testing = std.testing;8const testing = std.testing;
99
10pub const std_options = struct {
11 pub const http_disable_tls = true;
12};
13
10const max_header_size = 8192;14const max_header_size = 8192;
1115
12var gpa_server = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){};16var gpa_server = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){};