From 22be2a835549a5328610d16e95eec3daed7eba77 Mon Sep 17 00:00:00 2001 From: MildlyMeticulous <302576729+MildlyMeticulous@users.noreply.github.com> Date: Fri, 24 Jul 2026 21:45:26 -0700 Subject: [PATCH] fix: decode keys that are quoted because they contain = encode() already quotes a key containing `=` via safe(), but decode() matched the key with `[^=]+`, so it split on the `=` inside the quotes. stringify -> parse silently corrupted both the key and the value: ini.parse(ini.stringify({ "a=b": "c" })) // { "\"a": "b\"=c" } Allow a fully-quoted string as the key alternative, falling back to the old `[^=]+` when the quote is unterminated so unquoted keys are unchanged. --- lib/ini.js | 6 ++++-- test/quoted-keys.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 test/quoted-keys.js 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() +})