From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- testing/xpcshell/moz-http2/moz-http2.js | 1920 +++++++++++++++++++++++++++++++ 1 file changed, 1920 insertions(+) create mode 100644 testing/xpcshell/moz-http2/moz-http2.js (limited to 'testing/xpcshell/moz-http2/moz-http2.js') diff --git a/testing/xpcshell/moz-http2/moz-http2.js b/testing/xpcshell/moz-http2/moz-http2.js new file mode 100644 index 0000000000..334ccd8e1c --- /dev/null +++ b/testing/xpcshell/moz-http2/moz-http2.js @@ -0,0 +1,1920 @@ +/* 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 = + "" + + "" + + "HOORAY!" + + // 'You Win!' used in tests to check we reached this server + "You Win! (by requesting" + + pathName + + ")" + + ""; + 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; + } + + 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") + ) { + 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; + } + + let answers = []; + 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 = '