From b389e7c0255dbbe31e8f44dfa45b81a3f5779cf3 Mon Sep 17 00:00:00 2001 From: Talha Shafeeq Date: Sun, 11 Oct 2026 18:51:31 +0500 Subject: [PATCH] fix: normalize vary header handling to prevent duplicate or omitted accept-encoding Signed-off-by: Talha Shafeeq --- index.js | 29 +++++++++++++++------ test/global-compress.test.js | 49 ++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 53c5a4f..783589d 100644 --- a/index.js +++ b/index.js @@ -570,15 +570,30 @@ function compress (params) { } function setVaryHeader (reply) { - if (reply.hasHeader('Vary')) { - const rawHeaderValue = reply.getHeader('Vary') - const headerValueArray = Array.isArray(rawHeaderValue) ? rawHeaderValue : [rawHeaderValue] - if (!headerValueArray.some((h) => h.includes('accept-encoding'))) { - reply.header('Vary', headerValueArray.concat('accept-encoding').join(', ')) + const current = reply.getHeader('vary') + + if (current === undefined) { + reply.header('vary', 'accept-encoding') + return + } + + const value = Array.isArray(current) ? current.join(', ') : String(current) + + // Walk the comma-separated tokens without splitting the string. + const headerLength = value.length + let start = 0 + for (let i = 0; i <= headerLength; i++) { + if (i === headerLength || value[i] === ',') { + const token = value.slice(start, i).trim().toLowerCase() + // A `Vary: *` already covers every request header. + if (token === 'accept-encoding' || token === '*') { + return + } + start = i + 1 } - } else { - reply.header('Vary', 'accept-encoding') } + + reply.header('vary', value + ', accept-encoding') } function onEnd (err) { diff --git a/test/global-compress.test.js b/test/global-compress.test.js index 6b42877..24e89b7 100644 --- a/test/global-compress.test.js +++ b/test/global-compress.test.js @@ -3167,6 +3167,55 @@ test('It should not add `accept-encoding` to `Vary` reply header if already pres }) }) +test('It should handle case-insensitivity, substring safety, and wildcard in Vary header', async (t) => { + t.plan(3) + + const fastify = Fastify() + await fastify.register(compressPlugin, { global: true }) + + fastify.get('/case', (_request, reply) => { + reply + .header('vary', 'Accept-Encoding') + .type('text/plain') + .compress(createReadStream('./package.json')) + }) + + fastify.get('/substring', (_request, reply) => { + reply + .header('vary', 'x-accept-encoding') + .type('text/plain') + .compress(createReadStream('./package.json')) + }) + + fastify.get('/wildcard', (_request, reply) => { + reply + .header('vary', '*') + .type('text/plain') + .compress(createReadStream('./package.json')) + }) + + const res1 = await fastify.inject({ + url: '/case', + method: 'GET', + headers: { 'accept-encoding': 'deflate' } + }) + t.assert.equal(res1.headers.vary, 'Accept-Encoding') + + const res2 = await fastify.inject({ + url: '/substring', + method: 'GET', + headers: { 'accept-encoding': 'deflate' } + }) + t.assert.equal(res2.headers.vary, 'x-accept-encoding, accept-encoding') + + const res3 = await fastify.inject({ + url: '/wildcard', + method: 'GET', + headers: { 'accept-encoding': 'deflate' } + }) + t.assert.equal(res3.headers.vary, '*') +}) + test('It should follow the `Accept-Encoding` request header encoding order', async (t) => { t.plan(3)