@@ -181,23 +181,23 @@ export class Multipart implements Part {
181181 console . warn ( "Invalid boundary:" , new TextDecoder ( ) . decode ( boundary ) , "\nMust be 1 to 70 characters long, not end with space, and may only contain: A-Z a-z 0-9 '()+_,-./:=? and space" ) ;
182182
183183 const parts : Uint8Array [ ] = [ ] ;
184- const fullBoundarySequence = new Uint8Array ( Multipart . combineArrays ( [ Multipart . DOUBLE_DASH , boundary , Multipart . CRLF ] ) ) ;
185- const endBoundarySequence = new Uint8Array ( Multipart . combineArrays ( [ Multipart . DOUBLE_DASH , boundary , Multipart . DOUBLE_DASH , Multipart . CRLF ] ) ) ;
186184
187- let start = 0 ;
188- while ( true ) {
189- const boundaryIndex = Multipart . findSequenceIndex ( data , fullBoundarySequence , start ) ;
190- if ( boundaryIndex === - 1 ) break ;
191-
192- const partStart = boundaryIndex + fullBoundarySequence . length ;
193- const nextBoundaryIndex = Multipart . findSequenceIndex ( data , fullBoundarySequence , partStart ) ;
194- const endBoundaryIndex = Multipart . findSequenceIndex ( data , endBoundarySequence , partStart ) ;
185+ // add artificial CRLF at the start of the data
186+ const paddedData = Multipart . combineArrays ( [ Multipart . CRLF , data ] ) ;
187+ const closingBoundaryDelimiter = Multipart . combineArrays ( [ boundary , Multipart . DOUBLE_DASH ] ) ;
195188
196- // -2 to ignore the mandatory CRLF at the end of the body
197- const partEnd = nextBoundaryIndex === - 1 ? ( endBoundaryIndex === - 1 ? data . length : endBoundaryIndex - 2 ) : nextBoundaryIndex - 2 ;
198-
199- if ( partStart < partEnd ) parts . push ( data . slice ( partStart , partEnd ) ) ;
200- start = partEnd ;
189+ let start = 0 ;
190+ while ( start < paddedData . length ) {
191+ const boundaryIndices = Multipart . findBoundaryBounds ( paddedData , boundary , start ) ;
192+ if ( boundaryIndices === null ) break ;
193+ const [ , boundaryEnd ] = boundaryIndices ;
194+ const nextBoundaryIndices =
195+ Multipart . findBoundaryBounds ( paddedData , boundary , boundaryEnd + 1 )
196+ ?? Multipart . findBoundaryBounds ( paddedData , closingBoundaryDelimiter , boundaryEnd + 1 ) ;
197+ if ( nextBoundaryIndices === null ) break ;
198+ const [ nextBoundaryStart ] = nextBoundaryIndices ;
199+ parts . push ( paddedData . slice ( boundaryEnd , nextBoundaryStart ) ) ;
200+ start = nextBoundaryStart ;
201201 }
202202
203203 const parsedParts = parts . map ( Component . parse ) ;
@@ -267,6 +267,35 @@ export class Multipart implements Part {
267267 return - 1 ;
268268 }
269269
270+ /**
271+ * Find boundary delimiter start and end index
272+ * @param data Multipart body bytes
273+ * @param boundary The multipart boundary bytes
274+ * @param [start] The index to start the search at (i.e. the number of bytes to skip/ignore at the beginning of the byte array). Defaults to 0.
275+ * @returns The start and end index of the boundary delimiter, or `null` if no boundary delimiter can be found
276+ * @internal
277+ */
278+ private static findBoundaryBounds ( data : Uint8Array , boundary : Uint8Array , start = 0 ) : [ number , number ] | null {
279+ if ( start >= data . length ) return null ;
280+ const boundaryStartIndex = Multipart . findSequenceIndex ( data , Multipart . combineArrays ( [ Multipart . CRLF , Multipart . DOUBLE_DASH , boundary ] ) , start ) ;
281+ if ( boundaryStartIndex === - 1 ) return null ;
282+ let currentEndOfBoundaryIndex = boundaryStartIndex + boundary . length + 4 ;
283+ while ( currentEndOfBoundaryIndex < data . length ) {
284+ const byte = data [ currentEndOfBoundaryIndex ] ;
285+ if ( byte === Multipart . CR && data [ currentEndOfBoundaryIndex + 1 ] === Multipart . LF )
286+ return [ boundaryStartIndex , currentEndOfBoundaryIndex + 2 ] ;
287+ if ( byte === Multipart . SP || byte === 0x09 ) {
288+ currentEndOfBoundaryIndex ++ ;
289+ continue ;
290+ }
291+ // encountered non-linear whitespace after boundary and before any CRLF
292+ // meaning the boundary could not be terminated, therefore continue search for boundary
293+ return Multipart . findBoundaryBounds ( data , boundary , boundaryStartIndex + 2 ) ;
294+ }
295+
296+ return null ;
297+ }
298+
270299 /**
271300 * Parse header params in the format `key=value;foo = "bar"; baz`
272301 */
0 commit comments