aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/os/hostname.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/os/hostname.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (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.zig147
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"),
+ );
+}