Skip to content

Commit 5791cb6

Browse files
ztannerijjk
andauthored
[Backport v14] add additional x-middleware-set-cookie filtering (#75561) (#75870)
Backports: - #75561 - #73482 --------- Co-authored-by: JJ Kasper <jj@jjsweb.site>
1 parent 8129a61 commit 5791cb6

File tree

13 files changed

+83
-0
lines changed

13 files changed

+83
-0
lines changed

packages/next/src/server/lib/router-server.ts

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { getNextPathnameInfo } from '../../shared/lib/router/utils/get-next-path
4141
import { getHostname } from '../../shared/lib/get-hostname'
4242
import { detectDomainLocale } from '../../shared/lib/i18n/detect-domain-locale'
4343
import { normalizedAssetPrefix } from '../../shared/lib/normalized-asset-prefix'
44+
import { filterInternalHeaders } from './server-ipc/utils'
4445

4546
const debug = setupDebug('next:router-server:main')
4647
const isNextFont = (pathname: string | null) =>
@@ -149,6 +150,11 @@ export async function initialize(opts: {
149150
require('./render-server') as typeof import('./render-server')
150151

151152
const requestHandlerImpl: WorkerRequestHandler = async (req, res) => {
153+
// internal headers should not be honored by the request handler
154+
if (!process.env.NEXT_PRIVATE_TEST_HEADERS) {
155+
filterInternalHeaders(req.headers)
156+
}
157+
152158
if (
153159
!opts.minimalMode &&
154160
config.i18n &&

packages/next/src/server/lib/router-utils/resolve-routes.ts

+8
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,14 @@ export function getResolveRoutes(
589589
) {
590590
continue
591591
}
592+
593+
// for set-cookie, the header shouldn't be added to the response
594+
// as it's only needed for the request to the middleware function.
595+
if (key === 'x-middleware-set-cookie') {
596+
req.headers[key] = value
597+
continue
598+
}
599+
592600
if (value) {
593601
resHeaders[key] = value
594602
req.headers[key] = value

packages/next/src/server/lib/server-ipc/utils.ts

+23
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,26 @@ export const filterReqHeaders = (
3636
}
3737
return headers as Record<string, undefined | string | string[]>
3838
}
39+
40+
// These are headers that are only used internally and should
41+
// not be honored from the external request
42+
const INTERNAL_HEADERS = [
43+
'x-middleware-rewrite',
44+
'x-middleware-redirect',
45+
'x-middleware-set-cookie',
46+
'x-middleware-skip',
47+
'x-middleware-override-headers',
48+
'x-middleware-next',
49+
'x-now-route-matches',
50+
'x-matched-path',
51+
]
52+
53+
export const filterInternalHeaders = (
54+
headers: Record<string, undefined | string | string[]>
55+
) => {
56+
for (const header in headers) {
57+
if (INTERNAL_HEADERS.includes(header)) {
58+
delete headers[header]
59+
}
60+
}
61+
}

packages/next/src/server/send-response.ts

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export async function sendResponse(
2525

2626
// Copy over the response headers.
2727
response.headers?.forEach((value, name) => {
28+
// `x-middleware-set-cookie` is an internal header not needed for the response
29+
if (name.toLowerCase() === 'x-middleware-set-cookie') {
30+
return
31+
}
32+
2833
// The append handling is special cased for `set-cookie`.
2934
if (name.toLowerCase() === 'set-cookie') {
3035
// TODO: (wyattjoh) replace with native response iteration when we can upgrade undici

test/e2e/app-dir/app-middleware/app-middleware.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,18 @@ createNextDescribe(
174174
await browser.deleteCookies()
175175
})
176176

177+
it('should omit internal headers for middleware cookies', async () => {
178+
const response = await next.fetch('/rsc-cookies/cookie-options')
179+
expect(response.status).toBe(200)
180+
expect(response.headers.get('x-middleware-set-cookie')).toBeNull()
181+
182+
const response2 = await next.fetch('/cookies/api')
183+
expect(response2.status).toBe(200)
184+
expect(response2.headers.get('x-middleware-set-cookie')).toBeNull()
185+
expect(response2.headers.get('set-cookie')).toBeDefined()
186+
expect(response2.headers.get('set-cookie')).toContain('example')
187+
})
188+
177189
it('should respect cookie options of merged middleware cookies', async () => {
178190
const browser = await next.browser('/rsc-cookies/cookie-options')
179191

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { NextResponse } from 'next/server'
2+
3+
export function GET() {
4+
const response = new NextResponse()
5+
response.cookies.set({
6+
name: 'example',
7+
value: 'example',
8+
})
9+
10+
return response
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { cookies } from 'next/headers'
2+
3+
export default async function Page() {
4+
const cookieLength = (await cookies()).size
5+
return <div id="cookies">cookies: {cookieLength}</div>
6+
}

test/integration/required-server-files-ssr-404/test/index.test.js

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ describe('Required Server Files', () => {
4444
}
4545
await fs.rename(join(appDir, 'pages'), join(appDir, 'pages-bak'))
4646

47+
process.env.NEXT_PRIVATE_TEST_HEADERS = '1'
4748
nextApp = nextServer({
4849
conf: {},
4950
dir: appDir,
@@ -57,6 +58,7 @@ describe('Required Server Files', () => {
5758
console.log(`Listening at ::${appPort}`)
5859
})
5960
afterAll(async () => {
61+
delete process.env.NEXT_PRIVATE_TEST_HEADERS
6062
if (server) server.close()
6163
await fs.rename(join(appDir, 'pages-bak'), join(appDir, 'pages'))
6264
})

test/production/standalone-mode/required-server-files/required-server-files-app.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe('required server files app router', () => {
2525
}) => {
2626
// test build against environment with next support
2727
process.env.NOW_BUILDER = nextEnv ? '1' : ''
28+
process.env.NEXT_PRIVATE_TEST_HEADERS = '1'
2829

2930
next = await createNext({
3031
files: {
@@ -96,6 +97,7 @@ describe('required server files app router', () => {
9697
await setupNext({ nextEnv: true, minimalMode: true })
9798
})
9899
afterAll(async () => {
100+
delete process.env.NEXT_PRIVATE_TEST_HEADERS
99101
await next.destroy()
100102
if (server) await killApp(server)
101103
})

test/production/standalone-mode/required-server-files/required-server-files-i18n.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ describe('required server files i18n', () => {
2424

2525
beforeAll(async () => {
2626
let wasmPkgIsAvailable = false
27+
process.env.NEXT_PRIVATE_TEST_HEADERS = '1'
2728

2829
const res = await nodeFetch(
2930
`https://registry.npmjs.com/@next/swc-wasm-nodejs/-/swc-wasm-nodejs-${
@@ -128,6 +129,7 @@ describe('required server files i18n', () => {
128129
)
129130
})
130131
afterAll(async () => {
132+
delete process.env.NEXT_PRIVATE_TEST_HEADERS
131133
await next.destroy()
132134
if (server) await killApp(server)
133135
})

test/production/standalone-mode/required-server-files/required-server-files-ppr.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('required server files app router', () => {
2727
}) => {
2828
// test build against environment with next support
2929
process.env.NOW_BUILDER = nextEnv ? '1' : ''
30+
process.env.NEXT_PRIVATE_TEST_HEADERS = '1'
3031

3132
next = await createNext({
3233
files: {
@@ -106,6 +107,7 @@ describe('required server files app router', () => {
106107
await setupNext({ nextEnv: true, minimalMode: true })
107108
})
108109
afterAll(async () => {
110+
delete process.env.NEXT_PRIVATE_TEST_HEADERS
109111
await next.destroy()
110112
if (server) await killApp(server)
111113
})

test/production/standalone-mode/required-server-files/required-server-files.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe('required server files', () => {
3232
}) => {
3333
// test build against environment with next support
3434
process.env.NOW_BUILDER = nextEnv ? '1' : ''
35+
process.env.NEXT_PRIVATE_TEST_HEADERS = '1'
3536

3637
next = await createNext({
3738
files: {
@@ -139,6 +140,7 @@ describe('required server files', () => {
139140
await setupNext({ nextEnv: true, minimalMode: true })
140141
})
141142
afterAll(async () => {
143+
delete process.env.NEXT_PRIVATE_TEST_HEADERS
142144
await next.destroy()
143145
if (server) await killApp(server)
144146
})

test/production/standalone-mode/response-cache/index.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ describe('minimal-mode-response-cache', () => {
2222
beforeAll(async () => {
2323
// test build against environment with next support
2424
process.env.NOW_BUILDER = '1'
25+
process.env.NEXT_PRIVATE_TEST_HEADERS = '1'
2526

2627
next = await createNext({
2728
files: new FileRef(join(__dirname, 'app')),
@@ -84,6 +85,7 @@ describe('minimal-mode-response-cache', () => {
8485
appPort = `http://127.0.0.1:${port}`
8586
})
8687
afterAll(async () => {
88+
delete process.env.NEXT_PRIVATE_TEST_HEADERS
8789
await next.destroy()
8890
if (server) await killApp(server)
8991
})

0 commit comments

Comments
 (0)