( url: string, signal: AbortSignal, redirectChecker: (originalUrl: string, redirectUrl: string) => boolean, depth = 0, )
| 260 | } |
| 261 | |
| 262 | export async function getWithPermittedRedirects( |
| 263 | url: string, |
| 264 | signal: AbortSignal, |
| 265 | redirectChecker: (originalUrl: string, redirectUrl: string) => boolean, |
| 266 | depth = 0, |
| 267 | ): Promise<AxiosResponse<ArrayBuffer> | RedirectInfo> { |
| 268 | if (depth > MAX_REDIRECTS) { |
| 269 | throw new Error(`Too many redirects (exceeded ${MAX_REDIRECTS})`) |
| 270 | } |
| 271 | try { |
| 272 | return await axios.get(url, { |
| 273 | signal, |
| 274 | timeout: FETCH_TIMEOUT_MS, |
| 275 | maxRedirects: 0, |
| 276 | responseType: 'arraybuffer', |
| 277 | maxContentLength: MAX_HTTP_CONTENT_LENGTH, |
| 278 | headers: { |
| 279 | Accept: 'text/markdown, text/html, */*', |
| 280 | 'User-Agent': getWebFetchUserAgent(), |
| 281 | }, |
| 282 | }) |
| 283 | } catch (error) { |
| 284 | if ( |
| 285 | axios.isAxiosError(error) && |
| 286 | error.response && |
| 287 | [301, 302, 307, 308].includes(error.response.status) |
| 288 | ) { |
| 289 | const redirectLocation = error.response.headers.location |
| 290 | if (!redirectLocation) { |
| 291 | throw new Error('Redirect missing Location header') |
| 292 | } |
| 293 | |
| 294 | // Resolve relative URLs against the original URL |
| 295 | const redirectUrl = new URL(redirectLocation, url).toString() |
| 296 | |
| 297 | if (redirectChecker(url, redirectUrl)) { |
| 298 | // Recursively follow the permitted redirect |
| 299 | return getWithPermittedRedirects( |
| 300 | redirectUrl, |
| 301 | signal, |
| 302 | redirectChecker, |
| 303 | depth + 1, |
| 304 | ) |
| 305 | } else { |
| 306 | // Return redirect information to the caller |
| 307 | return { |
| 308 | type: 'redirect', |
| 309 | originalUrl: url, |
| 310 | redirectUrl, |
| 311 | statusCode: error.response.status, |
| 312 | } |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | // Detect egress proxy blocks: the proxy returns 403 with |
| 317 | // X-Proxy-Error: blocked-by-allowlist when egress is restricted |
| 318 | if ( |
| 319 | axios.isAxiosError(error) && |
no test coverage detected