| author | |
| committer | |
| log | b428612a202a76f7a0aee18bde00c104753f3e60 |
| tree | 93f059f67c5ddee228e5e2e18a81e6bde96b4b1b |
| parent | 774df26835069039ba739828a7619393de01a5f2 |
10 files changed, 339 insertions(+), 250 deletions(-)
lib/std/Io.zig+26-19| ... | ... | @@ -719,31 +719,38 @@ pub const Timestamp = struct { |
| 719 | 719 | /// |
| 720 | 720 | /// The epoch is implementation-defined. For example NTFS/Windows uses |
| 721 | 721 | /// 1601-01-01. |
| 722 | realtime, | |
| 722 | real, | |
| 723 | 723 | /// A nonsettable system-wide clock that represents time since some |
| 724 | 724 | /// unspecified point in the past. |
| 725 | 725 | /// |
| 726 | /// On Linux, corresponds to how long the system has been running since | |
| 727 | /// it booted. | |
| 726 | /// Monotonic: Guarantees that the time returned by consecutive calls | |
| 727 | /// will not go backwards, but successive calls may return identical | |
| 728 | /// (not-increased) time values. | |
| 728 | 729 | /// |
| 729 | 730 | /// Not affected by discontinuous jumps in the system time (e.g., if |
| 730 | /// the system administrator manually changes the clock), but is | |
| 731 | /// affected by frequency adjustments. **This clock does not count time | |
| 732 | /// that the system is suspended.** | |
| 731 | /// the system administrator manually changes the clock), but may be | |
| 732 | /// affected by frequency adjustments. | |
| 733 | 733 | /// |
| 734 | /// Guarantees that the time returned by consecutive calls will not go | |
| 735 | /// backwards, but successive calls may return identical | |
| 736 | /// (not-increased) time values. | |
| 734 | /// This clock expresses intent to **exclude time that the system is | |
| 735 | /// suspended**. However, implementations may be unable to satisify | |
| 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, but | |
| 739 | /// implementations should exclude that time if possible. | |
| 740 | monotonic, | |
| 741 | /// Identical to `monotonic` except it also includes any time that the | |
| 742 | /// system is suspended, if possible. However, it may be implemented | |
| 743 | /// identically to `monotonic`. | |
| 744 | boottime, | |
| 745 | process_cputime_id, | |
| 746 | thread_cputime_id, | |
| 745 | /// * On Linux, corresponds `CLOCK_BOOTTIME`. | |
| 746 | /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`. | |
| 747 | boot, | |
| 748 | /// Tracks the amount of CPU in user or kernel mode used by the calling | |
| 749 | /// process. | |
| 750 | cpu_process, | |
| 751 | /// Tracks the amount of CPU in user or kernel mode used by the calling | |
| 752 | /// thread. | |
| 753 | cpu_thread, | |
| 747 | 754 | }; |
| 748 | 755 | |
| 749 | 756 | pub fn durationTo(from: Timestamp, to: Timestamp) Duration { |
| ... | ... | @@ -825,7 +832,7 @@ pub const Duration = struct { |
| 825 | 832 | } |
| 826 | 833 | |
| 827 | 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 | }; |
| 831 | 838 |
lib/std/Io/File.zig+5| ... | ... | @@ -319,6 +319,11 @@ pub const Reader = struct { |
| 319 | 319 | }; |
| 320 | 320 | } |
| 321 | 321 | |
| 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 | 327 | pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader { |
| 323 | 328 | return .{ |
| 324 | 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 | 1032 | // and uses the NTFS/Windows epoch, which is 1601-01-01. |
| 1033 | 1033 | return @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100; |
| 1034 | 1034 | }, |
| 1035 | .monotonic, .boottime => { | |
| 1035 | .monotonic, .uptime => { | |
| 1036 | 1036 | // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. |
| 1037 | 1037 | return .{ .timestamp = windows.QueryPerformanceCounter() }; |
| 1038 | 1038 | }, |
| ... | ... | @@ -1132,7 +1132,8 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 1132 | 1132 | .sec = std.math.maxInt(sec_type), |
| 1133 | 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 | 1137 | const ns = d.duration.nanoseconds; |
| 1137 | 1138 | break :t .{ |
| 1138 | 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 | 1332 | fn ipConnectPosix( |
| 1332 | 1333 | userdata: ?*anyopaque, |
| 1333 | 1334 | address: *const Io.net.IpAddress, |
| 1334 | options: Io.net.IpAddress.BindOptions, | |
| 1335 | options: Io.net.IpAddress.ConnectOptions, | |
| 1335 | 1336 | ) Io.net.IpAddress.ConnectError!Io.net.Stream { |
| 1337 | if (options.timeout != .none) @panic("TODO"); | |
| 1336 | 1338 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 1337 | 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 | 1344 | var storage: PosixAddress = undefined; |
| 1340 | 1345 | var addr_len = addressToPosix(address, &storage); |
| 1341 | 1346 | try posixConnect(pool, socket_fd, &storage.any, addr_len); |
| ... | ... | @@ -1490,11 +1495,11 @@ fn netSend( |
| 1490 | 1495 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 1491 | 1496 | |
| 1492 | 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 | 1499 | @as(u32, if (flags.dont_route) posix.MSG.DONTROUTE else 0) | |
| 1495 | 1500 | @as(u32, if (flags.eor) posix.MSG.EOR else 0) | |
| 1496 | 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 | 1503 | posix.MSG.NOSIGNAL; |
| 1499 | 1504 | |
| 1500 | 1505 | var i: usize = 0; |
| ... | ... | @@ -2024,11 +2029,17 @@ fn recoverableOsBugDetected() void { |
| 2024 | 2029 | |
| 2025 | 2030 | fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t { |
| 2026 | 2031 | return switch (clock) { |
| 2027 | .realtime => posix.CLOCK.REALTIME, | |
| 2028 | .monotonic => posix.CLOCK.MONOTONIC, | |
| 2029 | .boottime => posix.CLOCK.BOOTTIME, | |
| 2030 | .process_cputime_id => posix.CLOCK.PROCESS_CPUTIME_ID, | |
| 2031 | .thread_cputime_id => posix.CLOCK.THREAD_CPUTIME_ID, | |
| 2032 | .real => posix.CLOCK.REALTIME, | |
| 2033 | .awake => switch (builtin.os.tag) { | |
| 2034 | .macos, .ios, .watchos, .tvos => posix.CLOCK.UPTIME_RAW, | |
| 2035 | else => posix.CLOCK.MONOTONIC, | |
| 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 | } |
| 2034 | 2045 | |
| ... | ... | @@ -2036,7 +2047,7 @@ fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t { |
| 2036 | 2047 | return switch (clock) { |
| 2037 | 2048 | .realtime => .REALTIME, |
| 2038 | 2049 | .monotonic => .MONOTONIC, |
| 2039 | .boottime => .MONOTONIC, | |
| 2050 | .uptime => .MONOTONIC, | |
| 2040 | 2051 | .process_cputime_id => .PROCESS_CPUTIME_ID, |
| 2041 | 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 | 186 | /// Waits for a TCP connection. When using this API, `bind` does not need |
| 187 | 187 | /// to be called. The returned `Server` has an open `stream`. |
| 188 | 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 | } |
| 191 | 191 | |
| 192 | 192 | pub const BindError = error{ |
| ... | ... | @@ -236,6 +236,8 @@ pub const IpAddress = union(enum) { |
| 236 | 236 | AddressInUse, |
| 237 | 237 | AddressUnavailable, |
| 238 | 238 | AddressFamilyUnsupported, |
| 239 | /// Insufficient memory or other resource internal to the operating system. | |
| 240 | SystemResources, | |
| 239 | 241 | ConnectionPending, |
| 240 | 242 | ConnectionRefused, |
| 241 | 243 | ConnectionResetByPeer, |
| ... | ... | @@ -246,12 +248,23 @@ pub const IpAddress = union(enum) { |
| 246 | 248 | /// One of the `ConnectOptions` is not supported by the Io |
| 247 | 249 | /// implementation. |
| 248 | 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; | |
| 250 | 259 | |
| 251 | pub const ConnectOptions = BindOptions; | |
| 260 | pub const ConnectOptions = struct { | |
| 261 | mode: Socket.Mode, | |
| 262 | protocol: ?Protocol = null, | |
| 263 | timeout: Io.Timeout = .none, | |
| 264 | }; | |
| 252 | 265 | |
| 253 | 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 | 268 | return io.vtable.ipConnect(io.userdata, address, options); |
| 256 | 269 | } |
| 257 | 270 | }; |
| ... | ... | @@ -997,7 +1010,7 @@ pub const Stream = struct { |
| 997 | 1010 | socket: Socket, |
| 998 | 1011 | |
| 999 | 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 | 1014 | s.* = undefined; |
| 1002 | 1015 | } |
| 1003 | 1016 | |
| ... | ... | @@ -1040,10 +1053,13 @@ pub const Stream = struct { |
| 1040 | 1053 | return n; |
| 1041 | 1054 | } |
| 1042 | 1055 | |
| 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 | 1057 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); |
| 1045 | 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 | }; |
| 1049 | 1065 | |
| ... | ... | @@ -1078,7 +1094,10 @@ pub const Stream = struct { |
| 1078 | 1094 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); |
| 1079 | 1095 | const io = w.io; |
| 1080 | 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 | 1101 | return io_w.consume(n); |
| 1083 | 1102 | } |
| 1084 | 1103 | }; |
| ... | ... | @@ -1104,7 +1123,7 @@ pub const Server = struct { |
| 1104 | 1123 | |
| 1105 | 1124 | /// Blocks until a client connects to the server. |
| 1106 | 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 | }; |
| 1110 | 1129 |
lib/std/Io/net/HostName.zig+51-13| ... | ... | @@ -19,12 +19,12 @@ bytes: []const u8, |
| 19 | 19 | |
| 20 | 20 | pub const max_len = 255; |
| 21 | 21 | |
| 22 | pub const InitError = error{ | |
| 22 | pub const ValidateError = error{ | |
| 23 | 23 | NameTooLong, |
| 24 | 24 | InvalidHostName, |
| 25 | 25 | }; |
| 26 | 26 | |
| 27 | pub fn init(bytes: []const u8) InitError!HostName { | |
| 27 | pub fn validate(bytes: []const u8) ValidateError!void { | |
| 28 | 28 | if (bytes.len > max_len) return error.NameTooLong; |
| 29 | 29 | if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName; |
| 30 | 30 | for (bytes) |byte| { |
| ... | ... | @@ -33,10 +33,34 @@ pub fn init(bytes: []const u8) InitError!HostName { |
| 33 | 33 | } |
| 34 | 34 | return error.InvalidHostName; |
| 35 | 35 | } |
| 36 | } | |
| 37 | ||
| 38 | pub fn init(bytes: []const u8) ValidateError!HostName { | |
| 39 | try validate(bytes); | |
| 36 | 40 | return .{ .bytes = bytes }; |
| 37 | 41 | } |
| 38 | 42 | |
| 39 | /// TODO add a retry field here | |
| 43 | pub 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 | ||
| 52 | test 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) | |
| 60 | pub fn eql(a: HostName, b: HostName) bool { | |
| 61 | return std.ascii.eqlIgnoreCase(a.bytes, b.bytes); | |
| 62 | } | |
| 63 | ||
| 40 | 64 | pub const LookupOptions = struct { |
| 41 | 65 | port: u16, |
| 42 | 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 | 290 | var answers_remaining = answers.len; |
| 267 | 291 | for (answers) |*answer| answer.len = 0; |
| 268 | 292 | |
| 269 | // boottime is chosen because time the computer is suspended should count | |
| 293 | // boot clock is chosen because time the computer is suspended should count | |
| 270 | 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 | 296 | const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); |
| 273 | 297 | const attempt_duration: Io.Duration = .{ |
| 274 | 298 | .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts, |
| 275 | 299 | }; |
| 276 | 300 | |
| 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 | 302 | const max_messages = queries_buffer.len * ResolvConf.max_nameservers; |
| 279 | 303 | { |
| 280 | 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 | 542 | return n; |
| 519 | 543 | } |
| 520 | 544 | |
| 521 | pub const ExpandError = error{InvalidDnsPacket} || InitError; | |
| 545 | pub const ExpandError = error{InvalidDnsPacket} || ValidateError; | |
| 522 | 546 | |
| 523 | 547 | /// Decompresses a DNS name. |
| 524 | 548 | /// |
| ... | ... | @@ -618,22 +642,36 @@ pub const DnsResponse = struct { |
| 618 | 642 | } |
| 619 | 643 | }; |
| 620 | 644 | |
| 621 | pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError; | |
| 645 | pub const ConnectError = LookupError || IpAddress.ConnectError; | |
| 622 | 646 | |
| 623 | pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream { | |
| 647 | pub fn connect( | |
| 648 | host_name: HostName, | |
| 649 | io: Io, | |
| 650 | port: u16, | |
| 651 | options: IpAddress.ConnectOptions, | |
| 652 | ) ConnectError!Stream { | |
| 624 | 653 | var addresses_buffer: [32]IpAddress = undefined; |
| 654 | var canonical_name_buffer: [HostName.max_len]u8 = undefined; | |
| 625 | 655 | |
| 626 | const results = try lookup(host_name, .{ | |
| 656 | const results = try lookup(host_name, io, .{ | |
| 627 | 657 | .port = port, |
| 628 | 658 | .addresses_buffer = &addresses_buffer, |
| 629 | .canonical_name_buffer = &.{}, | |
| 659 | .canonical_name_buffer = &canonical_name_buffer, | |
| 630 | 660 | }); |
| 631 | 661 | const addresses = addresses_buffer[0..results.addresses_len]; |
| 632 | 662 | |
| 633 | 663 | if (addresses.len == 0) return error.UnknownHostName; |
| 634 | 664 | |
| 635 | for (addresses) |addr| { | |
| 636 | return addr.connectTcp(io) catch |err| switch (err) { | |
| 665 | // TODO instead of serially, use a Select API to send out | |
| 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 | 675 | error.ConnectionRefused => continue, |
| 638 | 676 | else => |e| return e, |
| 639 | 677 | }; |
lib/std/Io/net/test.zig+91-86| ... | ... | @@ -7,32 +7,30 @@ const testing = std.testing; |
| 7 | 7 | test "parse and render IP addresses at comptime" { |
| 8 | 8 | comptime { |
| 9 | 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}); | |
| 11 | 11 | |
| 12 | 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}); | |
| 14 | 14 | |
| 15 | 15 | try testing.expectError(error.ParseFailed, net.IpAddress.parse("::123.123.123.123", 0)); |
| 16 | 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 | } |
| 21 | 19 | |
| 22 | 20 | test "format IPv6 address with no zero runs" { |
| 23 | const addr = try std.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}); | |
| 21 | const addr = try net.IpAddress.parseIp6("2001:db8:1:2:3:4:5:6", 0); | |
| 22 | try testing.expectFmt("[2001:db8:1:2:3:4:5:6]:0", "{f}", .{addr}); | |
| 25 | 23 | } |
| 26 | 24 | |
| 27 | 25 | test "parse IPv6 addresses and check compressed form" { |
| 28 | try std.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), | |
| 26 | try testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{ | |
| 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}", .{ | |
| 32 | try std.net.IpAddress.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0), | |
| 29 | try testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{ | |
| 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}", .{ | |
| 35 | try std.net.IpAddress.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0), | |
| 32 | try testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{ | |
| 33 | try net.IpAddress.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0), | |
| 36 | 34 | }); |
| 37 | 35 | } |
| 38 | 36 | |
| ... | ... | @@ -43,14 +41,14 @@ test "parse IPv6 address, check raw bytes" { |
| 43 | 41 | 0x00, 0x01, 0x00, 0x00, // :0001:0000 |
| 44 | 42 | 0x00, 0x00, 0x00, 0x02, // :0000:0002 |
| 45 | 43 | }; |
| 46 | ||
| 47 | const addr = try std.net.IpAddress.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0); | |
| 48 | ||
| 49 | const actual_raw = addr.in6.sa.addr[0..]; | |
| 50 | try std.testing.expectEqualSlices(u8, expected_raw[0..], actual_raw); | |
| 44 | const addr = try net.IpAddress.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0); | |
| 45 | try testing.expectEqualSlices(u8, &expected_raw, &addr.ip6.bytes); | |
| 51 | 46 | } |
| 52 | 47 | |
| 53 | 48 | test "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 | 52 | var buffer: [100]u8 = undefined; |
| 55 | 53 | const ips = [_][]const u8{ |
| 56 | 54 | "FF01:0:0:0:0:0:0:FB", |
| ... | ... | @@ -79,12 +77,12 @@ test "parse and render IPv6 addresses" { |
| 79 | 77 | for (ips, 0..) |ip, i| { |
| 80 | 78 | const addr = net.IpAddress.parseIp6(ip, 0) catch unreachable; |
| 81 | 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])); | |
| 83 | 81 | |
| 84 | 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 | 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 | } |
| 90 | 88 | |
| ... | ... | @@ -97,21 +95,23 @@ test "parse and render IPv6 addresses" { |
| 97 | 95 | try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("1", 0)); |
| 98 | 96 | // TODO Make this test pass on other operating systems. |
| 99 | 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 | 99 | // 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)); | |
| 103 | try testing.expectError(error.Overflow, net.IpAddress.resolveIp6("ff01::fb%12345678901234", 0)); | |
| 100 | try testing.expectError(error.Overflow, net.IpAddress.resolveIp6(io, "ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0)); | |
| 101 | try testing.expectError(error.Overflow, net.IpAddress.resolveIp6(io, "ff01::fb%12345678901234", 0)); | |
| 104 | 102 | } |
| 105 | 103 | } |
| 106 | 104 | |
| 107 | 105 | test "invalid but parseable IPv6 scope ids" { |
| 106 | const io = testing.io; | |
| 107 | ||
| 108 | 108 | if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) { |
| 109 | 109 | // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux. |
| 110 | 110 | // TODO Make this test pass on other operating systems. |
| 111 | 111 | return error.SkipZigTest; |
| 112 | 112 | } |
| 113 | 113 | |
| 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 | } |
| 116 | 116 | |
| 117 | 117 | test "parse and render IPv4 addresses" { |
| ... | ... | @@ -125,7 +125,7 @@ test "parse and render IPv4 addresses" { |
| 125 | 125 | }) |ip| { |
| 126 | 126 | const addr = net.IpAddress.parseIp4(ip, 0) catch unreachable; |
| 127 | 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 | } |
| 130 | 130 | |
| 131 | 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 | 136 | try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0)); |
| 137 | 137 | } |
| 138 | 138 | |
| 139 | test "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 | ||
| 150 | 139 | test "resolve DNS" { |
| 151 | 140 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 152 | 141 | |
| 153 | if (builtin.os.tag == .windows) { | |
| 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 | } | |
| 142 | const io = testing.io; | |
| 161 | 143 | |
| 162 | 144 | // Resolve localhost, this should not fail. |
| 163 | 145 | { |
| 164 | 146 | const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80); |
| 165 | 147 | const localhost_v6 = try net.IpAddress.parse("::2", 80); |
| 166 | 148 | |
| 167 | const result = try net.getAddressList(testing.allocator, "localhost", 80); | |
| 168 | defer result.deinit(); | |
| 169 | for (result.addrs) |addr| { | |
| 170 | if (addr.eql(localhost_v4) or addr.eql(localhost_v6)) break; | |
| 149 | var addresses_buffer: [8]net.IpAddress = undefined; | |
| 150 | var canon_name_buffer: [net.HostName.max_len]u8 = undefined; | |
| 151 | const result = try net.HostName.lookup(try .init("localhost"), io, .{ | |
| 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 | 158 | } else @panic("unexpected address for localhost"); |
| 172 | 159 | } |
| 173 | 160 | |
| 174 | 161 | { |
| 175 | 162 | // The tests are required to work even when there is no Internet connection, |
| 176 | 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 | 171 | error.UnknownHostName => return error.SkipZigTest, |
| 179 | error.TemporaryNameServerFailure => return error.SkipZigTest, | |
| 172 | error.NameServerFailure => return error.SkipZigTest, | |
| 180 | 173 | else => return err, |
| 181 | 174 | }; |
| 182 | result.deinit(); | |
| 175 | _ = result; | |
| 183 | 176 | } |
| 184 | 177 | } |
| 185 | 178 | |
| ... | ... | @@ -187,6 +180,8 @@ test "listen on a port, send bytes, receive bytes" { |
| 187 | 180 | if (builtin.single_threaded) return error.SkipZigTest; |
| 188 | 181 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 189 | 182 | |
| 183 | const io = testing.io; | |
| 184 | ||
| 190 | 185 | if (builtin.os.tag == .windows) { |
| 191 | 186 | _ = try std.os.windows.WSAStartup(2, 2); |
| 192 | 187 | } |
| ... | ... | @@ -198,28 +193,28 @@ test "listen on a port, send bytes, receive bytes" { |
| 198 | 193 | |
| 199 | 194 | // Try only the IPv4 variant as some CI builders have no IPv6 localhost |
| 200 | 195 | // configured. |
| 201 | const localhost = try net.IpAddress.parse("127.0.0.1", 0); | |
| 196 | const localhost: net.IpAddress = .{ .ip4 = .loopback(0) }; | |
| 202 | 197 | |
| 203 | var server = try localhost.listen(.{}); | |
| 204 | defer server.deinit(); | |
| 198 | var server = try localhost.listen(io, .{}); | |
| 199 | defer server.deinit(io); | |
| 205 | 200 | |
| 206 | 201 | const S = struct { |
| 207 | 202 | fn clientFn(server_address: net.IpAddress) !void { |
| 208 | const socket = try net.tcpConnectToAddress(server_address); | |
| 209 | defer socket.close(); | |
| 203 | var stream = try server_address.connect(io, .{ .mode = .stream }); | |
| 204 | defer stream.close(io); | |
| 210 | 205 | |
| 211 | var stream_writer = socket.writer(&.{}); | |
| 206 | var stream_writer = stream.writer(io, &.{}); | |
| 212 | 207 | try stream_writer.interface.writeAll("Hello world!"); |
| 213 | 208 | } |
| 214 | 209 | }; |
| 215 | 210 | |
| 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 | 212 | defer t.join(); |
| 218 | 213 | |
| 219 | var client = try server.accept(); | |
| 220 | defer client.stream.close(); | |
| 214 | var client = try server.accept(io); | |
| 215 | defer client.stream.close(io); | |
| 221 | 216 | var buf: [16]u8 = undefined; |
| 222 | var stream_reader = client.stream.reader(&.{}); | |
| 217 | var stream_reader = client.stream.reader(io, &.{}); | |
| 223 | 218 | const n = try stream_reader.interface().readSliceShort(&buf); |
| 224 | 219 | |
| 225 | 220 | try testing.expectEqual(@as(usize, 12), n); |
| ... | ... | @@ -232,13 +227,15 @@ test "listen on an in use port" { |
| 232 | 227 | return error.SkipZigTest; |
| 233 | 228 | } |
| 234 | 229 | |
| 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) }; | |
| 236 | 233 | |
| 237 | var server1 = try localhost.listen(.{ .reuse_address = true }); | |
| 238 | defer server1.deinit(); | |
| 234 | var server1 = try localhost.listen(io, .{ .reuse_address = true }); | |
| 235 | defer server1.deinit(io); | |
| 239 | 236 | |
| 240 | var server2 = try server1.listen_address.listen(.{ .reuse_address = true }); | |
| 241 | defer server2.deinit(); | |
| 237 | var server2 = try server1.socket.address.listen(io, .{ .reuse_address = true }); | |
| 238 | defer server2.deinit(io); | |
| 242 | 239 | } |
| 243 | 240 | |
| 244 | 241 | fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void { |
| ... | ... | @@ -268,9 +265,11 @@ fn testClient(addr: net.IpAddress) anyerror!void { |
| 268 | 265 | fn testServer(server: *net.Server) anyerror!void { |
| 269 | 266 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 270 | 267 | |
| 271 | var client = try server.accept(); | |
| 268 | const io = testing.io; | |
| 269 | ||
| 270 | var client = try server.accept(io); | |
| 272 | 271 | |
| 273 | const stream = client.stream.writer(); | |
| 272 | const stream = client.stream.writer(io); | |
| 274 | 273 | try stream.print("hello from server\n", .{}); |
| 275 | 274 | } |
| 276 | 275 | |
| ... | ... | @@ -278,6 +277,8 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 278 | 277 | if (builtin.single_threaded) return error.SkipZigTest; |
| 279 | 278 | if (!net.has_unix_sockets) return error.SkipZigTest; |
| 280 | 279 | |
| 280 | const io = testing.io; | |
| 281 | ||
| 281 | 282 | if (builtin.os.tag == .windows) { |
| 282 | 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 | 294 | const socket_addr = try net.IpAddress.initUnix(socket_path); |
| 294 | 295 | defer std.fs.cwd().deleteFile(socket_path) catch {}; |
| 295 | 296 | |
| 296 | var server = try socket_addr.listen(.{}); | |
| 297 | defer server.deinit(); | |
| 297 | var server = try socket_addr.listen(io, .{}); | |
| 298 | defer server.deinit(io); | |
| 298 | 299 | |
| 299 | 300 | const S = struct { |
| 300 | 301 | fn clientFn(path: []const u8) !void { |
| 301 | const socket = try net.connectUnixSocket(path); | |
| 302 | defer socket.close(); | |
| 302 | var stream = try net.connectUnixSocket(path); | |
| 303 | defer stream.close(io); | |
| 303 | 304 | |
| 304 | var stream_writer = socket.writer(&.{}); | |
| 305 | var stream_writer = stream.writer(io, &.{}); | |
| 305 | 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 | 310 | const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path}); |
| 310 | 311 | defer t.join(); |
| 311 | 312 | |
| 312 | var client = try server.accept(); | |
| 313 | defer client.stream.close(); | |
| 313 | var client = try server.accept(io); | |
| 314 | defer client.stream.close(io); | |
| 314 | 315 | var buf: [16]u8 = undefined; |
| 315 | var stream_reader = client.stream.reader(&.{}); | |
| 316 | var stream_reader = client.stream.reader(io, &.{}); | |
| 316 | 317 | const n = try stream_reader.interface().readSliceShort(&buf); |
| 317 | 318 | |
| 318 | 319 | try testing.expectEqual(@as(usize, 12), n); |
| ... | ... | @@ -324,14 +325,16 @@ test "listen on a unix socket with reuse_address option" { |
| 324 | 325 | // Windows doesn't implement reuse port option. |
| 325 | 326 | if (builtin.os.tag == .windows) return error.SkipZigTest; |
| 326 | 327 | |
| 328 | const io = testing.io; | |
| 329 | ||
| 327 | 330 | const socket_path = try generateFileName("socket.unix"); |
| 328 | 331 | defer testing.allocator.free(socket_path); |
| 329 | 332 | |
| 330 | 333 | const socket_addr = try net.Address.initUnix(socket_path); |
| 331 | 334 | defer std.fs.cwd().deleteFile(socket_path) catch {}; |
| 332 | 335 | |
| 333 | var server = try socket_addr.listen(.{ .reuse_address = true }); | |
| 334 | server.deinit(); | |
| 336 | var server = try socket_addr.listen(io, .{ .reuse_address = true }); | |
| 337 | server.deinit(io); | |
| 335 | 338 | } |
| 336 | 339 | |
| 337 | 340 | fn generateFileName(base_name: []const u8) ![]const u8 { |
| ... | ... | @@ -351,19 +354,21 @@ test "non-blocking tcp server" { |
| 351 | 354 | return error.SkipZigTest; |
| 352 | 355 | } |
| 353 | 356 | |
| 354 | const localhost = try net.IpAddress.parse("127.0.0.1", 0); | |
| 355 | var server = localhost.listen(.{ .force_nonblocking = true }); | |
| 356 | defer server.deinit(); | |
| 357 | const io = testing.io; | |
| 358 | ||
| 359 | const localhost: net.IpAddress = .{ .ip4 = .loopback(0) }; | |
| 360 | var server = localhost.listen(io, .{ .force_nonblocking = true }); | |
| 361 | defer server.deinit(io); | |
| 357 | 362 | |
| 358 | const accept_err = server.accept(); | |
| 363 | const accept_err = server.accept(io); | |
| 359 | 364 | try testing.expectError(error.WouldBlock, accept_err); |
| 360 | 365 | |
| 361 | const socket_file = try net.tcpConnectToAddress(server.listen_address); | |
| 366 | const socket_file = try net.tcpConnectToAddress(server.socket.address); | |
| 362 | 367 | defer socket_file.close(); |
| 363 | 368 | |
| 364 | var client = try server.accept(); | |
| 365 | defer client.stream.close(); | |
| 366 | const stream = client.stream.writer(); | |
| 369 | var client = try server.accept(io); | |
| 370 | defer client.stream.close(io); | |
| 371 | const stream = client.stream.writer(io); | |
| 367 | 372 | try stream.print("hello from server\n", .{}); |
| 368 | 373 | |
| 369 | 374 | var buf: [100]u8 = undefined; |
lib/std/Uri.zig+23-15| ... | ... | @@ -1,45 +1,48 @@ |
| 1 | //! Uniform Resource Identifier (URI) parsing roughly adhering to <https://tools.ietf.org/html/rfc3986>. | |
| 2 | //! Does not do perfect grammar and character class checking, but should be robust against URIs in the wild. | |
| 1 | //! Uniform Resource Identifier (URI) parsing roughly adhering to | |
| 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. | |
| 3 | 4 | |
| 4 | 5 | const std = @import("std.zig"); |
| 5 | 6 | const testing = std.testing; |
| 6 | 7 | const Uri = @This(); |
| 7 | 8 | const Allocator = std.mem.Allocator; |
| 8 | 9 | const Writer = std.Io.Writer; |
| 10 | const HostName = std.Io.net.HostName; | |
| 9 | 11 | |
| 10 | 12 | scheme: []const u8, |
| 11 | 13 | user: ?Component = null, |
| 12 | 14 | password: ?Component = null, |
| 15 | /// If non-null, already validated. | |
| 13 | 16 | host: ?Component = null, |
| 14 | 17 | port: ?u16 = null, |
| 15 | 18 | path: Component = Component.empty, |
| 16 | 19 | query: ?Component = null, |
| 17 | 20 | fragment: ?Component = null, |
| 18 | 21 | |
| 19 | pub const host_name_max = 255; | |
| 22 | pub const GetHostError = error{UriMissingHost}; | |
| 20 | 23 | |
| 21 | 24 | /// Returned value may point into `buffer` or be the original string. |
| 22 | 25 | /// |
| 23 | /// Suggested buffer length: `host_name_max`. | |
| 24 | /// | |
| 25 | 26 | /// See also: |
| 26 | 27 | /// * `getHostAlloc` |
| 27 | pub fn getHost(uri: Uri, buffer: []u8) error{ UriMissingHost, UriHostTooLong }![]const u8 { | |
| 28 | pub fn getHost(uri: Uri, buffer: *[HostName.max_len]u8) GetHostError!HostName { | |
| 28 | 29 | const component = uri.host orelse return error.UriMissingHost; |
| 29 | return component.toRaw(buffer) catch |err| switch (err) { | |
| 30 | error.NoSpaceLeft => return error.UriHostTooLong, | |
| 30 | const bytes = component.toRaw(buffer) catch |err| switch (err) { | |
| 31 | error.NoSpaceLeft => unreachable, // `host` already validated. | |
| 31 | 32 | }; |
| 33 | return .{ .bytes = bytes }; | |
| 32 | 34 | } |
| 33 | 35 | |
| 36 | pub const GetHostAllocError = GetHostError || error{OutOfMemory}; | |
| 37 | ||
| 34 | 38 | /// Returned value may point into `buffer` or be the original string. |
| 35 | 39 | /// |
| 36 | 40 | /// See also: |
| 37 | 41 | /// * `getHost` |
| 38 | pub fn getHostAlloc(uri: Uri, arena: Allocator) error{ UriMissingHost, UriHostTooLong, OutOfMemory }![]const u8 { | |
| 42 | pub fn getHostAlloc(uri: Uri, arena: Allocator) GetHostAllocError![]const u8 { | |
| 39 | 43 | const component = uri.host orelse return error.UriMissingHost; |
| 40 | const result = try component.toRawMaybeAlloc(arena); | |
| 41 | if (result.len > host_name_max) return error.UriHostTooLong; | |
| 42 | return result; | |
| 44 | const bytes = try component.toRawMaybeAlloc(arena); | |
| 45 | return .{ .bytes = bytes }; | |
| 43 | 46 | } |
| 44 | 47 | |
| 45 | 48 | pub const Component = union(enum) { |
| ... | ... | @@ -397,7 +400,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 397 | 400 | .scheme = new_parsed.scheme, |
| 398 | 401 | .user = new_parsed.user, |
| 399 | 402 | .password = new_parsed.password, |
| 400 | .host = new_parsed.host, | |
| 403 | .host = try validateHost(new_parsed.host), | |
| 401 | 404 | .port = new_parsed.port, |
| 402 | 405 | .path = remove_dot_segments(new_path), |
| 403 | 406 | .query = new_parsed.query, |
| ... | ... | @@ -408,7 +411,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 408 | 411 | .scheme = base.scheme, |
| 409 | 412 | .user = new_parsed.user, |
| 410 | 413 | .password = new_parsed.password, |
| 411 | .host = host, | |
| 414 | .host = try validateHost(host), | |
| 412 | 415 | .port = new_parsed.port, |
| 413 | 416 | .path = remove_dot_segments(new_path), |
| 414 | 417 | .query = new_parsed.query, |
| ... | ... | @@ -430,7 +433,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 430 | 433 | .scheme = base.scheme, |
| 431 | 434 | .user = base.user, |
| 432 | 435 | .password = base.password, |
| 433 | .host = base.host, | |
| 436 | .host = try validateHost(base.host), | |
| 434 | 437 | .port = base.port, |
| 435 | 438 | .path = path, |
| 436 | 439 | .query = query, |
| ... | ... | @@ -438,6 +441,11 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 438 | 441 | }; |
| 439 | 442 | } |
| 440 | 443 | |
| 444 | fn validateHost(bytes: []const u8) []const u8 { | |
| 445 | try HostName.validate(bytes); | |
| 446 | return bytes; | |
| 447 | } | |
| 448 | ||
| 441 | 449 | /// In-place implementation of RFC 3986, Section 5.2.4. |
| 442 | 450 | fn remove_dot_segments(path: []u8) Component { |
| 443 | 451 | var in_i: usize = 0; |
lib/std/fs/test.zig+1-1| ... | ... | @@ -2281,7 +2281,7 @@ test "seekTo flushes buffered data" { |
| 2281 | 2281 | } |
| 2282 | 2282 | |
| 2283 | 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); | |
| 2285 | 2285 | |
| 2286 | 2286 | var buf: [4]u8 = undefined; |
| 2287 | 2287 | try file_reader.interface.readSliceAll(&buf); |
lib/std/http/Client.zig+55-69| ... | ... | @@ -15,6 +15,7 @@ const assert = std.debug.assert; |
| 15 | 15 | const Io = std.Io; |
| 16 | 16 | const Writer = std.Io.Writer; |
| 17 | 17 | const Reader = std.Io.Reader; |
| 18 | const HostName = std.Io.net.HostName; | |
| 18 | 19 | |
| 19 | 20 | const Client = @This(); |
| 20 | 21 | |
| ... | ... | @@ -69,7 +70,7 @@ pub const ConnectionPool = struct { |
| 69 | 70 | |
| 70 | 71 | /// The criteria for a connection to be considered a match. |
| 71 | 72 | pub const Criteria = struct { |
| 72 | host: []const u8, | |
| 73 | host: HostName, | |
| 73 | 74 | port: u16, |
| 74 | 75 | protocol: Protocol, |
| 75 | 76 | }; |
| ... | ... | @@ -89,7 +90,7 @@ pub const ConnectionPool = struct { |
| 89 | 90 | if (connection.port != criteria.port) continue; |
| 90 | 91 | |
| 91 | 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; | |
| 93 | 94 | |
| 94 | 95 | pool.acquireUnsafe(connection); |
| 95 | 96 | return connection; |
| ... | ... | @@ -118,19 +119,19 @@ pub const ConnectionPool = struct { |
| 118 | 119 | /// If the connection is marked as closing, it will be closed instead. |
| 119 | 120 | /// |
| 120 | 121 | /// Threadsafe. |
| 121 | pub fn release(pool: *ConnectionPool, connection: *Connection) void { | |
| 122 | pub fn release(pool: *ConnectionPool, connection: *Connection, io: Io) void { | |
| 122 | 123 | pool.mutex.lock(); |
| 123 | 124 | defer pool.mutex.unlock(); |
| 124 | 125 | |
| 125 | 126 | pool.used.remove(&connection.pool_node); |
| 126 | 127 | |
| 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); | |
| 128 | 129 | |
| 129 | 130 | if (pool.free_len >= pool.free_size) { |
| 130 | 131 | const popped: *Connection = @alignCast(@fieldParentPtr("pool_node", pool.free.popFirst().?)); |
| 131 | 132 | pool.free_len -= 1; |
| 132 | 133 | |
| 133 | popped.destroy(); | |
| 134 | popped.destroy(io); | |
| 134 | 135 | } |
| 135 | 136 | |
| 136 | 137 | if (connection.proxied) { |
| ... | ... | @@ -178,21 +179,21 @@ pub const ConnectionPool = struct { |
| 178 | 179 | /// All future operations on the connection pool will deadlock. |
| 179 | 180 | /// |
| 180 | 181 | /// Threadsafe. |
| 181 | pub fn deinit(pool: *ConnectionPool) void { | |
| 182 | pub fn deinit(pool: *ConnectionPool, io: Io) void { | |
| 182 | 183 | pool.mutex.lock(); |
| 183 | 184 | |
| 184 | 185 | var next = pool.free.first; |
| 185 | 186 | while (next) |node| { |
| 186 | 187 | const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node)); |
| 187 | 188 | next = node.next; |
| 188 | connection.destroy(); | |
| 189 | connection.destroy(io); | |
| 189 | 190 | } |
| 190 | 191 | |
| 191 | 192 | next = pool.used.first; |
| 192 | 193 | while (next) |node| { |
| 193 | 194 | const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node)); |
| 194 | 195 | next = node.next; |
| 195 | connection.destroy(); | |
| 196 | connection.destroy(io); | |
| 196 | 197 | } |
| 197 | 198 | |
| 198 | 199 | pool.* = undefined; |
| ... | ... | @@ -242,19 +243,19 @@ pub const Connection = struct { |
| 242 | 243 | |
| 243 | 244 | fn create( |
| 244 | 245 | client: *Client, |
| 245 | remote_host: []const u8, | |
| 246 | remote_host: HostName, | |
| 246 | 247 | port: u16, |
| 247 | 248 | stream: Io.net.Stream, |
| 248 | 249 | ) error{OutOfMemory}!*Plain { |
| 249 | 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 | 252 | const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len); |
| 252 | 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 | 255 | const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size]; |
| 255 | 256 | const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size]; |
| 256 | 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 | 259 | const plain: *Plain = @ptrCast(base); |
| 259 | 260 | plain.* = .{ |
| 260 | 261 | .connection = .{ |
| ... | ... | @@ -263,7 +264,7 @@ pub const Connection = struct { |
| 263 | 264 | .stream_reader = stream.reader(socket_read_buffer), |
| 264 | 265 | .pool_node = .{}, |
| 265 | 266 | .port = port, |
| 266 | .host_len = @intCast(remote_host.len), | |
| 267 | .host_len = @intCast(remote_host.bytes.len), | |
| 267 | 268 | .proxied = false, |
| 268 | 269 | .closing = false, |
| 269 | 270 | .protocol = .plain, |
| ... | ... | @@ -283,9 +284,9 @@ pub const Connection = struct { |
| 283 | 284 | return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size; |
| 284 | 285 | } |
| 285 | 286 | |
| 286 | fn host(plain: *Plain) []u8 { | |
| 287 | fn host(plain: *Plain) HostName { | |
| 287 | 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 | }; |
| 291 | 292 | |
| ... | ... | @@ -295,15 +296,15 @@ pub const Connection = struct { |
| 295 | 296 | |
| 296 | 297 | fn create( |
| 297 | 298 | client: *Client, |
| 298 | remote_host: []const u8, | |
| 299 | remote_host: HostName, | |
| 299 | 300 | port: u16, |
| 300 | 301 | stream: Io.net.Stream, |
| 301 | 302 | ) error{ OutOfMemory, TlsInitializationFailed }!*Tls { |
| 302 | 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 | 305 | const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len); |
| 305 | 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 | 308 | // The TLS client wants enough buffer for the max encrypted frame |
| 308 | 309 | // size, and the HTTP body reader wants enough buffer for the |
| 309 | 310 | // entire HTTP header. This means we need a combined upper bound. |
| ... | ... | @@ -313,7 +314,7 @@ pub const Connection = struct { |
| 313 | 314 | const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size]; |
| 314 | 315 | const socket_read_buffer = socket_write_buffer.ptr[socket_write_buffer.len..][0..client.tls_buffer_size]; |
| 315 | 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 | 318 | const tls: *Tls = @ptrCast(base); |
| 318 | 319 | tls.* = .{ |
| 319 | 320 | .connection = .{ |
| ... | ... | @@ -322,17 +323,17 @@ pub const Connection = struct { |
| 322 | 323 | .stream_reader = stream.reader(socket_read_buffer), |
| 323 | 324 | .pool_node = .{}, |
| 324 | 325 | .port = port, |
| 325 | .host_len = @intCast(remote_host.len), | |
| 326 | .host_len = @intCast(remote_host.bytes.len), | |
| 326 | 327 | .proxied = false, |
| 327 | 328 | .closing = false, |
| 328 | 329 | .protocol = .tls, |
| 329 | 330 | }, |
| 330 | 331 | // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true |
| 331 | 332 | .client = std.crypto.tls.Client.init( |
| 332 | tls.connection.stream_reader.interface(), | |
| 333 | &tls.connection.stream_reader.interface, | |
| 333 | 334 | &tls.connection.stream_writer.interface, |
| 334 | 335 | .{ |
| 335 | .host = .{ .explicit = remote_host }, | |
| 336 | .host = .{ .explicit = remote_host.bytes }, | |
| 336 | 337 | .ca = .{ .bundle = client.ca_bundle }, |
| 337 | 338 | .ssl_key_log = client.ssl_key_log, |
| 338 | 339 | .read_buffer = tls_read_buffer, |
| ... | ... | @@ -359,9 +360,9 @@ pub const Connection = struct { |
| 359 | 360 | client.write_buffer_size + client.tls_buffer_size; |
| 360 | 361 | } |
| 361 | 362 | |
| 362 | fn host(tls: *Tls) []u8 { | |
| 363 | fn host(tls: *Tls) HostName { | |
| 363 | 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 | }; |
| 367 | 368 | |
| ... | ... | @@ -384,7 +385,7 @@ pub const Connection = struct { |
| 384 | 385 | return c.stream_reader.stream; |
| 385 | 386 | } |
| 386 | 387 | |
| 387 | pub fn host(c: *Connection) []u8 { | |
| 388 | pub fn host(c: *Connection) HostName { | |
| 388 | 389 | return switch (c.protocol) { |
| 389 | 390 | .tls => { |
| 390 | 391 | if (disable_tls) unreachable; |
| ... | ... | @@ -400,8 +401,8 @@ pub const Connection = struct { |
| 400 | 401 | |
| 401 | 402 | /// If this is called without calling `flush` or `end`, data will be |
| 402 | 403 | /// dropped unsent. |
| 403 | pub fn destroy(c: *Connection) void { | |
| 404 | c.getStream().close(); | |
| 404 | pub fn destroy(c: *Connection, io: Io) void { | |
| 405 | c.stream_reader.stream.close(io); | |
| 405 | 406 | switch (c.protocol) { |
| 406 | 407 | .tls => { |
| 407 | 408 | if (disable_tls) unreachable; |
| ... | ... | @@ -437,7 +438,7 @@ pub const Connection = struct { |
| 437 | 438 | const tls: *Tls = @alignCast(@fieldParentPtr("connection", c)); |
| 438 | 439 | return &tls.client.reader; |
| 439 | 440 | }, |
| 440 | .plain => c.stream_reader.interface(), | |
| 441 | .plain => &c.stream_reader.interface, | |
| 441 | 442 | }; |
| 442 | 443 | } |
| 443 | 444 | |
| ... | ... | @@ -866,6 +867,7 @@ pub const Request = struct { |
| 866 | 867 | |
| 867 | 868 | /// Returns the request's `Connection` back to the pool of the `Client`. |
| 868 | 869 | pub fn deinit(r: *Request) void { |
| 870 | const io = r.client.io; | |
| 869 | 871 | if (r.connection) |connection| { |
| 870 | 872 | connection.closing = connection.closing or switch (r.reader.state) { |
| 871 | 873 | .ready => false, |
| ... | ... | @@ -880,7 +882,7 @@ pub const Request = struct { |
| 880 | 882 | }, |
| 881 | 883 | else => true, |
| 882 | 884 | }; |
| 883 | r.client.connection_pool.release(connection); | |
| 885 | r.client.connection_pool.release(connection, io); | |
| 884 | 886 | } |
| 885 | 887 | r.* = undefined; |
| 886 | 888 | } |
| ... | ... | @@ -1182,6 +1184,7 @@ pub const Request = struct { |
| 1182 | 1184 | /// |
| 1183 | 1185 | /// `aux_buf` must outlive accesses to `Request.uri`. |
| 1184 | 1186 | fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void { |
| 1187 | const io = r.client.io; | |
| 1185 | 1188 | const new_location = head.location orelse return error.HttpRedirectLocationMissing; |
| 1186 | 1189 | if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize; |
| 1187 | 1190 | const location = aux_buf.*[0..new_location.len]; |
| ... | ... | @@ -1204,13 +1207,13 @@ pub const Request = struct { |
| 1204 | 1207 | const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme; |
| 1205 | 1208 | const old_connection = r.connection.?; |
| 1206 | 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 | 1211 | const new_host = try new_uri.getHost(&new_host_name_buffer); |
| 1209 | 1212 | const keep_privileged_headers = |
| 1210 | 1213 | std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and |
| 1211 | sameParentDomain(old_host, new_host); | |
| 1214 | old_host.sameParentDomain(new_host); | |
| 1212 | 1215 | |
| 1213 | r.client.connection_pool.release(old_connection); | |
| 1216 | r.client.connection_pool.release(old_connection, io); | |
| 1214 | 1217 | r.connection = null; |
| 1215 | 1218 | |
| 1216 | 1219 | if (!keep_privileged_headers) { |
| ... | ... | @@ -1266,7 +1269,7 @@ pub const Request = struct { |
| 1266 | 1269 | |
| 1267 | 1270 | pub const Proxy = struct { |
| 1268 | 1271 | protocol: Protocol, |
| 1269 | host: []const u8, | |
| 1272 | host: HostName, | |
| 1270 | 1273 | authorization: ?[]const u8, |
| 1271 | 1274 | port: u16, |
| 1272 | 1275 | supports_connect: bool, |
| ... | ... | @@ -1277,9 +1280,10 @@ pub const Proxy = struct { |
| 1277 | 1280 | /// All pending requests must be de-initialized and all active connections released |
| 1278 | 1281 | /// before calling this function. |
| 1279 | 1282 | pub fn deinit(client: *Client) void { |
| 1283 | const io = client.io; | |
| 1280 | 1284 | assert(client.connection_pool.used.first == null); // There are still active requests. |
| 1281 | 1285 | |
| 1282 | client.connection_pool.deinit(); | |
| 1286 | client.connection_pool.deinit(io); | |
| 1283 | 1287 | if (!disable_tls) client.ca_bundle.deinit(client.allocator); |
| 1284 | 1288 | |
| 1285 | 1289 | client.* = undefined; |
| ... | ... | @@ -1385,7 +1389,7 @@ pub const basic_authorization = struct { |
| 1385 | 1389 | } |
| 1386 | 1390 | }; |
| 1387 | 1391 | |
| 1388 | pub const ConnectTcpError = Allocator.Error || error{ | |
| 1392 | pub const ConnectTcpError = error{ | |
| 1389 | 1393 | ConnectionRefused, |
| 1390 | 1394 | NetworkUnreachable, |
| 1391 | 1395 | ConnectionTimedOut, |
| ... | ... | @@ -1393,17 +1397,16 @@ pub const ConnectTcpError = Allocator.Error || error{ |
| 1393 | 1397 | TemporaryNameServerFailure, |
| 1394 | 1398 | NameServerFailure, |
| 1395 | 1399 | UnknownHostName, |
| 1396 | HostLacksNetworkAddresses, | |
| 1397 | 1400 | UnexpectedConnectFailure, |
| 1398 | 1401 | TlsInitializationFailed, |
| 1399 | }; | |
| 1402 | } || Allocator.Error || Io.Cancelable; | |
| 1400 | 1403 | |
| 1401 | 1404 | /// Reuses a `Connection` if one matching `host` and `port` is already open. |
| 1402 | 1405 | /// |
| 1403 | 1406 | /// Threadsafe. |
| 1404 | 1407 | pub fn connectTcp( |
| 1405 | 1408 | client: *Client, |
| 1406 | host: []const u8, | |
| 1409 | host: HostName, | |
| 1407 | 1410 | port: u16, |
| 1408 | 1411 | protocol: Protocol, |
| 1409 | 1412 | ) ConnectTcpError!*Connection { |
| ... | ... | @@ -1411,16 +1414,17 @@ pub fn connectTcp( |
| 1411 | 1414 | } |
| 1412 | 1415 | |
| 1413 | 1416 | pub const ConnectTcpOptions = struct { |
| 1414 | host: Io.net.HostName, | |
| 1417 | host: HostName, | |
| 1415 | 1418 | port: u16, |
| 1416 | 1419 | protocol: Protocol, |
| 1417 | 1420 | |
| 1418 | proxied_host: ?[]const u8 = null, | |
| 1421 | proxied_host: ?HostName = null, | |
| 1419 | 1422 | proxied_port: ?u16 = null, |
| 1423 | timeout: Io.Timeout = .none, | |
| 1420 | 1424 | }; |
| 1421 | 1425 | |
| 1422 | 1426 | pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection { |
| 1423 | const host = options.host_name; | |
| 1427 | const host = options.host; | |
| 1424 | 1428 | const port = options.port; |
| 1425 | 1429 | const protocol = options.protocol; |
| 1426 | 1430 | |
| ... | ... | @@ -1433,17 +1437,15 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp |
| 1433 | 1437 | .protocol = protocol, |
| 1434 | 1438 | })) |conn| return conn; |
| 1435 | 1439 | |
| 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 | 1441 | error.ConnectionRefused => return error.ConnectionRefused, |
| 1438 | 1442 | error.NetworkUnreachable => return error.NetworkUnreachable, |
| 1439 | 1443 | error.ConnectionTimedOut => return error.ConnectionTimedOut, |
| 1440 | 1444 | error.ConnectionResetByPeer => return error.ConnectionResetByPeer, |
| 1441 | error.TemporaryNameServerFailure => return error.TemporaryNameServerFailure, | |
| 1442 | 1445 | error.NameServerFailure => return error.NameServerFailure, |
| 1443 | 1446 | error.UnknownHostName => return error.UnknownHostName, |
| 1444 | error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses, | |
| 1445 | 1447 | error.Canceled => return error.Canceled, |
| 1446 | else => return error.UnexpectedConnectFailure, | |
| 1448 | //else => return error.UnexpectedConnectFailure, | |
| 1447 | 1449 | }; |
| 1448 | 1450 | errdefer stream.close(); |
| 1449 | 1451 | |
| ... | ... | @@ -1479,7 +1481,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 1479 | 1481 | errdefer client.allocator.destroy(conn); |
| 1480 | 1482 | conn.* = .{ .data = undefined }; |
| 1481 | 1483 | |
| 1482 | const stream = try std.net.connectUnixSocket(path); | |
| 1484 | const stream = try Io.net.connectUnixSocket(path); | |
| 1483 | 1485 | errdefer stream.close(); |
| 1484 | 1486 | |
| 1485 | 1487 | conn.data = .{ |
| ... | ... | @@ -1504,9 +1506,10 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 1504 | 1506 | pub fn connectProxied( |
| 1505 | 1507 | client: *Client, |
| 1506 | 1508 | proxy: *Proxy, |
| 1507 | proxied_host: []const u8, | |
| 1509 | proxied_host: HostName, | |
| 1508 | 1510 | proxied_port: u16, |
| 1509 | 1511 | ) !*Connection { |
| 1512 | const io = client.io; | |
| 1510 | 1513 | if (!proxy.supports_connect) return error.TunnelNotSupported; |
| 1511 | 1514 | |
| 1512 | 1515 | if (client.connection_pool.findConnection(.{ |
| ... | ... | @@ -1526,12 +1529,12 @@ pub fn connectProxied( |
| 1526 | 1529 | }); |
| 1527 | 1530 | errdefer { |
| 1528 | 1531 | connection.closing = true; |
| 1529 | client.connection_pool.release(connection); | |
| 1532 | client.connection_pool.release(connection, io); | |
| 1530 | 1533 | } |
| 1531 | 1534 | |
| 1532 | 1535 | var req = client.request(.CONNECT, .{ |
| 1533 | 1536 | .scheme = "http", |
| 1534 | .host = .{ .raw = proxied_host }, | |
| 1537 | .host = .{ .raw = proxied_host.bytes }, | |
| 1535 | 1538 | .port = proxied_port, |
| 1536 | 1539 | }, .{ |
| 1537 | 1540 | .redirect_behavior = .unhandled, |
| ... | ... | @@ -1576,7 +1579,7 @@ pub const ConnectError = ConnectTcpError || RequestError; |
| 1576 | 1579 | /// This function is threadsafe. |
| 1577 | 1580 | pub fn connect( |
| 1578 | 1581 | client: *Client, |
| 1579 | host: []const u8, | |
| 1582 | host: HostName, | |
| 1580 | 1583 | port: u16, |
| 1581 | 1584 | protocol: Protocol, |
| 1582 | 1585 | ) ConnectError!*Connection { |
| ... | ... | @@ -1586,9 +1589,7 @@ pub fn connect( |
| 1586 | 1589 | } orelse return client.connectTcp(host, port, protocol); |
| 1587 | 1590 | |
| 1588 | 1591 | // Prevent proxying through itself. |
| 1589 | if (std.ascii.eqlIgnoreCase(proxy.host, host) and | |
| 1590 | proxy.port == port and proxy.protocol == protocol) | |
| 1591 | { | |
| 1592 | if (proxy.host.eql(host) and proxy.port == port and proxy.protocol == protocol) { | |
| 1592 | 1593 | return client.connectTcp(host, port, protocol); |
| 1593 | 1594 | } |
| 1594 | 1595 | |
| ... | ... | @@ -1608,7 +1609,6 @@ pub fn connect( |
| 1608 | 1609 | pub const RequestError = ConnectTcpError || error{ |
| 1609 | 1610 | UnsupportedUriScheme, |
| 1610 | 1611 | UriMissingHost, |
| 1611 | UriHostTooLong, | |
| 1612 | 1612 | CertificateBundleLoadFailure, |
| 1613 | 1613 | }; |
| 1614 | 1614 | |
| ... | ... | @@ -1697,7 +1697,7 @@ pub fn request( |
| 1697 | 1697 | } |
| 1698 | 1698 | |
| 1699 | 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 | 1701 | const host_name = try uri.getHost(&host_name_buffer); |
| 1702 | 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 | 1835 | return .{ .status = response.head.status }; |
| 1836 | 1836 | } |
| 1837 | 1837 | |
| 1838 | pub 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 | ||
| 1845 | test 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 | ||
| 1852 | 1838 | test { |
| 1853 | 1839 | _ = Response; |
| 1854 | 1840 | } |
lib/std/http/test.zig+36-26| ... | ... | @@ -53,7 +53,7 @@ test "trailers" { |
| 53 | 53 | |
| 54 | 54 | const gpa = std.testing.allocator; |
| 55 | 55 | |
| 56 | var client: http.Client = .{ .allocator = gpa }; | |
| 56 | var client: http.Client = .{ .allocator = gpa, .io = io }; | |
| 57 | 57 | defer client.deinit(); |
| 58 | 58 | |
| 59 | 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 | 141 | "0\r\n" ++ |
| 142 | 142 | "\r\n"; |
| 143 | 143 | |
| 144 | const gpa = std.testing.allocator; | |
| 145 | var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); | |
| 144 | const host_name: net.HostName = try .init("127.0.0.1"); | |
| 145 | var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream }); | |
| 146 | 146 | defer stream.close(io); |
| 147 | var stream_writer = stream.writer(&.{}); | |
| 147 | var stream_writer = stream.writer(io, &.{}); | |
| 148 | 148 | try stream_writer.interface.writeAll(request_bytes); |
| 149 | 149 | |
| 150 | const gpa = std.testing.allocator; | |
| 150 | 151 | const expected_response = |
| 151 | 152 | "HTTP/1.1 200 OK\r\n" ++ |
| 152 | 153 | "connection: close\r\n" ++ |
| ... | ... | @@ -154,8 +155,8 @@ test "HTTP server handles a chunked transfer coding request" { |
| 154 | 155 | "content-type: text/plain\r\n" ++ |
| 155 | 156 | "\r\n" ++ |
| 156 | 157 | "message from server!\n"; |
| 157 | var stream_reader = stream.reader(&.{}); | |
| 158 | const response = try stream_reader.interface().allocRemaining(gpa, .limited(expected_response.len + 1)); | |
| 158 | var stream_reader = stream.reader(io, &.{}); | |
| 159 | const response = try stream_reader.interface.allocRemaining(gpa, .limited(expected_response.len + 1)); | |
| 159 | 160 | defer gpa.free(response); |
| 160 | 161 | try expectEqualStrings(expected_response, response); |
| 161 | 162 | } |
| ... | ... | @@ -241,7 +242,7 @@ test "echo content server" { |
| 241 | 242 | defer test_server.destroy(); |
| 242 | 243 | |
| 243 | 244 | { |
| 244 | var client: http.Client = .{ .allocator = std.testing.allocator }; | |
| 245 | var client: http.Client = .{ .allocator = std.testing.allocator, .io = io }; | |
| 245 | 246 | defer client.deinit(); |
| 246 | 247 | |
| 247 | 248 | try echoTests(&client, test_server.port()); |
| ... | ... | @@ -294,14 +295,15 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 294 | 295 | defer test_server.destroy(); |
| 295 | 296 | |
| 296 | 297 | const request_bytes = "GET /foo HTTP/1.1\r\n\r\n"; |
| 297 | const gpa = std.testing.allocator; | |
| 298 | var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); | |
| 298 | const host_name: net.HostName = try .init("127.0.0.1"); | |
| 299 | var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream }); | |
| 299 | 300 | defer stream.close(io); |
| 300 | var stream_writer = stream.writer(&.{}); | |
| 301 | var stream_writer = stream.writer(io, &.{}); | |
| 301 | 302 | try stream_writer.interface.writeAll(request_bytes); |
| 302 | 303 | |
| 303 | var stream_reader = stream.reader(&.{}); | |
| 304 | const response = try stream_reader.interface().allocRemaining(gpa, .unlimited); | |
| 304 | var stream_reader = stream.reader(io, &.{}); | |
| 305 | const gpa = std.testing.allocator; | |
| 306 | const response = try stream_reader.interface.allocRemaining(gpa, .unlimited); | |
| 305 | 307 | defer gpa.free(response); |
| 306 | 308 | |
| 307 | 309 | var expected_response = std.array_list.Managed(u8).init(gpa); |
| ... | ... | @@ -366,14 +368,15 @@ test "receiving arbitrary http headers from the client" { |
| 366 | 368 | "CoNneCtIoN:close\r\n" ++ |
| 367 | 369 | "aoeu: asdf \r\n" ++ |
| 368 | 370 | "\r\n"; |
| 369 | const gpa = std.testing.allocator; | |
| 370 | var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); | |
| 371 | const host_name: net.HostName = try .init("127.0.0.1"); | |
| 372 | var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream }); | |
| 371 | 373 | defer stream.close(io); |
| 372 | var stream_writer = stream.writer(&.{}); | |
| 374 | var stream_writer = stream.writer(io, &.{}); | |
| 373 | 375 | try stream_writer.interface.writeAll(request_bytes); |
| 374 | 376 | |
| 375 | var stream_reader = stream.reader(&.{}); | |
| 376 | const response = try stream_reader.interface().allocRemaining(gpa, .unlimited); | |
| 377 | var stream_reader = stream.reader(io, &.{}); | |
| 378 | const gpa = std.testing.allocator; | |
| 379 | const response = try stream_reader.interface.allocRemaining(gpa, .unlimited); | |
| 377 | 380 | defer gpa.free(response); |
| 378 | 381 | |
| 379 | 382 | var expected_response = std.array_list.Managed(u8).init(gpa); |
| ... | ... | @@ -413,7 +416,7 @@ test "general client/server API coverage" { |
| 413 | 416 | else => |e| return e, |
| 414 | 417 | }; |
| 415 | 418 | |
| 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 | 546 | |
| 544 | 547 | fn getUnusedTcpPort() !u16 { |
| 545 | 548 | const addr = try net.IpAddress.parse("127.0.0.1", 0); |
| 546 | var s = try addr.listen(.{}); | |
| 547 | defer s.deinit(); | |
| 548 | return s.listen_address.in.getPort(); | |
| 549 | var s = try addr.listen(io, .{}); | |
| 550 | defer s.deinit(io); | |
| 551 | return s.socket.address.getPort(); | |
| 549 | 552 | } |
| 550 | 553 | }); |
| 551 | 554 | defer test_server.destroy(); |
| ... | ... | @@ -553,7 +556,7 @@ test "general client/server API coverage" { |
| 553 | 556 | const log = std.log.scoped(.client); |
| 554 | 557 | |
| 555 | 558 | const gpa = std.testing.allocator; |
| 556 | var client: http.Client = .{ .allocator = gpa }; | |
| 559 | var client: http.Client = .{ .allocator = gpa, .io = io }; | |
| 557 | 560 | defer client.deinit(); |
| 558 | 561 | |
| 559 | 562 | const port = test_server.port(); |
| ... | ... | @@ -918,7 +921,10 @@ test "Server streams both reading and writing" { |
| 918 | 921 | }); |
| 919 | 922 | defer test_server.destroy(); |
| 920 | 923 | |
| 921 | var client: http.Client = .{ .allocator = std.testing.allocator }; | |
| 924 | var client: http.Client = .{ | |
| 925 | .allocator = std.testing.allocator, | |
| 926 | .io = io, | |
| 927 | }; | |
| 922 | 928 | defer client.deinit(); |
| 923 | 929 | |
| 924 | 930 | var redirect_buffer: [555]u8 = undefined; |
| ... | ... | @@ -1089,17 +1095,20 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1089 | 1095 | } |
| 1090 | 1096 | |
| 1091 | 1097 | const TestServer = struct { |
| 1098 | io: Io, | |
| 1092 | 1099 | shutting_down: bool, |
| 1093 | 1100 | server_thread: std.Thread, |
| 1094 | 1101 | net_server: net.Server, |
| 1095 | 1102 | |
| 1096 | 1103 | fn destroy(self: *@This()) void { |
| 1104 | const io = self.io; | |
| 1097 | 1105 | self.shutting_down = true; |
| 1098 | const conn = net.tcpConnectToAddress(self.net_server.listen_address) catch @panic("shutdown failure"); | |
| 1099 | conn.close(); | |
| 1106 | var stream = self.net_server.socket.address.connect(io, .{ .mode = .stream }) catch | |
| 1107 | @panic("shutdown failure"); | |
| 1108 | stream.close(io); | |
| 1100 | 1109 | |
| 1101 | 1110 | self.server_thread.join(); |
| 1102 | self.net_server.deinit(); | |
| 1111 | self.net_server.deinit(io); | |
| 1103 | 1112 | std.testing.allocator.destroy(self); |
| 1104 | 1113 | } |
| 1105 | 1114 | |
| ... | ... | @@ -1118,6 +1127,7 @@ fn createTestServer(io: Io, S: type) !*TestServer { |
| 1118 | 1127 | const address = try net.IpAddress.parse("127.0.0.1", 0); |
| 1119 | 1128 | const test_server = try std.testing.allocator.create(TestServer); |
| 1120 | 1129 | test_server.* = .{ |
| 1130 | .io = io, | |
| 1121 | 1131 | .net_server = try address.listen(io, .{ .reuse_address = true }), |
| 1122 | 1132 | .shutting_down = false, |
| 1123 | 1133 | .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}), |