Skip to content

Commit d2cd116

Browse files
authored
make fs body immutable and encode only once (nodejs#1814)
1 parent 860c261 commit d2cd116

File tree

1 file changed

+35
-54
lines changed

1 file changed

+35
-54
lines changed

lib/fetch/body.js

Lines changed: 35 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ function extractBody (object, keepalive = false) {
9999
// Set source to a copy of the bytes held by object.
100100
source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength))
101101
} else if (util.isFormDataLike(object)) {
102-
const boundary = '----formdata-undici-' + Math.random()
102+
const boundary = `----formdata-undici-${Math.random()}`.replace('.', '').slice(0, 32)
103103
const prefix = `--${boundary}\r\nContent-Disposition: form-data`
104104

105105
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
@@ -109,68 +109,49 @@ function extractBody (object, keepalive = false) {
109109

110110
// Set action to this step: run the multipart/form-data
111111
// encoding algorithm, with object’s entry list and UTF-8.
112-
action = async function * (object) {
113-
const enc = new TextEncoder()
114-
115-
for (const [name, value] of object) {
116-
if (typeof value === 'string') {
117-
yield enc.encode(
118-
prefix +
119-
`; name="${escape(normalizeLinefeeds(name))}"` +
120-
`\r\n\r\n${normalizeLinefeeds(value)}\r\n`
121-
)
122-
} else {
123-
yield enc.encode(
124-
prefix +
125-
`; name="${escape(normalizeLinefeeds(name))}"` +
126-
(value.name ? `; filename="${escape(value.name)}"` : '') +
127-
'\r\n' +
128-
`Content-Type: ${
129-
value.type || 'application/octet-stream'
130-
}\r\n\r\n`
131-
)
132-
133-
yield * value.stream()
134-
135-
// '\r\n' encoded
136-
yield new Uint8Array([13, 10])
137-
}
112+
// - This ensures that the body is immutable and can't be changed afterwords
113+
// - That the content-length is calculated in advance.
114+
// - And that all parts are pre-encoded and ready to be sent.
115+
116+
const enc = new TextEncoder()
117+
const blobParts = []
118+
const rn = new Uint8Array([13, 10]) // '\r\n'
119+
length = 0
120+
121+
for (const [name, value] of object) {
122+
if (typeof value === 'string') {
123+
const chunk = enc.encode(prefix +
124+
`; name="${escape(normalizeLinefeeds(name))}"` +
125+
`\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
126+
blobParts.push(chunk)
127+
length += chunk.byteLength
128+
} else {
129+
const chunk = enc.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` +
130+
(value.name ? `; filename="${escape(value.name)}"` : '') + '\r\n' +
131+
`Content-Type: ${
132+
value.type || 'application/octet-stream'
133+
}\r\n\r\n`)
134+
blobParts.push(chunk, value, rn)
135+
length += chunk.byteLength + value.size + rn.byteLength
138136
}
139-
140-
yield enc.encode(`--${boundary}--`)
141137
}
142138

139+
const chunk = enc.encode(`--${boundary}--`)
140+
blobParts.push(chunk)
141+
length += chunk.byteLength
142+
143143
// Set source to object.
144144
source = object
145145

146-
// Set length to unclear, see html/6424 for improving this.
147-
length = (() => {
148-
const prefixLength = prefix.length
149-
const boundaryLength = boundary.length
150-
let bodyLength = 0
151-
152-
for (const [name, value] of object) {
153-
if (typeof value === 'string') {
154-
bodyLength +=
155-
prefixLength +
156-
Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
146+
action = async function * () {
147+
for (const part of blobParts) {
148+
if (part.stream) {
149+
yield * part.stream()
157150
} else {
158-
bodyLength +=
159-
prefixLength +
160-
Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"` + (value.name ? `; filename="${escape(value.name)}"` : '')) +
161-
2 + // \r\n
162-
`Content-Type: ${
163-
value.type || 'application/octet-stream'
164-
}\r\n\r\n`.length
165-
166-
// value is a Blob or File, and \r\n
167-
bodyLength += value.size + 2
151+
yield part
168152
}
169153
}
170-
171-
bodyLength += boundaryLength + 4 // --boundary--
172-
return bodyLength
173-
})()
154+
}
174155

175156
// Set type to `multipart/form-data; boundary=`,
176157
// followed by the multipart/form-data boundary string generated

0 commit comments

Comments
 (0)