Example
test parse {
const response_bytes = "HTTP/1.1 200 OK\r\n" ++
"LOcation:url\r\n" ++
"content-tYpe: text/plain\r\n" ++
"content-disposition:attachment; filename=example.txt \r\n" ++
"content-Length:10\r\n" ++
"TRansfer-encoding:\tdeflate, chunked \r\n" ++
"connectioN:\t keep-alive \r\n\r\n";
var header_buffer: [1024]u8 = undefined;
var res = Response{
.status = undefined,
.reason = undefined,
.version = undefined,
.keep_alive = false,
.parser = .init(&header_buffer),
};
@memcpy(header_buffer[0..response_bytes.len], response_bytes);
res.parser.header_bytes_len = response_bytes.len;
try res.parse(response_bytes);
try testing.expectEqual(.@"HTTP/1.1", res.version);
try testing.expectEqualStrings("OK", res.reason);
try testing.expectEqual(.ok, res.status);
try testing.expectEqualStrings("url", res.location.?);
try testing.expectEqualStrings("text/plain", res.content_type.?);
try testing.expectEqualStrings("attachment; filename=example.txt", res.content_disposition.?);
try testing.expectEqual(true, res.keep_alive);
try testing.expectEqual(10, res.content_length.?);
try testing.expectEqual(.chunked, res.transfer_encoding);
try testing.expectEqual(.deflate, res.transfer_compression);
}
Source
pub fn parse(res: *Response, bytes: []const u8) ParseError!void {
var it = mem.splitSequence(u8, bytes, "\r\n");
const first_line = it.next().?;
if (first_line.len < 12) {
return error.HttpHeadersInvalid;
}
const version: http.Version = switch (int64(first_line[0..8])) {
int64("HTTP/1.0") => .@"HTTP/1.0",
int64("HTTP/1.1") => .@"HTTP/1.1",
else => return error.HttpHeadersInvalid,
};
if (first_line[8] != ' ') return error.HttpHeadersInvalid;
const status: http.Status = @enumFromInt(parseInt3(first_line[9..12]));
const reason = mem.trimLeft(u8, first_line[12..], " ");
res.version = version;
res.status = status;
res.reason = reason;
res.keep_alive = switch (version) {
.@"HTTP/1.0" => false,
.@"HTTP/1.1" => true,
};
while (it.next()) |line| {
if (line.len == 0) return;
switch (line[0]) {
' ', '\t' => return error.HttpHeaderContinuationsUnsupported,
else => {},
}
var line_it = mem.splitScalar(u8, line, ':');
const header_name = line_it.next().?;
const header_value = mem.trim(u8, line_it.rest(), " \t");
if (header_name.len == 0) return error.HttpHeadersInvalid;
if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
res.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
} else if (std.ascii.eqlIgnoreCase(header_name, "content-type")) {
res.content_type = header_value;
} else if (std.ascii.eqlIgnoreCase(header_name, "location")) {
res.location = header_value;
} else if (std.ascii.eqlIgnoreCase(header_name, "content-disposition")) {
res.content_disposition = header_value;
} else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {
// Transfer-Encoding: second, first
// Transfer-Encoding: deflate, chunked
var iter = mem.splitBackwardsScalar(u8, header_value, ',');
const first = iter.first();
const trimmed_first = mem.trim(u8, first, " ");
var next: ?[]const u8 = first;
if (std.meta.stringToEnum(http.TransferEncoding, trimmed_first)) |transfer| {
if (res.transfer_encoding != .none) return error.HttpHeadersInvalid; // we already have a transfer encoding
res.transfer_encoding = transfer;
next = iter.next();
}
if (next) |second| {
const trimmed_second = mem.trim(u8, second, " ");
if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| {
if (res.transfer_compression != .identity) return error.HttpHeadersInvalid; // double compression is not supported
res.transfer_compression = transfer;
} else {
return error.HttpTransferEncodingUnsupported;
}
}
if (iter.next()) |_| return error.HttpTransferEncodingUnsupported;
} else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
const content_length = std.fmt.parseInt(u64, header_value, 10) catch return error.InvalidContentLength;
if (res.content_length != null and res.content_length != content_length) return error.HttpHeadersInvalid;
res.content_length = content_length;
} else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
if (res.transfer_compression != .identity) return error.HttpHeadersInvalid;
const trimmed = mem.trim(u8, header_value, " ");
if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
res.transfer_compression = ce;
} else {
return error.HttpTransferEncodingUnsupported;
}
}
}
return error.HttpHeadersInvalid; // missing empty line
}