diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx index 4ad11dd99..ad82f8c95 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx @@ -238,6 +238,12 @@ export default function SourceControl(): React.JSX.Element {
{ + const absolutePath = joinPath(worktreePath, entry.path) + e.dataTransfer.setData('text/x-orca-file-path', absolutePath) + e.dataTransfer.effectAllowed = 'copy' + }} onClick={() => handleOpenDiff(entry)} > { + it('keeps safe POSIX paths unquoted', () => { + expect(shellEscapePath('/tmp/file.txt', 'Macintosh')).toBe('/tmp/file.txt') + }) + + it('single-quotes POSIX paths with shell-special characters', () => { + expect(shellEscapePath("/tmp/it's here.txt", 'Linux')).toBe("'/tmp/it'\\''s here.txt'") + }) + + it('keeps safe Windows paths unquoted', () => { + expect(shellEscapePath('C:\\Users\\orca\\file.txt', 'Windows')).toBe( + 'C:\\Users\\orca\\file.txt' + ) + }) + + it('double-quotes Windows paths with spaces', () => { + expect(shellEscapePath('C:\\Users\\orca\\my file.txt', 'Windows')).toBe( + '"C:\\Users\\orca\\my file.txt"' + ) + }) + + it('double-quotes Windows paths with cmd separators', () => { + expect(shellEscapePath('C:\\Users\\orca\\a&b.txt', 'Windows')).toBe( + '"C:\\Users\\orca\\a&b.txt"' + ) + }) +}) diff --git a/src/renderer/src/components/terminal-pane/pane-helpers.ts b/src/renderer/src/components/terminal-pane/pane-helpers.ts index 614e6e3e8..5a78c18e4 100644 --- a/src/renderer/src/components/terminal-pane/pane-helpers.ts +++ b/src/renderer/src/components/terminal-pane/pane-helpers.ts @@ -21,9 +21,21 @@ export function fitAndFocusPanes(manager: PaneManager): void { focusActivePane(manager) } -export function shellEscapePath(path: string): string { +function isWindowsUserAgent(userAgent: string): boolean { + return userAgent.includes('Windows') +} + +export function shellEscapePath( + path: string, + userAgent: string = typeof navigator === 'undefined' ? '' : navigator.userAgent +): string { + if (isWindowsUserAgent(userAgent)) { + return /^[a-zA-Z0-9_./@:\\-]+$/.test(path) ? path : `"${path}"` + } + if (/^[a-zA-Z0-9_./@:-]+$/.test(path)) { return path } + return `'${path.replace(/'/g, "'\\''")}'` }