From d0cbe64e8783740ecc1315e04757008f624a0dbd Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 3 Oct 2024 17:23:04 -0600 Subject: [PATCH] [i18n] Routing fix (#70761) This fixes a bug where an incorrectly sanitized query parameter would cause an invalid routing condition resulting in the wrong route being served to users. This currently requires some specific edge conditions in order to trigger such as missing i18n configuration, self-hosted, specifically crafted query parameter, and a path-based middleware for authorization. --- packages/next/src/server/base-server.ts | 8 +++++ packages/next/src/server/lib/i18n-provider.ts | 31 +++++++++++++++++++ .../server/lib/router-utils/resolve-routes.ts | 5 +++ 3 files changed, 44 insertions(+) diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 7c88f4e7620de..b0b71b119cf6b 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -1017,6 +1017,14 @@ export default abstract class Server< req.headers['x-forwarded-proto'] ??= isHttps ? 'https' : 'http' req.headers['x-forwarded-for'] ??= originalRequest?.socket?.remoteAddress + // Validate that if i18n isn't configured or the passed parameters are not + // valid it should be removed from the query. + if (!this.i18nProvider?.validateQuery(parsedUrl.query)) { + delete parsedUrl.query.__nextLocale + delete parsedUrl.query.__nextDefaultLocale + delete parsedUrl.query.__nextInferredLocaleFromDefault + } + // This should be done before any normalization of the pathname happens as // it captures the initial URL. this.attachRequestMeta(req, parsedUrl) diff --git a/packages/next/src/server/lib/i18n-provider.ts b/packages/next/src/server/lib/i18n-provider.ts index efdbe4ac12dd4..0a1e7c7503167 100644 --- a/packages/next/src/server/lib/i18n-provider.ts +++ b/packages/next/src/server/lib/i18n-provider.ts @@ -134,6 +134,37 @@ export class I18NProvider { } } + /** + * Validates that the locale is valid. + * + * @param locale The locale to validate. + * @returns `true` if the locale is valid, `false` otherwise. + */ + private validate(locale: string): boolean { + return this.lowerCaseLocales.includes(locale.toLowerCase()) + } + + /** + * Validates that the locales in the query object are valid. + * + * @param query The query object to validate. + * @returns `true` if the locale is valid, `false` otherwise. + */ + public validateQuery(query: NextParsedUrlQuery) { + if (query.__nextLocale && !this.validate(query.__nextLocale)) { + return false + } + + if ( + query.__nextDefaultLocale && + !this.validate(query.__nextDefaultLocale) + ) { + return false + } + + return true + } + /** * Analyzes the pathname for a locale and returns the pathname without it. * 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 f12a3d2bc3c1e..95ed26105a0be 100644 --- a/packages/next/src/server/lib/router-utils/resolve-routes.ts +++ b/packages/next/src/server/lib/router-utils/resolve-routes.ts @@ -218,6 +218,11 @@ export function getResolveRoutes( parsedUrl.pathname = maybeAddTrailingSlash(parsedUrl.pathname) } } + } else { + // As i18n isn't configured we remove the locale related query params. + delete parsedUrl.query.__nextLocale + delete parsedUrl.query.__nextDefaultLocale + delete parsedUrl.query.__nextInferredLocaleFromDefault } const checkLocaleApi = (pathname: string) => {