src/msha/routes-engine/route-processor.ts (115 lines of code) (raw):

import chalk from "chalk"; import type http from "node:http"; import { logger } from "../../core/utils/logger.js"; import { globToRegExp, isValidGlobExpression } from "../../core/utils/glob.js"; import { AUTH_STATUS, CUSTOM_URL_SCHEME, SWA_CLI_APP_PROTOCOL } from "../../core/constants.js"; import { getIndexHtml } from "./rules/routes.js"; export function doesRequestPathMatchRoute( requestPath: string, routeRule: SWAConfigFileRoute | undefined, requestMethod: string | undefined | null, methods: string[] | undefined | null, authStatus: number ) { logger.silly(`check if request match route`); const route = routeRule?.route; const hasRouteRuleHasWildcard = route?.includes("*"); logger.silly(` - route: ${chalk.yellow(route)}`); logger.silly(` - wildcard: ${chalk.yellow(hasRouteRuleHasWildcard)}`); // Do not match auth requests besides /.auth/login/<idp> // /.auth/login/<idp> must match a non wildcard rule // no allowed role can be listed for a rule with route /.auth/login/<idp> if ( (authStatus != AUTH_STATUS.NoAuth && authStatus != AUTH_STATUS.HostNameAuthLogin) || (authStatus == AUTH_STATUS.HostNameAuthLogin && (hasRouteRuleHasWildcard || routeRule?.allowedRoles?.length)) ) { logger.silly(` - authStatus: ${chalk.yellow(authStatus)}`); logger.silly(` - allowedRoles: ${chalk.yellow(routeRule?.allowedRoles)}`); logger.silly(` - match: ${chalk.yellow(false)}`); return false; } // request method must match allowed methods if listed if (methods != null && !methods.includes(requestMethod!)) { logger.silly(` - methods: ${chalk.yellow(methods)}`); logger.silly(` - requestMethod: ${chalk.yellow(requestMethod)}`); logger.silly(` - match: ${chalk.yellow(false)}`); return false; } if (route === requestPath || (hasRouteRuleHasWildcard && doesRequestPathMatchWildcardRoute(requestPath, route))) { logger.silly(` - doesRequestPathMatchWildcardRoute: ${chalk.yellow(true)}`); return true; } // Since this is a file request, return now, since we are trying to get a match by appending /index.html doesn't apply here if (!route) { logger.silly(` - route: ${chalk.yellow(route || "<empty>")}`); logger.silly(` - match: ${chalk.yellow(false)}`); return false; } // If the request hasn't already matched the route, and the request is a non-file path, // try adding /index.html to the path to see if it then matches. This is especially handy // to match a request to the /{customPath}/* route const alternateRequestPath = getIndexHtml(requestPath); logger.silly(` - alternateRequestPath: ${chalk.yellow(alternateRequestPath)}`); return ( routeRule?.route === alternateRequestPath || (hasRouteRuleHasWildcard && doesRequestPathMatchWildcardRoute(alternateRequestPath, routeRule?.route)) ); } export function doesRequestPathMatchLegacyRoute( requestPath: string, routeRule: SWAConfigFileRoute | undefined, isAuthRequest: boolean, isFileRequest: boolean ) { const hasWildcard = routeRule?.route.includes("*"); if (routeRule?.route === requestPath || (!isAuthRequest && hasWildcard)) { return true; } // since this is a file request, don't perform the wildcard matching check if (isFileRequest) { return false; } // if the request hasn't already matched the route, and the request is a non-file path, // try adding /index.html to the path to see if it then matches. This is especially handy // to match a request to the /{customPath}/* route const alternateRequestPath = getIndexHtml(requestPath); return routeRule?.route === alternateRequestPath || (!isAuthRequest && hasWildcard); } function doesRequestPathMatchWildcardRoute(requestPath: string, requestPathFileWithWildcard: string | undefined) { logger.silly(`checking wildcard route`); logger.silly(` - glob: ${chalk.yellow(requestPathFileWithWildcard)}`); const pathBeforeWildcard = requestPathFileWithWildcard?.substr(0, requestPathFileWithWildcard?.indexOf("*")); logger.silly(` - pathBeforeWildcard: ${chalk.yellow(pathBeforeWildcard || "<empty>")}`); // before processing regexp which might be expensive // let's check first if both path and rule start with the same substring if (pathBeforeWildcard && requestPath.startsWith(pathBeforeWildcard) === false) { logger.silly(` - base path doesn't match. Exit`); return false; } // also, let's check if the route rule doesn't contains a wildcard in the middle of the path if (isValidGlobExpression(requestPathFileWithWildcard) === false) { logger.silly(` - route rule contains a wildcard in the middle of the path. Exit`); return false; } try { logger.silly(` - route regexp: ${chalk.yellow(requestPathFileWithWildcard)}`); // we don't support full globs in the config file. // add this little utility to convert a wildcard into a valid glob pattern const regexp = new RegExp(`^${globToRegExp(requestPathFileWithWildcard)}$`); logger.silly(` - regexp: ${chalk.yellow(regexp)}`); const isMatch = regexp.test(requestPath); logger.silly(` - isMatch: ${chalk.yellow(isMatch)}`); return isMatch; } catch (error) { logger.silly(` - ERROR: IGNORING REGEXP!!!`); logger.silly(` - read: ${chalk.yellow("https://learn.microsoft.com/en-us/azure/static-web-apps/configuration#wildcards")}`); logger.silly(` - error: ${chalk.yellow(error)}`); return false; } } export function isCustomUrl(req: http.IncomingMessage) { return !!req.url?.startsWith(CUSTOM_URL_SCHEME); } export function parseQueryParams(req: http.IncomingMessage, matchingRouteRule: SWAConfigFileRoute | undefined) { const urlPathnameWithQueryParams = matchingRouteRule?.rewrite || req.url; const url = new URL(urlPathnameWithQueryParams!, `${SWA_CLI_APP_PROTOCOL}://${req?.headers?.host}`); const urlQueryString = url.searchParams.toString(); const urlPathnameWithoutQueryParams = url.pathname; if (urlQueryString !== "") { logger.silly(` - url: ${chalk.yellow(url)}`); logger.silly(` - urlQueryString: ${chalk.yellow(urlQueryString)}`); url.searchParams.forEach((value, key) => { logger.silly(` - ${key}: ${chalk.yellow(value || "<undefined>")}`); }); logger.silly(` - urlPathnameWithQueryParams: ${chalk.yellow(urlPathnameWithQueryParams)}`); logger.silly(` - urlPathnameWithoutQueryParams: ${chalk.yellow(urlPathnameWithoutQueryParams)}`); } return { url, urlPathnameWithoutQueryParams, urlPathnameWithQueryParams, }; }