aboutsummaryrefslogtreecommitdiff
path: root/lib/std/http
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2024-01-31 21:03:40 -0800
committerGitHub <noreply@github.com>2024-01-31 21:03:40 -0800
commit776cd673f206099012d789fd5d05d49dd72b9faa (patch)
tree6f8f852fffbcb55724aa1a123d602472a2d855c8 /lib/std/http
parent788a0409af15d5823e0e96652ffb71458f78f820 (diff)
parentc1e7d0c08f96f390a641b082b33a8a8717cdd706 (diff)
downloadzig-776cd673f206099012d789fd5d05d49dd72b9faa.tar.gz
zig-776cd673f206099012d789fd5d05d49dd72b9faa.zip
Merge pull request #18746 from jacobly0/http-auth
http: support basic access authentication
Diffstat (limited to 'lib/std/http')
-rw-r--r--lib/std/http/Client.zig86
-rw-r--r--lib/std/http/Headers.zig54
2 files changed, 105 insertions, 35 deletions
diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig
index 8438a7bbd3..2c45b36173 100644
--- a/lib/std/http/Client.zig
+++ b/lib/std/http/Client.zig
@@ -339,7 +339,7 @@ pub const Connection = struct {
/// Writes the given buffer to the connection.
pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
- if (conn.write_end + buffer.len > conn.write_buf.len) {
+ if (conn.write_buf.len - conn.write_end < buffer.len) {
try conn.flush();
if (buffer.len > conn.write_buf.len) {
@@ -354,6 +354,13 @@ pub const Connection = struct {
return buffer.len;
}
+ /// Returns a buffer to be filled with exactly len bytes to write to the connection.
+ pub fn allocWriteBuffer(conn: *Connection, len: BufferSize) WriteError![]u8 {
+ if (conn.write_buf.len - conn.write_end < len) try conn.flush();
+ defer conn.write_end += len;
+ return conn.write_buf[conn.write_end..][0..len];
+ }
+
/// Flushes the write buffer to the connection.
pub fn flush(conn: *Connection) WriteError!void {
if (conn.write_end == 0) return;
@@ -695,6 +702,17 @@ pub const Request = struct {
try w.writeAll("\r\n");
}
+ if ((req.uri.user != null or req.uri.password != null) and
+ !req.headers.contains("authorization"))
+ {
+ try w.writeAll("Authorization: ");
+ const authorization = try req.connection.?.allocWriteBuffer(
+ @intCast(basic_authorization.valueLengthFromUri(req.uri)),
+ );
+ std.debug.assert(basic_authorization.value(req.uri, authorization).len == authorization.len);
+ try w.writeAll("\r\n");
+ }
+
if (!req.headers.contains("user-agent")) {
try w.writeAll("User-Agent: zig/");
try w.writeAll(builtin.zig_version_string);
@@ -1122,19 +1140,11 @@ pub fn loadDefaultProxies(client: *Client) !void {
},
};
- if (uri.user != null and uri.password != null) {
- const prefix = "Basic ";
-
- const unencoded = try std.fmt.allocPrint(client.allocator, "{s}:{s}", .{ uri.user.?, uri.password.? });
- defer client.allocator.free(unencoded);
-
- const buffer = try client.allocator.alloc(u8, std.base64.standard.Encoder.calcSize(unencoded.len) + prefix.len);
- defer client.allocator.free(buffer);
-
- const result = std.base64.standard.Encoder.encode(buffer[prefix.len..], unencoded);
- @memcpy(buffer[0..prefix.len], prefix);
-
- try client.http_proxy.?.headers.append("proxy-authorization", result);
+ if (uri.user != null or uri.password != null) {
+ const authorization = try client.allocator.alloc(u8, basic_authorization.valueLengthFromUri(uri));
+ errdefer client.allocator.free(authorization);
+ std.debug.assert(basic_authorization.value(uri, authorization).len == authorization.len);
+ try client.http_proxy.?.headers.appendOwned(.{ .unowned = "proxy-authorization" }, .{ .owned = authorization });
}
}
@@ -1173,22 +1183,48 @@ pub fn loadDefaultProxies(client: *Client) !void {
},
};
- if (uri.user != null and uri.password != null) {
- const prefix = "Basic ";
+ if (uri.user != null or uri.password != null) {
+ const authorization = try client.allocator.alloc(u8, basic_authorization.valueLengthFromUri(uri));
+ errdefer client.allocator.free(authorization);
+ std.debug.assert(basic_authorization.value(uri, authorization).len == authorization.len);
+ try client.https_proxy.?.headers.appendOwned(.{ .unowned = "proxy-authorization" }, .{ .owned = authorization });
+ }
+ }
+}
- const unencoded = try std.fmt.allocPrint(client.allocator, "{s}:{s}", .{ uri.user.?, uri.password.? });
- defer client.allocator.free(unencoded);
+pub const basic_authorization = struct {
+ pub const max_user_len = 255;
+ pub const max_password_len = 255;
+ pub const max_value_len = valueLength(max_user_len, max_password_len);
- const buffer = try client.allocator.alloc(u8, std.base64.standard.Encoder.calcSize(unencoded.len) + prefix.len);
- defer client.allocator.free(buffer);
+ const prefix = "Basic ";
- const result = std.base64.standard.Encoder.encode(buffer[prefix.len..], unencoded);
- @memcpy(buffer[0..prefix.len], prefix);
+ pub fn valueLength(user_len: usize, password_len: usize) usize {
+ return prefix.len + std.base64.standard.Encoder.calcSize(user_len + 1 + password_len);
+ }
- try client.https_proxy.?.headers.append("proxy-authorization", result);
- }
+ pub fn valueLengthFromUri(uri: Uri) usize {
+ return valueLength(
+ if (uri.user) |user| user.len else 0,
+ if (uri.password) |password| password.len else 0,
+ );
}
-}
+
+ pub fn value(uri: Uri, out: []u8) []u8 {
+ std.debug.assert(uri.user == null or uri.user.?.len <= max_user_len);
+ std.debug.assert(uri.password == null or uri.password.?.len <= max_password_len);
+
+ @memcpy(out[0..prefix.len], prefix);
+
+ var buf: [max_user_len + ":".len + max_password_len]u8 = undefined;
+ const unencoded = std.fmt.bufPrint(&buf, "{s}:{s}", .{
+ uri.user orelse "", uri.password orelse "",
+ }) catch unreachable;
+ const base64 = std.base64.standard.Encoder.encode(out[prefix.len..], unencoded);
+
+ return out[0 .. prefix.len + base64.len];
+ }
+};
pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };
diff --git a/lib/std/http/Headers.zig b/lib/std/http/Headers.zig
index c35775e65b..653ec05126 100644
--- a/lib/std/http/Headers.zig
+++ b/lib/std/http/Headers.zig
@@ -91,30 +91,64 @@ pub const Headers = struct {
///
/// If the `owned` field is true, both name and value will be copied.
pub fn append(headers: *Headers, name: []const u8, value: []const u8) !void {
- const n = headers.list.items.len;
+ try headers.appendOwned(.{ .unowned = name }, .{ .unowned = value });
+ }
- const value_duped = if (headers.owned) try headers.allocator.dupe(u8, value) else value;
- errdefer if (headers.owned) headers.allocator.free(value_duped);
+ pub const OwnedString = union(enum) {
+ /// A string allocated by the `allocator` field.
+ owned: []u8,
+ /// A string to be copied by the `allocator` field.
+ unowned: []const u8,
+ };
- var entry = Field{ .name = undefined, .value = value_duped };
+ /// Appends a header to the list.
+ ///
+ /// If the `owned` field is true, `name` and `value` will be copied if unowned.
+ pub fn appendOwned(headers: *Headers, name: OwnedString, value: OwnedString) !void {
+ const n = headers.list.items.len;
+ try headers.list.ensureUnusedCapacity(headers.allocator, 1);
+
+ const owned_value = switch (value) {
+ .owned => |owned| owned,
+ .unowned => |unowned| if (headers.owned)
+ try headers.allocator.dupe(u8, unowned)
+ else
+ unowned,
+ };
+ errdefer if (value == .unowned and headers.owned) headers.allocator.free(owned_value);
+
+ var entry = Field{ .name = undefined, .value = owned_value };
+
+ if (headers.index.getEntry(switch (name) {
+ inline else => |string| string,
+ })) |kv| {
+ defer switch (name) {
+ .owned => |owned| headers.allocator.free(owned),
+ .unowned => {},
+ };
- if (headers.index.getEntry(name)) |kv| {
entry.name = kv.key_ptr.*;
try kv.value_ptr.append(headers.allocator, n);
} else {
- const name_duped = if (headers.owned) try std.ascii.allocLowerString(headers.allocator, name) else name;
- errdefer if (headers.owned) headers.allocator.free(name_duped);
+ const owned_name = switch (name) {
+ .owned => |owned| owned,
+ .unowned => |unowned| if (headers.owned)
+ try std.ascii.allocLowerString(headers.allocator, unowned)
+ else
+ unowned,
+ };
+ errdefer if (name == .unowned and headers.owned) headers.allocator.free(owned_name);
- entry.name = name_duped;
+ entry.name = owned_name;
var new_index = try HeaderIndexList.initCapacity(headers.allocator, 1);
errdefer new_index.deinit(headers.allocator);
new_index.appendAssumeCapacity(n);
- try headers.index.put(headers.allocator, name_duped, new_index);
+ try headers.index.put(headers.allocator, owned_name, new_index);
}
- try headers.list.append(headers.allocator, entry);
+ headers.list.appendAssumeCapacity(entry);
}
/// Returns true if this list of headers contains the given name.