@@ -312,15 +312,52 @@ async function projectResponse(
312312 }
313313}
314314
315- function matchReturnedPath ( candidate : string [ ] , expected : readonly OracleEpmPathPart [ ] ) : void {
316- if ( candidate . length !== expected . length ) throw oracleEpmLocalError ( 'invalid_input' )
317- for ( let index = 0 ; index < expected . length ; index += 1 ) {
318- let decoded : string
315+ function decodeReturnedPathSegment ( encoded : string ) : string {
316+ let decoded : string
317+ try {
318+ decoded = decodeURIComponent ( encoded )
319+ } catch {
320+ throw oracleEpmLocalError ( 'invalid_input' )
321+ }
322+ let safetyValue = decoded
323+ for ( let depth = 0 ; depth < 4 && / % [ 0 - 9 A - F a - f ] { 2 } / . test ( safetyValue ) ; depth += 1 ) {
319324 try {
320- decoded = decodeURIComponent ( candidate [ index ] )
325+ safetyValue = decodeURIComponent ( safetyValue )
321326 } catch {
322327 throw oracleEpmLocalError ( 'invalid_input' )
323328 }
329+ }
330+ if (
331+ ! decoded ||
332+ / % [ 0 - 9 A - F a - f ] { 2 } / . test ( safetyValue ) ||
333+ safetyValue === '.' ||
334+ safetyValue === '..' ||
335+ / [ / \\ \u0000 - \u001f \u007f ] / . test ( safetyValue ) ||
336+ MALFORMED_UTF16 . test ( decoded )
337+ ) {
338+ throw oracleEpmLocalError ( 'invalid_input' )
339+ }
340+ return decoded
341+ }
342+
343+ function rawReturnedPathSegments ( href : string ) : string [ ] {
344+ const match = / ^ h t t p s : \/ \/ [ ^ / ? # ] * ( \/ [ ^ ? # ] * ) ? (?: \? [ ^ # ] * ) ? (?: # .* ) ? $ / i. exec ( href )
345+ if ( ! match ) throw oracleEpmLocalError ( 'invalid_input' )
346+ const rawPath = match [ 1 ] ?? ''
347+ if ( rawPath . includes ( '\\' ) ) throw oracleEpmLocalError ( 'invalid_input' )
348+ if ( ! rawPath ) return [ ]
349+ const segments = rawPath . slice ( 1 ) . split ( '/' )
350+ if ( segments . some ( ( segment ) => ! segment ) ) throw oracleEpmLocalError ( 'invalid_input' )
351+ return segments . map ( decodeReturnedPathSegment )
352+ }
353+
354+ function matchReturnedPath (
355+ candidate : readonly string [ ] ,
356+ expected : readonly OracleEpmPathPart [ ]
357+ ) : void {
358+ if ( candidate . length !== expected . length ) throw oracleEpmLocalError ( 'invalid_input' )
359+ for ( let index = 0 ; index < expected . length ; index += 1 ) {
360+ const decoded = candidate [ index ]
324361 const part = expected [ index ]
325362 if ( part . kind === 'literal' ) {
326363 if ( decoded !== part . value ) throw oracleEpmLocalError ( 'invalid_input' )
@@ -468,9 +505,11 @@ export function createOracleEpmClient(input: {
468505 typeof link . href !== 'string' ||
469506 link . href . length > 8_192 ||
470507 FORBIDDEN_LINK_TEXT . test ( link . href ) ||
471- MALFORMED_UTF16 . test ( link . href )
508+ MALFORMED_UTF16 . test ( link . href ) ||
509+ link . href . includes ( '#' )
472510 )
473511 throw oracleEpmLocalError ( 'invalid_input' )
512+ const candidate = rawReturnedPathSegments ( link . href )
474513 let url : URL
475514 try {
476515 url = new URL ( link . href )
@@ -480,17 +519,9 @@ export function createOracleEpmClient(input: {
480519 if ( url . origin !== destinationData . origin || url . username || url . password || url . hash )
481520 throw oracleEpmLocalError ( 'invalid_input' )
482521 const route = getOracleEpmRouteSpace ( policy . routeSpace )
483- const candidate = url . pathname . split ( '/' ) . filter ( Boolean )
484522 const prefix = [ ...destinationData . baseSegments , ...route . context , policy . version ]
485- const prefixMatches = ( expected : readonly string [ ] ) : boolean => {
486- try {
487- return expected . every (
488- ( part , index ) => decodeURIComponent ( candidate [ index ] ?? '' ) === part
489- )
490- } catch {
491- return false
492- }
493- }
523+ const prefixMatches = ( expected : readonly string [ ] ) : boolean =>
524+ expected . every ( ( part , index ) => candidate [ index ] === part )
494525 if ( policy . preserveGatewayBasePath && ! prefixMatches ( prefix ) )
495526 throw oracleEpmLocalError ( 'invalid_input' )
496527 const pathStart = policy . preserveGatewayBasePath ? prefix . length : route . context . length + 1
0 commit comments