zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 10beb19ce7804f9669f25a6b5d80e7eef3fdc14f (tree)
parent d051b1396358d1b9229aefeeb2a61f7f46a4e5c3
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Thu, 22 Feb 2024 18:52:00 -0700

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.

Diffstat:
Mlib/std/http/Client.zig | 14++++++++++++++
Mlib/std/http/Server.zig | 10+++++++++-
2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig @@ -1505,12 +1505,26 @@ pub const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{ /// /// The caller is responsible for calling `deinit()` on the `Request`. /// This function is threadsafe. +/// +/// Asserts that "\r\n" does not occur in any header name or value. pub fn open( client: *Client, method: http.Method, uri: Uri, options: RequestOptions, ) RequestError!Request { + if (std.debug.runtime_safety) { + for (options.extra_headers) |header| { + assert(std.mem.indexOfScalar(u8, header.name, ':') == null); + assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); + assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); + } + for (options.privileged_headers) |header| { + assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); + assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); + } + } + const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUrlScheme; const port: u16 = uri.port orelse switch (protocol) { diff --git a/lib/std/http/Server.zig b/lib/std/http/Server.zig @@ -296,6 +296,7 @@ pub const Request = struct { /// /// Asserts status is not `continue`. /// Asserts there are at most 25 extra_headers. + /// Asserts that "\r\n" does not occur in any header name or value. pub fn respond( request: *Request, content: []const u8, @@ -304,6 +305,13 @@ pub const Request = struct { const max_extra_headers = 25; assert(options.status != .@"continue"); assert(options.extra_headers.len <= max_extra_headers); + if (std.debug.runtime_safety) { + for (options.extra_headers) |header| { + assert(std.mem.indexOfScalar(u8, header.name, ':') == null); + assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); + assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); + } + } const transfer_encoding_none = (options.transfer_encoding orelse .chunked) == .none; const server_keep_alive = !transfer_encoding_none and options.keep_alive; @@ -765,7 +773,7 @@ pub const Response = struct { /// Respects the value of `elide_body` to omit all data after the headers. /// Asserts there are at most 25 trailers. pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void { - assert(r.content_length == null); + assert(r.transfer_encoding == .chunked); try flush_chunked(r, options.trailers); r.* = undefined; }