| author | |
| committer | |
| log | 10beb19ce7804f9669f25a6b5d80e7eef3fdc14f |
| tree | 17f59b46250bc7e2689d8fb1ce78fb990fb51176 |
| parent | d051b1396358d1b9229aefeeb2a61f7f46a4e5c3 |
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. | ||
| 1508 | pub fn open( | 1510 | pub 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; |
| 1515 | 1529 | ||
| 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 | } | ||
| 307 | 315 | ||
| 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 | } |