Socket
Socket
Sign inDemoInstall

node-fetch

Package Overview
Dependencies
5
Maintainers
4
Versions
96
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.0.0 to 3.1.0

src/utils/multipart-parser.js

23

@types/index.d.ts

@@ -74,2 +74,10 @@ /// <reference types="node" />

signal?: AbortSignal | null;
/**
* A string whose value is a same-origin URL, "about:client", or the empty string, to set request’s referrer.
*/
referrer?: string;
/**
* A referrer policy to set request’s referrerPolicy.
*/
referrerPolicy?: ReferrerPolicy;

@@ -99,2 +107,3 @@ // Node-fetch extensions to the whatwg/fetch spec

| URLSearchParams
| FormData
| NodeJS.ReadableStream

@@ -109,4 +118,8 @@ | string;

/**
* @deprecated Please use 'response.arrayBuffer()' instead of 'response.buffer()
*/
buffer(): Promise<Buffer>;
arrayBuffer(): Promise<ArrayBuffer>;
formData(): Promise<FormData>;
blob(): Promise<Blob>;

@@ -121,2 +134,3 @@ json(): Promise<unknown>;

export type RequestRedirect = 'error' | 'follow' | 'manual';
export type ReferrerPolicy = '' | 'no-referrer' | 'no-referrer-when-downgrade' | 'same-origin' | 'origin' | 'strict-origin' | 'origin-when-cross-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url';
export type RequestInfo = string | Request;

@@ -146,2 +160,10 @@ export class Request extends BodyMixin {

readonly url: string;
/**
* A string whose value is a same-origin URL, "about:client", or the empty string, to set request’s referrer.
*/
readonly referrer: string;
/**
* A referrer policy to set request’s referrerPolicy.
*/
readonly referrerPolicy: ReferrerPolicy;
clone(): Request;

@@ -165,2 +187,3 @@ }

static error(): Response;
static redirect(url: string, status?: number): Response;
}

@@ -167,0 +190,0 @@

10

package.json
{
"name": "node-fetch",
"version": "3.0.0",
"version": "3.1.0",
"description": "A light-weight module that brings Fetch API to node.js",

@@ -58,4 +58,4 @@ "main": "./src/index.js",

"form-data": "^4.0.0",
"formdata-node": "^3.5.4",
"mocha": "^8.3.2",
"formdata-node": "^4.2.4",
"mocha": "^9.1.3",
"p-timeout": "^5.0.0",

@@ -66,3 +66,4 @@ "tsd": "^0.14.0",

"dependencies": {
"data-uri-to-buffer": "^3.0.1",
"data-uri-to-buffer": "^4.0.0",
"formdata-polyfill": "^4.0.10",
"fetch-blob": "^3.1.2"

@@ -96,2 +97,3 @@ },

"capitalized-comments": 0,
"node/no-unsupported-features/es-syntax": 0,
"@typescript-eslint/member-ordering": 0

@@ -98,0 +100,0 @@ },

105

README.md

@@ -42,7 +42,5 @@ <div align="center">

