diff --git a/cli/tests/unit/flash_test.ts b/cli/tests/unit/flash_test.ts index 78340a3901..5e1e55d863 100644 --- a/cli/tests/unit/flash_test.ts +++ b/cli/tests/unit/flash_test.ts @@ -495,6 +495,43 @@ Deno.test( }, ); +Deno.test( + { permissions: { net: true } }, + async function httpServerCorrectLengthForUnicodeString() { + const ac = new AbortController(); + const listeningPromise = deferred(); + + const server = Deno.serve({ + handler: () => new Response("韓國".repeat(10)), + port: 4503, + signal: ac.signal, + onListen: onListen(listeningPromise), + onError: createOnErrorCb(ac), + }); + + await listeningPromise; + const conn = await Deno.connect({ port: 4503 }); + const encoder = new TextEncoder(); + const decoder = new TextDecoder(); + + const body = + `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`; + const writeResult = await conn.write(encoder.encode(body)); + assertEquals(body.length, writeResult); + + const buf = new Uint8Array(1024); + const readResult = await conn.read(buf); + assert(readResult); + const msg = decoder.decode(buf.subarray(0, readResult)); + + conn.close(); + + ac.abort(); + await server; + assert(msg.includes("Content-Length: 60")); + }, +); + Deno.test({ permissions: { net: true } }, async function httpServerWebSocket() { const ac = new AbortController(); const listeningPromise = deferred(); diff --git a/core/01_core.js b/core/01_core.js index e5e2c9fd5d..ddd3ac82d6 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -323,6 +323,7 @@ opNames: () => ops.op_op_names(), eventLoopHasMoreWork: () => ops.op_event_loop_has_more_work(), setPromiseRejectCallback: (fn) => ops.op_set_promise_reject_callback(fn), + byteLength: (str) => ops.op_str_byte_length(str), }); ObjectAssign(globalThis.__bootstrap, { core }); diff --git a/core/ops_builtin.rs b/core/ops_builtin.rs index bd19b74f6b..26ab4bed5a 100644 --- a/core/ops_builtin.rs +++ b/core/ops_builtin.rs @@ -39,6 +39,7 @@ pub(crate) fn init_builtins() -> Extension { op_metrics::decl(), op_format_file_name::decl(), op_is_proxy::decl(), + op_str_byte_length::decl(), ]) .ops(crate::ops_builtin_v8::init_builtins_v8()) .build() @@ -195,3 +196,15 @@ fn op_format_file_name(file_name: String) -> String { fn op_is_proxy(value: serde_v8::Value) -> bool { value.v8_value.is_proxy() } + +#[op(v8)] +fn op_str_byte_length( + scope: &mut v8::HandleScope, + value: serde_v8::Value, +) -> u32 { + if let Ok(string) = v8::Local::::try_from(value.v8_value) { + string.utf8_length(scope) as u32 + } else { + 0 + } +} diff --git a/ext/flash/01_http.js b/ext/flash/01_http.js index b00c9f8e49..949eb0ac31 100644 --- a/ext/flash/01_http.js +++ b/ext/flash/01_http.js @@ -122,7 +122,14 @@ // CRLF // [ message-body ] // - function http1Response(method, status, headerList, body, earlyEnd = false) { + function http1Response( + method, + status, + headerList, + body, + bodyLen, + earlyEnd = false, + ) { // HTTP uses a "." numbering scheme // HTTP-version = HTTP-name "/" DIGIT "." DIGIT // HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive @@ -155,7 +162,7 @@ // null body status is validated by inititalizeAResponse in ext/fetch if (body !== null && body !== undefined) { - str += `Content-Length: ${body.length}\r\n\r\n`; + str += `Content-Length: ${bodyLen}\r\n\r\n`; } else { str += "Transfer-Encoding: chunked\r\n\r\n"; return str; @@ -192,6 +199,7 @@ server, requestId, response, + responseLen, end, respondFast, ) { @@ -215,7 +223,7 @@ } } - if (nwritten < response.length) { + if (nwritten < responseLen) { core.opAsync( "op_flash_respond_async", server, @@ -421,16 +429,19 @@ const ws = resp[_ws]; if (isStreamingResponseBody === false) { + const length = respBody.byteLength || core.byteLength(respBody); const responseStr = http1Response( method, innerResp.status ?? 200, innerResp.headerList, respBody, + length, ); writeFixedResponse( serverId, i, responseStr, + length, !ws, // Don't close socket if there is a deferred websocket upgrade. respondFast, ); @@ -460,6 +471,7 @@ method, innerResp.status ?? 200, innerResp.headerList, + 0, // Content-Length will be set by the op. null, true, ), @@ -483,8 +495,10 @@ method, innerResp.status ?? 200, innerResp.headerList, + respBody.byteLength, null, ), + respBody.byteLength, false, respondFast, );