diff --git a/lib/fetch/body.js b/lib/fetch/body.js index beb1e027c98..0761302c9b9 100644 --- a/lib/fetch/body.js +++ b/lib/fetch/body.js @@ -262,100 +262,132 @@ function cloneBody (body) { } } -const methods = { - async blob () { - const chunks = [] +function bodyMixinMethods (instance) { + const methods = { + async blob () { + if (!(this instanceof instance)) { + throw new TypeError('Illegal invocation') + } - if (this[kState].body) { - if (isUint8Array(this[kState].body)) { - chunks.push(this[kState].body) - } else { - const stream = this[kState].body.stream + const chunks = [] - if (util.isDisturbed(stream)) { - throw new TypeError('disturbed') - } + if (this[kState].body) { + if (isUint8Array(this[kState].body)) { + chunks.push(this[kState].body) + } else { + const stream = this[kState].body.stream - if (stream.locked) { - throw new TypeError('locked') - } + if (util.isDisturbed(stream)) { + throw new TypeError('disturbed') + } + + if (stream.locked) { + throw new TypeError('locked') + } - // Compat. - stream[kBodyUsed] = true + // Compat. + stream[kBodyUsed] = true - for await (const chunk of stream) { - chunks.push(chunk) + for await (const chunk of stream) { + chunks.push(chunk) + } } } - } - return new Blob(chunks, { type: this.headers.get('Content-Type') || '' }) - }, + return new Blob(chunks, { type: this.headers.get('Content-Type') || '' }) + }, - async arrayBuffer () { - const blob = await this.blob() - return await blob.arrayBuffer() - }, + async arrayBuffer () { + if (!(this instanceof instance)) { + throw new TypeError('Illegal invocation') + } - async text () { - const blob = await this.blob() - return toUSVString(await blob.text()) - }, + const blob = await this.blob() + return await blob.arrayBuffer() + }, - async json () { - return JSON.parse(await this.text()) - }, + async text () { + if (!(this instanceof instance)) { + throw new TypeError('Illegal invocation') + } + + const blob = await this.blob() + return toUSVString(await blob.text()) + }, - async formData () { - const contentType = this.headers.get('Content-Type') - - // If mimeType’s essence is "multipart/form-data", then: - if (/multipart\/form-data/.test(contentType)) { - throw new NotSupportedError('multipart/form-data not supported') - } else if (/application\/x-www-form-urlencoded/.test(contentType)) { - // Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then: - - // 1. Let entries be the result of parsing bytes. - let entries - try { - entries = new URLSearchParams(await this.text()) - } catch (err) { - // istanbul ignore next: Unclear when new URLSearchParams can fail on a string. - // 2. If entries is failure, then throw a TypeError. - throw Object.assign(new TypeError(), { cause: err }) + async json () { + if (!(this instanceof instance)) { + throw new TypeError('Illegal invocation') } - // 3. Return a new FormData object whose entries are entries. - const formData = new FormData() - for (const [name, value] of entries) { - formData.append(name, value) + return JSON.parse(await this.text()) + }, + + async formData () { + if (!(this instanceof instance)) { + throw new TypeError('Illegal invocation') + } + + const contentType = this.headers.get('Content-Type') + + // If mimeType’s essence is "multipart/form-data", then: + if (/multipart\/form-data/.test(contentType)) { + throw new NotSupportedError('multipart/form-data not supported') + } else if (/application\/x-www-form-urlencoded/.test(contentType)) { + // Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then: + + // 1. Let entries be the result of parsing bytes. + let entries + try { + entries = new URLSearchParams(await this.text()) + } catch (err) { + // istanbul ignore next: Unclear when new URLSearchParams can fail on a string. + // 2. If entries is failure, then throw a TypeError. + throw Object.assign(new TypeError(), { cause: err }) + } + + // 3. Return a new FormData object whose entries are entries. + const formData = new FormData() + for (const [name, value] of entries) { + formData.append(name, value) + } + return formData + } else { + // Otherwise, throw a TypeError. + throw new TypeError() } - return formData - } else { - // Otherwise, throw a TypeError. - throw new TypeError() } } + + return methods } const properties = { body: { enumerable: true, get () { + if (!this || !this[kState]) { + throw new TypeError('Illegal invocation') + } + return this[kState].body ? this[kState].body.stream : null } }, bodyUsed: { enumerable: true, get () { + if (!this || !this[kState]) { + throw new TypeError('Illegal invocation') + } + return !!this[kState].body && util.isDisturbed(this[kState].body.stream) } } } function mixinBody (prototype) { - Object.assign(prototype, methods) - Object.defineProperties(prototype, properties) + Object.assign(prototype.prototype, bodyMixinMethods(prototype)) + Object.defineProperties(prototype.prototype, properties) } module.exports = { diff --git a/lib/fetch/request.js b/lib/fetch/request.js index 42c7eb447a2..e0a82bf095e 100644 --- a/lib/fetch/request.js +++ b/lib/fetch/request.js @@ -748,7 +748,7 @@ class Request { } } -mixinBody(Request.prototype) +mixinBody(Request) function makeRequest (init) { // https://fetch.spec.whatwg.org/#requests diff --git a/lib/fetch/response.js b/lib/fetch/response.js index 582876e050a..68c5c01e51e 100644 --- a/lib/fetch/response.js +++ b/lib/fetch/response.js @@ -287,7 +287,7 @@ class Response { return clonedResponseObject } } -mixinBody(Response.prototype) +mixinBody(Response) Object.defineProperties(Response.prototype, { type: kEnumerableProperty, diff --git a/test/fetch/request.js b/test/fetch/request.js index fbc8f075fc2..375107089d7 100644 --- a/test/fetch/request.js +++ b/test/fetch/request.js @@ -9,7 +9,7 @@ const { } = require('../../') const { kState } = require('../../lib/fetch/symbols.js') -test('arg validation', (t) => { +test('arg validation', async (t) => { // constructor t.throws(() => { // eslint-disable-next-line @@ -144,6 +144,16 @@ test('arg validation', (t) => { Request.prototype.signal.toString() }, TypeError) + t.throws(() => { + // eslint-disable-next-line no-unused-expressions + Request.prototype.body + }, TypeError) + + t.throws(() => { + // eslint-disable-next-line no-unused-expressions + Request.prototype.bodyUsed + }, TypeError) + t.throws(() => { Request.prototype.clone.call(null) }, TypeError) @@ -152,6 +162,26 @@ test('arg validation', (t) => { Request.prototype[Symbol.toStringTag].charAt(0) }) + for (const method of [ + 'text', + 'json', + 'arrayBuffer', + 'blob', + 'formData' + ]) { + await t.rejects(async () => { + await new Request('http://localhost')[method].call({ + blob () { + return { + text () { + return Promise.resolve('emulating this') + } + } + } + }) + }, TypeError) + } + t.end() }) diff --git a/test/fetch/response.js b/test/fetch/response.js index 374c84034e9..f1e12467620 100644 --- a/test/fetch/response.js +++ b/test/fetch/response.js @@ -5,7 +5,7 @@ const { Response } = require('../../') -test('arg validation', (t) => { +test('arg validation', async (t) => { // constructor t.throws(() => { // eslint-disable-next-line @@ -77,10 +77,32 @@ test('arg validation', (t) => { Response.prototype.headers.toString() }, TypeError) + t.throws(() => { + // eslint-disable-next-line no-unused-expressions + Response.prototype.body + }, TypeError) + + t.throws(() => { + // eslint-disable-next-line no-unused-expressions + Response.prototype.bodyUsed + }, TypeError) + t.throws(() => { Response.prototype.clone.call(null) }, TypeError) + await t.rejects(async () => { + await new Response('http://localhost').text.call({ + blob () { + return { + text () { + return Promise.resolve('emulating response.blob()') + } + } + } + }) + }, TypeError) + t.end() })