1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-29 16:30:56 -05:00
denoland-deno/cli/tests/unit/tls_test.ts
Bert Belder 640d431b35
fix(tls): flush send buffer in the background after closing TLS stream (#10146)
In #9118, TLS streams were split into a "read half" and a "write half"
using tokio::io::split() to allow concurrent Conn#read() and
Conn#write() calls without one blocking the other. However, this
introduced a bug: outgoing data gets discarded when the TLS stream is
gracefully closed, because the read half is closed too early, before all
TLS control data has been received.

Fixes: #9692
Fixes: #10049
Fixes: #10296
Fixes: denoland/deno_std#750
2021-05-11 03:11:26 +02:00

987 lines
26 KiB
TypeScript

// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
import {
assert,
assertEquals,
assertNotEquals,
assertStrictEquals,
assertThrows,
assertThrowsAsync,
Deferred,
deferred,
unitTest,
} from "./test_util.ts";
import { BufReader, BufWriter } from "../../../test_util/std/io/bufio.ts";
import { TextProtoReader } from "../../../test_util/std/textproto/mod.ts";
const encoder = new TextEncoder();
const decoder = new TextDecoder();
async function sleep(msec: number): Promise<void> {
await new Promise((res, _rej) => setTimeout(res, msec));
}
function unreachable(): never {
throw new Error("Unreachable code reached");
}
unitTest(async function connectTLSNoPerm(): Promise<void> {
await assertThrowsAsync(async () => {
await Deno.connectTls({ hostname: "github.com", port: 443 });
}, Deno.errors.PermissionDenied);
});
unitTest(
{ perms: { read: true, net: true } },
async function connectTLSInvalidHost(): Promise<void> {
const listener = await Deno.listenTls({
hostname: "localhost",
port: 3567,
certFile: "cli/tests/tls/localhost.crt",
keyFile: "cli/tests/tls/localhost.key",
});
await assertThrowsAsync(async () => {
await Deno.connectTls({ hostname: "127.0.0.1", port: 3567 });
}, TypeError);
listener.close();
},
);
unitTest(async function connectTLSCertFileNoReadPerm(): Promise<void> {
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<void> {
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<void> => {
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;
},
);
let nextPort = 3501;
function getPort() {
return nextPort++;
}
async function tlsPair(): Promise<[Deno.Conn, Deno.Conn]> {
const port = getPort();
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 endpoints = await Promise.all([acceptPromise, connectPromise]);
listener.close();
return endpoints;
}
async function sendThenCloseWriteThenReceive(
conn: Deno.Conn,
chunkCount: number,
chunkSize: number,
): Promise<void> {
const byteCount = chunkCount * chunkSize;
const buf = new Uint8Array(chunkSize); // Note: buf is size of _chunk_.
let n: number;
// Slowly send 42s.
buf.fill(42);
for (let remaining = byteCount; remaining > 0; remaining -= n) {
n = await conn.write(buf.subarray(0, remaining));
assert(n >= 1);
await sleep(10);
}
// Send EOF.
await conn.closeWrite();
// Receive 69s.
for (let remaining = byteCount; remaining > 0; remaining -= n) {
buf.fill(0);
n = await conn.read(buf) as number;
assert(n >= 1);
assertStrictEquals(buf[0], 69);
assertStrictEquals(buf[n - 1], 69);
}
conn.close();
}
async function receiveThenSend(
conn: Deno.Conn,
chunkCount: number,
chunkSize: number,
): Promise<void> {
const byteCount = chunkCount * chunkSize;
const buf = new Uint8Array(byteCount); // Note: buf size equals `byteCount`.
let n: number;
// Receive 42s.
for (let remaining = byteCount; remaining > 0; remaining -= n) {
buf.fill(0);
n = await conn.read(buf) as number;
assert(n >= 1);
assertStrictEquals(buf[0], 42);
assertStrictEquals(buf[n - 1], 42);
}
// Slowly send 69s.
buf.fill(69);
for (let remaining = byteCount; remaining > 0; remaining -= n) {
n = await conn.write(buf.subarray(0, remaining));
assert(n >= 1);
await sleep(10);
}
conn.close();
}
unitTest(
{ perms: { read: true, net: true } },
async function tlsServerStreamHalfCloseSendOneByte(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendThenCloseWriteThenReceive(serverConn, 1, 1),
receiveThenSend(clientConn, 1, 1),
]);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsClientStreamHalfCloseSendOneByte(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendThenCloseWriteThenReceive(clientConn, 1, 1),
receiveThenSend(serverConn, 1, 1),
]);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsServerStreamHalfCloseSendOneChunk(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendThenCloseWriteThenReceive(serverConn, 1, 1 << 20 /* 1 MB */),
receiveThenSend(clientConn, 1, 1 << 20 /* 1 MB */),
]);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsClientStreamHalfCloseSendOneChunk(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendThenCloseWriteThenReceive(clientConn, 1, 1 << 20 /* 1 MB */),
receiveThenSend(serverConn, 1, 1 << 20 /* 1 MB */),
]);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsServerStreamHalfCloseSendManyBytes(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendThenCloseWriteThenReceive(serverConn, 100, 1),
receiveThenSend(clientConn, 100, 1),
]);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsClientStreamHalfCloseSendManyBytes(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendThenCloseWriteThenReceive(clientConn, 100, 1),
receiveThenSend(serverConn, 100, 1),
]);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsServerStreamHalfCloseSendManyChunks(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendThenCloseWriteThenReceive(serverConn, 100, 1 << 16 /* 64 kB */),
receiveThenSend(clientConn, 100, 1 << 16 /* 64 kB */),
]);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsClientStreamHalfCloseSendManyChunks(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendThenCloseWriteThenReceive(clientConn, 100, 1 << 16 /* 64 kB */),
receiveThenSend(serverConn, 100, 1 << 16 /* 64 kB */),
]);
},
);
async function sendAlotReceiveNothing(conn: Deno.Conn): Promise<void> {
// Start receive op.
const readBuf = new Uint8Array(1024);
const readPromise = conn.read(readBuf);
// Send 1 MB of data.
const writeBuf = new Uint8Array(1 << 20 /* 1 MB */);
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<void> {
const readBuf = new Uint8Array(1024);
let n: number | null;
// Receive 1 MB of data.
for (let nread = 0; nread < 1 << 20 /* 1 MB */; 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 tlsServerStreamCancelRead(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendAlotReceiveNothing(serverConn),
receiveAlotSendNothing(clientConn),
]);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsClientStreamCancelRead(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendAlotReceiveNothing(clientConn),
receiveAlotSendNothing(serverConn),
]);
},
);
async function sendReceiveEmptyBuf(conn: Deno.Conn): Promise<void> {
const byteBuf = new Uint8Array([1]);
const emptyBuf = new Uint8Array(0);
let n: number | null;
n = await conn.write(emptyBuf);
assertStrictEquals(n, 0);
n = await conn.read(emptyBuf);
assertStrictEquals(n, 0);
n = await conn.write(byteBuf);
assertStrictEquals(n, 1);
n = await conn.read(byteBuf);
assertStrictEquals(n, 1);
await conn.closeWrite();
n = await conn.write(emptyBuf);
assertStrictEquals(n, 0);
await assertThrowsAsync(async () => {
await conn.write(byteBuf);
}, Deno.errors.BrokenPipe);
n = await conn.write(emptyBuf);
assertStrictEquals(n, 0);
n = await conn.read(byteBuf);
assertStrictEquals(n, null);
conn.close();
}
unitTest(
{ perms: { read: true, net: true } },
async function tlsStreamSendReceiveEmptyBuf(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
sendReceiveEmptyBuf(serverConn),
sendReceiveEmptyBuf(clientConn),
]);
},
);
function immediateClose(conn: Deno.Conn): Promise<void> {
conn.close();
return Promise.resolve();
}
async function closeWriteAndClose(conn: Deno.Conn): Promise<void> {
await conn.closeWrite();
if (await conn.read(new Uint8Array(1)) !== null) {
throw new Error("did not expect to receive data on TLS stream");
}
conn.close();
}
unitTest(
{ perms: { read: true, net: true } },
async function tlsServerStreamImmediateClose(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
immediateClose(serverConn),
closeWriteAndClose(clientConn),
]);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsClientStreamImmediateClose(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
closeWriteAndClose(serverConn),
immediateClose(clientConn),
]);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsClientAndServerStreamImmediateClose(): Promise<void> {
const [serverConn, clientConn] = await tlsPair();
await Promise.all([
immediateClose(serverConn),
immediateClose(clientConn),
]);
},
);
async function tlsWithTcpFailureTestImpl(
phase: "handshake" | "traffic",
cipherByteCount: number,
failureMode: "corruption" | "shutdown",
reverse: boolean,
): Promise<void> {
const tlsPort = getPort();
const tlsListener = Deno.listenTls({
hostname: "localhost",
port: tlsPort,
certFile: "cli/tests/tls/localhost.crt",
keyFile: "cli/tests/tls/localhost.key",
});
const tcpPort = getPort();
const tcpListener = Deno.listen({ hostname: "localhost", port: tcpPort });
const [tlsServerConn, tcpServerConn] = await Promise.all([
tlsListener.accept(),
Deno.connect({ hostname: "localhost", port: tlsPort }),
]);
const [tcpClientConn, tlsClientConn] = await Promise.all([
tcpListener.accept(),
Deno.connectTls({
hostname: "localhost",
port: tcpPort,
certFile: "cli/tests/tls/RootCA.crt",
}),
]);
tlsListener.close();
tcpListener.close();
const {
tlsConn1,
tlsConn2,
tcpConn1,
tcpConn2,
} = reverse
? {
tlsConn1: tlsClientConn,
tlsConn2: tlsServerConn,
tcpConn1: tcpClientConn,
tcpConn2: tcpServerConn,
}
: {
tlsConn1: tlsServerConn,
tlsConn2: tlsClientConn,
tcpConn1: tcpServerConn,
tcpConn2: tcpClientConn,
};
const tcpForwardingInterruptPromise1 = deferred<void>();
const tcpForwardingPromise1 = forwardBytes(
tcpConn2,
tcpConn1,
cipherByteCount,
tcpForwardingInterruptPromise1,
);
const tcpForwardingInterruptPromise2 = deferred<void>();
const tcpForwardingPromise2 = forwardBytes(
tcpConn1,
tcpConn2,
Infinity,
tcpForwardingInterruptPromise2,
);
switch (phase) {
case "handshake": {
let expectedError;
switch (failureMode) {
case "corruption":
expectedError = Deno.errors.InvalidData;
break;
case "shutdown":
expectedError = Deno.errors.UnexpectedEof;
break;
default:
unreachable();
}
const tlsTrafficPromise1 = Promise.all([
assertThrowsAsync(
() => sendBytes(tlsConn1, 0x01, 1),
expectedError,
),
assertThrowsAsync(
() => receiveBytes(tlsConn1, 0x02, 1),
expectedError,
),
]);
const tlsTrafficPromise2 = Promise.all([
assertThrowsAsync(
() => sendBytes(tlsConn2, 0x02, 1),
Deno.errors.UnexpectedEof,
),
assertThrowsAsync(
() => receiveBytes(tlsConn2, 0x01, 1),
Deno.errors.UnexpectedEof,
),
]);
await tcpForwardingPromise1;
switch (failureMode) {
case "corruption":
await sendBytes(tcpConn1, 0xff, 1 << 14 /* 16 kB */);
break;
case "shutdown":
await tcpConn1.closeWrite();
break;
default:
unreachable();
}
await tlsTrafficPromise1;
tcpForwardingInterruptPromise2.resolve();
await tcpForwardingPromise2;
await tcpConn2.closeWrite();
await tlsTrafficPromise2;
break;
}
case "traffic": {
await Promise.all([
sendBytes(tlsConn2, 0x88, 8888),
receiveBytes(tlsConn1, 0x88, 8888),
sendBytes(tlsConn1, 0x99, 99999),
receiveBytes(tlsConn2, 0x99, 99999),
]);
tcpForwardingInterruptPromise1.resolve();
await tcpForwardingPromise1;
switch (failureMode) {
case "corruption":
await sendBytes(tcpConn1, 0xff, 1 << 14 /* 16 kB */);
await assertThrowsAsync(
() => receiveEof(tlsConn1),
Deno.errors.InvalidData,
);
tcpForwardingInterruptPromise2.resolve();
break;
case "shutdown":
// Receiving a TCP FIN packet without receiving a TLS CloseNotify
// alert is not the expected mode of operation, but it is not a
// problem either, so it should be treated as if the TLS session was
// gracefully closed.
await Promise.all([
tcpConn1.closeWrite(),
await receiveEof(tlsConn1),
await tlsConn1.closeWrite(),
await receiveEof(tlsConn2),
]);
break;
default:
unreachable();
}
await tcpForwardingPromise2;
break;
}
default:
unreachable();
}
tlsServerConn.close();
tlsClientConn.close();
tcpServerConn.close();
tcpClientConn.close();
async function sendBytes(
conn: Deno.Conn,
byte: number,
count: number,
): Promise<void> {
let buf = new Uint8Array(1 << 12 /* 4 kB */);
buf.fill(byte);
while (count > 0) {
buf = buf.subarray(0, Math.min(buf.length, count));
const nwritten = await conn.write(buf);
assertStrictEquals(nwritten, buf.length);
count -= nwritten;
}
}
async function receiveBytes(
conn: Deno.Conn,
byte: number,
count: number,
): Promise<void> {
let buf = new Uint8Array(1 << 12 /* 4 kB */);
while (count > 0) {
buf = buf.subarray(0, Math.min(buf.length, count));
const r = await conn.read(buf);
assertNotEquals(r, null);
assert(buf.subarray(0, r!).every((b) => b === byte));
count -= r!;
}
}
async function receiveEof(conn: Deno.Conn) {
const buf = new Uint8Array(1);
const r = await conn.read(buf);
assertStrictEquals(r, null);
}
async function forwardBytes(
source: Deno.Conn,
sink: Deno.Conn,
count: number,
interruptPromise: Deferred<void>,
): Promise<void> {
let buf = new Uint8Array(1 << 12 /* 4 kB */);
while (count > 0) {
buf = buf.subarray(0, Math.min(buf.length, count));
const nread = await Promise.race([source.read(buf), interruptPromise]);
if (nread == null) break; // Either EOF or interrupted.
const nwritten = await sink.write(buf.subarray(0, nread));
assertStrictEquals(nread, nwritten);
count -= nwritten;
}
}
}
unitTest(
{ perms: { read: true, net: true } },
async function tlsHandshakeWithTcpCorruptionImmediately() {
await tlsWithTcpFailureTestImpl("handshake", 0, "corruption", false);
await tlsWithTcpFailureTestImpl("handshake", 0, "corruption", true);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsHandshakeWithTcpShutdownImmediately() {
await tlsWithTcpFailureTestImpl("handshake", 0, "shutdown", false);
await tlsWithTcpFailureTestImpl("handshake", 0, "shutdown", true);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsHandshakeWithTcpCorruptionAfter70Bytes() {
await tlsWithTcpFailureTestImpl("handshake", 76, "corruption", false);
await tlsWithTcpFailureTestImpl("handshake", 78, "corruption", true);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsHandshakeWithTcpShutdownAfter70bytes() {
await tlsWithTcpFailureTestImpl("handshake", 77, "shutdown", false);
await tlsWithTcpFailureTestImpl("handshake", 79, "shutdown", true);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsHandshakeWithTcpCorruptionAfter200Bytes() {
await tlsWithTcpFailureTestImpl("handshake", 200, "corruption", false);
await tlsWithTcpFailureTestImpl("handshake", 202, "corruption", true);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsHandshakeWithTcpShutdownAfter200bytes() {
await tlsWithTcpFailureTestImpl("handshake", 201, "shutdown", false);
await tlsWithTcpFailureTestImpl("handshake", 203, "shutdown", true);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsTrafficWithTcpCorruption() {
await tlsWithTcpFailureTestImpl("traffic", Infinity, "corruption", false);
await tlsWithTcpFailureTestImpl("traffic", Infinity, "corruption", true);
},
);
unitTest(
{ perms: { read: true, net: true } },
async function tlsTrafficWithTcpShutdown() {
await tlsWithTcpFailureTestImpl("traffic", Infinity, "shutdown", false);
await tlsWithTcpFailureTestImpl("traffic", Infinity, "shutdown", true);
},
);
function createHttpsListener(port: number): Deno.Listener {
// Query format: `curl --insecure https://localhost:8443/z/12345`
// The server returns a response consisting of 12345 times the letter 'z'.
const listener = Deno.listenTls({
hostname: "localhost",
port,
certFile: "./cli/tests/tls/localhost.crt",
keyFile: "./cli/tests/tls/localhost.key",
});
serve(listener);
return listener;
async function serve(listener: Deno.Listener) {
for await (const conn of listener) {
const EOL = "\r\n";
// Read GET request plus headers.
const buf = new Uint8Array(1 << 12 /* 4 kB */);
const decoder = new TextDecoder();
let req = "";
while (!req.endsWith(EOL + EOL)) {
const n = await conn.read(buf);
if (n === null) throw new Error("Unexpected EOF");
req += decoder.decode(buf.subarray(0, n));
}
// Parse GET request.
const { filler, count, version } =
/^GET \/(?<filler>[^\/]+)\/(?<count>\d+) HTTP\/(?<version>1\.\d)\r\n/
.exec(req)!.groups as {
filler: string;
count: string;
version: string;
};
// Generate response.
const resBody = new TextEncoder().encode(filler.repeat(+count));
const resHead = new TextEncoder().encode(
[
`HTTP/${version} 200 OK`,
`Content-Length: ${resBody.length}`,
"Content-Type: text/plain",
].join(EOL) + EOL + EOL,
);
// Send response.
await conn.write(resHead);
await conn.write(resBody);
// Close TCP connection.
conn.close();
}
}
}
async function curl(url: string): Promise<string> {
const curl = Deno.run({
cmd: ["curl", "--insecure", url],
stdout: "piped",
});
try {
const [status, output] = await Promise.all([curl.status(), curl.output()]);
if (!status.success) {
throw new Error(`curl ${url} failed: ${status.code}`);
}
return new TextDecoder().decode(output);
} finally {
curl.close();
}
}
unitTest(
{ perms: { read: true, net: true, run: true } },
async function curlFakeHttpsServer(): Promise<void> {
const port = getPort();
const listener = createHttpsListener(port);
const res1 = await curl(`https://localhost:${port}/d/1`);
assertStrictEquals(res1, "d");
const res2 = await curl(`https://localhost:${port}/e/12345`);
assertStrictEquals(res2, "e".repeat(12345));
const count3 = 1 << 17; // 128 kB.
const res3 = await curl(`https://localhost:${port}/n/${count3}`);
assertStrictEquals(res3, "n".repeat(count3));
const count4 = 12345678;
const res4 = await curl(`https://localhost:${port}/o/${count4}`);
assertStrictEquals(res4, "o".repeat(count4));
listener.close();
},
);
unitTest(
{ perms: { read: true, net: true } },
async function startTls(): Promise<void> {
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();
},
);