11cb0ef41Sopenharmony_ci'use strict' 21cb0ef41Sopenharmony_ci 31cb0ef41Sopenharmony_ciconst { FetchError, Request, isRedirect } = require('minipass-fetch') 41cb0ef41Sopenharmony_ciconst url = require('url') 51cb0ef41Sopenharmony_ci 61cb0ef41Sopenharmony_ciconst CachePolicy = require('./cache/policy.js') 71cb0ef41Sopenharmony_ciconst cache = require('./cache/index.js') 81cb0ef41Sopenharmony_ciconst remote = require('./remote.js') 91cb0ef41Sopenharmony_ci 101cb0ef41Sopenharmony_ci// given a Request, a Response and user options 111cb0ef41Sopenharmony_ci// return true if the response is a redirect that 121cb0ef41Sopenharmony_ci// can be followed. we throw errors that will result 131cb0ef41Sopenharmony_ci// in the fetch being rejected if the redirect is 141cb0ef41Sopenharmony_ci// possible but invalid for some reason 151cb0ef41Sopenharmony_ciconst canFollowRedirect = (request, response, options) => { 161cb0ef41Sopenharmony_ci if (!isRedirect(response.status)) { 171cb0ef41Sopenharmony_ci return false 181cb0ef41Sopenharmony_ci } 191cb0ef41Sopenharmony_ci 201cb0ef41Sopenharmony_ci if (options.redirect === 'manual') { 211cb0ef41Sopenharmony_ci return false 221cb0ef41Sopenharmony_ci } 231cb0ef41Sopenharmony_ci 241cb0ef41Sopenharmony_ci if (options.redirect === 'error') { 251cb0ef41Sopenharmony_ci throw new FetchError(`redirect mode is set to error: ${request.url}`, 261cb0ef41Sopenharmony_ci 'no-redirect', { code: 'ENOREDIRECT' }) 271cb0ef41Sopenharmony_ci } 281cb0ef41Sopenharmony_ci 291cb0ef41Sopenharmony_ci if (!response.headers.has('location')) { 301cb0ef41Sopenharmony_ci throw new FetchError(`redirect location header missing for: ${request.url}`, 311cb0ef41Sopenharmony_ci 'no-location', { code: 'EINVALIDREDIRECT' }) 321cb0ef41Sopenharmony_ci } 331cb0ef41Sopenharmony_ci 341cb0ef41Sopenharmony_ci if (request.counter >= request.follow) { 351cb0ef41Sopenharmony_ci throw new FetchError(`maximum redirect reached at: ${request.url}`, 361cb0ef41Sopenharmony_ci 'max-redirect', { code: 'EMAXREDIRECT' }) 371cb0ef41Sopenharmony_ci } 381cb0ef41Sopenharmony_ci 391cb0ef41Sopenharmony_ci return true 401cb0ef41Sopenharmony_ci} 411cb0ef41Sopenharmony_ci 421cb0ef41Sopenharmony_ci// given a Request, a Response, and the user's options return an object 431cb0ef41Sopenharmony_ci// with a new Request and a new options object that will be used for 441cb0ef41Sopenharmony_ci// following the redirect 451cb0ef41Sopenharmony_ciconst getRedirect = (request, response, options) => { 461cb0ef41Sopenharmony_ci const _opts = { ...options } 471cb0ef41Sopenharmony_ci const location = response.headers.get('location') 481cb0ef41Sopenharmony_ci const redirectUrl = new url.URL(location, /^https?:/.test(location) ? undefined : request.url) 491cb0ef41Sopenharmony_ci // Comment below is used under the following license: 501cb0ef41Sopenharmony_ci /** 511cb0ef41Sopenharmony_ci * @license 521cb0ef41Sopenharmony_ci * Copyright (c) 2010-2012 Mikeal Rogers 531cb0ef41Sopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License"); 541cb0ef41Sopenharmony_ci * you may not use this file except in compliance with the License. 551cb0ef41Sopenharmony_ci * You may obtain a copy of the License at 561cb0ef41Sopenharmony_ci * http://www.apache.org/licenses/LICENSE-2.0 571cb0ef41Sopenharmony_ci * Unless required by applicable law or agreed to in writing, 581cb0ef41Sopenharmony_ci * software distributed under the License is distributed on an "AS 591cb0ef41Sopenharmony_ci * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 601cb0ef41Sopenharmony_ci * express or implied. See the License for the specific language 611cb0ef41Sopenharmony_ci * governing permissions and limitations under the License. 621cb0ef41Sopenharmony_ci */ 631cb0ef41Sopenharmony_ci 641cb0ef41Sopenharmony_ci // Remove authorization if changing hostnames (but not if just 651cb0ef41Sopenharmony_ci // changing ports or protocols). This matches the behavior of request: 661cb0ef41Sopenharmony_ci // https://github.com/request/request/blob/b12a6245/lib/redirect.js#L134-L138 671cb0ef41Sopenharmony_ci if (new url.URL(request.url).hostname !== redirectUrl.hostname) { 681cb0ef41Sopenharmony_ci request.headers.delete('authorization') 691cb0ef41Sopenharmony_ci request.headers.delete('cookie') 701cb0ef41Sopenharmony_ci } 711cb0ef41Sopenharmony_ci 721cb0ef41Sopenharmony_ci // for POST request with 301/302 response, or any request with 303 response, 731cb0ef41Sopenharmony_ci // use GET when following redirect 741cb0ef41Sopenharmony_ci if ( 751cb0ef41Sopenharmony_ci response.status === 303 || 761cb0ef41Sopenharmony_ci (request.method === 'POST' && [301, 302].includes(response.status)) 771cb0ef41Sopenharmony_ci ) { 781cb0ef41Sopenharmony_ci _opts.method = 'GET' 791cb0ef41Sopenharmony_ci _opts.body = null 801cb0ef41Sopenharmony_ci request.headers.delete('content-length') 811cb0ef41Sopenharmony_ci } 821cb0ef41Sopenharmony_ci 831cb0ef41Sopenharmony_ci _opts.headers = {} 841cb0ef41Sopenharmony_ci request.headers.forEach((value, key) => { 851cb0ef41Sopenharmony_ci _opts.headers[key] = value 861cb0ef41Sopenharmony_ci }) 871cb0ef41Sopenharmony_ci 881cb0ef41Sopenharmony_ci _opts.counter = ++request.counter 891cb0ef41Sopenharmony_ci const redirectReq = new Request(url.format(redirectUrl), _opts) 901cb0ef41Sopenharmony_ci return { 911cb0ef41Sopenharmony_ci request: redirectReq, 921cb0ef41Sopenharmony_ci options: _opts, 931cb0ef41Sopenharmony_ci } 941cb0ef41Sopenharmony_ci} 951cb0ef41Sopenharmony_ci 961cb0ef41Sopenharmony_ciconst fetch = async (request, options) => { 971cb0ef41Sopenharmony_ci const response = CachePolicy.storable(request, options) 981cb0ef41Sopenharmony_ci ? await cache(request, options) 991cb0ef41Sopenharmony_ci : await remote(request, options) 1001cb0ef41Sopenharmony_ci 1011cb0ef41Sopenharmony_ci // if the request wasn't a GET or HEAD, and the response 1021cb0ef41Sopenharmony_ci // status is between 200 and 399 inclusive, invalidate the 1031cb0ef41Sopenharmony_ci // request url 1041cb0ef41Sopenharmony_ci if (!['GET', 'HEAD'].includes(request.method) && 1051cb0ef41Sopenharmony_ci response.status >= 200 && 1061cb0ef41Sopenharmony_ci response.status <= 399) { 1071cb0ef41Sopenharmony_ci await cache.invalidate(request, options) 1081cb0ef41Sopenharmony_ci } 1091cb0ef41Sopenharmony_ci 1101cb0ef41Sopenharmony_ci if (!canFollowRedirect(request, response, options)) { 1111cb0ef41Sopenharmony_ci return response 1121cb0ef41Sopenharmony_ci } 1131cb0ef41Sopenharmony_ci 1141cb0ef41Sopenharmony_ci const redirect = getRedirect(request, response, options) 1151cb0ef41Sopenharmony_ci return fetch(redirect.request, redirect.options) 1161cb0ef41Sopenharmony_ci} 1171cb0ef41Sopenharmony_ci 1181cb0ef41Sopenharmony_cimodule.exports = fetch 119