@@ -5,74 +5,103 @@ interface MultipartResult {
55 lastProcessedLength : number ;
66}
77
8- const CRLF = '\r\n' ;
9- const HEADER_VALUE_DELIMITER = ': ' ;
8+ const CRLF_BYTES = new Uint8Array ( [ 13 , 10 ] ) ; // \r\n
9+ const HEADER_VALUE_DELIMITER_BYTES = new TextEncoder ( ) . encode ( ': ' ) ;
10+
11+ function arrayIndexOf ( haystack : Uint8Array , needle : Uint8Array , start = 0 ) : number {
12+ for ( let i = start ; i <= haystack . length - needle . length ; i ++ ) {
13+ let found = true ;
14+ for ( let j = 0 ; j < needle . length ; j ++ ) {
15+ if ( haystack [ i + j ] !== needle [ j ] ) {
16+ found = false ;
17+ break ;
18+ }
19+ }
20+ if ( found ) {
21+ return i ;
22+ }
23+ }
24+ return - 1 ;
25+ }
1026
11- // temporary spike for backend problems
12- const BACKEND_LAST_CHUNK_BOUNDARY = '--boundary--' ;
27+ function arrayStartsWith ( array : Uint8Array , prefix : Uint8Array , start = 0 ) : boolean {
28+ if ( start + prefix . length > array . length ) {
29+ return false ;
30+ }
31+ for ( let i = 0 ; i < prefix . length ; i ++ ) {
32+ if ( array [ start + i ] !== prefix [ i ] ) {
33+ return false ;
34+ }
35+ }
36+ return true ;
37+ }
1338
14- function getContentHeadersData ( data : string , startPos : number ) : [ number , number ] {
39+ function getContentHeadersData ( data : Uint8Array , startPos : number ) : [ number , number ] {
1540 let contentLength = 0 ;
1641 let pos = startPos ;
42+ const decoder = new TextDecoder ( ) ;
1743
1844 while ( pos < data . length ) {
1945 // Check for end of headers
20- if ( data . startsWith ( CRLF , pos ) ) {
21- return [ contentLength , pos + CRLF . length ] ;
46+ if ( arrayStartsWith ( data , CRLF_BYTES , pos ) ) {
47+ return [ contentLength , pos + CRLF_BYTES . length ] ;
2248 }
2349
24- const nextCRLF = data . indexOf ( CRLF , pos ) ;
50+ const nextCRLF = arrayIndexOf ( data , CRLF_BYTES , pos ) ;
2551 if ( nextCRLF === - 1 ) {
2652 // Headers are incomplete
2753 return [ contentLength , startPos ] ;
2854 }
2955
3056 const line = data . slice ( pos , nextCRLF ) ;
31- const colonIndex = line . indexOf ( HEADER_VALUE_DELIMITER ) ;
57+ const colonIndex = arrayIndexOf ( line , HEADER_VALUE_DELIMITER_BYTES ) ;
3258
3359 if ( colonIndex !== - 1 ) {
34- const header = line . slice ( 0 , colonIndex ) . toLowerCase ( ) ;
35- const value = line . slice ( colonIndex + HEADER_VALUE_DELIMITER . length , nextCRLF ) ;
60+ const header = decoder . decode ( line . slice ( 0 , colonIndex ) ) . toLowerCase ( ) ;
61+ const value = decoder . decode (
62+ line . slice ( colonIndex + HEADER_VALUE_DELIMITER_BYTES . length ) ,
63+ ) ;
3664
37- if ( header . toLowerCase ( ) === 'content-length' ) {
65+ if ( header === 'content-length' ) {
3866 const length = parseInt ( value , 10 ) ;
3967 if ( ! isNaN ( length ) ) {
4068 contentLength = length ;
4169 }
4270 }
4371 }
4472
45- pos = nextCRLF + CRLF . length ;
73+ pos = nextCRLF + CRLF_BYTES . length ;
4674 }
4775
4876 // Headers are incomplete
4977 return [ contentLength , startPos ] ;
5078}
5179
5280export function parseMultipart ( {
53- responseText ,
81+ response ,
5482 lastProcessedLength,
5583 boundary = 'boundary' ,
5684} : {
57- responseText : string ;
85+ response : ArrayBuffer ;
5886 lastProcessedLength : number ;
5987 boundary ?: string ;
6088} ) : MultipartResult {
61- const data = responseText ;
62- const boundaryStr = `--${ boundary } ${ CRLF } ` ;
89+ const data = new Uint8Array ( response ) ;
90+ const boundaryBytes = new TextEncoder ( ) . encode ( `--${ boundary } \r\n` ) ;
6391 let pos = lastProcessedLength ;
6492 let lastProcessedPos = pos ;
6593 const chunks : StreamingChunk [ ] = [ ] ;
94+ const decoder = new TextDecoder ( ) ;
6695
6796 while ( pos < data . length ) {
6897 // Look for boundary
69- const boundaryPos = data . indexOf ( boundaryStr , pos ) ;
98+ const boundaryPos = arrayIndexOf ( data , boundaryBytes , pos ) ;
7099 if ( boundaryPos === - 1 ) {
71100 break ;
72101 }
73102
74103 // Move position past boundary
75- pos = boundaryPos + boundaryStr . length ;
104+ pos = boundaryPos + boundaryBytes . length ;
76105
77106 // Parse headers
78107 const [ contentLength , contentStart ] = getContentHeadersData ( data , pos ) ;
@@ -83,21 +112,15 @@ export function parseMultipart({
83112 }
84113
85114 // Check if we have enough data for the content
86- let contentEnd = contentStart + contentLength ;
115+ const contentEnd = contentStart + contentLength ;
87116 if ( contentEnd > data . length ) {
88- // last chunk has error with --boundary-- in content length
89- // eslint-disable-next-line no-negated-condition
90- if ( data . indexOf ( BACKEND_LAST_CHUNK_BOUNDARY , contentStart ) !== - 1 ) {
91- contentEnd = data . indexOf ( BACKEND_LAST_CHUNK_BOUNDARY , contentStart ) - 2 ;
92- } else {
93- // Content is incomplete
94- pos = lastProcessedLength ;
95- break ;
96- }
117+ // Content is incomplete
118+ pos = lastProcessedLength ;
119+ break ;
97120 }
98121
99- // Extract content
100- const content = data . slice ( contentStart , contentEnd ) ;
122+ // Extract content and decode to string
123+ const content = decoder . decode ( data . slice ( contentStart , contentEnd ) ) ;
101124
102125 // Try to parse JSON content
103126 try {
0 commit comments