@@ -231,38 +231,70 @@ const decodeDiscoveryMethod = Schema.decodeUnknownSync(DiscoveryMethod);
231231const decodeDiscoveryResource = Schema . decodeUnknownSync ( DiscoveryResource ) ;
232232const parseJson = Schema . decodeUnknownEffect ( Schema . fromJsonString ( Schema . Unknown ) ) ;
233233
234- const normalizeDiscoveryUrl = ( discoveryUrl : string ) : string => {
235- const trimmed = discoveryUrl . trim ( ) ;
236- if ( ! URL . canParse ( trimmed ) ) return trimmed ;
237- const parsed = new URL ( trimmed ) ;
238- if ( parsed . pathname !== "/$discovery/rest" ) return trimmed ;
239- const version = parsed . searchParams . get ( "version" ) ?. trim ( ) ;
240- if ( ! version ) return trimmed ;
241- const host = parsed . hostname . toLowerCase ( ) ;
242- if ( ! host . endsWith ( ".googleapis.com" ) ) return trimmed ;
234+ const DISCOVERY_SERVICE_PATH_RE =
235+ / ^ \/ d i s c o v e r y \/ v 1 \/ a p i s \/ ( [ A - Z a - z 0 - 9 . _ - ] + ) \/ ( [ A - Z a - z 0 - 9 . _ - ] + ) \/ r e s t \/ ? $ / ;
236+ const DISCOVERY_VERSION_RE = / ^ [ A - Z a - z 0 - 9 . _ - ] + $ / ;
237+
238+ const serviceFromGoogleApisHost = ( host : string ) : string | null => {
239+ if ( ! host . endsWith ( ".googleapis.com" ) ) return null ;
243240 const rawService = host . slice ( 0 , - ".googleapis.com" . length ) ;
241+ if ( ! rawService || rawService . includes ( "." ) ) return null ;
244242 const service =
245243 rawService === "calendar-json"
246244 ? "calendar"
247245 : rawService . endsWith ( "-json" )
248246 ? rawService . slice ( 0 , - 5 )
249247 : rawService ;
250- return service ? ` ${ DISCOVERY_SERVICE_HOST } / ${ service } / ${ version } /rest` : trimmed ;
248+ return / ^ [ a - z 0 - 9 ] [ a - z 0 - 9 - ] * $ / . test ( service ) ? service : null ;
251249} ;
252250
253- export const isGoogleDiscoveryUrl = ( url : string ) : boolean => {
254- const trimmed = url . trim ( ) ;
255- if ( ! URL . canParse ( trimmed ) ) return false ;
251+ export const normalizeGoogleDiscoveryUrl = ( discoveryUrl : string ) : string | null => {
252+ const trimmed = discoveryUrl . trim ( ) ;
253+ if ( ! URL . canParse ( trimmed ) ) return null ;
256254 const parsed = new URL ( trimmed ) ;
255+ if ( parsed . protocol !== "https:" || parsed . username || parsed . password || parsed . hash ) {
256+ return null ;
257+ }
258+
257259 const host = parsed . hostname . toLowerCase ( ) ;
258- if ( ! host . endsWith ( "googleapis.com" ) ) return false ;
259- return parsed . pathname . includes ( "/discovery/" ) || parsed . pathname . includes ( "$discovery" ) ;
260+ if ( host === "www.googleapis.com" ) {
261+ if ( parsed . search ) return null ;
262+ const match = parsed . pathname . match ( DISCOVERY_SERVICE_PATH_RE ) ;
263+ const service = match ?. [ 1 ] ;
264+ const version = match ?. [ 2 ] ;
265+ return service && version ? `${ DISCOVERY_SERVICE_HOST } /${ service } /${ version } /rest` : null ;
266+ }
267+
268+ const service = serviceFromGoogleApisHost ( host ) ;
269+ if ( ! service || ! [ "/$discovery/rest" , "/$discovery/rest/" ] . includes ( parsed . pathname ) ) {
270+ return null ;
271+ }
272+ const keys = [ ...parsed . searchParams . keys ( ) ] ;
273+ const version = parsed . searchParams . get ( "version" ) ?. trim ( ) ;
274+ if ( keys . length !== 1 || keys [ 0 ] !== "version" || ! version || ! DISCOVERY_VERSION_RE . test ( version ) ) {
275+ return null ;
276+ }
277+ return `${ DISCOVERY_SERVICE_HOST } /${ service } /${ version } /rest` ;
278+ } ;
279+
280+ const normalizeDiscoveryUrl = ( discoveryUrl : string ) : string => {
281+ return normalizeGoogleDiscoveryUrl ( discoveryUrl ) ?? discoveryUrl . trim ( ) ;
282+ } ;
283+
284+ export const isGoogleDiscoveryUrl = ( url : string ) : boolean => {
285+ return normalizeGoogleDiscoveryUrl ( url ) !== null ;
260286} ;
261287
262288export const fetchGoogleDiscoveryDocument = Effect . fn ( "OpenApi.fetchGoogleDiscoveryDocument" ) (
263289 function * ( discoveryUrl : string , credentials ?: SpecFetchCredentials ) {
290+ const normalizedDiscoveryUrl = normalizeGoogleDiscoveryUrl ( discoveryUrl ) ;
291+ if ( ! normalizedDiscoveryUrl ) {
292+ return yield * new OpenApiParseError ( {
293+ message : "Google Discovery document URL must be a supported googleapis.com HTTPS Discovery endpoint" ,
294+ } ) ;
295+ }
264296 const client = yield * HttpClient . HttpClient ;
265- const requestUrl = new URL ( discoveryUrl ) ;
297+ const requestUrl = new URL ( normalizedDiscoveryUrl ) ;
266298 for ( const [ name , value ] of Object . entries ( credentials ?. queryParams ?? { } ) ) {
267299 requestUrl . searchParams . set ( name , value ) ;
268300 }
0 commit comments