@@ -117,12 +117,16 @@ async function fastifyStatic (fastify, opts) {
117117 throw new TypeError ( '"wildcard" option must be a boolean' )
118118 }
119119 if ( opts . wildcard === undefined || opts . wildcard === true ) {
120+ let matchRoutePrefix
121+
120122 fastify . route ( {
121123 ...routeOpts ,
122124 method : [ 'HEAD' , 'GET' ] ,
123125 path : prefix + '*' ,
124126 handler ( req , reply ) {
125- const pathname = getPathnameForSend ( req . raw . url , req . routeOptions . url )
127+ matchRoutePrefix ??= createRoutePrefixMatcher ( req . routeOptions . url )
128+
129+ const pathname = getPathnameForSend ( req . raw . url , matchRoutePrefix )
126130 if ( ! pathname ) {
127131 return reply . callNotFound ( )
128132 }
@@ -569,23 +573,113 @@ function getEncodingHeader (headers, checked) {
569573}
570574
571575/**
572- * @param {string } url
576+ * @param {string } routePrefix
577+ * @returns {Array<string|undefined> }
578+ */
579+ function createRoutePrefixTokens ( routePrefix ) {
580+ const tokens = [ ]
581+ let routeIndex = 0
582+ let segmentStart = 0
583+
584+ while ( routeIndex < routePrefix . length ) {
585+ if ( routePrefix [ routeIndex ] !== ':' ) {
586+ routeIndex ++
587+ continue
588+ }
589+
590+ if ( segmentStart !== routeIndex ) {
591+ tokens . push ( routePrefix . slice ( segmentStart , routeIndex ) )
592+ }
593+
594+ routeIndex ++
595+ while ( routeIndex < routePrefix . length && routePrefix [ routeIndex ] !== '/' ) {
596+ routeIndex ++
597+ }
598+
599+ tokens . push ( undefined )
600+ segmentStart = routeIndex
601+ }
602+
603+ if ( segmentStart !== routePrefix . length ) {
604+ tokens . push ( routePrefix . slice ( segmentStart ) )
605+ }
606+
607+ return tokens
608+ }
609+
610+ /**
611+ * @param {string } pathname
612+ * @param {number } pathnameEnd
613+ * @param {Array<string|undefined> } tokens
614+ * @returns {number|undefined }
615+ */
616+ function getRoutePrefixMatchLength ( pathname , pathnameEnd , tokens ) {
617+ let pathnameIndex = 0
618+
619+ for ( const token of tokens ) {
620+ if ( token === undefined ) {
621+ const segmentStart = pathnameIndex
622+ const slashIndex = pathname . indexOf ( '/' , pathnameIndex )
623+
624+ pathnameIndex = slashIndex === - 1 || slashIndex > pathnameEnd
625+ ? pathnameEnd
626+ : slashIndex
627+
628+ if ( pathnameIndex === segmentStart ) {
629+ return
630+ }
631+
632+ continue
633+ }
634+
635+ const tokenEnd = pathnameIndex + token . length
636+ if ( tokenEnd > pathnameEnd || ! pathname . startsWith ( token , pathnameIndex ) ) {
637+ return
638+ }
639+
640+ pathnameIndex = tokenEnd
641+ }
642+
643+ return pathnameIndex
644+ }
645+
646+ /**
573647 * @param {string } route
648+ * @returns {(pathname: string, pathnameEnd: number) => number|undefined }
649+ */
650+ function createRoutePrefixMatcher ( route ) {
651+ const routePrefix = route . replace ( / \* $ / u, '' )
652+ const routePrefixLength = routePrefix . length
653+
654+ if ( routePrefix === '/' ) {
655+ return ( ) => 0
656+ }
657+
658+ if ( routePrefix . includes ( ':' ) === false ) {
659+ return ( pathname , pathnameEnd ) => routePrefixLength <= pathnameEnd && pathname . startsWith ( routePrefix )
660+ ? routePrefixLength
661+ : undefined
662+ }
663+
664+ const tokens = createRoutePrefixTokens ( routePrefix )
665+ return ( pathname , pathnameEnd ) => getRoutePrefixMatchLength ( pathname , pathnameEnd , tokens )
666+ }
667+
668+ /**
669+ * @param {string } url
670+ * @param {(pathname: string, pathnameEnd: number) => number|undefined } matchRoutePrefix
574671 * @returns {string|undefined }
575672 */
576- function getPathnameForSend ( url , route ) {
673+ function getPathnameForSend ( url , matchRoutePrefix ) {
577674 const questionMark = url . indexOf ( '?' )
578- let pathname = questionMark === - 1 ? url : url . slice ( 0 , questionMark )
579-
580- const routePrefix = route . endsWith ( '*' )
581- ? route . slice ( 0 , - 1 )
582- : route
675+ const pathnameEnd = questionMark === - 1 ? url . length : questionMark
583676
584- if ( routePrefix !== '/' && ! pathname . startsWith ( routePrefix ) ) {
677+ const prefixLength = matchRoutePrefix ( url , pathnameEnd )
678+ if ( prefixLength === undefined ) {
585679 return
586680 }
587681
588- pathname = pathname . slice ( routePrefix . length )
682+ let pathname = url . slice ( prefixLength , pathnameEnd )
589683
590684 if ( pathname === '' ) {
591685 pathname = '/'
0 commit comments