|
1 | 1 | import crypto from "node:crypto"; |
2 | 2 | import type { OutgoingHttpHeaders } from "node:http"; |
| 3 | +import { parse as parseQs, stringify as stringifyQs } from "node:querystring"; |
3 | 4 | import { Readable } from "node:stream"; |
4 | 5 |
|
5 | 6 | import { BuildId, HtmlPages, NextConfig } from "config/index.js"; |
@@ -442,31 +443,39 @@ export async function invalidateCDNOnRequest( |
442 | 443 | * Normalizes the Location header to either be a relative path or a full URL. |
443 | 444 | * If the Location header is relative to the origin, it will return a relative path. |
444 | 445 | * If it is an absolute URL, it will return the full URL. |
445 | | - * Both cases will ensure that the Location value is properly encoded according to RFC |
| 446 | + * Redirects from Next config query parameters are encoded using `stringifyQs` |
| 447 | + * Redirects from the middleware the query parameters are not encoded. |
446 | 448 | * |
447 | 449 | * @param location The Location header value |
448 | 450 | * @param baseUrl The base URL to use for relative paths (i.e the original request URL) |
449 | | - * @returns An encoded absolute or relative Location header value |
| 451 | + * @param encodeQuery Optional flag to indicate if query parameters should be encoded in the Location header |
| 452 | + * @returns An absolute or relative Location header value |
450 | 453 | */ |
451 | 454 | export function normalizeLocationHeader( |
452 | 455 | location: string, |
453 | 456 | baseUrl: string, |
| 457 | + encodeQuery = false, |
454 | 458 | ): string { |
455 | 459 | if (!URL.canParse(location)) { |
456 | 460 | // If the location is not a valid URL, return it as-is |
457 | 461 | return location; |
458 | 462 | } |
459 | 463 |
|
460 | | - const locationUrl = new URL(location); |
| 464 | + const locationURL = new URL(location); |
461 | 465 | const origin = new URL(baseUrl).origin; |
462 | 466 |
|
463 | | - // Encode the search parameters to ensure they are valid according to RFC |
464 | | - const searchParams = locationUrl.searchParams.toString(); |
465 | | - const encodedSearch = searchParams ? `?${searchParams}` : ""; |
466 | | - const href = `${locationUrl.origin}${locationUrl.pathname}${encodedSearch}${locationUrl.hash}`; |
467 | | - |
| 467 | + // Redirects from the middleware do not encode the query parameters |
| 468 | + let search = locationURL.search; |
| 469 | + // If encodeQuery is true, we need to encode the query parameters |
| 470 | + // This is used for redirects from Next config |
| 471 | + // We could have used URLSearchParams, but that encodes it too much. stringify from querystring uses encodeURIComponent |
| 472 | + // which is what Next.js does for redirects from the config |
| 473 | + if (encodeQuery) { |
| 474 | + search = search ? `?${stringifyQs(parseQs(search.slice(1)))}` : ""; |
| 475 | + } |
| 476 | + const href = `${locationURL.origin}${locationURL.pathname}${search}${locationURL.hash}`; |
468 | 477 | // The URL is relative if the origin is the same as the base URL's origin |
469 | | - if (locationUrl.origin === origin) { |
| 478 | + if (locationURL.origin === origin) { |
470 | 479 | return href.slice(origin.length); |
471 | 480 | } |
472 | 481 | return href; |
|
0 commit comments