From fcca3cd1a3f5430dd9d813d80a0f0512c99e371f Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Thu, 28 Sep 2023 11:16:39 +0000 Subject: [PATCH] std.http: introduce options to http client to allow for raw uris Addresses #17015 by introducing a new startWithOptions. The only option is currently is a flag to use the provided URI as is, without modification when passed to the server. Normally, this is not needed nor desired. However, some REST APIs may have requirements that cannot be satisfied with the default handling. --- lib/std/Uri.zig | 19 ++++++++++++++++--- lib/std/http/Client.zig | 31 +++++++++++++++++++++++-------- src/Package.zig | 2 +- test/standalone/http.zig | 32 ++++++++++++++++---------------- 4 files changed, 56 insertions(+), 28 deletions(-) diff --git a/lib/std/Uri.zig b/lib/std/Uri.zig index 1a2993f174..b27a3d7012 100644 --- a/lib/std/Uri.zig +++ b/lib/std/Uri.zig @@ -216,6 +216,7 @@ pub fn format( const needs_absolute = comptime std.mem.indexOf(u8, fmt, "+") != null; const needs_path = comptime std.mem.indexOf(u8, fmt, "/") != null or fmt.len == 0; + const raw_uri = comptime std.mem.indexOf(u8, fmt, "r") != null; const needs_fragment = comptime std.mem.indexOf(u8, fmt, "#") != null; if (needs_absolute) { @@ -246,18 +247,30 @@ pub fn format( if (uri.path.len == 0) { try writer.writeAll("/"); } else { - try Uri.writeEscapedPath(writer, uri.path); + if (raw_uri) { + try writer.writeAll(uri.path); + } else { + try Uri.writeEscapedPath(writer, uri.path); + } } if (uri.query) |q| { try writer.writeAll("?"); - try Uri.writeEscapedQuery(writer, q); + if (raw_uri) { + try writer.writeAll(q); + } else { + try Uri.writeEscapedQuery(writer, q); + } } if (needs_fragment) { if (uri.fragment) |f| { try writer.writeAll("#"); - try Uri.writeEscapedQuery(writer, f); + if (raw_uri) { + try writer.writeAll(f); + } else { + try Uri.writeEscapedQuery(writer, f); + } } } } diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index bfb96f2cfc..9e475df51b 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -538,8 +538,13 @@ pub const Request = struct { pub const StartError = Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding }; + pub const StartOptions = struct { + /// Specifies that the uri should be used as is + raw_uri: bool = false, + }; + /// Send the request to the server. - pub fn start(req: *Request) StartError!void { + pub fn start(req: *Request, options: StartOptions) StartError!void { if (!req.method.requestHasBody() and req.transfer_encoding != .none) return error.UnsupportedTransferEncoding; var buffered = std.io.bufferedWriter(req.connection.?.data.writer()); @@ -552,13 +557,22 @@ pub const Request = struct { try w.writeAll(req.uri.host.?); try w.writeByte(':'); try w.print("{}", .{req.uri.port.?}); - } else if (req.connection.?.data.proxied) { - // proxied connections require the full uri - try w.print("{+/}", .{req.uri}); } else { - try w.print("{/}", .{req.uri}); + if (req.connection.?.data.proxied) { + // proxied connections require the full uri + if (options.raw_uri) { + try w.print("{+/r}", .{req.uri}); + } else { + try w.print("{+/}", .{req.uri}); + } + } else { + if (options.raw_uri) { + try w.print("{/r}", .{req.uri}); + } else { + try w.print("{/}", .{req.uri}); + } + } } - try w.writeByte(' '); try w.writeAll(@tagName(req.version)); try w.writeAll("\r\n"); @@ -757,7 +771,7 @@ pub const Request = struct { try req.redirect(resolved_url); - try req.start(); + try req.start(.{}); } else { req.response.skip = false; if (!req.response.parser.done) { @@ -1141,6 +1155,7 @@ pub const FetchOptions = struct { method: http.Method = .GET, headers: http.Headers = http.Headers{ .allocator = std.heap.page_allocator, .owned = false }, payload: Payload = .none, + raw_uri: bool = false, }; pub const FetchResult = struct { @@ -1188,7 +1203,7 @@ pub fn fetch(client: *Client, allocator: Allocator, options: FetchOptions) !Fetc .none => {}, } - try req.start(); + try req.start(.{ .raw_uri = options.raw_uri }); switch (options.payload) { .string => |str| try req.writeAll(str), diff --git a/src/Package.zig b/src/Package.zig index 03c4c72ab0..d170baeae5 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -653,7 +653,7 @@ fn fetchAndUnpack( var req = try http_client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); if (req.response.status != .ok) { diff --git a/test/standalone/http.zig b/test/standalone/http.zig index f0dc0e6b2a..00fd4397b0 100644 --- a/test/standalone/http.zig +++ b/test/standalone/http.zig @@ -240,7 +240,7 @@ pub fn main() !void { var req = try client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); const body = try req.reader().readAllAlloc(calloc, 8192); @@ -265,7 +265,7 @@ pub fn main() !void { var req = try client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); const body = try req.reader().readAllAlloc(calloc, 8192 * 1024); @@ -289,7 +289,7 @@ pub fn main() !void { var req = try client.request(.HEAD, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); const body = try req.reader().readAllAlloc(calloc, 8192); @@ -315,7 +315,7 @@ pub fn main() !void { var req = try client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); const body = try req.reader().readAllAlloc(calloc, 8192); @@ -340,7 +340,7 @@ pub fn main() !void { var req = try client.request(.HEAD, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); const body = try req.reader().readAllAlloc(calloc, 8192); @@ -366,7 +366,7 @@ pub fn main() !void { var req = try client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); const body = try req.reader().readAllAlloc(calloc, 8192); @@ -395,7 +395,7 @@ pub fn main() !void { req.transfer_encoding = .{ .content_length = 14 }; - try req.start(); + try req.start(.{}); try req.writeAll("Hello, "); try req.writeAll("World!\n"); try req.finish(); @@ -425,7 +425,7 @@ pub fn main() !void { var req = try client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); const body = try req.reader().readAllAlloc(calloc, 8192); @@ -454,7 +454,7 @@ pub fn main() !void { req.transfer_encoding = .chunked; - try req.start(); + try req.start(.{}); try req.writeAll("Hello, "); try req.writeAll("World!\n"); try req.finish(); @@ -482,7 +482,7 @@ pub fn main() !void { var req = try client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); const body = try req.reader().readAllAlloc(calloc, 8192); @@ -506,7 +506,7 @@ pub fn main() !void { var req = try client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); const body = try req.reader().readAllAlloc(calloc, 8192); @@ -530,7 +530,7 @@ pub fn main() !void { var req = try client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); try req.wait(); const body = try req.reader().readAllAlloc(calloc, 8192); @@ -554,7 +554,7 @@ pub fn main() !void { var req = try client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); req.wait() catch |err| switch (err) { error.TooManyHttpRedirects => {}, else => return err, @@ -576,7 +576,7 @@ pub fn main() !void { var req = try client.request(.GET, uri, h, .{}); defer req.deinit(); - try req.start(); + try req.start(.{}); const result = req.wait(); try testing.expectError(error.ConnectionRefused, result); // expects not segfault but the regular error @@ -623,7 +623,7 @@ pub fn main() !void { req.transfer_encoding = .chunked; - try req.start(); + try req.start(.{}); try req.wait(); try testing.expectEqual(http.Status.@"continue", req.response.status); @@ -657,7 +657,7 @@ pub fn main() !void { req.transfer_encoding = .chunked; - try req.start(); + try req.start(.{}); try req.wait(); try testing.expectEqual(http.Status.expectation_failed, req.response.status); }