summaryrefslogtreecommitdiffstats
path: root/testing/xpcshell/moz-http2
diff options
context:
space:
mode:
Diffstat (limited to 'testing/xpcshell/moz-http2')
-rw-r--r--testing/xpcshell/moz-http2/http2-cert.key28
-rw-r--r--testing/xpcshell/moz-http2/http2-cert.key.keyspec1
-rw-r--r--testing/xpcshell/moz-http2/http2-cert.pem19
-rw-r--r--testing/xpcshell/moz-http2/http2-cert.pem.certspec4
-rw-r--r--testing/xpcshell/moz-http2/moz-http2-child.js33
-rw-r--r--testing/xpcshell/moz-http2/moz-http2.js1949
-rw-r--r--testing/xpcshell/moz-http2/proxy-cert.key28
-rw-r--r--testing/xpcshell/moz-http2/proxy-cert.key.keyspec1
-rw-r--r--testing/xpcshell/moz-http2/proxy-cert.pem19
-rw-r--r--testing/xpcshell/moz-http2/proxy-cert.pem.certspec4
10 files changed, 2086 insertions, 0 deletions
diff --git a/testing/xpcshell/moz-http2/http2-cert.key b/testing/xpcshell/moz-http2/http2-cert.key
new file mode 100644
index 0000000000..09e044f5e0
--- /dev/null
+++ b/testing/xpcshell/moz-http2/http2-cert.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6iFGoRI4W1kH9
+braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEI
+eqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6
+iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Za
+qn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7
+LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs
+2hgKNe2NAgMBAAECggEBAJ7LzjhhpFTsseD+j4XdQ8kvWCXOLpl4hNDhqUnaosWs
+VZskBFDlrJ/gw+McDu+mUlpl8MIhlABO4atGPd6e6CKHzJPnRqkZKcXmrD2IdT9s
+JbpZeec+XY+yOREaPNq4pLDN9fnKsF8SM6ODNcZLVWBSXn47kq18dQTPHcfLAFeI
+r8vh6Pld90AqFRUw1YCDRoZOs3CqeZVqWHhiy1M3kTB/cNkcltItABppAJuSPGgz
+iMnzbLm16+ZDAgQceNkIIGuHAJy4yrrK09vbJ5L7kRss9NtmA1hb6a4Mo7jmQXqg
+SwbkcOoaO1gcoDpngckxW2KzDmAR8iRyWUbuxXxtlEECgYEA3W4dT//r9o2InE0R
+TNqqnKpjpZN0KGyKXCmnF7umA3VkTVyqZ0xLi8cyY1hkYiDkVQ12CKwn1Vttt0+N
+gSfvj6CQmLaRR94GVXNEfhg9Iv59iFrOtRPZWB3V4HwakPXOCHneExNx7O/JznLp
+xD3BJ9I4GQ3oEXc8pdGTAfSMdCsCgYEA16dz2evDgKdn0v7Ak0rU6LVmckB3Gs3r
+ta15b0eP7E1FmF77yVMpaCicjYkQL63yHzTi3UlA66jAnW0fFtzClyl3TEMnXpJR
+3b5JCeH9O/Hkvt9Go5uLODMo70rjuVuS8gcK8myefFybWH/t3gXo59hspXiG+xZY
+EKd7mEW8MScCgYEAlkcrQaYQwK3hryJmwWAONnE1W6QtS1oOtOnX6zWBQAul3RMs
+2xpekyjHu8C7sBVeoZKXLt+X0SdR2Pz2rlcqMLHqMJqHEt1OMyQdse5FX8CT9byb
+WS11bmYhR08ywHryL7J100B5KzK6JZC7smGu+5WiWO6lN2VTFb6cJNGRmS0CgYAo
+tFCnp1qFZBOyvab3pj49lk+57PUOOCPvbMjo+ibuQT+LnRIFVA8Su+egx2got7pl
+rYPMpND+KiIBFOGzXQPVqFv+Jwa9UPzmz83VcbRspiG47UfWBbvnZbCqSgZlrCU2
+TaIBVAMuEgS4VZ0+NPtbF3yaVv+TUQpaSmKHwVHeLQKBgCgGe5NVgB0u9S36ltit
+tYlnPPjuipxv9yruq+nva+WKT0q/BfeIlH3IUf2qNFQhR6caJGv7BU7naqNGq80m
+ks/J5ExR5vBpxzXgc7oBn2pyFJYckbJoccrqv48GRBigJpDjmo1f8wZ7fNt/ULH1
+NBinA5ZsT8d0v3QCr2xDJH9D
+-----END PRIVATE KEY-----
diff --git a/testing/xpcshell/moz-http2/http2-cert.key.keyspec b/testing/xpcshell/moz-http2/http2-cert.key.keyspec
new file mode 100644
index 0000000000..4ad96d5159
--- /dev/null
+++ b/testing/xpcshell/moz-http2/http2-cert.key.keyspec
@@ -0,0 +1 @@
+default
diff --git a/testing/xpcshell/moz-http2/http2-cert.pem b/testing/xpcshell/moz-http2/http2-cert.pem
new file mode 100644
index 0000000000..1f89de1a45
--- /dev/null
+++ b/testing/xpcshell/moz-http2/http2-cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAfugAwIBAgIUCTTdK3eSofAM6mNwAi4Z4YUn8WEwDQYJKoZIhvcNAQEL
+BQAwGTEXMBUGA1UEAwwOIEhUVFAyIFRlc3QgQ0EwIhgPMjAxNzAxMDEwMDAwMDBa
+GA8yMDI3MDEwMTAwMDAwMFowGzEZMBcGA1UEAwwQIEhUVFAyIFRlc3QgQ2VydDCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9
+PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3
+HNUknAJ+zUP8HmnQOCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3Dg
+Dw2N/WYLK7AkkqR9uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7
+EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SK
+lWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0C
+AwEAAaNNMEswSQYDVR0RBEIwQIIJbG9jYWxob3N0gg9mb28uZXhhbXBsZS5jb22C
+EGFsdDEuZXhhbXBsZS5jb22CEGFsdDIuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL
+BQADggEBAE5aEiXOkvEYeWpMhkGheeeaKwgr44qiWJKC5N/8t+NprB3vNCbTMzE9
+09iWQh9EXbwMjMQ8H0uZwedek2sryxsTzxsdTC5qmEtxs/kbf0rTNUwQDjGHvzMk
+gO+ULESdLTcIFJ57olHaZaXtPGm2ELJAOiEpsYFTafmCEPXZ/b+UkGsSkuVLSOIA
+ClaIJgjff/ucvCvRwl79GzGDCoh3qpqhvxQpC/Fcdz1iQDYEVAmjgUrYJe1lTfj8
+ZozM1WIq8fQ3SCXTJK82CnX818tJio2PWq3uzb9vhpuxJJif7WoMP88Jpdh8zcEb
+YL15XPzhQMyor2p6XfwNI3J6347fd7U=
+-----END CERTIFICATE-----
diff --git a/testing/xpcshell/moz-http2/http2-cert.pem.certspec b/testing/xpcshell/moz-http2/http2-cert.pem.certspec
new file mode 100644
index 0000000000..69b3bc83e6
--- /dev/null
+++ b/testing/xpcshell/moz-http2/http2-cert.pem.certspec
@@ -0,0 +1,4 @@
+issuer: HTTP2 Test CA
+subject: HTTP2 Test Cert
+validity:20170101-20270101
+extension:subjectAlternativeName:localhost,foo.example.com,alt1.example.com,alt2.example.com
diff --git a/testing/xpcshell/moz-http2/moz-http2-child.js b/testing/xpcshell/moz-http2/moz-http2-child.js
new file mode 100644
index 0000000000..c8f5d99669
--- /dev/null
+++ b/testing/xpcshell/moz-http2/moz-http2-child.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint-env node */
+
+function sendBackResponse(evalResult, e) {
+ const output = { result: evalResult, error: "", errorStack: "" };
+ if (e) {
+ output.error = e.toString();
+ output.errorStack = e.stack;
+ }
+ process.send(output);
+}
+
+process.on("message", msg => {
+ const code = msg.code;
+ let evalResult = null;
+ try {
+ // eslint-disable-next-line no-eval
+ evalResult = eval(code);
+ if (evalResult instanceof Promise) {
+ evalResult
+ .then(x => sendBackResponse(x))
+ .catch(e => sendBackResponse(undefined, e));
+ return;
+ }
+ } catch (e) {
+ sendBackResponse(undefined, e);
+ return;
+ }
+ sendBackResponse(evalResult);
+});
diff --git a/testing/xpcshell/moz-http2/moz-http2.js b/testing/xpcshell/moz-http2/moz-http2.js
new file mode 100644
index 0000000000..933e99a005
--- /dev/null
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -0,0 +1,1949 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This module is the stateful server side of test_http2.js and is meant
+// to have node be restarted in between each invocation
+
+/* eslint-env node */
+
+var node_http2_root = "../node-http2";
+if (process.env.NODE_HTTP2_ROOT) {
+ node_http2_root = process.env.NODE_HTTP2_ROOT;
+}
+var http2 = require(node_http2_root);
+var fs = require("fs");
+var url = require("url");
+var crypto = require("crypto");
+const dnsPacket = require(`${node_http2_root}/../dns-packet`);
+const ip = require(`${node_http2_root}/../node_ip`);
+const { fork } = require("child_process");
+const path = require("path");
+const zlib = require("zlib");
+
+// Hook into the decompression code to log the decompressed name-value pairs
+var compression_module = node_http2_root + "/lib/protocol/compressor";
+var http2_compression = require(compression_module);
+var HeaderSetDecompressor = http2_compression.HeaderSetDecompressor;
+var originalRead = HeaderSetDecompressor.prototype.read;
+var lastDecompressor;
+var decompressedPairs;
+HeaderSetDecompressor.prototype.read = function () {
+ if (this != lastDecompressor) {
+ lastDecompressor = this;
+ decompressedPairs = [];
+ }
+ var pair = originalRead.apply(this, arguments);
+ if (pair) {
+ decompressedPairs.push(pair);
+ }
+ return pair;
+};
+
+var connection_module = node_http2_root + "/lib/protocol/connection";
+var http2_connection = require(connection_module);
+var Connection = http2_connection.Connection;
+var originalClose = Connection.prototype.close;
+Connection.prototype.close = function (error, lastId) {
+ if (lastId !== undefined) {
+ this._lastIncomingStream = lastId;
+ }
+
+ originalClose.apply(this, arguments);
+};
+
+var framer_module = node_http2_root + "/lib/protocol/framer";
+var http2_framer = require(framer_module);
+var Serializer = http2_framer.Serializer;
+var originalTransform = Serializer.prototype._transform;
+var newTransform = function (frame, encoding, done) {
+ if (frame.type == "DATA") {
+ // Insert our empty DATA frame
+ const emptyFrame = {};
+ emptyFrame.type = "DATA";
+ emptyFrame.data = Buffer.alloc(0);
+ emptyFrame.flags = [];
+ emptyFrame.stream = frame.stream;
+ var buffers = [];
+ Serializer.DATA(emptyFrame, buffers);
+ Serializer.commonHeader(emptyFrame, buffers);
+ for (var i = 0; i < buffers.length; i++) {
+ this.push(buffers[i]);
+ }
+
+ // Reset to the original version for later uses
+ Serializer.prototype._transform = originalTransform;
+ }
+ originalTransform.apply(this, arguments);
+};
+
+function getHttpContent(pathName) {
+ var content =
+ "<!doctype html>" +
+ "<html>" +
+ "<head><title>HOORAY!</title></head>" +
+ // 'You Win!' used in tests to check we reached this server
+ "<body>You Win! (by requesting" +
+ pathName +
+ ")</body>" +
+ "</html>";
+ return content;
+}
+
+function generateContent(size) {
+ var content = "";
+ for (var i = 0; i < size; i++) {
+ content += "0";
+ }
+ return content;
+}
+
+/* This takes care of responding to the multiplexed request for us */
+var m = {
+ mp1res: null,
+ mp2res: null,
+ buf: null,
+ mp1start: 0,
+ mp2start: 0,
+
+ checkReady() {
+ if (this.mp1res != null && this.mp2res != null) {
+ this.buf = generateContent(30 * 1024);
+ this.mp1start = 0;
+ this.mp2start = 0;
+ this.send(this.mp1res, 0);
+ setTimeout(this.send.bind(this, this.mp2res, 0), 5);
+ }
+ },
+
+ send(res, start) {
+ var end = Math.min(start + 1024, this.buf.length);
+ var content = this.buf.substring(start, end);
+ res.write(content);
+ if (end < this.buf.length) {
+ setTimeout(this.send.bind(this, res, end), 10);
+ } else {
+ // Clear these variables so we can run the test again with --verify
+ if (res == this.mp1res) {
+ this.mp1res = null;
+ } else {
+ this.mp2res = null;
+ }
+ res.end();
+ }
+ },
+};
+
+var runlater = function () {};
+runlater.prototype = {
+ req: null,
+ resp: null,
+ fin: true,
+
+ onTimeout: function onTimeout() {
+ this.resp.writeHead(200);
+ if (this.fin) {
+ this.resp.end("It's all good 750ms.");
+ }
+ },
+};
+
+var runConnectLater = function () {};
+runConnectLater.prototype = {
+ req: null,
+ resp: null,
+ connect: false,
+
+ onTimeout: function onTimeout() {
+ if (this.connect) {
+ this.resp.writeHead(200);
+ this.connect = true;
+ setTimeout(executeRunLaterCatchError, 50, this);
+ } else {
+ this.resp.end("HTTP/1.1 200\n\r\n\r");
+ }
+ },
+};
+
+var moreData = function () {};
+moreData.prototype = {
+ req: null,
+ resp: null,
+ iter: 3,
+
+ onTimeout: function onTimeout() {
+ // 1mb of data
+ const content = generateContent(1024 * 1024);
+ this.resp.write(content); // 1mb chunk
+ this.iter--;
+ if (!this.iter) {
+ this.resp.end();
+ } else {
+ setTimeout(executeRunLater, 1, this);
+ }
+ },
+};
+
+function executeRunLater(arg) {
+ arg.onTimeout();
+}
+
+function executeRunLaterCatchError(arg) {
+ arg.onTimeout();
+}
+
+var h11required_conn = null;
+var h11required_header = "yes";
+var didRst = false;
+var rstConnection = null;
+var illegalheader_conn = null;
+
+var gDoHPortsLog = [];
+var gDoHNewConnLog = {};
+var gDoHRequestCount = 0;
+
+// eslint-disable-next-line complexity
+function handleRequest(req, res) {
+ var u = "";
+ if (req.url != undefined) {
+ u = url.parse(req.url, true);
+ }
+ var content = getHttpContent(u.pathname);
+ var push, push1, push1a, push2, push3;
+
+ // PushService tests.
+ var pushPushServer1, pushPushServer2, pushPushServer3, pushPushServer4;
+
+ function createCNameContent(payload) {
+ let packet = dnsPacket.decode(payload);
+ if (
+ packet.questions[0].name == "cname.example.com" &&
+ packet.questions[0].type == "A"
+ ) {
+ return dnsPacket.encode({
+ id: 0,
+ type: "response",
+ flags: dnsPacket.RECURSION_DESIRED,
+ questions: [{ name: packet.questions[0].name, type: "A", class: "IN" }],
+ answers: [
+ {
+ name: packet.questions[0].name,
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "pointing-elsewhere.example.com",
+ },
+ ],
+ });
+ }
+ if (
+ packet.questions[0].name == "pointing-elsewhere.example.com" &&
+ packet.questions[0].type == "A"
+ ) {
+ return dnsPacket.encode({
+ id: 0,
+ type: "response",
+ flags: dnsPacket.RECURSION_DESIRED,
+ questions: [{ name: packet.questions[0].name, type: "A", class: "IN" }],
+ answers: [
+ {
+ name: packet.questions[0].name,
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "99.88.77.66",
+ },
+ ],
+ });
+ }
+
+ return dnsPacket.encode({
+ id: 0,
+ type: "response",
+ flags: dnsPacket.RECURSION_DESIRED | dnsPacket.rcodes.toRcode("NXDOMAIN"),
+ questions: [
+ {
+ name: packet.questions[0].name,
+ type: packet.questions[0].type,
+ class: "IN",
+ },
+ ],
+ answers: [],
+ });
+ }
+
+ function createCNameARecord() {
+ // test23 asks for cname-a.example.com
+ // this responds with a CNAME to here.example.com *and* an A record
+ // for here.example.com
+ let rContent;
+
+ rContent = Buffer.from(
+ "0000" +
+ "0100" +
+ "0001" + // QDCOUNT
+ "0002" + // ANCOUNT
+ "00000000" + // NSCOUNT + ARCOUNT
+ "07636E616D652d61" + // cname-a
+ "076578616D706C6503636F6D00" + // .example.com
+ "00010001" + // question type (A) + question class (IN)
+ // answer record 1
+ "C00C" + // name pointer to cname-a.example.com
+ "0005" + // type (CNAME)
+ "0001" + // class
+ "00000037" + // TTL
+ "0012" + // RDLENGTH
+ "0468657265" + // here
+ "076578616D706C6503636F6D00" + // .example.com
+ // answer record 2, the A entry for the CNAME above
+ "0468657265" + // here
+ "076578616D706C6503636F6D00" + // .example.com
+ "0001" + // type (A)
+ "0001" + // class
+ "00000037" + // TTL
+ "0004" + // RDLENGTH
+ "09080706", // IPv4 address
+ "hex"
+ );
+
+ return rContent;
+ }
+
+ function responseType(packet, responseIP) {
+ if (
+ !!packet.questions.length &&
+ packet.questions[0].name == "confirm.example.com" &&
+ packet.questions[0].type == "NS"
+ ) {
+ return "NS";
+ }
+
+ return ip.isV4Format(responseIP) ? "A" : "AAAA";
+ }
+
+ function handleAuth() {
+ // There's a Set-Cookie: header in the response for "/dns" , which this
+ // request subsequently would include if the http channel wasn't
+ // anonymous. Thus, if there's a cookie in this request, we know Firefox
+ // mishaved. If there's not, we're fine.
+ if (req.headers.cookie) {
+ res.writeHead(403);
+ res.end("cookie for me, not for you");
+ return false;
+ }
+ if (req.headers.authorization != "user:password") {
+ res.writeHead(401);
+ res.end("bad boy!");
+ return false;
+ }
+
+ return true;
+ }
+
+ function createDNSAnswer(response, packet, responseIP, requestPayload) {
+ // This shuts down the connection so we can test if the client reconnects
+ if (packet.questions.length && packet.questions[0].name == "closeme.com") {
+ response.stream.connection.close("INTERNAL_ERROR", response.stream.id);
+ return null;
+ }
+
+ let answers = [];
+ if (packet.questions.length && packet.questions[0].name.endsWith(".pd")) {
+ // Bug 1543811: test edns padding extension. Return whether padding was
+ // included via the first half of the ip address (1.1 vs 2.2) and the
+ // size of the request in the second half of the ip address allowing to
+ // verify that the correct amount of padding was added.
+ if (
+ !!packet.additionals.length &&
+ packet.additionals[0].type == "OPT" &&
+ packet.additionals[0].options.some(o => o.type === "PADDING")
+ ) {
+ // add padding to the response, because the client must be able ignore it
+ answers.push({
+ name: ".",
+ type: "PADDING",
+ data: Buffer.from(
+ // PADDING_PADDING_PADDING
+ "50414444494e475f50414444494e475f50414444494e47",
+ "hex"
+ ),
+ });
+ responseIP =
+ "1.1." +
+ ((requestPayload.length >> 8) & 0xff) +
+ "." +
+ (requestPayload.length & 0xff);
+ } else {
+ responseIP =
+ "2.2." +
+ ((requestPayload.length >> 8) & 0xff) +
+ "." +
+ (requestPayload.length & 0xff);
+ }
+ }
+
+ if (u.query.corruptedAnswer) {
+ // DNS response header is 12 bytes, we check for this minimum length
+ // at the start of decoding so this is the simplest way to force
+ // a decode error.
+ return "\xFF\xFF\xFF\xFF";
+ }
+
+ // Because we send two TRR requests (A and AAAA), skip the first two
+ // requests when testing retry.
+ if (u.query.retryOnDecodeFailure && gDoHRequestCount < 2) {
+ gDoHRequestCount++;
+ return "\xFF\xFF\xFF\xFF";
+ }
+
+ function responseData() {
+ if (
+ !!packet.questions.length &&
+ packet.questions[0].name == "confirm.example.com" &&
+ packet.questions[0].type == "NS"
+ ) {
+ return "ns.example.com";
+ }
+
+ return responseIP;
+ }
+
+ if (
+ responseIP != "none" &&
+ responseType(packet, responseIP) == packet.questions[0].type
+ ) {
+ answers.push({
+ name: u.query.hostname ? u.query.hostname : packet.questions[0].name,
+ ttl: 55,
+ type: responseType(packet, responseIP),
+ flush: false,
+ data: responseData(),
+ });
+ }
+
+ // for use with test_dns_by_type_resolve.js
+ if (packet.questions[0].type == "TXT") {
+ answers.push({
+ name: packet.questions[0].name,
+ type: packet.questions[0].type,
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: Buffer.from(
+ "62586B67646D39705932556761584D6762586B676347467A63336476636D513D",
+ "hex"
+ ),
+ });
+ }
+
+ if (u.query.cnameloop) {
+ answers.push({
+ name: "cname.example.com",
+ type: "CNAME",
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: "pointing-elsewhere.example.com",
+ });
+ }
+
+ if (req.headers["accept-language"] || req.headers["user-agent"]) {
+ // If we get this header, don't send back any response. This should
+ // cause the tests to fail. This is easier then actually sending back
+ // the header value into test_trr.js
+ answers = [];
+ }
+
+ let buf = dnsPacket.encode({
+ type: "response",
+ id: packet.id,
+ flags: dnsPacket.RECURSION_DESIRED,
+ questions: packet.questions,
+ answers,
+ });
+
+ return buf;
+ }
+
+ function getDelayFromPacket(packet, type) {
+ let delay = 0;
+ if (packet.questions[0].type == "A") {
+ delay = parseInt(u.query.delayIPv4);
+ } else if (packet.questions[0].type == "AAAA") {
+ delay = parseInt(u.query.delayIPv6);
+ }
+
+ if (u.query.slowConfirm && type == "NS") {
+ delay += 1000;
+ }
+
+ return delay;
+ }
+
+ function writeDNSResponse(response, buf, delay, contentType) {
+ function writeResponse(resp, buffer) {
+ resp.setHeader("Set-Cookie", "trackyou=yes; path=/; max-age=100000;");
+ resp.setHeader("Content-Type", contentType);
+ if (req.headers["accept-encoding"].includes("gzip")) {
+ zlib.gzip(buffer, function (err, result) {
+ resp.setHeader("Content-Encoding", "gzip");
+ resp.setHeader("Content-Length", result.length);
+ try {
+ resp.writeHead(200);
+ resp.end(result);
+ } catch (e) {
+ // connection was closed by the time we started writing.
+ }
+ });
+ } else {
+ const output = Buffer.from(buffer, "utf-8");
+ resp.setHeader("Content-Length", output.length);
+ try {
+ resp.writeHead(200);
+ resp.write(output);
+ resp.end("");
+ } catch (e) {
+ // connection was closed by the time we started writing.
+ }
+ }
+ }
+
+ if (delay) {
+ setTimeout(
+ arg => {
+ writeResponse(arg[0], arg[1]);
+ },
+ delay,
+ [response, buf]
+ );
+ return;
+ }
+
+ writeResponse(response, buf);
+ }
+
+ if (req.httpVersionMajor === 2) {
+ res.setHeader("X-Connection-Http2", "yes");
+ res.setHeader("X-Http2-StreamId", "" + req.stream.id);
+ } else {
+ res.setHeader("X-Connection-Http2", "no");
+ }
+
+ if (u.pathname === "/exit") {
+ res.setHeader("Content-Type", "text/plain");
+ res.setHeader("Connection", "close");
+ res.writeHead(200);
+ res.end("ok");
+ process.exit();
+ }
+
+ if (req.method == "CONNECT") {
+ if (req.headers.host == "illegalhpacksoft.example.com:80") {
+ illegalheader_conn = req.stream.connection;
+ res.setHeader("Content-Type", "text/html");
+ res.setHeader("x-softillegalhpack", "true");
+ res.writeHead(200);
+ res.end(content);
+ return;
+ } else if (req.headers.host == "illegalhpackhard.example.com:80") {
+ res.setHeader("Content-Type", "text/html");
+ res.setHeader("x-hardillegalhpack", "true");
+ res.writeHead(200);
+ res.end(content);
+ return;
+ } else if (req.headers.host == "750.example.com:80") {
+ // This response will mock a response through a proxy to a HTTP server.
+ // After 750ms , a 200 response for the proxy will be sent then
+ // after additional 50ms a 200 response for the HTTP GET request.
+ let rl = new runConnectLater();
+ rl.req = req;
+ rl.resp = res;
+ setTimeout(executeRunLaterCatchError, 750, rl);
+ return;
+ } else if (req.headers.host == "h11required.com:80") {
+ if (req.httpVersionMajor === 2) {
+ res.stream.reset("HTTP_1_1_REQUIRED");
+ }
+ return;
+ }
+ } else if (u.pathname === "/750ms") {
+ let rl = new runlater();
+ rl.req = req;
+ rl.resp = res;
+ setTimeout(executeRunLater, 750, rl);
+ return;
+ } else if (u.pathname === "/750msNoData") {
+ let rl = new runlater();
+ rl.req = req;
+ rl.resp = res;
+ rl.fin = false;
+ setTimeout(executeRunLater, 750, rl);
+ return;
+ } else if (u.pathname === "/multiplex1" && req.httpVersionMajor === 2) {
+ res.setHeader("Content-Type", "text/plain");
+ res.writeHead(200);
+ m.mp1res = res;
+ m.checkReady();
+ return;
+ } else if (u.pathname === "/multiplex2" && req.httpVersionMajor === 2) {
+ res.setHeader("Content-Type", "text/plain");
+ res.writeHead(200);
+ m.mp2res = res;
+ m.checkReady();
+ return;
+ } else if (u.pathname === "/header") {
+ var val = req.headers["x-test-header"];
+ if (val) {
+ res.setHeader("X-Received-Test-Header", val);
+ }
+ } else if (u.pathname === "/doubleheader") {
+ res.setHeader("Content-Type", "text/html");
+ res.writeHead(200);
+ res.write(content);
+ res.writeHead(200);
+ res.end();
+ return;
+ } else if (u.pathname === "/cookie_crumbling") {
+ res.setHeader("X-Received-Header-Pairs", JSON.stringify(decompressedPairs));
+ } else if (u.pathname === "/push") {
+ push = res.push("/push.js");
+ push.writeHead(200, {
+ "content-type": "application/javascript",
+ pushed: "yes",
+ "content-length": 11,
+ "X-Connection-Http2": "yes",
+ });
+ push.end("// comments");
+ content = '<head> <script src="push.js"/></head>body text';
+ } else if (u.pathname === "/push.js") {
+ content = "// comments";
+ res.setHeader("pushed", "no");
+ } else if (u.pathname === "/push2") {
+ push = res.push("/push2.js");
+ push.writeHead(200, {
+ "content-type": "application/javascript",
+ pushed: "yes",
+ // no content-length
+ "X-Connection-Http2": "yes",
+ });
+ push.end("// comments");
+ content = '<head> <script src="push2.js"/></head>body text';
+ } else if (u.pathname === "/push5") {
+ push = res.push("/push5.js");
+ push.writeHead(200, {
+ "content-type": "application/javascript",
+ pushed: "yes",
+ // no content-length
+ "X-Connection-Http2": "yes",
+ });
+ content = generateContent(1024 * 150);
+ push.write(content);
+ push.end();
+ content = '<head> <script src="push5.js"/></head>body text';
+ } else if (u.pathname === "/pushapi1") {
+ push1 = res.push({
+ hostname: "localhost:" + serverPort,
+ port: serverPort,
+ path: "/pushapi1/1",
+ method: "GET",
+ headers: { "x-pushed-request": "true", "x-foo": "bar" },
+ });
+ push1.writeHead(200, {
+ pushed: "yes",
+ "content-length": 1,
+ subresource: "1",
+ "X-Connection-Http2": "yes",
+ });
+ push1.end("1");
+
+ push1a = res.push({
+ hostname: "localhost:" + serverPort,
+ port: serverPort,
+ path: "/pushapi1/1",
+ method: "GET",
+ headers: { "x-foo": "bar", "x-pushed-request": "true" },
+ });
+ push1a.writeHead(200, {
+ pushed: "yes",
+ "content-length": 1,
+ subresource: "1a",
+ "X-Connection-Http2": "yes",
+ });
+ push1a.end("1");
+
+ push2 = res.push({
+ hostname: "localhost:" + serverPort,
+ port: serverPort,
+ path: "/pushapi1/2",
+ method: "GET",
+ headers: { "x-pushed-request": "true" },
+ });
+ push2.writeHead(200, {
+ pushed: "yes",
+ subresource: "2",
+ "content-length": 1,
+ "X-Connection-Http2": "yes",
+ });
+ push2.end("2");
+
+ push3 = res.push({
+ hostname: "localhost:" + serverPort,
+ port: serverPort,
+ path: "/pushapi1/3",
+ method: "GET",
+ headers: { "x-pushed-request": "true", "Accept-Encoding": "br" },
+ });
+ push3.writeHead(200, {
+ pushed: "yes",
+ "content-length": 6,
+ subresource: "3",
+ "content-encoding": "br",
+ "X-Connection-Http2": "yes",
+ });
+ push3.end(Buffer.from([0x8b, 0x00, 0x80, 0x33, 0x0a, 0x03])); // '3\n'
+
+ content = "0";
+ } else if (u.pathname === "/big") {
+ content = generateContent(128 * 1024);
+ var hash = crypto.createHash("md5");
+ hash.update(content);
+ let md5 = hash.digest("hex");
+ res.setHeader("X-Expected-MD5", md5);
+ } else if (u.pathname === "/huge") {
+ content = generateContent(1024);
+ res.setHeader("Content-Type", "text/plain");
+ res.writeHead(200);
+ // 1mb of data
+ for (let i = 0; i < 1024 * 1; i++) {
+ res.write(content); // 1kb chunk
+ }
+ res.end();
+ return;
+ } else if (u.pathname === "/post" || u.pathname === "/patch") {
+ if (req.method != "POST" && req.method != "PATCH") {
+ res.writeHead(405);
+ res.end("Unexpected method: " + req.method);
+ return;
+ }
+
+ var post_hash = crypto.createHash("md5");
+ var received_data = false;
+ req.on("data", function receivePostData(chunk) {
+ received_data = true;
+ post_hash.update(chunk.toString());
+ });
+ req.on("end", function finishPost() {
+ let md5 = received_data ? post_hash.digest("hex") : "0";
+ res.setHeader("X-Calculated-MD5", md5);
+ res.writeHead(200);
+ res.end(content);
+ });
+
+ return;
+ } else if (u.pathname === "/750msPost") {
+ if (req.method != "POST") {
+ res.writeHead(405);
+ res.end("Unexpected method: " + req.method);
+ return;
+ }
+
+ var accum = 0;
+ req.on("data", function receivePostData(chunk) {
+ accum += chunk.length;
+ });
+ req.on("end", function finishPost() {
+ res.setHeader("X-Recvd", accum);
+ let rl = new runlater();
+ rl.req = req;
+ rl.resp = res;
+ setTimeout(executeRunLater, 750, rl);
+ });
+
+ return;
+ } else if (u.pathname === "/h11required_stream") {
+ if (req.httpVersionMajor === 2) {
+ h11required_conn = req.stream.connection;
+ res.stream.reset("HTTP_1_1_REQUIRED");
+ return;
+ }
+ } else if (u.pathname === "/bigdownload") {
+ res.setHeader("Content-Type", "text/html");
+ res.writeHead(200);
+
+ let rl = new moreData();
+ rl.req = req;
+ rl.resp = res;
+ setTimeout(executeRunLater, 1, rl);
+ return;
+ } else if (u.pathname === "/h11required_session") {
+ if (req.httpVersionMajor === 2) {
+ if (h11required_conn !== req.stream.connection) {
+ h11required_header = "no";
+ }
+ res.stream.connection.close("HTTP_1_1_REQUIRED", res.stream.id - 2);
+ return;
+ }
+ res.setHeader("X-H11Required-Stream-Ok", h11required_header);
+ } else if (u.pathname === "/rstonce") {
+ if (!didRst && req.httpVersionMajor === 2) {
+ didRst = true;
+ rstConnection = req.stream.connection;
+ req.stream.reset("REFUSED_STREAM");
+ return;
+ }
+
+ if (rstConnection === null || rstConnection !== req.stream.connection) {
+ if (req.httpVersionMajor != 2) {
+ res.setHeader("Connection", "close");
+ }
+ res.writeHead(400);
+ res.end("WRONG CONNECTION, HOMIE!");
+ return;
+ }
+
+ // Clear these variables so we can run the test again with --verify
+ didRst = false;
+ rstConnection = null;
+
+ if (req.httpVersionMajor != 2) {
+ res.setHeader("Connection", "close");
+ }
+ res.writeHead(200);
+ res.end("It's all good.");
+ return;
+ } else if (u.pathname === "/continuedheaders") {
+ var pushRequestHeaders = { "x-pushed-request": "true" };
+ var pushResponseHeaders = {
+ "content-type": "text/plain",
+ "content-length": "2",
+ "X-Connection-Http2": "yes",
+ };
+ var pushHdrTxt =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ var pullHdrTxt = pushHdrTxt.split("").reverse().join("");
+ for (let i = 0; i < 265; i++) {
+ pushRequestHeaders["X-Push-Test-Header-" + i] = pushHdrTxt;
+ res.setHeader("X-Pull-Test-Header-" + i, pullHdrTxt);
+ }
+ push = res.push({
+ hostname: "localhost:" + serverPort,
+ port: serverPort,
+ path: "/continuedheaders/push",
+ method: "GET",
+ headers: pushRequestHeaders,
+ });
+ push.writeHead(200, pushResponseHeaders);
+ push.end("ok");
+ } else if (u.pathname === "/altsvc1") {
+ if (
+ req.httpVersionMajor != 2 ||
+ req.scheme != "http" ||
+ req.headers["alt-used"] != "foo.example.com:" + serverPort
+ ) {
+ res.writeHead(400);
+ res.end("WHAT?");
+ return;
+ }
+ // test the alt svc frame for use with altsvc2
+ res.altsvc(
+ "foo.example.com",
+ serverPort,
+ "h2",
+ 3600,
+ req.headers["x-redirect-origin"]
+ );
+ } else if (u.pathname === "/altsvc2") {
+ if (
+ req.httpVersionMajor != 2 ||
+ req.scheme != "http" ||
+ req.headers["alt-used"] != "foo.example.com:" + serverPort
+ ) {
+ res.writeHead(400);
+ res.end("WHAT?");
+ return;
+ }
+ }
+
+ // for use with test_altsvc.js
+ else if (u.pathname === "/altsvc-test") {
+ res.setHeader("Cache-Control", "no-cache");
+ res.setHeader("Alt-Svc", "h2=" + req.headers["x-altsvc"]);
+ }
+ // for use with test_http3.js
+ else if (u.pathname === "/http3-test") {
+ res.setHeader("Cache-Control", "no-cache");
+ res.setHeader("Alt-Svc", "h3-29=" + req.headers["x-altsvc"]);
+ }
+ // for use with test_http3.js
+ else if (u.pathname === "/http3-test2") {
+ res.setHeader("Cache-Control", "no-cache");
+ res.setHeader(
+ "Alt-Svc",
+ "h2=foo2.example.com:8000,h3-29=" +
+ req.headers["x-altsvc"] +
+ ",h3-30=foo2.example.com:8443"
+ );
+ }
+ // for use with test_trr.js
+ else if (u.pathname === "/dns-cname") {
+ // asking for cname.example.com
+
+ function emitResponse(response, payload) {
+ let pcontent = createCNameContent(payload);
+ response.setHeader("Content-Type", "application/dns-message");
+ response.setHeader("Content-Length", pcontent.length);
+ response.writeHead(200);
+ response.write(pcontent);
+ response.end("");
+ }
+
+ let payload = Buffer.from("");
+ req.on("data", function receiveData(chunk) {
+ payload = Buffer.concat([payload, chunk]);
+ });
+ req.on("end", function finishedData() {
+ emitResponse(res, payload);
+ });
+ return;
+ } else if (u.pathname == "/get-doh-req-port-log") {
+ let rContent = JSON.stringify(gDoHPortsLog);
+ res.setHeader("Content-Type", "text/plain");
+ res.setHeader("Content-Length", rContent.length);
+ res.writeHead(400);
+ res.end(rContent);
+ return;
+ } else if (u.pathname == "/reset-doh-request-count") {
+ gDoHRequestCount = 0;
+ res.setHeader("Content-Type", "text/plain");
+ res.setHeader("Content-Length", "ok".length);
+ res.writeHead(200);
+ res.write("ok");
+ res.end("");
+ return;
+ } else if (u.pathname == "/doh") {
+ let responseIP = u.query.responseIP;
+ if (!responseIP) {
+ responseIP = "5.5.5.5";
+ }
+
+ let redirect = u.query.redirect;
+ if (redirect) {
+ responseIP = redirect;
+ if (u.query.dns) {
+ res.setHeader(
+ "Location",
+ "https://localhost:" +
+ serverPort +
+ "/doh?responseIP=" +
+ responseIP +
+ "&dns=" +
+ u.query.dns
+ );
+ } else {
+ res.setHeader(
+ "Location",
+ "https://localhost:" + serverPort + "/doh?responseIP=" + responseIP
+ );
+ }
+ res.writeHead(307);
+ res.end("");
+ return;
+ }
+
+ if (u.query.auth) {
+ if (!handleAuth()) {
+ return;
+ }
+ }
+
+ if (u.query.noResponse) {
+ return;
+ }
+
+ if (u.query.push) {
+ // push.example.org has AAAA entry 2018::2018
+ let pcontent = dnsPacket.encode({
+ id: 0,
+ type: "response",
+ flags: dnsPacket.RECURSION_DESIRED,
+ questions: [{ name: "push.example.org", type: "AAAA", class: "IN" }],
+ answers: [
+ {
+ name: "push.example.org",
+ type: "AAAA",
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: "2018::2018",
+ },
+ ],
+ });
+ push = res.push({
+ hostname: "foo.example.com:" + serverPort,
+ port: serverPort,
+ path: "/dns-pushed-response?dns=AAAAAAABAAAAAAAABHB1c2gHZXhhbXBsZQNvcmcAABwAAQ",
+ method: "GET",
+ headers: {
+ accept: "application/dns-message",
+ },
+ });
+ push.writeHead(200, {
+ "content-type": "application/dns-message",
+ pushed: "yes",
+ "content-length": pcontent.length,
+ "X-Connection-Http2": "yes",
+ });
+ push.end(pcontent);
+ }
+
+ let payload = Buffer.from("");
+
+ function emitResponse(response, requestPayload, decodedPacket, delay) {
+ let packet = decodedPacket || dnsPacket.decode(requestPayload);
+ let answer = createDNSAnswer(
+ response,
+ packet,
+ responseIP,
+ requestPayload
+ );
+ if (!answer) {
+ return;
+ }
+ writeDNSResponse(
+ response,
+ answer,
+ delay || getDelayFromPacket(packet, responseType(packet, responseIP)),
+ "application/dns-message"
+ );
+ }
+
+ if (u.query.dns) {
+ payload = Buffer.from(u.query.dns, "base64");
+ emitResponse(res, payload);
+ return;
+ }
+
+ req.on("data", function receiveData(chunk) {
+ payload = Buffer.concat([payload, chunk]);
+ });
+ req.on("end", function finishedData() {
+ // parload is empty when we send redirect response.
+ if (payload.length) {
+ let packet = dnsPacket.decode(payload);
+ let delay;
+ if (u.query.conncycle) {
+ let name = packet.questions[0].name;
+ if (name.startsWith("newconn")) {
+ // If we haven't seen a req for this newconn name before,
+ // or if we've seen one for the same name on the same port,
+ // synthesize a timeout.
+ if (
+ !gDoHNewConnLog[name] ||
+ gDoHNewConnLog[name] == req.remotePort
+ ) {
+ delay = 1000;
+ }
+ if (!gDoHNewConnLog[name]) {
+ gDoHNewConnLog[name] = req.remotePort;
+ }
+ }
+ gDoHPortsLog.push([packet.questions[0].name, req.remotePort]);
+ } else {
+ gDoHPortsLog = [];
+ gDoHNewConnLog = {};
+ }
+ emitResponse(res, payload, packet, delay);
+ }
+ });
+ return;
+ } else if (u.pathname === "/httpssvc") {
+ let payload = Buffer.from("");
+ req.on("data", function receiveData(chunk) {
+ payload = Buffer.concat([payload, chunk]);
+ });
+ req.on("end", function finishedData() {
+ let packet = dnsPacket.decode(payload);
+ let answers = [];
+ answers.push({
+ name: packet.questions[0].name,
+ type: packet.questions[0].type,
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "h3pool",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { key: "no-default-alpn" },
+ { key: "port", value: 8888 },
+ { key: "ipv4hint", value: "1.2.3.4" },
+ { key: "echconfig", value: "123..." },
+ { key: "ipv6hint", value: "::1" },
+ { key: 30, value: "somelargestring" },
+ { key: "odoh", value: "456..." },
+ ],
+ },
+ });
+ answers.push({
+ name: packet.questions[0].name,
+ type: packet.questions[0].type,
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: {
+ priority: 2,
+ name: ".",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "ipv4hint", value: ["1.2.3.4", "5.6.7.8"] },
+ { key: "echconfig", value: "abc..." },
+ { key: "ipv6hint", value: ["::1", "fe80::794f:6d2c:3d5e:7836"] },
+ { key: "odoh", value: "def..." },
+ ],
+ },
+ });
+ answers.push({
+ name: packet.questions[0].name,
+ type: packet.questions[0].type,
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "hello",
+ values: [],
+ },
+ });
+ let buf = dnsPacket.encode({
+ type: "response",
+ id: packet.id,
+ flags: dnsPacket.RECURSION_DESIRED,
+ questions: packet.questions,
+ answers,
+ });
+
+ res.setHeader("Content-Type", "application/dns-message");
+ res.setHeader("Content-Length", buf.length);
+ res.writeHead(200);
+ res.write(buf);
+ res.end("");
+ });
+ return;
+ } else if (u.pathname === "/httpssvc_as_altsvc") {
+ let payload = Buffer.from("");
+ req.on("data", function receiveData(chunk) {
+ payload = Buffer.concat([payload, chunk]);
+ });
+ req.on("end", function finishedData() {
+ let packet = dnsPacket.decode(payload);
+ let answers = [];
+ if (packet.questions[0].type == "HTTPS") {
+ let priority = 1;
+ // Set an invalid priority to test the case when receiving a corrupted
+ // response.
+ if (packet.questions[0].name === "foo.notexisted.com") {
+ priority = 0;
+ }
+ answers.push({
+ name: packet.questions[0].name,
+ type: packet.questions[0].type,
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: {
+ priority,
+ name: "foo.example.com",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "port", value: serverPort },
+ { key: 30, value: "somelargestring" },
+ ],
+ },
+ });
+ } else {
+ answers.push({
+ name: packet.questions[0].name,
+ type: "A",
+ ttl: 55,
+ flush: false,
+ data: "127.0.0.1",
+ });
+ }
+
+ let buf = dnsPacket.encode({
+ type: "response",
+ id: packet.id,
+ flags: dnsPacket.RECURSION_DESIRED,
+ questions: packet.questions,
+ answers,
+ });
+
+ res.setHeader("Content-Type", "application/dns-message");
+ res.setHeader("Content-Length", buf.length);
+ res.writeHead(200);
+ res.write(buf);
+ res.end("");
+ });
+ return;
+ } else if (u.pathname === "/httpssvc_use_iphint") {
+ let payload = Buffer.from("");
+ req.on("data", function receiveData(chunk) {
+ payload = Buffer.concat([payload, chunk]);
+ });
+ req.on("end", function finishedData() {
+ let packet = dnsPacket.decode(payload);
+ let answers = [];
+ answers.push({
+ name: packet.questions[0].name,
+ type: "HTTPS",
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: {
+ priority: 1,
+ name: ".",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "port", value: serverPort },
+ { key: "ipv4hint", value: "127.0.0.1" },
+ ],
+ },
+ });
+
+ let buf = dnsPacket.encode({
+ type: "response",
+ id: packet.id,
+ flags: dnsPacket.RECURSION_DESIRED,
+ questions: packet.questions,
+ answers,
+ });
+
+ res.setHeader("Content-Type", "application/dns-message");
+ res.setHeader("Content-Length", buf.length);
+ res.writeHead(200);
+ res.write(buf);
+ res.end("");
+ });
+ return;
+ } else if (u.pathname === "/dns-cname-a") {
+ let rContent = createCNameARecord();
+ res.setHeader("Content-Type", "application/dns-message");
+ res.setHeader("Content-Length", rContent.length);
+ res.writeHead(200);
+ res.write(rContent);
+ res.end("");
+ return;
+ } else if (u.pathname === "/websocket") {
+ res.setHeader("Upgrade", "websocket");
+ res.setHeader("Connection", "Upgrade");
+ var wshash = crypto.createHash("sha1");
+ wshash.update(req.headers["sec-websocket-key"]);
+ wshash.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+ let key = wshash.digest("base64");
+ res.setHeader("Sec-WebSocket-Accept", key);
+ res.writeHead(101);
+ res.end("something....");
+ return;
+ }
+ // for use with test_dns_by_type_resolve.js
+ else if (u.pathname === "/txt-dns-push") {
+ // _esni_push.example.com has A entry 127.0.0.1
+ let rContent = Buffer.from(
+ "0000010000010001000000000A5F65736E695F70757368076578616D706C6503636F6D0000010001C00C000100010000003700047F000001",
+ "hex"
+ );
+
+ // _esni_push.example.com has TXT entry 2062586B67646D39705932556761584D6762586B676347467A63336476636D513D
+ var pcontent = Buffer.from(
+ "0000818000010001000000000A5F65736E695F70757368076578616D706C6503636F6D0000100001C00C001000010000003700212062586B67646D39705932556761584D6762586B676347467A63336476636D513D",
+ "hex"
+ );
+
+ push = res.push({
+ hostname: "foo.example.com:" + serverPort,
+ port: serverPort,
+ path: "/dns-pushed-response?dns=AAABAAABAAAAAAABCl9lc25pX3B1c2gHZXhhbXBsZQNjb20AABAAAQAAKRAAAAAAAAAIAAgABAABAAA",
+ method: "GET",
+ headers: {
+ accept: "application/dns-message",
+ },
+ });
+ push.writeHead(200, {
+ "content-type": "application/dns-message",
+ pushed: "yes",
+ "content-length": pcontent.length,
+ "X-Connection-Http2": "yes",
+ });
+ push.end(pcontent);
+ res.setHeader("Content-Type", "application/dns-message");
+ res.setHeader("Content-Length", rContent.length);
+ res.writeHead(200);
+ res.write(rContent);
+ res.end("");
+ return;
+ } else if (u.pathname === "/.well-known/http-opportunistic") {
+ res.setHeader("Cache-Control", "no-cache");
+ res.setHeader("Content-Type", "application/json");
+ res.writeHead(200, "OK");
+ res.end('["http://' + req.headers.host + '"]');
+ return;
+ } else if (u.pathname === "/stale-while-revalidate-loop-test") {
+ res.setHeader(
+ "Cache-Control",
+ "s-maxage=86400, stale-while-revalidate=86400, immutable"
+ );
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
+ res.setHeader("X-Content-Type-Options", "nosniff");
+ res.setHeader("Content-Length", "1");
+ res.writeHead(200, "OK");
+ res.end("1");
+ return;
+ }
+
+ // for PushService tests.
+ else if (u.pathname === "/pushSubscriptionSuccess/subscribe") {
+ res.setHeader(
+ "Location",
+ "https://localhost:" + serverPort + "/pushSubscriptionSuccesss"
+ );
+ res.setHeader(
+ "Link",
+ '</pushEndpointSuccess>; rel="urn:ietf:params:push", ' +
+ '</receiptPushEndpointSuccess>; rel="urn:ietf:params:push:receipt"'
+ );
+ res.writeHead(201, "OK");
+ res.end("");
+ return;
+ } else if (u.pathname === "/pushSubscriptionSuccesss") {
+ // do nothing.
+ return;
+ } else if (u.pathname === "/pushSubscriptionMissingLocation/subscribe") {
+ res.setHeader(
+ "Link",
+ '</pushEndpointMissingLocation>; rel="urn:ietf:params:push", ' +
+ '</receiptPushEndpointMissingLocation>; rel="urn:ietf:params:push:receipt"'
+ );
+ res.writeHead(201, "OK");
+ res.end("");
+ return;
+ } else if (u.pathname === "/pushSubscriptionMissingLink/subscribe") {
+ res.setHeader(
+ "Location",
+ "https://localhost:" + serverPort + "/subscriptionMissingLink"
+ );
+ res.writeHead(201, "OK");
+ res.end("");
+ return;
+ } else if (u.pathname === "/pushSubscriptionLocationBogus/subscribe") {
+ res.setHeader("Location", "1234");
+ res.setHeader(
+ "Link",
+ '</pushEndpointLocationBogus; rel="urn:ietf:params:push", ' +
+ '</receiptPushEndpointLocationBogus>; rel="urn:ietf:params:push:receipt"'
+ );
+ res.writeHead(201, "OK");
+ res.end("");
+ return;
+ } else if (u.pathname === "/pushSubscriptionMissingLink1/subscribe") {
+ res.setHeader(
+ "Location",
+ "https://localhost:" + serverPort + "/subscriptionMissingLink1"
+ );
+ res.setHeader(
+ "Link",
+ '</receiptPushEndpointMissingLink1>; rel="urn:ietf:params:push:receipt"'
+ );
+ res.writeHead(201, "OK");
+ res.end("");
+ return;
+ } else if (u.pathname === "/pushSubscriptionMissingLink2/subscribe") {
+ res.setHeader(
+ "Location",
+ "https://localhost:" + serverPort + "/subscriptionMissingLink2"
+ );
+ res.setHeader(
+ "Link",
+ '</pushEndpointMissingLink2>; rel="urn:ietf:params:push"'
+ );
+ res.writeHead(201, "OK");
+ res.end("");
+ return;
+ } else if (u.pathname === "/subscriptionMissingLink2") {
+ // do nothing.
+ return;
+ } else if (u.pathname === "/pushSubscriptionNot201Code/subscribe") {
+ res.setHeader(
+ "Location",
+ "https://localhost:" + serverPort + "/subscriptionNot2xxCode"
+ );
+ res.setHeader(
+ "Link",
+ '</pushEndpointNot201Code>; rel="urn:ietf:params:push", ' +
+ '</receiptPushEndpointNot201Code>; rel="urn:ietf:params:push:receipt"'
+ );
+ res.writeHead(200, "OK");
+ res.end("");
+ return;
+ } else if (u.pathname === "/pushNotifications/subscription1") {
+ pushPushServer1 = res.push({
+ hostname: "localhost:" + serverPort,
+ port: serverPort,
+ path: "/pushNotificationsDeliver1",
+ method: "GET",
+ headers: {
+ "Encryption-Key":
+ 'keyid="notification1"; dh="BO_tgGm-yvYAGLeRe16AvhzaUcpYRiqgsGOlXpt0DRWDRGGdzVLGlEVJMygqAUECarLnxCiAOHTP_znkedrlWoU"',
+ Encryption: 'keyid="notification1";salt="uAZaiXpOSfOLJxtOCZ09dA"',
+ "Content-Encoding": "aesgcm128",
+ },
+ });
+ pushPushServer1.writeHead(200, {
+ subresource: "1",
+ });
+
+ pushPushServer1.end(
+ "370aeb3963f12c4f12bf946bd0a7a9ee7d3eaff8f7aec62b530fc25cfa",
+ "hex"
+ );
+ return;
+ } else if (u.pathname === "/pushNotifications/subscription2") {
+ pushPushServer2 = res.push({
+ hostname: "localhost:" + serverPort,
+ port: serverPort,
+ path: "/pushNotificationsDeliver3",
+ method: "GET",
+ headers: {
+ "Encryption-Key":
+ 'keyid="notification2"; dh="BKVdQcgfncpNyNWsGrbecX0zq3eHIlHu5XbCGmVcxPnRSbhjrA6GyBIeGdqsUL69j5Z2CvbZd-9z1UBH0akUnGQ"',
+ Encryption: 'keyid="notification2";salt="vFn3t3M_k42zHBdpch3VRw"',
+ "Content-Encoding": "aesgcm128",
+ },
+ });
+ pushPushServer2.writeHead(200, {
+ subresource: "1",
+ });
+
+ pushPushServer2.end(
+ "66df5d11daa01e5c802ff97cdf7f39684b5bf7c6418a5cf9b609c6826c04b25e403823607ac514278a7da945",
+ "hex"
+ );
+ return;
+ } else if (u.pathname === "/pushNotifications/subscription3") {
+ pushPushServer3 = res.push({
+ hostname: "localhost:" + serverPort,
+ port: serverPort,
+ path: "/pushNotificationsDeliver3",
+ method: "GET",
+ headers: {
+ "Encryption-Key":
+ 'keyid="notification3";dh="BD3xV_ACT8r6hdIYES3BJj1qhz9wyv7MBrG9vM2UCnjPzwE_YFVpkD-SGqE-BR2--0M-Yf31wctwNsO1qjBUeMg"',
+ Encryption:
+ 'keyid="notification3"; salt="DFq188piWU7osPBgqn4Nlg"; rs=24',
+ "Content-Encoding": "aesgcm128",
+ },
+ });
+ pushPushServer3.writeHead(200, {
+ subresource: "1",
+ });
+
+ pushPushServer3.end(
+ "2caaeedd9cf1059b80c58b6c6827da8ff7de864ac8bea6d5775892c27c005209cbf9c4de0c3fbcddb9711d74eaeebd33f7275374cb42dd48c07168bc2cc9df63e045ce2d2a2408c66088a40c",
+ "hex"
+ );
+ return;
+ } else if (u.pathname == "/pushNotifications/subscription4") {
+ pushPushServer4 = res.push({
+ hostname: "localhost:" + serverPort,
+ port: serverPort,
+ path: "/pushNotificationsDeliver4",
+ method: "GET",
+ headers: {
+ "Crypto-Key":
+ 'keyid="notification4";dh="BJScXUUTcs7D8jJWI1AOxSgAKkF7e56ay4Lek52TqDlWo1yGd5czaxFWfsuP4j7XNWgGYm60-LKpSUMlptxPFVQ"',
+ Encryption: 'keyid="notification4"; salt="sn9p2QqF3V6KBclda8vx7w"',
+ "Content-Encoding": "aesgcm",
+ },
+ });
+ pushPushServer4.writeHead(200, {
+ subresource: "1",
+ });
+
+ pushPushServer4.end(
+ "9eba7ba6192544a39bd9e9b58e702d0748f1776b27f6616cdc55d29ed5a015a6db8f2dd82cd5751a14315546194ff1c18458ab91eb36c9760ccb042670001fd9964557a079553c3591ee131ceb259389cfffab3ab873f873caa6a72e87d262b8684c3260e5940b992234deebf57a9ff3a8775742f3cbcb152d249725a28326717e19cce8506813a155eff5df9bdba9e3ae8801d3cc2b7e7f2f1b6896e63d1fdda6f85df704b1a34db7b2dd63eba11ede154300a318c6f83c41a3d32356a196e36bc905b99195fd91ae4ff3f545c42d17f1fdc1d5bd2bf7516d0765e3a859fffac84f46160b79cedda589f74c25357cf6988cd8ba83867ebd86e4579c9d3b00a712c77fcea3b663007076e21f9819423faa830c2176ff1001c1690f34be26229a191a938517",
+ "hex"
+ );
+ return;
+ } else if (
+ u.pathname === "/pushNotificationsDeliver1" ||
+ u.pathname === "/pushNotificationsDeliver2" ||
+ u.pathname === "/pushNotificationsDeliver3"
+ ) {
+ res.writeHead(410, "GONE");
+ res.end("");
+ return;
+ } else if (u.pathname === "/illegalhpacksoft") {
+ // This will cause the compressor to compress a header that is not legal,
+ // but only affects the stream, not the session.
+ illegalheader_conn = req.stream.connection;
+ res.setHeader("Content-Type", "text/html");
+ res.setHeader("x-softillegalhpack", "true");
+ res.writeHead(200);
+ res.end(content);
+ return;
+ } else if (u.pathname === "/illegalhpackhard") {
+ // This will cause the compressor to insert an HPACK instruction that will
+ // cause a session failure.
+ res.setHeader("Content-Type", "text/html");
+ res.setHeader("x-hardillegalhpack", "true");
+ res.writeHead(200);
+ res.end(content);
+ return;
+ } else if (u.pathname === "/illegalhpack_validate") {
+ if (req.stream.connection === illegalheader_conn) {
+ res.setHeader("X-Did-Goaway", "no");
+ } else {
+ res.setHeader("X-Did-Goaway", "yes");
+ }
+ // Fall through to the default response behavior
+ } else if (u.pathname === "/foldedheader") {
+ res.setHeader("X-Folded-Header", "this is\n folded");
+ // Fall through to the default response behavior
+ } else if (u.pathname === "/emptydata") {
+ // Overwrite the original transform with our version that will insert an
+ // empty DATA frame at the beginning of the stream response, then fall
+ // through to the default response behavior.
+ Serializer.prototype._transform = newTransform;
+ }
+
+ // for use with test_immutable.js
+ else if (u.pathname === "/immutable-test-without-attribute") {
+ res.setHeader("Cache-Control", "max-age=100000");
+ res.setHeader("Etag", "1");
+ if (req.headers["if-none-match"]) {
+ res.setHeader("x-conditional", "true");
+ }
+ // default response from here
+ } else if (u.pathname === "/immutable-test-with-attribute") {
+ res.setHeader("Cache-Control", "max-age=100000, immutable");
+ res.setHeader("Etag", "2");
+ if (req.headers["if-none-match"]) {
+ res.setHeader("x-conditional", "true");
+ }
+ // default response from here
+ } else if (u.pathname === "/immutable-test-expired-with-Expires-header") {
+ res.setHeader("Cache-Control", "immutable");
+ res.setHeader("Expires", "Mon, 01 Jan 1990 00:00:00 GMT");
+ res.setHeader("Etag", "3");
+
+ if (req.headers["if-none-match"]) {
+ res.setHeader("x-conditional", "true");
+ }
+ } else if (
+ u.pathname === "/immutable-test-expired-with-last-modified-header"
+ ) {
+ res.setHeader("Cache-Control", "public, max-age=3600, immutable");
+ res.setHeader("Date", "Mon, 01 Jan 1990 00:00:00 GMT");
+ res.setHeader("Last-modified", "Mon, 01 Jan 1990 00:00:00 GMT");
+ res.setHeader("Etag", "4");
+
+ if (req.headers["if-none-match"]) {
+ res.setHeader("x-conditional", "true");
+ }
+ } else if (u.pathname === "/origin-4") {
+ let originList = [];
+ req.stream.connection.originFrame(originList);
+ res.setHeader("x-client-port", req.remotePort);
+ } else if (u.pathname === "/origin-6") {
+ let originList = [
+ "https://alt1.example.com:" + serverPort,
+ "https://alt2.example.com:" + serverPort,
+ "https://bar.example.com:" + serverPort,
+ ];
+ req.stream.connection.originFrame(originList);
+ res.setHeader("x-client-port", req.remotePort);
+ } else if (u.pathname === "/origin-11-a") {
+ res.setHeader("x-client-port", req.remotePort);
+
+ const pushb = res.push({
+ hostname: "foo.example.com:" + serverPort,
+ port: serverPort,
+ path: "/origin-11-b",
+ method: "GET",
+ headers: { "x-pushed-request": "true", "x-foo": "bar" },
+ });
+ pushb.writeHead(200, {
+ pushed: "yes",
+ "content-length": 1,
+ });
+ pushb.end("1");
+
+ const pushc = res.push({
+ hostname: "bar.example.com:" + serverPort,
+ port: serverPort,
+ path: "/origin-11-c",
+ method: "GET",
+ headers: { "x-pushed-request": "true", "x-foo": "bar" },
+ });
+ pushc.writeHead(200, {
+ pushed: "yes",
+ "content-length": 1,
+ });
+ pushc.end("1");
+
+ const pushd = res.push({
+ hostname: "madeup.example.com:" + serverPort,
+ port: serverPort,
+ path: "/origin-11-d",
+ method: "GET",
+ headers: { "x-pushed-request": "true", "x-foo": "bar" },
+ });
+ pushd.writeHead(200, {
+ pushed: "yes",
+ "content-length": 1,
+ });
+ pushd.end("1");
+
+ const pushe = res.push({
+ hostname: "alt1.example.com:" + serverPort,
+ port: serverPort,
+ path: "/origin-11-e",
+ method: "GET",
+ headers: { "x-pushed-request": "true", "x-foo": "bar" },
+ });
+ pushe.writeHead(200, {
+ pushed: "yes",
+ "content-length": 1,
+ });
+ pushe.end("1");
+ } else if (u.pathname.substring(0, 8) === "/origin-") {
+ // test_origin.js coalescing
+ res.setHeader("x-client-port", req.remotePort);
+ } else if (u.pathname === "/statusphrase") {
+ // Fortunately, the node-http2 API is dumb enough to allow this right on
+ // through, so we can easily test rejecting this on gecko's end.
+ res.writeHead("200 OK");
+ res.end(content);
+ return;
+ } else if (u.pathname === "/doublepush") {
+ push1 = res.push("/doublypushed");
+ push1.writeHead(200, {
+ "content-type": "text/plain",
+ pushed: "yes",
+ "content-length": 6,
+ "X-Connection-Http2": "yes",
+ });
+ push1.end("pushed");
+
+ push2 = res.push("/doublypushed");
+ push2.writeHead(200, {
+ "content-type": "text/plain",
+ pushed: "yes",
+ "content-length": 6,
+ "X-Connection-Http2": "yes",
+ });
+ push2.end("pushed");
+ } else if (u.pathname === "/doublypushed") {
+ content = "not pushed";
+ } else if (u.pathname === "/diskcache") {
+ content = "this was pulled via h2";
+ } else if (u.pathname === "/pushindisk") {
+ var pushedContent = "this was pushed via h2";
+ push = res.push("/diskcache");
+ push.writeHead(200, {
+ "content-type": "text/html",
+ pushed: "yes",
+ "content-length": pushedContent.length,
+ "X-Connection-Http2": "yes",
+ });
+ push.end(pushedContent);
+ }
+
+ // For test_header_Server_Timing.js
+ else if (u.pathname === "/server-timing") {
+ res.setHeader("Content-Type", "text/plain");
+ res.setHeader("Content-Length", "12");
+ res.setHeader("Trailer", "Server-Timing");
+ res.setHeader(
+ "Server-Timing",
+ "metric; dur=123.4; desc=description, metric2; dur=456.78; desc=description1"
+ );
+ res.write("data reached");
+ res.addTrailers({
+ "Server-Timing":
+ "metric3; dur=789.11; desc=description2, metric4; dur=1112.13; desc=description3",
+ });
+ res.end();
+ return;
+ } else if (u.pathname === "/redirect_to_http") {
+ res.setHeader(
+ "Location",
+ `http://test.httpsrr.redirect.com:${u.query.port}/redirect_to_http`
+ );
+ res.writeHead(307);
+ res.end("");
+ return;
+ } else if (u.pathname === "/103_response") {
+ let link_val = req.headers["link-to-set"];
+ if (link_val) {
+ res.setHeader("link", link_val);
+ }
+ res.setHeader("something", "something");
+ res.writeHead(103);
+
+ res.setHeader("Content-Type", "text/plain");
+ res.setHeader("Content-Length", "12");
+ res.writeHead(200);
+ res.write("data reached");
+ res.end();
+ return;
+ } else if (u.pathname.startsWith("/invalid_response_header/")) {
+ // response headers with invalid characters in the name / value (RFC7540 Sec 10.3)
+ let kind = u.pathname.slice("/invalid_response_header/".length);
+ if (kind === "name_spaces") {
+ res.setHeader("With Spaces", "Hello");
+ } else if (kind === "value_line_feed") {
+ res.setHeader("invalid-header", "line\nfeed");
+ } else if (kind === "value_carriage_return") {
+ res.setHeader("invalid-header", "carriage\rreturn");
+ } else if (kind === "value_null") {
+ res.setHeader("invalid-header", "null\0");
+ }
+
+ res.writeHead(200);
+ res.end("");
+ return;
+ } else if (u.pathname === "/origin_header") {
+ let originHeader = req.headers.origin;
+ res.setHeader("Content-Length", originHeader.length);
+ res.setHeader("Content-Type", "text/plain");
+ res.writeHead(200);
+ res.write(originHeader);
+ res.end();
+ return;
+ }
+
+ res.setHeader("Content-Type", "text/html");
+ if (req.httpVersionMajor != 2) {
+ res.setHeader("Connection", "close");
+ }
+ res.writeHead(200);
+ res.end(content);
+}
+
+// Set up the SSL certs for our server - this server has a cert for foo.example.com
+// signed by netwerk/tests/unit/http2-ca.pem
+var options = {
+ key: fs.readFileSync(__dirname + "/http2-cert.key"),
+ cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
+};
+
+if (process.env.HTTP2_LOG !== undefined) {
+ var log_module = node_http2_root + "/test/util";
+ options.log = require(log_module).createLogger("server");
+}
+
+var server = http2.createServer(options, handleRequest);
+
+server.on("connection", function (socket) {
+ socket.on("error", function () {
+ // Ignoring SSL socket errors, since they usually represent a connection that was tore down
+ // by the browser because of an untrusted certificate. And this happens at least once, when
+ // the first test case if done.
+ });
+});
+
+server.on("connect", function (req, clientSocket, head) {
+ clientSocket.write(
+ "HTTP/1.1 404 Not Found\r\nProxy-agent: Node.js-Proxy\r\n\r\n"
+ );
+ clientSocket.destroy();
+});
+
+function makeid(length) {
+ var result = "";
+ var characters =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ var charactersLength = characters.length;
+ for (var i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+}
+
+let globalObjects = {};
+var serverPort;
+
+const listen = (serv, envport) => {
+ if (!serv) {
+ return Promise.resolve(0);
+ }
+
+ let portSelection = 0;
+ if (envport !== undefined) {
+ try {
+ portSelection = parseInt(envport, 10);
+ } catch (e) {
+ portSelection = -1;
+ }
+ }
+ return new Promise(resolve => {
+ serv.listen(portSelection, "0.0.0.0", 2000, () => {
+ resolve(serv.address().port);
+ });
+ });
+};
+
+const http = require("http");
+let httpServer = http.createServer((req, res) => {
+ if (req.method != "POST") {
+ let u = url.parse(req.url, true);
+ if (u.pathname == "/test") {
+ // This path is used to test that the server is working properly
+ res.writeHead(200);
+ res.end("OK");
+ return;
+ }
+ res.writeHead(405);
+ res.end("Unexpected method: " + req.method);
+ return;
+ }
+
+ let code = "";
+ req.on("data", function receivePostData(chunk) {
+ code += chunk;
+ });
+ req.on("end", function finishPost() {
+ let u = url.parse(req.url, true);
+ if (u.pathname == "/fork") {
+ let id = forkProcess();
+ computeAndSendBackResponse(id);
+ return;
+ }
+
+ if (u.pathname.startsWith("/kill/")) {
+ let id = u.pathname.slice(6);
+ let forked = globalObjects[id];
+ if (!forked) {
+ computeAndSendBackResponse(undefined, new Error("could not find id"));
+ return;
+ }
+
+ new Promise((resolve, reject) => {
+ forked.resolve = resolve;
+ forked.reject = reject;
+ forked.kill();
+ })
+ .then(x =>
+ computeAndSendBackResponse(
+ undefined,
+ new Error(`incorrectly resolved ${x}`)
+ )
+ )
+ .catch(e => {
+ // We indicate a proper shutdown by resolving with undefined.
+ if (e && e.toString().match(/child process exit closing code/)) {
+ e = undefined;
+ }
+ computeAndSendBackResponse(undefined, e);
+ });
+ return;
+ }
+
+ if (u.pathname.startsWith("/execute/")) {
+ let id = u.pathname.slice(9);
+ let forked = globalObjects[id];
+ if (!forked) {
+ computeAndSendBackResponse(undefined, new Error("could not find id"));
+ return;
+ }
+
+ new Promise((resolve, reject) => {
+ forked.resolve = resolve;
+ forked.reject = reject;
+ forked.send({ code });
+ })
+ .then(x => sendBackResponse(x))
+ .catch(e => computeAndSendBackResponse(undefined, e));
+ }
+
+ function computeAndSendBackResponse(evalResult, e) {
+ let output = { result: evalResult, error: "", errorStack: "" };
+ if (e) {
+ output.error = e.toString();
+ output.errorStack = e.stack;
+ }
+ sendBackResponse(output);
+ }
+
+ function sendBackResponse(output) {
+ output = JSON.stringify(output);
+
+ res.setHeader("Content-Length", output.length);
+ res.setHeader("Content-Type", "application/json");
+ res.writeHead(200);
+ res.write(output);
+ res.end("");
+ }
+ });
+});
+
+function forkProcess() {
+ let scriptPath = path.resolve(__dirname, "moz-http2-child.js");
+ let id = makeid(6);
+ let forked = fork(scriptPath);
+ forked.errors = "";
+ globalObjects[id] = forked;
+ forked.on("message", msg => {
+ if (forked.resolve) {
+ forked.resolve(msg);
+ forked.resolve = null;
+ } else {
+ console.log(
+ `forked process without handler sent: ${JSON.stringify(msg)}`
+ );
+ forked.errors += `forked process without handler sent: ${JSON.stringify(
+ msg
+ )}\n`;
+ }
+ });
+
+ let exitFunction = (code, signal) => {
+ if (globalObjects[id]) {
+ delete globalObjects[id];
+ } else {
+ // already called
+ return;
+ }
+
+ if (!forked.reject) {
+ console.log(
+ `child process ${id} closing code: ${code} signal: ${signal}`
+ );
+ return;
+ }
+
+ if (forked.errors != "") {
+ forked.reject(forked.errors);
+ forked.errors = "";
+ forked.reject = null;
+ return;
+ }
+
+ forked.reject(`child process exit closing code: ${code} signal: ${signal}`);
+ forked.reject = null;
+ };
+
+ forked.on("error", exitFunction);
+ forked.on("close", exitFunction);
+ forked.on("exit", exitFunction);
+
+ return id;
+}
+
+Promise.all([
+ listen(server, process.env.MOZHTTP2_PORT).then(port => (serverPort = port)),
+ listen(httpServer, process.env.MOZNODE_EXEC_PORT),
+]).then(([sPort, nodeExecPort]) => {
+ console.log(`HTTP2 server listening on ports ${sPort},${nodeExecPort}`);
+});
diff --git a/testing/xpcshell/moz-http2/proxy-cert.key b/testing/xpcshell/moz-http2/proxy-cert.key
new file mode 100644
index 0000000000..09e044f5e0
--- /dev/null
+++ b/testing/xpcshell/moz-http2/proxy-cert.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6iFGoRI4W1kH9
+braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEI
+eqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6
+iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Za
+qn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7
+LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs
+2hgKNe2NAgMBAAECggEBAJ7LzjhhpFTsseD+j4XdQ8kvWCXOLpl4hNDhqUnaosWs
+VZskBFDlrJ/gw+McDu+mUlpl8MIhlABO4atGPd6e6CKHzJPnRqkZKcXmrD2IdT9s
+JbpZeec+XY+yOREaPNq4pLDN9fnKsF8SM6ODNcZLVWBSXn47kq18dQTPHcfLAFeI
+r8vh6Pld90AqFRUw1YCDRoZOs3CqeZVqWHhiy1M3kTB/cNkcltItABppAJuSPGgz
+iMnzbLm16+ZDAgQceNkIIGuHAJy4yrrK09vbJ5L7kRss9NtmA1hb6a4Mo7jmQXqg
+SwbkcOoaO1gcoDpngckxW2KzDmAR8iRyWUbuxXxtlEECgYEA3W4dT//r9o2InE0R
+TNqqnKpjpZN0KGyKXCmnF7umA3VkTVyqZ0xLi8cyY1hkYiDkVQ12CKwn1Vttt0+N
+gSfvj6CQmLaRR94GVXNEfhg9Iv59iFrOtRPZWB3V4HwakPXOCHneExNx7O/JznLp
+xD3BJ9I4GQ3oEXc8pdGTAfSMdCsCgYEA16dz2evDgKdn0v7Ak0rU6LVmckB3Gs3r
+ta15b0eP7E1FmF77yVMpaCicjYkQL63yHzTi3UlA66jAnW0fFtzClyl3TEMnXpJR
+3b5JCeH9O/Hkvt9Go5uLODMo70rjuVuS8gcK8myefFybWH/t3gXo59hspXiG+xZY
+EKd7mEW8MScCgYEAlkcrQaYQwK3hryJmwWAONnE1W6QtS1oOtOnX6zWBQAul3RMs
+2xpekyjHu8C7sBVeoZKXLt+X0SdR2Pz2rlcqMLHqMJqHEt1OMyQdse5FX8CT9byb
+WS11bmYhR08ywHryL7J100B5KzK6JZC7smGu+5WiWO6lN2VTFb6cJNGRmS0CgYAo
+tFCnp1qFZBOyvab3pj49lk+57PUOOCPvbMjo+ibuQT+LnRIFVA8Su+egx2got7pl
+rYPMpND+KiIBFOGzXQPVqFv+Jwa9UPzmz83VcbRspiG47UfWBbvnZbCqSgZlrCU2
+TaIBVAMuEgS4VZ0+NPtbF3yaVv+TUQpaSmKHwVHeLQKBgCgGe5NVgB0u9S36ltit
+tYlnPPjuipxv9yruq+nva+WKT0q/BfeIlH3IUf2qNFQhR6caJGv7BU7naqNGq80m
+ks/J5ExR5vBpxzXgc7oBn2pyFJYckbJoccrqv48GRBigJpDjmo1f8wZ7fNt/ULH1
+NBinA5ZsT8d0v3QCr2xDJH9D
+-----END PRIVATE KEY-----
diff --git a/testing/xpcshell/moz-http2/proxy-cert.key.keyspec b/testing/xpcshell/moz-http2/proxy-cert.key.keyspec
new file mode 100644
index 0000000000..4ad96d5159
--- /dev/null
+++ b/testing/xpcshell/moz-http2/proxy-cert.key.keyspec
@@ -0,0 +1 @@
+default
diff --git a/testing/xpcshell/moz-http2/proxy-cert.pem b/testing/xpcshell/moz-http2/proxy-cert.pem
new file mode 100644
index 0000000000..2fdbed67ed
--- /dev/null
+++ b/testing/xpcshell/moz-http2/proxy-cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAfugAwIBAgIUSQPP46/Ps3zlBla2VmTXZQF0LzkwDQYJKoZIhvcNAQEL
+BQAwGTEXMBUGA1UEAwwOIFByb3h5IFRlc3QgQ0EwIhgPMjAyMjAxMDEwMDAwMDBa
+GA8yMDMyMDEwMTAwMDAwMFowGzEZMBcGA1UEAwwQIFByb3h5IFRlc3QgQ2VydDCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9
+PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3
+HNUknAJ+zUP8HmnQOCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3Dg
+Dw2N/WYLK7AkkqR9uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7
+EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SK
+lWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0C
+AwEAAaNNMEswSQYDVR0RBEIwQIIJbG9jYWxob3N0gg9mb28uZXhhbXBsZS5jb22C
+EGFsdDEuZXhhbXBsZS5jb22CEGFsdDIuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL
+BQADggEBAD64pXpPNZGq0wo+RVjewxqjcEC+nj4de9qVo8nBssIaCCU5nRT5FXh+
+8KERCtP3Q9ZLX4hvSkG5w+Oz0OxBYoad/lp7Ax6CwOCG6/SPIp87MbNzsoJkkubG
++7SAZHjMiHpbh3IVw/m6/nCYTOcmf4E6PgKnGAOT84lBiXbIkbMHbMVskDOHkTO4
+WTz/Kx+OlvvAJD/aj6Yhn7T3bUsHDkzsRJFiPMLqZkPCW0Yl83IuCoNLy8puMAsA
+cYfjVNAiAdVIlwViEUYlHc1jtzH2ZfvPZRylrRHHAPTt8wXffD4WTa9iIqa3x1D4
+/rpcwWElQ/vmNZ7HzBdZZmBRYSd/NVs=
+-----END CERTIFICATE-----
diff --git a/testing/xpcshell/moz-http2/proxy-cert.pem.certspec b/testing/xpcshell/moz-http2/proxy-cert.pem.certspec
new file mode 100644
index 0000000000..c7a11d2bd4
--- /dev/null
+++ b/testing/xpcshell/moz-http2/proxy-cert.pem.certspec
@@ -0,0 +1,4 @@
+issuer: Proxy Test CA
+subject: Proxy Test Cert
+validity:20220101-20320101
+extension:subjectAlternativeName:localhost,foo.example.com,alt1.example.com,alt2.example.com