diff --git a/lib/utils.js b/lib/utils.js index 4f21e7ef1e3..82b2eee568c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -96,7 +96,9 @@ function acceptParams (str) { var splitIndex = str.indexOf('=', index); if (splitIndex === -1) break; - var colonIndex = str.indexOf(';', index); + // a parameter value may be a quoted-string, which can itself contain + // ";" - so the terminating ";" must not be one inside quotes + var colonIndex = indexOfUnquoted(str, ';', index); var endIndex = colonIndex === -1 ? length : colonIndex; if (splitIndex > endIndex) { @@ -119,6 +121,35 @@ function acceptParams (str) { return ret; } +/** + * Find the index of the first unescaped, unquoted occurrence of `char` + * in `str` at or after `fromIndex`, treating a double-quoted substring + * (a quoted-string parameter value) as opaque so that `char` occurring + * inside it is ignored. Returns -1 if no such occurrence is found. + * + * @param {String} str + * @param {String} char + * @param {Number} fromIndex + * @return {Number} + * @api private + */ + +function indexOfUnquoted(str, char, fromIndex) { + var quoted = false; + + for (var i = fromIndex; i < str.length; i++) { + var c = str[i]; + + if (c === '"' && str[i - 1] !== '\\') { + quoted = !quoted; + } else if (c === char && !quoted) { + return i; + } + } + + return -1; +} + /** * Compile "etag" value to function. * diff --git a/test/utils.js b/test/utils.js index d1174d014cf..b7856121886 100644 --- a/test/utils.js +++ b/test/utils.js @@ -43,6 +43,24 @@ describe('utils.normalizeType acceptParams method', () => { params: {} }); }); + + it('should not split a quoted parameter value on an embedded ";"', () => { + const result = utils.normalizeType('text/plain; foo="a;b"; bar=baz'); + assert.deepEqual(result, { + value: 'text/plain', + quality: 1, + params: { foo: '"a;b"', bar: 'baz' } + }); + }); + + it('should not treat an escaped quote inside a parameter value as closing it', () => { + const result = utils.normalizeType('text/plain; foo="a\\"b;c"; bar=baz'); + assert.deepEqual(result, { + value: 'text/plain', + quality: 1, + params: { foo: '"a\\"b;c"', bar: 'baz' } + }); + }); }); describe('utils.setCharset(type, charset)', function () {