2022-06-16 23:43:01 +02:00
|
|
|
/* eslint-disable no-redeclare */
|
2022-01-26 07:22:11 +01:00
|
|
|
import type {
|
|
|
|
Header,
|
|
|
|
Redirect,
|
|
|
|
Rewrite,
|
|
|
|
RouteType,
|
|
|
|
} from '../lib/load-custom-routes'
|
|
|
|
import type { Route } from './router'
|
|
|
|
import type { BaseNextRequest } from './base-http'
|
|
|
|
import type { ParsedUrlQuery } from 'querystring'
|
|
|
|
|
|
|
|
import { getRedirectStatus, modifyRouteRegex } from '../lib/load-custom-routes'
|
2022-04-27 11:50:29 +02:00
|
|
|
import { getPathMatch } from '../shared/lib/router/utils/path-match'
|
2022-01-26 07:22:11 +01:00
|
|
|
import {
|
|
|
|
compileNonPath,
|
|
|
|
prepareDestination,
|
|
|
|
} from '../shared/lib/router/utils/prepare-destination'
|
|
|
|
import { getRequestMeta } from './request-meta'
|
|
|
|
import { stringify as stringifyQs } from 'querystring'
|
|
|
|
import { format as formatUrl } from 'url'
|
|
|
|
import { normalizeRepeatedSlashes } from '../shared/lib/utils'
|
|
|
|
|
2022-06-16 23:43:01 +02:00
|
|
|
export function getCustomRoute(params: {
|
|
|
|
rule: Header
|
|
|
|
type: RouteType
|
|
|
|
restrictedRedirectPaths: string[]
|
|
|
|
}): Route & Header
|
|
|
|
export function getCustomRoute(params: {
|
|
|
|
rule: Rewrite
|
|
|
|
type: RouteType
|
|
|
|
restrictedRedirectPaths: string[]
|
|
|
|
}): Route & Rewrite
|
|
|
|
export function getCustomRoute(params: {
|
|
|
|
rule: Redirect
|
|
|
|
type: RouteType
|
|
|
|
restrictedRedirectPaths: string[]
|
|
|
|
}): Route & Redirect
|
|
|
|
export function getCustomRoute(params: {
|
2022-01-26 07:22:11 +01:00
|
|
|
rule: Rewrite | Redirect | Header
|
|
|
|
type: RouteType
|
|
|
|
restrictedRedirectPaths: string[]
|
2022-06-16 23:43:01 +02:00
|
|
|
}): (Route & Rewrite) | (Route & Header) | (Route & Rewrite) {
|
|
|
|
const { rule, type, restrictedRedirectPaths } = params
|
2022-04-27 11:50:29 +02:00
|
|
|
const match = getPathMatch(rule.source, {
|
|
|
|
strict: true,
|
|
|
|
removeUnnamedParams: true,
|
|
|
|
regexModifier: !(rule as any).internal
|
2022-01-26 07:22:11 +01:00
|
|
|
? (regex: string) =>
|
|
|
|
modifyRouteRegex(
|
|
|
|
regex,
|
|
|
|
type === 'redirect' ? restrictedRedirectPaths : undefined
|
|
|
|
)
|
2022-04-27 11:50:29 +02:00
|
|
|
: undefined,
|
|
|
|
})
|
2022-01-26 07:22:11 +01:00
|
|
|
|
|
|
|
return {
|
|
|
|
...rule,
|
|
|
|
type,
|
|
|
|
match,
|
|
|
|
name: type,
|
|
|
|
fn: async (_req, _res, _params, _parsedUrl) => ({ finished: false }),
|
2022-06-16 23:43:01 +02:00
|
|
|
}
|
2022-01-26 07:22:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export const createHeaderRoute = ({
|
|
|
|
rule,
|
|
|
|
restrictedRedirectPaths,
|
|
|
|
}: {
|
|
|
|
rule: Header
|
|
|
|
restrictedRedirectPaths: string[]
|
2022-06-16 23:43:01 +02:00
|
|
|
}): Route => {
|
2022-01-26 07:22:11 +01:00
|
|
|
const headerRoute = getCustomRoute({
|
|
|
|
type: 'header',
|
|
|
|
rule,
|
|
|
|
restrictedRedirectPaths,
|
|
|
|
})
|
|
|
|
return {
|
|
|
|
match: headerRoute.match,
|
2022-06-16 23:43:01 +02:00
|
|
|
matchesBasePath: true,
|
|
|
|
matchesLocale: true,
|
|
|
|
matchesLocaleAPIRoutes: true,
|
|
|
|
matchesTrailingSlash: true,
|
2022-01-26 07:22:11 +01:00
|
|
|
has: headerRoute.has,
|
|
|
|
type: headerRoute.type,
|
|
|
|
name: `${headerRoute.type} ${headerRoute.source} header route`,
|
|
|
|
fn: async (_req, res, params, _parsedUrl) => {
|
|
|
|
const hasParams = Object.keys(params).length > 0
|
2022-06-16 23:43:01 +02:00
|
|
|
for (const header of headerRoute.headers) {
|
2022-01-26 07:22:11 +01:00
|
|
|
let { key, value } = header
|
|
|
|
if (hasParams) {
|
|
|
|
key = compileNonPath(key, params)
|
|
|
|
value = compileNonPath(value, params)
|
|
|
|
}
|
|
|
|
res.setHeader(key, value)
|
|
|
|
}
|
|
|
|
return { finished: false }
|
|
|
|
},
|
2022-06-16 23:43:01 +02:00
|
|
|
}
|
2022-01-26 07:22:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export const createRedirectRoute = ({
|
|
|
|
rule,
|
|
|
|
restrictedRedirectPaths,
|
|
|
|
}: {
|
|
|
|
rule: Redirect
|
|
|
|
restrictedRedirectPaths: string[]
|
2022-06-16 23:43:01 +02:00
|
|
|
}): Route => {
|
2022-01-26 07:22:11 +01:00
|
|
|
const redirectRoute = getCustomRoute({
|
|
|
|
type: 'redirect',
|
|
|
|
rule,
|
|
|
|
restrictedRedirectPaths,
|
|
|
|
})
|
|
|
|
return {
|
|
|
|
internal: redirectRoute.internal,
|
|
|
|
type: redirectRoute.type,
|
|
|
|
match: redirectRoute.match,
|
2022-06-16 23:43:01 +02:00
|
|
|
matchesBasePath: true,
|
|
|
|
matchesLocale: redirectRoute.internal ? undefined : true,
|
|
|
|
matchesLocaleAPIRoutes: true,
|
|
|
|
matchesTrailingSlash: true,
|
2022-01-26 07:22:11 +01:00
|
|
|
has: redirectRoute.has,
|
|
|
|
statusCode: redirectRoute.statusCode,
|
|
|
|
name: `Redirect route ${redirectRoute.source}`,
|
|
|
|
fn: async (req, res, params, parsedUrl) => {
|
|
|
|
const { parsedDestination } = prepareDestination({
|
|
|
|
appendParamsToQuery: false,
|
|
|
|
destination: redirectRoute.destination,
|
|
|
|
params: params,
|
|
|
|
query: parsedUrl.query,
|
|
|
|
})
|
|
|
|
|
|
|
|
const { query } = parsedDestination
|
|
|
|
delete (parsedDestination as any).query
|
|
|
|
|
|
|
|
parsedDestination.search = stringifyQuery(req, query)
|
|
|
|
|
|
|
|
let updatedDestination = formatUrl(parsedDestination)
|
|
|
|
|
|
|
|
if (updatedDestination.startsWith('/')) {
|
|
|
|
updatedDestination = normalizeRepeatedSlashes(updatedDestination)
|
|
|
|
}
|
|
|
|
|
|
|
|
res
|
2022-06-16 23:43:01 +02:00
|
|
|
.redirect(updatedDestination, getRedirectStatus(redirectRoute))
|
2022-01-26 07:22:11 +01:00
|
|
|
.body(updatedDestination)
|
|
|
|
.send()
|
|
|
|
|
|
|
|
return {
|
|
|
|
finished: true,
|
|
|
|
}
|
|
|
|
},
|
2022-06-16 23:43:01 +02:00
|
|
|
}
|
2022-01-26 07:22:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// since initial query values are decoded by querystring.parse
|
|
|
|
// we need to re-encode them here but still allow passing through
|
|
|
|
// values from rewrites/redirects
|
|
|
|
export const stringifyQuery = (req: BaseNextRequest, query: ParsedUrlQuery) => {
|
2022-01-28 17:20:34 +01:00
|
|
|
const initialQuery = getRequestMeta(req, '__NEXT_INIT_QUERY') || {}
|
|
|
|
const initialQueryValues: Array<string | string[]> =
|
|
|
|
Object.values(initialQuery)
|
2022-01-26 07:22:11 +01:00
|
|
|
|
|
|
|
return stringifyQs(query, undefined, undefined, {
|
|
|
|
encodeURIComponent(value) {
|
2022-01-28 17:20:34 +01:00
|
|
|
if (
|
|
|
|
value in initialQuery ||
|
|
|
|
initialQueryValues.some((initialQueryVal: string | string[]) => {
|
|
|
|
// `value` always refers to a query value, even if it's nested in an array
|
|
|
|
return Array.isArray(initialQueryVal)
|
|
|
|
? initialQueryVal.includes(value)
|
|
|
|
: initialQueryVal === value
|
|
|
|
})
|
|
|
|
) {
|
|
|
|
// Encode keys and values from initial query
|
2022-01-26 07:22:11 +01:00
|
|
|
return encodeURIComponent(value)
|
|
|
|
}
|
2022-01-28 17:20:34 +01:00
|
|
|
|
2022-01-26 07:22:11 +01:00
|
|
|
return value
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|