utils.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.interpolateDynamicPath = interpolateDynamicPath;
  6. exports.getUtils = getUtils;
  7. exports.vercelHeader = void 0;
  8. var _url = require("url");
  9. var _querystring = require("querystring");
  10. var _normalizeLocalePath = require("../../../../shared/lib/i18n/normalize-locale-path");
  11. var _pathMatch = require("../../../../shared/lib/router/utils/path-match");
  12. var _routeRegex = require("../../../../shared/lib/router/utils/route-regex");
  13. var _routeMatcher = require("../../../../shared/lib/router/utils/route-matcher");
  14. var _prepareDestination = require("../../../../shared/lib/router/utils/prepare-destination");
  15. var _acceptHeader = require("../../../../server/accept-header");
  16. var _detectLocaleCookie = require("../../../../shared/lib/i18n/detect-locale-cookie");
  17. var _detectDomainLocale = require("../../../../shared/lib/i18n/detect-domain-locale");
  18. var _denormalizePagePath = require("../../../../shared/lib/page-path/denormalize-page-path");
  19. var _cookie = _interopRequireDefault(require("next/dist/compiled/cookie"));
  20. var _constants = require("../../../../shared/lib/constants");
  21. var _requestMeta = require("../../../../server/request-meta");
  22. var _removeTrailingSlash = require("../../../../shared/lib/router/utils/remove-trailing-slash");
  23. function _interopRequireDefault(obj) {
  24. return obj && obj.__esModule ? obj : {
  25. default: obj
  26. };
  27. }
  28. const vercelHeader = "x-vercel-id";
  29. exports.vercelHeader = vercelHeader;
  30. function interpolateDynamicPath(pathname, params, defaultRouteRegex) {
  31. if (!defaultRouteRegex) return pathname;
  32. for (const param of Object.keys(defaultRouteRegex.groups)){
  33. const { optional , repeat } = defaultRouteRegex.groups[param];
  34. let builtParam = `[${repeat ? "..." : ""}${param}]`;
  35. if (optional) {
  36. builtParam = `[${builtParam}]`;
  37. }
  38. const paramIdx = pathname.indexOf(builtParam);
  39. if (paramIdx > -1) {
  40. let paramValue;
  41. if (Array.isArray(params[param])) {
  42. paramValue = params[param].map((v)=>v && encodeURIComponent(v)).join("/");
  43. } else {
  44. paramValue = params[param] && encodeURIComponent(params[param]);
  45. }
  46. pathname = pathname.slice(0, paramIdx) + (paramValue || "") + pathname.slice(paramIdx + builtParam.length);
  47. }
  48. }
  49. return pathname;
  50. }
  51. function getUtils({ page , i18n , basePath , rewrites , pageIsDynamic , trailingSlash }) {
  52. let defaultRouteRegex;
  53. let dynamicRouteMatcher;
  54. let defaultRouteMatches;
  55. if (pageIsDynamic) {
  56. defaultRouteRegex = (0, _routeRegex).getNamedRouteRegex(page);
  57. dynamicRouteMatcher = (0, _routeMatcher).getRouteMatcher(defaultRouteRegex);
  58. defaultRouteMatches = dynamicRouteMatcher(page);
  59. }
  60. function handleRewrites(req, parsedUrl) {
  61. const rewriteParams = {};
  62. let fsPathname = parsedUrl.pathname;
  63. const matchesPage = ()=>{
  64. const fsPathnameNoSlash = (0, _removeTrailingSlash).removeTrailingSlash(fsPathname || "");
  65. return fsPathnameNoSlash === (0, _removeTrailingSlash).removeTrailingSlash(page) || (dynamicRouteMatcher == null ? void 0 : dynamicRouteMatcher(fsPathnameNoSlash));
  66. };
  67. const checkRewrite = (rewrite)=>{
  68. const matcher = (0, _pathMatch).getPathMatch(rewrite.source + (trailingSlash ? "(/)?" : ""), {
  69. removeUnnamedParams: true,
  70. strict: true
  71. });
  72. let params = matcher(parsedUrl.pathname);
  73. if ((rewrite.has || rewrite.missing) && params) {
  74. const hasParams = (0, _prepareDestination).matchHas(req, parsedUrl.query, rewrite.has, rewrite.missing);
  75. if (hasParams) {
  76. Object.assign(params, hasParams);
  77. } else {
  78. params = false;
  79. }
  80. }
  81. if (params) {
  82. const { parsedDestination , destQuery } = (0, _prepareDestination).prepareDestination({
  83. appendParamsToQuery: true,
  84. destination: rewrite.destination,
  85. params: params,
  86. query: parsedUrl.query
  87. });
  88. // if the rewrite destination is external break rewrite chain
  89. if (parsedDestination.protocol) {
  90. return true;
  91. }
  92. Object.assign(rewriteParams, destQuery, params);
  93. Object.assign(parsedUrl.query, parsedDestination.query);
  94. delete parsedDestination.query;
  95. Object.assign(parsedUrl, parsedDestination);
  96. fsPathname = parsedUrl.pathname;
  97. if (basePath) {
  98. fsPathname = fsPathname.replace(new RegExp(`^${basePath}`), "") || "/";
  99. }
  100. if (i18n) {
  101. const destLocalePathResult = (0, _normalizeLocalePath).normalizeLocalePath(fsPathname, i18n.locales);
  102. fsPathname = destLocalePathResult.pathname;
  103. parsedUrl.query.nextInternalLocale = destLocalePathResult.detectedLocale || params.nextInternalLocale;
  104. }
  105. if (fsPathname === page) {
  106. return true;
  107. }
  108. if (pageIsDynamic && dynamicRouteMatcher) {
  109. const dynamicParams = dynamicRouteMatcher(fsPathname);
  110. if (dynamicParams) {
  111. parsedUrl.query = {
  112. ...parsedUrl.query,
  113. ...dynamicParams
  114. };
  115. return true;
  116. }
  117. }
  118. }
  119. return false;
  120. };
  121. for (const rewrite1 of rewrites.beforeFiles || []){
  122. checkRewrite(rewrite1);
  123. }
  124. if (fsPathname !== page) {
  125. let finished = false;
  126. for (const rewrite of rewrites.afterFiles || []){
  127. finished = checkRewrite(rewrite);
  128. if (finished) break;
  129. }
  130. if (!finished && !matchesPage()) {
  131. for (const rewrite of rewrites.fallback || []){
  132. finished = checkRewrite(rewrite);
  133. if (finished) break;
  134. }
  135. }
  136. }
  137. return rewriteParams;
  138. }
  139. function handleBasePath(req, parsedUrl) {
  140. // always strip the basePath if configured since it is required
  141. req.url = req.url.replace(new RegExp(`^${basePath}`), "") || "/";
  142. parsedUrl.pathname = parsedUrl.pathname.replace(new RegExp(`^${basePath}`), "") || "/";
  143. }
  144. function getParamsFromRouteMatches(req, renderOpts, detectedLocale) {
  145. return (0, _routeMatcher).getRouteMatcher(function() {
  146. const { groups , routeKeys } = defaultRouteRegex;
  147. return {
  148. re: {
  149. // Simulate a RegExp match from the \`req.url\` input
  150. exec: (str)=>{
  151. const obj = (0, _querystring).parse(str);
  152. const matchesHasLocale = i18n && detectedLocale && obj["1"] === detectedLocale;
  153. // favor named matches if available
  154. const routeKeyNames = Object.keys(routeKeys || {});
  155. const filterLocaleItem = (val)=>{
  156. if (i18n) {
  157. // locale items can be included in route-matches
  158. // for fallback SSG pages so ensure they are
  159. // filtered
  160. const isCatchAll = Array.isArray(val);
  161. const _val = isCatchAll ? val[0] : val;
  162. if (typeof _val === "string" && i18n.locales.some((item)=>{
  163. if (item.toLowerCase() === _val.toLowerCase()) {
  164. detectedLocale = item;
  165. renderOpts.locale = detectedLocale;
  166. return true;
  167. }
  168. return false;
  169. })) {
  170. // remove the locale item from the match
  171. if (isCatchAll) {
  172. val.splice(0, 1);
  173. }
  174. // the value is only a locale item and
  175. // shouldn't be added
  176. return isCatchAll ? val.length === 0 : true;
  177. }
  178. }
  179. return false;
  180. };
  181. if (routeKeyNames.every((name)=>obj[name])) {
  182. return routeKeyNames.reduce((prev, keyName)=>{
  183. const paramName = routeKeys == null ? void 0 : routeKeys[keyName];
  184. if (paramName && !filterLocaleItem(obj[keyName])) {
  185. prev[groups[paramName].pos] = obj[keyName];
  186. }
  187. return prev;
  188. }, {});
  189. }
  190. return Object.keys(obj).reduce((prev, key)=>{
  191. if (!filterLocaleItem(obj[key])) {
  192. let normalizedKey = key;
  193. if (matchesHasLocale) {
  194. normalizedKey = parseInt(key, 10) - 1 + "";
  195. }
  196. return Object.assign(prev, {
  197. [normalizedKey]: obj[key]
  198. });
  199. }
  200. return prev;
  201. }, {});
  202. }
  203. },
  204. groups
  205. };
  206. }())(req.headers["x-now-route-matches"]);
  207. }
  208. function normalizeVercelUrl(req, trustQuery, paramKeys) {
  209. // make sure to normalize req.url on Vercel to strip dynamic params
  210. // from the query which are added during routing
  211. if (pageIsDynamic && trustQuery && defaultRouteRegex) {
  212. const _parsedUrl = (0, _url).parse(req.url, true);
  213. delete _parsedUrl.search;
  214. for (const param of paramKeys || Object.keys(defaultRouteRegex.groups)){
  215. delete _parsedUrl.query[param];
  216. }
  217. req.url = (0, _url).format(_parsedUrl);
  218. }
  219. }
  220. function normalizeDynamicRouteParams(params, ignoreOptional) {
  221. let hasValidParams = true;
  222. if (!defaultRouteRegex) return {
  223. params,
  224. hasValidParams: false
  225. };
  226. params = Object.keys(defaultRouteRegex.groups).reduce((prev, key)=>{
  227. let value = params[key];
  228. // if the value matches the default value we can't rely
  229. // on the parsed params, this is used to signal if we need
  230. // to parse x-now-route-matches or not
  231. const defaultValue = defaultRouteMatches[key];
  232. const isOptional = defaultRouteRegex.groups[key].optional;
  233. const isDefaultValue = Array.isArray(defaultValue) ? defaultValue.some((defaultVal)=>{
  234. return Array.isArray(value) ? value.some((val)=>val.includes(defaultVal)) : value == null ? void 0 : value.includes(defaultVal);
  235. }) : value == null ? void 0 : value.includes(defaultValue);
  236. if (isDefaultValue || typeof value === "undefined" && !(isOptional && ignoreOptional)) {
  237. hasValidParams = false;
  238. }
  239. // non-provided optional values should be undefined so normalize
  240. // them to undefined
  241. if (isOptional && (!value || Array.isArray(value) && value.length === 1 && // fallback optional catch-all SSG pages have
  242. // [[...paramName]] for the root path on Vercel
  243. (value[0] === "index" || value[0] === `[[...${key}]]`))) {
  244. value = undefined;
  245. delete params[key];
  246. }
  247. // query values from the proxy aren't already split into arrays
  248. // so make sure to normalize catch-all values
  249. if (value && typeof value === "string" && defaultRouteRegex.groups[key].repeat) {
  250. value = value.split("/");
  251. }
  252. if (value) {
  253. prev[key] = value;
  254. }
  255. return prev;
  256. }, {});
  257. return {
  258. params,
  259. hasValidParams
  260. };
  261. }
  262. function handleLocale(req, res, parsedUrl, routeNoAssetPath, shouldNotRedirect) {
  263. if (!i18n) return;
  264. const pathname = parsedUrl.pathname || "/";
  265. let defaultLocale = i18n.defaultLocale;
  266. let detectedLocale = (0, _detectLocaleCookie).detectLocaleCookie(req, i18n.locales);
  267. let acceptPreferredLocale;
  268. try {
  269. acceptPreferredLocale = i18n.localeDetection !== false ? (0, _acceptHeader).acceptLanguage(req.headers["accept-language"], i18n.locales) : detectedLocale;
  270. } catch (_) {
  271. acceptPreferredLocale = detectedLocale;
  272. }
  273. const { host } = req.headers || {};
  274. // remove port from host and remove port if present
  275. const hostname = host && host.split(":")[0].toLowerCase();
  276. const detectedDomain = (0, _detectDomainLocale).detectDomainLocale(i18n.domains, hostname);
  277. if (detectedDomain) {
  278. defaultLocale = detectedDomain.defaultLocale;
  279. detectedLocale = defaultLocale;
  280. (0, _requestMeta).addRequestMeta(req, "__nextIsLocaleDomain", true);
  281. }
  282. // if not domain specific locale use accept-language preferred
  283. detectedLocale = detectedLocale || acceptPreferredLocale;
  284. let localeDomainRedirect;
  285. const localePathResult = (0, _normalizeLocalePath).normalizeLocalePath(pathname, i18n.locales);
  286. routeNoAssetPath = (0, _normalizeLocalePath).normalizeLocalePath(routeNoAssetPath, i18n.locales).pathname;
  287. if (localePathResult.detectedLocale) {
  288. detectedLocale = localePathResult.detectedLocale;
  289. req.url = (0, _url).format({
  290. ...parsedUrl,
  291. pathname: localePathResult.pathname
  292. });
  293. (0, _requestMeta).addRequestMeta(req, "__nextStrippedLocale", true);
  294. parsedUrl.pathname = localePathResult.pathname;
  295. }
  296. // If a detected locale is a domain specific locale and we aren't already
  297. // on that domain and path prefix redirect to it to prevent duplicate
  298. // content from multiple domains
  299. if (detectedDomain) {
  300. const localeToCheck = localePathResult.detectedLocale ? detectedLocale : acceptPreferredLocale;
  301. const matchedDomain = (0, _detectDomainLocale).detectDomainLocale(i18n.domains, undefined, localeToCheck);
  302. if (matchedDomain && matchedDomain.domain !== detectedDomain.domain) {
  303. localeDomainRedirect = `http${matchedDomain.http ? "" : "s"}://${matchedDomain.domain}/${localeToCheck === matchedDomain.defaultLocale ? "" : localeToCheck}`;
  304. }
  305. }
  306. const denormalizedPagePath = (0, _denormalizePagePath).denormalizePagePath(pathname);
  307. const detectedDefaultLocale = !detectedLocale || detectedLocale.toLowerCase() === defaultLocale.toLowerCase();
  308. const shouldStripDefaultLocale = false;
  309. // detectedDefaultLocale &&
  310. // denormalizedPagePath.toLowerCase() === \`/\${i18n.defaultLocale.toLowerCase()}\`
  311. const shouldAddLocalePrefix = !detectedDefaultLocale && denormalizedPagePath === "/";
  312. detectedLocale = detectedLocale || i18n.defaultLocale;
  313. if (!shouldNotRedirect && !req.headers[vercelHeader] && i18n.localeDetection !== false && (localeDomainRedirect || shouldAddLocalePrefix || shouldStripDefaultLocale)) {
  314. // set the NEXT_LOCALE cookie when a user visits the default locale
  315. // with the locale prefix so that they aren't redirected back to
  316. // their accept-language preferred locale
  317. if (shouldStripDefaultLocale && acceptPreferredLocale !== defaultLocale) {
  318. const previous = res.getHeader("set-cookie");
  319. res.setHeader("set-cookie", [
  320. ...typeof previous === "string" ? [
  321. previous
  322. ] : Array.isArray(previous) ? previous : [],
  323. _cookie.default.serialize("NEXT_LOCALE", defaultLocale, {
  324. httpOnly: true,
  325. path: "/"
  326. }),
  327. ]);
  328. }
  329. res.setHeader("Location", (0, _url).format({
  330. // make sure to include any query values when redirecting
  331. ...parsedUrl,
  332. pathname: localeDomainRedirect ? localeDomainRedirect : shouldStripDefaultLocale ? basePath || "/" : `${basePath}/${detectedLocale}`
  333. }));
  334. res.statusCode = _constants.TEMPORARY_REDIRECT_STATUS;
  335. res.end();
  336. return;
  337. }
  338. detectedLocale = localePathResult.detectedLocale || detectedDomain && detectedDomain.defaultLocale || defaultLocale;
  339. return {
  340. defaultLocale,
  341. detectedLocale,
  342. routeNoAssetPath
  343. };
  344. }
  345. return {
  346. handleLocale,
  347. handleRewrites,
  348. handleBasePath,
  349. defaultRouteRegex,
  350. normalizeVercelUrl,
  351. dynamicRouteMatcher,
  352. defaultRouteMatches,
  353. getParamsFromRouteMatches,
  354. normalizeDynamicRouteParams,
  355. interpolateDynamicPath: (pathname, params)=>interpolateDynamicPath(pathname, params, defaultRouteRegex)
  356. };
  357. }
  358. //# sourceMappingURL=utils.js.map