diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 43e7aaaeede80..6909f42809ec2 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -127,6 +127,7 @@ import getRouteFromAssetPath from '../shared/lib/router/utils/get-route-from-ass import { stripInternalHeaders } from './internal-utils' import { RSCPathnameNormalizer } from './future/normalizers/request/rsc' import { PostponedPathnameNormalizer } from './future/normalizers/request/postponed' +import type { TLSSocket } from 'tls' export type FindComponentsResult = { components: LoadComponentsReturnType @@ -894,6 +895,15 @@ export default abstract class Server { } } + req.headers['x-forwarded-host'] ??= `${this.hostname}:${this.port}` + req.headers['x-forwarded-port'] ??= this.port?.toString() + const { originalRequest } = req as NodeNextRequest + req.headers['x-forwarded-proto'] ??= (originalRequest.socket as TLSSocket) + ?.encrypted + ? 'https' + : 'http' + req.headers['x-forwarded-for'] ??= originalRequest.socket?.remoteAddress + this.attachRequestMeta(req, parsedUrl) const domainLocale = this.i18nProvider?.detectDomainLocale( diff --git a/packages/next/src/server/internal-utils.ts b/packages/next/src/server/internal-utils.ts index 1e712b16379a4..fa0454d950a8e 100644 --- a/packages/next/src/server/internal-utils.ts +++ b/packages/next/src/server/internal-utils.ts @@ -2,6 +2,7 @@ import type { IncomingHttpHeaders } from 'http' import type { NextParsedUrlQuery } from './request-meta' import { NEXT_RSC_UNION_QUERY } from '../client/components/app-router-headers' +import { INTERNAL_HEADERS } from '../shared/lib/constants' const INTERNAL_QUERY_NAMES = [ '__nextFallback', @@ -39,19 +40,6 @@ export function stripInternalSearchParams( return (isStringUrl ? instance.toString() : instance) as T } -/** - * Headers that are set by the Next.js server and should be stripped from the - * request headers going to the user's application. - */ -const INTERNAL_HEADERS = [ - 'x-invoke-path', - 'x-invoke-status', - 'x-invoke-error', - 'x-invoke-query', - 'x-invoke-output', - 'x-middleware-invoke', -] as const - /** * Strip internal headers from the request headers. * diff --git a/packages/next/src/server/lib/mock-request.ts b/packages/next/src/server/lib/mock-request.ts index d5b63ac2902aa..b55f264e768d2 100644 --- a/packages/next/src/server/lib/mock-request.ts +++ b/packages/next/src/server/lib/mock-request.ts @@ -37,13 +37,15 @@ export class MockedRequest extends Stream.Readable implements IncomingMessage { private bodyReadable?: Stream.Readable // If we don't actually have a socket, we'll just use a mock one that - // always returns false for the `encrypted` property. + // always returns false for the `encrypted` property and undefined for the + // `remoteAddress` property. public socket: Socket = new Proxy({} as TLSSocket, { get: (_target, prop) => { - if (prop !== 'encrypted') { + if (prop !== 'encrypted' && prop !== 'remoteAddress') { throw new Error('Method not implemented') } + if (prop === 'remoteAddress') return undefined // For this mock request, always ensure we just respond with the encrypted // set to false to ensure there's no odd leakages. return false diff --git a/packages/next/src/server/lib/router-server.ts b/packages/next/src/server/lib/router-server.ts index 3d15e9491e4a1..644dea1f77c67 100644 --- a/packages/next/src/server/lib/router-server.ts +++ b/packages/next/src/server/lib/router-server.ts @@ -32,7 +32,6 @@ import { PERMANENT_REDIRECT_STATUS, } from '../../shared/lib/constants' import { DevBundlerService } from './dev-bundler-service' -import type { TLSSocket } from 'tls' const debug = setupDebug('next:router-server:main') @@ -225,17 +224,6 @@ export async function initialize(opts: { 'x-middleware-invoke': '', 'x-invoke-path': invokePath, 'x-invoke-query': encodeURIComponent(JSON.stringify(parsedUrl.query)), - 'x-forwarded-host': - req.headers['x-forwarded-host'] ?? req.headers.host ?? opts.hostname, - 'x-forwarded-port': - req.headers['x-forwarded-port'] ?? opts.port.toString(), - 'x-forwarded-proto': - req.headers['x-forwarded-proto'] ?? - (req.socket as TLSSocket).encrypted - ? 'https' - : 'http', - 'x-forwarded-for': - req.headers['x-forwarded-for'] ?? req.socket.remoteAddress, ...(additionalInvokeHeaders || {}), } Object.assign(req.headers, invokeHeaders) diff --git a/packages/next/src/server/lib/router-utils/proxy-request.ts b/packages/next/src/server/lib/router-utils/proxy-request.ts index c95df56f0f292..3b2f4c7ca0648 100644 --- a/packages/next/src/server/lib/router-utils/proxy-request.ts +++ b/packages/next/src/server/lib/router-utils/proxy-request.ts @@ -24,11 +24,13 @@ export async function proxyRequest( target, changeOrigin: true, ignorePath: true, - xfwd: true, ws: true, // we limit proxy requests to 30s by default, in development // we don't time out WebSocket requests to allow proxying proxyTimeout: proxyTimeout === null ? undefined : proxyTimeout || 30_000, + headers: { + 'x-forwarded-host': req.headers.host || '', + }, }) await new Promise((proxyResolve, proxyReject) => { diff --git a/packages/next/src/server/lib/router-utils/resolve-routes.ts b/packages/next/src/server/lib/router-utils/resolve-routes.ts index 377ab17503982..e77d6fc6ba0d6 100644 --- a/packages/next/src/server/lib/router-utils/resolve-routes.ts +++ b/packages/next/src/server/lib/router-utils/resolve-routes.ts @@ -1,4 +1,3 @@ -import type { TLSSocket } from 'tls' import type { FsOutput } from './filesystem' import type { IncomingMessage, ServerResponse } from 'http' import type { NextConfigComplete } from '../../config-shared' @@ -38,6 +37,7 @@ import { prepareDestination, } from '../../../shared/lib/router/utils/prepare-destination' import { createRequestResponseMocks } from '../mock-request' +import type { TLSSocket } from 'tls' const debug = setupDebug('next:router-server:resolve-routes') diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index a453d2a06a10c..c9f8c9020bc48 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -2,7 +2,6 @@ import './node-environment' import './require-hook' import './node-polyfill-crypto' -import type { TLSSocket } from 'tls' import type { CacheFs } from '../shared/lib/utils' import { DecodeError, @@ -41,7 +40,6 @@ import { SERVER_DIRECTORY, NEXT_FONT_MANIFEST, PHASE_PRODUCTION_BUILD, - INTERNAL_HEADERS, } from '../shared/lib/constants' import { findDir } from '../lib/find-pages-dir' import { NodeNextRequest, NodeNextResponse } from './base-http/node' @@ -1616,10 +1614,6 @@ export default class NextNodeServer extends BaseServer { > let bubblingResult = false - for (const key of INTERNAL_HEADERS) { - delete req.headers[key] - } - // Strip the internal headers. this.stripInternalHeaders(req) @@ -1746,11 +1740,8 @@ export default class NextNodeServer extends BaseServer { parsedUrl: NextUrlWithParsedQuery, isUpgradeReq?: boolean ) { - const protocol = - ((req as NodeNextRequest).originalRequest?.socket as TLSSocket) - ?.encrypted || req.headers['x-forwarded-proto']?.includes('https') - ? 'https' - : 'http' + // Injected in base-server.ts + const protocol = req.headers['x-forwarded-proto'] as 'https' | 'http' // When there are hostname and port we build an absolute URL const initUrl = diff --git a/packages/next/src/server/web/adapter.ts b/packages/next/src/server/web/adapter.ts index 36b074208f4c1..5f69e0c593161 100644 --- a/packages/next/src/server/web/adapter.ts +++ b/packages/next/src/server/web/adapter.ts @@ -11,11 +11,7 @@ import { waitUntilSymbol } from './spec-extension/fetch-event' import { NextURL } from './next-url' import { stripInternalSearchParams } from '../internal-utils' import { normalizeRscURL } from '../../shared/lib/router/utils/app-paths' -import { - NEXT_ROUTER_PREFETCH, - NEXT_ROUTER_STATE_TREE, - RSC, -} from '../../client/components/app-router-headers' +import { FLIGHT_PARAMETERS } from '../../client/components/app-router-headers' import { NEXT_QUERY_PARAM_PREFIX } from '../../lib/constants' import { ensureInstrumentationRegistered } from './globals' import { RequestAsyncStorageWrapper } from '../async-storage/request-async-storage-wrapper' @@ -47,12 +43,6 @@ class NextRequestHint extends NextRequest { } } -const FLIGHT_PARAMETERS = [ - [RSC], - [NEXT_ROUTER_STATE_TREE], - [NEXT_ROUTER_PREFETCH], -] as const - export type AdapterOptions = { handler: NextMiddleware page: string diff --git a/packages/next/src/shared/lib/constants.ts b/packages/next/src/shared/lib/constants.ts index eef3be8b70913..69ef1107ab3aa 100644 --- a/packages/next/src/shared/lib/constants.ts +++ b/packages/next/src/shared/lib/constants.ts @@ -10,11 +10,16 @@ export const COMPILER_NAMES = { edgeServer: 'edge-server', } as const +/** + * Headers that are set by the Next.js server and should be stripped from the + * request headers going to the user's application. + */ export const INTERNAL_HEADERS = [ - 'x-invoke-path', - 'x-invoke-status', 'x-invoke-error', + 'x-invoke-output', + 'x-invoke-path', 'x-invoke-query', + 'x-invoke-status', 'x-middleware-invoke', ] as const diff --git a/test/e2e/app-dir/x-forwarded-headers/middleware.ts b/test/e2e/app-dir/x-forwarded-headers/middleware.ts new file mode 100644 index 0000000000000..b75c57a077bcd --- /dev/null +++ b/test/e2e/app-dir/x-forwarded-headers/middleware.ts @@ -0,0 +1,14 @@ +import { NextResponse } from 'next/server' + +export function middleware(req: Request) { + return NextResponse.next({ + headers: { + 'middleware-x-forwarded-host': + req.headers.get('x-forwarded-host') ?? 'wrong', + 'middleware-x-forwarded-port': + req.headers.get('x-forwarded-port') ?? 'wrong', + 'middleware-x-forwarded-proto': + req.headers.get('x-forwarded-proto') ?? 'wrong', + }, + }) +} diff --git a/test/e2e/app-dir/x-forwarded-headers/x-forwarded-headers.test.ts b/test/e2e/app-dir/x-forwarded-headers/x-forwarded-headers.test.ts index 00aca2a9182e2..a91e29320c32a 100644 --- a/test/e2e/app-dir/x-forwarded-headers/x-forwarded-headers.test.ts +++ b/test/e2e/app-dir/x-forwarded-headers/x-forwarded-headers.test.ts @@ -8,6 +8,10 @@ createNextDescribe('x-forwarded-headers', { files: __dirname }, ({ next }) => { expect(headers['x-forwarded-host']).toBe(url.host) expect(headers['x-forwarded-port']).toBe(url.port) expect(headers['x-forwarded-proto']).toBe(url.protocol.replace(':', '')) - console.log(headers) + expect(headers['middleware-x-forwarded-host']).toBe(url.host) + expect(headers['middleware-x-forwarded-port']).toBe(url.port) + expect(headers['middleware-x-forwarded-proto']).toBe( + url.protocol.replace(':', '') + ) }) })