From 3b0a34afbca40694b3b170b7f1cb681e9454435a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristo=CC=81fer=20R?= Date: Thu, 31 Oct 2024 21:55:02 -0400 Subject: [PATCH] Extract OSC 7 hostname parsing into helper functions --- src/termio/stream_handler.zig | 111 ++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index bd2017f89..8db67f66c 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -1030,6 +1030,48 @@ pub const StreamHandler = struct { self.terminal.markSemanticPrompt(.command); } + pub fn bufPrintHostnameFromFileUri(buf: []u8, uri: std.Uri) ![]const u8 { + // Get the raw string of the URI. Its unclear to me if the various + // tags of this enum guarantee no percent-encoding so we just + // check all of it. This isn't a performance critical path. + const host_component = uri.host orelse return error.NoHostnameInUri; + const host = switch (host_component) { + .raw => |v| v, + .percent_encoded => |v| v, + }; + + // When the "Private Wi-Fi address" setting is toggled on macOS the hostname + // is set to a string of digits separated by a colon, e.g. '12:34:56:78:90:12'. + // The URI will be parsed as if the last set o digit is a port, so we need to + // make sure that part is included when it's set. + if (uri.port) |port| { + var fbs = std.io.fixedBufferStream(buf); + std.fmt.format(fbs.writer().any(), "{s}:{d}", .{ host, port }) catch |err| switch (err) { + error.NoSpaceLeft => return error.NoSpaceLeft, + else => unreachable, + }; + + return fbs.getWritten(); + } + + return host; + } + + pub fn isLocalHostname(hostname: []const u8) !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. + var buf: [posix.HOST_NAME_MAX]u8 = undefined; + const ourHostname = posix.gethostname(&buf) catch |err| { + return err; + }; + + return std.mem.eql(u8, hostname, ourHostname); + } + pub fn reportPwd(self: *StreamHandler, url: []const u8) !void { if (builtin.os.tag == .windows) { log.warn("reportPwd unimplemented on windows", .{}); @@ -1048,57 +1090,34 @@ pub const StreamHandler = struct { return; } + // RFC 793 defines port numbers as 16-bit numbers. 5 digits is sufficient to represent + // the maximum since 2^16 - 1 = 65_535. + // See https://www.rfc-editor.org/rfc/rfc793#section-3.1. + const PORT_NUMBER_MAX_DIGITS = 5; + // Make sure there is space for a max length hostname + the max number of digits. + var host_and_port_buf: [posix.HOST_NAME_MAX + PORT_NUMBER_MAX_DIGITS]u8 = undefined; + + const hostname = bufPrintHostnameFromFileUri(&host_and_port_buf, uri) catch |err| switch (err) { + error.NoHostnameInUri => { + log.warn("OSC 7 uri must contain a hostname: {}", .{err}); + return; + }, + error.NoSpaceLeft => |e| { + log.warn("failed to get full hostname for OSC 7 validation: {}", .{e}); + return; + }, + }; + // OSC 7 is a little sketchy because anyone can send any value from // any host (such an SSH session). The best practice terminals follow // is to valid the hostname to be local. - const host_valid = host_valid: { - const host_component = uri.host orelse break :host_valid false; - - // Get the raw string of the URI. Its unclear to me if the various - // tags of this enum guarantee no percent-encoding so we just - // check all of it. This isn't a performance critical path. - const host = host: { - const h = switch (host_component) { - .raw => |v| v, - .percent_encoded => |v| v, - }; - if (h.len == 0 or std.mem.eql(u8, "localhost", h)) { - break :host_valid true; - } - - // When the "Private Wi-Fi address" setting is toggled on macOS the hostname - // is set to a string of digits separated by a colon, e.g. '12:34:56:78:90:12'. - // The URI will be parsed as if the last set o digit is a port, so we need to - // make sure that part is included when it's set. - if (uri.port) |port| { - // RFC 793 defines port numbers as 16-bit numbers. 5 digits is sufficient to represent - // the maximum since 2^16 - 1 = 65_535. - // See https://www.rfc-editor.org/rfc/rfc793#section-3.1. - const PORT_NUMBER_MAX_DIGITS = 5; - - // Make sure there is space for a max length hostname + the max number of digits. - var host_and_port_buf: [posix.HOST_NAME_MAX + PORT_NUMBER_MAX_DIGITS]u8 = undefined; - - const host_and_port = std.fmt.bufPrint(&host_and_port_buf, "{s}:{d}", .{ h, port }) catch |err| { - log.warn("failed to get full hostname for OSC 7 validation: {}", .{err}); - break :host_valid false; - }; - - break :host host_and_port; - } else { - break :host h; - } - }; - - // Otherwise, it must match our hostname. - var buf: [posix.HOST_NAME_MAX]u8 = undefined; - const hostname = posix.gethostname(&buf) catch |err| { + const host_valid = isLocalHostname(hostname) catch |err| switch (err) { + error.PermissionDenied, error.Unexpected => { log.warn("failed to get hostname for OSC 7 validation: {}", .{err}); - break :host_valid false; - }; - - break :host_valid std.mem.eql(u8, host, hostname); + return; + }, }; + if (!host_valid) { log.warn("OSC 7 host must be local", .{}); return;