authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-03-06 23:35:35-06:00
committergravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-03-09 14:55:13-06:00
log0a4130f364c2714b206257d0cf589103da823407
tree6c790af4ce7c8c98cca5e1114602a103d8a06ca1
parentfd2f906d1ede2b65ba21eec59137b2d4b676eedc
signature Commit is signed but in an unrecognized format.

std.http: handle relative redirects


4 files changed, 196 insertions(+), 61 deletions(-)

lib/std/Uri.zig+96-13
...@@ -16,15 +16,27 @@ fragment: ?[]const u8,...@@ -16,15 +16,27 @@ fragment: ?[]const u8,
1616
17/// Applies URI encoding and replaces all reserved characters with their respective %XX code.17/// Applies URI encoding and replaces all reserved characters with their respective %XX code.
18pub fn escapeString(allocator: std.mem.Allocator, input: []const u8) error{OutOfMemory}![]const u8 {18pub fn escapeString(allocator: std.mem.Allocator, input: []const u8) error{OutOfMemory}![]const u8 {
19 return escapeStringWithFn(allocator, input, isUnreserved);
20}
21
22pub fn escapePath(allocator: std.mem.Allocator, input: []const u8) error{OutOfMemory}![]const u8 {
23 return escapeStringWithFn(allocator, input, isPathChar);
24}
25
26pub fn escapeQuery(allocator: std.mem.Allocator, input: []const u8) error{OutOfMemory}![]const u8 {
27 return escapeStringWithFn(allocator, input, isQueryChar);
28}
29
30pub fn escapeStringWithFn(allocator: std.mem.Allocator, input: []const u8, comptime keepUnescaped: fn (c: u8) bool) std.mem.Allocator.Error![]const u8 {
19 var outsize: usize = 0;31 var outsize: usize = 0;
20 for (input) |c| {32 for (input) |c| {
21 outsize += if (isUnreserved(c)) @as(usize, 1) else 3;33 outsize += if (keepUnescaped(c)) @as(usize, 1) else 3;
22 }34 }
23 var output = try allocator.alloc(u8, outsize);35 var output = try allocator.alloc(u8, outsize);
24 var outptr: usize = 0;36 var outptr: usize = 0;
2537
26 for (input) |c| {38 for (input) |c| {
27 if (isUnreserved(c)) {39 if (keepUnescaped(c)) {
28 output[outptr] = c;40 output[outptr] = c;
29 outptr += 1;41 outptr += 1;
30 } else {42 } else {
...@@ -94,13 +106,14 @@ pub fn unescapeString(allocator: std.mem.Allocator, input: []const u8) error{Out...@@ -94,13 +106,14 @@ pub fn unescapeString(allocator: std.mem.Allocator, input: []const u8) error{Out
94106
95pub const ParseError = error{ UnexpectedCharacter, InvalidFormat, InvalidPort };107pub const ParseError = error{ UnexpectedCharacter, InvalidFormat, InvalidPort };
96108
97/// Parses the URI or returns an error.109/// Parses the URI or returns an error. This function is not compliant, but is required to parse
110/// some forms of URIs in the wild. Such as HTTP Location headers.
98/// The return value will contain unescaped strings pointing into the111/// The return value will contain unescaped strings pointing into the
99/// original `text`. Each component that is provided, will be non-`null`.112/// original `text`. Each component that is provided, will be non-`null`.
100pub fn parse(text: []const u8) ParseError!Uri {113pub fn parseWithoutScheme(text: []const u8) ParseError!Uri {
101 var reader = SliceReader{ .slice = text };114 var reader = SliceReader{ .slice = text };
102 var uri = Uri{115 var uri = Uri{
103 .scheme = reader.readWhile(isSchemeChar),116 .scheme = "",
104 .user = null,117 .user = null,
105 .password = null,118 .password = null,
106 .host = null,119 .host = null,
...@@ -110,14 +123,6 @@ pub fn parse(text: []const u8) ParseError!Uri {...@@ -110,14 +123,6 @@ pub fn parse(text: []const u8) ParseError!Uri {
110 .fragment = null,123 .fragment = null,
111 };124 };
112125
113 // after the scheme, a ':' must appear
114 if (reader.get()) |c| {
115 if (c != ':')
116 return error.UnexpectedCharacter;
117 } else {
118 return error.InvalidFormat;
119 }
120
121 if (reader.peekPrefix("//")) { // authority part126 if (reader.peekPrefix("//")) { // authority part
122 std.debug.assert(reader.get().? == '/');127 std.debug.assert(reader.get().? == '/');
123 std.debug.assert(reader.get().? == '/');128 std.debug.assert(reader.get().? == '/');
...@@ -179,6 +184,76 @@ pub fn parse(text: []const u8) ParseError!Uri {...@@ -179,6 +184,76 @@ pub fn parse(text: []const u8) ParseError!Uri {
179 return uri;184 return uri;
180}185}
181186
187/// Parses the URI or returns an error.
188/// The return value will contain unescaped strings pointing into the
189/// original `text`. Each component that is provided, will be non-`null`.
190pub fn parse(text: []const u8) ParseError!Uri {
191 var reader = SliceReader{ .slice = text };
192 const scheme = reader.readWhile(isSchemeChar);
193
194 // after the scheme, a ':' must appear
195 if (reader.get()) |c| {
196 if (c != ':')
197 return error.UnexpectedCharacter;
198 } else {
199 return error.InvalidFormat;
200 }
201
202 var uri = try parseWithoutScheme(reader.readUntilEof());
203 uri.scheme = scheme;
204
205 return uri;
206}
207
208/// Resolves a URI against a base URI, conforming to RFC 3986, Section 5.
209/// arena owns any memory allocated by this function.
210pub fn resolve(Base: Uri, R: Uri, strict: bool, arena: std.mem.Allocator) !Uri {
211 var T: Uri = undefined;
212
213 if (R.scheme.len > 0 and !((!strict) and (std.mem.eql(u8, R.scheme, Base.scheme)))) {
214 T.scheme = R.scheme;
215 T.user = R.user;
216 T.host = R.host;
217 T.port = R.port;
218 T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path });
219 T.query = R.query;
220 } else {
221 if (R.host) |host| {
222 T.user = R.user;
223 T.host = host;
224 T.port = R.port;
225 T.path = R.path;
226 T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path });
227 T.query = R.query;
228 } else {
229 if (R.path.len == 0) {
230 T.path = Base.path;
231 if (R.query) |query| {
232 T.query = query;
233 } else {
234 T.query = Base.query;
235 }
236 } else {
237 if (R.path[0] == '/') {
238 T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path });
239 } else {
240 T.path = try std.fs.path.resolvePosix(arena, &.{ "/", Base.path, R.path });
241 }
242 T.query = R.query;
243 }
244
245 T.user = Base.user;
246 T.host = Base.host;
247 T.port = Base.port;
248 }
249 T.scheme = Base.scheme;
250 }
251
252 T.fragment = R.fragment;
253
254 return T;
255}
256
182const SliceReader = struct {257const SliceReader = struct {
183 const Self = @This();258 const Self = @This();
184259
...@@ -284,6 +359,14 @@ fn isPathSeparator(c: u8) bool {...@@ -284,6 +359,14 @@ fn isPathSeparator(c: u8) bool {
284 };359 };
285}360}
286361
362fn isPathChar(c: u8) bool {
363 return isUnreserved(c) or isSubLimit(c) or c == '/' or c == ':' or c == '@';
364}
365
366fn isQueryChar(c: u8) bool {
367 return isPathChar(c) or c == '?';
368}
369
287fn isQuerySeparator(c: u8) bool {370fn isQuerySeparator(c: u8) bool {
288 return switch (c) {371 return switch (c) {
289 '#' => true,372 '#' => true,
lib/std/crypto/tls/Client.zig+1-1
...@@ -89,7 +89,7 @@ pub const StreamInterface = struct {...@@ -89,7 +89,7 @@ pub const StreamInterface = struct {
89};89};
9090
91pub fn InitError(comptime Stream: type) type {91pub fn InitError(comptime Stream: type) type {
92 return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || error {92 return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || error{
93 InsufficientEntropy,93 InsufficientEntropy,
94 DiskQuota,94 DiskQuota,
95 LockViolation,95 LockViolation,
lib/std/http/Client.zig+96-44
...@@ -29,9 +29,10 @@ const ConnectionPool = std.TailQueue(Connection);...@@ -29,9 +29,10 @@ const ConnectionPool = std.TailQueue(Connection);
29const ConnectionNode = ConnectionPool.Node;29const ConnectionNode = ConnectionPool.Node;
3030
31/// Acquires an existing connection from the connection pool. This function is threadsafe.31/// Acquires an existing connection from the connection pool. This function is threadsafe.
32pub fn acquire(client: *Client, node: *ConnectionNode) void {32/// If the caller already holds the connection mutex, it should pass `true` for `held`.
33 client.connection_mutex.lock();33pub fn acquire(client: *Client, node: *ConnectionNode, held: bool) void {
34 defer client.connection_mutex.unlock();34 if (!held) client.connection_mutex.lock();
35 defer if (!held) client.connection_mutex.unlock();
3536
36 client.connection_pool.remove(node);37 client.connection_pool.remove(node);
37 client.connection_used.append(node);38 client.connection_used.append(node);
...@@ -40,16 +41,17 @@ pub fn acquire(client: *Client, node: *ConnectionNode) void {...@@ -40,16 +41,17 @@ pub fn acquire(client: *Client, node: *ConnectionNode) void {
40/// Tries to release a connection back to the connection pool. This function is threadsafe.41/// Tries to release a connection back to the connection pool. This function is threadsafe.
41/// If the connection is marked as closing, it will be closed instead.42/// If the connection is marked as closing, it will be closed instead.
42pub fn release(client: *Client, node: *ConnectionNode) void {43pub fn release(client: *Client, node: *ConnectionNode) void {
44 client.connection_mutex.lock();
45 defer client.connection_mutex.unlock();
46
47 client.connection_used.remove(node);
48
43 if (node.data.closing) {49 if (node.data.closing) {
44 node.data.close(client);50 node.data.close(client);
4551
46 return client.allocator.destroy(node);52 return client.allocator.destroy(node);
47 }53 }
4854
49 client.connection_mutex.lock();
50 defer client.connection_mutex.unlock();
51
52 client.connection_used.remove(node);
53 client.connection_pool.append(node);55 client.connection_pool.append(node);
54}56}
5557
...@@ -83,7 +85,7 @@ pub const Connection = struct {...@@ -83,7 +85,7 @@ pub const Connection = struct {
83 }85 }
84 }86 }
8587
86 pub const ReadError = std.net.Stream.ReadError || error{88 pub const ReadError = net.Stream.ReadError || error{
87 TlsConnectionTruncated,89 TlsConnectionTruncated,
88 TlsRecordOverflow,90 TlsRecordOverflow,
89 TlsDecodeError,91 TlsDecodeError,
...@@ -115,7 +117,7 @@ pub const Connection = struct {...@@ -115,7 +117,7 @@ pub const Connection = struct {
115 }117 }
116 }118 }
117119
118 pub const WriteError = std.net.Stream.WriteError || error{};120 pub const WriteError = net.Stream.WriteError || error{};
119 pub const Writer = std.io.Writer(*Connection, WriteError, write);121 pub const Writer = std.io.Writer(*Connection, WriteError, write);
120122
121 pub fn writer(conn: *Connection) Writer {123 pub fn writer(conn: *Connection) Writer {
...@@ -139,14 +141,21 @@ pub const Request = struct {...@@ -139,14 +141,21 @@ pub const Request = struct {
139 const read_buffer_size = 8192;141 const read_buffer_size = 8192;
140 const ReadBufferIndex = std.math.IntFittingRange(0, read_buffer_size);142 const ReadBufferIndex = std.math.IntFittingRange(0, read_buffer_size);
141143
144 uri: Uri,
142 client: *Client,145 client: *Client,
143 connection: *ConnectionNode,146 connection: *ConnectionNode,
144 redirects_left: u32,
145 response: Response,147 response: Response,
146 /// These are stored in Request so that they are available when following148 /// These are stored in Request so that they are available when following
147 /// redirects.149 /// redirects.
148 headers: Headers,150 headers: Headers,
149151
152 redirects_left: u32,
153 handle_redirects: bool,
154 compression_init: bool,
155
156 /// Used as a allocator for resolving redirects locations.
157 arena: std.heap.ArenaAllocator,
158
150 /// Read buffer for the connection. This is used to pull in large amounts of data from the connection even if the user asks for a small amount. This can probably be removed with careful planning.159 /// Read buffer for the connection. This is used to pull in large amounts of data from the connection even if the user asks for a small amount. This can probably be removed with careful planning.
151 read_buffer: [read_buffer_size]u8 = undefined,160 read_buffer: [read_buffer_size]u8 = undefined,
152 read_buffer_start: ReadBufferIndex = 0,161 read_buffer_start: ReadBufferIndex = 0,
...@@ -661,6 +670,7 @@ pub const Request = struct {...@@ -661,6 +670,7 @@ pub const Request = struct {
661 pub const Headers = struct {670 pub const Headers = struct {
662 version: http.Version = .@"HTTP/1.1",671 version: http.Version = .@"HTTP/1.1",
663 method: http.Method = .GET,672 method: http.Method = .GET,
673 user_agent: []const u8 = "Zig (std.http)",
664 connection: http.Connection = .keep_alive,674 connection: http.Connection = .keep_alive,
665 transfer_encoding: RequestTransfer = .none,675 transfer_encoding: RequestTransfer = .none,
666676
...@@ -668,6 +678,7 @@ pub const Request = struct {...@@ -668,6 +678,7 @@ pub const Request = struct {
668 };678 };
669679
670 pub const Options = struct {680 pub const Options = struct {
681 handle_redirects: bool = true,
671 max_redirects: u32 = 3,682 max_redirects: u32 = 3,
672 header_strategy: HeaderStrategy = .{ .dynamic = 16 * 1024 },683 header_strategy: HeaderStrategy = .{ .dynamic = 16 * 1024 },
673684
...@@ -703,10 +714,11 @@ pub const Request = struct {...@@ -703,10 +714,11 @@ pub const Request = struct {
703 req.client.release(req.connection);714 req.client.release(req.connection);
704 }715 }
705716
717 req.arena.deinit();
706 req.* = undefined;718 req.* = undefined;
707 }719 }
708720
709 const ReadRawError = Connection.ReadError || std.Uri.ParseError || RequestError || error{721 const ReadRawError = Connection.ReadError || Uri.ParseError || RequestError || error{
710 UnexpectedEndOfStream,722 UnexpectedEndOfStream,
711 TooManyHttpRedirects,723 TooManyHttpRedirects,
712 HttpRedirectMissingLocation,724 HttpRedirectMissingLocation,
...@@ -723,9 +735,7 @@ pub const Request = struct {...@@ -723,9 +735,7 @@ pub const Request = struct {
723 var index: usize = 0;735 var index: usize = 0;
724 while (index == 0) {736 while (index == 0) {
725 const amt = try req.readRawAdvanced(buffer[index..]);737 const amt = try req.readRawAdvanced(buffer[index..]);
726 const zero_means_end = req.response.done and req.response.headers.status.class() != .redirect;738 if (amt == 0 and req.response.done) break;
727
728 if (amt == 0 and zero_means_end) break;
729 index += amt;739 index += amt;
730 }740 }
731741
...@@ -769,6 +779,8 @@ pub const Request = struct {...@@ -769,6 +779,8 @@ pub const Request = struct {
769 }779 }
770 } else if (req.response.headers.content_length) |content_length| {780 } else if (req.response.headers.content_length) |content_length| {
771 req.response.next_chunk_length = content_length;781 req.response.next_chunk_length = content_length;
782
783 if (content_length == 0) req.response.done = true;
772 } else {784 } else {
773 req.response.done = true;785 req.response.done = true;
774 }786 }
...@@ -779,7 +791,7 @@ pub const Request = struct {...@@ -779,7 +791,7 @@ pub const Request = struct {
779 return 0;791 return 0;
780 }792 }
781793
782 pub const WaitForCompleteHeadError = ReadRawError || error {794 pub const WaitForCompleteHeadError = ReadRawError || error{
783 UnexpectedEndOfStream,795 UnexpectedEndOfStream,
784796
785 HttpHeadersExceededSizeLimit,797 HttpHeadersExceededSizeLimit,
...@@ -810,27 +822,8 @@ pub const Request = struct {...@@ -810,27 +822,8 @@ pub const Request = struct {
810822
811 /// This one can return 0 without meaning EOF.823 /// This one can return 0 without meaning EOF.
812 fn readRawAdvanced(req: *Request, buffer: []u8) !usize {824 fn readRawAdvanced(req: *Request, buffer: []u8) !usize {
813 if (req.response.done) {825 assert(req.response.state.isContent());
814 if (req.response.headers.status.class() == .redirect) {826 if (req.response.done) return 0;
815 if (req.redirects_left == 0) return error.TooManyHttpRedirects;
816
817 const location = req.response.headers.location orelse
818 return error.HttpRedirectMissingLocation;
819 const new_url = try std.Uri.parse(location);
820 const new_req = try req.client.request(new_url, req.headers, .{
821 .max_redirects = req.redirects_left - 1,
822 .header_strategy = if (req.response.header_bytes_owned) .{
823 .dynamic = req.response.max_header_bytes,
824 } else .{
825 .static = req.response.header_bytes.unusedCapacitySlice(),
826 },
827 });
828 req.deinit();
829 req.* = new_req;
830 } else {
831 return 0;
832 }
833 }
834827
835 // var in: []const u8 = undefined;828 // var in: []const u8 = undefined;
836 if (req.read_buffer_start == req.read_buffer_len) {829 if (req.read_buffer_start == req.read_buffer_len) {
...@@ -851,7 +844,7 @@ pub const Request = struct {...@@ -851,7 +844,7 @@ pub const Request = struct {
851 const data_avail = req.response.next_chunk_length;844 const data_avail = req.response.next_chunk_length;
852 const out_avail = buffer.len;845 const out_avail = buffer.len;
853846
854 if (req.response.state.isContent() and req.response.headers.status.class() == .redirect) {847 if (req.handle_redirects and req.response.headers.status.class() == .redirect) {
855 const can_read = @intCast(usize, @min(buf_avail, data_avail));848 const can_read = @intCast(usize, @min(buf_avail, data_avail));
856 req.response.next_chunk_length -= can_read;849 req.response.next_chunk_length -= can_read;
857850
...@@ -859,7 +852,6 @@ pub const Request = struct {...@@ -859,7 +852,6 @@ pub const Request = struct {
859 req.client.release(req.connection);852 req.client.release(req.connection);
860 req.connection = undefined;853 req.connection = undefined;
861 req.response.done = true;854 req.response.done = true;
862 continue;
863 }855 }
864856
865 return 0; // skip over as much data as possible857 return 0; // skip over as much data as possible
...@@ -943,7 +935,7 @@ pub const Request = struct {...@@ -943,7 +935,7 @@ pub const Request = struct {
943 const data_avail = req.response.next_chunk_length;935 const data_avail = req.response.next_chunk_length;
944 const out_avail = buffer.len - out_index;936 const out_avail = buffer.len - out_index;
945937
946 if (req.response.state.isContent() and req.response.headers.status.class() == .redirect) {938 if (req.handle_redirects and req.response.headers.status.class() == .redirect) {
947 const can_read = @intCast(usize, @min(buf_avail, data_avail));939 const can_read = @intCast(usize, @min(buf_avail, data_avail));
948 req.response.next_chunk_length -= can_read;940 req.response.next_chunk_length -= can_read;
949941
...@@ -990,9 +982,41 @@ pub const Request = struct {...@@ -990,9 +982,41 @@ pub const Request = struct {
990 }982 }
991983
992 pub fn read(req: *Request, buffer: []u8) ReadError!usize {984 pub fn read(req: *Request, buffer: []u8) ReadError!usize {
993 if (!req.response.state.isContent()) try req.waitForCompleteHead();985 while (true) {
986 if (!req.response.state.isContent()) try req.waitForCompleteHead();
987
988 if (req.handle_redirects and req.response.headers.status.class() == .redirect) {
989 assert(try req.readRaw(buffer) == 0);
990
991 if (req.redirects_left == 0) return error.TooManyHttpRedirects;
992
993 const location = req.response.headers.location orelse
994 return error.HttpRedirectMissingLocation;
995 const new_url = Uri.parse(location) catch try Uri.parseWithoutScheme(location);
996
997 var new_arena = std.heap.ArenaAllocator.init(req.client.allocator);
998 const resolved_url = try req.uri.resolve(new_url, false, new_arena.allocator());
999 errdefer new_arena.deinit();
1000
1001 req.arena.deinit();
1002 req.arena = new_arena;
1003
1004 const new_req = try req.client.request(resolved_url, req.headers, .{
1005 .max_redirects = req.redirects_left - 1,
1006 .header_strategy = if (req.response.header_bytes_owned) .{
1007 .dynamic = req.response.max_header_bytes,
1008 } else .{
1009 .static = req.response.header_bytes.unusedCapacitySlice(),
1010 },
1011 });
1012 req.deinit();
1013 req.* = new_req;
1014 } else {
1015 break;
1016 }
1017 }
9941018
995 if (req.response.compression == .none and req.response.state.isContent()) {1019 if (req.response.compression == .none) {
996 if (req.response.headers.transfer_compression) |compression| {1020 if (req.response.headers.transfer_compression) |compression| {
997 switch (compression) {1021 switch (compression) {
998 .compress => unreachable,1022 .compress => unreachable,
...@@ -1084,6 +1108,8 @@ pub const Request = struct {...@@ -1084,6 +1108,8 @@ pub const Request = struct {
1084};1108};
10851109
1086pub fn deinit(client: *Client) void {1110pub fn deinit(client: *Client) void {
1111 client.connection_mutex.lock();
1112
1087 var next = client.connection_pool.first;1113 var next = client.connection_pool.first;
1088 while (next) |node| {1114 while (next) |node| {
1089 next = node.next;1115 next = node.next;
...@@ -1106,7 +1132,7 @@ pub fn deinit(client: *Client) void {...@@ -1106,7 +1132,7 @@ pub fn deinit(client: *Client) void {
1106 client.* = undefined;1132 client.* = undefined;
1107}1133}
11081134
1109pub const ConnectError = std.mem.Allocator.Error || std.net.TcpConnectToHostError || std.crypto.tls.Client.InitError(std.net.Stream);1135pub const ConnectError = std.mem.Allocator.Error || net.TcpConnectToHostError || std.crypto.tls.Client.InitError(net.Stream);
11101136
1111pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectError!*ConnectionNode {1137pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectError!*ConnectionNode {
1112 { // Search through the connection pool for a potential connection.1138 { // Search through the connection pool for a potential connection.
...@@ -1120,7 +1146,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio...@@ -1120,7 +1146,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio
1120 const same_protocol = node.data.protocol == protocol;1146 const same_protocol = node.data.protocol == protocol;
11211147
1122 if (same_host and same_port and same_protocol) {1148 if (same_host and same_port and same_protocol) {
1123 client.acquire(node);1149 client.acquire(node, true);
1124 return node;1150 return node;
1125 }1151 }
11261152
...@@ -1168,6 +1194,7 @@ pub const RequestError = ConnectError || Connection.WriteError || error{...@@ -1168,6 +1194,7 @@ pub const RequestError = ConnectError || Connection.WriteError || error{
1168 InvalidPadding,1194 InvalidPadding,
1169 MissingEndCertificateMarker,1195 MissingEndCertificateMarker,
1170 Unseekable,1196 Unseekable,
1197 EndOfStream,
1171};1198};
11721199
1173pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Request.Options) RequestError!Request {1200pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Request.Options) RequestError!Request {
...@@ -1196,27 +1223,52 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req...@@ -1196,27 +1223,52 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req
1196 }1223 }
11971224
1198 var req: Request = .{1225 var req: Request = .{
1226 .uri = uri,
1199 .client = client,1227 .client = client,
1200 .headers = headers,1228 .headers = headers,
1201 .connection = try client.connect(host, port, protocol),1229 .connection = try client.connect(host, port, protocol),
1202 .redirects_left = options.max_redirects,1230 .redirects_left = options.max_redirects,
1231 .handle_redirects = options.handle_redirects,
1232 .compression_init = false,
1203 .response = switch (options.header_strategy) {1233 .response = switch (options.header_strategy) {
1204 .dynamic => |max| Request.Response.initDynamic(max),1234 .dynamic => |max| Request.Response.initDynamic(max),
1205 .static => |buf| Request.Response.initStatic(buf),1235 .static => |buf| Request.Response.initStatic(buf),
1206 },1236 },
1237 .arena = undefined,
1207 };1238 };
12081239
1240 req.arena = std.heap.ArenaAllocator.init(client.allocator);
1241
1209 {1242 {
1210 var buffered = std.io.bufferedWriter(req.connection.data.writer());1243 var buffered = std.io.bufferedWriter(req.connection.data.writer());
1211 const writer = buffered.writer();1244 const writer = buffered.writer();
12121245
1246 const escaped_path = try Uri.escapePath(client.allocator, uri.path);
1247 defer client.allocator.free(escaped_path);
1248
1249 const escaped_query = if (uri.query) |q| try Uri.escapeQuery(client.allocator, q) else null;
1250 defer if (escaped_query) |q| client.allocator.free(q);
1251
1252 const escaped_fragment = if (uri.fragment) |f| try Uri.escapeQuery(client.allocator, f) else null;
1253 defer if (escaped_fragment) |f| client.allocator.free(f);
1254
1213 try writer.writeAll(@tagName(headers.method));1255 try writer.writeAll(@tagName(headers.method));
1214 try writer.writeByte(' ');1256 try writer.writeByte(' ');
1215 try writer.writeAll(uri.path);1257 try writer.writeAll(escaped_path);
1258 if (escaped_query) |q| {
1259 try writer.writeByte('?');
1260 try writer.writeAll(q);
1261 }
1262 if (escaped_fragment) |f| {
1263 try writer.writeByte('#');
1264 try writer.writeAll(f);
1265 }
1216 try writer.writeByte(' ');1266 try writer.writeByte(' ');
1217 try writer.writeAll(@tagName(headers.version));1267 try writer.writeAll(@tagName(headers.version));
1218 try writer.writeAll("\r\nHost: ");1268 try writer.writeAll("\r\nHost: ");
1219 try writer.writeAll(host);1269 try writer.writeAll(host);
1270 try writer.writeAll("\r\nUser-Agent: ");
1271 try writer.writeAll(headers.user_agent);
1220 if (headers.connection == .close) {1272 if (headers.connection == .close) {
1221 try writer.writeAll("\r\nConnection: close");1273 try writer.writeAll("\r\nConnection: close");
1222 } else {1274 } else {
lib/std/net.zig+3-3
...@@ -741,9 +741,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {...@@ -741,9 +741,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
741 return Stream{ .handle = sockfd };741 return Stream{ .handle = sockfd };
742}742}
743743
744const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.os.SocketError || std.os.BindError || error {744const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.os.SocketError || std.os.BindError || error{
745 // TODO: break this up into error sets from the various underlying functions745 // TODO: break this up into error sets from the various underlying functions
746 746
747 TemporaryNameServerFailure,747 TemporaryNameServerFailure,
748 NameServerFailure,748 NameServerFailure,
749 AddressFamilyNotSupported,749 AddressFamilyNotSupported,
...@@ -760,7 +760,7 @@ const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError ||...@@ -760,7 +760,7 @@ const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError ||
760 Incomplete,760 Incomplete,
761 InvalidIpv4Mapping,761 InvalidIpv4Mapping,
762 InvalidIPAddressFormat,762 InvalidIPAddressFormat,
763 763
764 InterfaceNotFound,764 InterfaceNotFound,
765 FileSystem,765 FileSystem,
766};766};