Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Better object checks #673

Merged
merged 4 commits into from Sep 14, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 8 additions & 49 deletions src/body.js
Expand Up @@ -13,7 +13,7 @@ import FetchError from './fetch-error';
let convert;
try {
convert = require('encoding').convert;
} catch (error) {}
} catch (error) { }

const INTERNALS = Symbol('Body internals');

Expand All @@ -36,10 +36,10 @@ export default function Body(body, {
if (body == null) {
// Body is undefined or null
body = null;
} else if (isURLSearchParams(body)) {
} else if (body instanceof URLSearchParams) {
// Body is a URLSearchParams
body = Buffer.from(body.toString());
} else if (isBlob(body)) {
} else if (body instanceof Blob) {
// Body is blob
} else if (Buffer.isBuffer(body)) {
// Body is Buffer
Expand Down Expand Up @@ -201,7 +201,7 @@ function consumeBody() {
}

// Body is blob
if (isBlob(body)) {
if (body instanceof Blob) {
body = body.stream();
}

Expand Down Expand Up @@ -341,47 +341,6 @@ function convertBody(buffer, headers) {
).toString();
}

/**
* Detect a URLSearchParams object
* ref: https://github.com/bitinn/node-fetch/issues/296#issuecomment-307598143
*
* @param Object obj Object to detect by type or brand
* @return String
*/
function isURLSearchParams(obj) {
// Duck-typing as a necessary condition.
if (typeof obj !== 'object' ||
typeof obj.append !== 'function' ||
typeof obj.delete !== 'function' ||
typeof obj.get !== 'function' ||
typeof obj.getAll !== 'function' ||
typeof obj.has !== 'function' ||
typeof obj.set !== 'function') {
return false;
}

// Brand-checking and more duck-typing as optional condition.
return obj.constructor.name === 'URLSearchParams' ||
Object.prototype.toString.call(obj) === '[object URLSearchParams]' ||
typeof obj.sort === 'function';
}

/**
* Check if `obj` is a W3C `Blob` object (which `File` inherits from)
* @param {*} obj
* @return {boolean}
*/
function isBlob(obj) {
return typeof obj === 'object' &&
typeof obj.arrayBuffer === 'function' &&
typeof obj.type === 'string' &&
typeof obj.stream === 'function' &&
typeof obj.constructor === 'function' &&
typeof obj.constructor.name === 'string' &&
/^(Blob|File)$/.test(obj.constructor.name) &&
/^(Blob|File)$/.test(obj[Symbol.toStringTag]);
}

/**
* Clone body given Res/Req instance
*
Expand Down Expand Up @@ -434,12 +393,12 @@ export function extractContentType(body) {
return 'text/plain;charset=UTF-8';
}

if (isURLSearchParams(body)) {
if (body instanceof URLSearchParams) {
// Body is a URLSearchParams
return 'application/x-www-form-urlencoded;charset=UTF-8';
}

if (isBlob(body)) {
if (body instanceof Blob) {
// Body is blob
return body.type || null;
}
Expand Down Expand Up @@ -491,7 +450,7 @@ export function getTotalBytes(instance) {
return 0;
}

if (isBlob(body)) {
if (body instanceof Blob) {
return body.size;
}

Expand Down Expand Up @@ -526,7 +485,7 @@ export function writeToStream(dest, instance) {
if (body === null) {
// Body is null
dest.end();
} else if (isBlob(body)) {
} else if (body instanceof Blob) {
body.stream().pipe(dest);
} else if (Buffer.isBuffer(body)) {
// Body is buffer
Expand Down
21 changes: 13 additions & 8 deletions src/request.js
Expand Up @@ -30,17 +30,22 @@ const streamDestructionSupported = 'destroy' in Stream.Readable.prototype;
function isRequest(input) {
return (
typeof input === 'object' &&
typeof input[INTERNALS] === 'object'
typeof input[INTERNALS] === 'object'
);
}

/**
* Check if a value is an instance of AbortSignal. This function ponyfills `signal instanceof AbortSignal`.
*
* @param Mixed signal
* @return Boolean
*/
function isAbortSignal(signal) {
const proto = (
return (
signal &&
typeof signal === 'object' &&
Object.getPrototypeOf(signal)
typeof signal === 'object' &&
signal[Symbol.toStringTag] === 'AbortSignal'
);
return Boolean(proto && proto.constructor.name === 'AbortSignal');
}

/**
Expand Down Expand Up @@ -75,7 +80,7 @@ export default class Request {
method = method.toUpperCase();

if ((init.body != null || isRequest(input) && input.body !== null) &&
(method === 'GET' || method === 'HEAD')) {
(method === 'GET' || method === 'HEAD')) {
throw new TypeError('Request with GET/HEAD method cannot have body');
}

Expand Down Expand Up @@ -205,8 +210,8 @@ export function getNodeRequestOptions(request) {

if (
request.signal &&
request.body instanceof Stream.Readable &&
!streamDestructionSupported
request.body instanceof Stream.Readable &&
!streamDestructionSupported
) {
throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8');
}
Expand Down