next
Version:
The React Framework
223 lines (222 loc) • 8.54 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.matchHas = matchHas;
exports.compileNonPath = compileNonPath;
exports.default = prepareDestination;
exports.getSafeParamName = void 0;
var _parseUrl = require("./parse-url");
var pathToRegexp = _interopRequireWildcard(require("next/dist/compiled/path-to-regexp"));
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
} else {
var newObj = {
};
if (obj != null) {
for(var key in obj){
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {
};
if (desc.get || desc.set) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
}
newObj.default = obj;
return newObj;
}
}
const getSafeParamName = (paramName)=>{
let newParamName = '';
for(let i = 0; i < paramName.length; i++){
const charCode = paramName.charCodeAt(i);
if (charCode > 64 && charCode < 91 || charCode > 96 && charCode < 123 // a-z
) {
newParamName += paramName[i];
}
}
return newParamName;
};
exports.getSafeParamName = getSafeParamName;
function matchHas(req, has, query) {
const params = {
};
const allMatch = has.every((hasItem)=>{
let value;
let key = hasItem.key;
switch(hasItem.type){
case 'header':
{
key = key.toLowerCase();
value = req.headers[key];
break;
}
case 'cookie':
{
value = req.cookies[hasItem.key];
break;
}
case 'query':
{
value = query[key];
break;
}
case 'host':
{
const { host } = (req === null || req === void 0 ? void 0 : req.headers) || {
};
// remove port from host if present
const hostname = host === null || host === void 0 ? void 0 : host.split(':')[0].toLowerCase();
value = hostname;
break;
}
default:
{
break;
}
}
if (!hasItem.value && value) {
params[getSafeParamName(key)] = value;
return true;
} else if (value) {
const matcher = new RegExp(`^${hasItem.value}$`);
const matches = Array.isArray(value) ? value.slice(-1)[0].match(matcher) : value.match(matcher);
if (matches) {
if (Array.isArray(matches)) {
if (matches.groups) {
Object.keys(matches.groups).forEach((groupKey)=>{
params[groupKey] = matches.groups[groupKey];
});
} else if (hasItem.type === 'host' && matches[0]) {
params.host = matches[0];
}
}
return true;
}
}
return false;
});
if (allMatch) {
return params;
}
return false;
}
function compileNonPath(value, params) {
if (!value.includes(':')) {
return value;
}
for (const key of Object.keys(params)){
if (value.includes(`:${key}`)) {
value = value.replace(new RegExp(`:${key}\\*`, 'g'), `:${key}--ESCAPED_PARAM_ASTERISKS`).replace(new RegExp(`:${key}\\?`, 'g'), `:${key}--ESCAPED_PARAM_QUESTION`).replace(new RegExp(`:${key}\\+`, 'g'), `:${key}--ESCAPED_PARAM_PLUS`).replace(new RegExp(`:${key}(?!\\w)`, 'g'), `--ESCAPED_PARAM_COLON${key}`);
}
}
value = value.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, '\\$1').replace(/--ESCAPED_PARAM_PLUS/g, '+').replace(/--ESCAPED_PARAM_COLON/g, ':').replace(/--ESCAPED_PARAM_QUESTION/g, '?').replace(/--ESCAPED_PARAM_ASTERISKS/g, '*');
// the value needs to start with a forward-slash to be compiled
// correctly
return pathToRegexp.compile(`/${value}`, {
validate: false
})(params).substr(1);
}
const escapeSegment = (str, segmentName)=>str.replace(new RegExp(`:${segmentName}`, 'g'), `__ESC_COLON_${segmentName}`)
;
const unescapeSegments = (str)=>str.replace(/__ESC_COLON_/gi, ':')
;
function prepareDestination(destination, params, query, appendParamsToQuery) {
// clone query so we don't modify the original
query = Object.assign({
}, query);
const hadLocale = query.__nextLocale;
delete query.__nextLocale;
delete query.__nextDefaultLocale;
let escapedDestination = destination;
for (const param of Object.keys({
...params,
...query
})){
escapedDestination = escapeSegment(escapedDestination, param);
}
const parsedDestination = (0, _parseUrl).parseUrl(escapedDestination);
const destQuery = parsedDestination.query;
const destPath = unescapeSegments(`${parsedDestination.pathname}${parsedDestination.hash || ''}`);
const destHostname = unescapeSegments(parsedDestination.hostname || '');
const destPathParamKeys = [];
const destHostnameParamKeys = [];
pathToRegexp.pathToRegexp(destPath, destPathParamKeys);
pathToRegexp.pathToRegexp(destHostname, destHostnameParamKeys);
const destParams = [];
destPathParamKeys.forEach((key)=>destParams.push(key.name)
);
destHostnameParamKeys.forEach((key)=>destParams.push(key.name)
);
const destPathCompiler = pathToRegexp.compile(destPath, // we don't validate while compiling the destination since we should
// have already validated before we got to this point and validating
// breaks compiling destinations with named pattern params from the source
// e.g. /something:hello(.*) -> /another/:hello is broken with validation
// since compile validation is meant for reversing and not for inserting
// params from a separate path-regex into another
{
validate: false
});
const destHostnameCompiler = pathToRegexp.compile(destHostname, {
validate: false
});
let newUrl;
// update any params in query values
for (const [key, strOrArray] of Object.entries(destQuery)){
// the value needs to start with a forward-slash to be compiled
// correctly
if (Array.isArray(strOrArray)) {
destQuery[key] = strOrArray.map((value)=>compileNonPath(unescapeSegments(value), params)
);
} else {
destQuery[key] = compileNonPath(unescapeSegments(strOrArray), params);
}
}
// add path params to query if it's not a redirect and not
// already defined in destination query or path
let paramKeys = Object.keys(params);
// remove internal param for i18n
if (hadLocale) {
paramKeys = paramKeys.filter((name)=>name !== 'nextInternalLocale'
);
}
if (appendParamsToQuery && !paramKeys.some((key)=>destParams.includes(key)
)) {
for (const key of paramKeys){
if (!(key in destQuery)) {
destQuery[key] = params[key];
}
}
}
try {
newUrl = destPathCompiler(params);
const [pathname, hash] = newUrl.split('#');
parsedDestination.hostname = destHostnameCompiler(params);
parsedDestination.pathname = pathname;
parsedDestination.hash = `${hash ? '#' : ''}${hash || ''}`;
delete parsedDestination.search;
} catch (err) {
if (err.message.match(/Expected .*? to not repeat, but got an array/)) {
throw new Error(`To use a multi-match in the destination you must add \`*\` at the end of the param name to signify it should repeat. https://nextjs.org/docs/messages/invalid-multi-match`);
}
throw err;
}
// Query merge order lowest priority to highest
// 1. initial URL query values
// 2. path segment values
// 3. destination specified query values
parsedDestination.query = {
...query,
...parsedDestination.query
};
return {
newUrl,
parsedDestination
};
}
//# sourceMappingURL=prepare-destination.js.map