prepare-destination.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.matchHas = matchHas;
  6. exports.compileNonPath = compileNonPath;
  7. exports.prepareDestination = prepareDestination;
  8. var _extends = require("@swc/helpers/lib/_extends.js").default;
  9. var _pathToRegexp = require("next/dist/compiled/path-to-regexp");
  10. var _escapeRegexp = require("../../escape-regexp");
  11. var _parseUrl = require("./parse-url");
  12. /**
  13. * Ensure only a-zA-Z are used for param names for proper interpolating
  14. * with path-to-regexp
  15. */ function getSafeParamName(paramName) {
  16. let newParamName = '';
  17. for(let i = 0; i < paramName.length; i++){
  18. const charCode = paramName.charCodeAt(i);
  19. if (charCode > 64 && charCode < 91 || charCode > 96 && charCode < 123 // a-z
  20. ) {
  21. newParamName += paramName[i];
  22. }
  23. }
  24. return newParamName;
  25. }
  26. function escapeSegment(str, segmentName) {
  27. return str.replace(new RegExp(`:${(0, _escapeRegexp).escapeStringRegexp(segmentName)}`, 'g'), `__ESC_COLON_${segmentName}`);
  28. }
  29. function unescapeSegments(str) {
  30. return str.replace(/__ESC_COLON_/gi, ':');
  31. }
  32. function matchHas(req, query, has = [], missing = []) {
  33. const params = {};
  34. const hasMatch = (hasItem)=>{
  35. let value;
  36. let key = hasItem.key;
  37. switch(hasItem.type){
  38. case 'header':
  39. {
  40. key = key.toLowerCase();
  41. value = req.headers[key];
  42. break;
  43. }
  44. case 'cookie':
  45. {
  46. value = req.cookies[hasItem.key];
  47. break;
  48. }
  49. case 'query':
  50. {
  51. value = query[key];
  52. break;
  53. }
  54. case 'host':
  55. {
  56. const { host } = (req == null ? void 0 : req.headers) || {};
  57. // remove port from host if present
  58. const hostname = host == null ? void 0 : host.split(':')[0].toLowerCase();
  59. value = hostname;
  60. break;
  61. }
  62. default:
  63. {
  64. break;
  65. }
  66. }
  67. if (!hasItem.value && value) {
  68. params[getSafeParamName(key)] = value;
  69. return true;
  70. } else if (value) {
  71. const matcher = new RegExp(`^${hasItem.value}$`);
  72. const matches = Array.isArray(value) ? value.slice(-1)[0].match(matcher) : value.match(matcher);
  73. if (matches) {
  74. if (Array.isArray(matches)) {
  75. if (matches.groups) {
  76. Object.keys(matches.groups).forEach((groupKey)=>{
  77. params[groupKey] = matches.groups[groupKey];
  78. });
  79. } else if (hasItem.type === 'host' && matches[0]) {
  80. params.host = matches[0];
  81. }
  82. }
  83. return true;
  84. }
  85. }
  86. return false;
  87. };
  88. const allMatch = has.every((item)=>hasMatch(item)) && !missing.some((item)=>hasMatch(item));
  89. if (allMatch) {
  90. return params;
  91. }
  92. return false;
  93. }
  94. function compileNonPath(value, params) {
  95. if (!value.includes(':')) {
  96. return value;
  97. }
  98. for (const key of Object.keys(params)){
  99. if (value.includes(`:${key}`)) {
  100. 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}`);
  101. }
  102. }
  103. 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, '*');
  104. // the value needs to start with a forward-slash to be compiled
  105. // correctly
  106. return (0, _pathToRegexp).compile(`/${value}`, {
  107. validate: false
  108. })(params).slice(1);
  109. }
  110. function prepareDestination(args) {
  111. const query = Object.assign({}, args.query);
  112. delete query.__nextLocale;
  113. delete query.__nextDefaultLocale;
  114. delete query.__nextDataReq;
  115. let escapedDestination = args.destination;
  116. for (const param of Object.keys(_extends({}, args.params, query))){
  117. escapedDestination = escapeSegment(escapedDestination, param);
  118. }
  119. const parsedDestination = (0, _parseUrl).parseUrl(escapedDestination);
  120. const destQuery = parsedDestination.query;
  121. const destPath = unescapeSegments(`${parsedDestination.pathname}${parsedDestination.hash || ''}`);
  122. const destHostname = unescapeSegments(parsedDestination.hostname || '');
  123. const destPathParamKeys = [];
  124. const destHostnameParamKeys = [];
  125. (0, _pathToRegexp).pathToRegexp(destPath, destPathParamKeys);
  126. (0, _pathToRegexp).pathToRegexp(destHostname, destHostnameParamKeys);
  127. const destParams = [];
  128. destPathParamKeys.forEach((key)=>destParams.push(key.name));
  129. destHostnameParamKeys.forEach((key)=>destParams.push(key.name));
  130. const destPathCompiler = (0, _pathToRegexp).compile(destPath, // we don't validate while compiling the destination since we should
  131. // have already validated before we got to this point and validating
  132. // breaks compiling destinations with named pattern params from the source
  133. // e.g. /something:hello(.*) -> /another/:hello is broken with validation
  134. // since compile validation is meant for reversing and not for inserting
  135. // params from a separate path-regex into another
  136. {
  137. validate: false
  138. });
  139. const destHostnameCompiler = (0, _pathToRegexp).compile(destHostname, {
  140. validate: false
  141. });
  142. // update any params in query values
  143. for (const [key1, strOrArray] of Object.entries(destQuery)){
  144. // the value needs to start with a forward-slash to be compiled
  145. // correctly
  146. if (Array.isArray(strOrArray)) {
  147. destQuery[key1] = strOrArray.map((value)=>compileNonPath(unescapeSegments(value), args.params));
  148. } else {
  149. destQuery[key1] = compileNonPath(unescapeSegments(strOrArray), args.params);
  150. }
  151. }
  152. // add path params to query if it's not a redirect and not
  153. // already defined in destination query or path
  154. let paramKeys = Object.keys(args.params).filter((name)=>name !== 'nextInternalLocale');
  155. if (args.appendParamsToQuery && !paramKeys.some((key)=>destParams.includes(key))) {
  156. for (const key of paramKeys){
  157. if (!(key in destQuery)) {
  158. destQuery[key] = args.params[key];
  159. }
  160. }
  161. }
  162. let newUrl;
  163. try {
  164. newUrl = destPathCompiler(args.params);
  165. const [pathname, hash] = newUrl.split('#');
  166. parsedDestination.hostname = destHostnameCompiler(args.params);
  167. parsedDestination.pathname = pathname;
  168. parsedDestination.hash = `${hash ? '#' : ''}${hash || ''}`;
  169. delete parsedDestination.search;
  170. } catch (err) {
  171. if (err.message.match(/Expected .*? to not repeat, but got an array/)) {
  172. 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`);
  173. }
  174. throw err;
  175. }
  176. // Query merge order lowest priority to highest
  177. // 1. initial URL query values
  178. // 2. path segment values
  179. // 3. destination specified query values
  180. parsedDestination.query = _extends({}, query, parsedDestination.query);
  181. return {
  182. newUrl,
  183. destQuery,
  184. parsedDestination
  185. };
  186. }
  187. //# sourceMappingURL=prepare-destination.js.map