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
|
---|