os: fix off-by-one error in ShellEscapeWriter (#9842)

I am truly not sure why the tests never caught this, but I just fell for
the oldest trick in the book

Fixes a crash on GTK when a file is dragged then dropped into the
terminal - should not impact 1.2 as this is a casualty of the Zig 0.15
port
pull/9572/head
Mitchell Hashimoto 2025-12-07 21:17:12 -08:00 committed by GitHub
commit 0bbd7c8f9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 9 additions and 10 deletions

View File

@ -69,6 +69,7 @@ test {
_ = i18n; _ = i18n;
_ = path; _ = path;
_ = uri; _ = uri;
_ = shell;
if (comptime builtin.os.tag == .linux) { if (comptime builtin.os.tag == .linux) {
_ = kernel_info; _ = kernel_info;

View File

@ -5,8 +5,6 @@ const Writer = std.Io.Writer;
/// Writer that escapes characters that shells treat specially to reduce the /// Writer that escapes characters that shells treat specially to reduce the
/// risk of injection attacks or other such weirdness. Specifically excludes /// risk of injection attacks or other such weirdness. Specifically excludes
/// linefeeds so that they can be used to delineate lists of file paths. /// linefeeds so that they can be used to delineate lists of file paths.
///
/// T should be a Zig type that follows the `std.Io.Writer` interface.
pub const ShellEscapeWriter = struct { pub const ShellEscapeWriter = struct {
writer: Writer, writer: Writer,
child: *Writer, child: *Writer,
@ -33,7 +31,7 @@ pub const ShellEscapeWriter = struct {
var count: usize = 0; var count: usize = 0;
for (data[0 .. data.len - 1]) |chunk| try self.writeEscaped(chunk, &count); for (data[0 .. data.len - 1]) |chunk| try self.writeEscaped(chunk, &count);
for (0..splat) |_| try self.writeEscaped(data[data.len], &count); for (0..splat) |_| try self.writeEscaped(data[data.len - 1], &count);
return count; return count;
} }
@ -67,7 +65,7 @@ pub const ShellEscapeWriter = struct {
test "shell escape 1" { test "shell escape 1" {
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer }; var shell: ShellEscapeWriter = .init(&writer);
try shell.writer.writeAll("abc"); try shell.writer.writeAll("abc");
try testing.expectEqualStrings("abc", writer.buffered()); try testing.expectEqualStrings("abc", writer.buffered());
} }
@ -75,7 +73,7 @@ test "shell escape 1" {
test "shell escape 2" { test "shell escape 2" {
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer }; var shell: ShellEscapeWriter = .init(&writer);
try shell.writer.writeAll("a c"); try shell.writer.writeAll("a c");
try testing.expectEqualStrings("a\\ c", writer.buffered()); try testing.expectEqualStrings("a\\ c", writer.buffered());
} }
@ -83,7 +81,7 @@ test "shell escape 2" {
test "shell escape 3" { test "shell escape 3" {
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer }; var shell: ShellEscapeWriter = .init(&writer);
try shell.writer.writeAll("a?c"); try shell.writer.writeAll("a?c");
try testing.expectEqualStrings("a\\?c", writer.buffered()); try testing.expectEqualStrings("a\\?c", writer.buffered());
} }
@ -91,7 +89,7 @@ test "shell escape 3" {
test "shell escape 4" { test "shell escape 4" {
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer }; var shell: ShellEscapeWriter = .init(&writer);
try shell.writer.writeAll("a\\c"); try shell.writer.writeAll("a\\c");
try testing.expectEqualStrings("a\\\\c", writer.buffered()); try testing.expectEqualStrings("a\\\\c", writer.buffered());
} }
@ -99,7 +97,7 @@ test "shell escape 4" {
test "shell escape 5" { test "shell escape 5" {
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer }; var shell: ShellEscapeWriter = .init(&writer);
try shell.writer.writeAll("a|c"); try shell.writer.writeAll("a|c");
try testing.expectEqualStrings("a\\|c", writer.buffered()); try testing.expectEqualStrings("a\\|c", writer.buffered());
} }
@ -107,7 +105,7 @@ test "shell escape 5" {
test "shell escape 6" { test "shell escape 6" {
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer }; var shell: ShellEscapeWriter = .init(&writer);
try shell.writer.writeAll("a\"c"); try shell.writer.writeAll("a\"c");
try testing.expectEqualStrings("a\\\"c", writer.buffered()); try testing.expectEqualStrings("a\\\"c", writer.buffered());
} }
@ -115,7 +113,7 @@ test "shell escape 6" {
test "shell escape 7" { test "shell escape 7" {
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer }; var shell: ShellEscapeWriter = .init(&writer);
try shell.writer.writeAll("a(1)"); try shell.writer.writeAll("a(1)");
try testing.expectEqualStrings("a\\(1\\)", writer.buffered()); try testing.expectEqualStrings("a\\(1\\)", writer.buffered());
} }