authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-22 18:52:00-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:37:11-07:00
log10beb19ce7804f9669f25a6b5d80e7eef3fdc14f
tree17f59b46250bc7e2689d8fb1ce78fb990fb51176
parentd051b1396358d1b9229aefeeb2a61f7f46a4e5c3

std.http: assert against \r\n in headers

The HTTP specification does not provide a way to escape \r\n in headers, so it's the API user's responsibility to ensure the header names and values do not contain \r\n. Also header names must not contain ':'. It's an assertion, not an error, because the calling code very likely is using hard-coded values or server-provided values that do not need to be checked, and the error would be unreachable anyway. Untrusted user input must not be put directly into into HTTP headers.

2 files changed, 23 insertions(+), 1 deletions(-)

lib/std/http/Client.zig+14
...@@ -1505,12 +1505,26 @@ pub const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{...@@ -1505,12 +1505,26 @@ pub const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{
1505///1505///
1506/// The caller is responsible for calling `deinit()` on the `Request`.1506/// The caller is responsible for calling `deinit()` on the `Request`.
1507/// This function is threadsafe.1507/// This function is threadsafe.
1508///
1509/// Asserts that "\r\n" does not occur in any header name or value.
1508pub fn open(1510pub fn open(
1509 client: *Client,1511 client: *Client,
1510 method: http.Method,1512 method: http.Method,
1511 uri: Uri,1513 uri: Uri,
1512 options: RequestOptions,1514 options: RequestOptions,
1513) RequestError!Request {1515) RequestError!Request {
1516 if (std.debug.runtime_safety) {
1517 for (options.extra_headers) |header| {
1518 assert(std.mem.indexOfScalar(u8, header.name, ':') == null);
1519 assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null);
1520 assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null);
1521 }
1522 for (options.privileged_headers) |header| {
1523 assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null);
1524 assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null);
1525 }
1526 }
1527
1514 const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUrlScheme;1528 const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUrlScheme;
15151529
1516 const port: u16 = uri.port orelse switch (protocol) {1530 const port: u16 = uri.port orelse switch (protocol) {
lib/std/http/Server.zig+9-1
...@@ -296,6 +296,7 @@ pub const Request = struct {...@@ -296,6 +296,7 @@ pub const Request = struct {
296 ///296 ///
297 /// Asserts status is not `continue`.297 /// Asserts status is not `continue`.
298 /// Asserts there are at most 25 extra_headers.298 /// Asserts there are at most 25 extra_headers.
299 /// Asserts that "\r\n" does not occur in any header name or value.
299 pub fn respond(300 pub fn respond(
300 request: *Request,301 request: *Request,
301 content: []const u8,302 content: []const u8,
...@@ -304,6 +305,13 @@ pub const Request = struct {...@@ -304,6 +305,13 @@ pub const Request = struct {
304 const max_extra_headers = 25;305 const max_extra_headers = 25;
305 assert(options.status != .@"continue");306 assert(options.status != .@"continue");
306 assert(options.extra_headers.len <= max_extra_headers);307 assert(options.extra_headers.len <= max_extra_headers);
308 if (std.debug.runtime_safety) {
309 for (options.extra_headers) |header| {
310 assert(std.mem.indexOfScalar(u8, header.name, ':') == null);
311 assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null);
312 assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null);
313 }
314 }
307315
308 const transfer_encoding_none = (options.transfer_encoding orelse .chunked) == .none;316 const transfer_encoding_none = (options.transfer_encoding orelse .chunked) == .none;
309 const server_keep_alive = !transfer_encoding_none and options.keep_alive;317 const server_keep_alive = !transfer_encoding_none and options.keep_alive;
...@@ -765,7 +773,7 @@ pub const Response = struct {...@@ -765,7 +773,7 @@ pub const Response = struct {
765 /// Respects the value of `elide_body` to omit all data after the headers.773 /// Respects the value of `elide_body` to omit all data after the headers.
766 /// Asserts there are at most 25 trailers.774 /// Asserts there are at most 25 trailers.
767 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {775 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {
768 assert(r.content_length == null);776 assert(r.transfer_encoding == .chunked);
769 try flush_chunked(r, options.trailers);777 try flush_chunked(r, options.trailers);
770 r.* = undefined;778 r.* = undefined;
771 }779 }