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