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 {...@@ -339,7 +339,7 @@ pub const Connection = struct {
339339
340 /// Writes the given buffer to the connection.340 /// Writes the given buffer to the connection.
341 pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {341 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) {
343 try conn.flush();343 try conn.flush();
344344
345 if (buffer.len > conn.write_buf.len) {345 if (buffer.len > conn.write_buf.len) {
...@@ -354,6 +354,13 @@ pub const Connection = struct {...@@ -354,6 +354,13 @@ pub const Connection = struct {
354 return buffer.len;354 return buffer.len;
355 }355 }
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
357 /// Flushes the write buffer to the connection.364 /// Flushes the write buffer to the connection.
358 pub fn flush(conn: *Connection) WriteError!void {365 pub fn flush(conn: *Connection) WriteError!void {
359 if (conn.write_end == 0) return;366 if (conn.write_end == 0) return;
...@@ -657,7 +664,7 @@ pub const Request = struct {...@@ -657,7 +664,7 @@ pub const Request = struct {
657 };664 };
658 }665 }
659666
660 pub const SendError = Allocator.Error || Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };667 pub const SendError = Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
661668
662 pub const SendOptions = struct {669 pub const SendOptions = struct {
663 /// Specifies that the uri should be used as is. You guarantee that the uri is already escaped.670 /// 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 {...@@ -699,7 +706,10 @@ pub const Request = struct {
699 !req.headers.contains("authorization"))706 !req.headers.contains("authorization"))
700 {707 {
701 try w.writeAll("Authorization: ");708 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);
703 try w.writeAll("\r\n");713 try w.writeAll("\r\n");
704 }714 }
705715
...@@ -1131,10 +1141,8 @@ pub fn loadDefaultProxies(client: *Client) !void {...@@ -1131,10 +1141,8 @@ pub fn loadDefaultProxies(client: *Client) !void {
1131 };1141 };
11321142
1133 if (uri.user != null or uri.password != null) {1143 if (uri.user != null or uri.password != null) {
1134 const authorization = try basicAuthorizationValue(client.allocator, uri);1144 var authorization: [basic_authorization.max_value_len]u8 = undefined;
1135 defer client.allocator.free(authorization);1145 try client.http_proxy.?.headers.append("proxy-authorization", basic_authorization.value(uri, &authorization));
1136
1137 try client.http_proxy.?.headers.append("proxy-authorization", authorization);
1138 }1146 }
1139 }1147 }
11401148
...@@ -1174,31 +1182,45 @@ pub fn loadDefaultProxies(client: *Client) !void {...@@ -1174,31 +1182,45 @@ pub fn loadDefaultProxies(client: *Client) !void {
1174 };1182 };
11751183
1176 if (uri.user != null or uri.password != null) {1184 if (uri.user != null or uri.password != null) {
1177 const authorization = try basicAuthorizationValue(client.allocator, uri);1185 var authorization: [basic_authorization.max_value_len]u8 = undefined;
1178 defer client.allocator.free(authorization);1186 try client.https_proxy.?.headers.append("proxy-authorization", basic_authorization.value(uri, &authorization));
1179
1180 try client.https_proxy.?.headers.append("proxy-authorization", authorization);
1181 }1187 }
1182 }1188 }
1183}1189}
11841190
1185pub fn basicAuthorizationValue(1191pub const basic_authorization = struct {
1186 allocator: Allocator,1192 pub const max_user_len = 255;
1187 uri: Uri,1193 pub const max_password_len = 255;
1188) Allocator.Error![]const u8 {1194 pub const max_value_len = valueLength(max_user_len, max_password_len);
1195
1189 const prefix = "Basic ";1196 const prefix = "Basic ";
11901197
1191 const unencoded = try std.fmt.allocPrint(allocator, "{s}:{s}", .{ uri.user orelse "", uri.password orelse "" });1198 pub fn valueLength(user_len: usize, password_len: usize) usize {
1192 defer allocator.free(unencoded);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));1209 pub fn value(uri: Uri, out: []u8) []u8 {
1195 errdefer allocator.free(buffer);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);1213 @memcpy(out[0..prefix.len], prefix);
1198 _ = std.base64.standard.Encoder.encode(buffer[prefix.len..], unencoded);
11991214
1200 return buffer;1215 var buf: [max_user_len + ":".len + max_password_len]u8 = undefined;
1201}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
1203pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };1225pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };
12041226