66 lines
1.6 KiB
TypeScript
66 lines
1.6 KiB
TypeScript
const QUOTES = new Set(['"', "'"])
|
|
|
|
type ExtractExecutableTokenOptions = {
|
|
platform?: NodeJS.Platform
|
|
}
|
|
|
|
export function extractExecutableToken(
|
|
command: string | null | undefined,
|
|
options: ExtractExecutableTokenOptions = {}
|
|
): string | null {
|
|
const input = command?.trim()
|
|
if (!input) {
|
|
return null
|
|
}
|
|
const backslashEscapes = (options.platform ?? process.platform) !== 'win32'
|
|
let index = 0
|
|
let quote: string | null = null
|
|
let token = ''
|
|
while (index < input.length) {
|
|
const char = input[index]
|
|
if (quote) {
|
|
if (char === quote) {
|
|
quote = null
|
|
} else if (char === '\\' && quote === '"' && backslashEscapes && index + 1 < input.length) {
|
|
index += 1
|
|
token += input[index]
|
|
} else {
|
|
token += char
|
|
}
|
|
} else if (QUOTES.has(char)) {
|
|
quote = char
|
|
} else if (/\s/.test(char)) {
|
|
break
|
|
} else if (char === '\\' && backslashEscapes && index + 1 < input.length) {
|
|
index += 1
|
|
token += input[index]
|
|
} else {
|
|
token += char
|
|
}
|
|
index += 1
|
|
}
|
|
return token.length > 0 ? token : null
|
|
}
|
|
|
|
export function hasPathSeparatorToken(token: string): boolean {
|
|
return token.includes('/') || token.includes('\\')
|
|
}
|
|
|
|
export function isSafeExecutableBasename(token: string): boolean {
|
|
return /^[A-Za-z0-9._+-]+$/.test(token)
|
|
}
|
|
|
|
export function isSafeOverrideExecutableToken(token: string): boolean {
|
|
if (token.includes('\0')) {
|
|
return false
|
|
}
|
|
if (!hasPathSeparatorToken(token)) {
|
|
return isSafeExecutableBasename(token)
|
|
}
|
|
return (
|
|
!token.includes('..') &&
|
|
!/[|&;<>(){}[\]$`"'*!?]/.test(token) &&
|
|
/^[A-Za-z0-9._+\-/:\\~ ]+$/.test(token)
|
|
)
|
|
}
|