Skip to content

Commit 19390c7

Browse files
wojpawlikKhafraDev
authored andcommitted
feat: accept third-party Blobs in FormData (nodejs#1099)
1 parent 200d7f9 commit 19390c7

4 files changed

Lines changed: 34 additions & 7 deletions

File tree

lib/fetch/formdata.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
'use strict'
22

3-
const { Blob } = require('buffer')
3+
const { isBlobLike, toUSVString } = require('./util')
44
const { kState } = require('./symbols')
55
const { File } = require('./file')
6-
const { toUSVString } = require('./util')
76

87
class FormData {
98
constructor (...args) {
@@ -25,7 +24,7 @@ class FormData {
2524
`Failed to execute 'append' on 'FormData': 2 arguments required, but only ${args.length} present.`
2625
)
2726
}
28-
if (args.length === 3 && !(args[1] instanceof Blob)) {
27+
if (args.length === 3 && !isBlobLike(args[1])) {
2928
throw new TypeError(
3029
"Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'"
3130
)
@@ -34,7 +33,7 @@ class FormData {
3433
const filename = args.length === 3 ? toUSVString(args[2]) : undefined
3534

3635
// 1. Let value be value if given; otherwise blobValue.
37-
const value = args[1] instanceof Blob ? args[1] : toUSVString(args[1])
36+
const value = isBlobLike(args[1]) ? args[1] : toUSVString(args[1])
3837

3938
// 2. Let entry be the result of creating an entry with
4039
// name, value, and filename if given.
@@ -135,7 +134,7 @@ class FormData {
135134
`Failed to execute 'set' on 'FormData': 2 arguments required, but only ${args.length} present.`
136135
)
137136
}
138-
if (args.length === 3 && !(args[1] instanceof Blob)) {
137+
if (args.length === 3 && !isBlobLike(args[1])) {
139138
throw new TypeError(
140139
"Failed to execute 'set' on 'FormData': parameter 2 is not of type 'Blob'"
141140
)
@@ -147,7 +146,7 @@ class FormData {
147146
// are:
148147

149148
// 1. Let value be value if given; otherwise blobValue.
150-
const value = args[1] instanceof Blob ? args[1] : toUSVString(args[1])
149+
const value = isBlobLike(args[1]) ? args[1] : toUSVString(args[1])
151150

152151
// 2. Let entry be the result of creating an entry with name, value, and
153152
// filename if given.
@@ -214,7 +213,7 @@ function makeEntry (name, value, filename) {
214213

215214
// 3. If value is a Blob object and not a File object, then set value to a new File
216215
// object, representing the same bytes, whose name attribute value is "blob".
217-
if (value instanceof Blob && !(value instanceof File)) {
216+
if (isBlobLike(value) && !(value instanceof File)) {
218217
value = new File([value], 'blob')
219218
}
220219

lib/fetch/util.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const { redirectStatus } = require('./constants')
44
const { performance } = require('perf_hooks')
5+
const { Blob } = require('buffer')
56
const nodeUtil = require('util')
67

78
let ReadableStream
@@ -68,6 +69,18 @@ function requestBadPort (request) {
6869
return 'allowed'
6970
}
7071

72+
// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
73+
function isBlobLike (object) {
74+
return object instanceof Blob || (
75+
object &&
76+
typeof object === 'object' &&
77+
typeof object.constructor === 'function' &&
78+
(typeof object.stream === 'function' ||
79+
typeof object.arrayBuffer === 'function') &&
80+
/^(Blob|File)$/.test(object[Symbol.toStringTag])
81+
)
82+
}
83+
7184
// Check whether |statusText| is a ByteString and
7285
// matches the Reason-Phrase token production.
7386
// RFC 2616: https://tools.ietf.org/html/rfc2616
@@ -349,5 +362,6 @@ module.exports = {
349362
requestCurrentURL,
350363
responseURL,
351364
responseLocationURL,
365+
isBlobLike,
352366
isValidReasonPhrase
353367
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"cronometro": "^0.8.0",
6464
"delay": "^5.0.0",
6565
"docsify-cli": "^4.4.3",
66+
"formdata-node": "^4.3.1",
6667
"https-pem": "^2.0.0",
6768
"husky": "^7.0.2",
6869
"jest": "^27.2.0",

test/fetch/formdata.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const { test } = require('tap')
44
const { FormData, File } = require('../../')
5+
const { Blob: ThirdPartyBlob } = require('formdata-node')
56
const { Blob } = require('buffer')
67

78
test('arg validation', (t) => {
@@ -104,6 +105,18 @@ test('append blob', async (t) => {
104105
t.end()
105106
})
106107

108+
test('append third-party blob', async (t) => {
109+
const form = new FormData()
110+
form.set('asd', new ThirdPartyBlob(['asd1']))
111+
112+
t.equal(form.has('asd'), true)
113+
t.equal(await form.get('asd').text(), 'asd1')
114+
form.delete('asd')
115+
t.equal(form.get('asd'), null)
116+
117+
t.end()
118+
})
119+
107120
test('append string', (t) => {
108121
const form = new FormData()
109122
form.set('k1', 'v1')

0 commit comments

Comments
 (0)