diff --git a/lib/ini.js b/lib/ini.js index beb390d..d2ec28c 100644 --- a/lib/ini.js +++ b/lib/ini.js @@ -108,8 +108,10 @@ const decode = (str, opt = {}) => { const out = Object.create(null) let p = out let section = null - // section |key = value - const re = /^\[([^\]]*)\]\s*$|^([^=]+)(=(.*))?$/i + // A key may contain `=` as long as it is quoted, which is what encode() + // emits for such keys via safe(). + // section |key = value + const re = /^\[([^\]]*)\]\s*$|^("(?:\\.|[^"\\])*"\s*|[^=]+)(=(.*))?$/i const lines = str.split(/[\r\n]+/g) const duplicates = {} diff --git a/test/quoted-keys.js b/test/quoted-keys.js new file mode 100644 index 0000000..f05d714 --- /dev/null +++ b/test/quoted-keys.js @@ -0,0 +1,34 @@ +const i = require('../') +const test = require('tap').test + +test('decode a quoted key containing =', function (t) { + t.same(i.decode('"a=b"=c\n'), { 'a=b': 'c' }) + t.same(i.decode('"key=with=eq"=v\n'), { 'key=with=eq': 'v' }) + t.same(i.decode('"=lead"=v\n'), { '=lead': 'v' }) + t.same(i.decode('"tr="=v\n'), { 'tr=': 'v' }) + t.end() +}) + +test('encode then decode round-trips keys containing =', function (t) { + for (const obj of [ + { 'a=b': 'c' }, + { 'key=with=eq': 'v' }, + { '=lead': 'v' }, + { 'tr=': 'v' }, + { a: { 'b=c': 'd' } }, + ]) { + t.same(i.decode(i.encode(obj)), obj) + } + t.end() +}) + +test('an unquoted key still splits on the first =', function (t) { + t.same(i.decode('a=b=c\n'), { a: 'b=c' }) + t.same(i.decode('a="x=y"\n'), { a: 'x=y' }) + t.end() +}) + +test('a key with an unterminated quote is not treated as quoted', function (t) { + t.same(i.decode('"a=b\n'), { '"a': 'b' }) + t.end() +})