diff --git a/lib/ini.js b/lib/ini.js index beb390d..fde472f 100644 --- a/lib/ini.js +++ b/lib/ini.js @@ -111,7 +111,8 @@ const decode = (str, opt = {}) => { // section |key = value const re = /^\[([^\]]*)\]\s*$|^([^=]+)(=(.*))?$/i const lines = str.split(/[\r\n]+/g) - const duplicates = {} + // 2026-10-11: Count only keys from the input, including prototype member names. + const duplicates = Object.create(null) for (const line of lines) { if (!line || line.match(/^\s*[;#]/) || line.match(/^\s*$/)) { diff --git a/test/duplicate-properties.js b/test/duplicate-properties.js index 6f47c91..b47f9a1 100644 --- a/test/duplicate-properties.js +++ b/test/duplicate-properties.js @@ -38,3 +38,14 @@ test('encode duplicate properties with bracketedArray=false', function (t) { t.matchSnapshot(e) t.end() }) + +// 2026-10-11: Prototype member names are ordinary duplicate keys in INI data. +test('decode duplicate keys named after prototype properties', function (t) { + for (const key of ['constructor', 'toString', 'valueOf', 'hasOwnProperty']) { + const input = `${key}=first\n${key}=second\n${key}=third` + t.same(i.decode(input, { bracketedArray: false })[key], ['first', 'second', 'third'], key) + t.same(i.decode(`${key}=single`, { bracketedArray: false })[key], 'single', `${key} stays scalar`) + t.same(i.decode(input)[key], 'third', `${key} follows default last-value behavior`) + } + t.end() +})