authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-05 20:27:09-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:48-07:00
logb428612a202a76f7a0aee18bde00c104753f3e60
tree93f059f67c5ddee228e5e2e18a81e6bde96b4b1b
parent774df26835069039ba739828a7619393de01a5f2

WIP: hack away at std.Io return flight


10 files changed, 339 insertions(+), 250 deletions(-)

lib/std/Io.zig+26-19
...@@ -719,31 +719,38 @@ pub const Timestamp = struct {...@@ -719,31 +719,38 @@ pub const Timestamp = struct {
719 ///719 ///
720 /// The epoch is implementation-defined. For example NTFS/Windows uses720 /// The epoch is implementation-defined. For example NTFS/Windows uses
721 /// 1601-01-01.721 /// 1601-01-01.
722 realtime,722 real,
723 /// A nonsettable system-wide clock that represents time since some723 /// A nonsettable system-wide clock that represents time since some
724 /// unspecified point in the past.724 /// unspecified point in the past.
725 ///725 ///
726 /// On Linux, corresponds to how long the system has been running since726 /// Monotonic: Guarantees that the time returned by consecutive calls
727 /// it booted.727 /// will not go backwards, but successive calls may return identical
728 /// (not-increased) time values.
728 ///729 ///
729 /// Not affected by discontinuous jumps in the system time (e.g., if730 /// Not affected by discontinuous jumps in the system time (e.g., if
730 /// the system administrator manually changes the clock), but is731 /// the system administrator manually changes the clock), but may be
731 /// affected by frequency adjustments. **This clock does not count time732 /// affected by frequency adjustments.
732 /// that the system is suspended.**
733 ///733 ///
734 /// Guarantees that the time returned by consecutive calls will not go734 /// This clock expresses intent to **exclude time that the system is
735 /// backwards, but successive calls may return identical735 /// suspended**. However, implementations may be unable to satisify
736 /// (not-increased) time values.736 /// this, and may include that time.
737 ///
738 /// * On Linux, corresponds `CLOCK_MONOTONIC`.
739 /// * On macOS, corresponds to `CLOCK_UPTIME_RAW`.
740 awake,
741 /// Identical to `awake` except it expresses intent to include time
742 /// that the system is suspended, however, it may be implemented
743 /// identically to `awake`.
737 ///744 ///
738 /// May or may not include time the system is suspended, but745 /// * On Linux, corresponds `CLOCK_BOOTTIME`.
739 /// implementations should exclude that time if possible.746 /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`.
740 monotonic,747 boot,
741 /// Identical to `monotonic` except it also includes any time that the748 /// Tracks the amount of CPU in user or kernel mode used by the calling
742 /// system is suspended, if possible. However, it may be implemented749 /// process.
743 /// identically to `monotonic`.750 cpu_process,
744 boottime,751 /// Tracks the amount of CPU in user or kernel mode used by the calling
745 process_cputime_id,752 /// thread.
746 thread_cputime_id,753 cpu_thread,
747 };754 };
748755
749 pub fn durationTo(from: Timestamp, to: Timestamp) Duration {756 pub fn durationTo(from: Timestamp, to: Timestamp) Duration {
...@@ -825,7 +832,7 @@ pub const Duration = struct {...@@ -825,7 +832,7 @@ pub const Duration = struct {
825 }832 }
826833
827 pub fn sleep(duration: Duration, io: Io) SleepError!void {834 pub fn sleep(duration: Duration, io: Io) SleepError!void {
828 return io.vtable.sleep(io.userdata, .{ .duration = .{ .duration = duration, .clock = .monotonic } });835 return io.vtable.sleep(io.userdata, .{ .duration = .{ .duration = duration, .clock = .awake } });
829 }836 }
830};837};
831838
lib/std/Io/File.zig+5
...@@ -319,6 +319,11 @@ pub const Reader = struct {...@@ -319,6 +319,11 @@ pub const Reader = struct {
319 };319 };
320 }320 }
321321
322 /// Takes a legacy `std.fs.File` to help with upgrading.
323 pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader {
324 return .init(.{ .handle = file.handle }, io, buffer);
325 }
326
322 pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {327 pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
323 return .{328 return .{
324 .io = io,329 .io = io,
lib/std/Io/Threaded.zig+23-12
...@@ -1032,7 +1032,7 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Err...@@ -1032,7 +1032,7 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Err
1032 // and uses the NTFS/Windows epoch, which is 1601-01-01.1032 // and uses the NTFS/Windows epoch, which is 1601-01-01.
1033 return @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100;1033 return @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100;
1034 },1034 },
1035 .monotonic, .boottime => {1035 .monotonic, .uptime => {
1036 // QPC on windows doesn't fail on >= XP/2000 and includes time suspended.1036 // QPC on windows doesn't fail on >= XP/2000 and includes time suspended.
1037 return .{ .timestamp = windows.QueryPerformanceCounter() };1037 return .{ .timestamp = windows.QueryPerformanceCounter() };
1038 },1038 },
...@@ -1132,7 +1132,8 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {...@@ -1132,7 +1132,8 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
1132 .sec = std.math.maxInt(sec_type),1132 .sec = std.math.maxInt(sec_type),
1133 .nsec = std.math.maxInt(nsec_type),1133 .nsec = std.math.maxInt(nsec_type),
1134 };1134 };
1135 if (d.clock != .monotonic) return error.UnsupportedClock;1135 // TODO check which clock nanosleep uses on this host
1136 // and return error.UnsupportedClock if it does not match
1136 const ns = d.duration.nanoseconds;1137 const ns = d.duration.nanoseconds;
1137 break :t .{1138 break :t .{
1138 .sec = @intCast(@divFloor(ns, std.time.ns_per_s)),1139 .sec = @intCast(@divFloor(ns, std.time.ns_per_s)),
...@@ -1331,11 +1332,15 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio...@@ -1331,11 +1332,15 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio
1331fn ipConnectPosix(1332fn ipConnectPosix(
1332 userdata: ?*anyopaque,1333 userdata: ?*anyopaque,
1333 address: *const Io.net.IpAddress,1334 address: *const Io.net.IpAddress,
1334 options: Io.net.IpAddress.BindOptions,1335 options: Io.net.IpAddress.ConnectOptions,
1335) Io.net.IpAddress.ConnectError!Io.net.Stream {1336) Io.net.IpAddress.ConnectError!Io.net.Stream {
1337 if (options.timeout != .none) @panic("TODO");
1336 const pool: *Pool = @ptrCast(@alignCast(userdata));1338 const pool: *Pool = @ptrCast(@alignCast(userdata));
1337 const family = posixAddressFamily(address);1339 const family = posixAddressFamily(address);
1338 const socket_fd = try openSocketPosix(pool, family, options);1340 const socket_fd = try openSocketPosix(pool, family, .{
1341 .mode = options.mode,
1342 .protocol = options.protocol,
1343 });
1339 var storage: PosixAddress = undefined;1344 var storage: PosixAddress = undefined;
1340 var addr_len = addressToPosix(address, &storage);1345 var addr_len = addressToPosix(address, &storage);
1341 try posixConnect(pool, socket_fd, &storage.any, addr_len);1346 try posixConnect(pool, socket_fd, &storage.any, addr_len);
...@@ -1490,11 +1495,11 @@ fn netSend(...@@ -1490,11 +1495,11 @@ fn netSend(
1490 const pool: *Pool = @ptrCast(@alignCast(userdata));1495 const pool: *Pool = @ptrCast(@alignCast(userdata));
14911496
1492 const posix_flags: u32 =1497 const posix_flags: u32 =
1493 @as(u32, if (flags.confirm) posix.MSG.CONFIRM else 0) |1498 @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) |
1494 @as(u32, if (flags.dont_route) posix.MSG.DONTROUTE else 0) |1499 @as(u32, if (flags.dont_route) posix.MSG.DONTROUTE else 0) |
1495 @as(u32, if (flags.eor) posix.MSG.EOR else 0) |1500 @as(u32, if (flags.eor) posix.MSG.EOR else 0) |
1496 @as(u32, if (flags.oob) posix.MSG.OOB else 0) |1501 @as(u32, if (flags.oob) posix.MSG.OOB else 0) |
1497 @as(u32, if (flags.fastopen) posix.MSG.FASTOPEN else 0) |1502 @as(u32, if (@hasDecl(posix.MSG, "FASTOPEN") and flags.fastopen) posix.MSG.FASTOPEN else 0) |
1498 posix.MSG.NOSIGNAL;1503 posix.MSG.NOSIGNAL;
14991504
1500 var i: usize = 0;1505 var i: usize = 0;
...@@ -2024,11 +2029,17 @@ fn recoverableOsBugDetected() void {...@@ -2024,11 +2029,17 @@ fn recoverableOsBugDetected() void {
20242029
2025fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t {2030fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t {
2026 return switch (clock) {2031 return switch (clock) {
2027 .realtime => posix.CLOCK.REALTIME,2032 .real => posix.CLOCK.REALTIME,
2028 .monotonic => posix.CLOCK.MONOTONIC,2033 .awake => switch (builtin.os.tag) {
2029 .boottime => posix.CLOCK.BOOTTIME,2034 .macos, .ios, .watchos, .tvos => posix.CLOCK.UPTIME_RAW,
2030 .process_cputime_id => posix.CLOCK.PROCESS_CPUTIME_ID,2035 else => posix.CLOCK.MONOTONIC,
2031 .thread_cputime_id => posix.CLOCK.THREAD_CPUTIME_ID,2036 },
2037 .boot => switch (builtin.os.tag) {
2038 .macos, .ios, .watchos, .tvos => posix.CLOCK.MONOTONIC_RAW,
2039 else => posix.CLOCK.BOOTTIME,
2040 },
2041 .cpu_process => posix.CLOCK.PROCESS_CPUTIME_ID,
2042 .cpu_thread => posix.CLOCK.THREAD_CPUTIME_ID,
2032 };2043 };
2033}2044}
20342045
...@@ -2036,7 +2047,7 @@ fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t {...@@ -2036,7 +2047,7 @@ fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t {
2036 return switch (clock) {2047 return switch (clock) {
2037 .realtime => .REALTIME,2048 .realtime => .REALTIME,
2038 .monotonic => .MONOTONIC,2049 .monotonic => .MONOTONIC,
2039 .boottime => .MONOTONIC,2050 .uptime => .MONOTONIC,
2040 .process_cputime_id => .PROCESS_CPUTIME_ID,2051 .process_cputime_id => .PROCESS_CPUTIME_ID,
2041 .thread_cputime_id => .THREAD_CPUTIME_ID,2052 .thread_cputime_id => .THREAD_CPUTIME_ID,
2042 };2053 };
lib/std/Io/net.zig+28-9
...@@ -186,7 +186,7 @@ pub const IpAddress = union(enum) {...@@ -186,7 +186,7 @@ pub const IpAddress = union(enum) {
186 /// Waits for a TCP connection. When using this API, `bind` does not need186 /// Waits for a TCP connection. When using this API, `bind` does not need
187 /// to be called. The returned `Server` has an open `stream`.187 /// to be called. The returned `Server` has an open `stream`.
188 pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server {188 pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server {
189 return io.vtable.tcpListen(io.userdata, address, options);189 return io.vtable.listen(io.userdata, address, options);
190 }190 }
191191
192 pub const BindError = error{192 pub const BindError = error{
...@@ -236,6 +236,8 @@ pub const IpAddress = union(enum) {...@@ -236,6 +236,8 @@ pub const IpAddress = union(enum) {
236 AddressInUse,236 AddressInUse,
237 AddressUnavailable,237 AddressUnavailable,
238 AddressFamilyUnsupported,238 AddressFamilyUnsupported,
239 /// Insufficient memory or other resource internal to the operating system.
240 SystemResources,
239 ConnectionPending,241 ConnectionPending,
240 ConnectionRefused,242 ConnectionRefused,
241 ConnectionResetByPeer,243 ConnectionResetByPeer,
...@@ -246,12 +248,23 @@ pub const IpAddress = union(enum) {...@@ -246,12 +248,23 @@ pub const IpAddress = union(enum) {
246 /// One of the `ConnectOptions` is not supported by the Io248 /// One of the `ConnectOptions` is not supported by the Io
247 /// implementation.249 /// implementation.
248 OptionUnsupported,250 OptionUnsupported,
249 } || Io.UnexpectedError || Io.Cancelable;251 /// Per-process limit on the number of open file descriptors has been reached.
252 ProcessFdQuotaExceeded,
253 /// System-wide limit on the total number of open files has been reached.
254 SystemFdQuotaExceeded,
255 ProtocolUnsupportedBySystem,
256 ProtocolUnsupportedByAddressFamily,
257 SocketModeUnsupported,
258 } || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;
250259
251 pub const ConnectOptions = BindOptions;260 pub const ConnectOptions = struct {
261 mode: Socket.Mode,
262 protocol: ?Protocol = null,
263 timeout: Io.Timeout = .none,
264 };
252265
253 /// Initiates a connection-oriented network stream.266 /// Initiates a connection-oriented network stream.
254 pub fn connect(address: IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream {267 pub fn connect(address: *const IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream {
255 return io.vtable.ipConnect(io.userdata, address, options);268 return io.vtable.ipConnect(io.userdata, address, options);
256 }269 }
257};270};
...@@ -997,7 +1010,7 @@ pub const Stream = struct {...@@ -997,7 +1010,7 @@ pub const Stream = struct {
997 socket: Socket,1010 socket: Socket,
9981011
999 pub fn close(s: *Stream, io: Io) void {1012 pub fn close(s: *Stream, io: Io) void {
1000 io.vtable.netClose(io.userdata, s.socket);1013 io.vtable.netClose(io.userdata, s.socket.handle);
1001 s.* = undefined;1014 s.* = undefined;
1002 }1015 }
10031016
...@@ -1040,10 +1053,13 @@ pub const Stream = struct {...@@ -1040,10 +1053,13 @@ pub const Stream = struct {
1040 return n;1053 return n;
1041 }1054 }
10421055
1043 fn readVec(io_r: *Reader, data: [][]u8) Io.Reader.Error!usize {1056 fn readVec(io_r: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
1044 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));1057 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
1045 const io = r.io;1058 const io = r.io;
1046 return io.vtable.netReadVec(io.vtable.userdata, r.stream, io_r, data);1059 return io.vtable.netRead(io.userdata, r.stream, data) catch |err| {
1060 r.err = err;
1061 return error.ReadFailed;
1062 };
1047 }1063 }
1048 };1064 };
10491065
...@@ -1078,7 +1094,10 @@ pub const Stream = struct {...@@ -1078,7 +1094,10 @@ pub const Stream = struct {
1078 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));1094 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
1079 const io = w.io;1095 const io = w.io;
1080 const buffered = io_w.buffered();1096 const buffered = io_w.buffered();
1081 const n = try io.vtable.netWrite(io.vtable.userdata, w.stream, buffered, data, splat);1097 const n = io.vtable.netWrite(io.userdata, w.stream, buffered, data, splat) catch |err| {
1098 w.err = err;
1099 return error.WriteFailed;
1100 };
1082 return io_w.consume(n);1101 return io_w.consume(n);
1083 }1102 }
1084 };1103 };
...@@ -1104,7 +1123,7 @@ pub const Server = struct {...@@ -1104,7 +1123,7 @@ pub const Server = struct {
11041123
1105 /// Blocks until a client connects to the server.1124 /// Blocks until a client connects to the server.
1106 pub fn accept(s: *Server, io: Io) AcceptError!Stream {1125 pub fn accept(s: *Server, io: Io) AcceptError!Stream {
1107 return io.vtable.accept(io, s);1126 return io.vtable.accept(io.userdata, s);
1108 }1127 }
1109};1128};
11101129
lib/std/Io/net/HostName.zig+51-13
...@@ -19,12 +19,12 @@ bytes: []const u8,...@@ -19,12 +19,12 @@ bytes: []const u8,
1919
20pub const max_len = 255;20pub const max_len = 255;
2121
22pub const InitError = error{22pub const ValidateError = error{
23 NameTooLong,23 NameTooLong,
24 InvalidHostName,24 InvalidHostName,
25};25};
2626
27pub fn init(bytes: []const u8) InitError!HostName {27pub fn validate(bytes: []const u8) ValidateError!void {
28 if (bytes.len > max_len) return error.NameTooLong;28 if (bytes.len > max_len) return error.NameTooLong;
29 if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName;29 if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName;
30 for (bytes) |byte| {30 for (bytes) |byte| {
...@@ -33,10 +33,34 @@ pub fn init(bytes: []const u8) InitError!HostName {...@@ -33,10 +33,34 @@ pub fn init(bytes: []const u8) InitError!HostName {
33 }33 }
34 return error.InvalidHostName;34 return error.InvalidHostName;
35 }35 }
36}
37
38pub fn init(bytes: []const u8) ValidateError!HostName {
39 try validate(bytes);
36 return .{ .bytes = bytes };40 return .{ .bytes = bytes };
37}41}
3842
39/// TODO add a retry field here43pub fn sameParentDomain(parent_host: HostName, child_host: HostName) bool {
44 const parent_bytes = parent_host.bytes;
45 const child_bytes = child_host.bytes;
46 if (!std.ascii.endsWithIgnoreCase(child_bytes, parent_bytes)) return false;
47 if (child_bytes.len == parent_bytes.len) return true;
48 if (parent_bytes.len > child_bytes.len) return false;
49 return child_bytes[child_bytes.len - parent_bytes.len - 1] == '.';
50}
51
52test sameParentDomain {
53 try std.testing.expect(!sameParentDomain(try .init("foo.com"), try .init("bar.com")));
54 try std.testing.expect(sameParentDomain(try .init("foo.com"), try .init("foo.com")));
55 try std.testing.expect(sameParentDomain(try .init("foo.com"), try .init("bar.foo.com")));
56 try std.testing.expect(!sameParentDomain(try .init("bar.foo.com"), try .init("foo.com")));
57}
58
59/// Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)
60pub fn eql(a: HostName, b: HostName) bool {
61 return std.ascii.eqlIgnoreCase(a.bytes, b.bytes);
62}
63
40pub const LookupOptions = struct {64pub const LookupOptions = struct {
41 port: u16,65 port: u16,
42 /// Must have at least length 2.66 /// Must have at least length 2.
...@@ -266,15 +290,15 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio...@@ -266,15 +290,15 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio
266 var answers_remaining = answers.len;290 var answers_remaining = answers.len;
267 for (answers) |*answer| answer.len = 0;291 for (answers) |*answer| answer.len = 0;
268292
269 // boottime is chosen because time the computer is suspended should count293 // boot clock is chosen because time the computer is suspended should count
270 // against time spent waiting for external messages to arrive.294 // against time spent waiting for external messages to arrive.
271 var now_ts = try Io.Timestamp.now(io, .boottime);295 var now_ts = try Io.Timestamp.now(io, .boot);
272 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));296 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));
273 const attempt_duration: Io.Duration = .{297 const attempt_duration: Io.Duration = .{
274 .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts,298 .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts,
275 };299 };
276300
277 send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try Io.Timestamp.now(io, .boottime)) {301 send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try Io.Timestamp.now(io, .boot)) {
278 const max_messages = queries_buffer.len * ResolvConf.max_nameservers;302 const max_messages = queries_buffer.len * ResolvConf.max_nameservers;
279 {303 {
280 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined;304 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined;
...@@ -518,7 +542,7 @@ fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: u...@@ -518,7 +542,7 @@ fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: u
518 return n;542 return n;
519}543}
520544
521pub const ExpandError = error{InvalidDnsPacket} || InitError;545pub const ExpandError = error{InvalidDnsPacket} || ValidateError;
522546
523/// Decompresses a DNS name.547/// Decompresses a DNS name.
524///548///
...@@ -618,22 +642,36 @@ pub const DnsResponse = struct {...@@ -618,22 +642,36 @@ pub const DnsResponse = struct {
618 }642 }
619};643};
620644
621pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError;645pub const ConnectError = LookupError || IpAddress.ConnectError;
622646
623pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream {647pub fn connect(
648 host_name: HostName,
649 io: Io,
650 port: u16,
651 options: IpAddress.ConnectOptions,
652) ConnectError!Stream {
624 var addresses_buffer: [32]IpAddress = undefined;653 var addresses_buffer: [32]IpAddress = undefined;
654 var canonical_name_buffer: [HostName.max_len]u8 = undefined;
625655
626 const results = try lookup(host_name, .{656 const results = try lookup(host_name, io, .{
627 .port = port,657 .port = port,
628 .addresses_buffer = &addresses_buffer,658 .addresses_buffer = &addresses_buffer,
629 .canonical_name_buffer = &.{},659 .canonical_name_buffer = &canonical_name_buffer,
630 });660 });
631 const addresses = addresses_buffer[0..results.addresses_len];661 const addresses = addresses_buffer[0..results.addresses_len];
632662
633 if (addresses.len == 0) return error.UnknownHostName;663 if (addresses.len == 0) return error.UnknownHostName;
634664
635 for (addresses) |addr| {665 // TODO instead of serially, use a Select API to send out
636 return addr.connectTcp(io) catch |err| switch (err) {666 // the connections simultaneously and then keep the first
667 // successful one, canceling the rest.
668
669 // TODO On Linux this should additionally use an Io.Queue based
670 // DNS resolution API in order to send out a connection after
671 // each DNS response before waiting for the rest of them.
672
673 for (addresses) |*addr| {
674 return addr.connect(io, options) catch |err| switch (err) {
637 error.ConnectionRefused => continue,675 error.ConnectionRefused => continue,
638 else => |e| return e,676 else => |e| return e,
639 };677 };
lib/std/Io/net/test.zig+91-86
...@@ -7,32 +7,30 @@ const testing = std.testing;...@@ -7,32 +7,30 @@ const testing = std.testing;
7test "parse and render IP addresses at comptime" {7test "parse and render IP addresses at comptime" {
8 comptime {8 comptime {
9 const ipv6addr = net.IpAddress.parse("::1", 0) catch unreachable;9 const ipv6addr = net.IpAddress.parse("::1", 0) catch unreachable;
10 try std.testing.expectFmt("[::1]:0", "{f}", .{ipv6addr});10 try testing.expectFmt("[::1]:0", "{f}", .{ipv6addr});
1111
12 const ipv4addr = net.IpAddress.parse("127.0.0.1", 0) catch unreachable;12 const ipv4addr = net.IpAddress.parse("127.0.0.1", 0) catch unreachable;
13 try std.testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr});13 try testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr});
1414
15 try testing.expectError(error.ParseFailed, net.IpAddress.parse("::123.123.123.123", 0));15 try testing.expectError(error.ParseFailed, net.IpAddress.parse("::123.123.123.123", 0));
16 try testing.expectError(error.ParseFailed, net.IpAddress.parse("127.01.0.1", 0));16 try testing.expectError(error.ParseFailed, net.IpAddress.parse("127.01.0.1", 0));
17 try testing.expectError(error.ParseFailed, net.IpAddress.resolveIp("::123.123.123.123", 0));
18 try testing.expectError(error.ParseFailed, net.IpAddress.resolveIp("127.01.0.1", 0));
19 }17 }
20}18}
2119
22test "format IPv6 address with no zero runs" {20test "format IPv6 address with no zero runs" {
23 const addr = try std.net.IpAddress.parseIp6("2001:db8:1:2:3:4:5:6", 0);21 const addr = try net.IpAddress.parseIp6("2001:db8:1:2:3:4:5:6", 0);
24 try std.testing.expectFmt("[2001:db8:1:2:3:4:5:6]:0", "{f}", .{addr});22 try testing.expectFmt("[2001:db8:1:2:3:4:5:6]:0", "{f}", .{addr});
25}23}
2624
27test "parse IPv6 addresses and check compressed form" {25test "parse IPv6 addresses and check compressed form" {
28 try std.testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{26 try testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{
29 try std.net.IpAddress.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0),27 try net.IpAddress.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0),
30 });28 });
31 try std.testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{29 try testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{
32 try std.net.IpAddress.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0),30 try net.IpAddress.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0),
33 });31 });
34 try std.testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{32 try testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{
35 try std.net.IpAddress.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0),33 try net.IpAddress.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0),
36 });34 });
37}35}
3836
...@@ -43,14 +41,14 @@ test "parse IPv6 address, check raw bytes" {...@@ -43,14 +41,14 @@ test "parse IPv6 address, check raw bytes" {
43 0x00, 0x01, 0x00, 0x00, // :0001:000041 0x00, 0x01, 0x00, 0x00, // :0001:0000
44 0x00, 0x00, 0x00, 0x02, // :0000:000242 0x00, 0x00, 0x00, 0x02, // :0000:0002
45 };43 };
4644 const addr = try net.IpAddress.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0);
47 const addr = try std.net.IpAddress.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0);45 try testing.expectEqualSlices(u8, &expected_raw, &addr.ip6.bytes);
48
49 const actual_raw = addr.in6.sa.addr[0..];
50 try std.testing.expectEqualSlices(u8, expected_raw[0..], actual_raw);
51}46}
5247
53test "parse and render IPv6 addresses" {48test "parse and render IPv6 addresses" {
49 // TODO make this test parsing and rendering only, then it doesn't need I/O
50 const io = testing.io;
51
54 var buffer: [100]u8 = undefined;52 var buffer: [100]u8 = undefined;
55 const ips = [_][]const u8{53 const ips = [_][]const u8{
56 "FF01:0:0:0:0:0:0:FB",54 "FF01:0:0:0:0:0:0:FB",
...@@ -79,12 +77,12 @@ test "parse and render IPv6 addresses" {...@@ -79,12 +77,12 @@ test "parse and render IPv6 addresses" {
79 for (ips, 0..) |ip, i| {77 for (ips, 0..) |ip, i| {
80 const addr = net.IpAddress.parseIp6(ip, 0) catch unreachable;78 const addr = net.IpAddress.parseIp6(ip, 0) catch unreachable;
81 var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable;79 var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable;
82 try std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3]));80 try testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3]));
8381
84 if (builtin.os.tag == .linux) {82 if (builtin.os.tag == .linux) {
85 const addr_via_resolve = net.IpAddress.resolveIp6(ip, 0) catch unreachable;83 const addr_via_resolve = net.IpAddress.resolveIp6(io, ip, 0) catch unreachable;
86 var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr_via_resolve}) catch unreachable;84 var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr_via_resolve}) catch unreachable;
87 try std.testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3]));85 try testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3]));
88 }86 }
89 }87 }
9088
...@@ -97,21 +95,23 @@ test "parse and render IPv6 addresses" {...@@ -97,21 +95,23 @@ test "parse and render IPv6 addresses" {
97 try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("1", 0));95 try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("1", 0));
98 // TODO Make this test pass on other operating systems.96 // TODO Make this test pass on other operating systems.
99 if (builtin.os.tag == .linux or comptime builtin.os.tag.isDarwin() or builtin.os.tag == .windows) {97 if (builtin.os.tag == .linux or comptime builtin.os.tag.isDarwin() or builtin.os.tag == .windows) {
100 try testing.expectError(error.Incomplete, net.IpAddress.resolveIp6("ff01::fb%", 0));98 try testing.expectError(error.Incomplete, net.IpAddress.resolveIp6(io, "ff01::fb%", 0));
101 // Assumes IFNAMESIZE will always be a multiple of 299 // Assumes IFNAMESIZE will always be a multiple of 2
102 try testing.expectError(error.Overflow, net.IpAddress.resolveIp6("ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0));100 try testing.expectError(error.Overflow, net.IpAddress.resolveIp6(io, "ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0));
103 try testing.expectError(error.Overflow, net.IpAddress.resolveIp6("ff01::fb%12345678901234", 0));101 try testing.expectError(error.Overflow, net.IpAddress.resolveIp6(io, "ff01::fb%12345678901234", 0));
104 }102 }
105}103}
106104
107test "invalid but parseable IPv6 scope ids" {105test "invalid but parseable IPv6 scope ids" {
106 const io = testing.io;
107
108 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {108 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {
109 // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux.109 // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux.
110 // TODO Make this test pass on other operating systems.110 // TODO Make this test pass on other operating systems.
111 return error.SkipZigTest;111 return error.SkipZigTest;
112 }112 }
113113
114 try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6("ff01::fb%123s45678901234", 0));114 try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6(io, "ff01::fb%123s45678901234", 0));
115}115}
116116
117test "parse and render IPv4 addresses" {117test "parse and render IPv4 addresses" {
...@@ -125,7 +125,7 @@ test "parse and render IPv4 addresses" {...@@ -125,7 +125,7 @@ test "parse and render IPv4 addresses" {
125 }) |ip| {125 }) |ip| {
126 const addr = net.IpAddress.parseIp4(ip, 0) catch unreachable;126 const addr = net.IpAddress.parseIp4(ip, 0) catch unreachable;
127 var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable;127 var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable;
128 try std.testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2]));128 try testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2]));
129 }129 }
130130
131 try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0));131 try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0));
...@@ -136,50 +136,43 @@ test "parse and render IPv4 addresses" {...@@ -136,50 +136,43 @@ test "parse and render IPv4 addresses" {
136 try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0));136 try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0));
137}137}
138138
139test "parse and render UNIX addresses" {
140 if (builtin.os.tag == .wasi) return error.SkipZigTest;
141 if (!net.has_unix_sockets) return error.SkipZigTest;
142
143 const addr = net.Address.initUnix("/tmp/testpath") catch unreachable;
144 try std.testing.expectFmt("/tmp/testpath", "{f}", .{addr});
145
146 const too_long = [_]u8{'a'} ** 200;
147 try testing.expectError(error.NameTooLong, net.Address.initUnix(too_long[0..]));
148}
149
150test "resolve DNS" {139test "resolve DNS" {
151 if (builtin.os.tag == .wasi) return error.SkipZigTest;140 if (builtin.os.tag == .wasi) return error.SkipZigTest;
152141
153 if (builtin.os.tag == .windows) {142 const io = testing.io;
154 _ = try std.os.windows.WSAStartup(2, 2);
155 }
156 defer {
157 if (builtin.os.tag == .windows) {
158 std.os.windows.WSACleanup() catch unreachable;
159 }
160 }
161143
162 // Resolve localhost, this should not fail.144 // Resolve localhost, this should not fail.
163 {145 {
164 const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80);146 const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80);
165 const localhost_v6 = try net.IpAddress.parse("::2", 80);147 const localhost_v6 = try net.IpAddress.parse("::2", 80);
166148
167 const result = try net.getAddressList(testing.allocator, "localhost", 80);149 var addresses_buffer: [8]net.IpAddress = undefined;
168 defer result.deinit();150 var canon_name_buffer: [net.HostName.max_len]u8 = undefined;
169 for (result.addrs) |addr| {151 const result = try net.HostName.lookup(try .init("localhost"), io, .{
170 if (addr.eql(localhost_v4) or addr.eql(localhost_v6)) break;152 .port = 80,
153 .addresses_buffer = &addresses_buffer,
154 .canonical_name_buffer = &canon_name_buffer,
155 });
156 for (addresses_buffer[0..result.addresses_len]) |addr| {
157 if (addr.eql(&localhost_v4) or addr.eql(&localhost_v6)) break;
171 } else @panic("unexpected address for localhost");158 } else @panic("unexpected address for localhost");
172 }159 }
173160
174 {161 {
175 // The tests are required to work even when there is no Internet connection,162 // The tests are required to work even when there is no Internet connection,
176 // so some of these errors we must accept and skip the test.163 // so some of these errors we must accept and skip the test.
177 const result = net.getAddressList(testing.allocator, "example.com", 80) catch |err| switch (err) {164 var addresses_buffer: [8]net.IpAddress = undefined;
165 var canon_name_buffer: [net.HostName.max_len]u8 = undefined;
166 const result = net.HostName.lookup(try .init("example.com"), io, .{
167 .port = 80,
168 .addresses_buffer = &addresses_buffer,
169 .canonical_name_buffer = &canon_name_buffer,
170 }) catch |err| switch (err) {
178 error.UnknownHostName => return error.SkipZigTest,171 error.UnknownHostName => return error.SkipZigTest,
179 error.TemporaryNameServerFailure => return error.SkipZigTest,172 error.NameServerFailure => return error.SkipZigTest,
180 else => return err,173 else => return err,
181 };174 };
182 result.deinit();175 _ = result;
183 }176 }
184}177}
185178
...@@ -187,6 +180,8 @@ test "listen on a port, send bytes, receive bytes" {...@@ -187,6 +180,8 @@ test "listen on a port, send bytes, receive bytes" {
187 if (builtin.single_threaded) return error.SkipZigTest;180 if (builtin.single_threaded) return error.SkipZigTest;
188 if (builtin.os.tag == .wasi) return error.SkipZigTest;181 if (builtin.os.tag == .wasi) return error.SkipZigTest;
189182
183 const io = testing.io;
184
190 if (builtin.os.tag == .windows) {185 if (builtin.os.tag == .windows) {
191 _ = try std.os.windows.WSAStartup(2, 2);186 _ = try std.os.windows.WSAStartup(2, 2);
192 }187 }
...@@ -198,28 +193,28 @@ test "listen on a port, send bytes, receive bytes" {...@@ -198,28 +193,28 @@ test "listen on a port, send bytes, receive bytes" {
198193
199 // Try only the IPv4 variant as some CI builders have no IPv6 localhost194 // Try only the IPv4 variant as some CI builders have no IPv6 localhost
200 // configured.195 // configured.
201 const localhost = try net.IpAddress.parse("127.0.0.1", 0);196 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
202197
203 var server = try localhost.listen(.{});198 var server = try localhost.listen(io, .{});
204 defer server.deinit();199 defer server.deinit(io);
205200
206 const S = struct {201 const S = struct {
207 fn clientFn(server_address: net.IpAddress) !void {202 fn clientFn(server_address: net.IpAddress) !void {
208 const socket = try net.tcpConnectToAddress(server_address);203 var stream = try server_address.connect(io, .{ .mode = .stream });
209 defer socket.close();204 defer stream.close(io);
210205
211 var stream_writer = socket.writer(&.{});206 var stream_writer = stream.writer(io, &.{});
212 try stream_writer.interface.writeAll("Hello world!");207 try stream_writer.interface.writeAll("Hello world!");
213 }208 }
214 };209 };
215210
216 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.listen_address});211 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.socket.address});
217 defer t.join();212 defer t.join();
218213
219 var client = try server.accept();214 var client = try server.accept(io);
220 defer client.stream.close();215 defer client.stream.close(io);
221 var buf: [16]u8 = undefined;216 var buf: [16]u8 = undefined;
222 var stream_reader = client.stream.reader(&.{});217 var stream_reader = client.stream.reader(io, &.{});
223 const n = try stream_reader.interface().readSliceShort(&buf);218 const n = try stream_reader.interface().readSliceShort(&buf);
224219
225 try testing.expectEqual(@as(usize, 12), n);220 try testing.expectEqual(@as(usize, 12), n);
...@@ -232,13 +227,15 @@ test "listen on an in use port" {...@@ -232,13 +227,15 @@ test "listen on an in use port" {
232 return error.SkipZigTest;227 return error.SkipZigTest;
233 }228 }
234229
235 const localhost = try net.IpAddress.parse("127.0.0.1", 0);230 const io = testing.io;
231
232 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
236233
237 var server1 = try localhost.listen(.{ .reuse_address = true });234 var server1 = try localhost.listen(io, .{ .reuse_address = true });
238 defer server1.deinit();235 defer server1.deinit(io);
239236
240 var server2 = try server1.listen_address.listen(.{ .reuse_address = true });237 var server2 = try server1.socket.address.listen(io, .{ .reuse_address = true });
241 defer server2.deinit();238 defer server2.deinit(io);
242}239}
243240
244fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {241fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
...@@ -268,9 +265,11 @@ fn testClient(addr: net.IpAddress) anyerror!void {...@@ -268,9 +265,11 @@ fn testClient(addr: net.IpAddress) anyerror!void {
268fn testServer(server: *net.Server) anyerror!void {265fn testServer(server: *net.Server) anyerror!void {
269 if (builtin.os.tag == .wasi) return error.SkipZigTest;266 if (builtin.os.tag == .wasi) return error.SkipZigTest;
270267
271 var client = try server.accept();268 const io = testing.io;
269
270 var client = try server.accept(io);
272271
273 const stream = client.stream.writer();272 const stream = client.stream.writer(io);
274 try stream.print("hello from server\n", .{});273 try stream.print("hello from server\n", .{});
275}274}
276275
...@@ -278,6 +277,8 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -278,6 +277,8 @@ test "listen on a unix socket, send bytes, receive bytes" {
278 if (builtin.single_threaded) return error.SkipZigTest;277 if (builtin.single_threaded) return error.SkipZigTest;
279 if (!net.has_unix_sockets) return error.SkipZigTest;278 if (!net.has_unix_sockets) return error.SkipZigTest;
280279
280 const io = testing.io;
281
281 if (builtin.os.tag == .windows) {282 if (builtin.os.tag == .windows) {
282 _ = try std.os.windows.WSAStartup(2, 2);283 _ = try std.os.windows.WSAStartup(2, 2);
283 }284 }
...@@ -293,15 +294,15 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -293,15 +294,15 @@ test "listen on a unix socket, send bytes, receive bytes" {
293 const socket_addr = try net.IpAddress.initUnix(socket_path);294 const socket_addr = try net.IpAddress.initUnix(socket_path);
294 defer std.fs.cwd().deleteFile(socket_path) catch {};295 defer std.fs.cwd().deleteFile(socket_path) catch {};
295296
296 var server = try socket_addr.listen(.{});297 var server = try socket_addr.listen(io, .{});
297 defer server.deinit();298 defer server.deinit(io);
298299
299 const S = struct {300 const S = struct {
300 fn clientFn(path: []const u8) !void {301 fn clientFn(path: []const u8) !void {
301 const socket = try net.connectUnixSocket(path);302 var stream = try net.connectUnixSocket(path);
302 defer socket.close();303 defer stream.close(io);
303304
304 var stream_writer = socket.writer(&.{});305 var stream_writer = stream.writer(io, &.{});
305 try stream_writer.interface.writeAll("Hello world!");306 try stream_writer.interface.writeAll("Hello world!");
306 }307 }
307 };308 };
...@@ -309,10 +310,10 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -309,10 +310,10 @@ test "listen on a unix socket, send bytes, receive bytes" {
309 const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path});310 const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path});
310 defer t.join();311 defer t.join();
311312
312 var client = try server.accept();313 var client = try server.accept(io);
313 defer client.stream.close();314 defer client.stream.close(io);
314 var buf: [16]u8 = undefined;315 var buf: [16]u8 = undefined;
315 var stream_reader = client.stream.reader(&.{});316 var stream_reader = client.stream.reader(io, &.{});
316 const n = try stream_reader.interface().readSliceShort(&buf);317 const n = try stream_reader.interface().readSliceShort(&buf);
317318
318 try testing.expectEqual(@as(usize, 12), n);319 try testing.expectEqual(@as(usize, 12), n);
...@@ -324,14 +325,16 @@ test "listen on a unix socket with reuse_address option" {...@@ -324,14 +325,16 @@ test "listen on a unix socket with reuse_address option" {
324 // Windows doesn't implement reuse port option.325 // Windows doesn't implement reuse port option.
325 if (builtin.os.tag == .windows) return error.SkipZigTest;326 if (builtin.os.tag == .windows) return error.SkipZigTest;
326327
328 const io = testing.io;
329
327 const socket_path = try generateFileName("socket.unix");330 const socket_path = try generateFileName("socket.unix");
328 defer testing.allocator.free(socket_path);331 defer testing.allocator.free(socket_path);
329332
330 const socket_addr = try net.Address.initUnix(socket_path);333 const socket_addr = try net.Address.initUnix(socket_path);
331 defer std.fs.cwd().deleteFile(socket_path) catch {};334 defer std.fs.cwd().deleteFile(socket_path) catch {};
332335
333 var server = try socket_addr.listen(.{ .reuse_address = true });336 var server = try socket_addr.listen(io, .{ .reuse_address = true });
334 server.deinit();337 server.deinit(io);
335}338}
336339
337fn generateFileName(base_name: []const u8) ![]const u8 {340fn generateFileName(base_name: []const u8) ![]const u8 {
...@@ -351,19 +354,21 @@ test "non-blocking tcp server" {...@@ -351,19 +354,21 @@ test "non-blocking tcp server" {
351 return error.SkipZigTest;354 return error.SkipZigTest;
352 }355 }
353356
354 const localhost = try net.IpAddress.parse("127.0.0.1", 0);357 const io = testing.io;
355 var server = localhost.listen(.{ .force_nonblocking = true });358
356 defer server.deinit();359 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
360 var server = localhost.listen(io, .{ .force_nonblocking = true });
361 defer server.deinit(io);
357362
358 const accept_err = server.accept();363 const accept_err = server.accept(io);
359 try testing.expectError(error.WouldBlock, accept_err);364 try testing.expectError(error.WouldBlock, accept_err);
360365
361 const socket_file = try net.tcpConnectToAddress(server.listen_address);366 const socket_file = try net.tcpConnectToAddress(server.socket.address);
362 defer socket_file.close();367 defer socket_file.close();
363368
364 var client = try server.accept();369 var client = try server.accept(io);
365 defer client.stream.close();370 defer client.stream.close(io);
366 const stream = client.stream.writer();371 const stream = client.stream.writer(io);
367 try stream.print("hello from server\n", .{});372 try stream.print("hello from server\n", .{});
368373
369 var buf: [100]u8 = undefined;374 var buf: [100]u8 = undefined;
lib/std/Uri.zig+23-15
...@@ -1,45 +1,48 @@...@@ -1,45 +1,48 @@
1//! Uniform Resource Identifier (URI) parsing roughly adhering to <https://tools.ietf.org/html/rfc3986>.1//! Uniform Resource Identifier (URI) parsing roughly adhering to
2//! Does not do perfect grammar and character class checking, but should be robust against URIs in the wild.2//! <https://tools.ietf.org/html/rfc3986>. Does not do perfect grammar and
3//! character class checking, but should be robust against URIs in the wild.
34
4const std = @import("std.zig");5const std = @import("std.zig");
5const testing = std.testing;6const testing = std.testing;
6const Uri = @This();7const Uri = @This();
7const Allocator = std.mem.Allocator;8const Allocator = std.mem.Allocator;
8const Writer = std.Io.Writer;9const Writer = std.Io.Writer;
10const HostName = std.Io.net.HostName;
911
10scheme: []const u8,12scheme: []const u8,
11user: ?Component = null,13user: ?Component = null,
12password: ?Component = null,14password: ?Component = null,
15/// If non-null, already validated.
13host: ?Component = null,16host: ?Component = null,
14port: ?u16 = null,17port: ?u16 = null,
15path: Component = Component.empty,18path: Component = Component.empty,
16query: ?Component = null,19query: ?Component = null,
17fragment: ?Component = null,20fragment: ?Component = null,
1821
19pub const host_name_max = 255;22pub const GetHostError = error{UriMissingHost};
2023
21/// Returned value may point into `buffer` or be the original string.24/// Returned value may point into `buffer` or be the original string.
22///25///
23/// Suggested buffer length: `host_name_max`.
24///
25/// See also:26/// See also:
26/// * `getHostAlloc`27/// * `getHostAlloc`
27pub fn getHost(uri: Uri, buffer: []u8) error{ UriMissingHost, UriHostTooLong }![]const u8 {28pub fn getHost(uri: Uri, buffer: *[HostName.max_len]u8) GetHostError!HostName {
28 const component = uri.host orelse return error.UriMissingHost;29 const component = uri.host orelse return error.UriMissingHost;
29 return component.toRaw(buffer) catch |err| switch (err) {30 const bytes = component.toRaw(buffer) catch |err| switch (err) {
30 error.NoSpaceLeft => return error.UriHostTooLong,31 error.NoSpaceLeft => unreachable, // `host` already validated.
31 };32 };
33 return .{ .bytes = bytes };
32}34}
3335
36pub const GetHostAllocError = GetHostError || error{OutOfMemory};
37
34/// Returned value may point into `buffer` or be the original string.38/// Returned value may point into `buffer` or be the original string.
35///39///
36/// See also:40/// See also:
37/// * `getHost`41/// * `getHost`
38pub fn getHostAlloc(uri: Uri, arena: Allocator) error{ UriMissingHost, UriHostTooLong, OutOfMemory }![]const u8 {42pub fn getHostAlloc(uri: Uri, arena: Allocator) GetHostAllocError![]const u8 {
39 const component = uri.host orelse return error.UriMissingHost;43 const component = uri.host orelse return error.UriMissingHost;
40 const result = try component.toRawMaybeAlloc(arena);44 const bytes = try component.toRawMaybeAlloc(arena);
41 if (result.len > host_name_max) return error.UriHostTooLong;45 return .{ .bytes = bytes };
42 return result;
43}46}
4447
45pub const Component = union(enum) {48pub const Component = union(enum) {
...@@ -397,7 +400,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE...@@ -397,7 +400,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
397 .scheme = new_parsed.scheme,400 .scheme = new_parsed.scheme,
398 .user = new_parsed.user,401 .user = new_parsed.user,
399 .password = new_parsed.password,402 .password = new_parsed.password,
400 .host = new_parsed.host,403 .host = try validateHost(new_parsed.host),
401 .port = new_parsed.port,404 .port = new_parsed.port,
402 .path = remove_dot_segments(new_path),405 .path = remove_dot_segments(new_path),
403 .query = new_parsed.query,406 .query = new_parsed.query,
...@@ -408,7 +411,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE...@@ -408,7 +411,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
408 .scheme = base.scheme,411 .scheme = base.scheme,
409 .user = new_parsed.user,412 .user = new_parsed.user,
410 .password = new_parsed.password,413 .password = new_parsed.password,
411 .host = host,414 .host = try validateHost(host),
412 .port = new_parsed.port,415 .port = new_parsed.port,
413 .path = remove_dot_segments(new_path),416 .path = remove_dot_segments(new_path),
414 .query = new_parsed.query,417 .query = new_parsed.query,
...@@ -430,7 +433,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE...@@ -430,7 +433,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
430 .scheme = base.scheme,433 .scheme = base.scheme,
431 .user = base.user,434 .user = base.user,
432 .password = base.password,435 .password = base.password,
433 .host = base.host,436 .host = try validateHost(base.host),
434 .port = base.port,437 .port = base.port,
435 .path = path,438 .path = path,
436 .query = query,439 .query = query,
...@@ -438,6 +441,11 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE...@@ -438,6 +441,11 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
438 };441 };
439}442}
440443
444fn validateHost(bytes: []const u8) []const u8 {
445 try HostName.validate(bytes);
446 return bytes;
447}
448
441/// In-place implementation of RFC 3986, Section 5.2.4.449/// In-place implementation of RFC 3986, Section 5.2.4.
442fn remove_dot_segments(path: []u8) Component {450fn remove_dot_segments(path: []u8) Component {
443 var in_i: usize = 0;451 var in_i: usize = 0;
lib/std/fs/test.zig+1-1
...@@ -2281,7 +2281,7 @@ test "seekTo flushes buffered data" {...@@ -2281,7 +2281,7 @@ test "seekTo flushes buffered data" {
2281 }2281 }
22822282
2283 var read_buffer: [16]u8 = undefined;2283 var read_buffer: [16]u8 = undefined;
2284 var file_reader: std.Io.File.Reader = .init(file, io, &read_buffer);2284 var file_reader: std.Io.File.Reader = .initAdapted(file, io, &read_buffer);
22852285
2286 var buf: [4]u8 = undefined;2286 var buf: [4]u8 = undefined;
2287 try file_reader.interface.readSliceAll(&buf);2287 try file_reader.interface.readSliceAll(&buf);
lib/std/http/Client.zig+55-69
...@@ -15,6 +15,7 @@ const assert = std.debug.assert;...@@ -15,6 +15,7 @@ const assert = std.debug.assert;
15const Io = std.Io;15const Io = std.Io;
16const Writer = std.Io.Writer;16const Writer = std.Io.Writer;
17const Reader = std.Io.Reader;17const Reader = std.Io.Reader;
18const HostName = std.Io.net.HostName;
1819
19const Client = @This();20const Client = @This();
2021
...@@ -69,7 +70,7 @@ pub const ConnectionPool = struct {...@@ -69,7 +70,7 @@ pub const ConnectionPool = struct {
6970
70 /// The criteria for a connection to be considered a match.71 /// The criteria for a connection to be considered a match.
71 pub const Criteria = struct {72 pub const Criteria = struct {
72 host: []const u8,73 host: HostName,
73 port: u16,74 port: u16,
74 protocol: Protocol,75 protocol: Protocol,
75 };76 };
...@@ -89,7 +90,7 @@ pub const ConnectionPool = struct {...@@ -89,7 +90,7 @@ pub const ConnectionPool = struct {
89 if (connection.port != criteria.port) continue;90 if (connection.port != criteria.port) continue;
9091
91 // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)92 // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)
92 if (!std.ascii.eqlIgnoreCase(connection.host(), criteria.host)) continue;93 if (!connection.host().eql(criteria.host)) continue;
9394
94 pool.acquireUnsafe(connection);95 pool.acquireUnsafe(connection);
95 return connection;96 return connection;
...@@ -118,19 +119,19 @@ pub const ConnectionPool = struct {...@@ -118,19 +119,19 @@ pub const ConnectionPool = struct {
118 /// If the connection is marked as closing, it will be closed instead.119 /// If the connection is marked as closing, it will be closed instead.
119 ///120 ///
120 /// Threadsafe.121 /// Threadsafe.
121 pub fn release(pool: *ConnectionPool, connection: *Connection) void {122 pub fn release(pool: *ConnectionPool, connection: *Connection, io: Io) void {
122 pool.mutex.lock();123 pool.mutex.lock();
123 defer pool.mutex.unlock();124 defer pool.mutex.unlock();
124125
125 pool.used.remove(&connection.pool_node);126 pool.used.remove(&connection.pool_node);
126127
127 if (connection.closing or pool.free_size == 0) return connection.destroy();128 if (connection.closing or pool.free_size == 0) return connection.destroy(io);
128129
129 if (pool.free_len >= pool.free_size) {130 if (pool.free_len >= pool.free_size) {
130 const popped: *Connection = @alignCast(@fieldParentPtr("pool_node", pool.free.popFirst().?));131 const popped: *Connection = @alignCast(@fieldParentPtr("pool_node", pool.free.popFirst().?));
131 pool.free_len -= 1;132 pool.free_len -= 1;
132133
133 popped.destroy();134 popped.destroy(io);
134 }135 }
135136
136 if (connection.proxied) {137 if (connection.proxied) {
...@@ -178,21 +179,21 @@ pub const ConnectionPool = struct {...@@ -178,21 +179,21 @@ pub const ConnectionPool = struct {
178 /// All future operations on the connection pool will deadlock.179 /// All future operations on the connection pool will deadlock.
179 ///180 ///
180 /// Threadsafe.181 /// Threadsafe.
181 pub fn deinit(pool: *ConnectionPool) void {182 pub fn deinit(pool: *ConnectionPool, io: Io) void {
182 pool.mutex.lock();183 pool.mutex.lock();
183184
184 var next = pool.free.first;185 var next = pool.free.first;
185 while (next) |node| {186 while (next) |node| {
186 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));187 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
187 next = node.next;188 next = node.next;
188 connection.destroy();189 connection.destroy(io);
189 }190 }
190191
191 next = pool.used.first;192 next = pool.used.first;
192 while (next) |node| {193 while (next) |node| {
193 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));194 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
194 next = node.next;195 next = node.next;
195 connection.destroy();196 connection.destroy(io);
196 }197 }
197198
198 pool.* = undefined;199 pool.* = undefined;
...@@ -242,19 +243,19 @@ pub const Connection = struct {...@@ -242,19 +243,19 @@ pub const Connection = struct {
242243
243 fn create(244 fn create(
244 client: *Client,245 client: *Client,
245 remote_host: []const u8,246 remote_host: HostName,
246 port: u16,247 port: u16,
247 stream: Io.net.Stream,248 stream: Io.net.Stream,
248 ) error{OutOfMemory}!*Plain {249 ) error{OutOfMemory}!*Plain {
249 const gpa = client.allocator;250 const gpa = client.allocator;
250 const alloc_len = allocLen(client, remote_host.len);251 const alloc_len = allocLen(client, remote_host.bytes.len);
251 const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);252 const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);
252 errdefer gpa.free(base);253 errdefer gpa.free(base);
253 const host_buffer = base[@sizeOf(Plain)..][0..remote_host.len];254 const host_buffer = base[@sizeOf(Plain)..][0..remote_host.bytes.len];
254 const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size];255 const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size];
255 const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size];256 const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size];
256 assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len);257 assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len);
257 @memcpy(host_buffer, remote_host);258 @memcpy(host_buffer, remote_host.bytes);
258 const plain: *Plain = @ptrCast(base);259 const plain: *Plain = @ptrCast(base);
259 plain.* = .{260 plain.* = .{
260 .connection = .{261 .connection = .{
...@@ -263,7 +264,7 @@ pub const Connection = struct {...@@ -263,7 +264,7 @@ pub const Connection = struct {
263 .stream_reader = stream.reader(socket_read_buffer),264 .stream_reader = stream.reader(socket_read_buffer),
264 .pool_node = .{},265 .pool_node = .{},
265 .port = port,266 .port = port,
266 .host_len = @intCast(remote_host.len),267 .host_len = @intCast(remote_host.bytes.len),
267 .proxied = false,268 .proxied = false,
268 .closing = false,269 .closing = false,
269 .protocol = .plain,270 .protocol = .plain,
...@@ -283,9 +284,9 @@ pub const Connection = struct {...@@ -283,9 +284,9 @@ pub const Connection = struct {
283 return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size;284 return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size;
284 }285 }
285286
286 fn host(plain: *Plain) []u8 {287 fn host(plain: *Plain) HostName {
287 const base: [*]u8 = @ptrCast(plain);288 const base: [*]u8 = @ptrCast(plain);
288 return base[@sizeOf(Plain)..][0..plain.connection.host_len];289 return .{ .bytes = base[@sizeOf(Plain)..][0..plain.connection.host_len] };
289 }290 }
290 };291 };
291292
...@@ -295,15 +296,15 @@ pub const Connection = struct {...@@ -295,15 +296,15 @@ pub const Connection = struct {
295296
296 fn create(297 fn create(
297 client: *Client,298 client: *Client,
298 remote_host: []const u8,299 remote_host: HostName,
299 port: u16,300 port: u16,
300 stream: Io.net.Stream,301 stream: Io.net.Stream,
301 ) error{ OutOfMemory, TlsInitializationFailed }!*Tls {302 ) error{ OutOfMemory, TlsInitializationFailed }!*Tls {
302 const gpa = client.allocator;303 const gpa = client.allocator;
303 const alloc_len = allocLen(client, remote_host.len);304 const alloc_len = allocLen(client, remote_host.bytes.len);
304 const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len);305 const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len);
305 errdefer gpa.free(base);306 errdefer gpa.free(base);
306 const host_buffer = base[@sizeOf(Tls)..][0..remote_host.len];307 const host_buffer = base[@sizeOf(Tls)..][0..remote_host.bytes.len];
307 // The TLS client wants enough buffer for the max encrypted frame308 // The TLS client wants enough buffer for the max encrypted frame
308 // size, and the HTTP body reader wants enough buffer for the309 // size, and the HTTP body reader wants enough buffer for the
309 // entire HTTP header. This means we need a combined upper bound.310 // entire HTTP header. This means we need a combined upper bound.
...@@ -313,7 +314,7 @@ pub const Connection = struct {...@@ -313,7 +314,7 @@ pub const Connection = struct {
313 const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];314 const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];
314 const socket_read_buffer = socket_write_buffer.ptr[socket_write_buffer.len..][0..client.tls_buffer_size];315 const socket_read_buffer = socket_write_buffer.ptr[socket_write_buffer.len..][0..client.tls_buffer_size];
315 assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);316 assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);
316 @memcpy(host_buffer, remote_host);317 @memcpy(host_buffer, remote_host.bytes);
317 const tls: *Tls = @ptrCast(base);318 const tls: *Tls = @ptrCast(base);
318 tls.* = .{319 tls.* = .{
319 .connection = .{320 .connection = .{
...@@ -322,17 +323,17 @@ pub const Connection = struct {...@@ -322,17 +323,17 @@ pub const Connection = struct {
322 .stream_reader = stream.reader(socket_read_buffer),323 .stream_reader = stream.reader(socket_read_buffer),
323 .pool_node = .{},324 .pool_node = .{},
324 .port = port,325 .port = port,
325 .host_len = @intCast(remote_host.len),326 .host_len = @intCast(remote_host.bytes.len),
326 .proxied = false,327 .proxied = false,
327 .closing = false,328 .closing = false,
328 .protocol = .tls,329 .protocol = .tls,
329 },330 },
330 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true331 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true
331 .client = std.crypto.tls.Client.init(332 .client = std.crypto.tls.Client.init(
332 tls.connection.stream_reader.interface(),333 &tls.connection.stream_reader.interface,
333 &tls.connection.stream_writer.interface,334 &tls.connection.stream_writer.interface,
334 .{335 .{
335 .host = .{ .explicit = remote_host },336 .host = .{ .explicit = remote_host.bytes },
336 .ca = .{ .bundle = client.ca_bundle },337 .ca = .{ .bundle = client.ca_bundle },
337 .ssl_key_log = client.ssl_key_log,338 .ssl_key_log = client.ssl_key_log,
338 .read_buffer = tls_read_buffer,339 .read_buffer = tls_read_buffer,
...@@ -359,9 +360,9 @@ pub const Connection = struct {...@@ -359,9 +360,9 @@ pub const Connection = struct {
359 client.write_buffer_size + client.tls_buffer_size;360 client.write_buffer_size + client.tls_buffer_size;
360 }361 }
361362
362 fn host(tls: *Tls) []u8 {363 fn host(tls: *Tls) HostName {
363 const base: [*]u8 = @ptrCast(tls);364 const base: [*]u8 = @ptrCast(tls);
364 return base[@sizeOf(Tls)..][0..tls.connection.host_len];365 return .{ .bytes = base[@sizeOf(Tls)..][0..tls.connection.host_len] };
365 }366 }
366 };367 };
367368
...@@ -384,7 +385,7 @@ pub const Connection = struct {...@@ -384,7 +385,7 @@ pub const Connection = struct {
384 return c.stream_reader.stream;385 return c.stream_reader.stream;
385 }386 }
386387
387 pub fn host(c: *Connection) []u8 {388 pub fn host(c: *Connection) HostName {
388 return switch (c.protocol) {389 return switch (c.protocol) {
389 .tls => {390 .tls => {
390 if (disable_tls) unreachable;391 if (disable_tls) unreachable;
...@@ -400,8 +401,8 @@ pub const Connection = struct {...@@ -400,8 +401,8 @@ pub const Connection = struct {
400401
401 /// If this is called without calling `flush` or `end`, data will be402 /// If this is called without calling `flush` or `end`, data will be
402 /// dropped unsent.403 /// dropped unsent.
403 pub fn destroy(c: *Connection) void {404 pub fn destroy(c: *Connection, io: Io) void {
404 c.getStream().close();405 c.stream_reader.stream.close(io);
405 switch (c.protocol) {406 switch (c.protocol) {
406 .tls => {407 .tls => {
407 if (disable_tls) unreachable;408 if (disable_tls) unreachable;
...@@ -437,7 +438,7 @@ pub const Connection = struct {...@@ -437,7 +438,7 @@ pub const Connection = struct {
437 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));438 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
438 return &tls.client.reader;439 return &tls.client.reader;
439 },440 },
440 .plain => c.stream_reader.interface(),441 .plain => &c.stream_reader.interface,
441 };442 };
442 }443 }
443444
...@@ -866,6 +867,7 @@ pub const Request = struct {...@@ -866,6 +867,7 @@ pub const Request = struct {
866867
867 /// Returns the request's `Connection` back to the pool of the `Client`.868 /// Returns the request's `Connection` back to the pool of the `Client`.
868 pub fn deinit(r: *Request) void {869 pub fn deinit(r: *Request) void {
870 const io = r.client.io;
869 if (r.connection) |connection| {871 if (r.connection) |connection| {
870 connection.closing = connection.closing or switch (r.reader.state) {872 connection.closing = connection.closing or switch (r.reader.state) {
871 .ready => false,873 .ready => false,
...@@ -880,7 +882,7 @@ pub const Request = struct {...@@ -880,7 +882,7 @@ pub const Request = struct {
880 },882 },
881 else => true,883 else => true,
882 };884 };
883 r.client.connection_pool.release(connection);885 r.client.connection_pool.release(connection, io);
884 }886 }
885 r.* = undefined;887 r.* = undefined;
886 }888 }
...@@ -1182,6 +1184,7 @@ pub const Request = struct {...@@ -1182,6 +1184,7 @@ pub const Request = struct {
1182 ///1184 ///
1183 /// `aux_buf` must outlive accesses to `Request.uri`.1185 /// `aux_buf` must outlive accesses to `Request.uri`.
1184 fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void {1186 fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void {
1187 const io = r.client.io;
1185 const new_location = head.location orelse return error.HttpRedirectLocationMissing;1188 const new_location = head.location orelse return error.HttpRedirectLocationMissing;
1186 if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize;1189 if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize;
1187 const location = aux_buf.*[0..new_location.len];1190 const location = aux_buf.*[0..new_location.len];
...@@ -1204,13 +1207,13 @@ pub const Request = struct {...@@ -1204,13 +1207,13 @@ pub const Request = struct {
1204 const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme;1207 const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme;
1205 const old_connection = r.connection.?;1208 const old_connection = r.connection.?;
1206 const old_host = old_connection.host();1209 const old_host = old_connection.host();
1207 var new_host_name_buffer: [Uri.host_name_max]u8 = undefined;1210 var new_host_name_buffer: [HostName.max_len]u8 = undefined;
1208 const new_host = try new_uri.getHost(&new_host_name_buffer);1211 const new_host = try new_uri.getHost(&new_host_name_buffer);
1209 const keep_privileged_headers =1212 const keep_privileged_headers =
1210 std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and1213 std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and
1211 sameParentDomain(old_host, new_host);1214 old_host.sameParentDomain(new_host);
12121215
1213 r.client.connection_pool.release(old_connection);1216 r.client.connection_pool.release(old_connection, io);
1214 r.connection = null;1217 r.connection = null;
12151218
1216 if (!keep_privileged_headers) {1219 if (!keep_privileged_headers) {
...@@ -1266,7 +1269,7 @@ pub const Request = struct {...@@ -1266,7 +1269,7 @@ pub const Request = struct {
12661269
1267pub const Proxy = struct {1270pub const Proxy = struct {
1268 protocol: Protocol,1271 protocol: Protocol,
1269 host: []const u8,1272 host: HostName,
1270 authorization: ?[]const u8,1273 authorization: ?[]const u8,
1271 port: u16,1274 port: u16,
1272 supports_connect: bool,1275 supports_connect: bool,
...@@ -1277,9 +1280,10 @@ pub const Proxy = struct {...@@ -1277,9 +1280,10 @@ pub const Proxy = struct {
1277/// All pending requests must be de-initialized and all active connections released1280/// All pending requests must be de-initialized and all active connections released
1278/// before calling this function.1281/// before calling this function.
1279pub fn deinit(client: *Client) void {1282pub fn deinit(client: *Client) void {
1283 const io = client.io;
1280 assert(client.connection_pool.used.first == null); // There are still active requests.1284 assert(client.connection_pool.used.first == null); // There are still active requests.
12811285
1282 client.connection_pool.deinit();1286 client.connection_pool.deinit(io);
1283 if (!disable_tls) client.ca_bundle.deinit(client.allocator);1287 if (!disable_tls) client.ca_bundle.deinit(client.allocator);
12841288
1285 client.* = undefined;1289 client.* = undefined;
...@@ -1385,7 +1389,7 @@ pub const basic_authorization = struct {...@@ -1385,7 +1389,7 @@ pub const basic_authorization = struct {
1385 }1389 }
1386};1390};
13871391
1388pub const ConnectTcpError = Allocator.Error || error{1392pub const ConnectTcpError = error{
1389 ConnectionRefused,1393 ConnectionRefused,
1390 NetworkUnreachable,1394 NetworkUnreachable,
1391 ConnectionTimedOut,1395 ConnectionTimedOut,
...@@ -1393,17 +1397,16 @@ pub const ConnectTcpError = Allocator.Error || error{...@@ -1393,17 +1397,16 @@ pub const ConnectTcpError = Allocator.Error || error{
1393 TemporaryNameServerFailure,1397 TemporaryNameServerFailure,
1394 NameServerFailure,1398 NameServerFailure,
1395 UnknownHostName,1399 UnknownHostName,
1396 HostLacksNetworkAddresses,
1397 UnexpectedConnectFailure,1400 UnexpectedConnectFailure,
1398 TlsInitializationFailed,1401 TlsInitializationFailed,
1399};1402} || Allocator.Error || Io.Cancelable;
14001403
1401/// Reuses a `Connection` if one matching `host` and `port` is already open.1404/// Reuses a `Connection` if one matching `host` and `port` is already open.
1402///1405///
1403/// Threadsafe.1406/// Threadsafe.
1404pub fn connectTcp(1407pub fn connectTcp(
1405 client: *Client,1408 client: *Client,
1406 host: []const u8,1409 host: HostName,
1407 port: u16,1410 port: u16,
1408 protocol: Protocol,1411 protocol: Protocol,
1409) ConnectTcpError!*Connection {1412) ConnectTcpError!*Connection {
...@@ -1411,16 +1414,17 @@ pub fn connectTcp(...@@ -1411,16 +1414,17 @@ pub fn connectTcp(
1411}1414}
14121415
1413pub const ConnectTcpOptions = struct {1416pub const ConnectTcpOptions = struct {
1414 host: Io.net.HostName,1417 host: HostName,
1415 port: u16,1418 port: u16,
1416 protocol: Protocol,1419 protocol: Protocol,
14171420
1418 proxied_host: ?[]const u8 = null,1421 proxied_host: ?HostName = null,
1419 proxied_port: ?u16 = null,1422 proxied_port: ?u16 = null,
1423 timeout: Io.Timeout = .none,
1420};1424};
14211425
1422pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {1426pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {
1423 const host = options.host_name;1427 const host = options.host;
1424 const port = options.port;1428 const port = options.port;
1425 const protocol = options.protocol;1429 const protocol = options.protocol;
14261430
...@@ -1433,17 +1437,15 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp...@@ -1433,17 +1437,15 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp
1433 .protocol = protocol,1437 .protocol = protocol,
1434 })) |conn| return conn;1438 })) |conn| return conn;
14351439
1436 const stream = host.connectTcp(client.io, port) catch |err| switch (err) {1440 const stream = host.connect(client.io, port, .{ .mode = .stream }) catch |err| switch (err) {
1437 error.ConnectionRefused => return error.ConnectionRefused,1441 error.ConnectionRefused => return error.ConnectionRefused,
1438 error.NetworkUnreachable => return error.NetworkUnreachable,1442 error.NetworkUnreachable => return error.NetworkUnreachable,
1439 error.ConnectionTimedOut => return error.ConnectionTimedOut,1443 error.ConnectionTimedOut => return error.ConnectionTimedOut,
1440 error.ConnectionResetByPeer => return error.ConnectionResetByPeer,1444 error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
1441 error.TemporaryNameServerFailure => return error.TemporaryNameServerFailure,
1442 error.NameServerFailure => return error.NameServerFailure,1445 error.NameServerFailure => return error.NameServerFailure,
1443 error.UnknownHostName => return error.UnknownHostName,1446 error.UnknownHostName => return error.UnknownHostName,
1444 error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses,
1445 error.Canceled => return error.Canceled,1447 error.Canceled => return error.Canceled,
1446 else => return error.UnexpectedConnectFailure,1448 //else => return error.UnexpectedConnectFailure,
1447 };1449 };
1448 errdefer stream.close();1450 errdefer stream.close();
14491451
...@@ -1479,7 +1481,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti...@@ -1479,7 +1481,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
1479 errdefer client.allocator.destroy(conn);1481 errdefer client.allocator.destroy(conn);
1480 conn.* = .{ .data = undefined };1482 conn.* = .{ .data = undefined };
14811483
1482 const stream = try std.net.connectUnixSocket(path);1484 const stream = try Io.net.connectUnixSocket(path);
1483 errdefer stream.close();1485 errdefer stream.close();
14841486
1485 conn.data = .{1487 conn.data = .{
...@@ -1504,9 +1506,10 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti...@@ -1504,9 +1506,10 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
1504pub fn connectProxied(1506pub fn connectProxied(
1505 client: *Client,1507 client: *Client,
1506 proxy: *Proxy,1508 proxy: *Proxy,
1507 proxied_host: []const u8,1509 proxied_host: HostName,
1508 proxied_port: u16,1510 proxied_port: u16,
1509) !*Connection {1511) !*Connection {
1512 const io = client.io;
1510 if (!proxy.supports_connect) return error.TunnelNotSupported;1513 if (!proxy.supports_connect) return error.TunnelNotSupported;
15111514
1512 if (client.connection_pool.findConnection(.{1515 if (client.connection_pool.findConnection(.{
...@@ -1526,12 +1529,12 @@ pub fn connectProxied(...@@ -1526,12 +1529,12 @@ pub fn connectProxied(
1526 });1529 });
1527 errdefer {1530 errdefer {
1528 connection.closing = true;1531 connection.closing = true;
1529 client.connection_pool.release(connection);1532 client.connection_pool.release(connection, io);
1530 }1533 }
15311534
1532 var req = client.request(.CONNECT, .{1535 var req = client.request(.CONNECT, .{
1533 .scheme = "http",1536 .scheme = "http",
1534 .host = .{ .raw = proxied_host },1537 .host = .{ .raw = proxied_host.bytes },
1535 .port = proxied_port,1538 .port = proxied_port,
1536 }, .{1539 }, .{
1537 .redirect_behavior = .unhandled,1540 .redirect_behavior = .unhandled,
...@@ -1576,7 +1579,7 @@ pub const ConnectError = ConnectTcpError || RequestError;...@@ -1576,7 +1579,7 @@ pub const ConnectError = ConnectTcpError || RequestError;
1576/// This function is threadsafe.1579/// This function is threadsafe.
1577pub fn connect(1580pub fn connect(
1578 client: *Client,1581 client: *Client,
1579 host: []const u8,1582 host: HostName,
1580 port: u16,1583 port: u16,
1581 protocol: Protocol,1584 protocol: Protocol,
1582) ConnectError!*Connection {1585) ConnectError!*Connection {
...@@ -1586,9 +1589,7 @@ pub fn connect(...@@ -1586,9 +1589,7 @@ pub fn connect(
1586 } orelse return client.connectTcp(host, port, protocol);1589 } orelse return client.connectTcp(host, port, protocol);
15871590
1588 // Prevent proxying through itself.1591 // Prevent proxying through itself.
1589 if (std.ascii.eqlIgnoreCase(proxy.host, host) and1592 if (proxy.host.eql(host) and proxy.port == port and proxy.protocol == protocol) {
1590 proxy.port == port and proxy.protocol == protocol)
1591 {
1592 return client.connectTcp(host, port, protocol);1593 return client.connectTcp(host, port, protocol);
1593 }1594 }
15941595
...@@ -1608,7 +1609,6 @@ pub fn connect(...@@ -1608,7 +1609,6 @@ pub fn connect(
1608pub const RequestError = ConnectTcpError || error{1609pub const RequestError = ConnectTcpError || error{
1609 UnsupportedUriScheme,1610 UnsupportedUriScheme,
1610 UriMissingHost,1611 UriMissingHost,
1611 UriHostTooLong,
1612 CertificateBundleLoadFailure,1612 CertificateBundleLoadFailure,
1613};1613};
16141614
...@@ -1697,7 +1697,7 @@ pub fn request(...@@ -1697,7 +1697,7 @@ pub fn request(
1697 }1697 }
16981698
1699 const connection = options.connection orelse c: {1699 const connection = options.connection orelse c: {
1700 var host_name_buffer: [Uri.host_name_max]u8 = undefined;1700 var host_name_buffer: [HostName.max_len]u8 = undefined;
1701 const host_name = try uri.getHost(&host_name_buffer);1701 const host_name = try uri.getHost(&host_name_buffer);
1702 break :c try client.connect(host_name, uriPort(uri, protocol), protocol);1702 break :c try client.connect(host_name, uriPort(uri, protocol), protocol);
1703 };1703 };
...@@ -1835,20 +1835,6 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {...@@ -1835,20 +1835,6 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
1835 return .{ .status = response.head.status };1835 return .{ .status = response.head.status };
1836}1836}
18371837
1838pub fn sameParentDomain(parent_host: []const u8, child_host: []const u8) bool {
1839 if (!std.ascii.endsWithIgnoreCase(child_host, parent_host)) return false;
1840 if (child_host.len == parent_host.len) return true;
1841 if (parent_host.len > child_host.len) return false;
1842 return child_host[child_host.len - parent_host.len - 1] == '.';
1843}
1844
1845test sameParentDomain {
1846 try testing.expect(!sameParentDomain("foo.com", "bar.com"));
1847 try testing.expect(sameParentDomain("foo.com", "foo.com"));
1848 try testing.expect(sameParentDomain("foo.com", "bar.foo.com"));
1849 try testing.expect(!sameParentDomain("bar.foo.com", "foo.com"));
1850}
1851
1852test {1838test {
1853 _ = Response;1839 _ = Response;
1854}1840}
lib/std/http/test.zig+36-26
...@@ -53,7 +53,7 @@ test "trailers" {...@@ -53,7 +53,7 @@ test "trailers" {
5353
54 const gpa = std.testing.allocator;54 const gpa = std.testing.allocator;
5555
56 var client: http.Client = .{ .allocator = gpa };56 var client: http.Client = .{ .allocator = gpa, .io = io };
57 defer client.deinit();57 defer client.deinit();
5858
59 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{59 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{
...@@ -141,12 +141,13 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -141,12 +141,13 @@ test "HTTP server handles a chunked transfer coding request" {
141 "0\r\n" ++141 "0\r\n" ++
142 "\r\n";142 "\r\n";
143143
144 const gpa = std.testing.allocator;144 const host_name: net.HostName = try .init("127.0.0.1");
145 var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());145 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
146 defer stream.close(io);146 defer stream.close(io);
147 var stream_writer = stream.writer(&.{});147 var stream_writer = stream.writer(io, &.{});
148 try stream_writer.interface.writeAll(request_bytes);148 try stream_writer.interface.writeAll(request_bytes);
149149
150 const gpa = std.testing.allocator;
150 const expected_response =151 const expected_response =
151 "HTTP/1.1 200 OK\r\n" ++152 "HTTP/1.1 200 OK\r\n" ++
152 "connection: close\r\n" ++153 "connection: close\r\n" ++
...@@ -154,8 +155,8 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -154,8 +155,8 @@ test "HTTP server handles a chunked transfer coding request" {
154 "content-type: text/plain\r\n" ++155 "content-type: text/plain\r\n" ++
155 "\r\n" ++156 "\r\n" ++
156 "message from server!\n";157 "message from server!\n";
157 var stream_reader = stream.reader(&.{});158 var stream_reader = stream.reader(io, &.{});
158 const response = try stream_reader.interface().allocRemaining(gpa, .limited(expected_response.len + 1));159 const response = try stream_reader.interface.allocRemaining(gpa, .limited(expected_response.len + 1));
159 defer gpa.free(response);160 defer gpa.free(response);
160 try expectEqualStrings(expected_response, response);161 try expectEqualStrings(expected_response, response);
161}162}
...@@ -241,7 +242,7 @@ test "echo content server" {...@@ -241,7 +242,7 @@ test "echo content server" {
241 defer test_server.destroy();242 defer test_server.destroy();
242243
243 {244 {
244 var client: http.Client = .{ .allocator = std.testing.allocator };245 var client: http.Client = .{ .allocator = std.testing.allocator, .io = io };
245 defer client.deinit();246 defer client.deinit();
246247
247 try echoTests(&client, test_server.port());248 try echoTests(&client, test_server.port());
...@@ -294,14 +295,15 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -294,14 +295,15 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
294 defer test_server.destroy();295 defer test_server.destroy();
295296
296 const request_bytes = "GET /foo HTTP/1.1\r\n\r\n";297 const request_bytes = "GET /foo HTTP/1.1\r\n\r\n";
297 const gpa = std.testing.allocator;298 const host_name: net.HostName = try .init("127.0.0.1");
298 var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());299 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
299 defer stream.close(io);300 defer stream.close(io);
300 var stream_writer = stream.writer(&.{});301 var stream_writer = stream.writer(io, &.{});
301 try stream_writer.interface.writeAll(request_bytes);302 try stream_writer.interface.writeAll(request_bytes);
302303
303 var stream_reader = stream.reader(&.{});304 var stream_reader = stream.reader(io, &.{});
304 const response = try stream_reader.interface().allocRemaining(gpa, .unlimited);305 const gpa = std.testing.allocator;
306 const response = try stream_reader.interface.allocRemaining(gpa, .unlimited);
305 defer gpa.free(response);307 defer gpa.free(response);
306308
307 var expected_response = std.array_list.Managed(u8).init(gpa);309 var expected_response = std.array_list.Managed(u8).init(gpa);
...@@ -366,14 +368,15 @@ test "receiving arbitrary http headers from the client" {...@@ -366,14 +368,15 @@ test "receiving arbitrary http headers from the client" {
366 "CoNneCtIoN:close\r\n" ++368 "CoNneCtIoN:close\r\n" ++
367 "aoeu: asdf \r\n" ++369 "aoeu: asdf \r\n" ++
368 "\r\n";370 "\r\n";
369 const gpa = std.testing.allocator;371 const host_name: net.HostName = try .init("127.0.0.1");
370 var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());372 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
371 defer stream.close(io);373 defer stream.close(io);
372 var stream_writer = stream.writer(&.{});374 var stream_writer = stream.writer(io, &.{});
373 try stream_writer.interface.writeAll(request_bytes);375 try stream_writer.interface.writeAll(request_bytes);
374376
375 var stream_reader = stream.reader(&.{});377 var stream_reader = stream.reader(io, &.{});
376 const response = try stream_reader.interface().allocRemaining(gpa, .unlimited);378 const gpa = std.testing.allocator;
379 const response = try stream_reader.interface.allocRemaining(gpa, .unlimited);
377 defer gpa.free(response);380 defer gpa.free(response);
378381
379 var expected_response = std.array_list.Managed(u8).init(gpa);382 var expected_response = std.array_list.Managed(u8).init(gpa);
...@@ -413,7 +416,7 @@ test "general client/server API coverage" {...@@ -413,7 +416,7 @@ test "general client/server API coverage" {
413 else => |e| return e,416 else => |e| return e,
414 };417 };
415418
416 try handleRequest(&request, net_server.listen_address.getPort());419 try handleRequest(&request, net_server.socket.address.getPort());
417 }420 }
418 }421 }
419 }422 }
...@@ -543,9 +546,9 @@ test "general client/server API coverage" {...@@ -543,9 +546,9 @@ test "general client/server API coverage" {
543546
544 fn getUnusedTcpPort() !u16 {547 fn getUnusedTcpPort() !u16 {
545 const addr = try net.IpAddress.parse("127.0.0.1", 0);548 const addr = try net.IpAddress.parse("127.0.0.1", 0);
546 var s = try addr.listen(.{});549 var s = try addr.listen(io, .{});
547 defer s.deinit();550 defer s.deinit(io);
548 return s.listen_address.in.getPort();551 return s.socket.address.getPort();
549 }552 }
550 });553 });
551 defer test_server.destroy();554 defer test_server.destroy();
...@@ -553,7 +556,7 @@ test "general client/server API coverage" {...@@ -553,7 +556,7 @@ test "general client/server API coverage" {
553 const log = std.log.scoped(.client);556 const log = std.log.scoped(.client);
554557
555 const gpa = std.testing.allocator;558 const gpa = std.testing.allocator;
556 var client: http.Client = .{ .allocator = gpa };559 var client: http.Client = .{ .allocator = gpa, .io = io };
557 defer client.deinit();560 defer client.deinit();
558561
559 const port = test_server.port();562 const port = test_server.port();
...@@ -918,7 +921,10 @@ test "Server streams both reading and writing" {...@@ -918,7 +921,10 @@ test "Server streams both reading and writing" {
918 });921 });
919 defer test_server.destroy();922 defer test_server.destroy();
920923
921 var client: http.Client = .{ .allocator = std.testing.allocator };924 var client: http.Client = .{
925 .allocator = std.testing.allocator,
926 .io = io,
927 };
922 defer client.deinit();928 defer client.deinit();
923929
924 var redirect_buffer: [555]u8 = undefined;930 var redirect_buffer: [555]u8 = undefined;
...@@ -1089,17 +1095,20 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -1089,17 +1095,20 @@ fn echoTests(client: *http.Client, port: u16) !void {
1089}1095}
10901096
1091const TestServer = struct {1097const TestServer = struct {
1098 io: Io,
1092 shutting_down: bool,1099 shutting_down: bool,
1093 server_thread: std.Thread,1100 server_thread: std.Thread,
1094 net_server: net.Server,1101 net_server: net.Server,
10951102
1096 fn destroy(self: *@This()) void {1103 fn destroy(self: *@This()) void {
1104 const io = self.io;
1097 self.shutting_down = true;1105 self.shutting_down = true;
1098 const conn = net.tcpConnectToAddress(self.net_server.listen_address) catch @panic("shutdown failure");1106 var stream = self.net_server.socket.address.connect(io, .{ .mode = .stream }) catch
1099 conn.close();1107 @panic("shutdown failure");
1108 stream.close(io);
11001109
1101 self.server_thread.join();1110 self.server_thread.join();
1102 self.net_server.deinit();1111 self.net_server.deinit(io);
1103 std.testing.allocator.destroy(self);1112 std.testing.allocator.destroy(self);
1104 }1113 }
11051114
...@@ -1118,6 +1127,7 @@ fn createTestServer(io: Io, S: type) !*TestServer {...@@ -1118,6 +1127,7 @@ fn createTestServer(io: Io, S: type) !*TestServer {
1118 const address = try net.IpAddress.parse("127.0.0.1", 0);1127 const address = try net.IpAddress.parse("127.0.0.1", 0);
1119 const test_server = try std.testing.allocator.create(TestServer);1128 const test_server = try std.testing.allocator.create(TestServer);
1120 test_server.* = .{1129 test_server.* = .{
1130 .io = io,
1121 .net_server = try address.listen(io, .{ .reuse_address = true }),1131 .net_server = try address.listen(io, .{ .reuse_address = true }),
1122 .shutting_down = false,1132 .shutting_down = false,
1123 .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}),1133 .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}),