You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
100 lines
3.7 KiB
100 lines
3.7 KiB
'use strict' |
|
|
|
const { FetchError, Request, isRedirect } = require('minipass-fetch') |
|
const url = require('url') |
|
|
|
const CachePolicy = require('./cache/policy.js') |
|
const cache = require('./cache/index.js') |
|
const remote = require('./remote.js') |
|
|
|
// given a Request, a Response and user options |
|
// return true if the response is a redirect that |
|
// can be followed. we throw errors that will result |
|
// in the fetch being rejected if the redirect is |
|
// possible but invalid for some reason |
|
const canFollowRedirect = (request, response, options) => { |
|
if (!isRedirect(response.status)) |
|
return false |
|
|
|
if (options.redirect === 'manual') |
|
return false |
|
|
|
if (options.redirect === 'error') |
|
throw new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect', { code: 'ENOREDIRECT' }) |
|
|
|
if (!response.headers.has('location')) |
|
throw new FetchError(`redirect location header missing for: ${request.url}`, 'no-location', { code: 'EINVALIDREDIRECT' }) |
|
|
|
if (request.counter >= request.follow) |
|
throw new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect', { code: 'EMAXREDIRECT' }) |
|
|
|
return true |
|
} |
|
|
|
// given a Request, a Response, and the user's options return an object |
|
// with a new Request and a new options object that will be used for |
|
// following the redirect |
|
const getRedirect = (request, response, options) => { |
|
const _opts = { ...options } |
|
const location = response.headers.get('location') |
|
const redirectUrl = new url.URL(location, /^https?:/.test(location) ? undefined : request.url) |
|
// Comment below is used under the following license: |
|
// Copyright (c) 2010-2012 Mikeal Rogers |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// Unless required by applicable law or agreed to in writing, |
|
// software distributed under the License is distributed on an "AS |
|
// IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either |
|
// express or implied. See the License for the specific language |
|
// governing permissions and limitations under the License. |
|
|
|
// Remove authorization if changing hostnames (but not if just |
|
// changing ports or protocols). This matches the behavior of request: |
|
// https://github.com/request/request/blob/b12a6245/lib/redirect.js#L134-L138 |
|
if (new url.URL(request.url).hostname !== redirectUrl.hostname) |
|
request.headers.delete('authorization') |
|
|
|
// for POST request with 301/302 response, or any request with 303 response, |
|
// use GET when following redirect |
|
if (response.status === 303 || (request.method === 'POST' && [301, 302].includes(response.status))) { |
|
_opts.method = 'GET' |
|
_opts.body = null |
|
request.headers.delete('content-length') |
|
} |
|
|
|
_opts.headers = {} |
|
request.headers.forEach((value, key) => { |
|
_opts.headers[key] = value |
|
}) |
|
|
|
_opts.counter = ++request.counter |
|
const redirectReq = new Request(url.format(redirectUrl), _opts) |
|
return { |
|
request: redirectReq, |
|
options: _opts, |
|
} |
|
} |
|
|
|
const fetch = async (request, options) => { |
|
const response = CachePolicy.storable(request, options) |
|
? await cache(request, options) |
|
: await remote(request, options) |
|
|
|
// if the request wasn't a GET or HEAD, and the response |
|
// status is between 200 and 399 inclusive, invalidate the |
|
// request url |
|
if (!['GET', 'HEAD'].includes(request.method) && |
|
response.status >= 200 && |
|
response.status <= 399) |
|
await cache.invalidate(request, options) |
|
|
|
if (!canFollowRedirect(request, response, options)) |
|
return response |
|
|
|
const redirect = getRedirect(request, response, options) |
|
return fetch(redirect.request, redirect.options) |
|
} |
|
|
|
module.exports = fetch
|
|
|