authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-08-27 16:36:24-05:00
committergravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-08-30 13:05:45-05:00
log4689d93cb204a4143770105200eb65dcdca5d7a0
treed02590b41be334c650d01dfb1c6436bc1cb064b6
parentddef683fcb321882cc912090a25c12bc8705ff55
signaturelock-open Commit is signed but in an unrecognized format.

std.http: allow for arbitrary http methods


4 files changed, 49 insertions(+), 22 deletions(-)

lib/std/http.zig+37-12
...@@ -1,3 +1,5 @@...@@ -1,3 +1,5 @@
1const std = @import("std.zig");
2
1pub const Client = @import("http/Client.zig");3pub const Client = @import("http/Client.zig");
2pub const Server = @import("http/Server.zig");4pub const Server = @import("http/Server.zig");
3pub const protocol = @import("http/protocol.zig");5pub const protocol = @import("http/protocol.zig");
...@@ -14,16 +16,36 @@ pub const Version = enum {...@@ -14,16 +16,36 @@ pub const Version = enum {
14/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods16/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
15/// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definition17/// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definition
16/// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH18/// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH
17pub const Method = enum {19pub const Method = enum(u64) { // TODO: should be u192 or u256, but neither is supported by the C backend, and therefore cannot pass CI
18 GET,20 GET = parse("GET"),
19 HEAD,21 HEAD = parse("HEAD"),
20 POST,22 POST = parse("POST"),
21 PUT,23 PUT = parse("PUT"),
22 DELETE,24 DELETE = parse("DELETE"),
23 CONNECT,25 CONNECT = parse("CONNECT"),
24 OPTIONS,26 OPTIONS = parse("OPTIONS"),
25 TRACE,27 TRACE = parse("TRACE"),
26 PATCH,28 PATCH = parse("PATCH"),
29
30 _,
31
32 /// Converts `s` into a type that may be used as a `Method` field.
33 /// Asserts that `s` is 24 or fewer bytes.
34 pub fn parse(s: []const u8) u64 {
35 var x: u64 = 0;
36 @memcpy(std.mem.asBytes(&x)[0..s.len], s);
37 return x;
38 }
39
40 pub fn write(self: Method, w: anytype) !void {
41 const bytes = std.mem.asBytes(&@intFromEnum(self));
42 const str = std.mem.sliceTo(bytes, 0);
43 try w.writeAll(str);
44 }
45
46 pub fn format(value: Method, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void {
47 return try value.write(writer);
48 }
2749
28 /// Returns true if a request of this method is allowed to have a body50 /// Returns true if a request of this method is allowed to have a body
29 /// Actual behavior from servers may vary and should still be checked51 /// Actual behavior from servers may vary and should still be checked
...@@ -31,6 +53,7 @@ pub const Method = enum {...@@ -31,6 +53,7 @@ pub const Method = enum {
31 return switch (self) {53 return switch (self) {
32 .POST, .PUT, .PATCH => true,54 .POST, .PUT, .PATCH => true,
33 .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false,55 .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false,
56 else => true,
34 };57 };
35 }58 }
3659
...@@ -40,6 +63,7 @@ pub const Method = enum {...@@ -40,6 +63,7 @@ pub const Method = enum {
40 return switch (self) {63 return switch (self) {
41 .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true,64 .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true,
42 .HEAD, .PUT, .TRACE => false,65 .HEAD, .PUT, .TRACE => false,
66 else => true,
43 };67 };
44 }68 }
4569
...@@ -50,6 +74,7 @@ pub const Method = enum {...@@ -50,6 +74,7 @@ pub const Method = enum {
50 return switch (self) {74 return switch (self) {
51 .GET, .HEAD, .OPTIONS, .TRACE => true,75 .GET, .HEAD, .OPTIONS, .TRACE => true,
52 .POST, .PUT, .DELETE, .CONNECT, .PATCH => false,76 .POST, .PUT, .DELETE, .CONNECT, .PATCH => false,
77 else => false,
53 };78 };
54 }79 }
5580
...@@ -60,6 +85,7 @@ pub const Method = enum {...@@ -60,6 +85,7 @@ pub const Method = enum {
60 return switch (self) {85 return switch (self) {
61 .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true,86 .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true,
62 .CONNECT, .POST, .PATCH => false,87 .CONNECT, .POST, .PATCH => false,
88 else => false,
63 };89 };
64 }90 }
6591
...@@ -70,6 +96,7 @@ pub const Method = enum {...@@ -70,6 +96,7 @@ pub const Method = enum {
70 return switch (self) {96 return switch (self) {
71 .GET, .HEAD => true,97 .GET, .HEAD => true,
72 .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false,98 .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false,
99 else => false,
73 };100 };
74 }101 }
75};102};
...@@ -269,8 +296,6 @@ pub const Connection = enum {...@@ -269,8 +296,6 @@ pub const Connection = enum {
269 close,296 close,
270};297};
271298
272const std = @import("std.zig");
273
274test {299test {
275 _ = Client;300 _ = Client;
276 _ = Method;301 _ = Method;
lib/std/http/Client.zig+5-5
...@@ -545,7 +545,7 @@ pub const Request = struct {...@@ -545,7 +545,7 @@ pub const Request = struct {
545 var buffered = std.io.bufferedWriter(req.connection.?.data.writer());545 var buffered = std.io.bufferedWriter(req.connection.?.data.writer());
546 const w = buffered.writer();546 const w = buffered.writer();
547547
548 try w.writeAll(@tagName(req.method));548 try req.method.write(w);
549 try w.writeByte(' ');549 try w.writeByte(' ');
550550
551 if (req.method == .CONNECT) {551 if (req.method == .CONNECT) {
...@@ -627,15 +627,15 @@ pub const Request = struct {...@@ -627,15 +627,15 @@ pub const Request = struct {
627 try buffered.flush();627 try buffered.flush();
628 }628 }
629629
630 pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;630 const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
631631
632 pub const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead);632 const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead);
633633
634 pub fn transferReader(req: *Request) TransferReader {634 fn transferReader(req: *Request) TransferReader {
635 return .{ .context = req };635 return .{ .context = req };
636 }636 }
637637
638 pub fn transferRead(req: *Request, buf: []u8) TransferReadError!usize {638 fn transferRead(req: *Request, buf: []u8) TransferReadError!usize {
639 if (req.response.parser.done) return 0;639 if (req.response.parser.done) return 0;
640640
641 var index: usize = 0;641 var index: usize = 0;
lib/std/http/Server.zig+6-4
...@@ -185,8 +185,10 @@ pub const Request = struct {...@@ -185,8 +185,10 @@ pub const Request = struct {
185 return error.HttpHeadersInvalid;185 return error.HttpHeadersInvalid;
186186
187 const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse return error.HttpHeadersInvalid;187 const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse return error.HttpHeadersInvalid;
188 if (method_end > 24) return error.HttpHeadersInvalid;
189
188 const method_str = first_line[0..method_end];190 const method_str = first_line[0..method_end];
189 const method = std.meta.stringToEnum(http.Method, method_str) orelse return error.UnknownHttpMethod;191 const method: http.Method = @enumFromInt(http.Method.parse(method_str));
190192
191 const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse return error.HttpHeadersInvalid;193 const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse return error.HttpHeadersInvalid;
192 if (version_start == method_end) return error.HttpHeadersInvalid;194 if (version_start == method_end) return error.HttpHeadersInvalid;
...@@ -467,11 +469,11 @@ pub const Response = struct {...@@ -467,11 +469,11 @@ pub const Response = struct {
467 try buffered.flush();469 try buffered.flush();
468 }470 }
469471
470 pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;472 const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
471473
472 pub const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead);474 const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead);
473475
474 pub fn transferReader(res: *Response) TransferReader {476 fn transferReader(res: *Response) TransferReader {
475 return .{ .context = res };477 return .{ .context = res };
476 }478 }
477479
test/standalone/http.zig+1-1
...@@ -20,7 +20,7 @@ var server: Server = undefined;...@@ -20,7 +20,7 @@ var server: Server = undefined;
20fn handleRequest(res: *Server.Response) !void {20fn handleRequest(res: *Server.Response) !void {
21 const log = std.log.scoped(.server);21 const log = std.log.scoped(.server);
2222
23 log.info("{s} {s} {s}", .{ @tagName(res.request.method), @tagName(res.request.version), res.request.target });23 log.info("{} {s} {s}", .{ res.request.method, @tagName(res.request.version), res.request.target });
2424
25 if (res.request.headers.contains("expect")) {25 if (res.request.headers.contains("expect")) {
26 if (mem.eql(u8, res.request.headers.getFirstValue("expect").?, "100-continue")) {26 if (mem.eql(u8, res.request.headers.getFirstValue("expect").?, "100-continue")) {