Fix drag and drop escaping (#18965)

This commit is contained in:
Tommaso Sciortino
2026-02-12 18:27:56 -08:00
committed by GitHub
parent 00f73b73bc
commit d82f66973f
8 changed files with 492 additions and 399 deletions

View File

@@ -6,7 +6,6 @@
import path from 'node:path';
import os from 'node:os';
import process from 'node:process';
import * as crypto from 'node:crypto';
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
@@ -14,15 +13,6 @@ import { fileURLToPath } from 'node:url';
export const GEMINI_DIR = '.gemini';
export const GOOGLE_ACCOUNTS_FILENAME = 'google_accounts.json';
/**
* Special characters that need to be escaped in file paths for shell compatibility.
* Note that windows doesn't escape tilda.
*/
export const SHELL_SPECIAL_CHARS =
process.platform === 'win32'
? /[ \t()[\]{};|*?$`'"#&<>!]/
: /[ \t()[\]{};|*?$`'"#&<>!~]/;
/**
* Returns the home directory.
* If GEMINI_CLI_HOME environment variable is set, it returns its value.
@@ -280,43 +270,43 @@ export function makeRelative(
}
/**
* Escapes special characters in a file path like macOS terminal does.
* Escapes: spaces, parentheses, brackets, braces, semicolons, ampersands, pipes,
* asterisks, question marks, dollar signs, backticks, quotes, hash, and other shell metacharacters.
* Escape paths for at-commands.
*
* - Windows: double quoted if they contain special chars, otherwise bare
* - POSIX: backslash-escaped
*/
export function escapePath(filePath: string): string {
let result = '';
for (let i = 0; i < filePath.length; i++) {
const char = filePath[i];
// Count consecutive backslashes before this character
let backslashCount = 0;
for (let j = i - 1; j >= 0 && filePath[j] === '\\'; j--) {
backslashCount++;
}
// Character is already escaped if there's an odd number of backslashes before it
const isAlreadyEscaped = backslashCount % 2 === 1;
// Only escape if not already escaped
if (!isAlreadyEscaped && SHELL_SPECIAL_CHARS.test(char)) {
result += '\\' + char;
} else {
result += char;
if (process.platform === 'win32') {
// Windows: Double quote if it contains space or special chars
if (/[\s()[\]{};|&^$!@%`'~]/.test(filePath)) {
return `"${filePath}"`;
}
return filePath;
} else {
// POSIX: Backslash escape
return filePath.replace(/([ \t()[\]{};|*?$`'"#&<>!~\\])/g, '\\$1');
}
return result;
}
/**
* Unescapes special characters in a file path.
* Removes backslash escaping from shell metacharacters.
* Unescapes paths for at-commands.
*
* - Windows: double quoted if they contain special chars, otherwise bare
* - POSIX: backslash-escaped
*/
export function unescapePath(filePath: string): string {
return filePath.replace(
new RegExp(`\\\\([${SHELL_SPECIAL_CHARS.source.slice(1, -1)}])`, 'g'),
'$1',
);
if (process.platform === 'win32') {
if (
filePath.length >= 2 &&
filePath.startsWith('"') &&
filePath.endsWith('"')
) {
return filePath.slice(1, -1);
}
return filePath;
} else {
return filePath.replace(/\\(.)/g, '$1');
}
}
/**
@@ -345,7 +335,7 @@ export function normalizePath(p: string): string {
* @returns True if childPath is a subpath of parentPath, false otherwise.
*/
export function isSubpath(parentPath: string, childPath: string): boolean {
const isWindows = os.platform() === 'win32';
const isWindows = process.platform === 'win32';
const pathModule = isWindows ? path.win32 : path;
// On Windows, path.relative is case-insensitive. On POSIX, it's case-sensitive.