mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
Merge pull request #19094 from truemedian/std-http-fields
std.http: fix http field parsing
This commit is contained in:
commit
671c2acf47
@ -489,9 +489,9 @@ pub const Response = struct {
|
|||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
var line_it = mem.splitSequence(u8, line, ": ");
|
var line_it = mem.splitScalar(u8, line, ':');
|
||||||
const header_name = line_it.next().?;
|
const header_name = line_it.next().?;
|
||||||
const header_value = line_it.rest();
|
const header_value = mem.trim(u8, line_it.rest(), " \t");
|
||||||
if (header_name.len == 0) return error.HttpHeadersInvalid;
|
if (header_name.len == 0) return error.HttpHeadersInvalid;
|
||||||
|
|
||||||
if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
|
if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
|
||||||
@ -551,6 +551,43 @@ pub const Response = struct {
|
|||||||
return error.HttpHeadersInvalid; // missing empty line
|
return error.HttpHeadersInvalid; // missing empty line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = proto.HeadersParser.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);
|
||||||
|
}
|
||||||
|
|
||||||
inline fn int64(array: *const [8]u8) u64 {
|
inline fn int64(array: *const [8]u8) u64 {
|
||||||
return @bitCast(array.*);
|
return @bitCast(array.*);
|
||||||
}
|
}
|
||||||
@ -575,6 +612,67 @@ pub const Response = struct {
|
|||||||
pub fn iterateHeaders(r: Response) http.HeaderIterator {
|
pub fn iterateHeaders(r: Response) http.HeaderIterator {
|
||||||
return http.HeaderIterator.init(r.parser.get());
|
return http.HeaderIterator.init(r.parser.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test iterateHeaders {
|
||||||
|
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 = proto.HeadersParser.init(&header_buffer),
|
||||||
|
};
|
||||||
|
|
||||||
|
@memcpy(header_buffer[0..response_bytes.len], response_bytes);
|
||||||
|
res.parser.header_bytes_len = response_bytes.len;
|
||||||
|
|
||||||
|
var it = res.iterateHeaders();
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("LOcation", header.name);
|
||||||
|
try testing.expectEqualStrings("url", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("content-tYpe", header.name);
|
||||||
|
try testing.expectEqualStrings("text/plain", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("content-disposition", header.name);
|
||||||
|
try testing.expectEqualStrings("attachment; filename=example.txt", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("content-Length", header.name);
|
||||||
|
try testing.expectEqualStrings("10", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("TRansfer-encoding", header.name);
|
||||||
|
try testing.expectEqualStrings("deflate, chunked", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("connectioN", header.name);
|
||||||
|
try testing.expectEqualStrings("keep-alive", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
try testing.expectEqual(null, it.next());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A HTTP request that has been sent.
|
/// A HTTP request that has been sent.
|
||||||
|
|||||||
@ -12,30 +12,43 @@ pub fn init(bytes: []const u8) HeaderIterator {
|
|||||||
|
|
||||||
pub fn next(it: *HeaderIterator) ?std.http.Header {
|
pub fn next(it: *HeaderIterator) ?std.http.Header {
|
||||||
const end = std.mem.indexOfPosLinear(u8, it.bytes, it.index, "\r\n").?;
|
const end = std.mem.indexOfPosLinear(u8, it.bytes, it.index, "\r\n").?;
|
||||||
var kv_it = std.mem.splitSequence(u8, it.bytes[it.index..end], ": ");
|
if (it.index == end) { // found the trailer boundary (\r\n\r\n)
|
||||||
const name = kv_it.next().?;
|
|
||||||
const value = kv_it.rest();
|
|
||||||
if (name.len == 0 and value.len == 0) {
|
|
||||||
if (it.is_trailer) return null;
|
if (it.is_trailer) return null;
|
||||||
|
|
||||||
const next_end = std.mem.indexOfPosLinear(u8, it.bytes, end + 2, "\r\n") orelse
|
const next_end = std.mem.indexOfPosLinear(u8, it.bytes, end + 2, "\r\n") orelse
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
var kv_it = std.mem.splitScalar(u8, it.bytes[end + 2 .. next_end], ':');
|
||||||
|
const name = kv_it.first();
|
||||||
|
const value = kv_it.rest();
|
||||||
|
|
||||||
it.is_trailer = true;
|
it.is_trailer = true;
|
||||||
it.index = next_end + 2;
|
it.index = next_end + 2;
|
||||||
kv_it = std.mem.splitSequence(u8, it.bytes[end + 2 .. next_end], ": ");
|
if (name.len == 0)
|
||||||
return .{
|
return null;
|
||||||
.name = kv_it.next().?,
|
|
||||||
.value = kv_it.rest(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
it.index = end + 2;
|
|
||||||
return .{
|
return .{
|
||||||
.name = name,
|
.name = name,
|
||||||
.value = value,
|
.value = std.mem.trim(u8, value, " \t"),
|
||||||
};
|
};
|
||||||
|
} else { // normal header
|
||||||
|
var kv_it = std.mem.splitScalar(u8, it.bytes[it.index..end], ':');
|
||||||
|
const name = kv_it.first();
|
||||||
|
const value = kv_it.rest();
|
||||||
|
|
||||||
|
it.index = end + 2;
|
||||||
|
if (name.len == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.name = name,
|
||||||
|
.value = std.mem.trim(u8, value, " \t"),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test next {
|
test next {
|
||||||
var it = HeaderIterator.init("200 OK\r\na: b\r\nc: \r\nd: e\r\n\r\nf: g\r\n\r\n");
|
var it = HeaderIterator.init("200 OK\r\na: b\r\nc: \r\nd:e\r\n\r\nf: g\r\n\r\n");
|
||||||
try std.testing.expect(!it.is_trailer);
|
try std.testing.expect(!it.is_trailer);
|
||||||
{
|
{
|
||||||
const header = it.next().?;
|
const header = it.next().?;
|
||||||
@ -62,7 +75,23 @@ test next {
|
|||||||
try std.testing.expectEqualStrings("g", header.value);
|
try std.testing.expectEqualStrings("g", header.value);
|
||||||
}
|
}
|
||||||
try std.testing.expectEqual(null, it.next());
|
try std.testing.expectEqual(null, it.next());
|
||||||
|
|
||||||
|
it = HeaderIterator.init("200 OK\r\n: ss\r\n\r\n");
|
||||||
|
try std.testing.expect(!it.is_trailer);
|
||||||
|
try std.testing.expectEqual(null, it.next());
|
||||||
|
|
||||||
|
it = HeaderIterator.init("200 OK\r\na:b\r\n\r\n: ss\r\n\r\n");
|
||||||
|
try std.testing.expect(!it.is_trailer);
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try std.testing.expect(!it.is_trailer);
|
||||||
|
try std.testing.expectEqualStrings("a", header.name);
|
||||||
|
try std.testing.expectEqualStrings("b", header.value);
|
||||||
|
}
|
||||||
|
try std.testing.expectEqual(null, it.next());
|
||||||
|
try std.testing.expect(it.is_trailer);
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderIterator = @This();
|
const HeaderIterator = @This();
|
||||||
const std = @import("../std.zig");
|
const std = @import("../std.zig");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|||||||
@ -211,9 +211,9 @@ pub const Request = struct {
|
|||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
var line_it = mem.splitSequence(u8, line, ": ");
|
var line_it = mem.splitScalar(u8, line, ':');
|
||||||
const header_name = line_it.next().?;
|
const header_name = line_it.next().?;
|
||||||
const header_value = line_it.rest();
|
const header_value = mem.trim(u8, line_it.rest(), " \t");
|
||||||
if (header_name.len == 0) return error.HttpHeadersInvalid;
|
if (header_name.len == 0) return error.HttpHeadersInvalid;
|
||||||
|
|
||||||
if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
|
if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
|
||||||
@ -271,6 +271,29 @@ pub const Request = struct {
|
|||||||
return error.MissingFinalNewline;
|
return error.MissingFinalNewline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test parse {
|
||||||
|
const request_bytes = "GET /hi HTTP/1.0\r\n" ++
|
||||||
|
"content-tYpe: text/plain\r\n" ++
|
||||||
|
"content-Length:10\r\n" ++
|
||||||
|
"expeCt: 100-continue \r\n" ++
|
||||||
|
"TRansfer-encoding:\tdeflate, chunked \r\n" ++
|
||||||
|
"connectioN:\t keep-alive \r\n\r\n";
|
||||||
|
|
||||||
|
const req = try parse(request_bytes);
|
||||||
|
|
||||||
|
try testing.expectEqual(.GET, req.method);
|
||||||
|
try testing.expectEqual(.@"HTTP/1.0", req.version);
|
||||||
|
try testing.expectEqualStrings("/hi", req.target);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("text/plain", req.content_type.?);
|
||||||
|
try testing.expectEqualStrings("100-continue", req.expect.?);
|
||||||
|
|
||||||
|
try testing.expectEqual(true, req.keep_alive);
|
||||||
|
try testing.expectEqual(10, req.content_length.?);
|
||||||
|
try testing.expectEqual(.chunked, req.transfer_encoding);
|
||||||
|
try testing.expectEqual(.deflate, req.transfer_compression);
|
||||||
|
}
|
||||||
|
|
||||||
inline fn int64(array: *const [8]u8) u64 {
|
inline fn int64(array: *const [8]u8) u64 {
|
||||||
return @bitCast(array.*);
|
return @bitCast(array.*);
|
||||||
}
|
}
|
||||||
@ -280,6 +303,66 @@ pub const Request = struct {
|
|||||||
return http.HeaderIterator.init(r.server.read_buffer[0..r.head_end]);
|
return http.HeaderIterator.init(r.server.read_buffer[0..r.head_end]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test iterateHeaders {
|
||||||
|
const request_bytes = "GET /hi HTTP/1.0\r\n" ++
|
||||||
|
"content-tYpe: text/plain\r\n" ++
|
||||||
|
"content-Length:10\r\n" ++
|
||||||
|
"expeCt: 100-continue \r\n" ++
|
||||||
|
"TRansfer-encoding:\tdeflate, chunked \r\n" ++
|
||||||
|
"connectioN:\t keep-alive \r\n\r\n";
|
||||||
|
|
||||||
|
var read_buffer: [500]u8 = undefined;
|
||||||
|
@memcpy(read_buffer[0..request_bytes.len], request_bytes);
|
||||||
|
|
||||||
|
var server: Server = .{
|
||||||
|
.connection = undefined,
|
||||||
|
.state = .ready,
|
||||||
|
.read_buffer = &read_buffer,
|
||||||
|
.read_buffer_len = request_bytes.len,
|
||||||
|
.next_request_start = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
var request: Request = .{
|
||||||
|
.server = &server,
|
||||||
|
.head_end = request_bytes.len,
|
||||||
|
.head = undefined,
|
||||||
|
.reader_state = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
var it = request.iterateHeaders();
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("content-tYpe", header.name);
|
||||||
|
try testing.expectEqualStrings("text/plain", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("content-Length", header.name);
|
||||||
|
try testing.expectEqualStrings("10", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("expeCt", header.name);
|
||||||
|
try testing.expectEqualStrings("100-continue", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("TRansfer-encoding", header.name);
|
||||||
|
try testing.expectEqualStrings("deflate, chunked", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const header = it.next().?;
|
||||||
|
try testing.expectEqualStrings("connectioN", header.name);
|
||||||
|
try testing.expectEqualStrings("keep-alive", header.value);
|
||||||
|
try testing.expect(!it.is_trailer);
|
||||||
|
}
|
||||||
|
try testing.expectEqual(null, it.next());
|
||||||
|
}
|
||||||
|
|
||||||
pub const RespondOptions = struct {
|
pub const RespondOptions = struct {
|
||||||
version: http.Version = .@"HTTP/1.1",
|
version: http.Version = .@"HTTP/1.1",
|
||||||
status: http.Status = .ok,
|
status: http.Status = .ok,
|
||||||
@ -1060,5 +1143,6 @@ const mem = std.mem;
|
|||||||
const net = std.net;
|
const net = std.net;
|
||||||
const Uri = std.Uri;
|
const Uri = std.Uri;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
const Server = @This();
|
const Server = @This();
|
||||||
|
|||||||
@ -328,8 +328,8 @@ test "receiving arbitrary http headers from the client" {
|
|||||||
defer test_server.destroy();
|
defer test_server.destroy();
|
||||||
|
|
||||||
const request_bytes = "GET /bar HTTP/1.1\r\n" ++
|
const request_bytes = "GET /bar HTTP/1.1\r\n" ++
|
||||||
"CoNneCtIoN: close\r\n" ++
|
"CoNneCtIoN:close\r\n" ++
|
||||||
"aoeu: asdf\r\n" ++
|
"aoeu: asdf \r\n" ++
|
||||||
"\r\n";
|
"\r\n";
|
||||||
const gpa = std.testing.allocator;
|
const gpa = std.testing.allocator;
|
||||||
const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
|
const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user