mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Address review comments.
- Cap the total number of requests at twice the maximum number of keys (currently 263, so 526 requests). Basically you can set and then query every key in one message. This is an absurdly high number but should prevent serious DOS attacks. - Clarify meaning of new hex color codes. - Better handle sending messages to the renderer in a way that should prevent deadlocks. - Handle 0-255 palette color requests by creatively using non-exhautive enums. - Fix an error in the query reply.
This commit is contained in:
@ -208,9 +208,11 @@ pub const RGB = struct {
|
||||
/// where <red>, <green>, and <blue> are floating point values between
|
||||
/// 0.0 and 1.0 (inclusive).
|
||||
///
|
||||
/// 3. #hhh, #hhhhhh, #hhhhhhhhh #hhhhhhhhhhhh
|
||||
/// 3. #rgb, #rrggbb, #rrrgggbbb #rrrrggggbbbb
|
||||
///
|
||||
/// where `h` is a single hexadecimal digit.
|
||||
/// where `r`, `g`, and `b` are a single hexadecimal digit.
|
||||
/// These specifiy a color with 4, 8, 12, and 16 bits of precision
|
||||
/// per color channel.
|
||||
pub fn parse(value: []const u8) !RGB {
|
||||
if (value.len == 0) {
|
||||
return error.InvalidFormat;
|
||||
|
@ -174,16 +174,24 @@ pub const Command = union(enum) {
|
||||
};
|
||||
|
||||
pub const KittyColorProtocol = struct {
|
||||
const Kind = enum {
|
||||
foreground,
|
||||
background,
|
||||
selection_foreground,
|
||||
selection_background,
|
||||
cursor,
|
||||
cursor_text,
|
||||
visual_bell,
|
||||
second_transparent_background,
|
||||
const Kind = enum(u9) {
|
||||
// These _must_ start at 256 since enum values 0-255 are reserved
|
||||
// for the palette.
|
||||
foreground = 256,
|
||||
background = 257,
|
||||
selection_foreground = 258,
|
||||
selection_background = 259,
|
||||
cursor = 260,
|
||||
cursor_text = 261,
|
||||
visual_bell = 262,
|
||||
second_transparent_background = 263,
|
||||
_,
|
||||
|
||||
// Make sure that this stays in sync with the higest numbered enum
|
||||
// value.
|
||||
const max: u9 = 263;
|
||||
};
|
||||
|
||||
const Request = union(enum) {
|
||||
query: Kind,
|
||||
set: struct {
|
||||
@ -1024,10 +1032,19 @@ pub const Parser = struct {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = std.meta.stringToEnum(Command.KittyColorProtocol.Kind, self.temp_state.key) orelse {
|
||||
const key = key: {
|
||||
break :key std.meta.stringToEnum(Command.KittyColorProtocol.Kind, self.temp_state.key) orelse {
|
||||
const v = std.fmt.parseUnsigned(u9, self.temp_state.key, 10) catch {
|
||||
log.warn("unknown key in kitty color protocol: {s}", .{self.temp_state.key});
|
||||
return;
|
||||
};
|
||||
if (v > 255) {
|
||||
log.warn("unknown key in kitty color protocol: {s}", .{self.temp_state.key});
|
||||
return;
|
||||
}
|
||||
break :key @as(Command.KittyColorProtocol.Kind, @enumFromInt(v));
|
||||
};
|
||||
};
|
||||
|
||||
const value = value: {
|
||||
if (self.buf_start == self.buf_idx) break :value "";
|
||||
@ -1037,6 +1054,11 @@ pub const Parser = struct {
|
||||
|
||||
switch (self.command) {
|
||||
.kitty_color_protocol => |*v| {
|
||||
if (v.list.items.len >= @as(usize, Command.KittyColorProtocol.Kind.max) * 2) {
|
||||
self.state = .invalid;
|
||||
log.warn("exceeded limit for number of keys in kitty color protocol, ignoring", .{});
|
||||
return;
|
||||
}
|
||||
if (kind == .key_only) {
|
||||
v.list.append(.{ .reset = key }) catch |err| {
|
||||
log.warn("unable to append kitty color protocol option: {}", .{err});
|
||||
@ -1651,12 +1673,12 @@ test "OSC: kitty color protocol" {
|
||||
var p: Parser = .{ .alloc = testing.allocator };
|
||||
defer p.deinit();
|
||||
|
||||
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc";
|
||||
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc;2=?;3=rgbi:1.0/1.0/1.0";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .kitty_color_protocol);
|
||||
try testing.expectEqual(@as(usize, 7), cmd.kitty_color_protocol.list.items.len);
|
||||
try testing.expectEqual(@as(usize, 9), cmd.kitty_color_protocol.list.items.len);
|
||||
try testing.expect(cmd.kitty_color_protocol.list.items[0] == .query);
|
||||
try testing.expectEqual(@as(Command.KittyColorProtocol.Kind, .foreground), cmd.kitty_color_protocol.list.items[0].query);
|
||||
try testing.expect(cmd.kitty_color_protocol.list.items[1] == .set);
|
||||
@ -1680,4 +1702,28 @@ test "OSC: kitty color protocol" {
|
||||
try testing.expectEqual(@as(u8, 0xaa), cmd.kitty_color_protocol.list.items[6].set.color.r);
|
||||
try testing.expectEqual(@as(u8, 0xbb), cmd.kitty_color_protocol.list.items[6].set.color.g);
|
||||
try testing.expectEqual(@as(u8, 0xcc), cmd.kitty_color_protocol.list.items[6].set.color.b);
|
||||
try testing.expect(cmd.kitty_color_protocol.list.items[7] == .query);
|
||||
try testing.expectEqual(@as(Command.KittyColorProtocol.Kind, @enumFromInt(2)), cmd.kitty_color_protocol.list.items[7].query);
|
||||
try testing.expect(cmd.kitty_color_protocol.list.items[8] == .set);
|
||||
try testing.expectEqual(@as(Command.KittyColorProtocol.Kind, @enumFromInt(3)), cmd.kitty_color_protocol.list.items[8].set.key);
|
||||
try testing.expectEqual(@as(u8, 0xff), cmd.kitty_color_protocol.list.items[8].set.color.r);
|
||||
try testing.expectEqual(@as(u8, 0xff), cmd.kitty_color_protocol.list.items[8].set.color.g);
|
||||
try testing.expectEqual(@as(u8, 0xff), cmd.kitty_color_protocol.list.items[8].set.color.b);
|
||||
}
|
||||
|
||||
test "OSC: kitty color protocol kind" {
|
||||
const info = @typeInfo(Command.KittyColorProtocol.Kind);
|
||||
|
||||
try std.testing.expectEqual(false, info.Enum.is_exhaustive);
|
||||
|
||||
var min: usize = std.math.maxInt(info.Enum.tag_type);
|
||||
var max: usize = 0;
|
||||
|
||||
inline for (info.Enum.fields) |field| {
|
||||
if (field.value > max) max = field.value;
|
||||
if (field.value < min) min = field.value;
|
||||
}
|
||||
|
||||
try std.testing.expect(min >= 256);
|
||||
try std.testing.expect(max == Command.KittyColorProtocol.Kind.max);
|
||||
}
|
||||
|
@ -1279,73 +1279,136 @@ pub const StreamHandler = struct {
|
||||
for (request.list.items) |item| {
|
||||
switch (item) {
|
||||
.query => |key| {
|
||||
const i = @intFromEnum(key);
|
||||
const color = switch (key) {
|
||||
.foreground => self.foreground_color,
|
||||
.background => self.background_color,
|
||||
.cursor => self.cursor_color,
|
||||
else => {
|
||||
else => color: {
|
||||
if (i > 255) {
|
||||
log.warn("ignoring unsupported kitty color protocol key: {s}", .{@tagName(key)});
|
||||
continue;
|
||||
}
|
||||
break :color self.terminal.color_palette.colors[i];
|
||||
},
|
||||
} orelse {
|
||||
log.warn("no color configured for: {s}", .{@tagName(key)});
|
||||
continue;
|
||||
};
|
||||
if (i <= 255)
|
||||
try writer.print(
|
||||
";rgb:{x:0>2}/{x:0>2}/{x:0>2}",
|
||||
.{
|
||||
@as(u16, color.r),
|
||||
@as(u16, color.g),
|
||||
@as(u16, color.b),
|
||||
},
|
||||
";{d}=rgb:{x:0>2}/{x:0>2}/{x:0>2}",
|
||||
.{ i, color.r, color.g, color.b },
|
||||
)
|
||||
else
|
||||
try writer.print(
|
||||
";{s}=rgb:{x:0>2}/{x:0>2}/{x:0>2}",
|
||||
.{ @tagName(key), color.r, color.g, color.b },
|
||||
);
|
||||
},
|
||||
.set => |v| switch (v.key) {
|
||||
.foreground => {
|
||||
self.foreground_color = v.color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
// See messageWriter which has similar logic and
|
||||
// explains why we may have to do this.
|
||||
const msg: renderer.Message = .{
|
||||
.foreground_color = v.color,
|
||||
}, .{ .forever = {} });
|
||||
};
|
||||
if (self.renderer_mailbox.push(msg, .{ .instant = {} }) == 0) {
|
||||
self.renderer_state.mutex.unlock();
|
||||
defer self.renderer_state.mutex.lock();
|
||||
_ = self.renderer_mailbox.push(msg, .{ .forever = {} });
|
||||
}
|
||||
},
|
||||
.background => {
|
||||
self.background_color = v.color;
|
||||
// See messageWriter which has similar logic and
|
||||
// explains why we may have to do this.
|
||||
const msg: renderer.Message = .{
|
||||
.background_color = v.color,
|
||||
};
|
||||
if (self.renderer_mailbox.push(msg, .{ .instant = {} }) == 0) {
|
||||
self.renderer_state.mutex.unlock();
|
||||
defer self.renderer_state.mutex.lock();
|
||||
_ = self.renderer_mailbox.push(msg, .{ .forever = {} });
|
||||
}
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.background_color = v.color,
|
||||
}, .{ .forever = {} });
|
||||
},
|
||||
.cursor => {
|
||||
self.cursor_color = v.color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
// See messageWriter which has similar logic and
|
||||
// explains why we may have to do this.
|
||||
const msg: renderer.Message = .{
|
||||
.cursor_color = v.color,
|
||||
}, .{ .forever = {} });
|
||||
};
|
||||
if (self.renderer_mailbox.push(msg, .{ .instant = {} }) == 0) {
|
||||
self.renderer_state.mutex.unlock();
|
||||
defer self.renderer_state.mutex.lock();
|
||||
_ = self.renderer_mailbox.push(msg, .{ .forever = {} });
|
||||
}
|
||||
},
|
||||
else => {
|
||||
const i = @intFromEnum(v.key);
|
||||
if (i > 255) {
|
||||
log.warn("ignoring unsupported kitty color protocol key: {s}", .{@tagName(v.key)});
|
||||
continue;
|
||||
}
|
||||
self.terminal.flags.dirty.palette = true;
|
||||
self.terminal.color_palette.colors[i] = v.color;
|
||||
self.terminal.color_palette.mask.unset(i);
|
||||
},
|
||||
},
|
||||
.reset => |key| switch (key) {
|
||||
.foreground => {
|
||||
self.foreground_color = self.default_foreground_color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.foreground_color = self.foreground_color,
|
||||
}, .{ .forever = {} });
|
||||
// See messageWriter which has similar logic and
|
||||
// explains why we may have to do this.
|
||||
const msg: renderer.Message = .{
|
||||
.foreground_color = self.default_foreground_color,
|
||||
};
|
||||
if (self.renderer_mailbox.push(msg, .{ .instant = {} }) == 0) {
|
||||
self.renderer_state.mutex.unlock();
|
||||
defer self.renderer_state.mutex.lock();
|
||||
_ = self.renderer_mailbox.push(msg, .{ .forever = {} });
|
||||
}
|
||||
},
|
||||
.background => {
|
||||
self.background_color = self.default_background_color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.background_color = self.background_color,
|
||||
}, .{ .forever = {} });
|
||||
// See messageWriter which has similar logic and
|
||||
// explains why we may have to do this.
|
||||
const msg: renderer.Message = .{
|
||||
.background_color = self.default_background_color,
|
||||
};
|
||||
if (self.renderer_mailbox.push(msg, .{ .instant = {} }) == 0) {
|
||||
self.renderer_state.mutex.unlock();
|
||||
defer self.renderer_state.mutex.lock();
|
||||
_ = self.renderer_mailbox.push(msg, .{ .forever = {} });
|
||||
}
|
||||
},
|
||||
.cursor => {
|
||||
self.cursor_color = self.default_cursor_color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.cursor_color = self.cursor_color,
|
||||
}, .{ .forever = {} });
|
||||
// See messageWriter which has similar logic and
|
||||
// explains why we may have to do this.
|
||||
const msg: renderer.Message = .{
|
||||
.cursor_color = self.default_cursor_color,
|
||||
};
|
||||
if (self.renderer_mailbox.push(msg, .{ .instant = {} }) == 0) {
|
||||
self.renderer_state.mutex.unlock();
|
||||
defer self.renderer_state.mutex.lock();
|
||||
_ = self.renderer_mailbox.push(msg, .{ .forever = {} });
|
||||
}
|
||||
},
|
||||
else => {
|
||||
const i = @intFromEnum(key);
|
||||
if (i > 255) {
|
||||
log.warn("ignoring unsupported kitty color protocol key: {s}", .{@tagName(key)});
|
||||
continue;
|
||||
}
|
||||
self.terminal.flags.dirty.palette = true;
|
||||
self.terminal.color_palette.colors[i] = self.terminal.default_palette[i];
|
||||
self.terminal.color_palette.mask.unset(i);
|
||||
},
|
||||
},
|
||||
}
|
||||
|
Reference in New Issue
Block a user