diff --git a/src/Command.zig b/src/Command.zig index ce606f770..f963a1857 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -256,7 +256,10 @@ pub fn expandPath(alloc: Allocator, cmd: []const u8) !?[]u8 { const full_path = path_buf[0..path_len :0]; // Stat it - const f = std.fs.openFileAbsolute(full_path, .{}) catch |err| switch (err) { + const f = std.fs.cwd().openFile( + full_path, + .{}, + ) catch |err| switch (err) { error.FileNotFound => continue, error.AccessDenied => { // Accumulate this and return it later so we can try other diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig index c7ebc6ac8..58e3cea9b 100644 --- a/src/apprt/surface.zig +++ b/src/apprt/surface.zig @@ -9,7 +9,7 @@ const Config = @import("../config.zig").Config; pub const Message = union(enum) { /// Represents a write request. Magic number comes from the max size /// we want this union to be. - pub const WriteReq = termio.MessageData(u8, 256); + pub const WriteReq = termio.MessageData(u8, 255); /// Set the title of the surface. /// TODO: we should change this to a "WriteReq" style structure in diff --git a/src/terminal/Parser.zig b/src/terminal/Parser.zig index 154dfee22..785fd938e 100644 --- a/src/terminal/Parser.zig +++ b/src/terminal/Parser.zig @@ -378,7 +378,11 @@ fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action { self.param_acc *|= 10; } self.param_acc +|= c - '0'; - self.param_acc_idx += 1; + + // Increment our accumulator index. If we overflow then + // we're out of bounds and we exit immediately. + self.param_acc_idx, const overflow = @addWithOverflow(self.param_acc_idx, 1); + if (overflow > 0) break :param null; // The client is expected to perform no action. break :param null; @@ -388,6 +392,9 @@ fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action { break :osc_put null; }, .csi_dispatch => csi_dispatch: { + // Ignore too many parameters + if (self.params_idx >= MAX_PARAMS) break :csi_dispatch null; + // Finalize parameters if we have one if (self.param_acc_idx > 0) { self.params[self.params_idx] = self.param_acc; @@ -904,3 +911,22 @@ test "csi followed by utf8" { try testing.expect(a[2] == null); } } + +test "csi: too many params" { + var p = init(); + _ = p.next(0x1B); + _ = p.next('['); + for (0..100) |_| { + _ = p.next('1'); + _ = p.next(';'); + } + _ = p.next('1'); + + { + const a = p.next('C'); + try testing.expect(p.state == .ground); + try testing.expect(a[0] == null); + try testing.expect(a[1] == null); + try testing.expect(a[2] == null); + } +} diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 1fda8c514..4309db4f9 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -969,43 +969,52 @@ pub fn Stream(comptime Handler: type) type { .change_window_title => |title| { if (@hasDecl(T, "changeWindowTitle")) { try self.handler.changeWindowTitle(title); + return; } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, .clipboard_contents => |clip| { if (@hasDecl(T, "clipboardContents")) { try self.handler.clipboardContents(clip.kind, clip.data); + return; } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, .prompt_start => |v| { - if (@hasDecl(T, "promptStart")) switch (v.kind) { - .primary, .right => try self.handler.promptStart(v.aid, v.redraw), - .continuation => try self.handler.promptContinuation(v.aid), + if (@hasDecl(T, "promptStart")) { + switch (v.kind) { + .primary, .right => try self.handler.promptStart(v.aid, v.redraw), + .continuation => try self.handler.promptContinuation(v.aid), + } + return; } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, .prompt_end => { if (@hasDecl(T, "promptEnd")) { try self.handler.promptEnd(); + return; } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, .end_of_input => { if (@hasDecl(T, "endOfInput")) { try self.handler.endOfInput(); + return; } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, .end_of_command => |end| { if (@hasDecl(T, "endOfCommand")) { try self.handler.endOfCommand(end.exit_code); + return; } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, .report_pwd => |v| { if (@hasDecl(T, "reportPwd")) { try self.handler.reportPwd(v.value); + return; } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, @@ -1017,12 +1026,14 @@ pub fn Stream(comptime Handler: type) type { }; try self.handler.setMouseShape(shape); + return; } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, .report_default_color => |v| { if (@hasDecl(T, "reportDefaultColor")) { try self.handler.reportDefaultColor(v.kind, v.terminator); + return; } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, @@ -1031,6 +1042,13 @@ pub fn Stream(comptime Handler: type) type { else log.warn("unimplemented OSC command: {}", .{cmd}), } + + // Fall through for when we don't have a handler. + if (@hasDecl(T, "oscUnimplemented")) { + try self.handler.oscUnimplemented(cmd); + } else { + log.warn("unimplemented OSC command: {s}", .{@tagName(cmd)}); + } } fn configureCharset( @@ -1598,3 +1616,28 @@ test "stream: insert characters" { for ("\x1B[?42@") |c| try s.next(c); try testing.expect(!s.handler.called); } + +test "stream: too many csi params" { + const H = struct { + pub fn setCursorRight(self: *@This(), v: u16) !void { + _ = v; + _ = self; + unreachable; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + try s.nextSlice("\x1B[1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1C"); +} + +test "stream: csi param too long" { + const H = struct { + pub fn setCursorRight(self: *@This(), v: u16) !void { + _ = v; + _ = self; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + try s.nextSlice("\x1B[1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111C"); +} diff --git a/src/termio/message.zig b/src/termio/message.zig index c7fe19976..e2b98c4aa 100644 --- a/src/termio/message.zig +++ b/src/termio/message.zig @@ -90,8 +90,9 @@ pub fn MessageData(comptime Elem: type, comptime small_size: comptime_int) type pub const Small = struct { pub const Max = small_size; pub const Array = [Max]Elem; + pub const Len = std.math.IntFittingRange(0, small_size); data: Array = undefined, - len: u8 = 0, + len: Len = 0, }; pub const Alloc = struct { @@ -182,3 +183,14 @@ test "MessageData init alloc" { try testing.expect(io == .alloc); io.alloc.alloc.free(io.alloc.data); } + +test "MessageData small fits non-u8 sized data" { + const testing = std.testing; + const alloc = testing.allocator; + + const len = 500; + const Data = MessageData(u8, len); + const input: []const u8 = "X" ** len; + const io = try Data.init(alloc, input); + try testing.expect(io == .small); +}