mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
fix(env-file): collapse escaped backslashes in quoted values (#1773)
* fix(env-file): collapse escaped backslashes in quoted values unescapeQuotedValue only collapsed an escaped quote (\" -> "), but the closing-quote scanner (findClosingQuote/isEscapedQuote) already treats a quoted value as backslash-escaped: an odd-length backslash run escapes the following quote, so a \\ pair is consumed as a single escaped backslash when locating the closing quote. Because the unescaper left \\ untouched, the two stages disagreed and a value such as "a\\b" round-tripped to the doubled "a\\b" instead of "a\b". Collapse \\ to \ in unescapeQuotedValue so both stages share one escaping rule. Lone backslashes before ordinary characters are still preserved. * test(env-file): cover escaped backslash adjacent to closing quote Locks down the terminator interaction between findClosingQuote/isEscapedQuote and unescapeQuotedValue: a value written as "a\\" (two backslashes before the closing quote) parses to a single trailing backslash.
This commit is contained in:
@@ -149,6 +149,30 @@ BAZ=qux
|
||||
})
|
||||
})
|
||||
|
||||
it('collapses escaped backslashes inside quoted values', () => {
|
||||
// The value content is C:\\Users\\me — escaped backslashes that the
|
||||
// closing-quote scanner already treats as single backslashes, so the
|
||||
// unescaper must collapse them too.
|
||||
const result = parseEnvFile('FOO="C:\\\\Users\\\\me"')
|
||||
expect(result).toEqual({ FOO: 'C:\\Users\\me' })
|
||||
})
|
||||
|
||||
it('keeps lone backslashes in quoted values intact', () => {
|
||||
// A single backslash before an ordinary character is not an escape and
|
||||
// must survive verbatim (e.g. a Windows path written without doubling).
|
||||
const result = parseEnvFile('FOO="C:\\Users\\me"')
|
||||
expect(result).toEqual({ FOO: 'C:\\Users\\me' })
|
||||
})
|
||||
|
||||
it('collapses an escaped backslash adjacent to the closing quote', () => {
|
||||
// The value content is a\\ — the escaped backslash sits right before the
|
||||
// terminator, the trickiest interaction between findClosingQuote (which
|
||||
// counts the even backslash run and keeps scanning) and unescapeQuotedValue
|
||||
// (which must collapse the pair to one trailing backslash).
|
||||
const result = parseEnvFile('FOO="a\\\\"')
|
||||
expect(result).toEqual({ FOO: 'a\\' })
|
||||
})
|
||||
|
||||
it('strips inline comments from unquoted values', () => {
|
||||
const result = parseEnvFile('FOO=bar # comment\nBAZ=qux')
|
||||
expect(result).toEqual({ FOO: 'bar', BAZ: 'qux' })
|
||||
|
||||
@@ -165,13 +165,19 @@ function findClosingQuote(value: string, quote: string): number {
|
||||
|
||||
/**
|
||||
* Unescapes the active quote delimiter in a quoted env value.
|
||||
*
|
||||
* findClosingQuote/isEscapedQuote treat the value as backslash-escaped: an
|
||||
* odd-length backslash run escapes the following quote, so `\\` is already
|
||||
* consumed as a single escaped backslash when locating the closing quote.
|
||||
* Collapse `\\` to `\` here too, otherwise the two stages disagree and a
|
||||
* value like "a\\b" round-trips to the doubled "a\\b" instead of "a\b".
|
||||
*/
|
||||
function unescapeQuotedValue(raw: string, quote: string): string {
|
||||
let result = ''
|
||||
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
if (raw[i] === '\\' && raw[i + 1] === quote) {
|
||||
result += quote
|
||||
if (raw[i] === '\\' && (raw[i + 1] === quote || raw[i + 1] === '\\')) {
|
||||
result += raw[i + 1]
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user