@@ -54,6 +54,9 @@ export interface ParseMultipartOptions {
5454 * Default: 2 MiB
5555 */
5656 maxFileSize ?: number
57+
58+ useContentPart ?: boolean
59+ onCreatePart ?( part : MultipartPart ) : Promise < void > | void
5760}
5861
5962/**
@@ -66,10 +69,10 @@ export interface ParseMultipartOptions {
6669 * @param options Options for the parser
6770 * @return A generator that yields `MultipartPart` objects
6871 */
69- export function * parseMultipart (
72+ export async function * parseMultipart (
7073 message : Uint8Array | Iterable < Uint8Array > ,
7174 options : ParseMultipartOptions ,
72- ) : Generator < MultipartPart , void , unknown > {
75+ ) : AsyncGenerator < MultipartPart , void , unknown > {
7376 let parser = new MultipartParser ( options . boundary , {
7477 maxHeaderSize : options . maxHeaderSize ,
7578 maxFileSize : options . maxFileSize ,
@@ -152,6 +155,9 @@ export class MultipartParser {
152155 #currentPart: MultipartPart | null = null
153156 #contentLength = 0
154157
158+ #useContentPart: MultipartParserOptions [ 'useContentPart' ]
159+ #onCreatePart: MultipartParserOptions [ 'onCreatePart' ]
160+
155161 constructor ( boundary : string , options ?: MultipartParserOptions ) {
156162 this . boundary = boundary
157163 this . maxHeaderSize = options ?. maxHeaderSize ?? 8 * oneKb
@@ -162,6 +168,9 @@ export class MultipartParser {
162168 this . #findBoundary = createSearch ( `\r\n--${ boundary } ` )
163169 this . #findPartialTailBoundary = createPartialTailSearch ( `\r\n--${ boundary } ` )
164170 this . #boundaryLength = 4 + boundary . length // length of '\r\n--' + boundary
171+
172+ this . #onCreatePart = options ?. onCreatePart
173+ this . #useContentPart = options ?. useContentPart ?? true
165174 }
166175
167176 /**
@@ -170,7 +179,7 @@ export class MultipartParser {
170179 * @param chunk A chunk of data to write to the parser
171180 * @return A generator yielding `MultipartPart` objects as they are parsed
172181 */
173- * write ( chunk : Uint8Array ) : Generator < MultipartPart , void , unknown > {
182+ async * write ( chunk : Uint8Array ) : AsyncGenerator < MultipartPart , void , unknown > {
174183 if ( this . #state === MultipartParserStateDone ) {
175184 throw new MultipartParseError ( 'Unexpected data after end of stream' )
176185 }
@@ -201,16 +210,16 @@ export class MultipartParser {
201210 let partialTailIndex = this . #findPartialTailBoundary( chunk )
202211
203212 if ( partialTailIndex === - 1 ) {
204- this . #append( index === 0 ? chunk : chunk . subarray ( index ) )
213+ await this . #append( index === 0 ? chunk : chunk . subarray ( index ) )
205214 } else {
206- this . #append( chunk . subarray ( index , partialTailIndex ) )
215+ await this . #append( chunk . subarray ( index , partialTailIndex ) )
207216 this . #buffer = chunk . subarray ( partialTailIndex )
208217 }
209218
210219 break
211220 }
212221
213- this . #append( chunk . subarray ( index , boundaryIndex ) )
222+ await this . #append( chunk . subarray ( index , boundaryIndex ) )
214223
215224 yield this . #currentPart!
216225
@@ -256,13 +265,19 @@ export class MultipartParser {
256265 throw new MaxHeaderSizeExceededError ( this . maxHeaderSize )
257266 }
258267
259- this . #currentPart = new MultipartPart ( chunk . subarray ( index , headerEndIndex ) , [ ] )
268+ const header = chunk . subarray ( index , headerEndIndex ) ;
269+ this . #currentPart = this . #useContentPart
270+ ? new MultipartContentPart ( header , [ ] )
271+ : new MultipartPart ( header )
272+
260273 this . #contentLength = 0
261274
262275 index = headerEndIndex + 4 // Skip header + \r\n\r\n
263276
264277 this . #state = MultipartParserStateBody
265278
279+ await this . #onCreatePart?.( this . #currentPart)
280+
266281 continue
267282 }
268283
@@ -283,12 +298,12 @@ export class MultipartParser {
283298 }
284299 }
285300
286- #append( chunk : Uint8Array ) : void {
301+ async #append( chunk : Uint8Array ) : Promise < void > {
287302 if ( this . #contentLength + chunk . length > this . maxFileSize ) {
288303 throw new MaxFileSizeExceededError ( this . maxFileSize )
289304 }
290305
291- this . #currentPart! . content . push ( chunk )
306+ await this . #currentPart! . append ( chunk )
292307 this . #contentLength += chunk . length
293308 }
294309
@@ -313,40 +328,16 @@ const decoder = new TextDecoder('utf-8', { fatal: true })
313328 * A part of a `multipart/*` HTTP message.
314329 */
315330export class MultipartPart {
316- /**
317- * The raw content of this part as an array of `Uint8Array` chunks.
318- */
319- readonly content : Uint8Array [ ]
320331
321332 #header: Uint8Array
322333 #headers?: Headers
323334
324- constructor ( header : Uint8Array , content : Uint8Array [ ] ) {
335+ constructor ( header : Uint8Array ) {
325336 this . #header = header
326- this . content = content
327337 }
328338
329- /**
330- * The content of this part as an `ArrayBuffer`.
331- */
332- get arrayBuffer ( ) : ArrayBuffer {
333- return this . bytes . buffer as ArrayBuffer
334- }
335-
336- /**
337- * The content of this part as a single `Uint8Array`. In `multipart/form-data` messages, this is useful
338- * for reading the value of files that were uploaded using `<input type="file">` fields.
339- */
340- get bytes ( ) : Uint8Array {
341- let buffer = new Uint8Array ( this . size )
342-
343- let offset = 0
344- for ( let chunk of this . content ) {
345- buffer . set ( chunk , offset )
346- offset += chunk . length
347- }
348-
349- return buffer
339+ async append ( chunk : Uint8Array ) {
340+ throw new Error ( "Not implemented. Please assign or override this method." ) ;
350341 }
351342
352343 /**
@@ -395,6 +386,47 @@ export class MultipartPart {
395386 return this . headers . contentDisposition . name
396387 }
397388
389+ }
390+
391+ export class MultipartContentPart extends MultipartPart {
392+
393+ /**
394+ * The raw content of this part as an array of `Uint8Array` chunks.
395+ */
396+ readonly content : Uint8Array [ ]
397+
398+ async append ( chunk : Uint8Array ) : Promise < void > {
399+ this . content . push ( chunk )
400+ }
401+
402+ constructor ( header : Uint8Array , content : Uint8Array [ ] ) {
403+ super ( header ) ;
404+ this . content = content
405+ }
406+
407+ /**
408+ * The content of this part as an `ArrayBuffer`.
409+ */
410+ get arrayBuffer ( ) : ArrayBuffer {
411+ return this . bytes . buffer as ArrayBuffer
412+ }
413+
414+ /**
415+ * The content of this part as a single `Uint8Array`. In `multipart/form-data` messages, this is useful
416+ * for reading the value of files that were uploaded using `<input type="file">` fields.
417+ */
418+ get bytes ( ) : Uint8Array {
419+ let buffer = new Uint8Array ( this . size )
420+
421+ let offset = 0
422+ for ( let chunk of this . content ) {
423+ buffer . set ( chunk , offset )
424+ offset += chunk . length
425+ }
426+
427+ return buffer
428+ }
429+
398430 /**
399431 * The size of the content in bytes.
400432 */
0 commit comments