authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-01-31 14:44:34+01:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-01-31 15:00:27+01:00
loga111f805cd6cc82952786d0ffccb5a31c68f6353
treea0dfe75cebdeaab9a4cb8c9b6c6a54022c5c8f9b
parent82b37ea0240c0e77857149d80beb4dda2b095dbb

http: avoid allocator use when encoding basic authorization


1 files changed, 45 insertions(+), 23 deletions(-)

lib/std/http/Client.zig+45-23
......@@ -339,7 +339,7 @@ pub const Connection = struct {
339339
340340 /// Writes the given buffer to the connection.
341341 pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
342 if (conn.write_end + buffer.len > conn.write_buf.len) {
342 if (conn.write_buf.len - conn.write_end < buffer.len) {
343343 try conn.flush();
344344
345345 if (buffer.len > conn.write_buf.len) {
......@@ -354,6 +354,13 @@ pub const Connection = struct {
354354 return buffer.len;
355355 }
356356
357 /// Returns a buffer to be filled with exactly len bytes to write to the connection.
358 pub fn allocWriteBuffer(conn: *Connection, len: BufferSize) WriteError![]u8 {
359 if (conn.write_buf.len - conn.write_end < len) try conn.flush();
360 defer conn.write_end += len;
361 return conn.write_buf[conn.write_end..][0..len];
362 }
363
357364 /// Flushes the write buffer to the connection.
358365 pub fn flush(conn: *Connection) WriteError!void {
359366 if (conn.write_end == 0) return;
......@@ -657,7 +664,7 @@ pub const Request = struct {
657664 };
658665 }
659666
660 pub const SendError = Allocator.Error || Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
667 pub const SendError = Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
661668
662669 pub const SendOptions = struct {
663670 /// Specifies that the uri should be used as is. You guarantee that the uri is already escaped.
......@@ -699,7 +706,10 @@ pub const Request = struct {
699706 !req.headers.contains("authorization"))
700707 {
701708 try w.writeAll("Authorization: ");
702 try w.writeAll(try basicAuthorizationValue(req.arena.allocator(), req.uri));
709 const authorization = try req.connection.?.allocWriteBuffer(
710 @intCast(basic_authorization.valueLengthFromUri(req.uri)),
711 );
712 std.debug.assert(basic_authorization.value(req.uri, authorization).len == authorization.len);
703713 try w.writeAll("\r\n");
704714 }
705715
......@@ -1131,10 +1141,8 @@ pub fn loadDefaultProxies(client: *Client) !void {
11311141 };
11321142
11331143 if (uri.user != null or uri.password != null) {
1134 const authorization = try basicAuthorizationValue(client.allocator, uri);
1135 defer client.allocator.free(authorization);
1136
1137 try client.http_proxy.?.headers.append("proxy-authorization", authorization);
1144 var authorization: [basic_authorization.max_value_len]u8 = undefined;
1145 try client.http_proxy.?.headers.append("proxy-authorization", basic_authorization.value(uri, &authorization));
11381146 }
11391147 }
11401148
......@@ -1174,31 +1182,45 @@ pub fn loadDefaultProxies(client: *Client) !void {
11741182 };
11751183
11761184 if (uri.user != null or uri.password != null) {
1177 const authorization = try basicAuthorizationValue(client.allocator, uri);
1178 defer client.allocator.free(authorization);
1179
1180 try client.https_proxy.?.headers.append("proxy-authorization", authorization);
1185 var authorization: [basic_authorization.max_value_len]u8 = undefined;
1186 try client.https_proxy.?.headers.append("proxy-authorization", basic_authorization.value(uri, &authorization));
11811187 }
11821188 }
11831189}
11841190
1185pub fn basicAuthorizationValue(
1186 allocator: Allocator,
1187 uri: Uri,
1188) Allocator.Error![]const u8 {
1191pub const basic_authorization = struct {
1192 pub const max_user_len = 255;
1193 pub const max_password_len = 255;
1194 pub const max_value_len = valueLength(max_user_len, max_password_len);
1195
11891196 const prefix = "Basic ";
11901197
1191 const unencoded = try std.fmt.allocPrint(allocator, "{s}:{s}", .{ uri.user orelse "", uri.password orelse "" });
1192 defer allocator.free(unencoded);
1198 pub fn valueLength(user_len: usize, password_len: usize) usize {
1199 return prefix.len + std.base64.standard.Encoder.calcSize(user_len + 1 + password_len);
1200 }
1201
1202 pub fn valueLengthFromUri(uri: Uri) usize {
1203 return valueLength(
1204 if (uri.user) |user| user.len else 0,
1205 if (uri.password) |password| password.len else 0,
1206 );
1207 }
11931208
1194 const buffer = try allocator.alloc(u8, prefix.len + std.base64.standard.Encoder.calcSize(unencoded.len));
1195 errdefer allocator.free(buffer);
1209 pub fn value(uri: Uri, out: []u8) []u8 {
1210 std.debug.assert(uri.user == null or uri.user.?.len <= max_user_len);
1211 std.debug.assert(uri.password == null or uri.password.?.len <= max_password_len);
11961212
1197 @memcpy(buffer[0..prefix.len], prefix);
1198 _ = std.base64.standard.Encoder.encode(buffer[prefix.len..], unencoded);
1213 @memcpy(out[0..prefix.len], prefix);
11991214
1200 return buffer;
1201}
1215 var buf: [max_user_len + ":".len + max_password_len]u8 = undefined;
1216 const unencoded = std.fmt.bufPrint(&buf, "{s}:{s}", .{
1217 uri.user orelse "", uri.password orelse "",
1218 }) catch unreachable;
1219 const base64 = std.base64.standard.Encoder.encode(out[prefix.len..], unencoded);
1220
1221 return out[0 .. prefix.len + base64.len];
1222 }
1223};
12021224
12031225pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };
12041226