diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/os/hostname.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/os/hostname.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/os/hostname.zig | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/os/hostname.zig b/vendor/libghostty-vt/src/os/hostname.zig new file mode 100644 index 00000000..af9148fb --- /dev/null +++ b/vendor/libghostty-vt/src/os/hostname.zig @@ -0,0 +1,147 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const posix = std.posix; + +pub const LocalHostnameValidationError = error{ + PermissionDenied, + Unexpected, +}; + +/// Validates a hostname according to [RFC 1123](https://www.rfc-editor.org/rfc/rfc1123) +/// +/// std.net.isValidHostname is (currently) too generous. It considers strings like +/// ".example.com", "exa..mple.com", and "-example.com" to be valid hostnames, which +/// is incorrect. +pub fn isValid(hostname: []const u8) bool { + if (hostname.len == 0) return false; + if (hostname[0] == '.') return false; + + // Ignore trailing dot (FQDN). It doesn't count toward our length. + const end = if (hostname[hostname.len - 1] == '.') end: { + if (hostname.len == 1) return false; + break :end hostname.len - 1; + } else hostname.len; + + if (end > 253) return false; + + // Hostnames are divided into dot-separated "labels", which: + // + // - Start with a letter or digit + // - Can contain letters, digits, or hyphens + // - Must end with a letter or digit + // - Have a minimum of 1 character and a maximum of 63 + var label_start: usize = 0; + var label_len: usize = 0; + for (hostname[0..end], 0..) |c, i| { + switch (c) { + '.' => { + if (label_len == 0 or label_len > 63) return false; + if (!std.ascii.isAlphanumeric(hostname[label_start])) return false; + if (!std.ascii.isAlphanumeric(hostname[i - 1])) return false; + + label_start = i + 1; + label_len = 0; + }, + '-' => { + label_len += 1; + }, + else => { + if (!std.ascii.isAlphanumeric(c)) return false; + label_len += 1; + }, + } + } + + // Validate the final label + if (label_len == 0 or label_len > 63) return false; + if (!std.ascii.isAlphanumeric(hostname[label_start])) return false; + if (!std.ascii.isAlphanumeric(hostname[end - 1])) return false; + + return true; +} + +test isValid { + const testing = std.testing; + + // Valid hostnames + try testing.expect(isValid("example")); + try testing.expect(isValid("example.com")); + try testing.expect(isValid("www.example.com")); + try testing.expect(isValid("sub.domain.example.com")); + try testing.expect(isValid("example.com.")); + try testing.expect(isValid("host-name.example.com.")); + try testing.expect(isValid("123.example.com.")); + try testing.expect(isValid("a-b.com")); + try testing.expect(isValid("a.b.c.d.e.f.g")); + try testing.expect(isValid("127.0.0.1")); // Also a valid hostname + try testing.expect(isValid("a" ** 63 ++ ".com")); // Label exactly 63 chars (valid) + try testing.expect(isValid("a." ** 126 ++ "a")); // Total length 253 (valid) + + // Invalid hostnames + try testing.expect(!isValid("")); + try testing.expect(!isValid(".example.com")); + try testing.expect(!isValid("example.com..")); + try testing.expect(!isValid("host..domain")); + try testing.expect(!isValid("-hostname")); + try testing.expect(!isValid("hostname-")); + try testing.expect(!isValid("a.-.b")); + try testing.expect(!isValid("host_name.com")); + try testing.expect(!isValid(".")); + try testing.expect(!isValid("..")); + try testing.expect(!isValid("a" ** 64 ++ ".com")); // Label length 64 (too long) + try testing.expect(!isValid("a." ** 126 ++ "ab")); // Total length 254 (too long) +} + +/// Checks if a hostname is local to the current machine. This matches +/// both "localhost" and the current hostname of the machine (as returned +/// by `gethostname`). +pub fn isLocal(hostname: []const u8) LocalHostnameValidationError!bool { + // A 'localhost' hostname is always considered local. + if (std.mem.eql(u8, "localhost", hostname)) return true; + + // If hostname is not "localhost" it must match our hostname. + switch (builtin.os.tag) { + .windows => { + const windows = @import("windows.zig"); + var buf: [256:0]u8 = undefined; + var nSize: windows.DWORD = buf.len; + if (windows.exp.kernel32.GetComputerNameA(&buf, &nSize) == 0) return false; + const ourHostname = buf[0..nSize]; + return std.mem.eql(u8, hostname, ourHostname); + }, + else => { + var buf: [posix.HOST_NAME_MAX]u8 = undefined; + const ourHostname = try posix.gethostname(&buf); + return std.mem.eql(u8, hostname, ourHostname); + }, + } +} + +test "isLocal returns true when provided hostname is localhost" { + try std.testing.expect(try isLocal("localhost")); +} + +test "isLocal returns true when hostname is local" { + switch (builtin.os.tag) { + .windows => { + const windows = @import("windows.zig"); + var buf: [256:0]u8 = undefined; + var nSize: windows.DWORD = buf.len; + if (windows.exp.kernel32.GetComputerNameA(&buf, &nSize) == 0) return error.GetComputerNameFailed; + const localHostname = buf[0..nSize]; + try std.testing.expect(try isLocal(localHostname)); + }, + else => { + var buf: [posix.HOST_NAME_MAX]u8 = undefined; + const localHostname = try posix.gethostname(&buf); + try std.testing.expect(try isLocal(localHostname)); + }, + } +} + +test "isLocal returns false when hostname is not local" { + try std.testing.expectEqual( + false, + try isLocal("not-the-local-hostname"), + ); +} |
