123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- 'use strict'
- const { URL } = require('url')
- const Minipass = require('minipass')
- const Headers = require('./headers.js')
- const { exportNodeCompatibleHeaders } = Headers
- const Body = require('./body.js')
- const { clone, extractContentType, getTotalBytes } = Body
-
- const version = require('../package.json').version
- const defaultUserAgent =
- `minipass-fetch/${version} (+https://github.com/isaacs/minipass-fetch)`
-
- const INTERNALS = Symbol('Request internals')
-
- const isRequest = input =>
- typeof input === 'object' && typeof input[INTERNALS] === 'object'
-
- const isAbortSignal = signal => {
- const proto = (
- signal
- && typeof signal === 'object'
- && Object.getPrototypeOf(signal)
- )
- return !!(proto && proto.constructor.name === 'AbortSignal')
- }
-
- class Request extends Body {
- constructor (input, init = {}) {
- const parsedURL = isRequest(input) ? new URL(input.url)
- : input && input.href ? new URL(input.href)
- : new URL(`${input}`)
-
- if (isRequest(input)) {
- init = { ...input[INTERNALS], ...init }
- } else if (!input || typeof input === 'string') {
- input = {}
- }
-
- const method = (init.method || input.method || 'GET').toUpperCase()
- const isGETHEAD = method === 'GET' || method === 'HEAD'
-
- if ((init.body !== null && init.body !== undefined ||
- isRequest(input) && input.body !== null) && isGETHEAD) {
- throw new TypeError('Request with GET/HEAD method cannot have body')
- }
-
- const inputBody = init.body !== null && init.body !== undefined ? init.body
- : isRequest(input) && input.body !== null ? clone(input)
- : null
-
- super(inputBody, {
- timeout: init.timeout || input.timeout || 0,
- size: init.size || input.size || 0,
- })
-
- const headers = new Headers(init.headers || input.headers || {})
-
- if (inputBody !== null && inputBody !== undefined &&
- !headers.has('Content-Type')) {
- const contentType = extractContentType(inputBody)
- if (contentType) {
- headers.append('Content-Type', contentType)
- }
- }
-
- const signal = 'signal' in init ? init.signal
- : null
-
- if (signal !== null && signal !== undefined && !isAbortSignal(signal)) {
- throw new TypeError('Expected signal must be an instanceof AbortSignal')
- }
-
- // TLS specific options that are handled by node
- const {
- ca,
- cert,
- ciphers,
- clientCertEngine,
- crl,
- dhparam,
- ecdhCurve,
- family,
- honorCipherOrder,
- key,
- passphrase,
- pfx,
- rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0',
- secureOptions,
- secureProtocol,
- servername,
- sessionIdContext,
- } = init
-
- this[INTERNALS] = {
- method,
- redirect: init.redirect || input.redirect || 'follow',
- headers,
- parsedURL,
- signal,
- ca,
- cert,
- ciphers,
- clientCertEngine,
- crl,
- dhparam,
- ecdhCurve,
- family,
- honorCipherOrder,
- key,
- passphrase,
- pfx,
- rejectUnauthorized,
- secureOptions,
- secureProtocol,
- servername,
- sessionIdContext,
- }
-
- // node-fetch-only options
- this.follow = init.follow !== undefined ? init.follow
- : input.follow !== undefined ? input.follow
- : 20
- this.compress = init.compress !== undefined ? init.compress
- : input.compress !== undefined ? input.compress
- : true
- this.counter = init.counter || input.counter || 0
- this.agent = init.agent || input.agent
- }
-
- get method () {
- return this[INTERNALS].method
- }
-
- get url () {
- return this[INTERNALS].parsedURL.toString()
- }
-
- get headers () {
- return this[INTERNALS].headers
- }
-
- get redirect () {
- return this[INTERNALS].redirect
- }
-
- get signal () {
- return this[INTERNALS].signal
- }
-
- clone () {
- return new Request(this)
- }
-
- get [Symbol.toStringTag] () {
- return 'Request'
- }
-
- static getNodeRequestOptions (request) {
- const parsedURL = request[INTERNALS].parsedURL
- const headers = new Headers(request[INTERNALS].headers)
-
- // fetch step 1.3
- if (!headers.has('Accept')) {
- headers.set('Accept', '*/*')
- }
-
- // Basic fetch
- if (!/^https?:$/.test(parsedURL.protocol)) {
- throw new TypeError('Only HTTP(S) protocols are supported')
- }
-
- if (request.signal &&
- Minipass.isStream(request.body) &&
- typeof request.body.destroy !== 'function') {
- throw new Error(
- 'Cancellation of streamed requests with AbortSignal is not supported')
- }
-
- // HTTP-network-or-cache fetch steps 2.4-2.7
- const contentLengthValue =
- (request.body === null || request.body === undefined) &&
- /^(POST|PUT)$/i.test(request.method) ? '0'
- : request.body !== null && request.body !== undefined
- ? getTotalBytes(request)
- : null
-
- if (contentLengthValue) {
- headers.set('Content-Length', contentLengthValue + '')
- }
-
- // HTTP-network-or-cache fetch step 2.11
- if (!headers.has('User-Agent')) {
- headers.set('User-Agent', defaultUserAgent)
- }
-
- // HTTP-network-or-cache fetch step 2.15
- if (request.compress && !headers.has('Accept-Encoding')) {
- headers.set('Accept-Encoding', 'gzip,deflate')
- }
-
- const agent = typeof request.agent === 'function'
- ? request.agent(parsedURL)
- : request.agent
-
- if (!headers.has('Connection') && !agent) {
- headers.set('Connection', 'close')
- }
-
- // TLS specific options that are handled by node
- const {
- ca,
- cert,
- ciphers,
- clientCertEngine,
- crl,
- dhparam,
- ecdhCurve,
- family,
- honorCipherOrder,
- key,
- passphrase,
- pfx,
- rejectUnauthorized,
- secureOptions,
- secureProtocol,
- servername,
- sessionIdContext,
- } = request[INTERNALS]
-
- // HTTP-network fetch step 4.2
- // chunked encoding is handled by Node.js
-
- // we cannot spread parsedURL directly, so we have to read each property one-by-one
- // and map them to the equivalent https?.request() method options
- const urlProps = {
- auth: parsedURL.username || parsedURL.password
- ? `${parsedURL.username}:${parsedURL.password}`
- : '',
- host: parsedURL.host,
- hostname: parsedURL.hostname,
- path: `${parsedURL.pathname}${parsedURL.search}`,
- port: parsedURL.port,
- protocol: parsedURL.protocol,
- }
-
- return {
- ...urlProps,
- method: request.method,
- headers: exportNodeCompatibleHeaders(headers),
- agent,
- ca,
- cert,
- ciphers,
- clientCertEngine,
- crl,
- dhparam,
- ecdhCurve,
- family,
- honorCipherOrder,
- key,
- passphrase,
- pfx,
- rejectUnauthorized,
- secureOptions,
- secureProtocol,
- servername,
- sessionIdContext,
- }
- }
- }
-
- module.exports = Request
-
- Object.defineProperties(Request.prototype, {
- method: { enumerable: true },
- url: { enumerable: true },
- headers: { enumerable: true },
- redirect: { enumerable: true },
- clone: { enumerable: true },
- signal: { enumerable: true },
- })
|