diff --git a/lib/main.js b/lib/main.js index 1d914575..e1c9b16e 100644 --- a/lib/main.js +++ b/lib/main.js @@ -22,28 +22,41 @@ function parse (src) { const key = match[1] // Default undefined or null to empty string - let value = (match[2] || '') + const unProcessedValue = (match[2] || '') + const value = parseValue(unProcessedValue) + // Add to object + obj[key] = value + } - // Remove whitespace - value = value.trim() + return obj +} - // Check if double quoted - const maybeQuote = value[0] +function parseValue (value) { + // Remove whitespace + const trimmedValue = value.trim() + // Check if double quoted + const maybeQuote = trimmedValue[0] + // Remove surrounding quotes + const valueNoQuotes = trimmedValue.replace(/^(['"`])([\s\S]*)\1$/mg, '$2') - // Remove surrounding quotes - value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2') + return unescapeValue(valueNoQuotes, maybeQuote) +} - // Expand newlines if double quoted - if (maybeQuote === '"') { - value = value.replace(/\\n/g, '\n') - value = value.replace(/\\r/g, '\r') - } +function unescapeCharacters (value) { + return value.replace(/\\([^$])/g, '$1') +} - // Add to object - obj[key] = value - } +function expandNewLines (value) { + return value.replace(/\\n/g, '\n').replace(/\\r/g, '\r') +} - return obj +function unescapeValue (value, maybeQuote) { + // Expand newlines if double quoted + if (maybeQuote === '"') { + return unescapeCharacters(expandNewLines(value)) + } else { + return unescapeCharacters(value) + } } function _log (message) { diff --git a/tests/.env-escaping b/tests/.env-escaping new file mode 100644 index 00000000..2af435ad --- /dev/null +++ b/tests/.env-escaping @@ -0,0 +1,7 @@ +ESCAPE_CHAR=ab\cde +ESCAPE_NEWLINE_NO_QUOTE=line1\nline2 +ESCAPE_NEWLINE_IN_DOUBLE_QUOTE="line1\nline2" +ESCAPE_QUOTES=single quote: \', double quote: \", backtick: \` +ESCAPE_TAB=\t +ESCAPE_QUOTES_IN_QUOTES="John said: \"Lets go\"" +ESCAPE_DOLLAR=\$\$\$ diff --git a/tests/test-parse-escaping.js b/tests/test-parse-escaping.js new file mode 100644 index 00000000..3ff92dcb --- /dev/null +++ b/tests/test-parse-escaping.js @@ -0,0 +1,22 @@ +const fs = require('fs') +const t = require('tap') + +const dotenv = require('../lib/main') + +const parsed = dotenv.parse(fs.readFileSync('tests/.env-escaping', { encoding: 'utf8' })) + +t.type(parsed, Object, 'should return an object') + +t.equal(parsed.ESCAPE_CHAR, 'abcde', 'a "regular" escaped character should not be modified by un-escaping') + +t.equal(parsed.ESCAPE_QUOTES, 'single quote: \', double quote: ", backtick: `', 'quote characters can be escaped just like any other "regular" character') + +t.equal(parsed.ESCAPE_NEWLINE_NO_QUOTE, 'line1nline2', 'without double quotes `\\n` is un-escaped to `n`') + +t.equal(parsed.ESCAPE_NEWLINE_IN_DOUBLE_QUOTE, 'line1\nline2', 'with double quotes `\\n` is expanded to `\n`') + +t.equal(parsed.ESCAPE_TAB, 't', '\\t has no special meaning (expansion) in this library') + +t.equal(parsed.ESCAPE_QUOTES_IN_QUOTES, 'John said: "Lets go"', 'quotes can be escaped inside quoted dotenv value') + +t.equal(parsed.ESCAPE_DOLLAR, '\\$\\$\\$', 'dollar sign is never escaped') diff --git a/tests/test-parse-multiline.js b/tests/test-parse-multiline.js index 67e18c40..68c1be90 100644 --- a/tests/test-parse-multiline.js +++ b/tests/test-parse-multiline.js @@ -23,9 +23,9 @@ t.equal(parsed.DOUBLE_QUOTES_SPACED, ' double quotes ', 'respects surround t.equal(parsed.EXPAND_NEWLINES, 'expand\nnew\nlines', 'expands newlines but only if double quoted') -t.equal(parsed.DONT_EXPAND_UNQUOTED, 'dontexpand\\nnewlines', 'expands newlines but only if double quoted') +t.equal(parsed.DONT_EXPAND_UNQUOTED, 'dontexpandnnewlines', 'expands newlines but only if double quoted') -t.equal(parsed.DONT_EXPAND_SQUOTED, 'dontexpand\\nnewlines', 'expands newlines but only if double quoted') +t.equal(parsed.DONT_EXPAND_SQUOTED, 'dontexpandnnewlines', 'expands newlines but only if double quoted') t.notOk(parsed.COMMENTS, 'ignores commented lines') diff --git a/tests/test-parse.js b/tests/test-parse.js index 50e95b2c..5504e334 100644 --- a/tests/test-parse.js +++ b/tests/test-parse.js @@ -49,9 +49,9 @@ t.equal(parsed.DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS, "double \"quotes\" and t.equal(parsed.EXPAND_NEWLINES, 'expand\nnew\nlines', 'expands newlines but only if double quoted') -t.equal(parsed.DONT_EXPAND_UNQUOTED, 'dontexpand\\nnewlines', 'expands newlines but only if double quoted') +t.equal(parsed.DONT_EXPAND_UNQUOTED, 'dontexpandnnewlines', 'expands newlines but only if double quoted') -t.equal(parsed.DONT_EXPAND_SQUOTED, 'dontexpand\\nnewlines', 'expands newlines but only if double quoted') +t.equal(parsed.DONT_EXPAND_SQUOTED, 'dontexpandnnewlines', 'expands newlines but only if double quoted') t.notOk(parsed.COMMENTS, 'ignores commented lines')