- [Streams](#streams)
- [Buffer](#buffer)
- [Accessing Headers and other Meta data](#accessing-headers-and-other-meta-data)
- [Accessing Headers and other Metadata](#accessing-headers-and-other-metadata)
- [Extract Set-Cookie Header](#extract-set-cookie-header)
- [Post data using a file stream](#post-data-using-a-file-stream)
- [Post with form-data (detect multipart)](#post-with-form-data-detect-multipart)
- [Request cancellation with AbortSignal](#request-cancellation-with-abortsignal)

@@ -72,3 +70,2 @@ - [API](#api)

- [body.text()](#bodytext)
- [body.buffer()](#bodybuffer)
- [Class: FetchError](#class-fetcherror)

@@ -117,2 +114,4 @@ - [Class: AbortError](#class-aborterror)

### ES Modules (ESM)
```js

@@ -122,14 +121,12 @@ import fetch from 'node-fetch';

If you want to patch the global object in node:
### CommonJS
```js
import fetch from 'node-fetch';
`node-fetch` from v3 is an ESM-only module - you are not able to import it with `require()`.
if (!globalThis.fetch) {
globalThis.fetch = fetch;
}
If you cannot switch to ESM, please use v2 which remains compatible with CommonJS. Critical bug fixes will continue to be published for v2.
```sh
npm install node-fetch@2
```
`node-fetch` is an ESM-only module - you are not able to import it with `require`. We recommend you stay on v2 which is built with CommonJS unless you use ESM yourself. We will continue to publish critical bug fixes for it.
Alternatively, you can use the async `import()` function from CommonJS to load `node-fetch` asynchronously:

@@ -142,2 +139,23 @@

### Providing global access
To use `fetch()` without importing it, you can patch the `global` object in node:
```js
// fetch-polyfill.js
import fetch from 'node-fetch';
if (!globalThis.fetch) {
globalThis.fetch = fetch;
globalThis.Headers = Headers;
globalThis.Request = Request;
globalThis.Response = Response;
}
// index.js
import './fetch-polyfill'
// ...
```
## Upgrading

@@ -248,4 +266,4 @@

constructor(response, ...args) {
super(`HTTP Error Response: ${response.status} ${response.statusText}`, ...args);
this.response = response;
super(`HTTP Error Response: ${response.status} ${response.statusText}`, ...args);
}

@@ -293,3 +311,3 @@ }

const response = await fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png');
const response = await fetch('https://github.githubassets.com/images/modules/logos_page/Octocat.png');

@@ -350,22 +368,7 @@ if (!response.ok) throw new Error(`unexpected response ${response.statusText}`);

### Buffer
### Accessing Headers and other Metadata
If you prefer to cache binary data in full, use buffer(). (NOTE: buffer() is a `node-fetch` only API)
```js
import fetch from 'node-fetch';
import fileType from 'file-type';
const response = await fetch('https://octodex.github.com/images/Fintechtocat.png');
const buffer = await response.buffer();
const type = await fileType.fromBuffer(buffer)
console.log(type);
```
### Accessing Headers and other Meta data
```js
import fetch from 'node-fetch';
const response = await fetch('https://github.com/');

@@ -393,11 +396,11 @@

### Post data using a file stream
### Post data using a file
```js
import {createReadStream} from 'fs';
import {fileFromSync} from 'fetch-blob/from.js';
import fetch from 'node-fetch';
const stream = createReadStream('input.txt');
const blob = fileFromSync('./input.txt', 'text/plain');
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: stream});
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: blob});
const data = await response.json();

@@ -408,3 +411,3 @@

node-fetch also supports spec-compliant FormData implementations such as [formdata-polyfill](https://www.npmjs.com/package/formdata-polyfill) and [formdata-node](https://github.com/octet-stream/form-data):
node-fetch also supports any spec-compliant FormData implementations such as [formdata-polyfill](https://www.npmjs.com/package/formdata-polyfill). But any other spec-compliant such as [formdata-node](https://github.com/octet-stream/form-data) works too, but we recommend formdata-polyfill because we use this one internally for decoding entries back to FormData.

@@ -414,5 +417,6 @@ ```js

import {FormData} from 'formdata-polyfill/esm-min.js';
// Alternative package:
import {FormData} from 'formdata-node';
// Alternative hack to get the same FormData instance as node-fetch
// const FormData = (await new Response(new URLSearchParams()).formData()).constructor
const form = new FormData();

@@ -437,4 +441,6 @@ form.set('greeting', 'Hello, world!');

import fetch from 'node-fetch';
import AbortController from 'abort-controller';
// AbortController was added in node v14.17.0 globally
const AbortController = globalThis.AbortController || await import('abort-controller')
const controller = new AbortController();

@@ -469,3 +475,3 @@ const timeout = setTimeout(() => {

`url` should be an absolute url, such as `https://example.com/`. A path-relative URL (`/file/under/root`) or protocol-relative URL (`//can-be-http-or-https.com/`) will result in a rejected `Promise`.
`url` should be an absolute URL, such as `https://example.com/`. A path-relative URL (`/file/under/root`) or protocol-relative URL (`//can-be-http-or-https.com/`) will result in a rejected `Promise`.

@@ -483,3 +489,3 @@ <a id="fetch-options"></a>

headers: {}, // Request headers. format is the identical to that accepted by the Headers constructor (see below)
body: null, // Request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream
body: null, // Request body. can be null, or a Node.js Readable stream
redirect: 'follow', // Set to `manual` to extract redirect headers, `error` to reject redirect

@@ -508,2 +514,3 @@ signal: null, // Pass an instance of AbortSignal to optionally abort requests

| `Content-Length` | _(automatically calculated, if possible)_ |
| `Host` | _(host and port information from the target URI)_ |
| `Transfer-Encoding` | `chunked` _(when `req.body` is a stream)_ |

@@ -579,3 +586,3 @@ | `User-Agent` | `node-fetch` |

const result = await res.clone().buffer();
const result = await res.clone().arrayBuffer();
console.dir(result);

@@ -599,4 +606,2 @@ ```

- `destination`
- `referrer`
- `referrerPolicy`
- `mode`

@@ -710,6 +715,2 @@ - `credentials`

The following methods are not yet implemented in node-fetch at this moment:
- `formData()`
#### body.body

@@ -733,2 +734,4 @@

#### body.formData()
#### body.blob()

@@ -746,10 +749,2 @@

#### body.buffer()
<small>_(node-fetch extension)_</small>
- Returns: `Promise<Buffer>`
Consume the body and return a promise that will resolve to a Buffer.
<a id="class-fetcherror"></a>

@@ -756,0 +751,0 @@

@@ -8,11 +8,11 @@

import Stream, {PassThrough} from 'stream';
import {types} from 'util';
import Stream, {PassThrough} from 'node:stream';
import {types, deprecate} from 'node:util';
import Blob from 'fetch-blob';
import {FormData, formDataToBlob} from 'formdata-polyfill/esm.min.js';
import {FetchError} from './errors/fetch-error.js';
import {FetchBaseError} from './errors/base.js';
import {formDataIterator, getBoundary, getFormDataLength} from './utils/form-data.js';
import {isBlob, isURLSearchParameters, isFormData} from './utils/is.js';
import {isBlob, isURLSearchParameters} from './utils/is.js';

@@ -40,3 +40,3 @@ const INTERNALS = Symbol('Body internals');

} else if (isURLSearchParameters(body)) {
// Body is a URLSearchParams
// Body is a URLSearchParams
body = Buffer.from(body.toString());

@@ -55,6 +55,6 @@ } else if (isBlob(body)) {

// Body is stream
} else if (isFormData(body)) {
// Body is an instance of formdata-node
boundary = `NodeFetchFormDataBoundary${getBoundary()}`;
body = Stream.Readable.from(formDataIterator(body, boundary));
} else if (body instanceof FormData) {
// Body is FormData
body = formDataToBlob(body);
boundary = body.type.split('=')[1];
} else {

@@ -66,4 +66,13 @@ // None of the above

let stream = body;
if (Buffer.isBuffer(body)) {
stream = Stream.Readable.from(body);
} else if (isBlob(body)) {
stream = Stream.Readable.from(body.stream());
}
this[INTERNALS] = {
body,
stream,
boundary,

@@ -86,3 +95,3 @@ disturbed: false,

get body() {
return this[INTERNALS].body;
return this[INTERNALS].stream;
}

@@ -104,2 +113,20 @@

async formData() {
const ct = this.headers.get('content-type');
if (ct.startsWith('application/x-www-form-urlencoded')) {
const formData = new FormData();
const parameters = new URLSearchParams(await this.text());
for (const [name, value] of parameters) {
formData.append(name, value);
}
return formData;
}
const {toFormData} = await import('./utils/multipart-parser.js');
return toFormData(this.body, ct);
}
/**

@@ -149,2 +176,4 @@ * Return raw response as Blob

Body.prototype.buffer = deprecate(Body.prototype.buffer, 'Please use \'response.arrayBuffer()\' instead of \'response.buffer()\'', 'node-fetch#buffer');
// In browsers, all properties are enumerable.

@@ -178,3 +207,3 @@ Object.defineProperties(Body.prototype, {

let {body} = data;
const {body} = data;

@@ -186,12 +215,2 @@ // Body is null

// Body is blob
if (isBlob(body)) {
body = Stream.Readable.from(body.stream());
}
// Body is buffer
if (Buffer.isBuffer(body)) {
return body;
}
/* c8 ignore next 3 */

@@ -248,3 +267,3 @@ if (!(body instanceof Stream)) {

let p2;
let {body} = instance;
let {body} = instance[INTERNALS];

@@ -265,3 +284,3 @@ // Don't allow cloning a used body

// Set instance body to teed body and return the other teed body
instance[INTERNALS].body = p1;
instance[INTERNALS].stream = p1;
body = p2;

@@ -273,2 +292,8 @@ }

const getNonSpecFormDataBoundary = deprecate(
body => body.getBoundary(),
'form-data doesn\'t follow the spec and requires special treatment. Use alternative package',
'https://github.com/node-fetch/node-fetch/issues/1167'
);
/**

@@ -310,11 +335,11 @@ * Performs the operation "extract a `Content-Type` value from |object|" as

if (body instanceof FormData) {
return `multipart/form-data; boundary=${request[INTERNALS].boundary}`;
}
// Detect form data input from form-data module
if (body && typeof body.getBoundary === 'function') {
return `multipart/form-data;boundary=${body.getBoundary()}`;
return `multipart/form-data;boundary=${getNonSpecFormDataBoundary(body)}`;
}
if (isFormData(body)) {
return `multipart/form-data; boundary=${request[INTERNALS].boundary}`;
}
// Body is stream - can't really do much about this

@@ -339,3 +364,3 @@ if (body instanceof Stream) {

export const getTotalBytes = request => {
const {body} = request;
const {body} = request[INTERNALS];

@@ -362,7 +387,2 @@ // Body is null or undefined

// Body is a spec-compliant form-data
if (isFormData(body)) {
return getFormDataLength(request[INTERNALS].boundary);
}
// Body is stream

@@ -383,9 +403,2 @@ return null;

dest.end();
} else if (isBlob(body)) {
// Body is Blob
Stream.Readable.from(body.stream()).pipe(dest);
} else if (Buffer.isBuffer(body)) {
// Body is buffer
dest.write(body);
dest.end();
} else {

@@ -392,0 +405,0 @@ // Body is stream

@@ -7,4 +7,4 @@ /**

import {types} from 'util';
import http from 'http';
import {types} from 'node:util';
import http from 'node:http';

@@ -11,0 +11,0 @@ const validateHeaderName = typeof http.validateHeaderName === 'function' ?

@@ -9,9 +9,9 @@ /**

import http from 'http';
import https from 'https';
import zlib from 'zlib';
import Stream, {PassThrough, pipeline as pump} from 'stream';
import http from 'node:http';
import https from 'node:https';
import zlib from 'node:zlib';
import Stream, {PassThrough, pipeline as pump} from 'node:stream';
import dataUriToBuffer from 'data-uri-to-buffer';
import {writeToStream} from './body.js';
import {writeToStream, clone} from './body.js';
import Response from './response.js';

@@ -23,2 +23,3 @@ import Headers, {fromRawHeaders} from './headers.js';

import {isRedirect} from './utils/is-redirect.js';
import {parseReferrerPolicyFromHeader} from './utils/referrer.js';

@@ -40,8 +41,8 @@ export {Headers, Request, Response, FetchError, AbortError, isRedirect};

const request = new Request(url, options_);
const options = getNodeRequestOptions(request);
if (!supportedSchemas.has(options.protocol)) {
throw new TypeError(`node-fetch cannot load ${url}. URL scheme "${options.protocol.replace(/:$/, '')}" is not supported.`);
const {parsedURL, options} = getNodeRequestOptions(request);
if (!supportedSchemas.has(parsedURL.protocol)) {
throw new TypeError(`node-fetch cannot load ${url}. URL scheme "${parsedURL.protocol.replace(/:$/, '')}" is not supported.`);
}
if (options.protocol === 'data:') {
if (parsedURL.protocol === 'data:') {
const data = dataUriToBuffer(request.url);

@@ -54,3 +55,3 @@ const response = new Response(data, {headers: {'Content-Type': data.typeFull}});

// Wrap http.request into fetch
const send = (options.protocol === 'https:' ? https : http).request;
const send = (parsedURL.protocol === 'https:' ? https : http).request;
const {signal} = request;

@@ -84,3 +85,3 @@ let response = null;

// Send request
const request_ = send(options);
const request_ = send(parsedURL, options);

@@ -174,5 +175,7 @@ if (signal) {

method: request.method,
body: request.body,
body: clone(request),
signal: request.signal,
size: request.size
size: request.size,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy
};

@@ -194,2 +197,8 @@

// HTTP-redirect fetch step 14
const responseReferrerPolicy = parseReferrerPolicyFromHeader(headers);
if (responseReferrerPolicy) {
requestOptions.referrerPolicy = responseReferrerPolicy;
}
// HTTP-redirect fetch step 15

@@ -196,0 +205,0 @@ resolve(fetch(new Request(locationURL, requestOptions)));

@@ -10,3 +10,3 @@

import {format as formatUrl} from 'url';
import {format as formatUrl} from 'node:url';
import Headers from './headers.js';

@@ -16,2 +16,5 @@ import Body, {clone, extractContentType, getTotalBytes} from './body.js';

import {getSearch} from './utils/get-search.js';
import {
validateReferrerPolicy, determineRequestsReferrer, DEFAULT_REFERRER_POLICY
} from './utils/referrer.js';

@@ -54,2 +57,6 @@ const INTERNALS = Symbol('Request internals');

if (parsedURL.username !== '' || parsedURL.password !== '') {
throw new TypeError(`${parsedURL} is an url with embedded credentails.`);
}
let method = init.method || input.method || 'GET';

@@ -79,3 +86,3 @@ method = method.toUpperCase();

if (contentType) {
headers.append('Content-Type', contentType);
headers.set('Content-Type', contentType);
}

@@ -96,2 +103,17 @@ }

// §5.4, Request constructor steps, step 15.1
// eslint-disable-next-line no-eq-null, eqeqeq
let referrer = init.referrer == null ? input.referrer : init.referrer;
if (referrer === '') {
// §5.4, Request constructor steps, step 15.2
referrer = 'no-referrer';
} else if (referrer) {
// §5.4, Request constructor steps, step 15.3.1, 15.3.2
const parsedReferrer = new URL(referrer);
// §5.4, Request constructor steps, step 15.3.3, 15.3.4
referrer = /^about:(\/\/)?client$/.test(parsedReferrer) ? 'client' : parsedReferrer;
} else {
referrer = undefined;
}
this[INTERNALS] = {

@@ -102,3 +124,4 @@ method,

parsedURL,
signal
signal,
referrer
};

@@ -113,2 +136,6 @@

this.insecureHTTPParser = init.insecureHTTPParser || input.insecureHTTPParser || false;
// §5.4, Request constructor steps, step 16.
// Default is empty string per https://fetch.spec.whatwg.org/#concept-request-referrer-policy
this.referrerPolicy = init.referrerPolicy || input.referrerPolicy || '';
}

@@ -136,2 +163,27 @@

// https://fetch.spec.whatwg.org/#dom-request-referrer
get referrer() {
if (this[INTERNALS].referrer === 'no-referrer') {
return '';
}
if (this[INTERNALS].referrer === 'client') {
return 'about:client';
}
if (this[INTERNALS].referrer) {
return this[INTERNALS].referrer.toString();
}
return undefined;
}
get referrerPolicy() {
return this[INTERNALS].referrerPolicy;
}
set referrerPolicy(referrerPolicy) {
this[INTERNALS].referrerPolicy = validateReferrerPolicy(referrerPolicy);
}
/**

@@ -157,3 +209,5 @@ * Clone this request

clone: {enumerable: true},
signal: {enumerable: true}
signal: {enumerable: true},
referrer: {enumerable: true},
referrerPolicy: {enumerable: true}
});

@@ -194,2 +248,25 @@

// 4.1. Main fetch, step 2.6
// > If request's referrer policy is the empty string, then set request's referrer policy to the
// > default referrer policy.
if (request.referrerPolicy === '') {
request.referrerPolicy = DEFAULT_REFERRER_POLICY;
}
// 4.1. Main fetch, step 2.7
// > If request's referrer is not "no-referrer", set request's referrer to the result of invoking
// > determine request's referrer.
if (request.referrer && request.referrer !== 'no-referrer') {
request[INTERNALS].referrer = determineRequestsReferrer(request);
} else {
request[INTERNALS].referrer = 'no-referrer';
}
// 4.5. HTTP-network-or-cache fetch, step 6.9
// > If httpRequest's referrer is a URL, then append `Referer`/httpRequest's referrer, serialized
// > and isomorphic encoded, to httpRequest's header list.
if (request[INTERNALS].referrer instanceof URL) {
headers.set('Referer', request.referrer);
}
// HTTP-network-or-cache fetch step 2.11

@@ -219,13 +296,8 @@ if (!headers.has('User-Agent')) {

// Manually spread the URL object instead of spread syntax
const requestOptions = {
// Pass the full URL directly to request(), but overwrite the following
// options:
const options = {
// Overwrite search to retain trailing ? (issue #776)
path: parsedURL.pathname + search,
pathname: parsedURL.pathname,
hostname: parsedURL.hostname,
protocol: parsedURL.protocol,
port: parsedURL.port,
hash: parsedURL.hash,
search: parsedURL.search,
query: parsedURL.query,
href: parsedURL.href,
// The following options are not expressed in the URL
method: request.method,

@@ -237,3 +309,6 @@ headers: headers[Symbol.for('nodejs.util.inspect.custom')](),

return requestOptions;
return {
parsedURL,
options
};
};

@@ -32,3 +32,3 @@ /**

if (body !== null && !headers.has('Content-Type')) {
const contentType = extractContentType(body);
const contentType = extractContentType(body, this);
if (contentType) {

@@ -99,3 +99,4 @@ headers.append('Content-Type', contentType);

redirected: this.redirected,
size: this.size
size: this.size,
highWaterMark: this.highWaterMark
});

@@ -102,0 +103,0 @@ }

@@ -12,4 +12,3 @@ /**

* ref: https://github.com/node-fetch/node-fetch/issues/296#issuecomment-307598143
*
* @param {*} obj
* @param {*} object - Object to check for
* @return {boolean}

@@ -33,4 +32,3 @@ */

* Check if `object` is a W3C `Blob` object (which `File` inherits from)
*
* @param {*} obj
* @param {*} object - Object to check for
* @return {boolean}

@@ -40,2 +38,3 @@ */

return (
object &&
typeof object === 'object' &&

@@ -51,27 +50,4 @@ typeof object.arrayBuffer === 'function' &&

/**
* Check if `obj` is a spec-compliant `FormData` object
*
* @param {*} object
* @return {boolean}
*/
export function isFormData(object) {
return (
typeof object === 'object' &&
typeof object.append === 'function' &&
typeof object.set === 'function' &&
typeof object.get === 'function' &&
typeof object.getAll === 'function' &&
typeof object.delete === 'function' &&
typeof object.keys === 'function' &&
typeof object.values === 'function' &&
typeof object.entries === 'function' &&
typeof object.constructor === 'function' &&
object[NAME] === 'FormData'
);
}
/**
* Check if `obj` is an instance of AbortSignal.
*
* @param {*} obj
* @param {*} object - Object to check for
* @return {boolean}

@@ -87,2 +63,1 @@ */

};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc