authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-18 23:34:04-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:26-07:00
log5ada2e39371202f7bbd454c37be31a602f534136
tree6547878160aa3f31f879f8cbd2f74e8f252d1f10
parent6c7b103122f7394f2596830e77c8194a0d33770e

std.http tests passing with updated writer API

fix splat implementation in std.fs.File update http.Client, with caveats: * TODO: only 1 underlying write call * TODO: don't rely on max_buffers_len exceeding the caller * TODO: handle splat update net.Stream API. also make it use WSASend on windows

10 files changed, 609 insertions(+), 580 deletions(-)

lib/std/Uri.zig+28-42
...@@ -42,18 +42,19 @@ pub const Component = union(enum) {...@@ -42,18 +42,19 @@ pub const Component = union(enum) {
4242
43 pub fn format(43 pub fn format(
44 component: Component,44 component: Component,
45 comptime fmt_str: []const u8,45 comptime fmt: []const u8,
46 _: std.fmt.FormatOptions,46 options: std.fmt.Options,
47 writer: *std.io.BufferedWriter,47 writer: *std.io.BufferedWriter,
48 ) anyerror!void {48 ) anyerror!void {
49 if (fmt_str.len == 0) {49 _ = options;
50 if (fmt.len == 0) {
50 try writer.print("std.Uri.Component{{ .{s} = \"{}\" }}", .{51 try writer.print("std.Uri.Component{{ .{s} = \"{}\" }}", .{
51 @tagName(component),52 @tagName(component),
52 std.zig.fmtEscapes(switch (component) {53 std.zig.fmtEscapes(switch (component) {
53 .raw, .percent_encoded => |string| string,54 .raw, .percent_encoded => |string| string,
54 }),55 }),
55 });56 });
56 } else if (comptime std.mem.eql(u8, fmt_str, "raw")) switch (component) {57 } else if (comptime std.mem.eql(u8, fmt, "raw")) switch (component) {
57 .raw => |raw| try writer.writeAll(raw),58 .raw => |raw| try writer.writeAll(raw),
58 .percent_encoded => |percent_encoded| {59 .percent_encoded => |percent_encoded| {
59 var start: usize = 0;60 var start: usize = 0;
...@@ -72,28 +73,28 @@ pub const Component = union(enum) {...@@ -72,28 +73,28 @@ pub const Component = union(enum) {
72 }73 }
73 try writer.writeAll(percent_encoded[start..]);74 try writer.writeAll(percent_encoded[start..]);
74 },75 },
75 } else if (comptime std.mem.eql(u8, fmt_str, "%")) switch (component) {76 } else if (comptime std.mem.eql(u8, fmt, "%")) switch (component) {
76 .raw => |raw| try percentEncode(writer, raw, isUnreserved),77 .raw => |raw| try percentEncode(writer, raw, isUnreserved),
77 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),78 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
78 } else if (comptime std.mem.eql(u8, fmt_str, "user")) switch (component) {79 } else if (comptime std.mem.eql(u8, fmt, "user")) switch (component) {
79 .raw => |raw| try percentEncode(writer, raw, isUserChar),80 .raw => |raw| try percentEncode(writer, raw, isUserChar),
80 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),81 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
81 } else if (comptime std.mem.eql(u8, fmt_str, "password")) switch (component) {82 } else if (comptime std.mem.eql(u8, fmt, "password")) switch (component) {
82 .raw => |raw| try percentEncode(writer, raw, isPasswordChar),83 .raw => |raw| try percentEncode(writer, raw, isPasswordChar),
83 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),84 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
84 } else if (comptime std.mem.eql(u8, fmt_str, "host")) switch (component) {85 } else if (comptime std.mem.eql(u8, fmt, "host")) switch (component) {
85 .raw => |raw| try percentEncode(writer, raw, isHostChar),86 .raw => |raw| try percentEncode(writer, raw, isHostChar),
86 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),87 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
87 } else if (comptime std.mem.eql(u8, fmt_str, "path")) switch (component) {88 } else if (comptime std.mem.eql(u8, fmt, "path")) switch (component) {
88 .raw => |raw| try percentEncode(writer, raw, isPathChar),89 .raw => |raw| try percentEncode(writer, raw, isPathChar),
89 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),90 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
90 } else if (comptime std.mem.eql(u8, fmt_str, "query")) switch (component) {91 } else if (comptime std.mem.eql(u8, fmt, "query")) switch (component) {
91 .raw => |raw| try percentEncode(writer, raw, isQueryChar),92 .raw => |raw| try percentEncode(writer, raw, isQueryChar),
92 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),93 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
93 } else if (comptime std.mem.eql(u8, fmt_str, "fragment")) switch (component) {94 } else if (comptime std.mem.eql(u8, fmt, "fragment")) switch (component) {
94 .raw => |raw| try percentEncode(writer, raw, isFragmentChar),95 .raw => |raw| try percentEncode(writer, raw, isFragmentChar),
95 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),96 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
96 } else @compileError("invalid format string '" ++ fmt_str ++ "'");97 } else @compileError("invalid format string '" ++ fmt ++ "'");
97 }98 }
9899
99 pub fn percentEncode(100 pub fn percentEncode(
...@@ -227,31 +228,21 @@ pub fn parseAfterScheme(scheme: []const u8, text: []const u8) ParseError!Uri {...@@ -227,31 +228,21 @@ pub fn parseAfterScheme(scheme: []const u8, text: []const u8) ParseError!Uri {
227pub const WriteToStreamOptions = struct {228pub const WriteToStreamOptions = struct {
228 /// When true, include the scheme part of the URI.229 /// When true, include the scheme part of the URI.
229 scheme: bool = false,230 scheme: bool = false,
230
231 /// When true, include the user and password part of the URI. Ignored if `authority` is false.231 /// When true, include the user and password part of the URI. Ignored if `authority` is false.
232 authentication: bool = false,232 authentication: bool = false,
233
234 /// When true, include the authority part of the URI.233 /// When true, include the authority part of the URI.
235 authority: bool = false,234 authority: bool = false,
236
237 /// When true, include the path part of the URI.235 /// When true, include the path part of the URI.
238 path: bool = false,236 path: bool = false,
239
240 /// When true, include the query part of the URI. Ignored when `path` is false.237 /// When true, include the query part of the URI. Ignored when `path` is false.
241 query: bool = false,238 query: bool = false,
242
243 /// When true, include the fragment part of the URI. Ignored when `path` is false.239 /// When true, include the fragment part of the URI. Ignored when `path` is false.
244 fragment: bool = false,240 fragment: bool = false,
245
246 /// When true, include the port part of the URI. Ignored when `port` is null.241 /// When true, include the port part of the URI. Ignored when `port` is null.
247 port: bool = true,242 port: bool = true,
248};243};
249244
250pub fn writeToStream(245pub fn writeToStream(uri: Uri, options: WriteToStreamOptions, writer: *std.io.BufferedWriter) anyerror!void {
251 uri: Uri,
252 options: WriteToStreamOptions,
253 writer: anytype,
254) @TypeOf(writer).Error!void {
255 if (options.scheme) {246 if (options.scheme) {
256 try writer.print("{s}:", .{uri.scheme});247 try writer.print("{s}:", .{uri.scheme});
257 if (options.authority and uri.host != null) {248 if (options.authority and uri.host != null) {
...@@ -261,45 +252,40 @@ pub fn writeToStream(...@@ -261,45 +252,40 @@ pub fn writeToStream(
261 if (options.authority) {252 if (options.authority) {
262 if (options.authentication and uri.host != null) {253 if (options.authentication and uri.host != null) {
263 if (uri.user) |user| {254 if (uri.user) |user| {
264 try writer.print("{user}", .{user});255 try writer.print("{fuser}", .{user});
265 if (uri.password) |password| {256 if (uri.password) |password| {
266 try writer.print(":{password}", .{password});257 try writer.print(":{fpassword}", .{password});
267 }258 }
268 try writer.writeByte('@');259 try writer.writeByte('@');
269 }260 }
270 }261 }
271 if (uri.host) |host| {262 if (uri.host) |host| {
272 try writer.print("{host}", .{host});263 try writer.print("{fhost}", .{host});
273 if (options.port) {264 if (options.port) {
274 if (uri.port) |port| try writer.print(":{d}", .{port});265 if (uri.port) |port| try writer.print(":{d}", .{port});
275 }266 }
276 }267 }
277 }268 }
278 if (options.path) {269 if (options.path) {
279 try writer.print("{path}", .{270 try writer.print("{fpath}", .{
280 if (uri.path.isEmpty()) Uri.Component{ .percent_encoded = "/" } else uri.path,271 if (uri.path.isEmpty()) Uri.Component{ .percent_encoded = "/" } else uri.path,
281 });272 });
282 if (options.query) {273 if (options.query) {
283 if (uri.query) |query| try writer.print("?{query}", .{query});274 if (uri.query) |query| try writer.print("?{fquery}", .{query});
284 }275 }
285 if (options.fragment) {276 if (options.fragment) {
286 if (uri.fragment) |fragment| try writer.print("#{fragment}", .{fragment});277 if (uri.fragment) |fragment| try writer.print("#{ffragment}", .{fragment});
287 }278 }
288 }279 }
289}280}
290281
291pub fn format(282pub fn format(uri: Uri, comptime fmt: []const u8, _: std.fmt.Options, writer: *std.io.BufferedWriter) anyerror!void {
292 uri: Uri,283 const scheme = comptime std.mem.indexOfScalar(u8, fmt, ';') != null or fmt.len == 0;
293 comptime fmt_str: []const u8,284 const authentication = comptime std.mem.indexOfScalar(u8, fmt, '@') != null or fmt.len == 0;
294 _: std.fmt.FormatOptions,285 const authority = comptime std.mem.indexOfScalar(u8, fmt, '+') != null or fmt.len == 0;
295 writer: anytype,286 const path = comptime std.mem.indexOfScalar(u8, fmt, '/') != null or fmt.len == 0;
296) @TypeOf(writer).Error!void {287 const query = comptime std.mem.indexOfScalar(u8, fmt, '?') != null or fmt.len == 0;
297 const scheme = comptime std.mem.indexOfScalar(u8, fmt_str, ';') != null or fmt_str.len == 0;288 const fragment = comptime std.mem.indexOfScalar(u8, fmt, '#') != null or fmt.len == 0;
298 const authentication = comptime std.mem.indexOfScalar(u8, fmt_str, '@') != null or fmt_str.len == 0;
299 const authority = comptime std.mem.indexOfScalar(u8, fmt_str, '+') != null or fmt_str.len == 0;
300 const path = comptime std.mem.indexOfScalar(u8, fmt_str, '/') != null or fmt_str.len == 0;
301 const query = comptime std.mem.indexOfScalar(u8, fmt_str, '?') != null or fmt_str.len == 0;
302 const fragment = comptime std.mem.indexOfScalar(u8, fmt_str, '#') != null or fmt_str.len == 0;
303289
304 return writeToStream(uri, .{290 return writeToStream(uri, .{
305 .scheme = scheme,291 .scheme = scheme,
...@@ -449,7 +435,7 @@ fn merge_paths(base: Component, new: []u8, aux_buf: *[]u8) error{NoSpaceLeft}!Co...@@ -449,7 +435,7 @@ fn merge_paths(base: Component, new: []u8, aux_buf: *[]u8) error{NoSpaceLeft}!Co
449 aux.initFixed(aux_buf.*);435 aux.initFixed(aux_buf.*);
450 if (!base.isEmpty()) {436 if (!base.isEmpty()) {
451 aux.print("{fpath}", .{base}) catch |err| return @errorCast(err);437 aux.print("{fpath}", .{base}) catch |err| return @errorCast(err);
452 aux.pos = std.mem.lastIndexOfScalar(u8, aux.getWritten(), '/') orelse438 aux.end = std.mem.lastIndexOfScalar(u8, aux.getWritten(), '/') orelse
453 return remove_dot_segments(new);439 return remove_dot_segments(new);
454 }440 }
455 aux.print("/{s}", .{new}) catch |err| return @errorCast(err);441 aux.print("/{s}", .{new}) catch |err| return @errorCast(err);
lib/std/crypto/ecdsa.zig+4-4
...@@ -135,8 +135,8 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -135,8 +135,8 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
135 /// The maximum length of the DER encoding is der_encoded_length_max.135 /// The maximum length of the DER encoding is der_encoded_length_max.
136 /// The function returns a slice, that can be shorter than der_encoded_length_max.136 /// The function returns a slice, that can be shorter than der_encoded_length_max.
137 pub fn toDer(sig: Signature, buf: *[der_encoded_length_max]u8) []u8 {137 pub fn toDer(sig: Signature, buf: *[der_encoded_length_max]u8) []u8 {
138 var fb = io.fixedBufferStream(buf);138 var w: std.io.BufferedWriter = undefined;
139 const w = fb.writer();139 w.initFixed(buf);
140 const r_len = @as(u8, @intCast(sig.r.len + (sig.r[0] >> 7)));140 const r_len = @as(u8, @intCast(sig.r.len + (sig.r[0] >> 7)));
141 const s_len = @as(u8, @intCast(sig.s.len + (sig.s[0] >> 7)));141 const s_len = @as(u8, @intCast(sig.s.len + (sig.s[0] >> 7)));
142 const seq_len = @as(u8, @intCast(2 + r_len + 2 + s_len));142 const seq_len = @as(u8, @intCast(2 + r_len + 2 + s_len));
...@@ -151,7 +151,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -151,7 +151,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
151 w.writeByte(0x00) catch unreachable;151 w.writeByte(0x00) catch unreachable;
152 }152 }
153 w.writeAll(&sig.s) catch unreachable;153 w.writeAll(&sig.s) catch unreachable;
154 return fb.getWritten();154 return w.getWritten();
155 }155 }
156156
157 // Read a DER-encoded integer.157 // Read a DER-encoded integer.
...@@ -176,7 +176,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -176,7 +176,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
176 /// Returns InvalidEncoding if the DER encoding is invalid.176 /// Returns InvalidEncoding if the DER encoding is invalid.
177 pub fn fromDer(der: []const u8) EncodingError!Signature {177 pub fn fromDer(der: []const u8) EncodingError!Signature {
178 var sig: Signature = mem.zeroInit(Signature, .{});178 var sig: Signature = mem.zeroInit(Signature, .{});
179 var fb = io.fixedBufferStream(der);179 var fb: std.io.FixedBufferStream = .{ .buffer = der };
180 const reader = fb.reader();180 const reader = fb.reader();
181 var buf: [2]u8 = undefined;181 var buf: [2]u8 = undefined;
182 _ = reader.readNoEof(&buf) catch return error.InvalidEncoding;182 _ = reader.readNoEof(&buf) catch return error.InvalidEncoding;
lib/std/fs/File.zig+109-107
...@@ -1594,122 +1594,124 @@ pub fn reader(file: File) Reader {...@@ -1594,122 +1594,124 @@ pub fn reader(file: File) Reader {
15941594
1595pub fn writer(file: File) std.io.Writer {1595pub fn writer(file: File) std.io.Writer {
1596 return .{1596 return .{
1597 .context = interface.handleToOpaque(file.handle),1597 .context = handleToOpaque(file.handle),
1598 .vtable = &.{1598 .vtable = &.{
1599 .writeSplat = interface.writeSplat,1599 .writeSplat = writer_writeSplat,
1600 .writeFile = interface.writeFile,1600 .writeFile = writer_writeFile,
1601 },1601 },
1602 };1602 };
1603}1603}
16041604
1605const interface = struct {1605/// Number of slices to store on the stack, when trying to send as many byte
1606 /// Number of slices to store on the stack, when trying to send as many byte1606/// vectors through the underlying write calls as possible.
1607 /// vectors through the underlying write calls as possible.1607const max_buffers_len = 16;
1608 const max_buffers_len = 16;
1609
1610 fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1611 const file = opaqueToHandle(context);
1612 var splat_buffer: [256]u8 = undefined;
1613 if (is_windows) {
1614 if (data.len == 1 and splat == 0) return 0;
1615 return windows.WriteFile(file, data[0], null);
1616 }
1617 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
1618 var len: usize = @min(iovecs.len, data.len);
1619 for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{
1620 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
1621 .len = d.len,
1622 };
1623 switch (splat) {
1624 0 => return std.posix.writev(file, iovecs[0 .. len - 1]),
1625 1 => return std.posix.writev(file, iovecs[0..len]),
1626 else => {
1627 const pattern = data[data.len - 1];
1628 if (pattern.len == 1) {
1629 const memset_len = @min(splat_buffer.len, splat);
1630 const buf = splat_buffer[0..memset_len];
1631 @memset(buf, pattern[0]);
1632 iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len };
1633 var remaining_splat = splat - buf.len;
1634 while (remaining_splat > 0 and len < iovecs.len) {
1635 iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len };
1636 remaining_splat -= splat_buffer.len;
1637 len += 1;
1638 }
1639 return std.posix.writev(file, iovecs[0..len]);
1640 }
1641 },
1642 }
1643 return std.posix.writev(file, iovecs[0..len]);
1644 }
1645
1646 fn writeFile(
1647 context: *anyopaque,
1648 in_file: std.fs.File,
1649 in_offset: u64,
1650 in_len: std.io.Writer.VTable.FileLen,
1651 headers_and_trailers: []const []const u8,
1652 headers_len: usize,
1653 ) anyerror!usize {
1654 const out_fd = opaqueToHandle(context);
1655 const in_fd = in_file.handle;
1656 const len_int = switch (in_len) {
1657 .zero => return interface.writeSplat(context, headers_and_trailers, 1),
1658 .entire_file => 0,
1659 else => in_len.int(),
1660 };
1661 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;
1662 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, headers_and_trailers.len)];
1663 for (iovecs, headers_and_trailers[0..iovecs.len]) |*v, d| v.* = .{ .base = d.ptr, .len = d.len };
1664 const headers = iovecs[0..@min(headers_len, iovecs.len)];
1665 const trailers = iovecs[headers.len..];
1666 const flags = 0;
1667 return posix.sendfile(out_fd, in_fd, in_offset, len_int, headers, trailers, flags) catch |err| switch (err) {
1668 error.Unseekable,
1669 error.FastOpenAlreadyInProgress,
1670 error.MessageTooBig,
1671 error.FileDescriptorNotASocket,
1672 error.NetworkUnreachable,
1673 error.NetworkSubsystemFailed,
1674 => return writeFileUnseekable(out_fd, in_fd, in_offset, in_len, headers_and_trailers, headers_len),
16751608
1676 else => |e| return e,1609pub fn writer_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1677 };1610 const file = opaqueToHandle(context);
1611 var splat_buffer: [256]u8 = undefined;
1612 if (is_windows) {
1613 if (data.len == 1 and splat == 0) return 0;
1614 return windows.WriteFile(file, data[0], null);
1615 }
1616 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
1617 var len: usize = @min(iovecs.len, data.len);
1618 for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{
1619 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
1620 .len = d.len,
1621 };
1622 switch (splat) {
1623 0 => return std.posix.writev(file, iovecs[0 .. len - 1]),
1624 1 => return std.posix.writev(file, iovecs[0..len]),
1625 else => {
1626 const pattern = data[data.len - 1];
1627 if (pattern.len == 1) {
1628 const memset_len = @min(splat_buffer.len, splat);
1629 const buf = splat_buffer[0..memset_len];
1630 @memset(buf, pattern[0]);
1631 iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len };
1632 var remaining_splat = splat - buf.len;
1633 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
1634 iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len };
1635 remaining_splat -= splat_buffer.len;
1636 len += 1;
1637 }
1638 if (remaining_splat > 0 and len < iovecs.len) {
1639 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
1640 len += 1;
1641 }
1642 return std.posix.writev(file, iovecs[0..len]);
1643 }
1644 },
1678 }1645 }
1646 return std.posix.writev(file, iovecs[0..len]);
1647}
16791648
1680 fn writeFileUnseekable(1649pub fn writer_writeFile(
1681 out_fd: Handle,1650 context: *anyopaque,
1682 in_fd: Handle,1651 in_file: std.fs.File,
1683 in_offset: u64,1652 in_offset: u64,
1684 in_len: std.io.Writer.VTable.FileLen,1653 in_len: std.io.Writer.VTable.FileLen,
1685 headers_and_trailers: []const []const u8,1654 headers_and_trailers: []const []const u8,
1686 headers_len: usize,1655 headers_len: usize,
1687 ) anyerror!usize {1656) anyerror!usize {
1688 _ = out_fd;1657 const out_fd = opaqueToHandle(context);
1689 _ = in_fd;1658 const in_fd = in_file.handle;
1690 _ = in_offset;1659 const len_int = switch (in_len) {
1691 _ = in_len;1660 .zero => return writer_writeSplat(context, headers_and_trailers, 1),
1692 _ = headers_and_trailers;1661 .entire_file => 0,
1693 _ = headers_len;1662 else => in_len.int(),
1694 @panic("TODO writeFileUnseekable");1663 };
1695 }1664 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;
16961665 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, headers_and_trailers.len)];
1697 fn handleToOpaque(handle: File.Handle) *anyopaque {1666 for (iovecs, headers_and_trailers[0..iovecs.len]) |*v, d| v.* = .{ .base = d.ptr, .len = d.len };
1698 return switch (@typeInfo(Handle)) {1667 const headers = iovecs[0..@min(headers_len, iovecs.len)];
1699 .pointer => @ptrCast(handle),1668 const trailers = iovecs[headers.len..];
1700 .int => @ptrFromInt(@as(u32, @bitCast(handle))),1669 const flags = 0;
1701 else => @compileError("unhandled"),1670 return posix.sendfile(out_fd, in_fd, in_offset, len_int, headers, trailers, flags) catch |err| switch (err) {
1702 };1671 error.Unseekable,
1703 }1672 error.FastOpenAlreadyInProgress,
1673 error.MessageTooBig,
1674 error.FileDescriptorNotASocket,
1675 error.NetworkUnreachable,
1676 error.NetworkSubsystemFailed,
1677 => return writeFileUnseekable(out_fd, in_fd, in_offset, in_len, headers_and_trailers, headers_len),
17041678
1705 fn opaqueToHandle(userdata: *anyopaque) Handle {1679 else => |e| return e,
1706 return switch (@typeInfo(Handle)) {1680 };
1707 .pointer => @ptrCast(userdata),1681}
1708 .int => @intCast(@intFromPtr(userdata)),1682
1709 else => @compileError("unhandled"),1683fn writeFileUnseekable(
1710 };1684 out_fd: Handle,
1711 }1685 in_fd: Handle,
1712};1686 in_offset: u64,
1687 in_len: std.io.Writer.VTable.FileLen,
1688 headers_and_trailers: []const []const u8,
1689 headers_len: usize,
1690) anyerror!usize {
1691 _ = out_fd;
1692 _ = in_fd;
1693 _ = in_offset;
1694 _ = in_len;
1695 _ = headers_and_trailers;
1696 _ = headers_len;
1697 @panic("TODO writeFileUnseekable");
1698}
1699
1700fn handleToOpaque(handle: Handle) *anyopaque {
1701 return switch (@typeInfo(Handle)) {
1702 .pointer => @ptrCast(handle),
1703 .int => @ptrFromInt(@as(u32, @bitCast(handle))),
1704 else => @compileError("unhandled"),
1705 };
1706}
1707
1708fn opaqueToHandle(userdata: *anyopaque) Handle {
1709 return switch (@typeInfo(Handle)) {
1710 .pointer => @ptrCast(userdata),
1711 .int => @intCast(@intFromPtr(userdata)),
1712 else => @compileError("unhandled"),
1713 };
1714}
17131715
1714pub const SeekableStream = io.SeekableStream(1716pub const SeekableStream = io.SeekableStream(
1715 File,1717 File,
lib/std/http/Client.zig+177-153
...@@ -187,6 +187,9 @@ pub const ConnectionPool = struct {...@@ -187,6 +187,9 @@ pub const ConnectionPool = struct {
187/// An interface to either a plain or TLS connection.187/// An interface to either a plain or TLS connection.
188pub const Connection = struct {188pub const Connection = struct {
189 stream: net.Stream,189 stream: net.Stream,
190 /// Populated when protocol is TLS; this is the writer given to the TLS
191 /// client, which writes directly to `stream`, unbuffered.
192 stream_writer: std.io.BufferedWriter,
190 /// undefined unless protocol is tls.193 /// undefined unless protocol is tls.
191 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,194 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,
192195
...@@ -210,9 +213,10 @@ pub const Connection = struct {...@@ -210,9 +213,10 @@ pub const Connection = struct {
210213
211 read_start: BufferSize = 0,214 read_start: BufferSize = 0,
212 read_end: BufferSize = 0,215 read_end: BufferSize = 0,
213 write_end: BufferSize = 0,216 read_buf: [buffer_size]u8,
214 read_buf: [buffer_size]u8 = undefined,217
215 write_buf: [buffer_size]u8 = undefined,218 write_buffer: [buffer_size]u8,
219 writer: std.io.BufferedWriter,
216220
217 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;221 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
218 const BufferSize = std.math.IntFittingRange(0, buffer_size);222 const BufferSize = std.math.IntFittingRange(0, buffer_size);
...@@ -314,59 +318,7 @@ pub const Connection = struct {...@@ -314,59 +318,7 @@ pub const Connection = struct {
314 pub const Reader = std.io.Reader(*Connection, ReadError, read);318 pub const Reader = std.io.Reader(*Connection, ReadError, read);
315319
316 pub fn reader(conn: *Connection) Reader {320 pub fn reader(conn: *Connection) Reader {
317 return Reader{ .context = conn };321 return .{ .context = conn };
318 }
319
320 pub fn writeAllDirectTls(conn: *Connection, buffer: []const u8) WriteError!void {
321 return conn.tls_client.writeAll(conn.stream, buffer) catch |err| switch (err) {
322 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
323 else => return error.UnexpectedWriteFailure,
324 };
325 }
326
327 pub fn writeAllDirect(conn: *Connection, buffer: []const u8) WriteError!void {
328 if (conn.protocol == .tls) {
329 if (disable_tls) unreachable;
330
331 return conn.writeAllDirectTls(buffer);
332 }
333
334 return conn.stream.writeAll(buffer) catch |err| switch (err) {
335 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
336 else => return error.UnexpectedWriteFailure,
337 };
338 }
339
340 /// Writes the given buffer to the connection.
341 pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
342 if (conn.write_buf.len - conn.write_end < buffer.len) {
343 try conn.flush();
344
345 if (buffer.len > conn.write_buf.len) {
346 try conn.writeAllDirect(buffer);
347 return buffer.len;
348 }
349 }
350
351 @memcpy(conn.write_buf[conn.write_end..][0..buffer.len], buffer);
352 conn.write_end += @intCast(buffer.len);
353
354 return buffer.len;
355 }
356
357 /// Returns a buffer to be filled with exactly len bytes to write to the connection.
358 pub fn allocWriteBuffer(conn: *Connection, len: BufferSize) WriteError![]u8 {
359 if (conn.write_buf.len - conn.write_end < len) try conn.flush();
360 defer conn.write_end += len;
361 return conn.write_buf[conn.write_end..][0..len];
362 }
363
364 /// Flushes the write buffer to the connection.
365 pub fn flush(conn: *Connection) WriteError!void {
366 if (conn.write_end == 0) return;
367
368 try conn.writeAllDirect(conn.write_buf[0..conn.write_end]);
369 conn.write_end = 0;
370 }322 }
371323
372 pub const WriteError = error{324 pub const WriteError = error{
...@@ -374,19 +326,12 @@ pub const Connection = struct {...@@ -374,19 +326,12 @@ pub const Connection = struct {
374 UnexpectedWriteFailure,326 UnexpectedWriteFailure,
375 };327 };
376328
377 pub const Writer = std.io.Writer(*Connection, WriteError, write);
378
379 pub fn writer(conn: *Connection) Writer {
380 return Writer{ .context = conn };
381 }
382
383 /// Closes the connection.
384 pub fn close(conn: *Connection, allocator: Allocator) void {329 pub fn close(conn: *Connection, allocator: Allocator) void {
385 if (conn.protocol == .tls) {330 if (conn.protocol == .tls) {
386 if (disable_tls) unreachable;331 if (disable_tls) unreachable;
387332
388 // try to cleanly close the TLS connection, for any server that cares.333 // try to cleanly close the TLS connection, for any server that cares.
389 _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {};334 _ = conn.tls_client.writeEnd("", true) catch {};
390 if (conn.tls_client.ssl_key_log) |key_log| key_log.file.close();335 if (conn.tls_client.ssl_key_log) |key_log| key_log.file.close();
391 allocator.destroy(conn.tls_client);336 allocator.destroy(conn.tls_client);
392 }337 }
...@@ -815,15 +760,12 @@ pub const Request = struct {...@@ -815,15 +760,12 @@ pub const Request = struct {
815 };760 };
816 }761 }
817762
818 pub const SendError = Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
819
820 /// Send the HTTP request headers to the server.763 /// Send the HTTP request headers to the server.
821 pub fn send(req: *Request) SendError!void {764 pub fn send(req: *Request) anyerror!void {
822 if (!req.method.requestHasBody() and req.transfer_encoding != .none)765 assert(req.transfer_encoding == .none or req.method.requestHasBody());
823 return error.UnsupportedTransferEncoding;
824766
825 const connection = req.connection.?;767 const connection = req.connection.?;
826 const w = connection.writer();768 const w = &connection.writer;
827769
828 try req.method.write(w);770 try req.method.write(w);
829 try w.writeByte(' ');771 try w.writeByte(' ');
...@@ -852,10 +794,7 @@ pub const Request = struct {...@@ -852,10 +794,7 @@ pub const Request = struct {
852 if (try emitOverridableHeader("authorization: ", req.headers.authorization, w)) {794 if (try emitOverridableHeader("authorization: ", req.headers.authorization, w)) {
853 if (req.uri.user != null or req.uri.password != null) {795 if (req.uri.user != null or req.uri.password != null) {
854 try w.writeAll("authorization: ");796 try w.writeAll("authorization: ");
855 const authorization = try connection.allocWriteBuffer(797 try basic_authorization.write(req.uri, w);
856 @intCast(basic_authorization.valueLengthFromUri(req.uri)),
857 );
858 assert(basic_authorization.value(req.uri, authorization).len == authorization.len);
859 try w.writeAll("\r\n");798 try w.writeAll("\r\n");
860 }799 }
861 }800 }
...@@ -914,7 +853,7 @@ pub const Request = struct {...@@ -914,7 +853,7 @@ pub const Request = struct {
914853
915 try w.writeAll("\r\n");854 try w.writeAll("\r\n");
916855
917 try connection.flush();856 try connection.writer.flush();
918 }857 }
919858
920 /// Returns true if the default behavior is required, otherwise handles859 /// Returns true if the default behavior is required, otherwise handles
...@@ -953,7 +892,9 @@ pub const Request = struct {...@@ -953,7 +892,9 @@ pub const Request = struct {
953 return index;892 return index;
954 }893 }
955894
956 pub const WaitError = RequestError || SendError || TransferReadError ||895 /// TODO collapse each error set into its own meta error code, and store
896 /// the underlying error code as a field on Request
897 pub const WaitError = RequestError || anyerror || TransferReadError ||
957 proto.HeadersParser.CheckCompleteHeadError || Response.ParseError ||898 proto.HeadersParser.CheckCompleteHeadError || Response.ParseError ||
958 error{899 error{
959 TooManyHttpRedirects,900 TooManyHttpRedirects,
...@@ -1132,59 +1073,112 @@ pub const Request = struct {...@@ -1132,59 +1073,112 @@ pub const Request = struct {
1132 return index;1073 return index;
1133 }1074 }
11341075
1135 pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };1076 /// Resulting `std.io.Writer` must used after `send` and before `finish`.
11361077 pub fn writer(req: *Request) std.io.Writer {
1137 pub const Writer = std.io.Writer(*Request, WriteError, write);1078 return .{
11381079 .context = req,
1139 pub fn writer(req: *Request) Writer {1080 .vtable = switch (req.transfer_encoding) {
1140 return .{ .context = req };1081 .chunked => &.{
1082 .writeSplat = chunked_writeSplat,
1083 .writeFile = chunked_writeFile,
1084 },
1085 .content_length => &.{
1086 .writeSplat = cl_writeSplat,
1087 .writeFile = cl_writeFile,
1088 },
1089 .none => unreachable,
1090 },
1091 };
1141 }1092 }
11421093
1143 /// Write `bytes` to the server. The `transfer_encoding` field determines how data will be sent.1094 fn chunked_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1144 /// Must be called after `send` and before `finish`.1095 const req: *Request = @ptrCast(@alignCast(context));
1145 pub fn write(req: *Request, bytes: []const u8) WriteError!usize {1096 var total: usize = 0;
1146 switch (req.transfer_encoding) {1097 for (data) |bytes| total += bytes.len;
1147 .chunked => {1098 if (total == 0) return 0;
1148 if (bytes.len > 0) {1099 var iovecs: [max_buffers_len][]const u8 = undefined;
1149 try req.connection.?.writer().print("{x}\r\n", .{bytes.len});1100 var header_buffer: [30]u8 = undefined;
1150 try req.connection.?.writer().writeAll(bytes);1101 var header_buffer_writer: std.io.BufferedWriter = undefined;
1151 try req.connection.?.writer().writeAll("\r\n");1102 header_buffer_writer.initFixed(&header_buffer);
1152 }1103 header_buffer_writer.print("{x}\r\n", .{total}) catch unreachable;
11531104 iovecs[0] = header_buffer_writer.getWritten();
1154 return bytes.len;1105 @memcpy(iovecs[1..][0..data.len], data);
1155 },1106 iovecs[data.len + 1] = "\r\n";
1156 .content_length => |*len| {1107 // TODO: only 1 underlying write call
1157 if (len.* < bytes.len) return error.MessageTooLong;1108 // TODO: don't rely on max_buffers_len exceeding the caller
1109 // TODO: handle splat
1110 _ = splat;
1111 const w = &req.connection.?.writer;
1112 try w.writevAll(iovecs[0 .. data.len + 2]);
1113 return total;
1114 }
11581115
1159 const amt = try req.connection.?.write(bytes);1116 const max_buffers_len = 16;
1160 len.* -= amt;1117
1161 return amt;1118 pub fn chunked_writeFile(
1162 },1119 context: *anyopaque,
1163 .none => return error.NotWriteable,1120 file: std.fs.File,
1164 }1121 offset: u64,
1122 len: std.io.Writer.VTable.FileLen,
1123 headers_and_trailers: []const []const u8,
1124 headers_len: usize,
1125 ) anyerror!usize {
1126 if (len == .entire_file) return error.Unimplemented;
1127 const req: *Request = @ptrCast(@alignCast(context));
1128 var total: usize = len.int();
1129 for (headers_and_trailers) |bytes| total += bytes.len;
1130 if (total == 0) return 0;
1131 var iovecs: [max_buffers_len][]const u8 = undefined;
1132 var header_buffer: [30]u8 = undefined;
1133 var header_buffer_writer: std.io.BufferedWriter = undefined;
1134 header_buffer_writer.initFixed(&header_buffer);
1135 header_buffer_writer.print("{x}\r\n", .{total}) catch unreachable;
1136 iovecs[0] = header_buffer_writer.getWritten();
1137 @memcpy(iovecs[1..][0..headers_and_trailers.len], headers_and_trailers);
1138 iovecs[headers_and_trailers.len + 1] = "\r\n";
1139 // TODO: only 1 underlying write call
1140 // TODO: don't rely on max_buffers_len exceeding the caller
1141 const w = &req.connection.?.writer;
1142 try w.writeFileAll(file, .{
1143 .offset = offset,
1144 .len = len,
1145 .headers_and_trailers = iovecs[0 .. headers_and_trailers.len + 2],
1146 .headers_len = headers_len + 1,
1147 });
1148 return total;
1165 }1149 }
11661150
1167 /// Write `bytes` to the server. The `transfer_encoding` field determines how data will be sent.1151 fn cl_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1168 /// Must be called after `send` and before `finish`.1152 const req: *Request = @ptrCast(@alignCast(context));
1169 pub fn writeAll(req: *Request, bytes: []const u8) WriteError!void {1153 const n = try req.connection.?.writer.writeSplat(data, splat);
1170 var index: usize = 0;1154 req.transfer_encoding.content_length -= n;
1171 while (index < bytes.len) {1155 return n;
1172 index += try write(req, bytes[index..]);
1173 }
1174 }1156 }
11751157
1176 pub const FinishError = WriteError || error{MessageNotCompleted};1158 pub fn cl_writeFile(
1159 context: *anyopaque,
1160 file: std.fs.File,
1161 offset: u64,
1162 len: std.io.Writer.VTable.FileLen,
1163 headers_and_trailers: []const []const u8,
1164 headers_len: usize,
1165 ) anyerror!usize {
1166 const req: *Request = @ptrCast(@alignCast(context));
1167 const n = try req.connection.?.writer.writeFile(file, offset, len, headers_and_trailers, headers_len);
1168 req.transfer_encoding.content_length -= n;
1169 return n;
1170 }
11771171
1178 /// Finish the body of a request. This notifies the server that you have no more data to send.1172 /// Finish the body of a request. This notifies the server that you have no more data to send.
1179 /// Must be called after `send`.1173 /// Must be called after `send`.
1180 pub fn finish(req: *Request) FinishError!void {1174 pub fn finish(req: *Request) anyerror!void {
1181 switch (req.transfer_encoding) {1175 switch (req.transfer_encoding) {
1182 .chunked => try req.connection.?.writer().writeAll("0\r\n\r\n"),1176 .chunked => try req.connection.?.writer.writeAll("0\r\n\r\n"),
1183 .content_length => |len| if (len != 0) return error.MessageNotCompleted,1177 .content_length => |len| assert(len == 0),
1184 .none => {},1178 .none => {},
1185 }1179 }
11861180
1187 try req.connection.?.flush();1181 try req.connection.?.writer.flush();
1188 }1182 }
1189};1183};
11901184
...@@ -1276,10 +1270,8 @@ pub const basic_authorization = struct {...@@ -1276,10 +1270,8 @@ pub const basic_authorization = struct {
1276 pub const max_password_len = 255;1270 pub const max_password_len = 255;
1277 pub const max_value_len = valueLength(max_user_len, max_password_len);1271 pub const max_value_len = valueLength(max_user_len, max_password_len);
12781272
1279 const prefix = "Basic ";
1280
1281 pub fn valueLength(user_len: usize, password_len: usize) usize {1273 pub fn valueLength(user_len: usize, password_len: usize) usize {
1282 return prefix.len + std.base64.standard.Encoder.calcSize(user_len + 1 + password_len);1274 return "Basic ".len + std.base64.standard.Encoder.calcSize(user_len + 1 + password_len);
1283 }1275 }
12841276
1285 pub fn valueLengthFromUri(uri: Uri) usize {1277 pub fn valueLengthFromUri(uri: Uri) usize {
...@@ -1290,6 +1282,13 @@ pub const basic_authorization = struct {...@@ -1290,6 +1282,13 @@ pub const basic_authorization = struct {
1290 }1282 }
12911283
1292 pub fn value(uri: Uri, out: []u8) []u8 {1284 pub fn value(uri: Uri, out: []u8) []u8 {
1285 var bw: std.io.BufferedWriter = undefined;
1286 bw.initFixed(out);
1287 write(uri, &bw) catch unreachable;
1288 return bw.getWritten();
1289 }
1290
1291 pub fn write(uri: Uri, out: *std.io.BufferedWriter) anyerror!void {
1293 var buf: [max_user_len + ":".len + max_password_len]u8 = undefined;1292 var buf: [max_user_len + ":".len + max_password_len]u8 = undefined;
1294 var bw: std.io.BufferedWriter = undefined;1293 var bw: std.io.BufferedWriter = undefined;
1295 bw.initFixed(&buf);1294 bw.initFixed(&buf);
...@@ -1297,9 +1296,7 @@ pub const basic_authorization = struct {...@@ -1297,9 +1296,7 @@ pub const basic_authorization = struct {
1297 uri.user orelse Uri.Component.empty,1296 uri.user orelse Uri.Component.empty,
1298 uri.password orelse Uri.Component.empty,1297 uri.password orelse Uri.Component.empty,
1299 }) catch unreachable;1298 }) catch unreachable;
1300 @memcpy(out[0..prefix.len], prefix);1299 try out.print("Basic {b64}", .{bw.getWritten()});
1301 const base64 = std.base64.standard.Encoder.encode(out[prefix.len..], bw.getWritten());
1302 return out[0 .. prefix.len + base64.len];
1303 }1300 }
1304};1301};
13051302
...@@ -1313,7 +1310,7 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1313,7 +1310,7 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1313 .host = host,1310 .host = host,
1314 .port = port,1311 .port = port,
1315 .protocol = protocol,1312 .protocol = protocol,
1316 })) |node| return node;1313 })) |conn| return conn;
13171314
1318 if (disable_tls and protocol == .tls)1315 if (disable_tls and protocol == .tls)
1319 return error.TlsInitializationFailed;1316 return error.TlsInitializationFailed;
...@@ -1336,7 +1333,12 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1336,7 +1333,12 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
13361333
1337 conn.* = .{1334 conn.* = .{
1338 .stream = stream,1335 .stream = stream,
1336 .stream_writer = undefined,
1339 .tls_client = undefined,1337 .tls_client = undefined,
1338 .read_buf = undefined,
1339
1340 .write_buffer = undefined,
1341 .writer = undefined, // populated below
13401342
1341 .protocol = protocol,1343 .protocol = protocol,
1342 .host = try client.allocator.dupe(u8, host),1344 .host = try client.allocator.dupe(u8, host),
...@@ -1346,36 +1348,55 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1346,36 +1348,55 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1346 };1348 };
1347 errdefer client.allocator.free(conn.host);1349 errdefer client.allocator.free(conn.host);
13481350
1349 if (protocol == .tls) {1351 switch (protocol) {
1350 if (disable_tls) unreachable;1352 .tls => {
1353 if (disable_tls) unreachable;
13511354
1352 conn.tls_client = try client.allocator.create(std.crypto.tls.Client);1355 const tls_client = try client.allocator.create(std.crypto.tls.Client);
1353 errdefer client.allocator.destroy(conn.tls_client);1356 errdefer client.allocator.destroy(tls_client);
1357
1358 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {
1359 const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {
1360 error.EnvironmentVariableNotFound, error.InvalidWtf8 => break :ssl_key_log_file null,
1361 error.OutOfMemory => return error.OutOfMemory,
1362 };
1363 defer client.allocator.free(ssl_key_log_path);
1364 break :ssl_key_log_file std.fs.cwd().createFile(ssl_key_log_path, .{
1365 .truncate = false,
1366 .mode = switch (builtin.os.tag) {
1367 .windows, .wasi => 0,
1368 else => 0o600,
1369 },
1370 }) catch null;
1371 } else null;
1372 errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();
1373
1374 conn.stream_writer = .{
1375 .unbuffered_writer = stream.writer(),
1376 .buffer = &.{},
1377 };
13541378
1355 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {1379 tls_client.* = std.crypto.tls.Client.init(stream, &conn.stream_writer, .{
1356 const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {1380 .host = .{ .explicit = host },
1357 error.EnvironmentVariableNotFound, error.InvalidWtf8 => break :ssl_key_log_file null,1381 .ca = .{ .bundle = client.ca_bundle },
1358 error.OutOfMemory => return error.OutOfMemory,1382 .ssl_key_log_file = ssl_key_log_file,
1383 }) catch return error.TlsInitializationFailed;
1384 // This is appropriate for HTTPS because the HTTP headers contain
1385 // the content length which is used to detect truncation attacks.
1386 tls_client.allow_truncation_attacks = true;
1387
1388 conn.writer = .{
1389 .unbuffered_writer = tls_client.writer(),
1390 .buffer = &conn.write_buffer,
1359 };1391 };
1360 defer client.allocator.free(ssl_key_log_path);1392 conn.tls_client = tls_client;
1361 break :ssl_key_log_file std.fs.cwd().createFile(ssl_key_log_path, .{1393 },
1362 .truncate = false,1394 .plain => {
1363 .mode = switch (builtin.os.tag) {1395 conn.writer = .{
1364 .windows, .wasi => 0,1396 .unbuffered_writer = stream.writer(),
1365 else => 0o600,1397 .buffer = &conn.write_buffer,
1366 },1398 };
1367 }) catch null;1399 },
1368 } else null;
1369 errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();
1370
1371 conn.tls_client.* = std.crypto.tls.Client.init(stream, .{
1372 .host = .{ .explicit = host },
1373 .ca = .{ .bundle = client.ca_bundle },
1374 .ssl_key_log_file = ssl_key_log_file,
1375 }) catch return error.TlsInitializationFailed;
1376 // This is appropriate for HTTPS because the HTTP headers contain
1377 // the content length which is used to detect truncation attacks.
1378 conn.tls_client.allow_truncation_attacks = true;
1379 }1400 }
13801401
1381 client.connection_pool.addUsed(conn);1402 client.connection_pool.addUsed(conn);
...@@ -1534,14 +1555,14 @@ pub fn connect(...@@ -1534,14 +1555,14 @@ pub fn connect(
1534 return conn;1555 return conn;
1535}1556}
15361557
1537pub const RequestError = ConnectTcpError || ConnectErrorPartial || Request.SendError ||1558/// TODO collapse each error set into its own meta error code, and store
1559/// the underlying error code as a field on Request
1560pub const RequestError = ConnectTcpError || ConnectErrorPartial || anyerror ||
1538 std.fmt.ParseIntError || Connection.WriteError ||1561 std.fmt.ParseIntError || Connection.WriteError ||
1539 error{1562 error{
1540 UnsupportedUriScheme,1563 UnsupportedUriScheme,
1541 UriMissingHost,1564 UriMissingHost,
1542
1543 CertificateBundleLoadFailure,1565 CertificateBundleLoadFailure,
1544 UnsupportedTransferEncoding,
1545 };1566 };
15461567
1547pub const RequestOptions = struct {1568pub const RequestOptions = struct {
...@@ -1754,7 +1775,10 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {...@@ -1754,7 +1775,10 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {
17541775
1755 try req.send();1776 try req.send();
17561777
1757 if (options.payload) |payload| try req.writeAll(payload);1778 if (options.payload) |payload| {
1779 var w = req.writer().unbuffered();
1780 try w.writeAll(payload);
1781 }
17581782
1759 try req.finish();1783 try req.finish();
1760 try req.wait();1784 try req.wait();
lib/std/http/Server.zig+70-138
...@@ -113,6 +113,7 @@ fn finishReceivingHead(s: *Server, head_end: usize) ReceiveHeadError!Request {...@@ -113,6 +113,7 @@ fn finishReceivingHead(s: *Server, head_end: usize) ReceiveHeadError!Request {
113 .head = Request.Head.parse(s.read_buffer[0..head_end]) catch113 .head = Request.Head.parse(s.read_buffer[0..head_end]) catch
114 return error.HttpHeadersInvalid,114 return error.HttpHeadersInvalid,
115 .reader_state = undefined,115 .reader_state = undefined,
116 .write_error = undefined,
116 };117 };
117}118}
118119
...@@ -125,6 +126,8 @@ pub const Request = struct {...@@ -125,6 +126,8 @@ pub const Request = struct {
125 remaining_content_length: u64,126 remaining_content_length: u64,
126 chunk_parser: http.ChunkParser,127 chunk_parser: http.ChunkParser,
127 },128 },
129 /// Populated when `error.HttpContinueWriteFailed` is received.
130 write_error: anyerror,
128131
129 pub const Compression = union(enum) {132 pub const Compression = union(enum) {
130 pub const DeflateDecompressor = std.compress.zlib.Decompressor(std.io.AnyReader);133 pub const DeflateDecompressor = std.compress.zlib.Decompressor(std.io.AnyReader);
...@@ -327,6 +330,7 @@ pub const Request = struct {...@@ -327,6 +330,7 @@ pub const Request = struct {
327 .head_end = request_bytes.len,330 .head_end = request_bytes.len,
328 .head = undefined,331 .head = undefined,
329 .reader_state = undefined,332 .reader_state = undefined,
333 .write_error = undefined,
330 };334 };
331335
332 var it = request.iterateHeaders();336 var it = request.iterateHeaders();
...@@ -391,7 +395,7 @@ pub const Request = struct {...@@ -391,7 +395,7 @@ pub const Request = struct {
391 request: *Request,395 request: *Request,
392 content: []const u8,396 content: []const u8,
393 options: RespondOptions,397 options: RespondOptions,
394 ) Response.WriteError!void {398 ) anyerror!void {
395 const max_extra_headers = 25;399 const max_extra_headers = 25;
396 assert(options.status != .@"continue");400 assert(options.status != .@"continue");
397 assert(options.extra_headers.len <= max_extra_headers);401 assert(options.extra_headers.len <= max_extra_headers);
...@@ -418,7 +422,8 @@ pub const Request = struct {...@@ -418,7 +422,8 @@ pub const Request = struct {
418 h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");422 h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");
419 if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n");423 if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n");
420 h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");424 h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");
421 try request.server.connection.stream.writeAll(h.items);425 var w = request.server.connection.stream.writer().unbuffered();
426 try w.writeAll(h.items);
422 return;427 return;
423 }428 }
424 h.printAssumeCapacity("{s} {d} {s}\r\n", .{429 h.printAssumeCapacity("{s} {d} {s}\r\n", .{
...@@ -438,47 +443,29 @@ pub const Request = struct {...@@ -438,47 +443,29 @@ pub const Request = struct {
438 }443 }
439444
440 var chunk_header_buffer: [18]u8 = undefined;445 var chunk_header_buffer: [18]u8 = undefined;
441 var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined;446 var iovecs: [max_extra_headers * 4 + 3][]const u8 = undefined;
442 var iovecs_len: usize = 0;447 var iovecs_len: usize = 0;
443448
444 iovecs[iovecs_len] = .{449 iovecs[iovecs_len] = h.items;
445 .base = h.items.ptr,
446 .len = h.items.len,
447 };
448 iovecs_len += 1;450 iovecs_len += 1;
449451
450 for (options.extra_headers) |header| {452 for (options.extra_headers) |header| {
451 iovecs[iovecs_len] = .{453 iovecs[iovecs_len] = header.name;
452 .base = header.name.ptr,
453 .len = header.name.len,
454 };
455 iovecs_len += 1;454 iovecs_len += 1;
456455
457 iovecs[iovecs_len] = .{456 iovecs[iovecs_len] = ": ";
458 .base = ": ",
459 .len = 2,
460 };
461 iovecs_len += 1;457 iovecs_len += 1;
462458
463 if (header.value.len != 0) {459 if (header.value.len != 0) {
464 iovecs[iovecs_len] = .{460 iovecs[iovecs_len] = header.value;
465 .base = header.value.ptr,
466 .len = header.value.len,
467 };
468 iovecs_len += 1;461 iovecs_len += 1;
469 }462 }
470463
471 iovecs[iovecs_len] = .{464 iovecs[iovecs_len] = "\r\n";
472 .base = "\r\n",
473 .len = 2,
474 };
475 iovecs_len += 1;465 iovecs_len += 1;
476 }466 }
477467
478 iovecs[iovecs_len] = .{468 iovecs[iovecs_len] = "\r\n";
479 .base = "\r\n",
480 .len = 2,
481 };
482 iovecs_len += 1;469 iovecs_len += 1;
483470
484 if (request.head.method != .HEAD) {471 if (request.head.method != .HEAD) {
...@@ -491,40 +478,26 @@ pub const Request = struct {...@@ -491,40 +478,26 @@ pub const Request = struct {
491 .{content.len},478 .{content.len},
492 ) catch unreachable;479 ) catch unreachable;
493480
494 iovecs[iovecs_len] = .{481 iovecs[iovecs_len] = chunk_header;
495 .base = chunk_header.ptr,
496 .len = chunk_header.len,
497 };
498 iovecs_len += 1;482 iovecs_len += 1;
499483
500 iovecs[iovecs_len] = .{484 iovecs[iovecs_len] = content;
501 .base = content.ptr,
502 .len = content.len,
503 };
504 iovecs_len += 1;485 iovecs_len += 1;
505486
506 iovecs[iovecs_len] = .{487 iovecs[iovecs_len] = "\r\n";
507 .base = "\r\n",
508 .len = 2,
509 };
510 iovecs_len += 1;488 iovecs_len += 1;
511 }489 }
512490
513 iovecs[iovecs_len] = .{491 iovecs[iovecs_len] = "0\r\n\r\n";
514 .base = "0\r\n\r\n",
515 .len = 5,
516 };
517 iovecs_len += 1;492 iovecs_len += 1;
518 } else if (content.len > 0) {493 } else if (content.len > 0) {
519 iovecs[iovecs_len] = .{494 iovecs[iovecs_len] = content;
520 .base = content.ptr,
521 .len = content.len,
522 };
523 iovecs_len += 1;495 iovecs_len += 1;
524 }496 }
525 }497 }
526498
527 try request.server.connection.stream.writevAll(iovecs[0..iovecs_len]);499 var w = request.server.connection.stream.writer().unbuffered();
500 try w.writevAll(iovecs[0..iovecs_len]);
528 }501 }
529502
530 pub const RespondStreamingOptions = struct {503 pub const RespondStreamingOptions = struct {
...@@ -740,7 +713,10 @@ pub const Request = struct {...@@ -740,7 +713,10 @@ pub const Request = struct {
740 return out_end;713 return out_end;
741 }714 }
742715
743 pub const ReaderError = Response.WriteError || error{716 pub const ReaderError = error{
717 /// Failed to write "100-continue" to the stream. Error value is
718 /// stored in `Request.write_error`.
719 HttpContinueWriteFailed,
744 /// The client sent an expect HTTP header value other than720 /// The client sent an expect HTTP header value other than
745 /// "100-continue".721 /// "100-continue".
746 HttpExpectationFailed,722 HttpExpectationFailed,
...@@ -760,7 +736,11 @@ pub const Request = struct {...@@ -760,7 +736,11 @@ pub const Request = struct {
760736
761 if (request.head.expect) |expect| {737 if (request.head.expect) |expect| {
762 if (mem.eql(u8, expect, "100-continue")) {738 if (mem.eql(u8, expect, "100-continue")) {
763 try request.server.connection.stream.writeAll("HTTP/1.1 100 Continue\r\n\r\n");739 var w = request.server.connection.stream.writer().unbuffered();
740 w.writeAll("HTTP/1.1 100 Continue\r\n\r\n") catch |err| {
741 request.write_error = err;
742 return error.HttpContinueWriteFailed;
743 };
764 request.head.expect = null;744 request.head.expect = null;
765 } else {745 } else {
766 return error.HttpExpectationFailed;746 return error.HttpExpectationFailed;
...@@ -845,14 +825,12 @@ pub const Response = struct {...@@ -845,14 +825,12 @@ pub const Response = struct {
845 chunked,825 chunked,
846 };826 };
847827
848 pub const WriteError = net.Stream.WriteError;
849
850 /// When using content-length, asserts that the amount of data sent matches828 /// When using content-length, asserts that the amount of data sent matches
851 /// the value sent in the header, then calls `flush`.829 /// the value sent in the header, then calls `flush`.
852 /// Otherwise, transfer-encoding: chunked is being used, and it writes the830 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
853 /// end-of-stream message, then flushes the stream to the system.831 /// end-of-stream message, then flushes the stream to the system.
854 /// Respects the value of `elide_body` to omit all data after the headers.832 /// Respects the value of `elide_body` to omit all data after the headers.
855 pub fn end(r: *Response) WriteError!void {833 pub fn end(r: *Response) anyerror!void {
856 switch (r.transfer_encoding) {834 switch (r.transfer_encoding) {
857 .content_length => |len| {835 .content_length => |len| {
858 assert(len == 0); // Trips when end() called before all bytes written.836 assert(len == 0); // Trips when end() called before all bytes written.
...@@ -877,7 +855,7 @@ pub const Response = struct {...@@ -877,7 +855,7 @@ pub const Response = struct {
877 /// flushes the stream to the system.855 /// flushes the stream to the system.
878 /// Respects the value of `elide_body` to omit all data after the headers.856 /// Respects the value of `elide_body` to omit all data after the headers.
879 /// Asserts there are at most 25 trailers.857 /// Asserts there are at most 25 trailers.
880 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {858 pub fn endChunked(r: *Response, options: EndChunkedOptions) anyerror!void {
881 assert(r.transfer_encoding == .chunked);859 assert(r.transfer_encoding == .chunked);
882 try flush_chunked(r, options.trailers);860 try flush_chunked(r, options.trailers);
883 r.* = undefined;861 r.* = undefined;
...@@ -887,7 +865,7 @@ pub const Response = struct {...@@ -887,7 +865,7 @@ pub const Response = struct {
887 /// would not exceed the content-length value sent in the HTTP header.865 /// would not exceed the content-length value sent in the HTTP header.
888 /// May return 0, which does not indicate end of stream. The caller decides866 /// May return 0, which does not indicate end of stream. The caller decides
889 /// when the end of stream occurs by calling `end`.867 /// when the end of stream occurs by calling `end`.
890 pub fn write(r: *Response, bytes: []const u8) WriteError!usize {868 pub fn write(r: *Response, bytes: []const u8) anyerror!usize {
891 switch (r.transfer_encoding) {869 switch (r.transfer_encoding) {
892 .content_length, .none => return @errorCast(cl_writeSplat(r, &.{bytes}, 1)),870 .content_length, .none => return @errorCast(cl_writeSplat(r, &.{bytes}, 1)),
893 .chunked => return @errorCast(chunked_writeSplat(r, &.{bytes}, 1)),871 .chunked => return @errorCast(chunked_writeSplat(r, &.{bytes}, 1)),
...@@ -916,7 +894,7 @@ pub const Response = struct {...@@ -916,7 +894,7 @@ pub const Response = struct {
916 return error.Unimplemented;894 return error.Unimplemented;
917 }895 }
918896
919 fn cl_write(context: *anyopaque, bytes: []const u8) WriteError!usize {897 fn cl_write(context: *anyopaque, bytes: []const u8) anyerror!usize {
920 const r: *Response = @constCast(@alignCast(@ptrCast(context)));898 const r: *Response = @constCast(@alignCast(@ptrCast(context)));
921899
922 var trash: u64 = std.math.maxInt(u64);900 var trash: u64 = std.math.maxInt(u64);
...@@ -932,17 +910,12 @@ pub const Response = struct {...@@ -932,17 +910,12 @@ pub const Response = struct {
932910
933 if (bytes.len + r.send_buffer_end > r.send_buffer.len) {911 if (bytes.len + r.send_buffer_end > r.send_buffer.len) {
934 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;912 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
935 var iovecs: [2]std.posix.iovec_const = .{913 var iovecs: [2][]const u8 = .{
936 .{914 r.send_buffer[r.send_buffer_start..][0..send_buffer_len],
937 .base = r.send_buffer.ptr + r.send_buffer_start,915 bytes,
938 .len = send_buffer_len,
939 },
940 .{
941 .base = bytes.ptr,
942 .len = bytes.len,
943 },
944 };916 };
945 const n = try r.stream.writev(&iovecs);917 var w = r.stream.writer().unbuffered();
918 const n = try w.writev(&iovecs);
946919
947 if (n >= send_buffer_len) {920 if (n >= send_buffer_len) {
948 // It was enough to reset the buffer.921 // It was enough to reset the buffer.
...@@ -985,10 +958,10 @@ pub const Response = struct {...@@ -985,10 +958,10 @@ pub const Response = struct {
985 _ = len;958 _ = len;
986 _ = headers_and_trailers;959 _ = headers_and_trailers;
987 _ = headers_len;960 _ = headers_len;
988 return error.Unimplemented;961 return error.Unimplemented; // TODO lower to a call to writeFile on the output
989 }962 }
990963
991 fn chunked_write(context: *anyopaque, bytes: []const u8) WriteError!usize {964 fn chunked_write(context: *anyopaque, bytes: []const u8) anyerror!usize {
992 const r: *Response = @constCast(@alignCast(@ptrCast(context)));965 const r: *Response = @constCast(@alignCast(@ptrCast(context)));
993 assert(r.transfer_encoding == .chunked);966 assert(r.transfer_encoding == .chunked);
994967
...@@ -1001,31 +974,17 @@ pub const Response = struct {...@@ -1001,31 +974,17 @@ pub const Response = struct {
1001 var header_buf: [18]u8 = undefined;974 var header_buf: [18]u8 = undefined;
1002 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{chunk_len}) catch unreachable;975 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{chunk_len}) catch unreachable;
1003976
1004 var iovecs: [5]std.posix.iovec_const = .{977 var iovecs: [5][]const u8 = .{
1005 .{978 r.send_buffer[r.send_buffer_start .. send_buffer_len - r.chunk_len],
1006 .base = r.send_buffer.ptr + r.send_buffer_start,979 chunk_header,
1007 .len = send_buffer_len - r.chunk_len,980 r.send_buffer[r.send_buffer_end - r.chunk_len ..][0..r.chunk_len],
1008 },981 bytes,
1009 .{982 "\r\n",
1010 .base = chunk_header.ptr,
1011 .len = chunk_header.len,
1012 },
1013 .{
1014 .base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
1015 .len = r.chunk_len,
1016 },
1017 .{
1018 .base = bytes.ptr,
1019 .len = bytes.len,
1020 },
1021 .{
1022 .base = "\r\n",
1023 .len = 2,
1024 },
1025 };983 };
1026 // TODO make this writev instead of writevAll, which involves984 // TODO make this writev instead of writevAll, which involves
1027 // complicating the logic of this function.985 // complicating the logic of this function.
1028 try r.stream.writevAll(&iovecs);986 var w = r.stream.writer().unbuffered();
987 try w.writevAll(&iovecs);
1029 r.send_buffer_start = 0;988 r.send_buffer_start = 0;
1030 r.send_buffer_end = 0;989 r.send_buffer_end = 0;
1031 r.chunk_len = 0;990 r.chunk_len = 0;
...@@ -1041,7 +1000,7 @@ pub const Response = struct {...@@ -1041,7 +1000,7 @@ pub const Response = struct {
10411000
1042 /// If using content-length, asserts that writing these bytes to the client1001 /// If using content-length, asserts that writing these bytes to the client
1043 /// would not exceed the content-length value sent in the HTTP header.1002 /// would not exceed the content-length value sent in the HTTP header.
1044 pub fn writeAll(r: *Response, bytes: []const u8) WriteError!void {1003 pub fn writeAll(r: *Response, bytes: []const u8) anyerror!void {
1045 var index: usize = 0;1004 var index: usize = 0;
1046 while (index < bytes.len) {1005 while (index < bytes.len) {
1047 index += try write(r, bytes[index..]);1006 index += try write(r, bytes[index..]);
...@@ -1051,20 +1010,21 @@ pub const Response = struct {...@@ -1051,20 +1010,21 @@ pub const Response = struct {
1051 /// Sends all buffered data to the client.1010 /// Sends all buffered data to the client.
1052 /// This is redundant after calling `end`.1011 /// This is redundant after calling `end`.
1053 /// Respects the value of `elide_body` to omit all data after the headers.1012 /// Respects the value of `elide_body` to omit all data after the headers.
1054 pub fn flush(r: *Response) WriteError!void {1013 pub fn flush(r: *Response) anyerror!void {
1055 switch (r.transfer_encoding) {1014 switch (r.transfer_encoding) {
1056 .none, .content_length => return flush_cl(r),1015 .none, .content_length => return flush_cl(r),
1057 .chunked => return flush_chunked(r, null),1016 .chunked => return flush_chunked(r, null),
1058 }1017 }
1059 }1018 }
10601019
1061 fn flush_cl(r: *Response) WriteError!void {1020 fn flush_cl(r: *Response) anyerror!void {
1062 try r.stream.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]);1021 var w = r.stream.writer().unbuffered();
1022 try w.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]);
1063 r.send_buffer_start = 0;1023 r.send_buffer_start = 0;
1064 r.send_buffer_end = 0;1024 r.send_buffer_end = 0;
1065 }1025 }
10661026
1067 fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) WriteError!void {1027 fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) anyerror!void {
1068 const max_trailers = 25;1028 const max_trailers = 25;
1069 if (end_trailers) |trailers| assert(trailers.len <= max_trailers);1029 if (end_trailers) |trailers| assert(trailers.len <= max_trailers);
1070 assert(r.transfer_encoding == .chunked);1030 assert(r.transfer_encoding == .chunked);
...@@ -1072,7 +1032,8 @@ pub const Response = struct {...@@ -1072,7 +1032,8 @@ pub const Response = struct {
1072 const http_headers = r.send_buffer[r.send_buffer_start .. r.send_buffer_end - r.chunk_len];1032 const http_headers = r.send_buffer[r.send_buffer_start .. r.send_buffer_end - r.chunk_len];
10731033
1074 if (r.elide_body) {1034 if (r.elide_body) {
1075 try r.stream.writeAll(http_headers);1035 var w = r.stream.writer().unbuffered();
1036 try w.writeAll(http_headers);
1076 r.send_buffer_start = 0;1037 r.send_buffer_start = 0;
1077 r.send_buffer_end = 0;1038 r.send_buffer_end = 0;
1078 r.chunk_len = 0;1039 r.chunk_len = 0;
...@@ -1082,78 +1043,49 @@ pub const Response = struct {...@@ -1082,78 +1043,49 @@ pub const Response = struct {
1082 var header_buf: [18]u8 = undefined;1043 var header_buf: [18]u8 = undefined;
1083 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable;1044 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable;
10841045
1085 var iovecs: [max_trailers * 4 + 5]std.posix.iovec_const = undefined;1046 var iovecs: [max_trailers * 4 + 5][]const u8 = undefined;
1086 var iovecs_len: usize = 0;1047 var iovecs_len: usize = 0;
10871048
1088 iovecs[iovecs_len] = .{1049 iovecs[iovecs_len] = http_headers;
1089 .base = http_headers.ptr,
1090 .len = http_headers.len,
1091 };
1092 iovecs_len += 1;1050 iovecs_len += 1;
10931051
1094 if (r.chunk_len > 0) {1052 if (r.chunk_len > 0) {
1095 iovecs[iovecs_len] = .{1053 iovecs[iovecs_len] = chunk_header;
1096 .base = chunk_header.ptr,
1097 .len = chunk_header.len,
1098 };
1099 iovecs_len += 1;1054 iovecs_len += 1;
11001055
1101 iovecs[iovecs_len] = .{1056 iovecs[iovecs_len] = r.send_buffer[r.send_buffer_end - r.chunk_len ..][0..r.chunk_len];
1102 .base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
1103 .len = r.chunk_len,
1104 };
1105 iovecs_len += 1;1057 iovecs_len += 1;
11061058
1107 iovecs[iovecs_len] = .{1059 iovecs[iovecs_len] = "\r\n";
1108 .base = "\r\n",
1109 .len = 2,
1110 };
1111 iovecs_len += 1;1060 iovecs_len += 1;
1112 }1061 }
11131062
1114 if (end_trailers) |trailers| {1063 if (end_trailers) |trailers| {
1115 iovecs[iovecs_len] = .{1064 iovecs[iovecs_len] = "0\r\n";
1116 .base = "0\r\n",
1117 .len = 3,
1118 };
1119 iovecs_len += 1;1065 iovecs_len += 1;
11201066
1121 for (trailers) |trailer| {1067 for (trailers) |trailer| {
1122 iovecs[iovecs_len] = .{1068 iovecs[iovecs_len] = trailer.name;
1123 .base = trailer.name.ptr,
1124 .len = trailer.name.len,
1125 };
1126 iovecs_len += 1;1069 iovecs_len += 1;
11271070
1128 iovecs[iovecs_len] = .{1071 iovecs[iovecs_len] = ": ";
1129 .base = ": ",
1130 .len = 2,
1131 };
1132 iovecs_len += 1;1072 iovecs_len += 1;
11331073
1134 if (trailer.value.len != 0) {1074 if (trailer.value.len != 0) {
1135 iovecs[iovecs_len] = .{1075 iovecs[iovecs_len] = trailer.value;
1136 .base = trailer.value.ptr,
1137 .len = trailer.value.len,
1138 };
1139 iovecs_len += 1;1076 iovecs_len += 1;
1140 }1077 }
11411078
1142 iovecs[iovecs_len] = .{1079 iovecs[iovecs_len] = "\r\n";
1143 .base = "\r\n",
1144 .len = 2,
1145 };
1146 iovecs_len += 1;1080 iovecs_len += 1;
1147 }1081 }
11481082
1149 iovecs[iovecs_len] = .{1083 iovecs[iovecs_len] = "\r\n";
1150 .base = "\r\n",
1151 .len = 2,
1152 };
1153 iovecs_len += 1;1084 iovecs_len += 1;
1154 }1085 }
11551086
1156 try r.stream.writevAll(iovecs[0..iovecs_len]);1087 var w = r.stream.writer().unbuffered();
1088 try w.writevAll(iovecs[0..iovecs_len]);
1157 r.send_buffer_start = 0;1089 r.send_buffer_start = 0;
1158 r.send_buffer_end = 0;1090 r.send_buffer_end = 0;
1159 r.chunk_len = 0;1091 r.chunk_len = 0;
lib/std/http/protocol.zig+5-20
...@@ -290,7 +290,7 @@ inline fn intShift(comptime T: type, x: anytype) T {...@@ -290,7 +290,7 @@ inline fn intShift(comptime T: type, x: anytype) T {
290const MockBufferedConnection = struct {290const MockBufferedConnection = struct {
291 pub const buffer_size = 0x2000;291 pub const buffer_size = 0x2000;
292292
293 conn: std.io.FixedBufferStream([]const u8),293 conn: std.io.FixedBufferStream,
294 buf: [buffer_size]u8 = undefined,294 buf: [buffer_size]u8 = undefined,
295 start: u16 = 0,295 start: u16 = 0,
296 end: u16 = 0,296 end: u16 = 0,
...@@ -343,27 +343,12 @@ const MockBufferedConnection = struct {...@@ -343,27 +343,12 @@ const MockBufferedConnection = struct {
343 return conn.readAtLeast(buffer, 1);343 return conn.readAtLeast(buffer, 1);
344 }344 }
345345
346 pub const ReadError = std.io.FixedBufferStream([]const u8).ReadError || error{EndOfStream};346 pub const ReadError = std.io.FixedBufferStream.ReadError || error{EndOfStream};
347 pub const Reader = std.io.Reader(*MockBufferedConnection, ReadError, read);347 pub const Reader = std.io.Reader(*MockBufferedConnection, ReadError, read);
348348
349 pub fn reader(conn: *MockBufferedConnection) Reader {349 pub fn reader(conn: *MockBufferedConnection) Reader {
350 return Reader{ .context = conn };350 return Reader{ .context = conn };
351 }351 }
352
353 pub fn writeAll(conn: *MockBufferedConnection, buffer: []const u8) WriteError!void {
354 return conn.conn.writeAll(buffer);
355 }
356
357 pub fn write(conn: *MockBufferedConnection, buffer: []const u8) WriteError!usize {
358 return conn.conn.write(buffer);
359 }
360
361 pub const WriteError = std.io.FixedBufferStream([]const u8).WriteError;
362 pub const Writer = std.io.Writer(*MockBufferedConnection, WriteError, write);
363
364 pub fn writer(conn: *MockBufferedConnection) Writer {
365 return Writer{ .context = conn };
366 }
367};352};
368353
369test "HeadersParser.read length" {354test "HeadersParser.read length" {
...@@ -374,7 +359,7 @@ test "HeadersParser.read length" {...@@ -374,7 +359,7 @@ test "HeadersParser.read length" {
374 const data = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nHello";359 const data = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nHello";
375360
376 var conn: MockBufferedConnection = .{361 var conn: MockBufferedConnection = .{
377 .conn = std.io.fixedBufferStream(data),362 .conn = .{ .buffer = data },
378 };363 };
379364
380 while (true) { // read headers365 while (true) { // read headers
...@@ -404,7 +389,7 @@ test "HeadersParser.read chunked" {...@@ -404,7 +389,7 @@ test "HeadersParser.read chunked" {
404 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\n\r\n";389 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\n\r\n";
405390
406 var conn: MockBufferedConnection = .{391 var conn: MockBufferedConnection = .{
407 .conn = std.io.fixedBufferStream(data),392 .conn = .{ .buffer = data },
408 };393 };
409394
410 while (true) { // read headers395 while (true) { // read headers
...@@ -433,7 +418,7 @@ test "HeadersParser.read chunked trailer" {...@@ -433,7 +418,7 @@ test "HeadersParser.read chunked trailer" {
433 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\nContent-Type: text/plain\r\n\r\n";418 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\nContent-Type: text/plain\r\n\r\n";
434419
435 var conn: MockBufferedConnection = .{420 var conn: MockBufferedConnection = .{
436 .conn = std.io.fixedBufferStream(data),421 .conn = .{ .buffer = data },
437 };422 };
438423
439 while (true) { // read headers424 while (true) { // read headers
lib/std/http/test.zig+18-11
...@@ -135,7 +135,8 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -135,7 +135,8 @@ test "HTTP server handles a chunked transfer coding request" {
135 const gpa = std.testing.allocator;135 const gpa = std.testing.allocator;
136 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());136 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
137 defer stream.close();137 defer stream.close();
138 try stream.writeAll(request_bytes);138 var writer = stream.writer().unbuffered();
139 try writer.writeAll(request_bytes);
139140
140 const expected_response =141 const expected_response =
141 "HTTP/1.1 200 OK\r\n" ++142 "HTTP/1.1 200 OK\r\n" ++
...@@ -276,7 +277,8 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -276,7 +277,8 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
276 const gpa = std.testing.allocator;277 const gpa = std.testing.allocator;
277 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());278 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
278 defer stream.close();279 defer stream.close();
279 try stream.writeAll(request_bytes);280 var writer = stream.writer().unbuffered();
281 try writer.writeAll(request_bytes);
280282
281 const response = try stream.reader().readAllAlloc(gpa, 8192);283 const response = try stream.reader().readAllAlloc(gpa, 8192);
282 defer gpa.free(response);284 defer gpa.free(response);
...@@ -339,7 +341,8 @@ test "receiving arbitrary http headers from the client" {...@@ -339,7 +341,8 @@ test "receiving arbitrary http headers from the client" {
339 const gpa = std.testing.allocator;341 const gpa = std.testing.allocator;
340 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());342 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
341 defer stream.close();343 defer stream.close();
342 try stream.writeAll(request_bytes);344 var writer = stream.writer().unbuffered();
345 try writer.writeAll(request_bytes);
343346
344 const response = try stream.reader().readAllAlloc(gpa, 8192);347 const response = try stream.reader().readAllAlloc(gpa, 8192);
345 defer gpa.free(response);348 defer gpa.free(response);
...@@ -960,8 +963,9 @@ test "Server streams both reading and writing" {...@@ -960,8 +963,9 @@ test "Server streams both reading and writing" {
960 try req.send();963 try req.send();
961 try req.wait();964 try req.wait();
962965
963 try req.writeAll("one ");966 var w = req.writer().unbuffered();
964 try req.writeAll("fish");967 try w.writeAll("one ");
968 try w.writeAll("fish");
965969
966 try req.finish();970 try req.finish();
967971
...@@ -992,8 +996,9 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -992,8 +996,9 @@ fn echoTests(client: *http.Client, port: u16) !void {
992 req.transfer_encoding = .{ .content_length = 14 };996 req.transfer_encoding = .{ .content_length = 14 };
993997
994 try req.send();998 try req.send();
995 try req.writeAll("Hello, ");999 var w = req.writer().unbuffered();
996 try req.writeAll("World!\n");1000 try w.writeAll("Hello, ");
1001 try w.writeAll("World!\n");
997 try req.finish();1002 try req.finish();
9981003
999 try req.wait();1004 try req.wait();
...@@ -1026,8 +1031,9 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -1026,8 +1031,9 @@ fn echoTests(client: *http.Client, port: u16) !void {
1026 req.transfer_encoding = .chunked;1031 req.transfer_encoding = .chunked;
10271032
1028 try req.send();1033 try req.send();
1029 try req.writeAll("Hello, ");1034 var w = req.writer().unbuffered();
1030 try req.writeAll("World!\n");1035 try w.writeAll("Hello, ");
1036 try w.writeAll("World!\n");
1031 try req.finish();1037 try req.finish();
10321038
1033 try req.wait();1039 try req.wait();
...@@ -1080,8 +1086,9 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -1080,8 +1086,9 @@ fn echoTests(client: *http.Client, port: u16) !void {
1080 req.transfer_encoding = .chunked;1086 req.transfer_encoding = .chunked;
10811087
1082 try req.send();1088 try req.send();
1083 try req.writeAll("Hello, ");1089 var w = req.writer().unbuffered();
1084 try req.writeAll("World!\n");1090 try w.writeAll("Hello, ");
1091 try w.writeAll("World!\n");
1085 try req.finish();1092 try req.finish();
10861093
1087 try req.wait();1094 try req.wait();
lib/std/net.zig+197-62
...@@ -849,9 +849,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {...@@ -849,9 +849,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
849 return Stream{ .handle = sockfd };849 return Stream{ .handle = sockfd };
850}850}
851851
852const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{852// TODO: Instead of having a massive error set, make the error set have categories, and then
853 // TODO: break this up into error sets from the various underlying functions853// store the sub-error as a diagnostic anyerror value.
854854const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || anyerror || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
855 TemporaryNameServerFailure,855 TemporaryNameServerFailure,
856 NameServerFailure,856 NameServerFailure,
857 AddressFamilyNotSupported,857 AddressFamilyNotSupported,
...@@ -1358,16 +1358,23 @@ fn linuxLookupNameFromHosts(...@@ -1358,16 +1358,23 @@ fn linuxLookupNameFromHosts(
13581358
1359 var buffered_reader = std.io.bufferedReader(file.reader());1359 var buffered_reader = std.io.bufferedReader(file.reader());
1360 const reader = buffered_reader.reader();1360 const reader = buffered_reader.reader();
1361 // TODO: rework buffered reader so that we can use its buffer directly when searching for delimiters
1361 var line_buf: [512]u8 = undefined;1362 var line_buf: [512]u8 = undefined;
1362 while (reader.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {1363 var line_buf_writer: std.io.BufferedWriter = undefined;
1363 error.StreamTooLong => blk: {1364 line_buf_writer.initFixed(&line_buf);
1364 // Skip to the delimiter in the reader, to fix parsing1365 while (true) {
1365 try reader.skipUntilDelimiterOrEof('\n');1366 const line = if (reader.streamUntilDelimiter(&line_buf_writer, '\n', line_buf.len)) |_| l: {
1366 // Use the truncated line. A truncated comment or hostname will be handled correctly.1367 break :l line_buf_writer.getWritten();
1367 break :blk &line_buf;1368 } else |err| switch (err) {
1368 },1369 error.EndOfStream => l: {
1369 else => |e| return e,1370 if (line_buf_writer.getWritten().len == 0) break;
1370 }) |line| {1371 // Skip to the delimiter in the reader, to fix parsing
1372 try reader.skipUntilDelimiterOrEof('\n');
1373 // Use the truncated line. A truncated comment or hostname will be handled correctly.
1374 break :l &line_buf;
1375 },
1376 else => |e| return e,
1377 };
1371 var split_it = mem.splitScalar(u8, line, '#');1378 var split_it = mem.splitScalar(u8, line, '#');
1372 const no_comment_line = split_it.first();1379 const no_comment_line = split_it.first();
13731380
...@@ -1559,16 +1566,23 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void {...@@ -1559,16 +1566,23 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void {
15591566
1560 var buf_reader = std.io.bufferedReader(file.reader());1567 var buf_reader = std.io.bufferedReader(file.reader());
1561 const stream = buf_reader.reader();1568 const stream = buf_reader.reader();
1569 // TODO: rework buffered reader so that we can use its buffer directly when searching for delimiters
1562 var line_buf: [512]u8 = undefined;1570 var line_buf: [512]u8 = undefined;
1563 while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {1571 var line_buf_writer: std.io.BufferedWriter = undefined;
1564 error.StreamTooLong => blk: {1572 line_buf_writer.initFixed(&line_buf);
1565 // Skip to the delimiter in the stream, to fix parsing1573 while (true) {
1566 try stream.skipUntilDelimiterOrEof('\n');1574 const line = if (stream.streamUntilDelimiter(&line_buf_writer, '\n', line_buf.len)) |_| l: {
1567 // Give an empty line to the while loop, which will be skipped.1575 break :l line_buf_writer.getWritten();
1568 break :blk line_buf[0..0];1576 } else |err| switch (err) {
1569 },1577 error.EndOfStream => l: {
1570 else => |e| return e,1578 if (line_buf_writer.getWritten().len == 0) break;
1571 }) |line| {1579 // Skip to the delimiter in the reader, to fix parsing
1580 try stream.skipUntilDelimiterOrEof('\n');
1581 // Give an empty line to the while loop, which will be skipped.
1582 break :l line_buf[0..0];
1583 },
1584 else => |e| return e,
1585 };
1572 const no_comment_line = no_comment_line: {1586 const no_comment_line = no_comment_line: {
1573 var split = mem.splitScalar(u8, line, '#');1587 var split = mem.splitScalar(u8, line, '#');
1574 break :no_comment_line split.first();1588 break :no_comment_line split.first();
...@@ -1833,7 +1847,9 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)...@@ -1833,7 +1847,9 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
1833pub const Stream = struct {1847pub const Stream = struct {
1834 /// Underlying platform-defined type which may or may not be1848 /// Underlying platform-defined type which may or may not be
1835 /// interchangeable with a file system file descriptor.1849 /// interchangeable with a file system file descriptor.
1836 handle: posix.socket_t,1850 handle: Handle,
1851
1852 pub const Handle = if (native_os == .windows) windows.ws2_32.SOCKET else posix.fd_t;
18371853
1838 pub fn close(s: Stream) void {1854 pub fn close(s: Stream) void {
1839 switch (native_os) {1855 switch (native_os) {
...@@ -1843,17 +1859,36 @@ pub const Stream = struct {...@@ -1843,17 +1859,36 @@ pub const Stream = struct {
1843 }1859 }
18441860
1845 pub const ReadError = posix.ReadError;1861 pub const ReadError = posix.ReadError;
1846 pub const WriteError = posix.WriteError;1862 pub const WriteError = posix.SendMsgError || error{
1863 ConnectionResetByPeer,
1864 SocketNotBound,
1865 MessageTooBig,
1866 NetworkSubsystemFailed,
1867 SystemResources,
1868 SocketNotConnected,
1869 Unexpected,
1870 };
18471871
1848 pub const Reader = io.Reader(Stream, ReadError, read);1872 pub const Reader = io.Reader(Stream, ReadError, read);
1849 pub const Writer = io.Writer(Stream, WriteError, write);
18501873
1851 pub fn reader(self: Stream) Reader {1874 pub fn reader(self: Stream) Reader {
1852 return .{ .context = self };1875 return .{ .context = self };
1853 }1876 }
18541877
1855 pub fn writer(self: Stream) Writer {1878 pub fn writer(stream: Stream) std.io.Writer {
1856 return .{ .context = self };1879 return .{
1880 .context = handleToOpaque(stream.handle),
1881 .vtable = switch (native_os) {
1882 .windows => &.{
1883 .writeSplat = windows_writeSplat,
1884 .writeFile = windows_writeFile,
1885 },
1886 else => &.{
1887 .writeSplat = posix_writeSplat,
1888 .writeFile = std.fs.File.writer_writeFile,
1889 },
1890 },
1891 };
1857 }1892 }
18581893
1859 pub fn read(self: Stream, buffer: []u8) ReadError!usize {1894 pub fn read(self: Stream, buffer: []u8) ReadError!usize {
...@@ -1898,48 +1933,148 @@ pub const Stream = struct {...@@ -1898,48 +1933,148 @@ pub const Stream = struct {
1898 return index;1933 return index;
1899 }1934 }
19001935
1901 /// TODO in evented I/O mode, this implementation incorrectly uses the event loop's1936 fn windows_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1902 /// file system thread instead of non-blocking. It needs to be reworked to properly1937 comptime assert(native_os == .windows);
1903 /// use non-blocking I/O.1938 if (data.len == 1 and splat == 0) return 0;
1904 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {1939 var splat_buffer: [256]u8 = undefined;
1905 if (native_os == .windows) {1940 var iovecs: [max_buffers_len]windows.WSABUF = undefined;
1906 return windows.WriteFile(self.handle, buffer, null);1941 var len: u32 = @min(iovecs.len, data.len);
1942 for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{
1943 .buf = if (d.len == 0) "" else d.ptr, // TODO: does Windows allow ptr=undefined len=0 ?
1944 .len = d.len,
1945 };
1946 switch (splat) {
1947 0 => len -= 1,
1948 1 => {},
1949 else => {
1950 const pattern = data[data.len - 1];
1951 if (pattern.len == 1) {
1952 const memset_len = @min(splat_buffer.len, splat);
1953 const buf = splat_buffer[0..memset_len];
1954 @memset(buf, pattern[0]);
1955 iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len };
1956 var remaining_splat = splat - buf.len;
1957 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
1958 iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len };
1959 remaining_splat -= splat_buffer.len;
1960 len += 1;
1961 }
1962 if (remaining_splat > 0 and len < iovecs.len) {
1963 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
1964 len += 1;
1965 }
1966 }
1967 },
1907 }1968 }
19081969 const handle = opaqueToHandle(context);
1909 return posix.write(self.handle, buffer);1970 var n: u32 = undefined;
1910 }1971 const rc = windows.ws2_32.WSASend(handle, &iovecs, len, &n, 0, null, null);
19111972 if (rc == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
1912 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {1973 .WSAECONNABORTED => return error.ConnectionResetByPeer,
1913 var index: usize = 0;1974 .WSAECONNRESET => return error.ConnectionResetByPeer,
1914 while (index < bytes.len) {1975 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
1915 index += try self.write(bytes[index..]);1976 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
1977 .WSAEINVAL => return error.SocketNotBound,
1978 .WSAEMSGSIZE => return error.MessageTooBig,
1979 .WSAENETDOWN => return error.NetworkSubsystemFailed,
1980 .WSAENETRESET => return error.ConnectionResetByPeer,
1981 .WSAENOBUFS => return error.SystemResources,
1982 .WSAENOTCONN => return error.SocketNotConnected,
1983 .WSAENOTSOCK => unreachable, // not a socket
1984 .WSAEOPNOTSUPP => unreachable, // only for message-oriented sockets
1985 .WSAESHUTDOWN => unreachable, // cannot send on a socket after write shutdown
1986 .WSAEWOULDBLOCK => return error.WouldBlock,
1987 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
1988 .WSA_IO_PENDING => unreachable, // not using overlapped I/O
1989 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
1990 else => |err| return windows.unexpectedWSAError(err),
1991 };
1992 return n;
1993 }
1994
1995 fn posix_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1996 const sock_fd = opaqueToHandle(context);
1997 comptime assert(native_os != .windows);
1998 var splat_buffer: [256]u8 = undefined;
1999 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
2000 var len: usize = @min(iovecs.len, data.len);
2001 for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{
2002 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
2003 .len = d.len,
2004 };
2005 var msg: posix.msghdr_const = .{
2006 .name = null,
2007 .namelen = 0,
2008 .iov = &iovecs,
2009 .iovlen = len,
2010 .control = null,
2011 .controllen = 0,
2012 .flags = 0,
2013 };
2014 switch (splat) {
2015 0 => msg.iovlen = len - 1,
2016 1 => {},
2017 else => {
2018 const pattern = data[data.len - 1];
2019 if (pattern.len == 1) {
2020 const memset_len = @min(splat_buffer.len, splat);
2021 const buf = splat_buffer[0..memset_len];
2022 @memset(buf, pattern[0]);
2023 iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len };
2024 var remaining_splat = splat - buf.len;
2025 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
2026 iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len };
2027 remaining_splat -= splat_buffer.len;
2028 len += 1;
2029 }
2030 if (remaining_splat > 0 and len < iovecs.len) {
2031 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
2032 len += 1;
2033 }
2034 msg.iovlen = len;
2035 }
2036 },
1916 }2037 }
2038 const flags = posix.MSG.NOSIGNAL;
2039 return std.posix.sendmsg(sock_fd, &msg, flags);
2040 }
2041
2042 fn windows_writeFile(
2043 context: *anyopaque,
2044 in_file: std.fs.File,
2045 in_offset: u64,
2046 in_len: std.io.Writer.VTable.FileLen,
2047 headers_and_trailers: []const []const u8,
2048 headers_len: usize,
2049 ) anyerror!usize {
2050 const len_int = switch (in_len) {
2051 .zero => return windows_writeSplat(context, headers_and_trailers, 1),
2052 .entire_file => std.math.maxInt(usize),
2053 else => in_len.int(),
2054 };
2055 if (headers_len > 0) return windows_writeSplat(context, headers_and_trailers[0..headers_len], 1);
2056 var file_contents_buffer: [4096]u8 = undefined;
2057 const read_buffer = file_contents_buffer[0..@min(file_contents_buffer.len, len_int)];
2058 const n = try windows.ReadFile(in_file.handle, read_buffer, in_offset);
2059 return windows_writeSplat(context, &.{read_buffer[0..n]}, 1);
1917 }2060 }
19182061
1919 /// See https://github.com/ziglang/zig/issues/76992062 const max_buffers_len = 8;
1920 /// See equivalent function: `std.fs.File.writev`.
1921 pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize {
1922 return posix.writev(self.handle, iovecs);
1923 }
19242063
1925 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in2064 fn handleToOpaque(handle: Handle) *anyopaque {
1926 /// order to handle partial writes from the underlying OS layer.2065 return switch (@typeInfo(Handle)) {
1927 /// See https://github.com/ziglang/zig/issues/76992066 .pointer => @ptrCast(handle),
1928 /// See equivalent function: `std.fs.File.writevAll`.2067 .int => @ptrFromInt(@as(u32, @bitCast(handle))),
1929 pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void {2068 else => @compileError("unhandled"),
1930 if (iovecs.len == 0) return;2069 };
2070 }
19312071
1932 var i: usize = 0;2072 fn opaqueToHandle(userdata: *anyopaque) Handle {
1933 while (true) {2073 return switch (@typeInfo(Handle)) {
1934 var amt = try self.writev(iovecs[i..]);2074 .pointer => @ptrCast(userdata),
1935 while (amt >= iovecs[i].len) {2075 .int => @intCast(@intFromPtr(userdata)),
1936 amt -= iovecs[i].len;2076 else => @compileError("unhandled"),
1937 i += 1;2077 };
1938 if (i >= iovecs.len) return;
1939 }
1940 iovecs[i].base += amt;
1941 iovecs[i].len -= amt;
1942 }
1943 }2078 }
1944};2079};
19452080
lib/std/os/windows.zig-34
...@@ -1690,40 +1690,6 @@ pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.so...@@ -1690,40 +1690,6 @@ pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.so
1690 return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen)));1690 return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen)));
1691}1691}
16921692
1693pub fn sendmsg(
1694 s: ws2_32.SOCKET,
1695 msg: *ws2_32.WSAMSG_const,
1696 flags: u32,
1697) i32 {
1698 var bytes_send: DWORD = undefined;
1699 if (ws2_32.WSASendMsg(s, msg, flags, &bytes_send, null, null) == ws2_32.SOCKET_ERROR) {
1700 return ws2_32.SOCKET_ERROR;
1701 } else {
1702 return @as(i32, @as(u31, @intCast(bytes_send)));
1703 }
1704}
1705
1706pub fn sendto(s: ws2_32.SOCKET, buf: [*]const u8, len: usize, flags: u32, to: ?*const ws2_32.sockaddr, to_len: ws2_32.socklen_t) i32 {
1707 var buffer = ws2_32.WSABUF{ .len = @as(u31, @truncate(len)), .buf = @constCast(buf) };
1708 var bytes_send: DWORD = undefined;
1709 if (ws2_32.WSASendTo(s, @as([*]ws2_32.WSABUF, @ptrCast(&buffer)), 1, &bytes_send, flags, to, @as(i32, @intCast(to_len)), null, null) == ws2_32.SOCKET_ERROR) {
1710 return ws2_32.SOCKET_ERROR;
1711 } else {
1712 return @as(i32, @as(u31, @intCast(bytes_send)));
1713 }
1714}
1715
1716pub fn recvfrom(s: ws2_32.SOCKET, buf: [*]u8, len: usize, flags: u32, from: ?*ws2_32.sockaddr, from_len: ?*ws2_32.socklen_t) i32 {
1717 var buffer = ws2_32.WSABUF{ .len = @as(u31, @truncate(len)), .buf = buf };
1718 var bytes_received: DWORD = undefined;
1719 var flags_inout = flags;
1720 if (ws2_32.WSARecvFrom(s, @as([*]ws2_32.WSABUF, @ptrCast(&buffer)), 1, &bytes_received, &flags_inout, from, @as(?*i32, @ptrCast(from_len)), null, null) == ws2_32.SOCKET_ERROR) {
1721 return ws2_32.SOCKET_ERROR;
1722 } else {
1723 return @as(i32, @as(u31, @intCast(bytes_received)));
1724 }
1725}
1726
1727pub fn poll(fds: [*]ws2_32.pollfd, n: c_ulong, timeout: i32) i32 {1693pub fn poll(fds: [*]ws2_32.pollfd, n: c_ulong, timeout: i32) i32 {
1728 return ws2_32.WSAPoll(fds, n, timeout);1694 return ws2_32.WSAPoll(fds, n, timeout);
1729}1695}
lib/std/os/windows/ws2_32.zig+1-9
...@@ -1829,7 +1829,7 @@ pub extern "ws2_32" fn sendto(...@@ -1829,7 +1829,7 @@ pub extern "ws2_32" fn sendto(
1829 buf: [*]const u8,1829 buf: [*]const u8,
1830 len: i32,1830 len: i32,
1831 flags: i32,1831 flags: i32,
1832 to: *const sockaddr,1832 to: ?*const sockaddr,
1833 tolen: i32,1833 tolen: i32,
1834) callconv(.winapi) i32;1834) callconv(.winapi) i32;
18351835
...@@ -2116,14 +2116,6 @@ pub extern "ws2_32" fn WSASendMsg(...@@ -2116,14 +2116,6 @@ pub extern "ws2_32" fn WSASendMsg(
2116 lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,2116 lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,
2117) callconv(.winapi) i32;2117) callconv(.winapi) i32;
21182118
2119pub extern "ws2_32" fn WSARecvMsg(
2120 s: SOCKET,
2121 lpMsg: *WSAMSG,
2122 lpdwNumberOfBytesRecv: ?*u32,
2123 lpOverlapped: ?*OVERLAPPED,
2124 lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,
2125) callconv(.winapi) i32;
2126
2127pub extern "ws2_32" fn WSASendDisconnect(2119pub extern "ws2_32" fn WSASendDisconnect(
2128 s: SOCKET,2120 s: SOCKET,
2129 lpOutboundDisconnectData: ?*WSABUF,2121 lpOutboundDisconnectData: ?*WSABUF,