90 lines
4.7 KiB
JavaScript
Executable File
90 lines
4.7 KiB
JavaScript
Executable File
import { searchParamsToUrlQuery } from '../shared/lib/router/utils/querystring';
|
|
import { formatWithValidation } from '../shared/lib/router/utils/format-url';
|
|
import { omit } from '../shared/lib/router/utils/omit';
|
|
import { normalizeRepeatedSlashes } from '../shared/lib/utils';
|
|
import { normalizePathTrailingSlash } from './normalize-trailing-slash';
|
|
import { isLocalURL } from '../shared/lib/router/utils/is-local-url';
|
|
import { isDynamicRoute } from '../shared/lib/router/utils';
|
|
import { interpolateAs } from '../shared/lib/router/utils/interpolate-as';
|
|
import { getRouteRegex } from '../shared/lib/router/utils/route-regex';
|
|
import { getRouteMatcher } from '../shared/lib/router/utils/route-matcher';
|
|
export function resolveHref(router, href, resolveAs) {
|
|
// we use a dummy base url for relative urls
|
|
let base;
|
|
let urlAsString = typeof href === 'string' ? href : formatWithValidation(href);
|
|
// repeated slashes and backslashes in the URL are considered
|
|
// invalid and will never match a Next.js page/file
|
|
// https://www.rfc-editor.org/rfc/rfc3986.html#section-3.1
|
|
const urlProtoMatch = urlAsString.match(/^[a-z][a-z0-9+.-]*:\/\//i);
|
|
const urlAsStringNoProto = urlProtoMatch ? urlAsString.slice(urlProtoMatch[0].length) : urlAsString;
|
|
const urlParts = urlAsStringNoProto.split('?', 1);
|
|
if ((urlParts[0] || '').match(/(\/\/|\\)/)) {
|
|
console.error("Invalid href '" + urlAsString + "' passed to next/router in page: '" + router.pathname + "'. Repeated forward-slashes (//) or backslashes \\ are not valid in the href.");
|
|
const normalizedUrl = normalizeRepeatedSlashes(urlAsStringNoProto);
|
|
urlAsString = (urlProtoMatch ? urlProtoMatch[0] : '') + normalizedUrl;
|
|
}
|
|
// Return because it cannot be routed by the Next.js router
|
|
if (!isLocalURL(urlAsString)) {
|
|
return resolveAs ? [
|
|
urlAsString
|
|
] : urlAsString;
|
|
}
|
|
try {
|
|
let baseBase = urlAsString.startsWith('#') ? router.asPath : router.pathname;
|
|
// If the provided href is only a query string, it is safer to use the asPath
|
|
// considering rewrites.
|
|
if (urlAsString.startsWith('?')) {
|
|
baseBase = router.asPath;
|
|
// However, if is a dynamic route, we need to use the pathname to preserve the
|
|
// query interpolation and rewrites (router.pathname will look like "/[slug]").
|
|
if (isDynamicRoute(router.pathname)) {
|
|
baseBase = router.pathname;
|
|
const routeRegex = getRouteRegex(router.pathname);
|
|
const match = getRouteMatcher(routeRegex)(router.asPath);
|
|
// For dynamic routes, if asPath doesn't match the pathname regex, it is a rewritten path.
|
|
// In this case, should use asPath to preserve the current URL.
|
|
if (!match) {
|
|
baseBase = router.asPath;
|
|
}
|
|
// Note: There is an edge case where the pathname is dynamic, and also a rewrite path to the same segment.
|
|
// E.g. in "/[slug]" path, rewrite "/foo" -> "/bar"
|
|
// In this case, it will be treated as a non-rewritten path and possibly interpolate the query string.
|
|
// E.g., "/any?slug=foo" will become the content of "/foo", not rewritten as "/bar"
|
|
// This is currently a trade-off of not resolving rewrite paths on every Router/Link call,
|
|
// but using a lighter route regex pattern check.
|
|
}
|
|
}
|
|
base = new URL(baseBase, 'http://n');
|
|
} catch (_) {
|
|
// fallback to / for invalid asPath values e.g. //
|
|
base = new URL('/', 'http://n');
|
|
}
|
|
try {
|
|
const finalUrl = new URL(urlAsString, base);
|
|
finalUrl.pathname = normalizePathTrailingSlash(finalUrl.pathname);
|
|
let interpolatedAs = '';
|
|
if (isDynamicRoute(finalUrl.pathname) && finalUrl.searchParams && resolveAs) {
|
|
const query = searchParamsToUrlQuery(finalUrl.searchParams);
|
|
const { result, params } = interpolateAs(finalUrl.pathname, finalUrl.pathname, query);
|
|
if (result) {
|
|
interpolatedAs = formatWithValidation({
|
|
pathname: result,
|
|
hash: finalUrl.hash,
|
|
query: omit(query, params)
|
|
});
|
|
}
|
|
}
|
|
// if the origin didn't change, it means we received a relative href
|
|
const resolvedHref = finalUrl.origin === base.origin ? finalUrl.href.slice(finalUrl.origin.length) : finalUrl.href;
|
|
return resolveAs ? [
|
|
resolvedHref,
|
|
interpolatedAs || resolvedHref
|
|
] : resolvedHref;
|
|
} catch (_) {
|
|
return resolveAs ? [
|
|
urlAsString
|
|
] : urlAsString;
|
|
}
|
|
}
|
|
|
|
//# sourceMappingURL=resolve-href.js.map
|