srctree

Techatrix parent 9727931f a07218cc
http: handle header fields with empty value

inlinesplit
lib/std/http/Client.zig added: 96, removed: 23, total 73
@@ -488,7 +488,7 @@ pub const Response = struct {
var line_it = mem.splitSequence(u8, line, ": ");
const header_name = line_it.next().?;
const header_value = line_it.rest();
if (header_value.len == 0) return error.HttpHeadersInvalid;
if (header_name.len == 0) return error.HttpHeadersInvalid;
 
if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
res.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
@@ -774,7 +774,7 @@ pub const Request = struct {
}
 
for (req.extra_headers) |header| {
assert(header.value.len != 0);
assert(header.name.len != 0);
 
try w.writeAll(header.name);
try w.writeAll(": ");
@@ -1515,11 +1515,13 @@ pub fn open(
) RequestError!Request {
if (std.debug.runtime_safety) {
for (options.extra_headers) |header| {
assert(header.name.len != 0);
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(header.name.len != 0);
assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null);
assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null);
}
 
lib/std/http/HeaderIterator.zig added: 96, removed: 23, total 73
@@ -15,7 +15,7 @@ pub fn next(it: *HeaderIterator) ?std.http.Header {
var kv_it = std.mem.splitSequence(u8, it.bytes[it.index..end], ": ");
const name = kv_it.next().?;
const value = kv_it.rest();
if (value.len == 0) {
if (name.len == 0 and value.len == 0) {
if (it.is_trailer) return null;
const next_end = std.mem.indexOfPosLinear(u8, it.bytes, end + 2, "\r\n") orelse
return null;
@@ -35,7 +35,7 @@ pub fn next(it: *HeaderIterator) ?std.http.Header {
}
 
test next {
var it = HeaderIterator.init("200 OK\r\na: b\r\nc: d\r\n\r\ne: f\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);
{
const header = it.next().?;
@@ -47,13 +47,19 @@ test next {
const header = it.next().?;
try std.testing.expect(!it.is_trailer);
try std.testing.expectEqualStrings("c", header.name);
try std.testing.expectEqualStrings("d", header.value);
try std.testing.expectEqualStrings("", header.value);
}
{
const header = it.next().?;
try std.testing.expect(!it.is_trailer);
try std.testing.expectEqualStrings("d", header.name);
try std.testing.expectEqualStrings("e", header.value);
}
{
const header = it.next().?;
try std.testing.expect(it.is_trailer);
try std.testing.expectEqualStrings("e", header.name);
try std.testing.expectEqualStrings("f", header.value);
try std.testing.expectEqualStrings("f", header.name);
try std.testing.expectEqualStrings("g", header.value);
}
try std.testing.expectEqual(null, it.next());
}
 
lib/std/http/Server.zig added: 96, removed: 23, total 73
@@ -211,7 +211,7 @@ pub const Request = struct {
var line_it = mem.splitSequence(u8, line, ": ");
const header_name = line_it.next().?;
const header_value = line_it.rest();
if (header_value.len == 0) return error.HttpHeadersInvalid;
if (header_name.len == 0) return error.HttpHeadersInvalid;
 
if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
head.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
@@ -311,6 +311,7 @@ pub const Request = struct {
assert(options.extra_headers.len <= max_extra_headers);
if (std.debug.runtime_safety) {
for (options.extra_headers) |header| {
assert(header.name.len != 0);
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);
@@ -370,11 +371,13 @@ pub const Request = struct {
};
iovecs_len += 1;
 
iovecs[iovecs_len] = .{
.iov_base = header.value.ptr,
.iov_len = header.value.len,
};
iovecs_len += 1;
if (header.value.len != 0) {
iovecs[iovecs_len] = .{
.iov_base = header.value.ptr,
.iov_len = header.value.len,
};
iovecs_len += 1;
}
 
iovecs[iovecs_len] = .{
.iov_base = "\r\n",
@@ -496,6 +499,7 @@ pub const Request = struct {
}
 
for (o.extra_headers) |header| {
assert(header.name.len != 0);
h.appendSliceAssumeCapacity(header.name);
h.appendSliceAssumeCapacity(": ");
h.appendSliceAssumeCapacity(header.value);
@@ -986,11 +990,13 @@ pub const Response = struct {
};
iovecs_len += 1;
 
iovecs[iovecs_len] = .{
.iov_base = trailer.value.ptr,
.iov_len = trailer.value.len,
};
iovecs_len += 1;
if (trailer.value.len != 0) {
iovecs[iovecs_len] = .{
.iov_base = trailer.value.ptr,
.iov_len = trailer.value.len,
};
iovecs_len += 1;
}
 
iovecs[iovecs_len] = .{
.iov_base = "\r\n",
 
lib/std/http/test.zig added: 96, removed: 23, total 73
@@ -479,6 +479,12 @@ test "general client/server API coverage" {
.{ .name = "location", .value = location },
},
});
} else if (mem.eql(u8, request.head.target, "/empty")) {
try request.respond("", .{
.extra_headers = &.{
.{ .name = "empty", .value = "" },
},
});
} else {
try request.respond("", .{ .status = .not_found });
}
@@ -491,7 +497,10 @@ test "general client/server API coverage" {
return s.listen_address.in.getPort();
}
});
defer test_server.destroy();
defer {
global.handle_new_requests = false;
test_server.destroy();
}
 
const log = std.log.scoped(.client);
 
@@ -654,6 +663,56 @@ test "general client/server API coverage" {
// connection has been closed
try expect(client.connection_pool.free_len == 0);
 
{ // handle empty header field value
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/empty", .{port});
defer gpa.free(location);
const uri = try std.Uri.parse(location);
 
log.info("{s}", .{location});
var server_header_buffer: [1024]u8 = undefined;
var req = try client.open(.GET, uri, .{
.server_header_buffer = &server_header_buffer,
.extra_headers = &.{
.{ .name = "empty", .value = "" },
},
});
defer req.deinit();
 
try req.send(.{});
try req.wait();
 
try std.testing.expectEqual(.ok, req.response.status);
 
const body = try req.reader().readAllAlloc(gpa, 8192);
defer gpa.free(body);
 
try expectEqualStrings("", body);
 
var it = req.response.iterateHeaders();
{
const header = it.next().?;
try expect(!it.is_trailer);
try expectEqualStrings("connection", header.name);
try expectEqualStrings("keep-alive", header.value);
}
{
const header = it.next().?;
try expect(!it.is_trailer);
try expectEqualStrings("content-length", header.name);
try expectEqualStrings("0", header.value);
}
{
const header = it.next().?;
try expect(!it.is_trailer);
try expectEqualStrings("empty", header.name);
try expectEqualStrings("", header.value);
}
try expectEqual(null, it.next());
}
 
// connection has been kept alive
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
 
{ // relative redirect
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/1", .{port});
defer gpa.free(location);