@@ -13,6 +13,28 @@ import {
1313// For Types Only
1414import { BusConnector } from "../bus.js" ;
1515
16+ // Module-scoped encoder/decoder singletons (avoids repeated allocations).
17+ const textEncoder = new TextEncoder ( ) ;
18+ const textDecoder = new TextDecoder ( ) ;
19+
20+ /** Find the first occurrence of `needle` bytes in `haystack`. Returns -1 if not found. */
21+ function indexOfBytes ( haystack , needle )
22+ {
23+ outer:
24+ for ( let i = 0 ; i <= haystack . length - needle . length ; i ++ )
25+ {
26+ for ( let j = 0 ; j < needle . length ; j ++ )
27+ {
28+ if ( haystack [ i + j ] !== needle [ j ] ) continue outer;
29+ }
30+ return i ;
31+ }
32+ return - 1 ;
33+ }
34+
35+ // Pre-encoded \r\n\r\n separator for binary header/body split.
36+ const CRLFCRLF = textEncoder . encode ( "\r\n\r\n" ) ;
37+
1638/**
1739 * @constructor
1840 *
@@ -69,116 +91,164 @@ FetchNetworkAdapter.prototype.tcp_probe = function(port)
6991} ;
7092
7193/**
94+ * HTTP data handler for port-80 TCP connections.
95+ *
96+ * Incoming TCP segments are buffered as raw bytes and searched for the
97+ * \r\n\r\n header/body separator. Once found the header portion alone is
98+ * text-decoded and parsed; the body stays as a binary Uint8Array.
99+ *
100+ * When a POST/PUT body spans multiple TCP segments the partial body is
101+ * stored in this._xb and subsequent segments are accumulated there until
102+ * Content-Length is satisfied, at which point the deferred fetch is fired.
103+ *
104+ * NOTE: Transfer-Encoding: chunked is not supported. Requests using it
105+ * will not be dispatched.
106+ *
72107 * @this {TCPConnection}
73108 * @param {!ArrayBuffer } data
74109 */
75110async function on_data_http ( data )
76111{
77112 // If we're buffering a partial request body, accumulate chunks until
78113 // Content-Length is satisfied, then fire the deferred fetch.
79- if ( this . _xb ) {
114+ if ( this . _xb )
115+ {
80116 const chunk = data instanceof Uint8Array ? data : new Uint8Array ( data ) ;
81117 const combined = new Uint8Array ( this . _xb . buf . length + chunk . length ) ;
82118 combined . set ( this . _xb . buf ) ;
83119 combined . set ( chunk , this . _xb . buf . length ) ;
84120 this . _xb . buf = combined ;
85- if ( this . _xb . buf . length >= this . _xb . cl ) {
86- const body = new TextDecoder ( ) . decode ( this . _xb . buf ) ;
121+ if ( this . _xb . buf . length >= this . _xb . cl )
122+ {
123+ const body = this . _xb . buf ;
87124 const done = this . _xb . done ;
88125 this . _xb = null ;
89126 done ( body ) ;
90127 }
91128 return ;
92129 }
93130
94- this . read = this . read || "" ;
95- this . read += new TextDecoder ( ) . decode ( data ) ;
96- if ( this . read && this . read . indexOf ( "\r\n\r\n" ) !== - 1 ) {
97- let offset = this . read . indexOf ( "\r\n\r\n" ) ;
98- let headers = this . read . substring ( 0 , offset ) . split ( / \r \n / ) ;
99- let data = this . read . substring ( offset + 4 ) ;
100- this . read = "" ;
101-
102- let first_line = headers [ 0 ] . split ( " " ) ;
103- let target ;
104- if ( / ^ h t t p s ? : / . test ( first_line [ 1 ] ) ) {
105- // HTTP proxy
106- target = new URL ( first_line [ 1 ] ) ;
131+ // Accumulate raw bytes (not text) so binary body data is preserved.
132+ const chunk = data instanceof Uint8Array ? data : new Uint8Array ( data ) ;
133+ if ( this . _raw )
134+ {
135+ const combined = new Uint8Array ( this . _raw . length + chunk . length ) ;
136+ combined . set ( this . _raw ) ;
137+ combined . set ( chunk , this . _raw . length ) ;
138+ this . _raw = combined ;
139+ }
140+ else
141+ {
142+ this . _raw = chunk ;
143+ }
144+
145+ const sep_index = indexOfBytes ( this . _raw , CRLFCRLF ) ;
146+ if ( sep_index === - 1 ) return ;
147+
148+ // Split into header (text) and body (binary).
149+ const headerBytes = this . _raw . slice ( 0 , sep_index ) ;
150+ const bodyBytes = this . _raw . slice ( sep_index + CRLFCRLF . length ) ;
151+ this . _raw = null ;
152+
153+ const headerText = textDecoder . decode ( headerBytes ) ;
154+ const headerLines = headerText . split ( / \r \n / ) ;
155+
156+ const first_line = headerLines [ 0 ] . split ( " " ) ;
157+ let target ;
158+ if ( / ^ h t t p s ? : / . test ( first_line [ 1 ] ) )
159+ {
160+ // HTTP proxy
161+ target = new URL ( first_line [ 1 ] ) ;
162+ }
163+ else
164+ {
165+ target = new URL ( "http://host" + first_line [ 1 ] ) ;
166+ }
167+ if ( typeof window !== "undefined" && target . protocol === "http:" && window . location . protocol === "https:" )
168+ {
169+ // fix "Mixed Content" errors
170+ target . protocol = "https:" ;
171+ }
172+
173+ const req_headers = new Headers ( ) ;
174+ for ( let i = 1 ; i < headerLines . length ; ++ i )
175+ {
176+ const header = this . net . parse_http_header ( headerLines [ i ] ) ;
177+ if ( ! header )
178+ {
179+ console . warn ( 'The request contains an invalid header: "%s"' , headerLines [ i ] ) ;
180+ this . net . respond_text_and_close ( this , 400 , "Bad Request" , `Invalid header in request: ${ headerLines [ i ] } ` ) ;
181+ return ;
107182 }
108- else {
109- target = new URL ( "http://host" + first_line [ 1 ] ) ;
183+ if ( header . key . toLowerCase ( ) === "host" ) target . host = header . value ;
184+ else req_headers . append ( header . key , header . value ) ;
185+ }
186+
187+ if ( ! this . net . cors_proxy && / ^ \d + \. e x t e r n a l $ / . test ( target . hostname ) )
188+ {
189+ dbg_log ( "Request to localhost: " + target . href , LOG_FETCH ) ;
190+ const localport = parseInt ( target . hostname . split ( "." ) [ 0 ] , 10 ) ;
191+ if ( ! isNaN ( localport ) && localport > 0 && localport < 65536 )
192+ {
193+ target . protocol = "http:" ;
194+ target . hostname = "localhost" ;
195+ target . port = localport . toString ( 10 ) ;
110196 }
111- if ( typeof window !== "undefined" && target . protocol === "http:" && window . location . protocol === "https:" ) {
112- // fix "Mixed Content" errors
113- target . protocol = "https:" ;
197+ else
198+ {
199+ console . warn ( 'Unknown port for localhost: "%s"' , target . href ) ;
200+ this . net . respond_text_and_close ( this , 400 , "Bad Request" , `Unknown port for localhost: ${ target . href } ` ) ;
201+ return ;
114202 }
203+ }
115204
116- let req_headers = new Headers ( ) ;
117- for ( let i = 1 ; i < headers . length ; ++ i ) {
118- const header = this . net . parse_http_header ( headers [ i ] ) ;
119- if ( ! header ) {
120- console . warn ( 'The request contains an invalid header: "%s"' , headers [ i ] ) ;
121- this . net . respond_text_and_close ( this , 400 , "Bad Request" , `Invalid header in request: ${ headers [ i ] } ` ) ;
122- return ;
123- }
124- if ( header . key . toLowerCase ( ) === "host" ) target . host = header . value ;
125- else req_headers . append ( header . key , header . value ) ;
126- }
205+ dbg_log ( "HTTP Dispatch: " + target . href , LOG_FETCH ) ;
206+ this . name = target . href ;
127207
128- if ( ! this . net . cors_proxy && / ^ \d + \. e x t e r n a l $ / . test ( target . hostname ) ) {
129- dbg_log ( "Request to localhost: " + target . href , LOG_FETCH ) ;
130- const localport = parseInt ( target . hostname . split ( "." ) [ 0 ] , 10 ) ;
131- if ( ! isNaN ( localport ) && localport > 0 && localport < 65536 ) {
132- target . protocol = "http:" ;
133- target . hostname = "localhost" ;
134- target . port = localport . toString ( 10 ) ;
135- } else {
136- console . warn ( 'Unknown port for localhost: "%s"' , target . href ) ;
137- this . net . respond_text_and_close ( this , 400 , "Bad Request" , `Unknown port for localhost: ${ target . href } ` ) ;
138- return ;
139- }
140- }
208+ const opts = {
209+ method : first_line [ 0 ] ,
210+ headers : req_headers ,
211+ } ;
141212
142- dbg_log ( "HTTP Dispatch: " + target . href , LOG_FETCH ) ;
143- this . name = target . href ;
144- let opts = {
145- method : first_line [ 0 ] ,
146- headers : req_headers ,
147- } ;
148- if ( [ "put" , "post" ] . indexOf ( opts . method . toLowerCase ( ) ) !== - 1 ) {
149- // Check if the body might be split across multiple TCP segments.
150- // If Content-Length is present and larger than what we have,
151- // buffer and wait for the remaining chunks.
152- const content_length = parseInt ( req_headers . get ( "content-length" ) || "0" , 10 ) ;
153- const body_bytes = ( data instanceof Uint8Array ) ? data : new TextEncoder ( ) . encode ( data ) ;
154- if ( content_length > 0 && body_bytes . length < content_length ) {
155- const fetch_url = this . net . cors_proxy ? this . net . cors_proxy + encodeURIComponent ( target . href ) : target . href ;
156- this . _xb = {
157- buf : body_bytes ,
158- cl : content_length ,
159- done : ( body ) => {
160- opts . body = body ;
161- this . _dispatch_fetch ( fetch_url , opts ) ;
162- } ,
163- } ;
164- return ;
165- }
166- opts . body = data ;
213+ if ( [ "put" , "post" ] . indexOf ( opts . method . toLowerCase ( ) ) !== - 1 )
214+ {
215+ // The body may span multiple TCP segments.
216+ // If Content-Length is present and larger than what we have so far,
217+ // buffer the partial body and wait for remaining chunks.
218+ const content_length = parseInt ( req_headers . get ( "content-length" ) || "0" , 10 ) ;
219+ if ( content_length > 0 && bodyBytes . length < content_length )
220+ {
221+ const fetch_url = this . net . cors_proxy
222+ ? this . net . cors_proxy + encodeURIComponent ( target . href )
223+ : target . href ;
224+ this . _xb = {
225+ buf : bodyBytes ,
226+ cl : content_length ,
227+ done : ( body ) => {
228+ opts . body = body ;
229+ dispatch_fetch ( this , fetch_url , opts ) ;
230+ } ,
231+ } ;
232+ return ;
167233 }
168-
169- const fetch_url = this . net . cors_proxy ? this . net . cors_proxy + encodeURIComponent ( target . href ) : target . href ;
170- this . _dispatch_fetch ( fetch_url , opts ) ;
234+ opts . body = bodyBytes ;
171235 }
236+
237+ const fetch_url = this . net . cors_proxy
238+ ? this . net . cors_proxy + encodeURIComponent ( target . href )
239+ : target . href ;
240+ dispatch_fetch ( this , fetch_url , opts ) ;
172241}
173242
174243/**
175- * @this {TCPConnection}
244+ * Execute the HTTP fetch and pipe the response back to the guest.
245+ *
246+ * @param {TCPConnection } conn
176247 * @param {string } fetch_url
177248 * @param {!Object } opts
178249 */
179- FetchNetworkAdapter . prototype . _dispatch_fetch = function ( fetch_url , opts )
250+ function dispatch_fetch ( conn , fetch_url , opts )
180251{
181- const encoder = new TextEncoder ( ) ;
182252 let response_started = false ;
183253 let handler = ( resp ) => {
184254 let resp_headers = new Headers ( resp . headers ) ;
@@ -190,40 +260,47 @@ FetchNetworkAdapter.prototype._dispatch_fetch = function(fetch_url, opts)
190260 resp_headers . set ( "x-fetch-resp-url" , resp . url ) ;
191261 resp_headers . set ( "connection" , "close" ) ;
192262
193- this . write ( this . net . form_response_head ( resp . status , resp . statusText , resp_headers ) ) ;
263+ conn . write ( conn . net . form_response_head ( resp . status , resp . statusText , resp_headers ) ) ;
194264 response_started = true ;
195265
196- if ( resp . body && resp . body . getReader ) {
266+ if ( resp . body && resp . body . getReader )
267+ {
197268 const resp_reader = resp . body . getReader ( ) ;
198269 const pump = ( { value, done } ) => {
199- if ( value ) {
200- this . write ( value ) ;
270+ if ( value )
271+ {
272+ conn . write ( value ) ;
201273 }
202- if ( done ) {
203- this . close ( ) ;
274+ if ( done )
275+ {
276+ conn . close ( ) ;
204277 }
205- else {
278+ else
279+ {
206280 return resp_reader . read ( ) . then ( pump ) ;
207281 }
208282 } ;
209283 resp_reader . read ( ) . then ( pump ) ;
210- } else {
284+ }
285+ else
286+ {
211287 resp . arrayBuffer ( ) . then ( buffer => {
212- this . write ( new Uint8Array ( buffer ) ) ;
213- this . close ( ) ;
288+ conn . write ( new Uint8Array ( buffer ) ) ;
289+ conn . close ( ) ;
214290 } ) ;
215291 }
216292 } ;
217293
218- this . net . fetch ( fetch_url , opts ) . then ( handler )
294+ conn . net . fetch ( fetch_url , opts ) . then ( handler )
219295 . catch ( ( e ) => {
220296 console . warn ( "Fetch Failed: " + fetch_url + "\n" + e ) ;
221- if ( ! response_started ) {
222- this . net . respond_text_and_close ( this , 502 , "Fetch Error" , `Fetch ${ fetch_url } failed:\n\n${ e . stack || e . message } ` ) ;
297+ if ( ! response_started )
298+ {
299+ conn . net . respond_text_and_close ( conn , 502 , "Fetch Error" , `Fetch ${ fetch_url } failed:\n\n${ e . stack || e . message } ` ) ;
223300 }
224- this . close ( ) ;
301+ conn . close ( ) ;
225302 } ) ;
226- } ;
303+ }
227304
228305FetchNetworkAdapter . prototype . fetch = async function ( url , options )
229306{
@@ -244,7 +321,7 @@ FetchNetworkAdapter.prototype.fetch = async function(url, options)
244321 statusText : "Fetch Error" ,
245322 headers : new Headers ( { "Content-Type" : "text/plain" } ) ,
246323 } ,
247- new TextEncoder ( ) . encode ( `Fetch ${ url } failed:\n\n${ e . stack } ` ) . buffer
324+ textEncoder . encode ( `Fetch ${ url } failed:\n\n${ e . stack } ` ) . buffer
248325 ] ;
249326 }
250327} ;
@@ -255,11 +332,12 @@ FetchNetworkAdapter.prototype.form_response_head = function(status_code, status_
255332 `HTTP/1.1 ${ status_code } ${ status_text } `
256333 ] ;
257334
258- for ( const [ key , value ] of headers . entries ( ) ) {
335+ for ( const [ key , value ] of headers . entries ( ) )
336+ {
259337 lines . push ( `${ key } : ${ value } ` ) ;
260338 }
261339
262- return new TextEncoder ( ) . encode ( lines . join ( "\r\n" ) + "\r\n\r\n" ) ;
340+ return textEncoder . encode ( lines . join ( "\r\n" ) + "\r\n\r\n" ) ;
263341} ;
264342
265343FetchNetworkAdapter . prototype . respond_text_and_close = function ( conn , status_code , status_text , body )
@@ -269,14 +347,15 @@ FetchNetworkAdapter.prototype.respond_text_and_close = function(conn, status_cod
269347 "content-length" : body . length . toString ( 10 ) ,
270348 "connection" : "close"
271349 } ) ;
272- conn . writev ( [ this . form_response_head ( status_code , status_text , headers ) , new TextEncoder ( ) . encode ( body ) ] ) ;
350+ conn . writev ( [ this . form_response_head ( status_code , status_text , headers ) , textEncoder . encode ( body ) ] ) ;
273351 conn . close ( ) ;
274352} ;
275353
276354FetchNetworkAdapter . prototype . parse_http_header = function ( header )
277355{
278356 const parts = header . match ( / ^ ( [ ^ : ] * ) : ( .* ) $ / ) ;
279- if ( ! parts ) {
357+ if ( ! parts )
358+ {
280359 dbg_log ( "Unable to parse HTTP header" , LOG_FETCH ) ;
281360 return ;
282361 }
0 commit comments