macos: add option to prompt user for confirmation on OSC 52 commands

This commit is contained in:
Gregory Anders
2023-11-09 20:45:50 -06:00
parent ace5693957
commit 86245ff0cf
16 changed files with 362 additions and 188 deletions

View File

@@ -140,8 +140,8 @@ const DerivedConfig = struct {
/// For docs for these, see the associated config they are derived from.
original_font_size: u8,
keybind: configpkg.Keybinds,
clipboard_read: bool,
clipboard_write: bool,
clipboard_read: configpkg.Config.ClipboardRequest,
clipboard_write: configpkg.Config.ClipboardRequest,
clipboard_trim_trailing_spaces: bool,
clipboard_paste_protection: bool,
clipboard_paste_bracketed_safe: bool,
@@ -689,8 +689,8 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
.cell_size => |size| try self.setCellSize(size),
.clipboard_read => |kind| {
if (!self.config.clipboard_read) {
log.info("application attempted to read clipboard, but 'clipboard-read' setting is off", .{});
if (self.config.clipboard_read == .deny) {
log.info("application attempted to read clipboard, but 'clipboard-read' is set to deny", .{});
return;
}
@@ -822,8 +822,8 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
}
fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard) !void {
if (!self.config.clipboard_write) {
log.info("application attempted to write clipboard, but 'clipboard-write' setting is off", .{});
if (self.config.clipboard_write == .deny) {
log.info("application attempted to write clipboard, but 'clipboard-write' is set to deny", .{});
return;
}
@@ -856,7 +856,8 @@ fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard)
};
assert(buf[buf.len] == 0);
self.rt_surface.setClipboardString(buf, loc) catch |err| {
const confirm = self.config.clipboard_write == .ask;
self.rt_surface.setClipboardString(buf, loc, confirm) catch |err| {
log.err("error setting clipboard string err={}", .{err});
return;
};
@@ -901,7 +902,7 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) void {
};
defer self.alloc.free(buf);
self.rt_surface.setClipboardString(buf, clipboard) catch |err| {
self.rt_surface.setClipboardString(buf, clipboard, false) catch |err| {
log.err("error setting clipboard string err={}", .{err});
return;
};
@@ -2279,7 +2280,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
};
defer self.alloc.free(buf);
self.rt_surface.setClipboardString(buf, .standard) catch |err| {
self.rt_surface.setClipboardString(buf, .standard, false) catch |err| {
log.err("error setting clipboard string err={}", .{err});
return true;
};
@@ -2509,19 +2510,25 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
/// only be called once for each request. The data is immediately copied so
/// it is safe to free the data after this call.
///
/// If "allow_unsafe" is false, then the data is checked for "safety" prior.
/// If unsafe data is detected, this will return error.UnsafePaste. Unsafe
/// data is defined as data that contains newlines, though this definition
/// may change later to detect other scenarios.
/// If `confirmed` is true then any clipboard confirmation prompts are skipped:
///
/// - For "regular" pasting this means that unsafe pastes are allowed. Unsafe
/// data is defined as data that contains newlines, though this definition
/// may change later to detect other scenarios.
///
/// - For OSC 52 pastes no prompt is shown to the user if `confirmed` is true.
///
/// If `confirmed` is false and either unsafe data is detected or the
/// `clipboard-read` option is set to `ask`, this will return error.UnsafePaste.
pub fn completeClipboardRequest(
self: *Surface,
req: apprt.ClipboardRequest,
data: []const u8,
allow_unsafe: bool,
confirmed: bool,
) !void {
switch (req) {
.paste => try self.completeClipboardPaste(data, allow_unsafe),
.osc_52 => |kind| try self.completeClipboardReadOSC52(data, kind),
.paste => try self.completeClipboardPaste(data, confirmed),
.osc_52 => |kind| try self.completeClipboardReadOSC52(data, kind, confirmed),
}
}
@@ -2534,9 +2541,9 @@ fn startClipboardRequest(
) !void {
switch (req) {
.paste => {}, // always allowed
.osc_52 => if (!self.config.clipboard_read) {
.osc_52 => if (self.config.clipboard_read == .deny) {
log.info(
"application attempted to read clipboard, but 'clipboard-read' setting is off",
"application attempted to read clipboard, but 'clipboard-read' is set to deny",
.{},
);
return;
@@ -2635,7 +2642,16 @@ fn completeClipboardPaste(
try self.io_thread.wakeup.notify();
}
fn completeClipboardReadOSC52(self: *Surface, data: []const u8, kind: u8) !void {
fn completeClipboardReadOSC52(self: *Surface, data: []const u8, kind: u8, confirmed: bool) !void {
// We should never get here if clipboard-read is set to deny
assert(self.config.clipboard_read != .deny);
// If clipboard-read is set to ask and we haven't confirmed with the user,
// do that now
if (self.config.clipboard_read == .ask and !confirmed) {
return error.UnauthorizedPaste;
}
// Even if the clipboard data is empty we reply, since presumably
// the client app is expecting a reply. We first allocate our buffer.
// This must hold the base64 encoded data PLUS the OSC code surrounding it.

View File

@@ -17,6 +17,7 @@ const CoreInspector = @import("../inspector/main.zig").Inspector;
const CoreSurface = @import("../Surface.zig");
const configpkg = @import("../config.zig");
const Config = configpkg.Config;
const ClipboardPromptReason = @import("../apprt/structs.zig").ClipboardPromptReason;
const log = std.log.scoped(.embedded_window);
@@ -68,10 +69,11 @@ pub const App = struct {
SurfaceUD,
[*:0]const u8,
*apprt.ClipboardRequest,
ClipboardPromptReason,
) callconv(.C) void,
/// Write the clipboard value.
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int) callconv(.C) void,
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int, bool) callconv(.C) void,
/// Create a new split view. If the embedder doesn't support split
/// views then this can be null.
@@ -506,7 +508,7 @@ pub const Surface = struct {
) void {
const alloc = self.app.core_app.alloc;
// Attempt to complete the request, but if its unsafe we may request
// Attempt to complete the request, but we may request
// confirmation.
self.core_surface.completeClipboardRequest(
state.*,
@@ -518,6 +520,17 @@ pub const Surface = struct {
self.opts.userdata,
str.ptr,
state,
.unsafe,
);
return;
},
error.UnauthorizedPaste => {
self.app.opts.confirm_read_clipboard(
self.opts.userdata,
str.ptr,
state,
.read,
);
return;
@@ -526,8 +539,8 @@ pub const Surface = struct {
else => log.err("error completing clipboard request err={}", .{err}),
};
// We don't defer this because the unsafe paste route preserves
// the clipboard request.
// We don't defer this because the clipboard confirmation route
// preserves the clipboard request.
alloc.destroy(state);
}
@@ -535,11 +548,13 @@ pub const Surface = struct {
self: *const Surface,
val: [:0]const u8,
clipboard_type: apprt.Clipboard,
confirm: bool,
) !void {
self.app.opts.write_clipboard(
self.opts.userdata,
val.ptr,
@intCast(@intFromEnum(clipboard_type)),
confirm,
);
}

View File

@@ -678,7 +678,9 @@ pub const Surface = struct {
self: *const Surface,
val: [:0]const u8,
clipboard_type: apprt.Clipboard,
confirm: bool,
) !void {
_ = confirm;
_ = self;
switch (clipboard_type) {
.standard => glfw.setClipboardString(val),

View File

@@ -515,7 +515,10 @@ pub fn setClipboardString(
self: *const Surface,
val: [:0]const u8,
clipboard_type: apprt.Clipboard,
confirm: bool,
) !void {
// TODO: implement confirmation dialog when clipboard-write is "ask"
_ = confirm;
const clipboard = getClipboard(@ptrCast(self.gl_area), clipboard_type);
c.gdk_clipboard_set_text(clipboard, val.ptr);
}

View File

@@ -41,3 +41,18 @@ pub const ClipboardRequest = union(enum) {
/// A request to write clipboard contents via OSC 52.
osc_52: u8,
};
/// The reason for displaying a clipboard prompt to the user
pub const ClipboardPromptReason = enum(i32) {
/// For pasting data only. Pasted data contains potentially unsafe
/// characters
unsafe = 1,
/// The user must authorize the application to read from the clipboard
read = 2,
/// The user must authorize the application to write to the clipboard
write = 3,
_,
};

View File

@@ -456,9 +456,10 @@ keybind: Keybinds = .{},
/// Whether to allow programs running in the terminal to read/write to
/// the system clipboard (OSC 52, for googling). The default is to
/// disallow clipboard reading but allow writing.
@"clipboard-read": bool = false,
@"clipboard-write": bool = true,
/// allow clipboard reading after prompting the user and allow writing
/// unconditionally.
@"clipboard-read": ClipboardRequest = .ask,
@"clipboard-write": ClipboardRequest = .allow,
/// Trims trailing whitespace on data that is copied to the clipboard.
/// This does not affect data sent to the clipboard via "clipboard-write".
@@ -467,8 +468,6 @@ keybind: Keybinds = .{},
/// Require confirmation before pasting text that appears unsafe. This helps
/// prevent a "copy/paste attack" where a user may accidentally execute unsafe
/// commands by pasting text with newlines.
///
/// This currently only works on Linux (GTK).
@"clipboard-paste-protection": bool = true,
/// If true, bracketed pastes will be considered safe. By default,
@@ -2278,7 +2277,7 @@ pub const ShellIntegrationFeatures = packed struct {
cursor: bool = true,
};
/// OSC 10 and 11 default color reporting format.
/// OSC 4, 10, 11, and 12 default color reporting format.
pub const OSCColorReportFormat = enum {
none,
@"8-bit",
@@ -2306,3 +2305,10 @@ pub const MouseShiftCapture = enum {
always,
never,
};
/// How to treat requests to write to or read from the clipboard
pub const ClipboardRequest = enum {
allow,
deny,
ask,
};