import { IExternalUrlPolicy } from 'src/core/server/types' ; import { CoreService } from 'src/core/types' ; import { IExternalUrl } from './types' ; import { InjectedMetadataSetup } from '../injected_metadata' ; import { Sha256 } from '../utils' ; interface SetupDeps { location : Pick < Location , 'href' > ; injectedMetadata : InjectedMetadataSetup ; } function * getHostHashes ( actualHost ) { yield new Sha256 ( ) . update ( actualHost , 'utf8' ) . digest ( 'hex' ) ; let host = actualHost . substr ( actualHost . indexOf ( '.' ) + 1 ) ; while ( host ) { yield new Sha256 ( ) . update ( host , 'utf8' ) . digest ( 'hex' ) ; if ( host . indexOf ( '.' ) === - 1 ) { break ; } host = host . substr ( host . indexOf ( '.' ) + 1 ) ; } } const isHostMatch = ( actualHost , ruleHostHash ) => { const hostToHash = ! actualHost . includes ( '[' ) && ! actualHost . endsWith ( '.' ) ? `${ actualHost }.` : actualHost ; for ( const hash of getHostHashes ( hostToHash ) ) { if ( hash === ruleHostHash ) { return true ; } } return false ; } ; const isProtocolMatch = ( actualProtocol , ruleProtocol ) => { return normalizeProtocol ( actualProtocol ) === normalizeProtocol ( ruleProtocol ) ; } ; function normalizeProtocol ( protocol ) { return protocol . endsWith ( ':' ) ? protocol . slice ( 0 , - 1 ) . toLowerCase ( ) : protocol . toLowerCase ( ) ; } const createExternalUrlValidation = ( rules , location , serverBasePath ) => { return function validateExternalUrl ( next ) { const base = new URL ( location . href ) ; const url = new URL ( next , base ) ; const isInternalURL = url . origin === base . origin && ( ! serverBasePath || url . pathname . startsWith ( `${ serverBasePath }/` ) ) ; if ( isInternalURL ) { return url ; } let allowed = null ; rules . forEach ( ( rule ) => { const hostMatch = rule . host ? isHostMatch ( url . hostname || '' , rule . host ) : true ; const protocolMatch = rule . protocol ? isProtocolMatch ( url . protocol , rule . protocol ) : true ; const isRuleMatch = hostMatch && protocolMatch ; if ( isRuleMatch && allowed !== false ) { allowed = rule . allow ; } } ) ; return allowed === true ? url : null ; } ; } ; export class ExternalUrlService implements CoreService < IExternalUrl > { setup ( { injectedMetadata , location } ) { const serverBasePath = injectedMetadata . getServerBasePath ( ) ; const { policy } = injectedMetadata . getExternalUrlConfig ( ) ; return { validateUrl : createExternalUrlValidation ( policy , location , serverBasePath ) , } ; } start ( ) { } stop ( ) { } }