@@ -4,29 +4,29 @@ import type { SSRManifest } from '../app/types.js';
44import { computePathnameFromDomain } from '../i18n/domain.js' ;
55import { AstroIntegrationLogger } from '../logger/core.js' ;
66import { getLogger } from '../logger/manifest-logger.js' ;
7+ import { validateAndDecodePathname } from '../util/pathname.js' ;
78import { matchAllRoutes , matchRoute } from './route-table.js' ;
89
910/**
10- * Decodes a pathname with `decodeURI`, falling back to the raw pathname when it
11- * contains an invalid percent-sequence (e.g. `%C0%AF`, an overlong-UTF-8 encoding of
12- * `/` commonly sent by path-traversal scanners). A raw `decodeURI()` would throw
13- * `URIError: URI malformed`, and because `match()` runs before `render()` that error
14- * escapes the adapter's request handler as an uncaught exception (HTTP 500) that user
15- * middleware can't catch.
11+ * Fully decodes a pathname, falling back to a single decode and then the raw pathname
12+ * when validation fails. Matching runs before `render()`, so it must not throw for
13+ * request input that render-time validation handles.
1614 */
17- function safeDecodeURI ( manifest : SSRManifest , pathname : string ) : string {
15+ function safeDecodePathname ( manifest : SSRManifest , pathname : string ) : string {
1816 try {
19- return decodeURI ( pathname ) ;
17+ return validateAndDecodePathname ( pathname ) ;
2018 } catch ( e : any ) {
21- // Malformed request paths are expected client input (commonly from automated
22- // scanners) rather than a server fault, and this runs per-request on the hot
23- // path. Log at `debug` so it stays diagnosable without flooding error logs.
24- // Allocated lazily — only on the malformed branch — with the same options
25- // and label as the facade's `adapterLogger`.
19+ // Path decoding failures are request input rather than a server fault. Log at
20+ // `debug` so they stay diagnosable without flooding error logs. The logger is
21+ // allocated lazily with the same options and label as the facade's `adapterLogger`.
2622 new AstroIntegrationLogger ( getLogger ( manifest ) . options , manifest . adapterName ) . debug (
2723 e . toString ( ) ,
2824 ) ;
29- return pathname ;
25+ try {
26+ return decodeURI ( pathname ) ;
27+ } catch {
28+ return pathname ;
29+ }
3030 }
3131}
3232
@@ -56,7 +56,8 @@ export function matchRequest(
5656 if ( ! pathname ) {
5757 pathname = prependForwardSlash ( stripRequestBase ( url . pathname , manifest . base ) ) ;
5858 }
59- const routeData = matchRoute ( manifest , safeDecodeURI ( manifest , pathname ) ) ;
59+ pathname = safeDecodePathname ( manifest , pathname ) ;
60+ const routeData = matchRoute ( manifest , pathname ) ;
6061 if ( ! routeData ) return undefined ;
6162 if ( allowPrerenderedRoutes ) {
6263 return routeData ;
@@ -68,7 +69,7 @@ export function matchRequest(
6869 // the same pattern should handle all other URLs.
6970 if ( routeData . prerender ) {
7071 if ( routeData . params . length > 0 ) {
71- const allMatches = matchAllRoutes ( manifest , safeDecodeURI ( manifest , pathname ) ) ;
72+ const allMatches = matchAllRoutes ( manifest , pathname ) ;
7273 return allMatches . find ( ( r ) => ! r . prerender ) ;
7374 }
7475 return undefined ;
0 commit comments