// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. import { assert, assertEquals, assertStrictEquals, assertThrows, assertThrowsAsync, deferred, unitTest, } from "./test_util.ts"; import { BufReader, BufWriter } from "../../../std/io/bufio.ts"; import { TextProtoReader } from "../../../std/textproto/mod.ts"; const encoder = new TextEncoder(); const decoder = new TextDecoder(); unitTest(async function connectTLSNoPerm(): Promise { await assertThrowsAsync(async () => { await Deno.connectTls({ hostname: "github.com", port: 443 }); }, Deno.errors.PermissionDenied); }); unitTest(async function connectTLSCertFileNoReadPerm(): Promise { await assertThrowsAsync(async () => { await Deno.connectTls({ hostname: "github.com", port: 443, certFile: "cli/tests/tls/RootCA.crt", }); }, Deno.errors.PermissionDenied); }); unitTest( { perms: { read: true, net: true } }, function listenTLSNonExistentCertKeyFiles(): void { const options = { hostname: "localhost", port: 3500, certFile: "cli/tests/tls/localhost.crt", keyFile: "cli/tests/tls/localhost.key", }; assertThrows(() => { Deno.listenTls({ ...options, certFile: "./non/existent/file", }); }, Deno.errors.NotFound); assertThrows(() => { Deno.listenTls({ ...options, keyFile: "./non/existent/file", }); }, Deno.errors.NotFound); }, ); unitTest({ perms: { net: true } }, function listenTLSNoReadPerm(): void { assertThrows(() => { Deno.listenTls({ hostname: "localhost", port: 3500, certFile: "cli/tests/tls/localhost.crt", keyFile: "cli/tests/tls/localhost.key", }); }, Deno.errors.PermissionDenied); }); unitTest( { perms: { read: true, write: true, net: true }, }, function listenTLSEmptyKeyFile(): void { const options = { hostname: "localhost", port: 3500, certFile: "cli/tests/tls/localhost.crt", keyFile: "cli/tests/tls/localhost.key", }; const testDir = Deno.makeTempDirSync(); const keyFilename = testDir + "/key.pem"; Deno.writeFileSync(keyFilename, new Uint8Array([]), { mode: 0o666, }); assertThrows(() => { Deno.listenTls({ ...options, keyFile: keyFilename, }); }, Error); }, ); unitTest( { perms: { read: true, write: true, net: true } }, function listenTLSEmptyCertFile(): void { const options = { hostname: "localhost", port: 3500, certFile: "cli/tests/tls/localhost.crt", keyFile: "cli/tests/tls/localhost.key", }; const testDir = Deno.makeTempDirSync(); const certFilename = testDir + "/cert.crt"; Deno.writeFileSync(certFilename, new Uint8Array([]), { mode: 0o666, }); assertThrows(() => { Deno.listenTls({ ...options, certFile: certFilename, }); }, Error); }, ); unitTest( { perms: { read: true, net: true } }, async function dialAndListenTLS(): Promise { const resolvable = deferred(); const hostname = "localhost"; const port = 3500; const listener = Deno.listenTls({ hostname, port, certFile: "cli/tests/tls/localhost.crt", keyFile: "cli/tests/tls/localhost.key", }); const response = encoder.encode( "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n", ); listener.accept().then( async (conn): Promise => { assert(conn.remoteAddr != null); assert(conn.localAddr != null); await conn.write(response); // TODO(bartlomieju): this might be a bug setTimeout(() => { conn.close(); resolvable.resolve(); }, 0); }, ); const conn = await Deno.connectTls({ hostname, port, certFile: "cli/tests/tls/RootCA.pem", }); assert(conn.rid > 0); const w = new BufWriter(conn); const r = new BufReader(conn); const body = `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\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, `line must be read: ${String(statusLine)}`); const m = statusLine.match(/^(.+?) (.+?) (.+?)$/); assert(m !== null, "must be matched"); const [_, proto, status, ok] = m; assertEquals(proto, "HTTP/1.1"); assertEquals(status, "200"); assertEquals(ok, "OK"); const headers = await tpr.readMIMEHeader(); assert(headers !== null); const contentLength = parseInt(headers.get("content-length")!); const bodyBuf = new Uint8Array(contentLength); await r.readFull(bodyBuf); assertEquals(decoder.decode(bodyBuf), "Hello World\n"); conn.close(); listener.close(); await resolvable; }, ); async function tlsPair(port: number): Promise<[Deno.Conn, Deno.Conn]> { const listener = Deno.listenTls({ hostname: "localhost", port, certFile: "cli/tests/tls/localhost.crt", keyFile: "cli/tests/tls/localhost.key", }); const acceptPromise = listener.accept(); const connectPromise = Deno.connectTls({ hostname: "localhost", port, certFile: "cli/tests/tls/RootCA.pem", }); const connections = await Promise.all([acceptPromise, connectPromise]); listener.close(); return connections; } async function sendCloseWrite(conn: Deno.Conn): Promise { const buf = new Uint8Array(1024); let n: number | null; // Send 1. n = await conn.write(new Uint8Array([1])); assertStrictEquals(n, 1); // Send EOF. await conn.closeWrite(); // Receive 2. n = await conn.read(buf); assertStrictEquals(n, 1); assertStrictEquals(buf[0], 2); conn.close(); } async function receiveCloseWrite(conn: Deno.Conn): Promise { const buf = new Uint8Array(1024); let n: number | null; // Receive 1. n = await conn.read(buf); assertStrictEquals(n, 1); assertStrictEquals(buf[0], 1); // Receive EOF. n = await conn.read(buf); assertStrictEquals(n, null); // Send 2. n = await conn.write(new Uint8Array([2])); assertStrictEquals(n, 1); conn.close(); } async function sendAlotReceiveNothing(conn: Deno.Conn): Promise { // Start receive op. const readBuf = new Uint8Array(1024); const readPromise = conn.read(readBuf); // Send 1 MB of data. const writeBuf = new Uint8Array(1 << 20); writeBuf.fill(42); await conn.write(writeBuf); // Send EOF. await conn.closeWrite(); // Close the connection. conn.close(); // Read op should be canceled. await assertThrowsAsync( async () => await readPromise, Deno.errors.Interrupted, ); } async function receiveAlotSendNothing(conn: Deno.Conn): Promise { const readBuf = new Uint8Array(1024); let n: number | null; // Receive 1 MB of data. for (let nread = 0; nread < 1 << 20; nread += n!) { n = await conn.read(readBuf); assertStrictEquals(typeof n, "number"); assert(n! > 0); assertStrictEquals(readBuf[0], 42); } // Close the connection, without sending anything at all. conn.close(); } unitTest( { perms: { read: true, net: true } }, async function tlsServerStreamHalfClose(): Promise { const [serverConn, clientConn] = await tlsPair(3501); await Promise.all([ sendCloseWrite(serverConn), receiveCloseWrite(clientConn), ]); }, ); unitTest( { perms: { read: true, net: true } }, async function tlsClientStreamHalfClose(): Promise { const [serverConn, clientConn] = await tlsPair(3502); await Promise.all([ sendCloseWrite(clientConn), receiveCloseWrite(serverConn), ]); }, ); unitTest( { perms: { read: true, net: true } }, async function tlsServerStreamCancelRead(): Promise { const [serverConn, clientConn] = await tlsPair(3503); await Promise.all([ sendAlotReceiveNothing(serverConn), receiveAlotSendNothing(clientConn), ]); }, ); unitTest( { perms: { read: true, net: true } }, async function tlsClientStreamCancelRead(): Promise { const [serverConn, clientConn] = await tlsPair(3504); await Promise.all([ sendAlotReceiveNothing(clientConn), receiveAlotSendNothing(serverConn), ]); }, ); unitTest( { perms: { read: true, net: true } }, async function startTls(): Promise { const hostname = "smtp.gmail.com"; const port = 587; const encoder = new TextEncoder(); let conn = await Deno.connect({ hostname, port, }); let writer = new BufWriter(conn); let reader = new TextProtoReader(new BufReader(conn)); let line: string | null = (await reader.readLine()) as string; assert(line.startsWith("220")); await writer.write(encoder.encode(`EHLO ${hostname}\r\n`)); await writer.flush(); while ((line = (await reader.readLine()) as string)) { assert(line.startsWith("250")); if (line.startsWith("250 ")) break; } await writer.write(encoder.encode("STARTTLS\r\n")); await writer.flush(); line = await reader.readLine(); // Received the message that the server is ready to establish TLS assertEquals(line, "220 2.0.0 Ready to start TLS"); conn = await Deno.startTls(conn, { hostname }); writer = new BufWriter(conn); reader = new TextProtoReader(new BufReader(conn)); // After that use TLS communication again await writer.write(encoder.encode(`EHLO ${hostname}\r\n`)); await writer.flush(); while ((line = (await reader.readLine()) as string)) { assert(line.startsWith("250")); if (line.startsWith("250 ")) break; } conn.close(); }, );