mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
std.Io.net: partially implement HostName.lookup
This commit is contained in:
parent
07cc4077fb
commit
bdf463bee2
5 changed files with 302 additions and 56 deletions
|
|
@ -660,7 +660,7 @@ pub const VTable = struct {
|
||||||
|
|
||||||
listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.ListenOptions) net.ListenError!net.Server,
|
listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.ListenOptions) net.ListenError!net.Server,
|
||||||
accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Server.Connection,
|
accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Server.Connection,
|
||||||
netRead: *const fn (?*anyopaque, src: net.Stream, dest: *Io.Writer, limit: Io.Limit) net.Stream.Reader.Error!usize,
|
netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize,
|
||||||
netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
|
netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
|
||||||
netClose: *const fn (?*anyopaque, stream: net.Stream) void,
|
netClose: *const fn (?*anyopaque, stream: net.Stream) void,
|
||||||
};
|
};
|
||||||
|
|
@ -760,6 +760,11 @@ pub const File = struct {
|
||||||
}
|
}
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError {
|
||||||
|
assert(std.fs.path.isAbsolute(absolute_path));
|
||||||
|
return Dir.cwd().openFile(io, absolute_path, flags);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Timestamp = enum(i96) {
|
pub const Timestamp = enum(i96) {
|
||||||
|
|
@ -1205,7 +1210,7 @@ pub fn asyncConcurrent(
|
||||||
const Args = @TypeOf(args);
|
const Args = @TypeOf(args);
|
||||||
const TypeErased = struct {
|
const TypeErased = struct {
|
||||||
fn start(context: *const anyopaque, result: *anyopaque) void {
|
fn start(context: *const anyopaque, result: *anyopaque) void {
|
||||||
const args_casted: *const Args = @alignCast(@ptrCast(context));
|
const args_casted: *const Args = @ptrCast(@alignCast(context));
|
||||||
const result_casted: *Result = @ptrCast(@alignCast(result));
|
const result_casted: *Result = @ptrCast(@alignCast(result));
|
||||||
result_casted.* = @call(.auto, function, args_casted.*);
|
result_casted.* = @call(.auto, function, args_casted.*);
|
||||||
}
|
}
|
||||||
|
|
@ -1234,7 +1239,7 @@ pub fn asyncDetached(io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf
|
||||||
const Args = @TypeOf(args);
|
const Args = @TypeOf(args);
|
||||||
const TypeErased = struct {
|
const TypeErased = struct {
|
||||||
fn start(context: *const anyopaque) void {
|
fn start(context: *const anyopaque) void {
|
||||||
const args_casted: *const Args = @alignCast(@ptrCast(context));
|
const args_casted: *const Args = @ptrCast(@alignCast(context));
|
||||||
@call(.auto, function, args_casted.*);
|
@call(.auto, function, args_casted.*);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ fn async(
|
||||||
start(context.ptr, result.ptr);
|
start(context.ptr, result.ptr);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
const cpu_count = pool.cpu_count catch {
|
const cpu_count = pool.cpu_count catch {
|
||||||
return asyncConcurrent(userdata, result.len, result_alignment, context, context_alignment, start) catch {
|
return asyncConcurrent(userdata, result.len, result_alignment, context, context_alignment, start) catch {
|
||||||
start(context.ptr, result.ptr);
|
start(context.ptr, result.ptr);
|
||||||
|
|
@ -244,7 +244,7 @@ fn async(
|
||||||
const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
|
const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
|
||||||
const result_offset = result_alignment.forward(context_offset + context.len);
|
const result_offset = result_alignment.forward(context_offset + context.len);
|
||||||
const n = result_offset + result.len;
|
const n = result_offset + result.len;
|
||||||
const closure: *AsyncClosure = @alignCast(@ptrCast(gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch {
|
const closure: *AsyncClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch {
|
||||||
start(context.ptr, result.ptr);
|
start(context.ptr, result.ptr);
|
||||||
return null;
|
return null;
|
||||||
}));
|
}));
|
||||||
|
|
@ -309,13 +309,13 @@ fn asyncConcurrent(
|
||||||
) error{OutOfMemory}!*Io.AnyFuture {
|
) error{OutOfMemory}!*Io.AnyFuture {
|
||||||
if (builtin.single_threaded) unreachable;
|
if (builtin.single_threaded) unreachable;
|
||||||
|
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
const cpu_count = pool.cpu_count catch 1;
|
const cpu_count = pool.cpu_count catch 1;
|
||||||
const gpa = pool.allocator;
|
const gpa = pool.allocator;
|
||||||
const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
|
const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
|
||||||
const result_offset = result_alignment.forward(context_offset + context.len);
|
const result_offset = result_alignment.forward(context_offset + context.len);
|
||||||
const n = result_offset + result_len;
|
const n = result_offset + result_len;
|
||||||
const closure: *AsyncClosure = @alignCast(@ptrCast(try gpa.alignedAlloc(u8, .of(AsyncClosure), n)));
|
const closure: *AsyncClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(AsyncClosure), n)));
|
||||||
|
|
||||||
closure.* = .{
|
closure.* = .{
|
||||||
.func = start,
|
.func = start,
|
||||||
|
|
@ -399,11 +399,11 @@ fn asyncDetached(
|
||||||
start: *const fn (context: *const anyopaque) void,
|
start: *const fn (context: *const anyopaque) void,
|
||||||
) void {
|
) void {
|
||||||
if (builtin.single_threaded) return start(context.ptr);
|
if (builtin.single_threaded) return start(context.ptr);
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
const cpu_count = pool.cpu_count catch 1;
|
const cpu_count = pool.cpu_count catch 1;
|
||||||
const gpa = pool.allocator;
|
const gpa = pool.allocator;
|
||||||
const n = DetachedClosure.contextEnd(context_alignment, context.len);
|
const n = DetachedClosure.contextEnd(context_alignment, context.len);
|
||||||
const closure: *DetachedClosure = @alignCast(@ptrCast(gpa.alignedAlloc(u8, .of(DetachedClosure), n) catch {
|
const closure: *DetachedClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(DetachedClosure), n) catch {
|
||||||
return start(context.ptr);
|
return start(context.ptr);
|
||||||
}));
|
}));
|
||||||
closure.* = .{
|
closure.* = .{
|
||||||
|
|
@ -451,7 +451,7 @@ fn await(
|
||||||
result_alignment: std.mem.Alignment,
|
result_alignment: std.mem.Alignment,
|
||||||
) void {
|
) void {
|
||||||
_ = result_alignment;
|
_ = result_alignment;
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
|
const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
|
||||||
closure.waitAndFree(pool.allocator, result);
|
closure.waitAndFree(pool.allocator, result);
|
||||||
}
|
}
|
||||||
|
|
@ -463,7 +463,7 @@ fn cancel(
|
||||||
result_alignment: std.mem.Alignment,
|
result_alignment: std.mem.Alignment,
|
||||||
) void {
|
) void {
|
||||||
_ = result_alignment;
|
_ = result_alignment;
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
|
const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
|
||||||
switch (@atomicRmw(
|
switch (@atomicRmw(
|
||||||
std.Thread.Id,
|
std.Thread.Id,
|
||||||
|
|
@ -486,7 +486,7 @@ fn cancel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancelRequested(userdata: ?*anyopaque) bool {
|
fn cancelRequested(userdata: ?*anyopaque) bool {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
_ = pool;
|
_ = pool;
|
||||||
const closure = current_closure orelse return false;
|
const closure = current_closure orelse return false;
|
||||||
return @atomicLoad(std.Thread.Id, &closure.cancel_tid, .acquire) == AsyncClosure.canceling_tid;
|
return @atomicLoad(std.Thread.Id, &closure.cancel_tid, .acquire) == AsyncClosure.canceling_tid;
|
||||||
|
|
@ -520,7 +520,7 @@ fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mut
|
||||||
}
|
}
|
||||||
|
|
||||||
fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
|
fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
comptime assert(@TypeOf(cond.state) == u64);
|
comptime assert(@TypeOf(cond.state) == u64);
|
||||||
const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
|
const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
|
||||||
const cond_state = &ints[0];
|
const cond_state = &ints[0];
|
||||||
|
|
@ -567,7 +567,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I
|
||||||
}
|
}
|
||||||
|
|
||||||
fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
|
fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
_ = pool;
|
_ = pool;
|
||||||
comptime assert(@TypeOf(cond.state) == u64);
|
comptime assert(@TypeOf(cond.state) == u64);
|
||||||
const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
|
const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
|
||||||
|
|
@ -624,7 +624,7 @@ fn createFile(
|
||||||
sub_path: []const u8,
|
sub_path: []const u8,
|
||||||
flags: Io.File.CreateFlags,
|
flags: Io.File.CreateFlags,
|
||||||
) Io.File.OpenError!Io.File {
|
) Io.File.OpenError!Io.File {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
try pool.checkCancel();
|
try pool.checkCancel();
|
||||||
const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
|
const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
|
||||||
const fs_file = try fs_dir.createFile(sub_path, flags);
|
const fs_file = try fs_dir.createFile(sub_path, flags);
|
||||||
|
|
@ -637,7 +637,7 @@ fn openFile(
|
||||||
sub_path: []const u8,
|
sub_path: []const u8,
|
||||||
flags: Io.File.OpenFlags,
|
flags: Io.File.OpenFlags,
|
||||||
) Io.File.OpenError!Io.File {
|
) Io.File.OpenError!Io.File {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
try pool.checkCancel();
|
try pool.checkCancel();
|
||||||
const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
|
const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
|
||||||
const fs_file = try fs_dir.openFile(sub_path, flags);
|
const fs_file = try fs_dir.openFile(sub_path, flags);
|
||||||
|
|
@ -645,14 +645,14 @@ fn openFile(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
|
fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
_ = pool;
|
_ = pool;
|
||||||
const fs_file: std.fs.File = .{ .handle = file.handle };
|
const fs_file: std.fs.File = .{ .handle = file.handle };
|
||||||
return fs_file.close();
|
return fs_file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: posix.off_t) Io.File.PReadError!usize {
|
fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: posix.off_t) Io.File.PReadError!usize {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
try pool.checkCancel();
|
try pool.checkCancel();
|
||||||
const fs_file: std.fs.File = .{ .handle = file.handle };
|
const fs_file: std.fs.File = .{ .handle = file.handle };
|
||||||
return switch (offset) {
|
return switch (offset) {
|
||||||
|
|
@ -662,7 +662,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: posix.off_t
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize {
|
fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
try pool.checkCancel();
|
try pool.checkCancel();
|
||||||
const fs_file: std.fs.File = .{ .handle = file.handle };
|
const fs_file: std.fs.File = .{ .handle = file.handle };
|
||||||
return switch (offset) {
|
return switch (offset) {
|
||||||
|
|
@ -672,14 +672,14 @@ fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posi
|
||||||
}
|
}
|
||||||
|
|
||||||
fn now(userdata: ?*anyopaque, clockid: posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp {
|
fn now(userdata: ?*anyopaque, clockid: posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
try pool.checkCancel();
|
try pool.checkCancel();
|
||||||
const timespec = try posix.clock_gettime(clockid);
|
const timespec = try posix.clock_gettime(clockid);
|
||||||
return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec);
|
return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {
|
fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
const deadline_nanoseconds: i96 = switch (deadline) {
|
const deadline_nanoseconds: i96 = switch (deadline) {
|
||||||
.duration => |duration| duration.nanoseconds,
|
.duration => |duration| duration.nanoseconds,
|
||||||
.timestamp => |timestamp| @intFromEnum(timestamp),
|
.timestamp => |timestamp| @intFromEnum(timestamp),
|
||||||
|
|
@ -704,7 +704,7 @@ fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
|
fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
_ = pool;
|
_ = pool;
|
||||||
|
|
||||||
var reset_event: std.Thread.ResetEvent = .{};
|
var reset_event: std.Thread.ResetEvent = .{};
|
||||||
|
|
@ -736,7 +736,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.ListenOptions) Io.net.ListenError!Io.net.Server {
|
fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.ListenOptions) Io.net.ListenError!Io.net.Server {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
try pool.checkCancel();
|
try pool.checkCancel();
|
||||||
|
|
||||||
const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0;
|
const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0;
|
||||||
|
|
@ -776,7 +776,7 @@ fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.List
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Server.Connection {
|
fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Server.Connection {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
try pool.checkCancel();
|
try pool.checkCancel();
|
||||||
|
|
||||||
var storage: PosixAddress = undefined;
|
var storage: PosixAddress = undefined;
|
||||||
|
|
@ -788,17 +788,20 @@ fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptErr
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn netReadPosix(
|
fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.net.Stream.Reader.Error!usize {
|
||||||
userdata: ?*anyopaque,
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
stream: Io.net.Stream,
|
|
||||||
w: *Io.Writer,
|
|
||||||
limit: Io.Limit,
|
|
||||||
) Io.net.Stream.Reader.Error!usize {
|
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
|
||||||
try pool.checkCancel();
|
try pool.checkCancel();
|
||||||
|
|
||||||
var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
|
var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
|
||||||
const dest = try w.writableVectorPosix(&iovecs_buffer, limit);
|
var i: usize = 0;
|
||||||
|
for (data) |buf| {
|
||||||
|
if (iovecs_buffer.len - i == 0) break;
|
||||||
|
if (buf.len != 0) {
|
||||||
|
iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dest = iovecs_buffer[0..i];
|
||||||
assert(dest[0].len > 0);
|
assert(dest[0].len > 0);
|
||||||
const n = try posix.readv(stream.handle, dest);
|
const n = try posix.readv(stream.handle, dest);
|
||||||
if (n == 0) return error.EndOfStream;
|
if (n == 0) return error.EndOfStream;
|
||||||
|
|
@ -812,7 +815,7 @@ fn netWritePosix(
|
||||||
data: []const []const u8,
|
data: []const []const u8,
|
||||||
splat: usize,
|
splat: usize,
|
||||||
) Io.net.Stream.Writer.Error!usize {
|
) Io.net.Stream.Writer.Error!usize {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
try pool.checkCancel();
|
try pool.checkCancel();
|
||||||
|
|
||||||
var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
|
var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
|
||||||
|
|
@ -866,7 +869,7 @@ fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void {
|
fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void {
|
||||||
const pool: *Pool = @alignCast(@ptrCast(userdata));
|
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||||
_ = pool;
|
_ = pool;
|
||||||
const net_stream: std.net.Stream = .{ .handle = stream.handle };
|
const net_stream: std.net.Stream = .{ .handle = stream.handle };
|
||||||
return net_stream.close();
|
return net_stream.close();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ const builtin = @import("builtin");
|
||||||
const native_os = builtin.os.tag;
|
const native_os = builtin.os.tag;
|
||||||
const std = @import("../std.zig");
|
const std = @import("../std.zig");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
pub const ListenError = std.net.Address.ListenError || Io.Cancelable;
|
pub const ListenError = std.net.Address.ListenError || Io.Cancelable;
|
||||||
|
|
||||||
|
|
@ -16,10 +17,233 @@ pub const ListenOptions = struct {
|
||||||
force_nonblocking: bool = false,
|
force_nonblocking: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// An already-validated host name.
|
||||||
|
pub const HostName = struct {
|
||||||
|
/// Externally managed memory. Already checked to be within `max_len`.
|
||||||
|
bytes: []const u8,
|
||||||
|
|
||||||
|
pub const max_len = 255;
|
||||||
|
|
||||||
|
pub const InitError = error{
|
||||||
|
NameTooLong,
|
||||||
|
InvalidHostName,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(bytes: []const u8) InitError!HostName {
|
||||||
|
if (bytes.len > max_len) return error.NameTooLong;
|
||||||
|
if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName;
|
||||||
|
for (bytes) |byte| {
|
||||||
|
if (!std.ascii.isAscii(byte) or byte == '.' or byte == '-' or std.ascii.isAlphanumeric(byte)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return error.InvalidHostName;
|
||||||
|
}
|
||||||
|
return .{ .bytes = bytes };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const LookupOptions = struct {
|
||||||
|
port: u16,
|
||||||
|
/// Must have at least length 2.
|
||||||
|
addresses_buffer: []IpAddress,
|
||||||
|
/// If a buffer of at least `max_len` is not provided, `lookup` may
|
||||||
|
/// return successfully with zero-length `LookupResult.canonical_name_len`.
|
||||||
|
///
|
||||||
|
/// Suggestion: if not interested in canonical name, pass an empty buffer;
|
||||||
|
/// otherwise pass a buffer of size `max_len`.
|
||||||
|
canonical_name_buffer: []u8,
|
||||||
|
/// `null` means either.
|
||||||
|
family: ?IpAddress.Tag = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LookupError = Io.Cancelable || error{};
|
||||||
|
|
||||||
|
pub const LookupResult = struct {
|
||||||
|
/// How many `LookupOptions.addresses_buffer` elements are populated.
|
||||||
|
addresses_len: usize,
|
||||||
|
/// Length zero means no canonical name returned.
|
||||||
|
canonical_name_len: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {
|
||||||
|
const name = host_name.bytes;
|
||||||
|
assert(name.len <= max_len);
|
||||||
|
assert(options.addresses_buffer.len >= 2);
|
||||||
|
|
||||||
|
if (native_os == .windows) @compileError("TODO");
|
||||||
|
if (builtin.link_libc) @compileError("TODO");
|
||||||
|
if (native_os == .linux) {
|
||||||
|
if (options.family != .ip6) {
|
||||||
|
if (IpAddress.parseIp4(name, options.port)) |addr| {
|
||||||
|
options.addresses_buffer[0] = addr;
|
||||||
|
return .{ .addresses_len = 1, .canonical_name_len = 0 };
|
||||||
|
} else |_| {}
|
||||||
|
}
|
||||||
|
if (options.family != .ip4) {
|
||||||
|
if (IpAddress.parseIp6(name, options.port)) |addr| {
|
||||||
|
options.addresses_buffer[0] = addr;
|
||||||
|
return .{ .addresses_len = 1, .canonical_name_len = 0 };
|
||||||
|
} else |_| {}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const result = try lookupHosts(io, options);
|
||||||
|
if (result.addresses_len > 0) return sortLookupResults(options, result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// RFC 6761 Section 6.3.3
|
||||||
|
// Name resolution APIs and libraries SHOULD recognize
|
||||||
|
// localhost names as special and SHOULD always return the IP
|
||||||
|
// loopback address for address queries and negative responses
|
||||||
|
// for all other query types.
|
||||||
|
|
||||||
|
// Check for equal to "localhost(.)" or ends in ".localhost(.)"
|
||||||
|
const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
|
||||||
|
if (std.mem.endsWith(u8, name, localhost) and
|
||||||
|
(name.len == localhost.len or name[name.len - localhost.len] == '.'))
|
||||||
|
{
|
||||||
|
var i: usize = 0;
|
||||||
|
if (options.family != .ip6) {
|
||||||
|
options.addresses_buffer[i] = .{ .ip4 = .localhost(options.port) };
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if (options.family != .ip4) {
|
||||||
|
options.addresses_buffer[i] = .{ .ip6 = .localhost(options.port) };
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
const canon_name = "localhost";
|
||||||
|
options.canonical_name_buffer[0..canon_name.len].* = canon_name.*;
|
||||||
|
return sortLookupResults(options, .{ .addresses_len = i, .canonical_name_len = canon_name.len });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const result = try lookupDns(io, options);
|
||||||
|
if (result.addresses_len > 0) return sortLookupResults(options, result);
|
||||||
|
}
|
||||||
|
return error.UnknownHostName;
|
||||||
|
}
|
||||||
|
@compileError("unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult {
|
||||||
|
_ = options;
|
||||||
|
_ = result;
|
||||||
|
@panic("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookupDns(io: Io, options: LookupOptions) !LookupResult {
|
||||||
|
_ = io;
|
||||||
|
_ = options;
|
||||||
|
@panic("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookupHosts(io: Io, options: LookupOptions) !LookupResult {
|
||||||
|
const file = Io.File.openFileAbsoluteZ(io, "/etc/hosts", .{}) catch |err| switch (err) {
|
||||||
|
error.FileNotFound,
|
||||||
|
error.NotDir,
|
||||||
|
error.AccessDenied,
|
||||||
|
=> return,
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
|
defer file.close();
|
||||||
|
|
||||||
|
var line_buf: [512]u8 = undefined;
|
||||||
|
var file_reader = file.reader(io, &line_buf);
|
||||||
|
return lookupHostsReader(options, &file_reader.interface) catch |err| switch (err) {
|
||||||
|
error.OutOfMemory => return error.OutOfMemory,
|
||||||
|
error.ReadFailed => return file_reader.err.?,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookupHostsReader(options: LookupOptions, reader: *Io.Reader) error{ReadFailed}!LookupResult {
|
||||||
|
var addresses_len: usize = 0;
|
||||||
|
var canonical_name_len: usize = 0;
|
||||||
|
while (true) {
|
||||||
|
const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
|
||||||
|
error.StreamTooLong => {
|
||||||
|
// Skip lines that are too long.
|
||||||
|
_ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) {
|
||||||
|
error.EndOfStream => break,
|
||||||
|
error.ReadFailed => return error.ReadFailed,
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
error.ReadFailed => return error.ReadFailed,
|
||||||
|
error.EndOfStream => break,
|
||||||
|
};
|
||||||
|
var split_it = std.mem.splitScalar(u8, line, '#');
|
||||||
|
const no_comment_line = split_it.first();
|
||||||
|
|
||||||
|
var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t");
|
||||||
|
const ip_text = line_it.next() orelse continue;
|
||||||
|
var first_name_text: ?[]const u8 = null;
|
||||||
|
while (line_it.next()) |name_text| {
|
||||||
|
if (std.mem.eql(u8, name_text, options.name)) {
|
||||||
|
if (first_name_text == null) first_name_text = name_text;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else continue;
|
||||||
|
|
||||||
|
if (canonical_name_len == 0) {
|
||||||
|
if (HostName.init(first_name_text)) |name_text| {
|
||||||
|
if (name_text.len <= options.canonical_name_buffer.len) {
|
||||||
|
@memcpy(options.canonical_name_buffer[0..name_text.len], name_text);
|
||||||
|
canonical_name_len = name_text.len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.family != .ip6) {
|
||||||
|
if (IpAddress.parseIp4(ip_text, options.port)) |addr| {
|
||||||
|
options.addresses_buffer[addresses_len] = addr;
|
||||||
|
addresses_len += 1;
|
||||||
|
if (options.addresses_buffer.len - addresses_len == 0) return .{
|
||||||
|
.addresses_len = addresses_len,
|
||||||
|
.canonical_name_len = canonical_name_len,
|
||||||
|
};
|
||||||
|
} else |_| {}
|
||||||
|
}
|
||||||
|
if (options.family != .ip4) {
|
||||||
|
if (IpAddress.parseIp6(ip_text, options.port)) |addr| {
|
||||||
|
options.addresses_buffer[addresses_len] = addr;
|
||||||
|
addresses_len += 1;
|
||||||
|
if (options.addresses_buffer.len - addresses_len == 0) return .{
|
||||||
|
.addresses_len = addresses_len,
|
||||||
|
.canonical_name_len = canonical_name_len,
|
||||||
|
};
|
||||||
|
} else |_| {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError;
|
||||||
|
|
||||||
|
pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream {
|
||||||
|
var addresses_buffer: [32]IpAddress = undefined;
|
||||||
|
|
||||||
|
const results = try lookup(host_name, .{
|
||||||
|
.port = port,
|
||||||
|
.addresses_buffer = &addresses_buffer,
|
||||||
|
.canonical_name_buffer = &.{},
|
||||||
|
});
|
||||||
|
const addresses = addresses_buffer[0..results.addresses_len];
|
||||||
|
|
||||||
|
if (addresses.len == 0) return error.UnknownHostName;
|
||||||
|
|
||||||
|
for (addresses) |addr| {
|
||||||
|
return addr.connectTcp(io) catch |err| switch (err) {
|
||||||
|
error.ConnectionRefused => continue,
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return error.ConnectionRefused;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const IpAddress = union(enum) {
|
pub const IpAddress = union(enum) {
|
||||||
ip4: Ip4Address,
|
ip4: Ip4Address,
|
||||||
ip6: Ip6Address,
|
ip6: Ip6Address,
|
||||||
|
|
||||||
|
pub const Tag = @typeInfo(IpAddress).@"union".tag_type.?;
|
||||||
|
|
||||||
/// Parse the given IP address string into an `IpAddress` value.
|
/// Parse the given IP address string into an `IpAddress` value.
|
||||||
pub fn parse(name: []const u8, port: u16) !IpAddress {
|
pub fn parse(name: []const u8, port: u16) !IpAddress {
|
||||||
if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
|
if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
|
||||||
|
|
@ -94,6 +318,13 @@ pub const Ip4Address = struct {
|
||||||
bytes: [4]u8,
|
bytes: [4]u8,
|
||||||
port: u16,
|
port: u16,
|
||||||
|
|
||||||
|
pub fn localhost(port: u16) Ip4Address {
|
||||||
|
return .{
|
||||||
|
.bytes = .{ 127, 0, 0, 1 },
|
||||||
|
.port = port,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub const ParseError = error{
|
pub const ParseError = error{
|
||||||
Overflow,
|
Overflow,
|
||||||
InvalidEnd,
|
InvalidEnd,
|
||||||
|
|
@ -373,7 +604,10 @@ pub const Stream = struct {
|
||||||
pub fn init(stream: Stream, buffer: []u8) Reader {
|
pub fn init(stream: Stream, buffer: []u8) Reader {
|
||||||
return .{
|
return .{
|
||||||
.interface = .{
|
.interface = .{
|
||||||
.vtable = &.{ .stream = streamImpl },
|
.vtable = &.{
|
||||||
|
.stream = streamImpl,
|
||||||
|
.readVec = readVec,
|
||||||
|
},
|
||||||
.buffer = buffer,
|
.buffer = buffer,
|
||||||
.seek = 0,
|
.seek = 0,
|
||||||
.end = 0,
|
.end = 0,
|
||||||
|
|
@ -384,9 +618,17 @@ pub const Stream = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn streamImpl(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
|
fn streamImpl(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
|
||||||
|
const dest = limit.slice(try io_w.writableSliceGreedy(1));
|
||||||
|
var data: [1][]u8 = .{dest};
|
||||||
|
const n = try readVec(io_r, &data);
|
||||||
|
io_w.advance(n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readVec(io_r: *Reader, data: [][]u8) Io.Reader.Error!usize {
|
||||||
const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
|
const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
|
||||||
const io = r.io;
|
const io = r.io;
|
||||||
return io.vtable.netRead(io.vtable.userdata, r.stream, io_w, limit);
|
return io.vtable.netReadVec(io.vtable.userdata, r.stream, io_r, data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ const builtin = @import("builtin");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const http = std.http;
|
const http = std.http;
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const net = std.net;
|
|
||||||
const Uri = std.Uri;
|
const Uri = std.Uri;
|
||||||
const Allocator = mem.Allocator;
|
const Allocator = mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const Io = std.Io;
|
||||||
const Writer = std.Io.Writer;
|
const Writer = std.Io.Writer;
|
||||||
const Reader = std.Io.Reader;
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
|
@ -22,6 +22,8 @@ pub const disable_tls = std.options.http_disable_tls;
|
||||||
|
|
||||||
/// Used for all client allocations. Must be thread-safe.
|
/// Used for all client allocations. Must be thread-safe.
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
/// Used for opening TCP connections.
|
||||||
|
io: Io,
|
||||||
|
|
||||||
ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},
|
ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},
|
||||||
ca_bundle_mutex: std.Thread.Mutex = .{},
|
ca_bundle_mutex: std.Thread.Mutex = .{},
|
||||||
|
|
@ -225,8 +227,8 @@ pub const Protocol = enum {
|
||||||
|
|
||||||
pub const Connection = struct {
|
pub const Connection = struct {
|
||||||
client: *Client,
|
client: *Client,
|
||||||
stream_writer: net.Stream.Writer,
|
stream_writer: Io.net.Stream.Writer,
|
||||||
stream_reader: net.Stream.Reader,
|
stream_reader: Io.net.Stream.Reader,
|
||||||
/// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
|
/// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
|
||||||
pool_node: std.DoublyLinkedList.Node,
|
pool_node: std.DoublyLinkedList.Node,
|
||||||
port: u16,
|
port: u16,
|
||||||
|
|
@ -242,7 +244,7 @@ pub const Connection = struct {
|
||||||
client: *Client,
|
client: *Client,
|
||||||
remote_host: []const u8,
|
remote_host: []const u8,
|
||||||
port: u16,
|
port: u16,
|
||||||
stream: net.Stream,
|
stream: Io.net.Stream,
|
||||||
) error{OutOfMemory}!*Plain {
|
) error{OutOfMemory}!*Plain {
|
||||||
const gpa = client.allocator;
|
const gpa = client.allocator;
|
||||||
const alloc_len = allocLen(client, remote_host.len);
|
const alloc_len = allocLen(client, remote_host.len);
|
||||||
|
|
@ -295,7 +297,7 @@ pub const Connection = struct {
|
||||||
client: *Client,
|
client: *Client,
|
||||||
remote_host: []const u8,
|
remote_host: []const u8,
|
||||||
port: u16,
|
port: u16,
|
||||||
stream: net.Stream,
|
stream: Io.net.Stream,
|
||||||
) error{ OutOfMemory, TlsInitializationFailed }!*Tls {
|
) error{ OutOfMemory, TlsInitializationFailed }!*Tls {
|
||||||
const gpa = client.allocator;
|
const gpa = client.allocator;
|
||||||
const alloc_len = allocLen(client, remote_host.len);
|
const alloc_len = allocLen(client, remote_host.len);
|
||||||
|
|
@ -363,7 +365,7 @@ pub const Connection = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ReadError = std.crypto.tls.Client.ReadError || std.net.Stream.ReadError;
|
pub const ReadError = std.crypto.tls.Client.ReadError || Io.net.Stream.ReadError;
|
||||||
|
|
||||||
pub fn getReadError(c: *const Connection) ?ReadError {
|
pub fn getReadError(c: *const Connection) ?ReadError {
|
||||||
return switch (c.protocol) {
|
return switch (c.protocol) {
|
||||||
|
|
@ -378,8 +380,8 @@ pub const Connection = struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getStream(c: *Connection) net.Stream {
|
fn getStream(c: *Connection) Io.net.Stream {
|
||||||
return c.stream_reader.getStream();
|
return c.stream_reader.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn host(c: *Connection) []u8 {
|
pub fn host(c: *Connection) []u8 {
|
||||||
|
|
@ -1409,7 +1411,7 @@ pub fn connectTcp(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ConnectTcpOptions = struct {
|
pub const ConnectTcpOptions = struct {
|
||||||
host: []const u8,
|
host: Io.net.HostName,
|
||||||
port: u16,
|
port: u16,
|
||||||
protocol: Protocol,
|
protocol: Protocol,
|
||||||
|
|
||||||
|
|
@ -1418,7 +1420,7 @@ pub const ConnectTcpOptions = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {
|
pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {
|
||||||
const host = options.host;
|
const host = options.host_name;
|
||||||
const port = options.port;
|
const port = options.port;
|
||||||
const protocol = options.protocol;
|
const protocol = options.protocol;
|
||||||
|
|
||||||
|
|
@ -1431,7 +1433,7 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp
|
||||||
.protocol = protocol,
|
.protocol = protocol,
|
||||||
})) |conn| return conn;
|
})) |conn| return conn;
|
||||||
|
|
||||||
const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {
|
const stream = host.connectTcp(client.io, port) catch |err| switch (err) {
|
||||||
error.ConnectionRefused => return error.ConnectionRefused,
|
error.ConnectionRefused => return error.ConnectionRefused,
|
||||||
error.NetworkUnreachable => return error.NetworkUnreachable,
|
error.NetworkUnreachable => return error.NetworkUnreachable,
|
||||||
error.ConnectionTimedOut => return error.ConnectionTimedOut,
|
error.ConnectionTimedOut => return error.ConnectionTimedOut,
|
||||||
|
|
@ -1440,6 +1442,7 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp
|
||||||
error.NameServerFailure => return error.NameServerFailure,
|
error.NameServerFailure => return error.NameServerFailure,
|
||||||
error.UnknownHostName => return error.UnknownHostName,
|
error.UnknownHostName => return error.UnknownHostName,
|
||||||
error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses,
|
error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses,
|
||||||
|
error.Canceled => return error.Canceled,
|
||||||
else => return error.UnexpectedConnectFailure,
|
else => return error.UnexpectedConnectFailure,
|
||||||
};
|
};
|
||||||
errdefer stream.close();
|
errdefer stream.close();
|
||||||
|
|
|
||||||
|
|
@ -1461,15 +1461,8 @@ test parseHosts {
|
||||||
try std.testing.expectFmt("127.0.0.2:1234", "{f}", .{addrs.items[0].addr});
|
try std.testing.expectFmt("127.0.0.2:1234", "{f}", .{addrs.items[0].addr});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isValidHostName(hostname: []const u8) bool {
|
pub fn isValidHostName(bytes: []const u8) bool {
|
||||||
if (hostname.len >= 254) return false;
|
_ = std.Io.net.HostName.init(bytes) catch return false;
|
||||||
if (!std.unicode.utf8ValidateSlice(hostname)) return false;
|
|
||||||
for (hostname) |byte| {
|
|
||||||
if (!std.ascii.isAscii(byte) or byte == '.' or byte == '-' or std.ascii.isAlphanumeric(byte)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue