Add terminal escape hatch panel
This commit is contained in:
+57
-11
@@ -21,6 +21,28 @@ pub const Location = struct {
|
||||
};
|
||||
|
||||
pub fn runRowsAlloc(allocator: std.mem.Allocator, io: std.Io, cwd: []const u8, argv: []const []const u8) ![][]const u8 {
|
||||
try validateArgv(argv);
|
||||
const command_preview = try commandPreviewAlloc(allocator, argv);
|
||||
defer allocator.free(command_preview);
|
||||
return runRowsWithPreviewAlloc(allocator, io, cwd, argv, "job", command_preview);
|
||||
}
|
||||
|
||||
pub fn shellRowsAlloc(allocator: std.mem.Allocator, io: std.Io, cwd: []const u8, command: []const u8) ![][]const u8 {
|
||||
try validateShellCommand(command);
|
||||
const argv = [_][]const u8{ "sh", "-c", command };
|
||||
const command_preview = try sanitizeLineAlloc(allocator, command);
|
||||
defer allocator.free(command_preview);
|
||||
return runRowsWithPreviewAlloc(allocator, io, cwd, &argv, "terminal", command_preview);
|
||||
}
|
||||
|
||||
fn runRowsWithPreviewAlloc(
|
||||
allocator: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
cwd: []const u8,
|
||||
argv: []const []const u8,
|
||||
kind: []const u8,
|
||||
command_preview: []const u8,
|
||||
) ![][]const u8 {
|
||||
try validateCwd(cwd);
|
||||
try validateArgv(argv);
|
||||
|
||||
@@ -30,9 +52,7 @@ pub fn runRowsAlloc(allocator: std.mem.Allocator, io: std.Io, cwd: []const u8, a
|
||||
rows.deinit(allocator);
|
||||
}
|
||||
|
||||
const command_preview = try commandPreviewAlloc(allocator, argv);
|
||||
defer allocator.free(command_preview);
|
||||
try rows.append(allocator, try std.fmt.allocPrint(allocator, "job:spawned:{s}", .{command_preview}));
|
||||
try rows.append(allocator, try std.fmt.allocPrint(allocator, "{s}:spawned:{s}", .{ kind, command_preview }));
|
||||
|
||||
const result = std.process.run(allocator, io, .{
|
||||
.argv = argv,
|
||||
@@ -40,16 +60,16 @@ pub fn runRowsAlloc(allocator: std.mem.Allocator, io: std.Io, cwd: []const u8, a
|
||||
.stdout_limit = .limited(256 * 1024),
|
||||
.stderr_limit = .limited(256 * 1024),
|
||||
}) catch |err| {
|
||||
try rows.append(allocator, try std.fmt.allocPrint(allocator, "job:status:spawn_error_{s}", .{@errorName(err)}));
|
||||
try rows.append(allocator, try std.fmt.allocPrint(allocator, "{s}:status:spawn_error_{s}", .{ kind, @errorName(err) }));
|
||||
return rows.toOwnedSlice(allocator);
|
||||
};
|
||||
defer allocator.free(result.stdout);
|
||||
defer allocator.free(result.stderr);
|
||||
|
||||
try appendTermRow(allocator, &rows, result.term);
|
||||
try appendTermRow(allocator, &rows, kind, result.term);
|
||||
try appendOutputRows(allocator, &rows, "stdout", result.stdout);
|
||||
try appendOutputRows(allocator, &rows, "stderr", result.stderr);
|
||||
if (rows.items.len == 2) try rows.append(allocator, try allocator.dupe(u8, "job:output_empty"));
|
||||
if (rows.items.len == 2) try rows.append(allocator, try std.fmt.allocPrint(allocator, "{s}:output_empty", .{kind}));
|
||||
return rows.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
@@ -108,6 +128,13 @@ fn validateArgv(argv: []const []const u8) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn validateShellCommand(command: []const u8) !void {
|
||||
if (command.len == 0 or !std.unicode.utf8ValidateSlice(command)) return Error.InvalidCommand;
|
||||
for (command) |byte| {
|
||||
if (byte == 0 or byte == '\n' or byte == '\r') return Error.InvalidCommand;
|
||||
}
|
||||
}
|
||||
|
||||
fn validatePath(path: []const u8) !void {
|
||||
if (path.len == 0 or !std.unicode.utf8ValidateSlice(path)) return Error.InvalidPath;
|
||||
if (std.mem.startsWith(u8, path, "/") or std.mem.indexOf(u8, path, "..") != null) return Error.InvalidPath;
|
||||
@@ -116,12 +143,12 @@ fn validatePath(path: []const u8) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn appendTermRow(allocator: std.mem.Allocator, rows: *std.ArrayList([]const u8), term: std.process.Child.Term) !void {
|
||||
fn appendTermRow(allocator: std.mem.Allocator, rows: *std.ArrayList([]const u8), kind: []const u8, term: std.process.Child.Term) !void {
|
||||
const row = switch (term) {
|
||||
.exited => |code| try std.fmt.allocPrint(allocator, "job:status:exit_{d}", .{code}),
|
||||
.signal => |sig| try std.fmt.allocPrint(allocator, "job:status:signal_{d}", .{@intFromEnum(sig)}),
|
||||
.stopped => |sig| try std.fmt.allocPrint(allocator, "job:status:stopped_{d}", .{@intFromEnum(sig)}),
|
||||
.unknown => |code| try std.fmt.allocPrint(allocator, "job:status:unknown_{d}", .{code}),
|
||||
.exited => |code| try std.fmt.allocPrint(allocator, "{s}:status:exit_{d}", .{ kind, code }),
|
||||
.signal => |sig| try std.fmt.allocPrint(allocator, "{s}:status:signal_{d}", .{ kind, @intFromEnum(sig) }),
|
||||
.stopped => |sig| try std.fmt.allocPrint(allocator, "{s}:status:stopped_{d}", .{ kind, @intFromEnum(sig) }),
|
||||
.unknown => |code| try std.fmt.allocPrint(allocator, "{s}:status:unknown_{d}", .{ kind, code }),
|
||||
};
|
||||
try rows.append(allocator, row);
|
||||
}
|
||||
@@ -196,3 +223,22 @@ test "adversarial: invalid commands rows and unparseable output are rejected" {
|
||||
try std.testing.expectError(Error.InvalidJobRow, locationFromRow("job:status:exit_1"));
|
||||
try std.testing.expectError(Error.InvalidPath, locationFromRow("stderr:../secret:1:1:nope"));
|
||||
}
|
||||
|
||||
test "regular: shell terminal rows capture command output and exit status" {
|
||||
const rows = try shellRowsAlloc(std.testing.allocator, std.testing.io, ".", "printf terminal_ok");
|
||||
defer freeOwnedRows(std.testing.allocator, rows);
|
||||
try std.testing.expect(rows.len >= 3);
|
||||
try std.testing.expectEqualStrings("terminal:spawned:printf_terminal_ok", rows[0]);
|
||||
try std.testing.expectEqualStrings("terminal:status:exit_0", rows[1]);
|
||||
try std.testing.expectEqualStrings("stdout:terminal_ok", rows[2]);
|
||||
}
|
||||
|
||||
test "adversarial: shell terminal rejects invalid command and reports shell failures" {
|
||||
try std.testing.expectError(Error.InvalidCommand, shellRowsAlloc(std.testing.allocator, std.testing.io, ".", ""));
|
||||
try std.testing.expectError(Error.InvalidCommand, shellRowsAlloc(std.testing.allocator, std.testing.io, ".", "echo bad\n"));
|
||||
|
||||
const rows = try shellRowsAlloc(std.testing.allocator, std.testing.io, ".", "definitely-not-a-mim-command");
|
||||
defer freeOwnedRows(std.testing.allocator, rows);
|
||||
try std.testing.expectEqualStrings("terminal:status:exit_127", rows[1]);
|
||||
try std.testing.expect(std.mem.startsWith(u8, rows[2], "stderr:"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user