[6a3a178] | 1 | 'use strict'
|
---|
| 2 |
|
---|
| 3 | const { FetchError, Request, isRedirect } = require('minipass-fetch')
|
---|
| 4 | const url = require('url')
|
---|
| 5 |
|
---|
| 6 | const CachePolicy = require('./cache/policy.js')
|
---|
| 7 | const cache = require('./cache/index.js')
|
---|
| 8 | const remote = require('./remote.js')
|
---|
| 9 |
|
---|
| 10 | // given a Request, a Response and user options
|
---|
| 11 | // return true if the response is a redirect that
|
---|
| 12 | // can be followed. we throw errors that will result
|
---|
| 13 | // in the fetch being rejected if the redirect is
|
---|
| 14 | // possible but invalid for some reason
|
---|
| 15 | const canFollowRedirect = (request, response, options) => {
|
---|
| 16 | if (!isRedirect(response.status))
|
---|
| 17 | return false
|
---|
| 18 |
|
---|
| 19 | if (options.redirect === 'manual')
|
---|
| 20 | return false
|
---|
| 21 |
|
---|
| 22 | if (options.redirect === 'error')
|
---|
| 23 | throw new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect', { code: 'ENOREDIRECT' })
|
---|
| 24 |
|
---|
| 25 | if (!response.headers.has('location'))
|
---|
| 26 | throw new FetchError(`redirect location header missing for: ${request.url}`, 'no-location', { code: 'EINVALIDREDIRECT' })
|
---|
| 27 |
|
---|
| 28 | if (request.counter >= request.follow)
|
---|
| 29 | throw new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect', { code: 'EMAXREDIRECT' })
|
---|
| 30 |
|
---|
| 31 | return true
|
---|
| 32 | }
|
---|
| 33 |
|
---|
| 34 | // given a Request, a Response, and the user's options return an object
|
---|
| 35 | // with a new Request and a new options object that will be used for
|
---|
| 36 | // following the redirect
|
---|
| 37 | const getRedirect = (request, response, options) => {
|
---|
| 38 | const _opts = { ...options }
|
---|
| 39 | const location = response.headers.get('location')
|
---|
| 40 | const redirectUrl = new url.URL(location, /^https?:/.test(location) ? undefined : request.url)
|
---|
| 41 | // Comment below is used under the following license:
|
---|
| 42 | // Copyright (c) 2010-2012 Mikeal Rogers
|
---|
| 43 | // Licensed under the Apache License, Version 2.0 (the "License");
|
---|
| 44 | // you may not use this file except in compliance with the License.
|
---|
| 45 | // You may obtain a copy of the License at
|
---|
| 46 | // http://www.apache.org/licenses/LICENSE-2.0
|
---|
| 47 | // Unless required by applicable law or agreed to in writing,
|
---|
| 48 | // software distributed under the License is distributed on an "AS
|
---|
| 49 | // IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
---|
| 50 | // express or implied. See the License for the specific language
|
---|
| 51 | // governing permissions and limitations under the License.
|
---|
| 52 |
|
---|
| 53 | // Remove authorization if changing hostnames (but not if just
|
---|
| 54 | // changing ports or protocols). This matches the behavior of request:
|
---|
| 55 | // https://github.com/request/request/blob/b12a6245/lib/redirect.js#L134-L138
|
---|
| 56 | if (new url.URL(request.url).hostname !== redirectUrl.hostname)
|
---|
| 57 | request.headers.delete('authorization')
|
---|
| 58 |
|
---|
| 59 | // for POST request with 301/302 response, or any request with 303 response,
|
---|
| 60 | // use GET when following redirect
|
---|
| 61 | if (response.status === 303 || (request.method === 'POST' && [301, 302].includes(response.status))) {
|
---|
| 62 | _opts.method = 'GET'
|
---|
| 63 | _opts.body = null
|
---|
| 64 | request.headers.delete('content-length')
|
---|
| 65 | }
|
---|
| 66 |
|
---|
| 67 | _opts.headers = {}
|
---|
| 68 | request.headers.forEach((value, key) => {
|
---|
| 69 | _opts.headers[key] = value
|
---|
| 70 | })
|
---|
| 71 |
|
---|
| 72 | _opts.counter = ++request.counter
|
---|
| 73 | const redirectReq = new Request(url.format(redirectUrl), _opts)
|
---|
| 74 | return {
|
---|
| 75 | request: redirectReq,
|
---|
| 76 | options: _opts,
|
---|
| 77 | }
|
---|
| 78 | }
|
---|
| 79 |
|
---|
| 80 | const fetch = async (request, options) => {
|
---|
| 81 | const response = CachePolicy.storable(request, options)
|
---|
| 82 | ? await cache(request, options)
|
---|
| 83 | : await remote(request, options)
|
---|
| 84 |
|
---|
| 85 | // if the request wasn't a GET or HEAD, and the response
|
---|
| 86 | // status is between 200 and 399 inclusive, invalidate the
|
---|
| 87 | // request url
|
---|
| 88 | if (!['GET', 'HEAD'].includes(request.method) &&
|
---|
| 89 | response.status >= 200 &&
|
---|
| 90 | response.status <= 399)
|
---|
| 91 | await cache.invalidate(request, options)
|
---|
| 92 |
|
---|
| 93 | if (!canFollowRedirect(request, response, options))
|
---|
| 94 | return response
|
---|
| 95 |
|
---|
| 96 | const redirect = getRedirect(request, response, options)
|
---|
| 97 | return fetch(redirect.request, redirect.options)
|
---|
| 98 | }
|
---|
| 99 |
|
---|
| 100 | module.exports = fetch
|
---|