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,
};
}