authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-23 16:52:03-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
log396464ee6b8f5534947e5f457dbd2a26a458cb0f
tree0b31766335cc1bc733d130e0cc00c782aacdf7b4
parent1bb75f9d6276ddf6b69717d9b1c2f68140727425

update std.net and nail down delimiter APIs

"exclusive" functions still need to report EndOfStream after the last returned slice

7 files changed, 441 insertions(+), 372 deletions(-)

lib/std/compress/flate/inflate.zig+1-1
...@@ -681,7 +681,7 @@ pub fn BitReader(comptime T: type) type {...@@ -681,7 +681,7 @@ pub fn BitReader(comptime T: type) type {
681 (self.nbits >> 3); // 0 for 0-7, 1 for 8-16, ... same as / 8681 (self.nbits >> 3); // 0 for 0-7, 1 for 8-16, ... same as / 8
682682
683 var buf: [t_bytes]u8 = [_]u8{0} ** t_bytes;683 var buf: [t_bytes]u8 = [_]u8{0} ** t_bytes;
684 const bytes_read = self.forward_reader.readShort(buf[0..empty_bytes]) catch 0;684 const bytes_read = self.forward_reader.readSliceShort(buf[0..empty_bytes]) catch 0;
685 if (bytes_read > 0) {685 if (bytes_read > 0) {
686 const u: T = std.mem.readInt(T, buf[0..t_bytes], .little);686 const u: T = std.mem.readInt(T, buf[0..t_bytes], .little);
687 self.bits |= u << @as(Tshift, @intCast(self.nbits));687 self.bits |= u << @as(Tshift, @intCast(self.nbits));
lib/std/crypto/Certificate/Bundle.zig+6-2
...@@ -225,7 +225,9 @@ pub const AddCertsFromFileError = Allocator.Error ||...@@ -225,7 +225,9 @@ pub const AddCertsFromFileError = Allocator.Error ||
225 error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker };225 error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker };
226226
227pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFromFileError!void {227pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFromFileError!void {
228 const size = try file.getEndPos();228 var file_reader = file.reader();
229 const size = try file_reader.getSize();
230 var br = file_reader.interface().unbuffered();
229231
230 // We borrow `bytes` as a temporary buffer for the base64-encoded data.232 // We borrow `bytes` as a temporary buffer for the base64-encoded data.
231 // This is possible by computing the decoded length and reserving the space233 // This is possible by computing the decoded length and reserving the space
...@@ -236,7 +238,9 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFrom...@@ -236,7 +238,9 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFrom
236 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);238 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);
237 const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);239 const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);
238 const buffer = cb.bytes.allocatedSlice()[end_reserved..];240 const buffer = cb.bytes.allocatedSlice()[end_reserved..];
239 const end_index = try file.readShort(buffer);241 const end_index = br.readSliceShort(buffer) catch |err| switch (err) {
242 error.ReadFailed => return file_reader.err.?,
243 };
240 const encoded_bytes = buffer[0..end_index];244 const encoded_bytes = buffer[0..end_index];
241245
242 const begin_marker = "-----BEGIN CERTIFICATE-----";246 const begin_marker = "-----BEGIN CERTIFICATE-----";
lib/std/fs/Dir.zig+4-18
...@@ -1947,22 +1947,6 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {...@@ -1947,22 +1947,6 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
1947 return windows.ReadLink(self.fd, sub_path_w, buffer);1947 return windows.ReadLink(self.fd, sub_path_w, buffer);
1948}1948}
19491949
1950/// Read all of file contents using a preallocated buffer.
1951/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
1952/// the situation is ambiguous. It could either mean that the entire file was read, and
1953/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
1954/// entire file.
1955/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1956/// On WASI, `file_path` should be encoded as valid UTF-8.
1957/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1958pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
1959 var file = try self.openFile(file_path, .{});
1960 defer file.close();
1961
1962 const end_index = try file.readAll(buffer);
1963 return buffer[0..end_index];
1964}
1965
1966pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{StreamTooLong};1950pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{StreamTooLong};
19671951
1968/// Reads all the bytes from the named file. On success, caller owns returned1952/// Reads all the bytes from the named file. On success, caller owns returned
...@@ -2046,10 +2030,13 @@ pub fn readFileIntoArrayList(...@@ -2046,10 +2030,13 @@ pub fn readFileIntoArrayList(
2046 var file = try dir.openFile(file_path, .{});2030 var file = try dir.openFile(file_path, .{});
2047 defer file.close();2031 defer file.close();
20482032
2033 var file_reader = file.reader();
2034
2049 // Apply size hint by adjusting the array list's capacity.2035 // Apply size hint by adjusting the array list's capacity.
2050 if (size_hint) |size| {2036 if (size_hint) |size| {
2051 try list.ensureUnusedCapacity(gpa, size);2037 try list.ensureUnusedCapacity(gpa, size);
2052 } else if (file.getEndPos()) |size| {2038 file_reader.size = size;
2039 } else if (file_reader.getSize()) |size| {
2053 // If the file size doesn't fit a usize it'll be certainly exceed the limit.2040 // If the file size doesn't fit a usize it'll be certainly exceed the limit.
2054 try list.ensureUnusedCapacity(gpa, std.math.cast(usize, size) orelse return error.StreamTooLong);2041 try list.ensureUnusedCapacity(gpa, std.math.cast(usize, size) orelse return error.StreamTooLong);
2055 } else |err| switch (err) {2042 } else |err| switch (err) {
...@@ -2058,7 +2045,6 @@ pub fn readFileIntoArrayList(...@@ -2058,7 +2045,6 @@ pub fn readFileIntoArrayList(
2058 else => |e| return e,2045 else => |e| return e,
2059 }2046 }
20602047
2061 var file_reader = file.reader();
2062 file_reader.interface().readRemainingArrayList(gpa, alignment, list, limit) catch |err| switch (err) {2048 file_reader.interface().readRemainingArrayList(gpa, alignment, list, limit) catch |err| switch (err) {
2063 error.OutOfMemory => return error.OutOfMemory,2049 error.OutOfMemory => return error.OutOfMemory,
2064 error.StreamTooLong => return error.StreamTooLong,2050 error.StreamTooLong => return error.StreamTooLong,
lib/std/fs/File.zig+13-14
...@@ -799,20 +799,6 @@ pub fn read(self: File, buffer: []u8) ReadError!usize {...@@ -799,20 +799,6 @@ pub fn read(self: File, buffer: []u8) ReadError!usize {
799 return posix.read(self.handle, buffer);799 return posix.read(self.handle, buffer);
800}800}
801801
802/// One-shot alternative to `std.io.BufferedReader.readShort` via `reader`.
803///
804/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
805/// means the file reached the end.
806pub fn readShort(self: File, buffer: []u8) ReadError!usize {
807 var index: usize = 0;
808 while (index != buffer.len) {
809 const n = try self.read(buffer[index..]);
810 if (n == 0) break;
811 index += n;
812 }
813 return index;
814}
815
816/// On Windows, this function currently does alter the file pointer.802/// On Windows, this function currently does alter the file pointer.
817/// https://github.com/ziglang/zig/issues/12783803/// https://github.com/ziglang/zig/issues/12783
818pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {804pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
...@@ -948,6 +934,19 @@ pub const Reader = struct {...@@ -948,6 +934,19 @@ pub const Reader = struct {
948 };934 };
949 }935 }
950936
937 pub fn getSize(r: *Reader) GetEndPosError!u64 {
938 return r.size orelse {
939 if (r.size_err) |err| return err;
940 if (r.file.getEndPos()) |size| {
941 r.size = size;
942 return size;
943 } else |err| {
944 r.size_err = err;
945 return err;
946 }
947 };
948 }
949
951 /// Number of slices to store on the stack, when trying to send as many byte950 /// Number of slices to store on the stack, when trying to send as many byte
952 /// vectors through the underlying read calls as possible.951 /// vectors through the underlying read calls as possible.
953 const max_buffers_len = 16;952 const max_buffers_len = 16;
lib/std/http/test.zig+14-8
...@@ -113,8 +113,9 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -113,8 +113,9 @@ test "HTTP server handles a chunked transfer coding request" {
113 try expect(request.head.transfer_encoding == .chunked);113 try expect(request.head.transfer_encoding == .chunked);
114114
115 var buf: [128]u8 = undefined;115 var buf: [128]u8 = undefined;
116 const n = try (try request.reader()).readAll(&buf);116 var br = (try request.reader()).unbuffered();
117 try expect(mem.eql(u8, buf[0..n], "ABCD"));117 const n = try br.readSliceShort(&buf);
118 try expectEqualStrings("ABCD", buf[0..n]);
118119
119 try request.respond("message from server!\n", .{120 try request.respond("message from server!\n", .{
120 .extra_headers = &.{121 .extra_headers = &.{
...@@ -143,7 +144,8 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -143,7 +144,8 @@ test "HTTP server handles a chunked transfer coding request" {
143 const gpa = std.testing.allocator;144 const gpa = std.testing.allocator;
144 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());145 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
145 defer stream.close();146 defer stream.close();
146 var writer = stream.writer().unbuffered();147 var stream_writer = stream.writer();
148 var writer = stream_writer.interface().unbuffered();
147 try writer.writeAll(request_bytes);149 try writer.writeAll(request_bytes);
148150
149 const expected_response =151 const expected_response =
...@@ -153,7 +155,8 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -153,7 +155,8 @@ test "HTTP server handles a chunked transfer coding request" {
153 "content-type: text/plain\r\n" ++155 "content-type: text/plain\r\n" ++
154 "\r\n" ++156 "\r\n" ++
155 "message from server!\n";157 "message from server!\n";
156 const response = try stream.reader().readRemainingAlloc(gpa, expected_response.len);158 var stream_reader = stream.reader();
159 const response = try stream_reader.interface().readRemainingAlloc(gpa, .limited(expected_response.len));
157 defer gpa.free(response);160 defer gpa.free(response);
158 try expectEqualStrings(expected_response, response);161 try expectEqualStrings(expected_response, response);
159}162}
...@@ -206,7 +209,7 @@ test "echo content server" {...@@ -206,7 +209,7 @@ test "echo content server" {
206 // request.head.target,209 // request.head.target,
207 //});210 //});
208211
209 const body = try (try request.reader()).readRemainingAlloc(std.testing.allocator, 8192);212 const body = try (try request.reader()).readRemainingAlloc(std.testing.allocator, .limited(8192));
210 defer std.testing.allocator.free(body);213 defer std.testing.allocator.free(body);
211214
212 try expect(mem.startsWith(u8, request.head.target, "/echo-content"));215 try expect(mem.startsWith(u8, request.head.target, "/echo-content"));
...@@ -288,10 +291,12 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -288,10 +291,12 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
288 const gpa = std.testing.allocator;291 const gpa = std.testing.allocator;
289 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());292 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
290 defer stream.close();293 defer stream.close();
291 var writer = stream.writer().unbuffered();294 var stream_writer = stream.writer();
295 var writer = stream_writer.interface().unbuffered();
292 try writer.writeAll(request_bytes);296 try writer.writeAll(request_bytes);
293297
294 const response = try stream.reader().readRemainingAlloc(gpa, 8192);298 var stream_reader = stream.reader();
299 const response = try stream_reader.interface().readRemainingAlloc(gpa, .limited(8192));
295 defer gpa.free(response);300 defer gpa.free(response);
296301
297 var expected_response = std.ArrayList(u8).init(gpa);302 var expected_response = std.ArrayList(u8).init(gpa);
...@@ -362,7 +367,8 @@ test "receiving arbitrary http headers from the client" {...@@ -362,7 +367,8 @@ test "receiving arbitrary http headers from the client" {
362 var writer = stream_writer.interface().unbuffered();367 var writer = stream_writer.interface().unbuffered();
363 try writer.writeAll(request_bytes);368 try writer.writeAll(request_bytes);
364369
365 const response = try stream.reader().readRemainingAlloc(gpa, .limited(8192));370 var stream_reader = stream.reader();
371 const response = try stream_reader.interface().readRemainingAlloc(gpa, .limited(8192));
366 defer gpa.free(response);372 defer gpa.free(response);
367373
368 var expected_response = std.ArrayList(u8).init(gpa);374 var expected_response = std.ArrayList(u8).init(gpa);
lib/std/io/BufferedReader.zig+82-56
...@@ -368,7 +368,7 @@ pub fn readSlice(br: *BufferedReader, buffer: []u8) Reader.Error!void {...@@ -368,7 +368,7 @@ pub fn readSlice(br: *BufferedReader, buffer: []u8) Reader.Error!void {
368368
369/// Returns the number of bytes read, which is less than `buffer.len` if and369/// Returns the number of bytes read, which is less than `buffer.len` if and
370/// only if the stream reached the end.370/// only if the stream reached the end.
371pub fn readShort(br: *BufferedReader, buffer: []u8) Reader.ShortError!usize {371pub fn readSliceShort(br: *BufferedReader, buffer: []u8) Reader.ShortError!usize {
372 _ = br;372 _ = br;
373 _ = buffer;373 _ = buffer;
374 @panic("TODO");374 @panic("TODO");
...@@ -467,10 +467,12 @@ pub fn readRemainingArrayList(...@@ -467,10 +467,12 @@ pub fn readRemainingArrayList(
467 }467 }
468}468}
469469
470pub const DelimiterInclusiveError = error{470pub const DelimiterError = error{
471 /// See the `Reader` implementation for detailed diagnostics.471 /// See the `Reader` implementation for detailed diagnostics.
472 ReadFailed,472 ReadFailed,
473 /// Stream ended before the delimiter was found.473 /// For "inclusive" functions, stream ended before the delimiter was found.
474 /// For "exclusive" functions, stream ended and there are no more bytes to
475 /// return.
474 EndOfStream,476 EndOfStream,
475 /// The delimiter was not found within a number of bytes matching the477 /// The delimiter was not found within a number of bytes matching the
476 /// capacity of the `BufferedReader`.478 /// capacity of the `BufferedReader`.
...@@ -488,13 +490,13 @@ pub const DelimiterInclusiveError = error{...@@ -488,13 +490,13 @@ pub const DelimiterInclusiveError = error{
488/// * `peekSentinel`490/// * `peekSentinel`
489/// * `takeDelimiterExclusive`491/// * `takeDelimiterExclusive`
490/// * `takeDelimiterInclusive`492/// * `takeDelimiterInclusive`
491pub fn takeSentinel(br: *BufferedReader, comptime sentinel: u8) DelimiterInclusiveError![:sentinel]u8 {493pub fn takeSentinel(br: *BufferedReader, comptime sentinel: u8) DelimiterError![:sentinel]u8 {
492 const result = try br.peekSentinel(sentinel);494 const result = try br.peekSentinel(sentinel);
493 br.toss(result.len + 1);495 br.toss(result.len + 1);
494 return result;496 return result;
495}497}
496498
497pub fn peekSentinel(br: *BufferedReader, comptime sentinel: u8) DelimiterInclusiveError![:sentinel]u8 {499pub fn peekSentinel(br: *BufferedReader, comptime sentinel: u8) DelimiterError![:sentinel]u8 {
498 const result = try br.takeDelimiterInclusive(sentinel);500 const result = try br.takeDelimiterInclusive(sentinel);
499 return result[0 .. result.len - 1 :sentinel];501 return result[0 .. result.len - 1 :sentinel];
500}502}
...@@ -510,58 +512,24 @@ pub fn peekSentinel(br: *BufferedReader, comptime sentinel: u8) DelimiterInclusi...@@ -510,58 +512,24 @@ pub fn peekSentinel(br: *BufferedReader, comptime sentinel: u8) DelimiterInclusi
510/// * `takeSentinel`512/// * `takeSentinel`
511/// * `takeDelimiterExclusive`513/// * `takeDelimiterExclusive`
512/// * `peekDelimiterInclusive`514/// * `peekDelimiterInclusive`
513pub fn takeDelimiterInclusive(br: *BufferedReader, delimiter: u8) DelimiterInclusiveError![]u8 {515pub fn takeDelimiterInclusive(br: *BufferedReader, delimiter: u8) DelimiterError![]u8 {
514 const result = try br.peekDelimiterInclusive(delimiter);516 const result = try br.peekDelimiterInclusive(delimiter);
515 br.toss(result.len);517 br.toss(result.len);
516 return result;518 return result;
517}519}
518520
519pub fn peekDelimiterInclusive(br: *BufferedReader, delimiter: u8) DelimiterInclusiveError![]u8 {
520 return (try br.peekDelimiterInclusiveUnlessEnd(delimiter)) orelse error.EndOfStream;
521}
522
523pub const DelimiterExclusiveError = error{
524 /// See the `Reader` implementation for detailed diagnostics.
525 ReadFailed,
526 /// The delimiter was not found within a number of bytes matching the
527 /// capacity of the `BufferedReader`.
528 StreamTooLong,
529};
530
531/// Returns a slice of the next bytes of buffered data from the stream until521/// Returns a slice of the next bytes of buffered data from the stream until
532/// `delimiter` is found, advancing the seek position.522/// `delimiter` is found, without advancing the seek position.
533///
534/// Returned slice excludes the delimiter.
535///523///
536/// End-of-stream is treated equivalent to a delimiter.524/// Returned slice includes the delimiter as the last byte.
537///525///
538/// Invalidates previously returned values from `peek`.526/// Invalidates previously returned values from `peek`.
539///527///
540/// See also:528/// See also:
541/// * `takeSentinel`529/// * `peekSentinel`
542/// * `takeDelimiterInclusive`
543/// * `peekDelimiterExclusive`530/// * `peekDelimiterExclusive`
544pub fn takeDelimiterExclusive(br: *BufferedReader, delimiter: u8) DelimiterExclusiveError![]u8 {531/// * `takeDelimiterInclusive`
545 const result = br.peekDelimiterInclusiveUnlessEnd(delimiter) catch |err| switch (err) {532pub fn peekDelimiterInclusive(br: *BufferedReader, delimiter: u8) DelimiterError![]u8 {
546 error.EndOfStream => {
547 br.toss(br.end);
548 return br.buffer[0..br.end];
549 },
550 else => |e| return e,
551 };
552 br.toss(result.len);
553 return result[0 .. result.len - 1];
554}
555
556pub fn peekDelimiterExclusive(br: *BufferedReader, delimiter: u8) DelimiterExclusiveError![]u8 {
557 const result = br.peekDelimiterInclusiveUnlessEnd(delimiter) catch |err| switch (err) {
558 error.EndOfStream => return br.buffer[0..br.end],
559 else => |e| return e,
560 };
561 return result[0 .. result.len - 1];
562}
563
564fn peekDelimiterInclusiveUnlessEnd(br: *BufferedReader, delimiter: u8) DelimiterInclusiveError!?[]u8 {
565 const buffer = br.buffer[0..br.end];533 const buffer = br.buffer[0..br.end];
566 const seek = br.seek;534 const seek = br.seek;
567 if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |end| {535 if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |end| {
...@@ -585,12 +553,70 @@ fn peekDelimiterInclusiveUnlessEnd(br: *BufferedReader, delimiter: u8) Delimiter...@@ -585,12 +553,70 @@ fn peekDelimiterInclusiveUnlessEnd(br: *BufferedReader, delimiter: u8) Delimiter
585 return error.StreamTooLong;553 return error.StreamTooLong;
586}554}
587555
556/// Returns a slice of the next bytes of buffered data from the stream until
557/// `delimiter` is found, advancing the seek position.
558///
559/// Returned slice excludes the delimiter. End-of-stream is treated equivalent
560/// to a delimiter, unless it would result in a length 0 return value, in which
561/// case `error.EndOfStream` is returned instead.
562///
563/// If the delimiter is not found within a number of bytes matching the
564/// capacity of this `BufferedReader`, `error.StreamTooLong` is returned. In
565/// such case, the stream state is unmodified as if this function was never
566/// called.
567///
568/// Invalidates previously returned values from `peek`.
569///
570/// See also:
571/// * `takeDelimiterInclusive`
572/// * `peekDelimiterExclusive`
573pub fn takeDelimiterExclusive(br: *BufferedReader, delimiter: u8) DelimiterError![]u8 {
574 const result = br.peekDelimiterInclusive(delimiter) catch |err| switch (err) {
575 error.EndOfStream => {
576 if (br.end == 0) return error.EndOfStream;
577 br.toss(br.end);
578 return br.buffer[0..br.end];
579 },
580 else => |e| return e,
581 };
582 br.toss(result.len);
583 return result[0 .. result.len - 1];
584}
585
586/// Returns a slice of the next bytes of buffered data from the stream until
587/// `delimiter` is found, without advancing the seek position.
588///
589/// Returned slice excludes the delimiter. End-of-stream is treated equivalent
590/// to a delimiter, unless it would result in a length 0 return value, in which
591/// case `error.EndOfStream` is returned instead.
592///
593/// If the delimiter is not found within a number of bytes matching the
594/// capacity of this `BufferedReader`, `error.StreamTooLong` is returned. In
595/// such case, the stream state is unmodified as if this function was never
596/// called.
597///
598/// Invalidates previously returned values from `peek`.
599///
600/// See also:
601/// * `peekDelimiterInclusive`
602/// * `takeDelimiterExclusive`
603pub fn peekDelimiterExclusive(br: *BufferedReader, delimiter: u8) DelimiterError![]u8 {
604 const result = br.peekDelimiterInclusive(delimiter) catch |err| switch (err) {
605 error.EndOfStream => {
606 if (br.end == 0) return error.EndOfStream;
607 return br.buffer[0..br.end];
608 },
609 else => |e| return e,
610 };
611 return result[0 .. result.len - 1];
612}
613
588/// Appends to `bw` contents by reading from the stream until `delimiter` is614/// Appends to `bw` contents by reading from the stream until `delimiter` is
589/// found. Does not write the delimiter itself.615/// found. Does not write the delimiter itself.
590///616///
591/// Returns number of bytes streamed.617/// Returns number of bytes streamed.
592pub fn streamToDelimiter(br: *BufferedReader, bw: *BufferedWriter, delimiter: u8) Reader.RwError!usize {618pub fn readDelimiter(br: *BufferedReader, bw: *BufferedWriter, delimiter: u8) Reader.RwError!usize {
593 const amount, const to = try br.streamToAny(bw, delimiter, .unlimited);619 const amount, const to = try br.readAny(bw, delimiter, .unlimited);
594 return switch (to) {620 return switch (to) {
595 .delimiter => amount,621 .delimiter => amount,
596 .limit => unreachable,622 .limit => unreachable,
...@@ -604,12 +630,12 @@ pub fn streamToDelimiter(br: *BufferedReader, bw: *BufferedWriter, delimiter: u8...@@ -604,12 +630,12 @@ pub fn streamToDelimiter(br: *BufferedReader, bw: *BufferedWriter, delimiter: u8
604/// Succeeds if stream ends before delimiter found.630/// Succeeds if stream ends before delimiter found.
605///631///
606/// Returns number of bytes streamed. The end is not signaled to the writer.632/// Returns number of bytes streamed. The end is not signaled to the writer.
607pub fn streamToDelimiterOrEnd(633pub fn readDelimiterEnding(
608 br: *BufferedReader,634 br: *BufferedReader,
609 bw: *BufferedWriter,635 bw: *BufferedWriter,
610 delimiter: u8,636 delimiter: u8,
611) Reader.RwAllError!usize {637) Reader.RwAllError!usize {
612 const amount, const to = try br.streamToAny(bw, delimiter, .unlimited);638 const amount, const to = try br.readAny(bw, delimiter, .unlimited);
613 return switch (to) {639 return switch (to) {
614 .delimiter, .end => amount,640 .delimiter, .end => amount,
615 .limit => unreachable,641 .limit => unreachable,
...@@ -627,13 +653,13 @@ pub const StreamDelimiterLimitedError = Reader.RwAllError || error{...@@ -627,13 +653,13 @@ pub const StreamDelimiterLimitedError = Reader.RwAllError || error{
627/// Does not write the delimiter itself.653/// Does not write the delimiter itself.
628///654///
629/// Returns number of bytes streamed.655/// Returns number of bytes streamed.
630pub fn streamToDelimiterOrLimit(656pub fn readDelimiterLimit(
631 br: *BufferedReader,657 br: *BufferedReader,
632 bw: *BufferedWriter,658 bw: *BufferedWriter,
633 delimiter: u8,659 delimiter: u8,
634 limit: Reader.Limit,660 limit: Reader.Limit,
635) StreamDelimiterLimitedError!usize {661) StreamDelimiterLimitedError!usize {
636 const amount, const to = try br.streamToAny(bw, delimiter, limit);662 const amount, const to = try br.readAny(bw, delimiter, limit);
637 return switch (to) {663 return switch (to) {
638 .delimiter => amount,664 .delimiter => amount,
639 .limit => error.StreamTooLong,665 .limit => error.StreamTooLong,
...@@ -641,7 +667,7 @@ pub fn streamToDelimiterOrLimit(...@@ -641,7 +667,7 @@ pub fn streamToDelimiterOrLimit(
641 };667 };
642}668}
643669
644fn streamToAny(670fn readAny(
645 br: *BufferedReader,671 br: *BufferedReader,
646 bw: *BufferedWriter,672 bw: *BufferedWriter,
647 delimiter: ?u8,673 delimiter: ?u8,
...@@ -971,15 +997,15 @@ test peekDelimiterExclusive {...@@ -971,15 +997,15 @@ test peekDelimiterExclusive {
971 return error.Unimplemented;997 return error.Unimplemented;
972}998}
973999
974test streamToDelimiter {1000test readDelimiter {
975 return error.Unimplemented;1001 return error.Unimplemented;
976}1002}
9771003
978test streamToDelimiterOrEnd {1004test readDelimiterEnding {
979 return error.Unimplemented;1005 return error.Unimplemented;
980}1006}
9811007
982test streamToDelimiterOrLimit {1008test readDelimiterLimit {
983 return error.Unimplemented;1009 return error.Unimplemented;
984}1010}
9851011
...@@ -1035,7 +1061,7 @@ test takeLeb128 {...@@ -1035,7 +1061,7 @@ test takeLeb128 {
1035 return error.Unimplemented;1061 return error.Unimplemented;
1036}1062}
10371063
1038test readShort {1064test readSliceShort {
1039 return error.Unimplemented;1065 return error.Unimplemented;
1040}1066}
10411067
lib/std/net.zig+321-273
...@@ -11,6 +11,8 @@ const io = std.io;...@@ -11,6 +11,8 @@ const io = std.io;
11const native_endian = builtin.target.cpu.arch.endian();11const native_endian = builtin.target.cpu.arch.endian();
12const native_os = builtin.os.tag;12const native_os = builtin.os.tag;
13const windows = std.os.windows;13const windows = std.os.windows;
14const Allocator = std.mem.Allocator;
15const ArrayList = std.ArrayListUnmanaged;
1416
15// Windows 10 added support for unix sockets in build 17063, redstone 4 is the17// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
16// first release to support them.18// first release to support them.
...@@ -818,7 +820,7 @@ pub const AddressList = struct {...@@ -818,7 +820,7 @@ pub const AddressList = struct {
818pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;820pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;
819821
820/// All memory allocated with `allocator` will be freed before this function returns.822/// All memory allocated with `allocator` will be freed before this function returns.
821pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream {823pub fn tcpConnectToHost(allocator: Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream {
822 const list = try getAddressList(allocator, name, port);824 const list = try getAddressList(allocator, name, port);
823 defer list.deinit();825 defer list.deinit();
824826
...@@ -851,7 +853,7 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {...@@ -851,7 +853,7 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
851853
852// TODO: Instead of having a massive error set, make the error set have categories, and then854// TODO: Instead of having a massive error set, make the error set have categories, and then
853// store the sub-error as a diagnostic value.855// store the sub-error as a diagnostic value.
854const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{856const GetAddressListError = Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
855 TemporaryNameServerFailure,857 TemporaryNameServerFailure,
856 NameServerFailure,858 NameServerFailure,
857 AddressFamilyNotSupported,859 AddressFamilyNotSupported,
...@@ -871,12 +873,13 @@ const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError ||...@@ -871,12 +873,13 @@ const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError ||
871873
872 InterfaceNotFound,874 InterfaceNotFound,
873 FileSystem,875 FileSystem,
876 ResolveConfParseFailed,
874};877};
875878
876/// Call `AddressList.deinit` on the result.879/// Call `AddressList.deinit` on the result.
877pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList {880pub fn getAddressList(gpa: Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList {
878 const result = blk: {881 const result = blk: {
879 var arena = std.heap.ArenaAllocator.init(allocator);882 var arena = std.heap.ArenaAllocator.init(gpa);
880 errdefer arena.deinit();883 errdefer arena.deinit();
881884
882 const result = try arena.allocator().create(AddressList);885 const result = try arena.allocator().create(AddressList);
...@@ -891,11 +894,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get...@@ -891,11 +894,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
891 errdefer result.deinit();894 errdefer result.deinit();
892895
893 if (native_os == .windows) {896 if (native_os == .windows) {
894 const name_c = try allocator.dupeZ(u8, name);897 const name_c = try gpa.dupeZ(u8, name);
895 defer allocator.free(name_c);898 defer gpa.free(name_c);
896899
897 const port_c = try std.fmt.allocPrintZ(allocator, "{}", .{port});900 const port_c = try std.fmt.allocPrintZ(gpa, "{}", .{port});
898 defer allocator.free(port_c);901 defer gpa.free(port_c);
899902
900 const ws2_32 = windows.ws2_32;903 const ws2_32 = windows.ws2_32;
901 const hints: posix.addrinfo = .{904 const hints: posix.addrinfo = .{
...@@ -963,11 +966,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get...@@ -963,11 +966,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
963 }966 }
964967
965 if (builtin.link_libc) {968 if (builtin.link_libc) {
966 const name_c = try allocator.dupeZ(u8, name);969 const name_c = try gpa.dupeZ(u8, name);
967 defer allocator.free(name_c);970 defer gpa.free(name_c);
968971
969 const port_c = try std.fmt.allocPrintZ(allocator, "{}", .{port});972 const port_c = try std.fmt.allocPrintZ(gpa, "{}", .{port});
970 defer allocator.free(port_c);973 defer gpa.free(port_c);
971974
972 const hints: posix.addrinfo = .{975 const hints: posix.addrinfo = .{
973 .flags = .{ .NUMERICSERV = true },976 .flags = .{ .NUMERICSERV = true },
...@@ -1030,17 +1033,17 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get...@@ -1030,17 +1033,17 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
10301033
1031 if (native_os == .linux) {1034 if (native_os == .linux) {
1032 const family = posix.AF.UNSPEC;1035 const family = posix.AF.UNSPEC;
1033 var lookup_addrs = std.ArrayList(LookupAddr).init(allocator);1036 var lookup_addrs: ArrayList(LookupAddr) = .empty;
1034 defer lookup_addrs.deinit();1037 defer lookup_addrs.deinit(gpa);
10351038
1036 var canon = std.ArrayList(u8).init(arena);1039 var canon: ArrayList(u8) = .empty;
1037 defer canon.deinit();1040 defer canon.deinit(gpa);
10381041
1039 try linuxLookupName(&lookup_addrs, &canon, name, family, .{ .NUMERICSERV = true }, port);1042 try linuxLookupName(gpa, &lookup_addrs, &canon, name, family, .{ .NUMERICSERV = true }, port);
10401043
1041 result.addrs = try arena.alloc(Address, lookup_addrs.items.len);1044 result.addrs = try arena.alloc(Address, lookup_addrs.items.len);
1042 if (canon.items.len != 0) {1045 if (canon.items.len != 0) {
1043 result.canon_name = try canon.toOwnedSlice();1046 result.canon_name = try arena.dupe(u8, canon.items);
1044 }1047 }
10451048
1046 for (lookup_addrs.items, 0..) |lookup_addr, i| {1049 for (lookup_addrs.items, 0..) |lookup_addr, i| {
...@@ -1067,8 +1070,9 @@ const DAS_PREFIX_SHIFT = 8;...@@ -1067,8 +1070,9 @@ const DAS_PREFIX_SHIFT = 8;
1067const DAS_ORDER_SHIFT = 0;1070const DAS_ORDER_SHIFT = 0;
10681071
1069fn linuxLookupName(1072fn linuxLookupName(
1070 addrs: *std.ArrayList(LookupAddr),1073 gpa: Allocator,
1071 canon: *std.ArrayList(u8),1074 addrs: *ArrayList(LookupAddr),
1075 canon: *ArrayList(u8),
1072 opt_name: ?[]const u8,1076 opt_name: ?[]const u8,
1073 family: posix.sa_family_t,1077 family: posix.sa_family_t,
1074 flags: posix.AI,1078 flags: posix.AI,
...@@ -1077,13 +1081,13 @@ fn linuxLookupName(...@@ -1077,13 +1081,13 @@ fn linuxLookupName(
1077 if (opt_name) |name| {1081 if (opt_name) |name| {
1078 // reject empty name and check len so it fits into temp bufs1082 // reject empty name and check len so it fits into temp bufs
1079 canon.items.len = 0;1083 canon.items.len = 0;
1080 try canon.appendSlice(name);1084 try canon.appendSlice(gpa, name);
1081 if (Address.parseExpectingFamily(name, family, port)) |addr| {1085 if (Address.parseExpectingFamily(name, family, port)) |addr| {
1082 try addrs.append(LookupAddr{ .addr = addr });1086 try addrs.append(gpa, .{ .addr = addr });
1083 } else |name_err| if (flags.NUMERICHOST) {1087 } else |name_err| if (flags.NUMERICHOST) {
1084 return name_err;1088 return name_err;
1085 } else {1089 } else {
1086 try linuxLookupNameFromHosts(addrs, canon, name, family, port);1090 try linuxLookupNameFromHosts(gpa, addrs, canon, name, family, port);
1087 if (addrs.items.len == 0) {1091 if (addrs.items.len == 0) {
1088 // RFC 6761 Section 6.3.31092 // RFC 6761 Section 6.3.3
1089 // Name resolution APIs and libraries SHOULD recognize localhost1093 // Name resolution APIs and libraries SHOULD recognize localhost
...@@ -1094,17 +1098,18 @@ fn linuxLookupName(...@@ -1094,17 +1098,18 @@ fn linuxLookupName(
1094 // Check for equal to "localhost(.)" or ends in ".localhost(.)"1098 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
1095 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";1099 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
1096 if (mem.endsWith(u8, name, localhost) and (name.len == localhost.len or name[name.len - localhost.len] == '.')) {1100 if (mem.endsWith(u8, name, localhost) and (name.len == localhost.len or name[name.len - localhost.len] == '.')) {
1097 try addrs.append(LookupAddr{ .addr = .{ .in = Ip4Address.parse("127.0.0.1", port) catch unreachable } });1101 try addrs.append(gpa, .{ .addr = .{ .in = Ip4Address.parse("127.0.0.1", port) catch unreachable } });
1098 try addrs.append(LookupAddr{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } });1102 try addrs.append(gpa, .{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } });
1099 return;1103 return;
1100 }1104 }
11011105
1102 try linuxLookupNameFromDnsSearch(addrs, canon, name, family, port);1106 try linuxLookupNameFromDnsSearch(gpa, addrs, canon, name, family, port);
1103 }1107 }
1104 }1108 }
1105 } else {1109 } else {
1106 try canon.resize(0);1110 try canon.resize(gpa, 0);
1107 try linuxLookupNameFromNull(addrs, family, flags, port);1111 try addrs.ensureUnusedCapacity(gpa, 1);
1112 linuxLookupNameFromNull(addrs, family, flags, port);
1108 }1113 }
1109 if (addrs.items.len == 0) return error.UnknownHostName;1114 if (addrs.items.len == 0) return error.UnknownHostName;
11101115
...@@ -1310,39 +1315,40 @@ fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool {...@@ -1310,39 +1315,40 @@ fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool {
1310}1315}
13111316
1312fn linuxLookupNameFromNull(1317fn linuxLookupNameFromNull(
1313 addrs: *std.ArrayList(LookupAddr),1318 addrs: *ArrayList(LookupAddr),
1314 family: posix.sa_family_t,1319 family: posix.sa_family_t,
1315 flags: posix.AI,1320 flags: posix.AI,
1316 port: u16,1321 port: u16,
1317) !void {1322) void {
1318 if (flags.PASSIVE) {1323 if (flags.PASSIVE) {
1319 if (family != posix.AF.INET6) {1324 if (family != posix.AF.INET6) {
1320 (try addrs.addOne()).* = LookupAddr{1325 addrs.appendAssumeCapacity(.{
1321 .addr = Address.initIp4([1]u8{0} ** 4, port),1326 .addr = Address.initIp4([1]u8{0} ** 4, port),
1322 };1327 });
1323 }1328 }
1324 if (family != posix.AF.INET) {1329 if (family != posix.AF.INET) {
1325 (try addrs.addOne()).* = LookupAddr{1330 addrs.appendAssumeCapacity(.{
1326 .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0),1331 .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0),
1327 };1332 });
1328 }1333 }
1329 } else {1334 } else {
1330 if (family != posix.AF.INET6) {1335 if (family != posix.AF.INET6) {
1331 (try addrs.addOne()).* = LookupAddr{1336 addrs.appendAssumeCapacity(.{
1332 .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port),1337 .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port),
1333 };1338 });
1334 }1339 }
1335 if (family != posix.AF.INET) {1340 if (family != posix.AF.INET) {
1336 (try addrs.addOne()).* = LookupAddr{1341 addrs.appendAssumeCapacity(.{
1337 .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0),1342 .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0),
1338 };1343 });
1339 }1344 }
1340 }1345 }
1341}1346}
13421347
1343fn linuxLookupNameFromHosts(1348fn linuxLookupNameFromHosts(
1344 addrs: *std.ArrayList(LookupAddr),1349 gpa: Allocator,
1345 canon: *std.ArrayList(u8),1350 addrs: *ArrayList(LookupAddr),
1351 canon: *ArrayList(u8),
1346 name: []const u8,1352 name: []const u8,
1347 family: posix.sa_family_t,1353 family: posix.sa_family_t,
1348 port: u16,1354 port: u16,
...@@ -1359,7 +1365,34 @@ fn linuxLookupNameFromHosts(...@@ -1359,7 +1365,34 @@ fn linuxLookupNameFromHosts(
1359 var line_buf: [512]u8 = undefined;1365 var line_buf: [512]u8 = undefined;
1360 var file_reader = file.reader();1366 var file_reader = file.reader();
1361 var br = file_reader.interface().buffered(&line_buf);1367 var br = file_reader.interface().buffered(&line_buf);
1362 while (br.takeSentinel('\n')) |line| {1368 return parseHosts(gpa, addrs, canon, name, family, port, &br) catch |err| switch (err) {
1369 error.OutOfMemory => return error.OutOfMemory,
1370 error.ReadFailed => return file_reader.err.?,
1371 };
1372}
1373
1374fn parseHosts(
1375 gpa: Allocator,
1376 addrs: *ArrayList(LookupAddr),
1377 canon: *ArrayList(u8),
1378 name: []const u8,
1379 family: posix.sa_family_t,
1380 port: u16,
1381 br: *std.io.BufferedReader,
1382) error{ OutOfMemory, ReadFailed }!void {
1383 while (true) {
1384 const line = br.takeDelimiterExclusive('\n') catch |err| switch (err) {
1385 error.StreamTooLong => {
1386 // Skip lines that are too long.
1387 br.discardDelimiterInclusive('\n') catch |e| switch (e) {
1388 error.EndOfStream => break,
1389 error.ReadFailed => return error.ReadFailed,
1390 };
1391 continue;
1392 },
1393 error.ReadFailed => return error.ReadFailed,
1394 error.EndOfStream => break,
1395 };
1363 var split_it = mem.splitScalar(u8, line, '#');1396 var split_it = mem.splitScalar(u8, line, '#');
1364 const no_comment_line = split_it.first();1397 const no_comment_line = split_it.first();
13651398
...@@ -1383,15 +1416,15 @@ fn linuxLookupNameFromHosts(...@@ -1383,15 +1416,15 @@ fn linuxLookupNameFromHosts(
1383 error.NonCanonical,1416 error.NonCanonical,
1384 => continue,1417 => continue,
1385 };1418 };
1386 try addrs.append(LookupAddr{ .addr = addr });1419 try addrs.append(gpa, .{ .addr = addr });
13871420
1388 // first name is canonical name1421 // first name is canonical name
1389 const name_text = first_name_text.?;1422 const name_text = first_name_text.?;
1390 if (isValidHostName(name_text)) {1423 if (isValidHostName(name_text)) {
1391 canon.items.len = 0;1424 canon.items.len = 0;
1392 try canon.appendSlice(name_text);1425 try canon.appendSlice(gpa, name_text);
1393 }1426 }
1394 } else |err| return err;1427 }
1395}1428}
13961429
1397pub fn isValidHostName(hostname: []const u8) bool {1430pub fn isValidHostName(hostname: []const u8) bool {
...@@ -1407,14 +1440,15 @@ pub fn isValidHostName(hostname: []const u8) bool {...@@ -1407,14 +1440,15 @@ pub fn isValidHostName(hostname: []const u8) bool {
1407}1440}
14081441
1409fn linuxLookupNameFromDnsSearch(1442fn linuxLookupNameFromDnsSearch(
1410 addrs: *std.ArrayList(LookupAddr),1443 gpa: Allocator,
1411 canon: *std.ArrayList(u8),1444 addrs: *ArrayList(LookupAddr),
1445 canon: *ArrayList(u8),
1412 name: []const u8,1446 name: []const u8,
1413 family: posix.sa_family_t,1447 family: posix.sa_family_t,
1414 port: u16,1448 port: u16,
1415) !void {1449) !void {
1416 var rc: ResolvConf = undefined;1450 var rc: ResolvConf = undefined;
1417 try getResolvConf(addrs.allocator, &rc);1451 rc.init(gpa) catch return error.ResolveConfParseFailed;
1418 defer rc.deinit();1452 defer rc.deinit();
14191453
1420 // Count dots, suppress search when >=ndots or name ends in1454 // Count dots, suppress search when >=ndots or name ends in
...@@ -1439,37 +1473,40 @@ fn linuxLookupNameFromDnsSearch(...@@ -1439,37 +1473,40 @@ fn linuxLookupNameFromDnsSearch(
1439 // provides the desired default canonical name (if the requested1473 // provides the desired default canonical name (if the requested
1440 // name is not a CNAME record) and serves as a buffer for passing1474 // name is not a CNAME record) and serves as a buffer for passing
1441 // the full requested name to name_from_dns.1475 // the full requested name to name_from_dns.
1442 try canon.resize(canon_name.len);1476 try canon.resize(gpa, canon_name.len);
1443 @memcpy(canon.items, canon_name);1477 @memcpy(canon.items, canon_name);
1444 try canon.append('.');1478 try canon.append(gpa, '.');
14451479
1446 var tok_it = mem.tokenizeAny(u8, search, " \t");1480 var tok_it = mem.tokenizeAny(u8, search, " \t");
1447 while (tok_it.next()) |tok| {1481 while (tok_it.next()) |tok| {
1448 canon.shrinkRetainingCapacity(canon_name.len + 1);1482 canon.shrinkRetainingCapacity(canon_name.len + 1);
1449 try canon.appendSlice(tok);1483 try canon.appendSlice(gpa, tok);
1450 try linuxLookupNameFromDns(addrs, canon, canon.items, family, rc, port);1484 try linuxLookupNameFromDns(gpa, addrs, canon, canon.items, family, rc, port);
1451 if (addrs.items.len != 0) return;1485 if (addrs.items.len != 0) return;
1452 }1486 }
14531487
1454 canon.shrinkRetainingCapacity(canon_name.len);1488 canon.shrinkRetainingCapacity(canon_name.len);
1455 return linuxLookupNameFromDns(addrs, canon, name, family, rc, port);1489 return linuxLookupNameFromDns(gpa, addrs, canon, name, family, rc, port);
1456}1490}
14571491
1458const dpc_ctx = struct {1492const dpc_ctx = struct {
1459 addrs: *std.ArrayList(LookupAddr),1493 gpa: Allocator,
1460 canon: *std.ArrayList(u8),1494 addrs: *ArrayList(LookupAddr),
1495 canon: *ArrayList(u8),
1461 port: u16,1496 port: u16,
1462};1497};
14631498
1464fn linuxLookupNameFromDns(1499fn linuxLookupNameFromDns(
1465 addrs: *std.ArrayList(LookupAddr),1500 gpa: Allocator,
1466 canon: *std.ArrayList(u8),1501 addrs: *ArrayList(LookupAddr),
1502 canon: *ArrayList(u8),
1467 name: []const u8,1503 name: []const u8,
1468 family: posix.sa_family_t,1504 family: posix.sa_family_t,
1469 rc: ResolvConf,1505 rc: ResolvConf,
1470 port: u16,1506 port: u16,
1471) !void {1507) !void {
1472 const ctx = dpc_ctx{1508 const ctx: dpc_ctx = .{
1509 .gpa = gpa,
1473 .addrs = addrs,1510 .addrs = addrs,
1474 .canon = canon,1511 .canon = canon,
1475 .port = port,1512 .port = port,
...@@ -1479,8 +1516,8 @@ fn linuxLookupNameFromDns(...@@ -1479,8 +1516,8 @@ fn linuxLookupNameFromDns(
1479 rr: u8,1516 rr: u8,
1480 };1517 };
1481 const afrrs = [_]AfRr{1518 const afrrs = [_]AfRr{
1482 AfRr{ .af = posix.AF.INET6, .rr = posix.RR.A },1519 .{ .af = posix.AF.INET6, .rr = posix.RR.A },
1483 AfRr{ .af = posix.AF.INET, .rr = posix.RR.AAAA },1520 .{ .af = posix.AF.INET, .rr = posix.RR.AAAA },
1484 };1521 };
1485 var qbuf: [2][280]u8 = undefined;1522 var qbuf: [2][280]u8 = undefined;
1486 var abuf: [2][512]u8 = undefined;1523 var abuf: [2][512]u8 = undefined;
...@@ -1500,7 +1537,7 @@ fn linuxLookupNameFromDns(...@@ -1500,7 +1537,7 @@ fn linuxLookupNameFromDns(
1500 ap[0].len = 0;1537 ap[0].len = 0;
1501 ap[1].len = 0;1538 ap[1].len = 0;
15021539
1503 try resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq], rc);1540 try rc.resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq]);
15041541
1505 var i: usize = 0;1542 var i: usize = 0;
1506 while (i < nq) : (i += 1) {1543 while (i < nq) : (i += 1) {
...@@ -1515,240 +1552,252 @@ fn linuxLookupNameFromDns(...@@ -1515,240 +1552,252 @@ fn linuxLookupNameFromDns(
1515}1552}
15161553
1517const ResolvConf = struct {1554const ResolvConf = struct {
1555 gpa: Allocator,
1518 attempts: u32,1556 attempts: u32,
1519 ndots: u32,1557 ndots: u32,
1520 timeout: u32,1558 timeout: u32,
1521 search: std.ArrayList(u8),1559 search: ArrayList(u8),
1522 ns: std.ArrayList(LookupAddr),1560 /// TODO there are actually only allowed to be maximum 3 nameservers, no need
15231561 /// for an array list.
1524 fn deinit(rc: *ResolvConf) void {1562 ns: ArrayList(LookupAddr),
1525 rc.ns.deinit();1563
1526 rc.search.deinit();1564 /// Returns `error.StreamTooLong` if a line is longer than 512 bytes.
1527 rc.* = undefined;1565 /// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761
1566 fn init(rc: *ResolvConf, gpa: Allocator) !void {
1567 rc.* = .{
1568 .gpa = gpa,
1569 .ns = .empty,
1570 .search = .empty,
1571 .ndots = 1,
1572 .timeout = 5,
1573 .attempts = 2,
1574 };
1575 errdefer rc.deinit();
1576
1577 const file = fs.openFileAbsoluteZ("/etc/resolv.conf", .{}) catch |err| switch (err) {
1578 error.FileNotFound,
1579 error.NotDir,
1580 error.AccessDenied,
1581 => return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53),
1582 else => |e| return e,
1583 };
1584 defer file.close();
1585
1586 var line_buf: [512]u8 = undefined;
1587 var file_reader = file.reader();
1588 var br = file_reader.interface().buffered(&line_buf);
1589 return parse(rc, &br) catch |err| switch (err) {
1590 error.ReadFailed => return file_reader.err.?,
1591 else => |e| return e,
1592 };
1528 }1593 }
1529};
1530
1531/// Returns `error.StreamTooLong` if a line is longer than 512 bytes.
1532/// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761
1533fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void {
1534 rc.* = .{
1535 .ns = std.ArrayList(LookupAddr).init(allocator),
1536 .search = std.ArrayList(u8).init(allocator),
1537 .ndots = 1,
1538 .timeout = 5,
1539 .attempts = 2,
1540 };
1541 errdefer rc.deinit();
1542
1543 const file = fs.openFileAbsoluteZ("/etc/resolv.conf", .{}) catch |err| switch (err) {
1544 error.FileNotFound,
1545 error.NotDir,
1546 error.AccessDenied,
1547 => return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53),
1548 else => |e| return e,
1549 };
1550 defer file.close();
15511594
1552 var line_buf: [512]u8 = undefined;1595 fn parse(rc: *ResolvConf, br: *std.io.BufferedReader) !void {
1553 var file_reader = file.reader();1596 const gpa = rc.gpa;
1554 var br = file_reader.interface().buffered(&line_buf);1597 while (br.takeSentinel('\n')) |line_with_comment| {
1555 while (br.takeSentinel('\n')) |line_with_comment| {1598 const line = line: {
1556 const line = line: {1599 var split = mem.splitScalar(u8, line_with_comment, '#');
1557 var split = mem.splitScalar(u8, line_with_comment, '#');1600 break :line split.first();
1558 break :line split.first();1601 };
1559 };1602 var line_it = mem.tokenizeAny(u8, line, " \t");
1560 var line_it = mem.tokenizeAny(u8, line, " \t");1603
15611604 const token = line_it.next() orelse continue;
1562 const token = line_it.next() orelse continue;1605 if (mem.eql(u8, token, "options")) {
1563 if (mem.eql(u8, token, "options")) {1606 while (line_it.next()) |sub_tok| {
1564 while (line_it.next()) |sub_tok| {1607 var colon_it = mem.splitScalar(u8, sub_tok, ':');
1565 var colon_it = mem.splitScalar(u8, sub_tok, ':');1608 const name = colon_it.first();
1566 const name = colon_it.first();1609 const value_txt = colon_it.next() orelse continue;
1567 const value_txt = colon_it.next() orelse continue;1610 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {
1568 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {1611 error.Overflow => 255,
1569 // TODO https://github.com/ziglang/zig/issues/118121612 error.InvalidCharacter => continue,
1570 error.Overflow => @as(u8, 255),1613 };
1571 error.InvalidCharacter => continue,1614 if (mem.eql(u8, name, "ndots")) {
1572 };1615 rc.ndots = @min(value, 15);
1573 if (mem.eql(u8, name, "ndots")) {1616 } else if (mem.eql(u8, name, "attempts")) {
1574 rc.ndots = @min(value, 15);1617 rc.attempts = @min(value, 10);
1575 } else if (mem.eql(u8, name, "attempts")) {1618 } else if (mem.eql(u8, name, "timeout")) {
1576 rc.attempts = @min(value, 10);1619 rc.timeout = @min(value, 60);
1577 } else if (mem.eql(u8, name, "timeout")) {1620 }
1578 rc.timeout = @min(value, 60);
1579 }1621 }
1622 } else if (mem.eql(u8, token, "nameserver")) {
1623 const ip_txt = line_it.next() orelse continue;
1624 try linuxLookupNameFromNumericUnspec(gpa, &rc.ns, ip_txt, 53);
1625 } else if (mem.eql(u8, token, "domain") or mem.eql(u8, token, "search")) {
1626 rc.search.items.len = 0;
1627 try rc.search.appendSlice(gpa, line_it.rest());
1580 }1628 }
1581 } else if (mem.eql(u8, token, "nameserver")) {1629 } else |err| return err;
1582 const ip_txt = line_it.next() orelse continue;
1583 try linuxLookupNameFromNumericUnspec(&rc.ns, ip_txt, 53);
1584 } else if (mem.eql(u8, token, "domain") or mem.eql(u8, token, "search")) {
1585 rc.search.items.len = 0;
1586 try rc.search.appendSlice(line_it.rest());
1587 }
1588 } else |err| return err;
15891630
1590 if (rc.ns.items.len == 0) {1631 if (rc.ns.items.len == 0) {
1591 return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53);1632 return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53);
1633 }
1592 }1634 }
1593}
15941635
1595fn linuxLookupNameFromNumericUnspec(1636 fn resMSendRc(
1596 addrs: *std.ArrayList(LookupAddr),1637 rc: ResolvConf,
1597 name: []const u8,1638 queries: []const []const u8,
1598 port: u16,1639 answers: [][]u8,
1599) !void {1640 answer_bufs: []const []u8,
1600 const addr = try Address.resolveIp(name, port);1641 ) !void {
1601 (try addrs.addOne()).* = LookupAddr{ .addr = addr };1642 const gpa = rc.gpa;
1602}1643 const timeout = 1000 * rc.timeout;
16031644 const attempts = rc.attempts;
1604fn resMSendRc(
1605 queries: []const []const u8,
1606 answers: [][]u8,
1607 answer_bufs: []const []u8,
1608 rc: ResolvConf,
1609) !void {
1610 const timeout = 1000 * rc.timeout;
1611 const attempts = rc.attempts;
16121645
1613 var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in);1646 var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in);
1614 var family: posix.sa_family_t = posix.AF.INET;1647 var family: posix.sa_family_t = posix.AF.INET;
16151648
1616 var ns_list = std.ArrayList(Address).init(rc.ns.allocator);1649 var ns_list: ArrayList(Address) = .empty;
1617 defer ns_list.deinit();1650 defer ns_list.deinit(gpa);
16181651
1619 try ns_list.resize(rc.ns.items.len);1652 try ns_list.resize(gpa, rc.ns.items.len);
1620 const ns = ns_list.items;
16211653
1622 for (rc.ns.items, 0..) |iplit, i| {1654 for (ns_list.items, rc.ns.items) |*ns, iplit| {
1623 ns[i] = iplit.addr;1655 ns.* = iplit.addr;
1624 assert(ns[i].getPort() == 53);1656 assert(ns.getPort() == 53);
1625 if (iplit.addr.any.family != posix.AF.INET) {1657 if (iplit.addr.any.family != posix.AF.INET) {
1626 family = posix.AF.INET6;1658 family = posix.AF.INET6;
1659 }
1627 }1660 }
1628 }
16291661
1630 const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;1662 const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;
1631 const fd = posix.socket(family, flags, 0) catch |err| switch (err) {1663 const fd = posix.socket(family, flags, 0) catch |err| switch (err) {
1632 error.AddressFamilyNotSupported => blk: {1664 error.AddressFamilyNotSupported => blk: {
1633 // Handle case where system lacks IPv6 support1665 // Handle case where system lacks IPv6 support
1634 if (family == posix.AF.INET6) {1666 if (family == posix.AF.INET6) {
1635 family = posix.AF.INET;1667 family = posix.AF.INET;
1636 break :blk try posix.socket(posix.AF.INET, flags, 0);1668 break :blk try posix.socket(posix.AF.INET, flags, 0);
1669 }
1670 return err;
1671 },
1672 else => |e| return e,
1673 };
1674 defer Stream.close(.{ .handle = fd });
1675
1676 // Past this point, there are no errors. Each individual query will
1677 // yield either no reply (indicated by zero length) or an answer
1678 // packet which is up to the caller to interpret.
1679
1680 // Convert any IPv4 addresses in a mixed environment to v4-mapped
1681 if (family == posix.AF.INET6) {
1682 try posix.setsockopt(
1683 fd,
1684 posix.SOL.IPV6,
1685 std.os.linux.IPV6.V6ONLY,
1686 &mem.toBytes(@as(c_int, 0)),
1687 );
1688 for (ns_list.items) |*ns| {
1689 if (ns.any.family != posix.AF.INET) continue;
1690 mem.writeInt(u32, ns.in6.sa.addr[12..], ns.in.sa.addr, native_endian);
1691 ns.in6.sa.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1692 ns.any.family = posix.AF.INET6;
1693 ns.in6.sa.flowinfo = 0;
1694 ns.in6.sa.scope_id = 0;
1637 }1695 }
1638 return err;1696 sl = @sizeOf(posix.sockaddr.in6);
1639 },
1640 else => |e| return e,
1641 };
1642 defer Stream.close(.{ .handle = fd });
1643
1644 // Past this point, there are no errors. Each individual query will
1645 // yield either no reply (indicated by zero length) or an answer
1646 // packet which is up to the caller to interpret.
1647
1648 // Convert any IPv4 addresses in a mixed environment to v4-mapped
1649 if (family == posix.AF.INET6) {
1650 try posix.setsockopt(
1651 fd,
1652 posix.SOL.IPV6,
1653 std.os.linux.IPV6.V6ONLY,
1654 &mem.toBytes(@as(c_int, 0)),
1655 );
1656 for (0..ns.len) |i| {
1657 if (ns[i].any.family != posix.AF.INET) continue;
1658 mem.writeInt(u32, ns[i].in6.sa.addr[12..], ns[i].in.sa.addr, native_endian);
1659 ns[i].in6.sa.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1660 ns[i].any.family = posix.AF.INET6;
1661 ns[i].in6.sa.flowinfo = 0;
1662 ns[i].in6.sa.scope_id = 0;
1663 }1697 }
1664 sl = @sizeOf(posix.sockaddr.in6);
1665 }
16661698
1667 // Get local address and open/bind a socket1699 // Get local address and open/bind a socket
1668 var sa: Address = undefined;1700 var sa: Address = undefined;
1669 @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0);1701 @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0);
1670 sa.any.family = family;1702 sa.any.family = family;
1671 try posix.bind(fd, &sa.any, sl);1703 try posix.bind(fd, &sa.any, sl);
16721704
1673 var pfd = [1]posix.pollfd{posix.pollfd{1705 var pfd = [1]posix.pollfd{posix.pollfd{
1674 .fd = fd,1706 .fd = fd,
1675 .events = posix.POLL.IN,1707 .events = posix.POLL.IN,
1676 .revents = undefined,1708 .revents = undefined,
1677 }};1709 }};
1678 const retry_interval = timeout / attempts;1710 const retry_interval = timeout / attempts;
1679 var next: u32 = 0;1711 var next: u32 = 0;
1680 var t2: u64 = @bitCast(std.time.milliTimestamp());1712 var t2: u64 = @bitCast(std.time.milliTimestamp());
1681 const t0 = t2;1713 const t0 = t2;
1682 var t1 = t2 - retry_interval;1714 var t1 = t2 - retry_interval;
16831715
1684 var servfail_retry: usize = undefined;1716 var servfail_retry: usize = undefined;
16851717
1686 outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) {1718 outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) {
1687 if (t2 - t1 >= retry_interval) {1719 if (t2 - t1 >= retry_interval) {
1688 // Query all configured nameservers in parallel1720 // Query all configured nameservers in parallel
1689 var i: usize = 0;1721 var i: usize = 0;
1690 while (i < queries.len) : (i += 1) {1722 while (i < queries.len) : (i += 1) {
1691 if (answers[i].len == 0) {1723 if (answers[i].len == 0) {
1692 var j: usize = 0;1724 for (ns_list.items) |*ns| {
1693 while (j < ns.len) : (j += 1) {1725 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1694 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;1726 }
1695 }1727 }
1696 }1728 }
1729 t1 = t2;
1730 servfail_retry = 2 * queries.len;
1697 }1731 }
1698 t1 = t2;
1699 servfail_retry = 2 * queries.len;
1700 }
17011732
1702 // Wait for a response, or until time to retry1733 // Wait for a response, or until time to retry
1703 const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2);1734 const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2);
1704 const nevents = posix.poll(&pfd, clamped_timeout) catch 0;1735 const nevents = posix.poll(&pfd, clamped_timeout) catch 0;
1705 if (nevents == 0) continue;1736 if (nevents == 0) continue;
1737
1738 while (true) {
1739 var sl_copy = sl;
1740 const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
1741
1742 // Ignore non-identifiable packets
1743 if (rlen < 4) continue;
1744
1745 // Ignore replies from addresses we didn't send to
1746 const ns = for (ns_list.items) |*ns| {
1747 if (ns.eql(sa)) break ns;
1748 } else continue;
1749
1750 // Find which query this answer goes with, if any
1751 var i: usize = next;
1752 while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or
1753 answer_bufs[next][1] != queries[i][1])) : (i += 1)
1754 {}
1755
1756 if (i == queries.len) continue;
1757 if (answers[i].len != 0) continue;
1758
1759 // Only accept positive or negative responses;
1760 // retry immediately on server failure, and ignore
1761 // all other codes such as refusal.
1762 switch (answer_bufs[next][3] & 15) {
1763 0, 3 => {},
1764 2 => if (servfail_retry != 0) {
1765 servfail_retry -= 1;
1766 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1767 },
1768 else => continue,
1769 }
17061770
1707 while (true) {1771 // Store answer in the right slot, or update next
1708 var sl_copy = sl;1772 // available temp slot if it's already in place.
1709 const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;1773 answers[i].len = rlen;
17101774 if (i == next) {
1711 // Ignore non-identifiable packets1775 while (next < queries.len and answers[next].len != 0) : (next += 1) {}
1712 if (rlen < 4) continue;1776 } else {
17131777 @memcpy(answer_bufs[i][0..rlen], answer_bufs[next][0..rlen]);
1714 // Ignore replies from addresses we didn't send to1778 }
1715 var j: usize = 0;
1716 while (j < ns.len and !ns[j].eql(sa)) : (j += 1) {}
1717 if (j == ns.len) continue;
1718
1719 // Find which query this answer goes with, if any
1720 var i: usize = next;
1721 while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or
1722 answer_bufs[next][1] != queries[i][1])) : (i += 1)
1723 {}
1724
1725 if (i == queries.len) continue;
1726 if (answers[i].len != 0) continue;
1727
1728 // Only accept positive or negative responses;
1729 // retry immediately on server failure, and ignore
1730 // all other codes such as refusal.
1731 switch (answer_bufs[next][3] & 15) {
1732 0, 3 => {},
1733 2 => if (servfail_retry != 0) {
1734 servfail_retry -= 1;
1735 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
1736 },
1737 else => continue,
1738 }
17391779
1740 // Store answer in the right slot, or update next1780 if (next == queries.len) break :outer;
1741 // available temp slot if it's already in place.
1742 answers[i].len = rlen;
1743 if (i == next) {
1744 while (next < queries.len and answers[next].len != 0) : (next += 1) {}
1745 } else {
1746 @memcpy(answer_bufs[i][0..rlen], answer_bufs[next][0..rlen]);
1747 }1781 }
1748
1749 if (next == queries.len) break :outer;
1750 }1782 }
1751 }1783 }
1784
1785 fn deinit(rc: *ResolvConf) void {
1786 const gpa = rc.gpa;
1787 rc.ns.deinit(gpa);
1788 rc.search.deinit(gpa);
1789 rc.* = undefined;
1790 }
1791};
1792
1793fn linuxLookupNameFromNumericUnspec(
1794 gpa: Allocator,
1795 addrs: *ArrayList(LookupAddr),
1796 name: []const u8,
1797 port: u16,
1798) !void {
1799 const addr = try Address.resolveIp(name, port);
1800 try addrs.append(gpa, .{ .addr = addr });
1752}1801}
17531802
1754fn dnsParse(1803fn dnsParse(
...@@ -1785,20 +1834,19 @@ fn dnsParse(...@@ -1785,20 +1834,19 @@ fn dnsParse(
1785}1834}
17861835
1787fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void {1836fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void {
1837 const gpa = ctx.gpa;
1788 switch (rr) {1838 switch (rr) {
1789 posix.RR.A => {1839 posix.RR.A => {
1790 if (data.len != 4) return error.InvalidDnsARecord;1840 if (data.len != 4) return error.InvalidDnsARecord;
1791 const new_addr = try ctx.addrs.addOne();1841 try ctx.addrs.append(gpa, .{
1792 new_addr.* = LookupAddr{
1793 .addr = Address.initIp4(data[0..4].*, ctx.port),1842 .addr = Address.initIp4(data[0..4].*, ctx.port),
1794 };1843 });
1795 },1844 },
1796 posix.RR.AAAA => {1845 posix.RR.AAAA => {
1797 if (data.len != 16) return error.InvalidDnsAAAARecord;1846 if (data.len != 16) return error.InvalidDnsAAAARecord;
1798 const new_addr = try ctx.addrs.addOne();1847 try ctx.addrs.append(gpa, .{
1799 new_addr.* = LookupAddr{
1800 .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0),1848 .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0),
1801 };1849 });
1802 },1850 },
1803 posix.RR.CNAME => {1851 posix.RR.CNAME => {
1804 var tmp: [256]u8 = undefined;1852 var tmp: [256]u8 = undefined;
...@@ -1807,7 +1855,7 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)...@@ -1807,7 +1855,7 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
1807 const canon_name = mem.sliceTo(&tmp, 0);1855 const canon_name = mem.sliceTo(&tmp, 0);
1808 if (isValidHostName(canon_name)) {1856 if (isValidHostName(canon_name)) {
1809 ctx.canon.items.len = 0;1857 ctx.canon.items.len = 0;
1810 try ctx.canon.appendSlice(canon_name);1858 try ctx.canon.appendSlice(gpa, canon_name);
1811 }1859 }
1812 },1860 },
1813 else => return,1861 else => return,