node-fetch
Advanced tools
Comparing version 3.1.0 to 3.1.1
{ | ||
"name": "node-fetch", | ||
"version": "3.1.0", | ||
"version": "3.1.1", | ||
"description": "A light-weight module that brings Fetch API to node.js", | ||
@@ -61,2 +61,3 @@ "main": "./src/index.js", | ||
"p-timeout": "^5.0.0", | ||
"stream-consumers": "^1.0.1", | ||
"tsd": "^0.14.0", | ||
@@ -67,4 +68,4 @@ "xo": "^0.39.1" | ||
"data-uri-to-buffer": "^4.0.0", | ||
"formdata-polyfill": "^4.0.10", | ||
"fetch-blob": "^3.1.2" | ||
"fetch-blob": "^3.1.3", | ||
"formdata-polyfill": "^4.0.10" | ||
}, | ||
@@ -71,0 +72,0 @@ "tsd": { |
@@ -44,3 +44,3 @@ <div align="center"> | ||
- [Extract Set-Cookie Header](#extract-set-cookie-header) | ||
- [Post data using a file stream](#post-data-using-a-file-stream) | ||
- [Post data using a file](#post-data-using-a-file) | ||
- [Request cancellation with AbortSignal](#request-cancellation-with-abortsignal) | ||
@@ -299,5 +299,5 @@ - [API](#api) | ||
```js | ||
import {createWriteStream} from 'fs'; | ||
import {pipeline} from 'stream'; | ||
import {promisify} from 'util' | ||
import {createWriteStream} from 'node:fs'; | ||
import {pipeline} from 'node:stream'; | ||
import {promisify} from 'node:util' | ||
import fetch from 'node-fetch'; | ||
@@ -408,3 +408,3 @@ | ||
import fetch from 'node-fetch'; | ||
import {FormData} from 'formdata-polyfill/esm-min.js'; | ||
import {FormData} from 'formdata-polyfill/esm.min.js'; | ||
@@ -523,4 +523,4 @@ // Alternative hack to get the same FormData instance as node-fetch | ||
```js | ||
import http from 'http'; | ||
import https from 'https'; | ||
import http from 'node:http'; | ||
import https from 'node:https'; | ||
@@ -583,3 +583,20 @@ const httpAgent = new http.Agent({ | ||
#### Manual Redirect | ||
The `redirect: 'manual'` option for node-fetch is different from the browser & specification, which | ||
results in an [opaque-redirect filtered response](https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect). | ||
node-fetch gives you the typical [basic filtered response](https://fetch.spec.whatwg.org/#concept-filtered-response-basic) instead. | ||
```js | ||
const fetch = require('node-fetch'); | ||
const response = await fetch('https://httpbin.org/status/301', { redirect: 'manual' }); | ||
if (response.status === 301 || response.status === 302) { | ||
const locationURL = new URL(response.headers.get('location'), response.url); | ||
const response2 = await fetch(locationURL, { redirect: 'manual' }); | ||
console.dir(response2); | ||
} | ||
``` | ||
<a id="class-request"></a> | ||
@@ -758,3 +775,3 @@ | ||
```sh | ||
npm install --save-dev @types/node-fetch | ||
npm install --save-dev @types/node-fetch@2.x | ||
``` | ||
@@ -761,0 +778,0 @@ |
@@ -9,3 +9,4 @@ | ||
import Stream, {PassThrough} from 'node:stream'; | ||
import {types, deprecate} from 'node:util'; | ||
import {types, deprecate, promisify} from 'node:util'; | ||
import {Buffer} from 'node:buffer'; | ||
@@ -19,2 +20,3 @@ import Blob from 'fetch-blob'; | ||
const pipeline = promisify(Stream.pipeline); | ||
const INTERNALS = Symbol('Body internals'); | ||
@@ -135,3 +137,3 @@ | ||
const ct = (this.headers && this.headers.get('content-type')) || (this[INTERNALS].body && this[INTERNALS].body.type) || ''; | ||
const buf = await this.buffer(); | ||
const buf = await this.arrayBuffer(); | ||
@@ -182,3 +184,6 @@ return new Blob([buf], { | ||
json: {enumerable: true}, | ||
text: {enumerable: true} | ||
text: {enumerable: true}, | ||
data: {get: deprecate(() => {}, | ||
'data doesn\'t exist, use json(), text(), arrayBuffer(), or body instead', | ||
'https://github.com/node-fetch/node-fetch/issues/1000 (response)')} | ||
}); | ||
@@ -386,5 +391,5 @@ | ||
* @param obj.body Body object from the Body instance. | ||
* @returns {void} | ||
* @returns {Promise<void>} | ||
*/ | ||
export const writeToStream = (dest, {body}) => { | ||
export const writeToStream = async (dest, {body}) => { | ||
if (body === null) { | ||
@@ -395,4 +400,4 @@ // Body is null | ||
// Body is stream | ||
body.pipe(dest); | ||
await pipeline(body, dest); | ||
} | ||
}; |
@@ -10,2 +10,3 @@ /** | ||
/* c8 ignore next 9 */ | ||
const validateHeaderName = typeof http.validateHeaderName === 'function' ? | ||
@@ -21,2 +22,3 @@ http.validateHeaderName : | ||
/* c8 ignore next 9 */ | ||
const validateHeaderValue = typeof http.validateHeaderValue === 'function' ? | ||
@@ -146,4 +148,4 @@ http.validateHeaderValue : | ||
} | ||
/* c8 ignore next */ | ||
}); | ||
/* c8 ignore next */ | ||
} | ||
@@ -150,0 +152,0 @@ |
@@ -13,2 +13,4 @@ /** | ||
import Stream, {PassThrough, pipeline as pump} from 'node:stream'; | ||
import {Buffer} from 'node:buffer'; | ||
import dataUriToBuffer from 'data-uri-to-buffer'; | ||
@@ -23,2 +25,3 @@ | ||
import {isRedirect} from './utils/is-redirect.js'; | ||
import {isDomainOrSubdomain} from './utils/is.js'; | ||
import {parseReferrerPolicyFromHeader} from './utils/referrer.js'; | ||
@@ -83,3 +86,3 @@ | ||
// Send request | ||
const request_ = send(parsedURL, options); | ||
const request_ = send(parsedURL.toString(), options); | ||
@@ -136,3 +139,15 @@ if (signal) { | ||
// HTTP fetch step 5.3 | ||
const locationURL = location === null ? null : new URL(location, request.url); | ||
let locationURL = null; | ||
try { | ||
locationURL = location === null ? null : new URL(location, request.url); | ||
} catch { | ||
// error here can only be invalid URL in Location: header | ||
// do not throw when options.redirect == manual | ||
// let the user extract the errorneous redirect URL | ||
if (request.redirect !== 'manual') { | ||
reject(new FetchError(`uri requested responds with an invalid redirect URL: ${location}`, 'invalid-redirect')); | ||
finalize(); | ||
return; | ||
} | ||
} | ||
@@ -146,7 +161,3 @@ // HTTP fetch step 5.5 | ||
case 'manual': | ||
// Node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL. | ||
if (locationURL !== null) { | ||
headers.set('Location', locationURL); | ||
} | ||
// Nothing to do | ||
break; | ||
@@ -182,2 +193,14 @@ case 'follow': { | ||
// when forwarding sensitive headers like "Authorization", | ||
// "WWW-Authenticate", and "Cookie" to untrusted targets, | ||
// headers will be ignored when following a redirect to a domain | ||
// that is not a subdomain match or exact match of the initial domain. | ||
// For example, a redirect from "foo.com" to either "foo.com" or "sub.foo.com" | ||
// will forward the sensitive headers, but a redirect to "bar.com" will not. | ||
if (!isDomainOrSubdomain(request.url, locationURL)) { | ||
for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) { | ||
requestOptions.headers.delete(name); | ||
} | ||
} | ||
// HTTP-redirect fetch step 9 | ||
@@ -223,2 +246,3 @@ if (response_.statusCode !== 303 && request.body && options_.body instanceof Stream.Readable) { | ||
// see https://github.com/nodejs/node/pull/29376 | ||
/* c8 ignore next 3 */ | ||
if (process.version < 'v12.10') { | ||
@@ -301,3 +325,4 @@ response_.on('aborted', abortAndFinalize); | ||
writeToStream(request_, request); | ||
// eslint-disable-next-line promise/prefer-await-to-then | ||
writeToStream(request_, request).catch(reject); | ||
}); | ||
@@ -304,0 +329,0 @@ } |
@@ -1,2 +0,1 @@ | ||
/** | ||
@@ -11,2 +10,3 @@ * Request.js | ||
import {format as formatUrl} from 'node:url'; | ||
import {deprecate} from 'node:util'; | ||
import Headers from './headers.js'; | ||
@@ -25,3 +25,3 @@ import Body, {clone, extractContentType, getTotalBytes} from './body.js'; | ||
* | ||
* @param {*} obj | ||
* @param {*} object | ||
* @return {boolean} | ||
@@ -36,2 +36,6 @@ */ | ||
const doBadDataWarn = deprecate(() => {}, | ||
'.data is not a valid RequestInit property, use .body instead', | ||
'https://github.com/node-fetch/node-fetch/issues/1000 (request)'); | ||
/** | ||
@@ -65,4 +69,8 @@ * Request class | ||
if ('data' in init) { | ||
doBadDataWarn(); | ||
} | ||
// eslint-disable-next-line no-eq-null, eqeqeq | ||
if (((init.body != null || isRequest(input)) && input.body !== null) && | ||
if ((init.body != null || (isRequest(input) && input.body !== null)) && | ||
(method === 'GET' || method === 'HEAD')) { | ||
@@ -140,2 +148,3 @@ throw new TypeError('Request with GET/HEAD method cannot have body'); | ||
/** @returns {string} */ | ||
get method() { | ||
@@ -145,2 +154,3 @@ return this[INTERNALS].method; | ||
/** @returns {string} */ | ||
get url() { | ||
@@ -150,2 +160,3 @@ return formatUrl(this[INTERNALS].parsedURL); | ||
/** @returns {Headers} */ | ||
get headers() { | ||
@@ -159,2 +170,3 @@ return this[INTERNALS].headers; | ||
/** @returns {AbortSignal} */ | ||
get signal() { | ||
@@ -217,4 +229,4 @@ return this[INTERNALS].signal; | ||
* | ||
* @param Request A Request instance | ||
* @return Object The options object to be passed to http.request | ||
* @param {Request} request - A Request instance | ||
* @return The options object to be passed to http.request | ||
*/ | ||
@@ -308,2 +320,3 @@ export const getNodeRequestOptions = request => { | ||
return { | ||
/** @type {URL} */ | ||
parsedURL, | ||
@@ -310,0 +323,0 @@ options |
@@ -59,1 +59,18 @@ /** | ||
}; | ||
/** | ||
* isDomainOrSubdomain reports whether sub is a subdomain (or exact match) of | ||
* the parent domain. | ||
* | ||
* Both domains must already be in canonical form. | ||
* @param {string|URL} original | ||
* @param {string|URL} destination | ||
*/ | ||
export const isDomainOrSubdomain = (destination, original) => { | ||
const orig = new URL(original).hostname; | ||
const dest = new URL(destination).hostname; | ||
return orig === dest || ( | ||
orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest) | ||
); | ||
}; |
@@ -1,2 +0,2 @@ | ||
import {isIP} from 'net'; | ||
import {isIP} from 'node:net'; | ||
@@ -3,0 +3,0 @@ /** |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
102809
2225
797
1
17
Updatedfetch-blob@^3.1.3