@@ -7,6 +7,7 @@ import { HttpHeaderLink } from "./link.ts";
77import { UrlError , validatePublicUrl } from "./url.ts" ;
88
99const logger = getLogger ( [ "fedify" , "runtime" , "docloader" ] ) ;
10+ const DEFAULT_MAX_REDIRECTION = 20 ;
1011
1112/**
1213 * A remote JSON-LD document and its context fetched by
@@ -352,27 +353,34 @@ export function getDocumentLoader(
352353 async function load (
353354 url : string ,
354355 options ?: DocumentLoaderOptions ,
356+ redirected = 0 ,
357+ visited = new Set < string > ( ) ,
355358 ) : Promise < RemoteDocument > {
356359 options ?. signal ?. throwIfAborted ( ) ;
357- if ( ! skipPreloadedContexts && url in preloadedContexts ) {
358- logger . debug ( "Using preloaded context: {url}." , { url } ) ;
360+ const currentUrl = new URL ( url ) . href ;
361+ if ( ! skipPreloadedContexts && currentUrl in preloadedContexts ) {
362+ logger . debug ( "Using preloaded context: {url}." , { url : currentUrl } ) ;
359363 return {
360364 contextUrl : null ,
361- document : preloadedContexts [ url ] ,
362- documentUrl : url ,
365+ document : preloadedContexts [ currentUrl ] ,
366+ documentUrl : currentUrl ,
363367 } ;
364368 }
365369 if ( ! allowPrivateAddress ) {
366370 try {
367- await validatePublicUrl ( url ) ;
371+ await validatePublicUrl ( currentUrl ) ;
368372 } catch ( error ) {
369373 if ( error instanceof UrlError ) {
370- logger . error ( "Disallowed private URL: {url}" , { url, error } ) ;
374+ logger . error ( "Disallowed private URL: {url}" , {
375+ url : currentUrl ,
376+ error,
377+ } ) ;
371378 }
372379 throw error ;
373380 }
374381 }
375- const request = createRequest ( url , { userAgent } ) ;
382+ visited . add ( currentUrl ) ;
383+ const request = createRequest ( currentUrl , { userAgent } ) ;
376384 logRequest ( request ) ;
377385 const response = await fetch ( request , {
378386 // Since Bun has a bug that ignores the `Request.redirect` option,
@@ -386,9 +394,34 @@ export function getDocumentLoader(
386394 response . status >= 300 && response . status < 400 &&
387395 response . headers . has ( "Location" )
388396 ) {
389- return load ( response . headers . get ( "Location" ) ! , options ) ;
397+ if ( redirected >= DEFAULT_MAX_REDIRECTION ) {
398+ logger . error (
399+ "Too many redirections ({redirections}) while fetching document." ,
400+ { redirections : redirected + 1 , url : currentUrl } ,
401+ ) ;
402+ throw new FetchError (
403+ currentUrl ,
404+ `Too many redirections (${ redirected + 1 } )` ,
405+ ) ;
406+ }
407+ const redirectUrl = new URL (
408+ response . headers . get ( "Location" ) ! ,
409+ response . url === "" ? currentUrl : response . url ,
410+ ) . href ;
411+ if ( visited . has ( redirectUrl ) ) {
412+ logger . error (
413+ "Detected a redirect loop while fetching document: {url} -> " +
414+ "{redirectUrl}" ,
415+ { url : currentUrl , redirectUrl } ,
416+ ) ;
417+ throw new FetchError (
418+ currentUrl ,
419+ `Redirect loop detected: ${ redirectUrl } ` ,
420+ ) ;
421+ }
422+ return load ( redirectUrl , options , redirected + 1 , visited ) ;
390423 }
391- return getRemoteDocument ( url , response , load ) ;
424+ return getRemoteDocument ( currentUrl , response , load ) ;
392425 }
393426 return load ;
394427}
0 commit comments