authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-31 21:03:40-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-01-31 21:03:40-08:00
log776cd673f206099012d789fd5d05d49dd72b9faa
tree6f8f852fffbcb55724aa1a123d602472a2d855c8
parent788a0409af15d5823e0e96652ffb71458f78f820
parentc1e7d0c08f96f390a641b082b33a8a8717cdd706
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #18746 from jacobly0/http-auth

http: support basic access authentication

2 files changed, 105 insertions(+), 35 deletions(-)

lib/std/http/Client.zig+61-25
...@@ -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;
...@@ -695,6 +702,17 @@ pub const Request = struct {...@@ -695,6 +702,17 @@ pub const Request = struct {
695 try w.writeAll("\r\n");702 try w.writeAll("\r\n");
696 }703 }
697704
705 if ((req.uri.user != null or req.uri.password != null) and
706 !req.headers.contains("authorization"))
707 {
708 try w.writeAll("Authorization: ");
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);
713 try w.writeAll("\r\n");
714 }
715
698 if (!req.headers.contains("user-agent")) {716 if (!req.headers.contains("user-agent")) {
699 try w.writeAll("User-Agent: zig/");717 try w.writeAll("User-Agent: zig/");
700 try w.writeAll(builtin.zig_version_string);718 try w.writeAll(builtin.zig_version_string);
...@@ -1122,19 +1140,11 @@ pub fn loadDefaultProxies(client: *Client) !void {...@@ -1122,19 +1140,11 @@ pub fn loadDefaultProxies(client: *Client) !void {
1122 },1140 },
1123 };1141 };
11241142
1125 if (uri.user != null and uri.password != null) {1143 if (uri.user != null or uri.password != null) {
1126 const prefix = "Basic ";1144 const authorization = try client.allocator.alloc(u8, basic_authorization.valueLengthFromUri(uri));
11271145 errdefer client.allocator.free(authorization);
1128 const unencoded = try std.fmt.allocPrint(client.allocator, "{s}:{s}", .{ uri.user.?, uri.password.? });1146 std.debug.assert(basic_authorization.value(uri, authorization).len == authorization.len);
1129 defer client.allocator.free(unencoded);1147 try client.http_proxy.?.headers.appendOwned(.{ .unowned = "proxy-authorization" }, .{ .owned = authorization });
1130
1131 const buffer = try client.allocator.alloc(u8, std.base64.standard.Encoder.calcSize(unencoded.len) + prefix.len);
1132 defer client.allocator.free(buffer);
1133
1134 const result = std.base64.standard.Encoder.encode(buffer[prefix.len..], unencoded);
1135 @memcpy(buffer[0..prefix.len], prefix);
1136
1137 try client.http_proxy.?.headers.append("proxy-authorization", result);
1138 }1148 }
1139 }1149 }
11401150
...@@ -1173,22 +1183,48 @@ pub fn loadDefaultProxies(client: *Client) !void {...@@ -1173,22 +1183,48 @@ pub fn loadDefaultProxies(client: *Client) !void {
1173 },1183 },
1174 };1184 };
11751185
1176 if (uri.user != null and uri.password != null) {1186 if (uri.user != null or uri.password != null) {
1177 const prefix = "Basic ";1187 const authorization = try client.allocator.alloc(u8, basic_authorization.valueLengthFromUri(uri));
1188 errdefer client.allocator.free(authorization);
1189 std.debug.assert(basic_authorization.value(uri, authorization).len == authorization.len);
1190 try client.https_proxy.?.headers.appendOwned(.{ .unowned = "proxy-authorization" }, .{ .owned = authorization });
1191 }
1192 }
1193}
11781194
1179 const unencoded = try std.fmt.allocPrint(client.allocator, "{s}:{s}", .{ uri.user.?, uri.password.? });1195pub const basic_authorization = struct {
1180 defer client.allocator.free(unencoded);1196 pub const max_user_len = 255;
1197 pub const max_password_len = 255;
1198 pub const max_value_len = valueLength(max_user_len, max_password_len);
11811199
1182 const buffer = try client.allocator.alloc(u8, std.base64.standard.Encoder.calcSize(unencoded.len) + prefix.len);1200 const prefix = "Basic ";
1183 defer client.allocator.free(buffer);
11841201
1185 const result = std.base64.standard.Encoder.encode(buffer[prefix.len..], unencoded);1202 pub fn valueLength(user_len: usize, password_len: usize) usize {
1186 @memcpy(buffer[0..prefix.len], prefix);1203 return prefix.len + std.base64.standard.Encoder.calcSize(user_len + 1 + password_len);
1204 }
11871205
1188 try client.https_proxy.?.headers.append("proxy-authorization", result);1206 pub fn valueLengthFromUri(uri: Uri) usize {
1189 }1207 return valueLength(
1208 if (uri.user) |user| user.len else 0,
1209 if (uri.password) |password| password.len else 0,
1210 );
1190 }1211 }
1191}1212
1213 pub fn value(uri: Uri, out: []u8) []u8 {
1214 std.debug.assert(uri.user == null or uri.user.?.len <= max_user_len);
1215 std.debug.assert(uri.password == null or uri.password.?.len <= max_password_len);
1216
1217 @memcpy(out[0..prefix.len], prefix);
1218
1219 var buf: [max_user_len + ":".len + max_password_len]u8 = undefined;
1220 const unencoded = std.fmt.bufPrint(&buf, "{s}:{s}", .{
1221 uri.user orelse "", uri.password orelse "",
1222 }) catch unreachable;
1223 const base64 = std.base64.standard.Encoder.encode(out[prefix.len..], unencoded);
1224
1225 return out[0 .. prefix.len + base64.len];
1226 }
1227};
11921228
1193pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };1229pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };
11941230
lib/std/http/Headers.zig+44-10
...@@ -91,30 +91,64 @@ pub const Headers = struct {...@@ -91,30 +91,64 @@ pub const Headers = struct {
91 ///91 ///
92 /// If the `owned` field is true, both name and value will be copied.92 /// If the `owned` field is true, both name and value will be copied.
93 pub fn append(headers: *Headers, name: []const u8, value: []const u8) !void {93 pub fn append(headers: *Headers, name: []const u8, value: []const u8) !void {
94 const n = headers.list.items.len;94 try headers.appendOwned(.{ .unowned = name }, .{ .unowned = value });
95 }
9596
96 const value_duped = if (headers.owned) try headers.allocator.dupe(u8, value) else value;97 pub const OwnedString = union(enum) {
97 errdefer if (headers.owned) headers.allocator.free(value_duped);98 /// A string allocated by the `allocator` field.
99 owned: []u8,
100 /// A string to be copied by the `allocator` field.
101 unowned: []const u8,
102 };
98103
99 var entry = Field{ .name = undefined, .value = value_duped };104 /// Appends a header to the list.
105 ///
106 /// If the `owned` field is true, `name` and `value` will be copied if unowned.
107 pub fn appendOwned(headers: *Headers, name: OwnedString, value: OwnedString) !void {
108 const n = headers.list.items.len;
109 try headers.list.ensureUnusedCapacity(headers.allocator, 1);
110
111 const owned_value = switch (value) {
112 .owned => |owned| owned,
113 .unowned => |unowned| if (headers.owned)
114 try headers.allocator.dupe(u8, unowned)
115 else
116 unowned,
117 };
118 errdefer if (value == .unowned and headers.owned) headers.allocator.free(owned_value);
119
120 var entry = Field{ .name = undefined, .value = owned_value };
121
122 if (headers.index.getEntry(switch (name) {
123 inline else => |string| string,
124 })) |kv| {
125 defer switch (name) {
126 .owned => |owned| headers.allocator.free(owned),
127 .unowned => {},
128 };
100129
101 if (headers.index.getEntry(name)) |kv| {
102 entry.name = kv.key_ptr.*;130 entry.name = kv.key_ptr.*;
103 try kv.value_ptr.append(headers.allocator, n);131 try kv.value_ptr.append(headers.allocator, n);
104 } else {132 } else {
105 const name_duped = if (headers.owned) try std.ascii.allocLowerString(headers.allocator, name) else name;133 const owned_name = switch (name) {
106 errdefer if (headers.owned) headers.allocator.free(name_duped);134 .owned => |owned| owned,
135 .unowned => |unowned| if (headers.owned)
136 try std.ascii.allocLowerString(headers.allocator, unowned)
137 else
138 unowned,
139 };
140 errdefer if (name == .unowned and headers.owned) headers.allocator.free(owned_name);
107141
108 entry.name = name_duped;142 entry.name = owned_name;
109143
110 var new_index = try HeaderIndexList.initCapacity(headers.allocator, 1);144 var new_index = try HeaderIndexList.initCapacity(headers.allocator, 1);
111 errdefer new_index.deinit(headers.allocator);145 errdefer new_index.deinit(headers.allocator);
112146
113 new_index.appendAssumeCapacity(n);147 new_index.appendAssumeCapacity(n);
114 try headers.index.put(headers.allocator, name_duped, new_index);148 try headers.index.put(headers.allocator, owned_name, new_index);
115 }149 }
116150
117 try headers.list.append(headers.allocator, entry);151 headers.list.appendAssumeCapacity(entry);
118 }152 }
119153
120 /// Returns true if this list of headers contains the given name.154 /// Returns true if this list of headers contains the given name.