Skip to content

Commit 55855ab

Browse files
authored
Merge pull request #1073 from xiehuanyi/fix/unicode-unquoting-parity
Keep fast-parser unquoting consistent at Unicode line boundaries
2 parents 2953207 + fde5db2 commit 55855ab

2 files changed

Lines changed: 30 additions & 3 deletions

File tree

‎lib/main.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,13 @@ function parseFast (src) {
214214
while (start < end && isWhitespace(str.charCodeAt(start))) start++
215215
while (end > start && isWhitespace(str.charCodeAt(end - 1))) end--
216216
const first = str.charCodeAt(start)
217-
if (end - start >= 2 && (first === 39 || first === 34 || first === 96) && str.charCodeAt(end - 1) === first) {
217+
value = str.slice(start, end)
218+
if (value.includes('\u2028') || value.includes('\u2029')) {
219+
// The classic parser's multiline anchors also unquote segments at
220+
// Unicode line boundaries inside an otherwise unquoted value.
221+
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2')
222+
} else if (end - start >= 2 && (first === 39 || first === 34 || first === 96) && str.charCodeAt(end - 1) === first) {
218223
value = str.slice(start + 1, end - 1)
219-
} else {
220-
value = str.slice(start, end)
221224
}
222225
i = hash
223226
// Unquoted values include Unicode separators, but comments do not.

‎tests/test-parse-fast.js‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,27 @@ t.test('config({ fast: true }) loads with fast parser', ct => {
274274
ct.equal(result.parsed.BASIC, 'basic')
275275
ct.end()
276276
})
277+
278+
t.test('fast parse matches classic unquoting at Unicode line boundaries', ct => {
279+
for (const separator of ['\u2028', '\u2029']) {
280+
for (const quote of ['"', "'", '`']) {
281+
const cases = [
282+
{ value: `prefix${separator}${quote}${quote}`, expected: `prefix${separator}` },
283+
{ value: `prefix${separator}${quote}suffix${quote}`, expected: `prefix${separator}suffix` },
284+
{ value: `prefix${separator}${quote}suffix${quote}${separator}tail`, expected: `prefix${separator}suffix${separator}tail` },
285+
{ value: `${quote}prefix${separator}suffix${quote}`, expected: `prefix${separator}suffix` },
286+
{ value: `prefix${separator}${quote}unterminated`, expected: `prefix${separator}${quote}unterminated` }
287+
]
288+
for (const { value, expected } of cases) {
289+
for (const buffer of [false, true]) {
290+
const input = `BEFORE=one\nA=${value}\nAFTER=two\n`
291+
const src = buffer ? Buffer.from(input) : input
292+
const result = { BEFORE: 'one', A: expected, AFTER: 'two' }
293+
ct.same(dotenv.parse(src), result, 'classic preserves its established unquoting rules')
294+
ct.same(dotenv.parse(src, { fast: true }), result, 'fast matches classic at Unicode boundaries')
295+
}
296+
}
297+
}
298+
}
299+
ct.end()
300+
})

0 commit comments

Comments
 (0)