Veröffentlichte Quellen
Quellen als ZIP herunterladen
CH-J Server Manager
Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.
CH-J / Server Manager
377 Dateien
core-20261019T111616Z-160a00bfcddc
Hinzugefügt: · Erstellt:
1
"use strict";2
const test = require("node:test");3
const assert = require("node:assert/strict");4
const http = require("node:http");5
const http2 = require("node:http2");6
const tls = require("node:tls");7
const net = require("node:net");8
const crypto = require("node:crypto");9
const zlib = require("node:zlib");10
const fs = require("node:fs");11
const path = require("node:path");12
const { once } = require("node:events");13
const { Server } = require("ssh2");14
const { parseTarget, normalizeOptions } = require("../src/main/diagnostics/common");15
const { HttpDiagnostics, nativeRequest, http3Request, getCurlCapability, MAX_BODY, securityHeaders, decodeBody, decoderInfo } = require("../src/main/diagnostics/httpDiagnostics");16
const { validateRuntime } = require("../src/main/diagnostics/httpRuntime");17
const { tlsProbe, tcpProbe, websocketProbe, sshIdentification } = require("../src/main/diagnostics/socketDiagnostics");18
const fixtures = path.join(__dirname, "fixtures", "diagnostics");19
const key = fs.readFileSync(path.join(fixtures, "server-key.pem")), cert = fs.readFileSync(path.join(fixtures, "server.pem")), ca = fs.readFileSync(path.join(fixtures, "ca.pem"));20
const signal = () => new AbortController().signal;21
const settings = { timeoutMs: 1000 };22
async function listen(t, server, host = "127.0.0.1") {23
const sockets = new Set(); server.on("connection", (socket) => { sockets.add(socket); socket.once("close", () => sockets.delete(socket)); });24
server.listen(0, host); await once(server, "listening");25
t.after(async () => { for (const socket of sockets) { if (socket.destroy) socket.destroy(); else socket.end(); } await new Promise((resolve) => server.close(resolve)); }); return server.address().port;26
}27
const dns = { resolve: async () => [{ address: "127.0.0.1", family: 4 }] };28
test("HTTP/1.1 measures actual bytes, headers, cold/warm reuse and bounded redirect chains", async (t) => {29
const server = http.createServer((req, res) => {30
if (req.url === "/loop") { res.writeHead(302, { location: "/loop" }); return res.end(); }31
if (req.url === "/redirect") { res.writeHead(301, { location: "/ok" }); return res.end(); }32
res.writeHead(200, { "content-type": "text/plain", "set-cookie": "password=private", "content-security-policy": "frame-ancestors 'none'" }); res.end("measured body");33
});34
const port = await listen(t, server), service = new HttpDiagnostics(dns, { native: true }), target = parseTarget(`http://localhost:${port}/redirect`);35
const options = normalizeOptions({ target: target.url, tools: ["http"], protocols: ["1.1"], repetitions: 3, warm: true });36
const result = await service.run(target, "127.0.0.1", options, signal(), () => {}), samples = result.comparisons[0].samples;37
assert.equal(samples.length, 3); assert.equal(samples[0].negotiated, "1.1"); assert.equal(samples[0].downloadedBytes, 13);38
assert.equal(samples[0].redirectCount, 1); assert.equal(samples[0].headers["set-cookie"], "[redacted]");39
assert.ok(samples[1].reused); assert.equal(samples[1].timings.tcpMs, null); assert.ok(samples[0].timings.totalMs > 0);40
assert.ok(!("_body" in samples[0])); assert.equal(result.comparisons[0].errorRate, 0);41
const resources = service.resources();42
try { await assert.rejects(service.request(parseTarget(`http://localhost:${port}/loop`), "127.0.0.1", "1.1", options, signal(), resources), { code: "REDIRECT_LOOP" }); }43
finally { service.dispose(resources); }44
assert.equal(securityHeaders({ "content-security-policy": "frame-ancestors 'none'" }, true).find((h) => h.name === "x-frame-options").status, "superseded-by-csp-frame-ancestors");45
});46
test("HTTP/2 uses real h2 TLS ALPN, verifies certificates and reuses the negotiated session", async (t) => {47
const server = http2.createSecureServer({ key, cert }); server.on("sessionError", () => {}); server.on("stream", (stream) => { stream.respond({ ":status": 200, "content-type": "text/plain" }); stream.end("h2 payload"); });48
const port = await listen(t, server), service = new HttpDiagnostics(dns, { ca, native: true }), target = parseTarget(`https://localhost:${port}/`);49
const options = normalizeOptions({ target: target.url, tools: ["http"], protocols: ["2"], repetitions: 3, warm: true });50
const result = await service.run(target, "127.0.0.1", options, signal(), () => {}), samples = result.comparisons[0].samples;51
assert.equal(samples[0].negotiated, "2"); assert.equal(samples[0].alpn, "h2"); assert.equal(samples[0].downloadedBytes, 10);52
assert.equal(samples[0].reused, false); assert.equal(samples[1].reused, true); assert.equal(samples[2].timings.tlsMs, null);53
await assert.rejects(nativeRequest(new URL(target.url), "127.0.0.1", "2", { ...settings, signal: signal() }));54
});55
test("HTTP timeout, cancellation and body cap terminate local requests", async (t) => {56
const server = http.createServer((req, res) => { if (req.url === "/large") res.end(Buffer.alloc(MAX_BODY + 1)); });57
const port = await listen(t, server);58
await assert.rejects(nativeRequest(new URL(`http://127.0.0.1:${port}/large`), "127.0.0.1", "1.1", { ...settings, signal: signal() }), { code: "BODY_LIMIT" });59
await assert.rejects(nativeRequest(new URL(`http://127.0.0.1:${port}/hang`), "127.0.0.1", "1.1", { timeoutMs: 30, signal: signal() }), { code: "TIMEOUT" });60
const controller = new AbortController(), request = nativeRequest(new URL(`http://127.0.0.1:${port}/hang`), "127.0.0.1", "1.1", { ...settings, signal: controller.signal });61
controller.abort(); await assert.rejects(request, { code: "CANCELLED" });62
});63
test("compression confirms gzip/deflate/Brotli using real decompression and rejects ignored Accept-Encoding", async (t) => {64
const body = Buffer.from("compression test ".repeat(100));65
const server = http.createServer((req, res) => {66
const encoding = req.headers["accept-encoding"];67
const compress = { gzip: zlib.gzipSync, deflate: zlib.deflateSync, br: zlib.brotliCompressSync, zstd: zlib.zstdCompressSync }[encoding];68
if (req.url !== "/ignored" && compress) { res.setHeader("content-encoding", encoding); res.end(compress(body)); } else res.end(body);69
});70
const port = await listen(t, server), service = new HttpDiagnostics(dns, { native: true });71
for (const ignored of [false, true]) {72
const target = parseTarget(`http://localhost:${port}/${ignored ? "ignored" : "ok"}`), options = normalizeOptions({ target: target.url, tools: ["compression"] });73
const result = await service.compression(target, "127.0.0.1", options, signal(), () => {}), br = result.results.find((r) => r.encoding === "br");74
assert.equal(br.status, ignored ? "unsupported" : "success"); if (!ignored) { assert.equal(br.decodedBytes, body.length); assert.ok(br.ratio < 1); assert.equal(br.comparable, true); }75
}76
});77
test("HTTP/3 requires HTTP3-enabled curl, --http3-only and an actual version 3 result; fallback is rejected", async () => {78
const url = new URL("https://localhost/"), calls = [];79
const metrics = { http_version: "3", response_code: 200, remote_ip: "127.0.0.1", remote_port: 443, size_download: 4, content_type: "text/plain", ssl_verify_result: 0, time_appconnect: 0.02, time_starttransfer: 0.03, time_total: 0.04, speed_download: 100 };80
const runner = async (_exe, args) => { calls.push(args); return args.includes("--version") ? { stdout: Buffer.from("curl 8.10.0 test\nFeatures: SSL HTTP2 HTTP3\n") } : { code: 0, stdout: Buffer.from("HTTP/3 103\r\nlink: </early.css>\r\n\r\nHTTP/3 200\r\ncontent-type: text/plain\r\n\r\nbody\nCHJ_METRICS:" + JSON.stringify(metrics)), stderr: "" }; };81
const result = await http3Request(url, "127.0.0.1", settings, signal(), runner);82
assert.equal(result.negotiated, "3"); assert.equal(result.headers["content-type"], "text/plain"); assert.equal(result.headers.link, undefined); assert.equal(result.timings.tcpMs, null); assert.equal(result.timings.quicHandshakeMs, 20);83
assert.ok(calls[1].includes("--http3-only")); assert.ok(!calls[1].includes("--http3")); assert.ok(!calls[1].includes("--insecure")); assert.equal(calls[1][0], "--disable");84
metrics.http_version = "2"; assert.equal((await http3Request(url, "127.0.0.1", settings, signal(), runner)).code, "HTTP3_FALLBACK_REJECTED");85
assert.equal((await http3Request(url, "127.0.0.1", settings, signal(), async () => ({ stdout: Buffer.from("curl 8.7.1\nFeatures: SSL HTTP2\n") }))).code, "HTTP_RUNTIME_INCOMPATIBLE");86
metrics.http_version = "3";87
await http3Request(new URL("https://localhost:444/"), "127.0.0.1", settings, signal(), runner);88
assert.ok(calls.at(-1).includes("localhost:444:127.0.0.1"));89
assert.equal((await getCurlCapability(async () => { throw Object.assign(new Error("missing"), { code: "ENOENT" }); })).available, false);90
});91
test("TLS inspection differentiates trust, hostname mismatch and expiration without changing global verification", async (t) => {92
const server = tls.createServer({ key, cert }), expiredServer = tls.createServer({ key, cert: fs.readFileSync(path.join(fixtures, "expired.pem")) });93
const port = await listen(t, server), expiredPort = await listen(t, expiredServer), target = parseTarget(`https://localhost:${port}/`), prior = process.env.NODE_TLS_REJECT_UNAUTHORIZED;94
const valid = await tlsProbe(target, "127.0.0.1", settings, signal(), "TLSv1.3", ca); assert.equal(valid.valid, true); assert.equal(valid.chain[0].publicKeyBits, 2048); assert.ok(valid.chain[0].signatureAlgorithm); assert.ok(valid.chain[0].fingerprint256);95
const mismatch = await tlsProbe({ ...target, host: "mismatch.test" }, "127.0.0.1", settings, signal(), "TLSv1.2", ca); assert.equal(mismatch.status, "invalid"); assert.ok(mismatch.errors.includes("ERR_TLS_CERT_ALTNAME_INVALID"));96
const untrusted = await tlsProbe(target, "127.0.0.1", settings, signal(), "TLSv1.3"); assert.equal(untrusted.valid, false);97
const expired = await tlsProbe({ ...target, port: expiredPort }, "127.0.0.1", settings, signal(), "TLSv1.3", ca); assert.equal(expired.valid, false); assert.ok(expired.errors.includes("CERT_HAS_EXPIRED")); assert.ok(expired.chain[0].daysUntilExpiration < 0);98
assert.equal(process.env.NODE_TLS_REJECT_UNAUTHORIZED, prior);99
});100
test("TCP reports actual connected/refused states and WebSocket validates the Upgrade accept hash", async (t) => {101
const tcp = net.createServer(), tcpPort = await listen(t, tcp); assert.equal((await tcpProbe("127.0.0.1", tcpPort, settings, signal())).status, "connected");102
const closed = net.createServer(); closed.listen(0, "127.0.0.1"); await once(closed, "listening"); const closedPort = closed.address().port; await new Promise((r) => closed.close(r));103
assert.equal((await tcpProbe("127.0.0.1", closedPort, settings, signal())).status, "refused");104
const ws = http.createServer((_req, res) => res.end());105
ws.on("upgrade", (req, socket) => { const accept = crypto.createHash("sha1").update(req.headers["sec-websocket-key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64"); socket.write("HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Accept: " + (req.url === "/bad" ? "invalid" : accept) + "\r\n\r\n"); });106
const port = await listen(t, ws);107
assert.equal((await websocketProbe(parseTarget(`ws://localhost:${port}/ok`), "127.0.0.1", settings, signal())).valid, true);108
assert.equal((await websocketProbe(parseTarget(`ws://localhost:${port}/bad`), "127.0.0.1", settings, signal())).valid, false);109
});110
test("SSH fingerprint is observed from a real handshake before any authentication request", async (t) => {111
let authentications = 0;112
const server = new Server({ hostKeys: [crypto.createPrivateKey(key).export({ type: "pkcs1", format: "pem" })] }, (client) => { client.on("error", () => {}); client.on("authentication", (ctx) => { authentications++; ctx.reject(); }); });113
server.on("error", () => {}); const port = await listen(t, server);114
const result = await sshIdentification("127.0.0.1", { timeoutMs: 2000, sshPort: port }, signal());115
assert.match(result.fingerprint, /^SHA256:/); assert.equal(result.authenticated, false); assert.equal(authentications, 0);116
});117
test("IPv6 HTTP and HTTPS are measured over a real local IPv6 socket", async (t) => {118
const server = http.createServer((_req, res) => res.end("v6")); let port;119
try { port = await listen(t, server, "::1"); } catch (error) { if (["EAFNOSUPPORT", "EADDRNOTAVAIL"].includes(error.code)) return t.skip("IPv6 loopback unavailable"); throw error; }120
const response = await nativeRequest(new URL(`http://[::1]:${port}/`), "::1", "1.1", { ...settings, signal: signal() });121
assert.equal(response.serverIp, "::1"); assert.equal(response.downloadedBytes, 2); assert.equal(response.negotiated, "1.1");122
const secureServer = require("node:https").createServer({ key, cert }, (_req, res) => res.end("v6 TLS"));123
const securePort = await listen(t, secureServer, "::1");124
const secure = await nativeRequest(new URL(`https://[::1]:${securePort}/`), "::1", "1.1", { ...settings, ca, signal: signal() });125
assert.equal(secure.status, "success"); assert.equal(secure.serverIp, "::1"); assert.ok(secure.timings.tlsMs > 0);126
});127
test("deflate accepts zlib-wrapped and raw DEFLATE; decoders come from the bundled runtime", async () => {128
const body = Buffer.from("raw deflate ".repeat(50));129
assert.deepEqual(await decodeBody(zlib.deflateSync(body), "deflate", signal()), body);130
assert.deepEqual(await decodeBody(zlib.deflateRawSync(body), "deflate", signal()), body);131
const info = decoderInfo(); assert.equal(info.runtime, "bundled"); assert.ok(info.encodings.includes("br")); assert.ok(info.brotli);132
});133
test("compression through the bundled curl runtime decodes gzip/deflate/Brotli/zstd without OS codecs", async (t) => {134
try { validateRuntime(); } catch { t.skip("bundled HTTP runtime for this platform is not present"); return; }135
const body = Buffer.from("bundled compression ".repeat(400));136
const server = http.createServer((req, res) => {137
const encoding = req.headers["accept-encoding"], compress = { gzip: zlib.gzipSync, deflate: zlib.deflateRawSync, br: zlib.brotliCompressSync, zstd: zlib.zstdCompressSync }[encoding];138
if (compress) { res.setHeader("content-encoding", encoding); res.end(compress(body)); } else res.end(body);139
});140
const port = await listen(t, server), service = new HttpDiagnostics(dns);141
const target = parseTarget(`http://127.0.0.1:${port}/`), result = await service.compression(target, "127.0.0.1", normalizeOptions({ target: target.url, tools: ["compression"] }), signal(), () => {});142
for (const encoding of ["identity", "gzip", "deflate", "br", "zstd"]) { const entry = result.results.find((r) => r.encoding === encoding); assert.equal(entry.status, "success", encoding); assert.equal(entry.decodedBytes, body.length); assert.equal(entry.comparable, true); }143
assert.equal(result.status, "success"); assert.equal(result.decoder.runtime, "bundled");144
});SHA-256
b0d389f5349417e68cb67d1e4adf673f9a269dc4a1d730141172773d5f9926f7CH-J Proprietary Software License 1.14 · Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.
SHA-256 des Archivs
a9605c0870a223b6a8542bb0705edd51d8b04f3069582653417a8397fabcc09a