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 1a2993f17425c048cad8bfab1241e271e8f9cb14..b27a3d70126d0982fdbd5b694cad7faeda69af81 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 bfb96f2cfcbb56f65d4b794b6c4824fb974cf75c..9e475df51ba3be997cd811f25bffc241a040eb19 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 03c4c72ab0b38461f9505cedcb4e54c5daf70078..d170baeae509d59111ca8e1d4e8510253c7baa53 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 f0dc0e6b2a546afc4814311318c3d4b42fdb6686..00fd4397b09d2099f8848fc6b274681824917673 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); } -- 2.54.0