ghostty/src/os/shell.zig
2025-03-26 19:52:36 +01:00

107 lines
3.6 KiB
Zig

const std = @import("std");
const testing = std.testing;
/// Writer that escapes characters that shells treat specially to reduce the
/// risk of injection attacks or other such weirdness. Specifically excludes
/// 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 fn ShellEscapeWriter(comptime T: type) type {
return struct {
child_writer: T,
fn write(self: *ShellEscapeWriter(T), data: []const u8) error{Error}!usize {
var count: usize = 0;
for (data) |byte| {
const buf = switch (byte) {
'\\',
'"',
'\'',
'$',
'`',
'*',
'?',
' ',
'|',
'(',
')',
=> &[_]u8{ '\\', byte },
else => &[_]u8{byte},
};
self.child_writer.writeAll(buf) catch return error.Error;
count += 1;
}
return count;
}
const Writer = std.io.Writer(*ShellEscapeWriter(T), error{Error}, write);
pub fn writer(self: *ShellEscapeWriter(T)) Writer {
return .{ .context = self };
}
};
}
test "shell escape 1" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("abc");
try testing.expectEqualStrings("abc", fmt.getWritten());
}
test "shell escape 2" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a c");
try testing.expectEqualStrings("a\\ c", fmt.getWritten());
}
test "shell escape 3" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a?c");
try testing.expectEqualStrings("a\\?c", fmt.getWritten());
}
test "shell escape 4" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a\\c");
try testing.expectEqualStrings("a\\\\c", fmt.getWritten());
}
test "shell escape 5" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a|c");
try testing.expectEqualStrings("a\\|c", fmt.getWritten());
}
test "shell escape 6" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a\"c");
try testing.expectEqualStrings("a\\\"c", fmt.getWritten());
}
test "shell escape 7" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a(1)");
try testing.expectEqualStrings("a\\(1\\)", fmt.getWritten());
}