diff options
Diffstat (limited to 'cli/tests/unit/flash_test.ts')
-rw-r--r-- | cli/tests/unit/flash_test.ts | 2533 |
1 files changed, 0 insertions, 2533 deletions
diff --git a/cli/tests/unit/flash_test.ts b/cli/tests/unit/flash_test.ts deleted file mode 100644 index d32e0a54f..000000000 --- a/cli/tests/unit/flash_test.ts +++ /dev/null @@ -1,2533 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -// deno-lint-ignore-file - -import { Buffer, BufReader, BufWriter } from "../../../test_util/std/io/mod.ts"; -import { TextProtoReader } from "../testdata/run/textproto.ts"; -import { - assert, - assertEquals, - assertRejects, - assertStringIncludes, - assertThrows, - Deferred, - deferred, - fail, -} from "./test_util.ts"; - -function createOnErrorCb(ac: AbortController): (err: unknown) => Response { - return (err) => { - console.error(err); - ac.abort(); - return new Response("Internal server error", { status: 500 }); - }; -} - -function onListen<T>( - p: Deferred<T>, -): ({ hostname, port }: { hostname: string; port: number }) => void { - return () => { - p.resolve(); - }; -} - -Deno.test(async function httpServerCanResolveHostnames() { - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: (_req) => new Response("ok"), - hostname: "localhost", - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const resp = await fetch("http://localhost:4501/", { - headers: { "connection": "close" }, - }); - const text = await resp.text(); - assertEquals(text, "ok"); - ac.abort(); - await server; -}); - -Deno.test(async function httpServerRejectsOnAddrInUse() { - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: (_req) => new Response("ok"), - hostname: "localhost", - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - assertRejects( - () => - Deno.serve({ - handler: (_req) => new Response("ok"), - hostname: "localhost", - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }), - Deno.errors.AddrInUse, - ); - ac.abort(); - await server; -}); - -Deno.test({ permissions: { net: true } }, async function httpServerBasic() { - const ac = new AbortController(); - const promise = deferred(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: async (request, { remoteAddr }) => { - // FIXME(bartlomieju): - // make sure that request can be inspected - console.log(request); - assertEquals(new URL(request.url).href, "http://127.0.0.1:4501/"); - assertEquals(await request.text(), ""); - assertEquals(remoteAddr.hostname, "127.0.0.1"); - promise.resolve(); - return new Response("Hello World", { headers: { "foo": "bar" } }); - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const resp = await fetch("http://127.0.0.1:4501/", { - headers: { "connection": "close" }, - }); - await promise; - const clone = resp.clone(); - const text = await resp.text(); - assertEquals(text, "Hello World"); - assertEquals(resp.headers.get("foo"), "bar"); - const cloneText = await clone.text(); - assertEquals(cloneText, "Hello World"); - ac.abort(); - await server; -}); - -Deno.test({ permissions: { net: true } }, async function httpServerOverload1() { - const ac = new AbortController(); - const promise = deferred(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }, async (request) => { - // FIXME(bartlomieju): - // make sure that request can be inspected - console.log(request); - assertEquals(new URL(request.url).href, "http://127.0.0.1:4501/"); - assertEquals(await request.text(), ""); - promise.resolve(); - return new Response("Hello World", { headers: { "foo": "bar" } }); - }); - - await listeningPromise; - const resp = await fetch("http://127.0.0.1:4501/", { - headers: { "connection": "close" }, - }); - await promise; - const clone = resp.clone(); - const text = await resp.text(); - assertEquals(text, "Hello World"); - assertEquals(resp.headers.get("foo"), "bar"); - const cloneText = await clone.text(); - assertEquals(cloneText, "Hello World"); - ac.abort(); - await server; -}); - -Deno.test({ permissions: { net: true } }, async function httpServerOverload2() { - const ac = new AbortController(); - const promise = deferred(); - const listeningPromise = deferred(); - - const server = Deno.serve(async (request) => { - // FIXME(bartlomieju): - // make sure that request can be inspected - console.log(request); - assertEquals(new URL(request.url).href, "http://127.0.0.1:4501/"); - assertEquals(await request.text(), ""); - promise.resolve(); - return new Response("Hello World", { headers: { "foo": "bar" } }); - }, { - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const resp = await fetch("http://127.0.0.1:4501/", { - headers: { "connection": "close" }, - }); - await promise; - const clone = resp.clone(); - const text = await resp.text(); - assertEquals(text, "Hello World"); - assertEquals(resp.headers.get("foo"), "bar"); - const cloneText = await clone.text(); - assertEquals(cloneText, "Hello World"); - ac.abort(); - await server; -}); - -Deno.test( - { permissions: { net: true } }, - async function httpServerErrorOverloadMissingHandler() { - // @ts-ignore - testing invalid overload - await assertRejects(() => Deno.serve(), TypeError, "handler"); - // @ts-ignore - testing invalid overload - await assertRejects(() => Deno.serve({}), TypeError, "handler"); - await assertRejects( - // @ts-ignore - testing invalid overload - () => Deno.serve({ handler: undefined }), - TypeError, - "handler", - ); - await assertRejects( - // @ts-ignore - testing invalid overload - () => Deno.serve(undefined, { handler: () => {} }), - TypeError, - "handler", - ); - }, -); - -Deno.test({ permissions: { net: true } }, async function httpServerPort0() { - const ac = new AbortController(); - - const server = Deno.serve({ - handler() { - return new Response("Hello World"); - }, - port: 0, - signal: ac.signal, - onListen({ port }) { - assert(port > 0 && port < 65536); - ac.abort(); - }, - }); - await server; -}); - -Deno.test( - { permissions: { net: true } }, - async function httpServerDefaultOnListenCallback() { - const ac = new AbortController(); - - const consoleLog = console.log; - console.log = (msg) => { - try { - const match = msg.match(/Listening on http:\/\/localhost:(\d+)\//); - assert(!!match); - const port = +match[1]; - assert(port > 0 && port < 65536); - } finally { - ac.abort(); - } - }; - - try { - const server = Deno.serve({ - handler() { - return new Response("Hello World"); - }, - hostname: "0.0.0.0", - port: 0, - signal: ac.signal, - }); - - await server; - } finally { - console.log = consoleLog; - } - }, -); - -// https://github.com/denoland/deno/issues/15107 -Deno.test( - { permissions: { net: true } }, - async function httpLazyHeadersIssue15107() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - let headers: Headers; - const server = Deno.serve({ - handler: async (request) => { - await request.text(); - headers = request.headers; - promise.resolve(); - return new Response(""); - }, - port: 2333, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const conn = await Deno.connect({ port: 2333 }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - conn.close(); - assertEquals(headers!.get("content-length"), "5"); - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpReadHeadersAfterClose() { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - - let req: Request; - const server = Deno.serve({ - handler: async (request) => { - await request.text(); - req = request; - promise.resolve(); - return new Response("Hello World"); - }, - port: 2334, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const conn = await Deno.connect({ port: 2334 }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - conn.close(); - - assertThrows( - () => { - req.headers; - }, - TypeError, - "request closed", - ); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerGetRequestBody() { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: (request) => { - assertEquals(request.body, null); - promise.resolve(); - return new Response("", { headers: {} }); - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const conn = await Deno.connect({ port: 4501 }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:4501\r\nContent-Length: 5\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - const resp = new Uint8Array(200); - const readResult = await conn.read(resp); - assert(readResult); - assert(readResult > 0); - - conn.close(); - await promise; - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerStreamResponse() { - const stream = new TransformStream(); - const writer = stream.writable.getWriter(); - writer.write(new TextEncoder().encode("hello ")); - writer.write(new TextEncoder().encode("world")); - writer.close(); - - const listeningPromise = deferred(); - const ac = new AbortController(); - const server = Deno.serve({ - handler: (request) => { - assert(!request.body); - return new Response(stream.readable); - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const resp = await fetch("http://127.0.0.1:4501/"); - const respBody = await resp.text(); - assertEquals("hello world", respBody); - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerStreamRequest() { - const stream = new TransformStream(); - const writer = stream.writable.getWriter(); - writer.write(new TextEncoder().encode("hello ")); - writer.write(new TextEncoder().encode("world")); - writer.close(); - const listeningPromise = deferred(); - const ac = new AbortController(); - const server = Deno.serve({ - handler: async (request) => { - const reqBody = await request.text(); - assertEquals("hello world", reqBody); - return new Response("yo"); - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const resp = await fetch("http://127.0.0.1:4501/", { - body: stream.readable, - method: "POST", - headers: { "connection": "close" }, - }); - - assertEquals(await resp.text(), "yo"); - ac.abort(); - await server; - }, -); - -Deno.test({ permissions: { net: true } }, async function httpServerClose() { - const ac = new AbortController(); - const listeningPromise = deferred(); - const server = Deno.serve({ - handler: () => new Response("ok"), - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - await listeningPromise; - const client = await Deno.connect({ port: 4501 }); - client.close(); - ac.abort(); - await server; -}); - -// FIXME: -Deno.test( - { permissions: { net: true } }, - async function httpServerEmptyBlobResponse() { - const ac = new AbortController(); - const listeningPromise = deferred(); - const server = Deno.serve({ - handler: () => new Response(new Blob([])), - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const resp = await fetch("http://127.0.0.1:4501/"); - const respBody = await resp.text(); - - assertEquals("", respBody); - ac.abort(); - await server; - }, -); - -// https://github.com/denoland/deno/issues/17291 -Deno.test( - { permissions: { net: true } }, - async function httpServerIncorrectChunkedResponse() { - const ac = new AbortController(); - const listeningPromise = deferred(); - const errorPromise = deferred(); - const server = Deno.serve({ - handler: () => { - const body = new ReadableStream({ - start(controller) { - // Non-encoded string is not a valid readable chunk. - controller.enqueue("wat"); - }, - }); - return new Response(body); - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: (err) => { - const errResp = new Response( - `Internal server error: ${(err as Error).message}`, - { status: 500 }, - ); - ac.abort(); - errorPromise.resolve(errResp); - return errResp; - }, - }); - - await listeningPromise; - - const resp = await fetch("http://127.0.0.1:4501/"); - // Incorrectly implemented reader ReadableStream should reject. - await assertRejects(() => resp.body!.getReader().read()); - - const err = await errorPromise as Response; - assertStringIncludes(await err.text(), "Expected ArrayBufferView"); - - ac.abort(); - await server; - }, -); - -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(); - const server = Deno.serve({ - handler: async (request) => { - const { - response, - socket, - } = Deno.upgradeWebSocket(request); - socket.onerror = () => fail(); - socket.onmessage = (m) => { - socket.send(m.data); - socket.close(1001); - }; - return response; - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const def = deferred(); - const ws = new WebSocket("ws://localhost:4501"); - ws.onmessage = (m) => assertEquals(m.data, "foo"); - ws.onerror = () => fail(); - ws.onclose = () => def.resolve(); - ws.onopen = () => ws.send("foo"); - - await def; - ac.abort(); - await server; -}); - -Deno.test( - { permissions: { net: true } }, - async function httpVeryLargeRequest() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - let headers: Headers; - const server = Deno.serve({ - handler: async (request) => { - headers = request.headers; - promise.resolve(); - return new Response(""); - }, - port: 2333, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const conn = await Deno.connect({ port: 2333 }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const smthElse = "x".repeat(16 * 1024 + 256); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\nSomething-Else: ${smthElse}\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - conn.close(); - assertEquals(headers!.get("content-length"), "5"); - assertEquals(headers!.get("something-else"), smthElse); - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpVeryLargeRequestAndBody() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - let headers: Headers; - let text: string; - const server = Deno.serve({ - handler: async (request) => { - headers = request.headers; - text = await request.text(); - promise.resolve(); - return new Response(""); - }, - port: 2333, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const conn = await Deno.connect({ port: 2333 }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const smthElse = "x".repeat(16 * 1024 + 256); - const reqBody = "hello world".repeat(1024); - let body = - `PUT / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: ${reqBody.length}\r\nSomething-Else: ${smthElse}\r\n\r\n${reqBody}`; - - while (body.length > 0) { - const writeResult = await conn.write(encoder.encode(body)); - body = body.slice(writeResult); - } - - await promise; - conn.close(); - - assertEquals(headers!.get("content-length"), `${reqBody.length}`); - assertEquals(headers!.get("something-else"), smthElse); - assertEquals(text!, reqBody); - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpConnectionClose() { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: () => { - promise.resolve(); - return new Response(""); - }, - port: 2333, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const conn = await Deno.connect({ port: 2333 }); - // Send GET request with a body + connection: close. - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nConnection: Close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - await promise; - conn.close(); - - ac.abort(); - await server; - }, -); - -// FIXME: auto request body reading is intefering with passing it as response. -// Deno.test( -// { permissions: { net: true } }, -// async function httpServerStreamDuplex() { -// const promise = deferred(); -// const ac = new AbortController(); - -// const server = Deno.serve(request => { -// assert(request.body); - -// promise.resolve(); -// return new Response(request.body); -// }, { port: 2333, signal: ac.signal }); - -// const ts = new TransformStream(); -// const writable = ts.writable.getWriter(); - -// const resp = await fetch("http://127.0.0.1:2333/", { -// method: "POST", -// body: ts.readable, -// }); - -// await promise; -// assert(resp.body); -// const reader = resp.body.getReader(); -// await writable.write(new Uint8Array([1])); -// const chunk1 = await reader.read(); -// assert(!chunk1.done); -// assertEquals(chunk1.value, new Uint8Array([1])); -// await writable.write(new Uint8Array([2])); -// const chunk2 = await reader.read(); -// assert(!chunk2.done); -// assertEquals(chunk2.value, new Uint8Array([2])); -// await writable.close(); -// const chunk3 = await reader.read(); -// assert(chunk3.done); - -// ac.abort(); -// await server; -// }, -// ); - -Deno.test( - { permissions: { net: true } }, - // Issue: https://github.com/denoland/deno/issues/10930 - async function httpServerStreamingResponse() { - // This test enqueues a single chunk for readable - // stream and waits for client to read that chunk and signal - // it before enqueueing subsequent chunk. Issue linked above - // presented a situation where enqueued chunks were not - // written to the HTTP connection until the next chunk was enqueued. - const listeningPromise = deferred(); - const promise = deferred(); - const ac = new AbortController(); - - let counter = 0; - - const deferreds = [ - deferred(), - deferred(), - deferred(), - ]; - - async function writeRequest(conn: Deno.Conn) { - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - const w = new BufWriter(conn); - const r = new BufReader(conn); - const body = `GET / HTTP/1.1\r\nHost: 127.0.0.1:4501\r\n\r\n`; - const writeResult = await w.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await w.flush(); - const tpr = new TextProtoReader(r); - const statusLine = await tpr.readLine(); - assert(statusLine !== null); - const headers = await tpr.readMimeHeader(); - assert(headers !== null); - - const chunkedReader = chunkedBodyReader(headers, r); - - const buf = new Uint8Array(5); - const dest = new Buffer(); - - let result: number | null; - - try { - while ((result = await chunkedReader.read(buf)) !== null) { - const len = Math.min(buf.byteLength, result); - - await dest.write(buf.subarray(0, len)); - - // Resolve a deferred - this will make response stream to - // enqueue next chunk. - deferreds[counter - 1].resolve(); - } - return decoder.decode(dest.bytes()); - } catch (e) { - console.error(e); - } - } - - function periodicStream() { - return new ReadableStream({ - start(controller) { - controller.enqueue(`${counter}\n`); - counter++; - }, - - async pull(controller) { - if (counter >= 3) { - return controller.close(); - } - - await deferreds[counter - 1]; - - controller.enqueue(`${counter}\n`); - counter++; - }, - }).pipeThrough(new TextEncoderStream()); - } - - const finished = Deno.serve({ - handler: () => { - promise.resolve(); - return new Response(periodicStream()); - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - // start a client - const clientConn = await Deno.connect({ port: 4501 }); - - const r1 = await writeRequest(clientConn); - assertEquals(r1, "0\n1\n2\n"); - - ac.abort(); - await promise; - await finished; - clientConn.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpRequestLatin1Headers() { - const listeningPromise = deferred(); - const promise = deferred(); - const ac = new AbortController(); - const server = Deno.serve({ - handler: (request) => { - assertEquals(request.headers.get("X-Header-Test"), "á"); - promise.resolve(); - return new Response("hello", { headers: { "X-Header-Test": "Æ" } }); - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - 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)); - } - - const buf = new Uint8Array(1024); - await clientConn.read(buf); - - await promise; - let responseText = new TextDecoder().decode(buf); - clientConn.close(); - - assert(/\r\n[Xx]-[Hh]eader-[Tt]est: Æ\r\n/.test(responseText)); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerRequestWithoutPath() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - // FIXME: - // assertEquals(new URL(request.url).href, "http://127.0.0.1:4501/"); - assertEquals(await request.text(), ""); - promise.resolve(); - return new Response("11"); - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const clientConn = await Deno.connect({ port: 4501 }); - - async function writeRequest(conn: Deno.Conn) { - const encoder = new TextEncoder(); - - const w = new BufWriter(conn); - const r = new BufReader(conn); - const body = - `CONNECT 127.0.0.1:4501 HTTP/1.1\r\nHost: 127.0.0.1:4501\r\n\r\n`; - const writeResult = await w.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await w.flush(); - const tpr = new TextProtoReader(r); - const statusLine = await tpr.readLine(); - assert(statusLine !== null); - const m = statusLine.match(/^(.+?) (.+?) (.+?)$/); - assert(m !== null, "must be matched"); - const [_, _proto, status, _ok] = m; - assertEquals(status, "200"); - const headers = await tpr.readMimeHeader(); - assert(headers !== null); - } - - await writeRequest(clientConn); - clientConn.close(); - await promise; - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpCookieConcatenation() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(await request.text(), ""); - assertEquals(request.headers.get("cookie"), "foo=bar, bar=foo"); - promise.resolve(); - return new Response("ok"); - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const resp = await fetch("http://127.0.0.1:4501/", { - headers: [ - ["connection", "close"], - ["cookie", "foo=bar"], - ["cookie", "bar=foo"], - ], - }); - await promise; - - const text = await resp.text(); - assertEquals(text, "ok"); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerCorrectSizeResponse() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const tmpFile = await Deno.makeTempFile(); - const file = await Deno.open(tmpFile, { write: true, read: true }); - await file.write(new Uint8Array(70 * 1024).fill(1)); // 70kb sent in 64kb + 6kb chunks - file.close(); - - const server = Deno.serve({ - handler: async (request) => { - const f = await Deno.open(tmpFile, { read: true }); - promise.resolve(); - return new Response(f.readable); - }, - port: 4503, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const resp = await fetch("http://127.0.0.1:4503/"); - await promise; - const body = await resp.arrayBuffer(); - - assertEquals(body.byteLength, 70 * 1024); - ac.abort(); - await server; - }, -); - -// https://github.com/denoland/deno/issues/12741 -// https://github.com/denoland/deno/pull/12746 -// https://github.com/denoland/deno/pull/12798 -Deno.test( - { permissions: { net: true, run: true } }, - async function httpServerDeleteRequestHasBody() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const hostname = "localhost"; - const port = 4501; - - const server = Deno.serve({ - handler: () => { - promise.resolve(); - return new Response("ok"); - }, - port: port, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const url = `http://${hostname}:${port}/`; - const args = ["-X", "DELETE", url]; - const { success } = await new Deno.Command("curl", { - args, - stdout: "null", - stderr: "null", - }).output(); - assert(success); - await promise; - ac.abort(); - - await server; - }, -); - -// FIXME: -Deno.test( - { permissions: { net: true } }, - async function httpServerRespondNonAsciiUint8Array() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.body, null); - promise.resolve(); - return new Response(new Uint8Array([128])); - }, - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - await listeningPromise; - const resp = await fetch("http://localhost:4501/"); - - await promise; - - assertEquals(resp.status, 200); - const body = await resp.arrayBuffer(); - assertEquals(new Uint8Array(body), new Uint8Array([128])); - - ac.abort(); - await server; - }, -); - -Deno.test("upgradeHttpRaw tcp", async () => { - const promise = deferred(); - const listeningPromise = deferred(); - const promise2 = deferred(); - const ac = new AbortController(); - const signal = ac.signal; - let conn: Deno.Conn; - let _head; - const handler = (req: Request) => { - [conn, _head] = Deno.upgradeHttpRaw(req); - - (async () => { - await conn.write( - new TextEncoder().encode("HTTP/1.1 101 Switching Protocols\r\n\r\n"), - ); - - promise.resolve(); - - const buf = new Uint8Array(1024); - const n = await conn.read(buf); - - assert(n != null); - const secondPacketText = new TextDecoder().decode(buf.slice(0, n)); - assertEquals(secondPacketText, "bla bla bla\nbla bla\nbla\n"); - - promise2.resolve(); - })(); - }; - const server = Deno.serve({ - // NOTE: `as any` is used to bypass type checking for the return value - // of the handler. - handler: handler as any, - port: 4501, - signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const tcpConn = await Deno.connect({ port: 4501 }); - await tcpConn.write( - new TextEncoder().encode( - "CONNECT server.example.com:80 HTTP/1.1\r\n\r\n", - ), - ); - - await promise; - - await tcpConn.write( - new TextEncoder().encode( - "bla bla bla\nbla bla\nbla\n", - ), - ); - - await promise2; - conn!.close(); - tcpConn.close(); - - ac.abort(); - await server; -}); - -// Some of these tests are ported from Hyper -// https://github.com/hyperium/hyper/blob/889fa2d87252108eb7668b8bf034ffcc30985117/src/proto/h1/role.rs -// https://github.com/hyperium/hyper/blob/889fa2d87252108eb7668b8bf034ffcc30985117/tests/server.rs - -Deno.test( - { permissions: { net: true } }, - async function httpServerParseRequest() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "GET"); - assertEquals(request.headers.get("host"), "deno.land"); - promise.resolve(); - return new Response("ok"); - }, - 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 body = `GET /echo HTTP/1.1\r\nHost: deno.land\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerParseHeaderHtabs() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "GET"); - assertEquals(request.headers.get("server"), "hello\tworld"); - promise.resolve(); - return new Response("ok"); - }, - 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 body = `GET / HTTP/1.1\r\nserver: hello\tworld\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerGetShouldIgnoreBody() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "GET"); - assertEquals(await request.text(), ""); - promise.resolve(); - return new Response("ok"); - }, - port: 4503, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const conn = await Deno.connect({ port: 4503 }); - const encoder = new TextEncoder(); - // Connection: close = don't try to parse the body as a new request - const body = - `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\nI shouldn't be read.\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerPostWithBody() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "POST"); - assertEquals(await request.text(), "I'm a good request."); - promise.resolve(); - return new Response("ok"); - }, - 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 body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 19\r\n\r\nI'm a good request.`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - conn.close(); - - ac.abort(); - await server; - }, -); - -type TestCase = { - headers?: Record<string, string>; - body: any; - expects_chunked?: boolean; - expects_con_len?: boolean; -}; - -function hasHeader(msg: string, name: string): boolean { - let n = msg.indexOf("\r\n\r\n") || msg.length; - return msg.slice(0, n).includes(name); -} - -function createServerLengthTest(name: string, testCase: TestCase) { - Deno.test(name, async function () { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "GET"); - promise.resolve(); - return new Response(testCase.body, testCase.headers ?? {}); - }, - 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 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); - await promise; - - const decoder = new TextDecoder(); - let msg = ""; - while (true) { - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - if (!readResult) { - break; - } - msg += decoder.decode(buf.subarray(0, readResult)); - try { - assert( - testCase.expects_chunked == hasHeader(msg, "Transfer-Encoding:"), - ); - assert(testCase.expects_chunked == hasHeader(msg, "chunked")); - assert(testCase.expects_con_len == hasHeader(msg, "Content-Length:")); - - const n = msg.indexOf("\r\n\r\n") + 4; - - if (testCase.expects_chunked) { - assertEquals(msg.slice(n + 1, n + 3), "\r\n"); - assertEquals(msg.slice(msg.length - 7), "\r\n0\r\n\r\n"); - } - - if (testCase.expects_con_len && typeof testCase.body === "string") { - assertEquals(msg.slice(n), testCase.body); - } - break; - } catch (e) { - continue; - } - } - - conn.close(); - - ac.abort(); - await server; - }); -} - -// Quick and dirty way to make a readable stream from a string. Alternatively, -// `readableStreamFromReader(file)` could be used. -function stream(s: string): ReadableStream<Uint8Array> { - return new Response(s).body!; -} - -createServerLengthTest("fixedResponseKnown", { - headers: { "content-length": "11" }, - body: "foo bar baz", - expects_chunked: false, - expects_con_len: true, -}); - -createServerLengthTest("fixedResponseUnknown", { - headers: { "content-length": "11" }, - body: stream("foo bar baz"), - expects_chunked: true, - expects_con_len: false, -}); - -createServerLengthTest("fixedResponseKnownEmpty", { - headers: { "content-length": "0" }, - body: "", - expects_chunked: false, - expects_con_len: true, -}); - -createServerLengthTest("chunkedRespondKnown", { - headers: { "transfer-encoding": "chunked" }, - body: "foo bar baz", - expects_chunked: false, - expects_con_len: true, -}); - -createServerLengthTest("chunkedRespondUnknown", { - headers: { "transfer-encoding": "chunked" }, - body: stream("foo bar baz"), - expects_chunked: true, - expects_con_len: false, -}); - -createServerLengthTest("autoResponseWithKnownLength", { - body: "foo bar baz", - expects_chunked: false, - expects_con_len: true, -}); - -createServerLengthTest("autoResponseWithUnknownLength", { - body: stream("foo bar baz"), - expects_chunked: true, - expects_con_len: false, -}); - -createServerLengthTest("autoResponseWithKnownLengthEmpty", { - body: "", - expects_chunked: false, - expects_con_len: true, -}); - -// FIXME: https://github.com/denoland/deno/issues/15892 -// createServerLengthTest("autoResponseWithUnknownLengthEmpty", { -// body: stream(""), -// expects_chunked: true, -// expects_con_len: false, -// }); - -Deno.test( - { permissions: { net: true } }, - async function httpServerGetChunkedResponseWithKa() { - const promises = [deferred(), deferred()]; - let reqCount = 0; - const listeningPromise = deferred(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "GET"); - promises[reqCount].resolve(); - reqCount++; - return new Response(reqCount <= 1 ? stream("foo bar baz") : "zar quux"); - }, - 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 body = - `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: keep-alive\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promises[0]; - } - - const decoder = new TextDecoder(); - { - let msg = ""; - while (true) { - try { - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - msg += decoder.decode(buf.subarray(0, readResult)); - assert(msg.endsWith("\r\nfoo bar baz\r\n0\r\n\r\n")); - break; - } catch { - continue; - } - } - } - - // once more! - { - const body = - `GET /quux 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); - await promises[1]; - } - { - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - assert(msg.endsWith("zar quux")); - } - - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerPostWithContentLengthBody() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "POST"); - assertEquals(request.headers.get("content-length"), "5"); - assertEquals(await request.text(), "hello"); - promise.resolve(); - return new Response("ok"); - }, - 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 body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 5\r\n\r\nhello`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerPostWithInvalidPrefixContentLength() { - const ac = new AbortController(); - const listeningPromise = deferred(); - const server = Deno.serve({ - handler: () => { - throw new Error("unreachable"); - }, - 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 = - `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: +5\r\n\r\nhello`; - 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)); - assert(msg.endsWith("HTTP/1.1 400 Bad Request\r\n\r\n")); - - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerPostWithChunkedBody() { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "POST"); - assertEquals(await request.text(), "qwert"); - promise.resolve(); - return new Response("ok"); - }, - 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 body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nq\r\n2\r\nwe\r\n2\r\nrt\r\n0\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerPostWithIncompleteBody() { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: async (r) => { - promise.resolve(); - assertEquals(await r.text(), "12345"); - return new Response("ok"); - }, - 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 body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 10\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - await promise; - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerHeadResponseDoesntSendBody() { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: () => { - promise.resolve(); - return new Response("NaN".repeat(100)); - }, - 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 = - `HEAD / 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); - - await promise; - - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - - assert(msg.endsWith("Content-Length: 300\r\n\r\n")); - - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerSendFile() { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - const tmpFile = await Deno.makeTempFile(); - const file = await Deno.open(tmpFile, { write: true, read: true }); - const data = new Uint8Array(70 * 1024).fill(1); - await file.write(data); - file.close(); - const server = Deno.serve({ - handler: async () => { - const f = await Deno.open(tmpFile, { read: true }); - promise.resolve(); - return new Response(f.readable, { status: 200 }); - }, - port: 4503, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const response = await fetch(`http://localhost:4503/`); - assertEquals(response.status, 200); - await promise; - assertEquals(new Uint8Array(await response.arrayBuffer()), data); - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerPostFile() { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - - const tmpFile = await Deno.makeTempFile(); - const file = await Deno.open(tmpFile, { write: true, read: true }); - const data = new Uint8Array(70 * 1024).fill(1); - await file.write(data); - file.close(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(new Uint8Array(await request.arrayBuffer()), data); - promise.resolve(); - return new Response("ok"); - }, - port: 4503, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - await listeningPromise; - const f = await Deno.open(tmpFile, { write: true, read: true }); - const response = await fetch(`http://localhost:4503/`, { - method: "POST", - body: f.readable, - }); - - await promise; - - assertEquals(response.status, 200); - assertEquals(await response.text(), "ok"); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function httpServerWithTls() { - const ac = new AbortController(); - const listeningPromise = deferred(); - const hostname = "127.0.0.1"; - const port = 4501; - - const server = Deno.serve({ - handler: () => new Response("Hello World"), - hostname, - port, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - cert: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.crt"), - key: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.key"), - }); - - await listeningPromise; - const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem"); - const client = Deno.createHttpClient({ caCerts: [caCert] }); - const resp = await fetch(`https://localhost:${port}/`, { - client, - headers: { "connection": "close" }, - }); - - const respBody = await resp.text(); - assertEquals("Hello World", respBody); - - client.close(); - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerRequestCLTE() { - const ac = new AbortController(); - const listeningPromise = deferred(); - const promise = deferred(); - - const server = Deno.serve({ - handler: async (req) => { - assertEquals(await req.text(), ""); - promise.resolve(); - return new Response("ok"); - }, - 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 body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 13\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nEXTRA`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerRequestTETE() { - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: () => { - throw new Error("oops"); - }, - port: 4503, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - const variations = [ - "Transfer-Encoding : chunked", - "Transfer-Encoding: xchunked", - "Transfer-Encoding: chunkedx", - "Transfer-Encoding\n: chunked", - ]; - - await listeningPromise; - for (const teHeader of variations) { - const conn = await Deno.connect({ port: 4503 }); - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\n${teHeader}\r\n\r\n0\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)); - assert(msg.endsWith("HTTP/1.1 400 Bad Request\r\n\r\n")); - - conn.close(); - } - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServer204ResponseDoesntSendContentLength() { - const listeningPromise = deferred(); - const ac = new AbortController(); - const server = Deno.serve({ - handler: (_request) => new Response(null, { status: 204 }), - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - try { - await listeningPromise; - const resp = await fetch("http://127.0.0.1:4501/", { - method: "GET", - headers: { "connection": "close" }, - }); - assertEquals(resp.headers.get("Content-Length"), null); - } finally { - ac.abort(); - await server; - } - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServer304ResponseDoesntSendBody() { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: () => { - promise.resolve(); - return new Response(null, { status: 304 }); - }, - 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); - - await promise; - - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - - assert(msg.startsWith("HTTP/1.1 304 Not Modified")); - assert(msg.endsWith("\r\n\r\n")); - - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerExpectContinue() { - const promise = deferred(); - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: async (req) => { - promise.resolve(); - assertEquals(await req.text(), "hello"); - return new Response(null, { status: 304 }); - }, - 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 = - `POST / HTTP/1.1\r\nHost: example.domain\r\nExpect: 100-continue\r\nContent-Length: 5\r\nConnection: close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - } - - await promise; - - { - const msgExpected = "HTTP/1.1 100 Continue\r\n\r\n"; - const buf = new Uint8Array(encoder.encode(msgExpected).byteLength); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - assert(msg.startsWith(msgExpected)); - } - - { - const body = "hello"; - 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)); - - assert(msg.startsWith("HTTP/1.1 304 Not Modified")); - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerExpectContinueButNoBodyLOL() { - const promise = deferred(); - const listeningPromise = deferred(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (req) => { - promise.resolve(); - assertEquals(await req.text(), ""); - return new Response(null, { status: 304 }); - }, - 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(); - - { - // // no content-length or transfer-encoding means no body! - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nExpect: 100-continue\r\nConnection: close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - } - - await promise; - - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - - assert(msg.startsWith("HTTP/1.1 304 Not Modified")); - conn.close(); - - ac.abort(); - await server; - }, -); - -const badRequests = [ - ["weirdMethodName", "GE T / HTTP/1.1\r\n\r\n"], - ["illegalRequestLength", "POST / HTTP/1.1\r\nContent-Length: foo\r\n\r\n"], - ["illegalRequestLength2", "POST / HTTP/1.1\r\nContent-Length: -1\r\n\r\n"], - ["illegalRequestLength3", "POST / HTTP/1.1\r\nContent-Length: 1.1\r\n\r\n"], - ["illegalRequestLength4", "POST / HTTP/1.1\r\nContent-Length: 1.\r\n\r\n"], -]; - -for (const [name, req] of badRequests) { - const testFn = { - [name]: async () => { - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: () => { - throw new Error("oops"); - }, - 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 writeResult = await conn.write(encoder.encode(req)); - assertEquals(req.length, writeResult); - } - - const buf = new Uint8Array(100); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - - assert(msg.startsWith("HTTP/1.1 400 ")); - conn.close(); - - ac.abort(); - await server; - }, - }[name]; - - Deno.test( - { permissions: { net: true } }, - testFn, - ); -} - -Deno.test( - { permissions: { net: true } }, - async function httpServerImplicitZeroContentLengthForHead() { - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: () => new Response(null), - 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 = - `HEAD / 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)); - - assert(msg.includes("Content-Length: 0")); - - conn.close(); - - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerConcurrentRequests() { - const ac = new AbortController(); - const listeningPromise = deferred(); - - let reqCount = -1; - let timerId: number | undefined; - const server = Deno.serve({ - handler: async (req) => { - reqCount++; - if (reqCount === 0) { - const msg = new TextEncoder().encode("data: hello\r\n\r\n"); - // SSE - const body = new ReadableStream({ - start(controller) { - timerId = setInterval(() => { - controller.enqueue(msg); - }, 1000); - }, - cancel() { - if (typeof timerId === "number") { - clearInterval(timerId); - } - }, - }); - return new Response(body, { - headers: { - "Content-Type": "text/event-stream", - }, - }); - } - - return new Response(`hello ${reqCount}`); - }, - port: 4503, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - const sseRequest = await fetch(`http://localhost:4503/`); - - const decoder = new TextDecoder(); - const stream = sseRequest.body!.getReader(); - { - const { done, value } = await stream.read(); - assert(!done); - assertEquals(decoder.decode(value), "data: hello\r\n\r\n"); - } - - const helloRequest = await fetch(`http://localhost:4503/`); - assertEquals(helloRequest.status, 200); - assertEquals(await helloRequest.text(), "hello 1"); - - { - const { done, value } = await stream.read(); - assert(!done); - assertEquals(decoder.decode(value), "data: hello\r\n\r\n"); - } - - await stream.cancel(); - clearInterval(timerId); - ac.abort(); - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function serveWithPrototypePollution() { - const originalThen = Promise.prototype.then; - const originalSymbolIterator = Array.prototype[Symbol.iterator]; - try { - Promise.prototype.then = Array.prototype[Symbol.iterator] = () => { - throw new Error(); - }; - const ac = new AbortController(); - const listeningPromise = deferred(); - const server = Deno.serve({ - handler: (_req) => new Response("ok"), - hostname: "localhost", - port: 4501, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - ac.abort(); - await server; - } finally { - Promise.prototype.then = originalThen; - Array.prototype[Symbol.iterator] = originalSymbolIterator; - } - }, -); - -// https://github.com/denoland/deno/issues/15549 -Deno.test( - { permissions: { net: true } }, - async function testIssue15549() { - const ac = new AbortController(); - const promise = deferred(); - let count = 0; - const server = Deno.serve(() => { - count++; - return new Response(`hello world ${count}`); - }, { - async onListen() { - const res1 = await fetch("http://localhost:9000/"); - assertEquals(await res1.text(), "hello world 1"); - - const res2 = await fetch("http://localhost:9000/"); - assertEquals(await res2.text(), "hello world 2"); - - promise.resolve(); - ac.abort(); - }, - signal: ac.signal, - }); - - await promise; - await server; - }, -); - -// https://github.com/denoland/deno/issues/15858 -Deno.test( - { permissions: { net: true } }, - async function httpServerCanCloneRequest() { - const ac = new AbortController(); - const listeningPromise = deferred(); - - const server = Deno.serve({ - handler: async (req) => { - const cloned = req.clone(); - assertEquals(req.headers, cloned.headers); - - // both requests can read body - await req.text(); - await cloned.json(); - - return new Response("ok"); - }, - signal: ac.signal, - onListen: onListen(listeningPromise), - onError: createOnErrorCb(ac), - }); - - try { - await listeningPromise; - const resp = await fetch("http://localhost:9000/", { - headers: { connection: "close" }, - method: "POST", - body: '{"sus":true}', - }); - const text = await resp.text(); - assertEquals(text, "ok"); - } finally { - ac.abort(); - await server; - } - }, -); - -// Checks large streaming response -// https://github.com/denoland/deno/issues/16567 -Deno.test( - { permissions: { net: true } }, - async function testIssue16567() { - const ac = new AbortController(); - const promise = deferred(); - const server = Deno.serve(() => - new Response( - new ReadableStream({ - start(c) { - // 2MB "a...a" response with 40 chunks - for (const _ of Array(40)) { - c.enqueue(new Uint8Array(50_000).fill(97)); - } - c.close(); - }, - }), - ), { - async onListen() { - const res1 = await fetch("http://localhost:9000/"); - assertEquals((await res1.text()).length, 40 * 50_000); - - promise.resolve(); - ac.abort(); - }, - signal: ac.signal, - }); - - await promise; - await server; - }, -); - -function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader { - // Based on https://tools.ietf.org/html/rfc2616#section-19.4.6 - const tp = new TextProtoReader(r); - let finished = false; - const chunks: Array<{ - offset: number; - data: Uint8Array; - }> = []; - async function read(buf: Uint8Array): Promise<number | null> { - if (finished) return null; - const [chunk] = chunks; - if (chunk) { - const chunkRemaining = chunk.data.byteLength - chunk.offset; - const readLength = Math.min(chunkRemaining, buf.byteLength); - for (let i = 0; i < readLength; i++) { - buf[i] = chunk.data[chunk.offset + i]; - } - chunk.offset += readLength; - if (chunk.offset === chunk.data.byteLength) { - chunks.shift(); - // Consume \r\n; - if ((await tp.readLine()) === null) { - throw new Deno.errors.UnexpectedEof(); - } - } - return readLength; - } - const line = await tp.readLine(); - if (line === null) throw new Deno.errors.UnexpectedEof(); - // TODO(bartlomieju): handle chunk extension - const [chunkSizeString] = line.split(";"); - const chunkSize = parseInt(chunkSizeString, 16); - if (Number.isNaN(chunkSize) || chunkSize < 0) { - throw new Deno.errors.InvalidData("Invalid chunk size"); - } - if (chunkSize > 0) { - if (chunkSize > buf.byteLength) { - let eof = await r.readFull(buf); - if (eof === null) { - throw new Deno.errors.UnexpectedEof(); - } - const restChunk = new Uint8Array(chunkSize - buf.byteLength); - eof = await r.readFull(restChunk); - if (eof === null) { - throw new Deno.errors.UnexpectedEof(); - } else { - chunks.push({ - offset: 0, - data: restChunk, - }); - } - return buf.byteLength; - } else { - const bufToFill = buf.subarray(0, chunkSize); - const eof = await r.readFull(bufToFill); - if (eof === null) { - throw new Deno.errors.UnexpectedEof(); - } - // Consume \r\n - if ((await tp.readLine()) === null) { - throw new Deno.errors.UnexpectedEof(); - } - return chunkSize; - } - } else { - assert(chunkSize === 0); - // Consume \r\n - if ((await r.readLine()) === null) { - throw new Deno.errors.UnexpectedEof(); - } - await readTrailers(h, r); - finished = true; - return null; - } - } - return { read }; -} - -async function readTrailers( - headers: Headers, - r: BufReader, -) { - const trailers = parseTrailer(headers.get("trailer")); - if (trailers == null) return; - const trailerNames = [...trailers.keys()]; - const tp = new TextProtoReader(r); - const result = await tp.readMimeHeader(); - if (result == null) { - throw new Deno.errors.InvalidData("Missing trailer header."); - } - const undeclared = [...result.keys()].filter( - (k) => !trailerNames.includes(k), - ); - if (undeclared.length > 0) { - throw new Deno.errors.InvalidData( - `Undeclared trailers: ${Deno.inspect(undeclared)}.`, - ); - } - for (const [k, v] of result) { - headers.append(k, v); - } - const missingTrailers = trailerNames.filter((k) => !result.has(k)); - if (missingTrailers.length > 0) { - throw new Deno.errors.InvalidData( - `Missing trailers: ${Deno.inspect(missingTrailers)}.`, - ); - } - headers.delete("trailer"); -} - -function parseTrailer(field: string | null): Headers | undefined { - if (field == null) { - return undefined; - } - const trailerNames = field.split(",").map((v) => v.trim().toLowerCase()); - if (trailerNames.length === 0) { - throw new Deno.errors.InvalidData("Empty trailer header."); - } - const prohibited = trailerNames.filter((k) => isProhibitedForTrailer(k)); - if (prohibited.length > 0) { - throw new Deno.errors.InvalidData( - `Prohibited trailer names: ${Deno.inspect(prohibited)}.`, - ); - } - return new Headers(trailerNames.map((key) => [key, ""])); -} - -function isProhibitedForTrailer(key: string): boolean { - const s = new Set(["transfer-encoding", "content-length", "trailer"]); - return s.has(key.toLowerCase()); -} |