diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts index c94e8bef1b..0c7aec155a 100644 --- a/cli/tests/unit/http_test.ts +++ b/cli/tests/unit/http_test.ts @@ -543,3 +543,46 @@ unitTest( listener.close(); }, ); + +unitTest({ perms: { net: true } }, async function httpRequestLatin1Headers() { + const promise = (async () => { + const listener = Deno.listen({ port: 4501 }); + for await (const conn of listener) { + const httpConn = Deno.serveHttp(conn); + for await (const { request, respondWith } of httpConn) { + assertEquals(request.headers.get("X-Header-Test"), "á"); + await respondWith( + new Response("", { headers: { "X-Header-Test": "Æ" } }), + ); + httpConn.close(); + } + break; + } + })(); + + const clientConn = await Deno.connect({ port: 4501 }); + const requestText = + "GET / HTTP/1.1\r\nHost: 127.0.0.1:4501\r\nX-Header-Test: á\r\n\r\n"; + const requestBytes = new Uint8Array(requestText.length); + for (let i = 0; i < requestText.length; i++) { + requestBytes[i] = requestText.charCodeAt(i); + } + let written = 0; + while (written < requestBytes.byteLength) { + written += await clientConn.write(requestBytes.slice(written)); + } + + let responseText = ""; + const buf = new Uint8Array(1024); + let read; + while ((read = await clientConn.read(buf)) !== null) { + for (let i = 0; i < read; i++) { + responseText += String.fromCharCode(buf[i]); + } + } + clientConn.close(); + + assert(/\r\n[Xx]-[Hh]eader-[Tt]est: Æ\r\n/.test(responseText)); + + await promise; +}); diff --git a/runtime/ops/http.rs b/runtime/ops/http.rs index 3e8a4ada78..cd1ac9242b 100644 --- a/runtime/ops/http.rs +++ b/runtime/ops/http.rs @@ -14,6 +14,7 @@ use deno_core::futures::StreamExt; use deno_core::op_async; use deno_core::op_sync; use deno_core::AsyncRefCell; +use deno_core::ByteString; use deno_core::CancelHandle; use deno_core::CancelTryFuture; use deno_core::Extension; @@ -140,9 +141,11 @@ struct NextRequestResponse( // response_sender_rid: ResourceId, // method: + // This is a String rather than a ByteString because reqwest will only return + // the method as a str which is guaranteed to be ASCII-only. String, // headers: - Vec<(String, String)>, + Vec<(ByteString, ByteString)>, // url: String, ); @@ -199,9 +202,10 @@ async fn op_http_request_next( let mut headers = Vec::with_capacity(req.headers().len()); for (name, value) in req.headers().iter() { - let name = name.to_string(); - let value = value.to_str().unwrap_or("").to_string(); - headers.push((name, value)); + let name: &[u8] = name.as_ref(); + let value = value.as_bytes(); + headers + .push((ByteString(name.to_owned()), ByteString(value.to_owned()))); } let url = { @@ -346,7 +350,7 @@ struct RespondArgs( // status: u16, // headers: - Vec<(String, String)>, + Vec<(ByteString, ByteString)>, ); async fn op_http_response( @@ -375,7 +379,7 @@ async fn op_http_response( builder.headers_mut().unwrap().reserve(headers.len()); for (key, value) in &headers { - builder = builder.header(key, value); + builder = builder.header(key.as_ref(), value.as_ref()); } let res;