tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

moz-http2.js (27601B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 // This module is the stateful server side of test_http2.js and is meant
      6 // to have node be restarted in between each invocation
      7 
      8 /* eslint-env node */
      9 
     10 var node_http2_root = "../node-http2";
     11 if (process.env.NODE_HTTP2_ROOT) {
     12  node_http2_root = process.env.NODE_HTTP2_ROOT;
     13 }
     14 var http2 = require(node_http2_root);
     15 var fs = require("fs");
     16 var url = require("url");
     17 var crypto = require("crypto");
     18 const ip = require(`${node_http2_root}/../node_ip`);
     19 const { fork } = require("child_process");
     20 const { spawn } = require("child_process");
     21 const path = require("path");
     22 
     23 // Hook into the decompression code to log the decompressed name-value pairs
     24 var compression_module = node_http2_root + "/lib/protocol/compressor";
     25 var http2_compression = require(compression_module);
     26 var HeaderSetDecompressor = http2_compression.HeaderSetDecompressor;
     27 var originalRead = HeaderSetDecompressor.prototype.read;
     28 var lastDecompressor;
     29 var decompressedPairs;
     30 HeaderSetDecompressor.prototype.read = function () {
     31  if (this != lastDecompressor) {
     32    lastDecompressor = this;
     33    decompressedPairs = [];
     34  }
     35  var pair = originalRead.apply(this, arguments);
     36  if (pair) {
     37    decompressedPairs.push(pair);
     38  }
     39  return pair;
     40 };
     41 
     42 var connection_module = node_http2_root + "/lib/protocol/connection";
     43 var http2_connection = require(connection_module);
     44 var Connection = http2_connection.Connection;
     45 var originalClose = Connection.prototype.close;
     46 Connection.prototype.close = function (error, lastId) {
     47  if (lastId !== undefined) {
     48    this._lastIncomingStream = lastId;
     49  }
     50 
     51  originalClose.apply(this, arguments);
     52 };
     53 
     54 var framer_module = node_http2_root + "/lib/protocol/framer";
     55 var http2_framer = require(framer_module);
     56 var Serializer = http2_framer.Serializer;
     57 var originalTransform = Serializer.prototype._transform;
     58 var newTransform = function (frame) {
     59  if (frame.type == "DATA") {
     60    // Insert our empty DATA frame
     61    const emptyFrame = {};
     62    emptyFrame.type = "DATA";
     63    emptyFrame.data = Buffer.alloc(0);
     64    emptyFrame.flags = [];
     65    emptyFrame.stream = frame.stream;
     66    var buffers = [];
     67    Serializer.DATA(emptyFrame, buffers);
     68    Serializer.commonHeader(emptyFrame, buffers);
     69    for (var i = 0; i < buffers.length; i++) {
     70      this.push(buffers[i]);
     71    }
     72 
     73    // Reset to the original version for later uses
     74    Serializer.prototype._transform = originalTransform;
     75  }
     76  originalTransform.apply(this, arguments);
     77 };
     78 
     79 function getHttpContent(pathName) {
     80  var content =
     81    "<!doctype html>" +
     82    "<html>" +
     83    "<head><title>HOORAY!</title></head>" +
     84    // 'You Win!' used in tests to check we reached this server
     85    "<body>You Win! (by requesting" +
     86    pathName +
     87    ")</body>" +
     88    "</html>";
     89  return content;
     90 }
     91 
     92 function generateContent(size) {
     93  var content = "";
     94  for (var i = 0; i < size; i++) {
     95    content += "0";
     96  }
     97  return content;
     98 }
     99 
    100 /* This takes care of responding to the multiplexed request for us */
    101 var m = {
    102  mp1res: null,
    103  mp2res: null,
    104  buf: null,
    105  mp1start: 0,
    106  mp2start: 0,
    107 
    108  checkReady() {
    109    if (this.mp1res != null && this.mp2res != null) {
    110      this.buf = generateContent(30 * 1024);
    111      this.mp1start = 0;
    112      this.mp2start = 0;
    113      this.send(this.mp1res, 0);
    114      setTimeout(this.send.bind(this, this.mp2res, 0), 5);
    115    }
    116  },
    117 
    118  send(res, start) {
    119    var end = Math.min(start + 1024, this.buf.length);
    120    var content = this.buf.substring(start, end);
    121    res.write(content);
    122    if (end < this.buf.length) {
    123      setTimeout(this.send.bind(this, res, end), 10);
    124    } else {
    125      // Clear these variables so we can run the test again with --verify
    126      if (res == this.mp1res) {
    127        this.mp1res = null;
    128      } else {
    129        this.mp2res = null;
    130      }
    131      res.end();
    132    }
    133  },
    134 };
    135 
    136 var runlater = function () {};
    137 runlater.prototype = {
    138  req: null,
    139  resp: null,
    140  fin: true,
    141 
    142  onTimeout: function onTimeout() {
    143    this.resp.writeHead(200);
    144    if (this.fin) {
    145      this.resp.end("It's all good 750ms.");
    146    }
    147  },
    148 };
    149 
    150 var runConnectLater = function () {};
    151 runConnectLater.prototype = {
    152  req: null,
    153  resp: null,
    154  connect: false,
    155 
    156  onTimeout: function onTimeout() {
    157    if (this.connect) {
    158      this.resp.writeHead(200);
    159      this.connect = true;
    160      setTimeout(executeRunLaterCatchError, 50, this);
    161    } else {
    162      this.resp.end("HTTP/1.1 200\n\r\n\r");
    163    }
    164  },
    165 };
    166 
    167 var moreData = function () {};
    168 moreData.prototype = {
    169  req: null,
    170  resp: null,
    171  iter: 3,
    172 
    173  onTimeout: function onTimeout() {
    174    // 1mb of data
    175    const content = generateContent(1024 * 1024);
    176    this.resp.write(content); // 1mb chunk
    177    this.iter--;
    178    if (!this.iter) {
    179      this.resp.end();
    180    } else {
    181      setTimeout(executeRunLater, 1, this);
    182    }
    183  },
    184 };
    185 
    186 var resetLater = function () {};
    187 resetLater.prototype = {
    188  resp: null,
    189 
    190  onTimeout: function onTimeout() {
    191    this.resp.stream.reset("HTTP_1_1_REQUIRED");
    192  },
    193 };
    194 
    195 function executeRunLater(arg) {
    196  arg.onTimeout();
    197 }
    198 
    199 function executeRunLaterCatchError(arg) {
    200  arg.onTimeout();
    201 }
    202 
    203 var h11required_conn = null;
    204 var h11required_header = "yes";
    205 var didRst = false;
    206 var rstConnection = null;
    207 var illegalheader_conn = null;
    208 
    209 // eslint-disable-next-line complexity
    210 function handleRequest(req, res) {
    211  var u = "";
    212  if (req.url != undefined) {
    213    u = url.parse(req.url, true);
    214  }
    215  var content = getHttpContent(u.pathname);
    216  var push;
    217 
    218  if (req.httpVersionMajor === 2) {
    219    res.setHeader("X-Connection-Http2", "yes");
    220    res.setHeader("X-Http2-StreamId", "" + req.stream.id);
    221  } else {
    222    res.setHeader("X-Connection-Http2", "no");
    223  }
    224 
    225  if (u.pathname === "/exit") {
    226    res.setHeader("Content-Type", "text/plain");
    227    res.setHeader("Connection", "close");
    228    res.writeHead(200);
    229    res.end("ok");
    230    process.exit();
    231  }
    232 
    233  if (req.method == "CONNECT") {
    234    if (req.headers.host == "illegalhpacksoft.example.com:80") {
    235      illegalheader_conn = req.stream.connection;
    236      res.setHeader("Content-Type", "text/html");
    237      res.setHeader("x-softillegalhpack", "true");
    238      res.writeHead(200);
    239      res.end(content);
    240      return;
    241    } else if (req.headers.host == "illegalhpackhard.example.com:80") {
    242      res.setHeader("Content-Type", "text/html");
    243      res.setHeader("x-hardillegalhpack", "true");
    244      res.writeHead(200);
    245      res.end(content);
    246      return;
    247    } else if (req.headers.host == "750.example.com:80") {
    248      // This response will mock a response through a proxy to a HTTP server.
    249      // After 750ms , a 200 response for the proxy will be sent then
    250      // after additional 50ms a 200 response for the HTTP GET request.
    251      let rl = new runConnectLater();
    252      rl.req = req;
    253      rl.resp = res;
    254      setTimeout(executeRunLaterCatchError, 750, rl);
    255      return;
    256    } else if (req.headers.host == "h11required.com:80") {
    257      if (req.httpVersionMajor === 2) {
    258        res.stream.reset("HTTP_1_1_REQUIRED");
    259      }
    260      return;
    261    }
    262  } else if (u.pathname === "/750ms") {
    263    let rl = new runlater();
    264    rl.req = req;
    265    rl.resp = res;
    266    setTimeout(executeRunLater, 750, rl);
    267    return;
    268  } else if (u.pathname === "/750msNoData") {
    269    let rl = new runlater();
    270    rl.req = req;
    271    rl.resp = res;
    272    rl.fin = false;
    273    setTimeout(executeRunLater, 750, rl);
    274    return;
    275  } else if (u.pathname === "/multiplex1" && req.httpVersionMajor === 2) {
    276    res.setHeader("Content-Type", "text/plain");
    277    res.writeHead(200);
    278    m.mp1res = res;
    279    m.checkReady();
    280    return;
    281  } else if (u.pathname === "/multiplex2" && req.httpVersionMajor === 2) {
    282    res.setHeader("Content-Type", "text/plain");
    283    res.writeHead(200);
    284    m.mp2res = res;
    285    m.checkReady();
    286    return;
    287  } else if (u.pathname === "/header") {
    288    var val = req.headers["x-test-header"];
    289    if (val) {
    290      res.setHeader("X-Received-Test-Header", val);
    291    }
    292  } else if (u.pathname === "/doubleheader") {
    293    res.setHeader("Content-Type", "text/html");
    294    res.writeHead(200);
    295    res.write(content);
    296    res.writeHead(200);
    297    res.end();
    298    return;
    299  } else if (u.pathname === "/cookie_crumbling") {
    300    res.setHeader("X-Received-Header-Pairs", JSON.stringify(decompressedPairs));
    301  } else if (u.pathname === "/big") {
    302    content = generateContent(128 * 1024);
    303    var hash = crypto.createHash("md5");
    304    hash.update(content);
    305    let md5 = hash.digest("hex");
    306    res.setHeader("X-Expected-MD5", md5);
    307  } else if (u.pathname === "/huge") {
    308    content = generateContent(1024);
    309    res.setHeader("Content-Type", "text/plain");
    310    res.writeHead(200);
    311    // 1mb of data
    312    for (let i = 0; i < 1024 * 1; i++) {
    313      res.write(content); // 1kb chunk
    314    }
    315    res.end();
    316    return;
    317  } else if (u.pathname === "/post" || u.pathname === "/patch") {
    318    if (req.method != "POST" && req.method != "PATCH") {
    319      res.writeHead(405);
    320      res.end("Unexpected method: " + req.method);
    321      return;
    322    }
    323 
    324    var post_hash = crypto.createHash("md5");
    325    var received_data = false;
    326    req.on("data", function receivePostData(chunk) {
    327      received_data = true;
    328      post_hash.update(chunk.toString());
    329    });
    330    req.on("end", function finishPost() {
    331      let md5 = received_data ? post_hash.digest("hex") : "0";
    332      res.setHeader("X-Calculated-MD5", md5);
    333      res.writeHead(200);
    334      res.end(content);
    335    });
    336 
    337    return;
    338  } else if (u.pathname === "/750msPost") {
    339    if (req.method != "POST") {
    340      res.writeHead(405);
    341      res.end("Unexpected method: " + req.method);
    342      return;
    343    }
    344 
    345    var accum = 0;
    346    req.on("data", function receivePostData(chunk) {
    347      accum += chunk.length;
    348    });
    349    req.on("end", function finishPost() {
    350      res.setHeader("X-Recvd", accum);
    351      let rl = new runlater();
    352      rl.req = req;
    353      rl.resp = res;
    354      setTimeout(executeRunLater, 750, rl);
    355    });
    356 
    357    return;
    358  } else if (u.pathname === "/h11required_stream") {
    359    if (req.httpVersionMajor === 2) {
    360      h11required_conn = req.stream.connection;
    361      res.stream.reset("HTTP_1_1_REQUIRED");
    362      return;
    363    }
    364  } else if (u.pathname === "/bigdownload") {
    365    res.setHeader("Content-Type", "text/html");
    366    res.writeHead(200);
    367 
    368    let rl = new moreData();
    369    rl.req = req;
    370    rl.resp = res;
    371    setTimeout(executeRunLater, 1, rl);
    372    return;
    373  } else if (u.pathname === "/h11required_session") {
    374    if (req.httpVersionMajor === 2) {
    375      if (h11required_conn !== req.stream.connection) {
    376        h11required_header = "no";
    377      }
    378      res.stream.connection.close("HTTP_1_1_REQUIRED", res.stream.id - 2);
    379      return;
    380    }
    381    res.setHeader("X-H11Required-Stream-Ok", h11required_header);
    382  } else if (u.pathname === "/h11required_with_content") {
    383    if (req.httpVersionMajor === 2) {
    384      res.setHeader("Content-Type", "text/plain");
    385      res.setHeader("Content-Length", "ok".length);
    386      res.writeHead(200);
    387      res.write("ok");
    388      let resetFunc = new resetLater();
    389      resetFunc.resp = res;
    390      setTimeout(executeRunLater, 1, resetFunc);
    391      return;
    392    }
    393  } else if (u.pathname === "/rstonce") {
    394    if (!didRst && req.httpVersionMajor === 2) {
    395      didRst = true;
    396      rstConnection = req.stream.connection;
    397      req.stream.reset("REFUSED_STREAM");
    398      return;
    399    }
    400 
    401    if (rstConnection === null || rstConnection !== req.stream.connection) {
    402      if (req.httpVersionMajor != 2) {
    403        res.setHeader("Connection", "close");
    404      }
    405      res.writeHead(400);
    406      res.end("WRONG CONNECTION, HOMIE!");
    407      return;
    408    }
    409 
    410    // Clear these variables so we can run the test again with --verify
    411    didRst = false;
    412    rstConnection = null;
    413 
    414    if (req.httpVersionMajor != 2) {
    415      res.setHeader("Connection", "close");
    416    }
    417    res.writeHead(200);
    418    res.end("It's all good.");
    419    return;
    420  } else if (u.pathname === "/continuedheaders") {
    421    var pushRequestHeaders = { "x-pushed-request": "true" };
    422    var pushResponseHeaders = {
    423      "content-type": "text/plain",
    424      "content-length": "2",
    425      "X-Connection-Http2": "yes",
    426    };
    427    var pushHdrTxt =
    428      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    429    var pullHdrTxt = pushHdrTxt.split("").reverse().join("");
    430    for (let i = 0; i < 265; i++) {
    431      pushRequestHeaders["X-Push-Test-Header-" + i] = pushHdrTxt;
    432      res.setHeader("X-Pull-Test-Header-" + i, pullHdrTxt);
    433    }
    434    push = res.push({
    435      hostname: "localhost:" + serverPort,
    436      port: serverPort,
    437      path: "/continuedheaders/push",
    438      method: "GET",
    439      headers: pushRequestHeaders,
    440    });
    441    push.writeHead(200, pushResponseHeaders);
    442    push.end("ok");
    443  } else if (u.pathname === "/hugecontinuedheaders") {
    444    for (let i = 0; i < u.query.size; i++) {
    445      res.setHeader(
    446        "X-Test-Header-" + i,
    447        "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".repeat(1024)
    448      );
    449    }
    450    res.writeHead(200);
    451    res.end(content);
    452    return;
    453  } else if (u.pathname === "/altsvc1") {
    454    if (
    455      req.httpVersionMajor != 2 ||
    456      req.scheme != "http" ||
    457      req.headers["alt-used"] != "foo.example.com:" + serverPort
    458    ) {
    459      res.writeHead(400);
    460      res.end("WHAT?");
    461      return;
    462    }
    463    // test the alt svc frame for use with altsvc2
    464    res.altsvc(
    465      "foo.example.com",
    466      serverPort,
    467      "h2",
    468      3600,
    469      req.headers["x-redirect-origin"]
    470    );
    471  } else if (u.pathname === "/altsvc2") {
    472    if (
    473      req.httpVersionMajor != 2 ||
    474      req.scheme != "http" ||
    475      req.headers["alt-used"] != "foo.example.com:" + serverPort
    476    ) {
    477      res.writeHead(400);
    478      res.end("WHAT?");
    479      return;
    480    }
    481  }
    482 
    483  // for use with test_altsvc.js
    484  else if (u.pathname === "/altsvc-test") {
    485    res.setHeader("Cache-Control", "no-cache");
    486    res.setHeader("Alt-Svc", "h2=" + req.headers["x-altsvc"]);
    487  }
    488  // for use with test_http3.js
    489  else if (u.pathname === "/http3-test") {
    490    res.setHeader("Cache-Control", "no-cache");
    491    res.setHeader("Alt-Svc", "h3=" + req.headers["x-altsvc"]);
    492  }
    493  // for use with test_http3.js
    494  else if (u.pathname === "/http3-test2") {
    495    res.setHeader("Cache-Control", "no-cache");
    496    res.setHeader(
    497      "Alt-Svc",
    498      "h2=foo2.example.com:8000,h3=" + req.headers["x-altsvc"]
    499    );
    500  } else if (u.pathname === "/http3-test3") {
    501    res.setHeader("Cache-Control", "no-cache");
    502    res.setHeader(
    503      "Alt-Svc",
    504      "h3-29=" + req.headers["x-altsvc"] + ",h3=" + req.headers["x-altsvc"]
    505    );
    506  } else if (u.pathname === "/websocket") {
    507    res.setHeader("Upgrade", "websocket");
    508    res.setHeader("Connection", "Upgrade");
    509    var wshash = crypto.createHash("sha1");
    510    wshash.update(req.headers["sec-websocket-key"]);
    511    wshash.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
    512    let key = wshash.digest("base64");
    513    res.setHeader("Sec-WebSocket-Accept", key);
    514    res.writeHead(101);
    515    res.end("something....");
    516    return;
    517  } else if (u.pathname === "/.well-known/http-opportunistic") {
    518    res.setHeader("Cache-Control", "no-cache");
    519    res.setHeader("Content-Type", "application/json");
    520    res.writeHead(200, "OK");
    521    res.end('["http://' + req.headers.host + '"]');
    522    return;
    523  } else if (u.pathname === "/stale-while-revalidate-loop-test") {
    524    res.setHeader(
    525      "Cache-Control",
    526      "s-maxage=86400, stale-while-revalidate=86400, immutable"
    527    );
    528    res.setHeader("Content-Type", "text/plain; charset=utf-8");
    529    res.setHeader("X-Content-Type-Options", "nosniff");
    530    res.setHeader("Content-Length", "1");
    531    res.writeHead(200, "OK");
    532    res.end("1");
    533    return;
    534  } else if (u.pathname === "/illegalhpacksoft") {
    535    // This will cause the compressor to compress a header that is not legal,
    536    // but only affects the stream, not the session.
    537    illegalheader_conn = req.stream.connection;
    538    res.setHeader("Content-Type", "text/html");
    539    res.setHeader("x-softillegalhpack", "true");
    540    res.writeHead(200);
    541    res.end(content);
    542    return;
    543  } else if (u.pathname === "/illegalhpackhard") {
    544    // This will cause the compressor to insert an HPACK instruction that will
    545    // cause a session failure.
    546    res.setHeader("Content-Type", "text/html");
    547    res.setHeader("x-hardillegalhpack", "true");
    548    res.writeHead(200);
    549    res.end(content);
    550    return;
    551  } else if (u.pathname === "/illegalhpack_validate") {
    552    if (req.stream.connection === illegalheader_conn) {
    553      res.setHeader("X-Did-Goaway", "no");
    554    } else {
    555      res.setHeader("X-Did-Goaway", "yes");
    556    }
    557    // Fall through to the default response behavior
    558  } else if (u.pathname === "/foldedheader") {
    559    res.setHeader("X-Folded-Header", "this is\n folded");
    560    // Fall through to the default response behavior
    561  } else if (u.pathname === "/emptydata") {
    562    // Overwrite the original transform with our version that will insert an
    563    // empty DATA frame at the beginning of the stream response, then fall
    564    // through to the default response behavior.
    565    Serializer.prototype._transform = newTransform;
    566  }
    567 
    568  // for use with test_immutable.js
    569  else if (u.pathname === "/immutable-test-without-attribute") {
    570    res.setHeader("Cache-Control", "max-age=100000");
    571    res.setHeader("Etag", "1");
    572    if (req.headers["if-none-match"]) {
    573      res.setHeader("x-conditional", "true");
    574    }
    575    // default response from here
    576  } else if (u.pathname === "/immutable-test-with-attribute") {
    577    res.setHeader("Cache-Control", "max-age=100000, immutable");
    578    res.setHeader("Etag", "2");
    579    if (req.headers["if-none-match"]) {
    580      res.setHeader("x-conditional", "true");
    581    }
    582    // default response from here
    583  } else if (u.pathname === "/immutable-test-expired-with-Expires-header") {
    584    res.setHeader("Cache-Control", "immutable");
    585    res.setHeader("Expires", "Mon, 01 Jan 1990 00:00:00 GMT");
    586    res.setHeader("Etag", "3");
    587 
    588    if (req.headers["if-none-match"]) {
    589      res.setHeader("x-conditional", "true");
    590    }
    591  } else if (
    592    u.pathname === "/immutable-test-expired-with-last-modified-header"
    593  ) {
    594    res.setHeader("Cache-Control", "public, max-age=3600, immutable");
    595    res.setHeader("Date", "Mon, 01 Jan 1990 00:00:00 GMT");
    596    res.setHeader("Last-modified", "Mon, 01 Jan 1990 00:00:00 GMT");
    597    res.setHeader("Etag", "4");
    598 
    599    if (req.headers["if-none-match"]) {
    600      res.setHeader("x-conditional", "true");
    601    }
    602  } else if (u.pathname === "/statusphrase") {
    603    // Fortunately, the node-http2 API is dumb enough to allow this right on
    604    // through, so we can easily test rejecting this on gecko's end.
    605    res.writeHead("200 OK");
    606    res.end(content);
    607    return;
    608  } else if (u.pathname === "/doublypushed") {
    609    content = "not pushed";
    610  } else if (u.pathname === "/diskcache") {
    611    content = "this was pulled via h2";
    612  }
    613 
    614  // For test_header_Server_Timing.js
    615  else if (u.pathname === "/server-timing") {
    616    res.setHeader("Content-Type", "text/plain");
    617    res.setHeader("Content-Length", "12");
    618    res.setHeader("Trailer", "Server-Timing");
    619    res.setHeader(
    620      "Server-Timing",
    621      "metric; dur=123.4; desc=description, metric2; dur=456.78; desc=description1"
    622    );
    623    res.write("data reached");
    624    res.addTrailers({
    625      "Server-Timing":
    626        "metric3; dur=789.11; desc=description2, metric4; dur=1112.13; desc=description3",
    627    });
    628    res.end();
    629    return;
    630  } else if (u.pathname === "/103_response") {
    631    let link_val = req.headers["link-to-set"];
    632    if (link_val) {
    633      res.setHeader("link", link_val);
    634    }
    635    res.setHeader("something", "something");
    636    res.writeHead(103);
    637 
    638    res.setHeader("Content-Type", "text/plain");
    639    res.setHeader("Content-Length", "12");
    640    res.writeHead(200);
    641    res.write("data reached");
    642    res.end();
    643    return;
    644  } else if (u.pathname.startsWith("/invalid_response_header/")) {
    645    // response headers with invalid characters in the name / value (RFC7540 Sec 10.3)
    646    let kind = u.pathname.slice("/invalid_response_header/".length);
    647    if (kind === "name_spaces") {
    648      res.setHeader("With Spaces", "Hello");
    649    } else if (kind === "value_line_feed") {
    650      res.setHeader("invalid-header", "line\nfeed");
    651    } else if (kind === "value_carriage_return") {
    652      res.setHeader("invalid-header", "carriage\rreturn");
    653    } else if (kind === "value_null") {
    654      res.setHeader("invalid-header", "null\0");
    655    }
    656 
    657    res.writeHead(200);
    658    res.end("");
    659    return;
    660  }
    661 
    662  res.setHeader("Content-Type", "text/html");
    663  if (req.httpVersionMajor != 2) {
    664    res.setHeader("Connection", "close");
    665  }
    666  res.writeHead(200);
    667  res.end(content);
    668 }
    669 
    670 // Set up the SSL certs for our server - this server has a cert for foo.example.com
    671 // signed by netwerk/tests/unit/http2-ca.pem
    672 var options = {
    673  key: fs.readFileSync(__dirname + "/http2-cert.key"),
    674  cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
    675 };
    676 
    677 if (process.env.HTTP2_LOG !== undefined) {
    678  var log_module = node_http2_root + "/test/util";
    679  options.log = require(log_module).createLogger("server");
    680 }
    681 
    682 var server = http2.createServer(options, handleRequest);
    683 
    684 server.on("connection", function (socket) {
    685  socket.on("error", function () {
    686    // Ignoring SSL socket errors, since they usually represent a connection that was tore down
    687    // by the browser because of an untrusted certificate. And this happens at least once, when
    688    // the first test case if done.
    689  });
    690 });
    691 
    692 server.on("connect", function (req, clientSocket) {
    693  clientSocket.write(
    694    "HTTP/1.1 404 Not Found\r\nProxy-agent: Node.js-Proxy\r\n\r\n"
    695  );
    696  clientSocket.destroy();
    697 });
    698 
    699 function makeid(length) {
    700  var result = "";
    701  var characters =
    702    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    703  var charactersLength = characters.length;
    704  for (var i = 0; i < length; i++) {
    705    result += characters.charAt(Math.floor(Math.random() * charactersLength));
    706  }
    707  return result;
    708 }
    709 
    710 let globalObjects = {};
    711 var serverPort;
    712 
    713 const listen = (serv, envport) => {
    714  if (!serv) {
    715    return Promise.resolve(0);
    716  }
    717 
    718  let portSelection = 0;
    719  if (envport !== undefined) {
    720    try {
    721      portSelection = parseInt(envport, 10);
    722    } catch (e) {
    723      portSelection = -1;
    724    }
    725  }
    726  return new Promise(resolve => {
    727    serv.listen(portSelection, "0.0.0.0", 2000, () => {
    728      resolve(serv.address().port);
    729    });
    730  });
    731 };
    732 
    733 const http = require("http");
    734 let httpServer = http.createServer((req, res) => {
    735  if (req.method != "POST") {
    736    let u = url.parse(req.url, true);
    737    if (u.pathname == "/test") {
    738      // This path is used to test that the server is working properly
    739      res.writeHead(200);
    740      res.end("OK");
    741      return;
    742    }
    743    res.writeHead(405);
    744    res.end("Unexpected method: " + req.method);
    745    return;
    746  }
    747 
    748  let code = "";
    749  req.on("data", function receivePostData(chunk) {
    750    code += chunk;
    751  });
    752  req.on("end", function finishPost() {
    753    let u = url.parse(req.url, true);
    754    if (u.pathname == "/fork") {
    755      let id = forkProcess();
    756      computeAndSendBackResponse(id);
    757      return;
    758    }
    759 
    760    if (u.pathname == "/forkH3Server") {
    761      forkH3Server(u.query.path, u.query.dbPath)
    762        .then(result => {
    763          computeAndSendBackResponse(result);
    764        })
    765        .catch(error => {
    766          computeAndSendBackResponse(error);
    767        });
    768      return;
    769    }
    770 
    771    if (u.pathname.startsWith("/kill/")) {
    772      let id = u.pathname.slice(6);
    773      let forked = globalObjects[id];
    774      if (!forked) {
    775        computeAndSendBackResponse(undefined, new Error("could not find id"));
    776        return;
    777      }
    778 
    779      new Promise((resolve, reject) => {
    780        forked.resolve = resolve;
    781        forked.reject = reject;
    782        forked.kill();
    783      })
    784        .then(x =>
    785          computeAndSendBackResponse(
    786            undefined,
    787            new Error(`incorrectly resolved ${x}`)
    788          )
    789        )
    790        .catch(e => {
    791          // We indicate a proper shutdown by resolving with undefined.
    792          if (e && e.toString().match(/child process exit closing code/)) {
    793            e = undefined;
    794          }
    795          computeAndSendBackResponse(undefined, e);
    796        });
    797      return;
    798    }
    799 
    800    if (u.pathname.startsWith("/execute/")) {
    801      let id = u.pathname.slice(9);
    802      let forked = globalObjects[id];
    803      if (!forked) {
    804        computeAndSendBackResponse(undefined, new Error("could not find id"));
    805        return;
    806      }
    807 
    808      let messageId = makeid(6);
    809      new Promise((resolve, reject) => {
    810        forked.messageHandlers[messageId] = { resolve, reject };
    811        forked.send({ code, messageId });
    812      })
    813        .then(x => sendBackResponse(x))
    814        .catch(e => computeAndSendBackResponse(undefined, e));
    815    }
    816 
    817    function computeAndSendBackResponse(evalResult, e) {
    818      let output = { result: evalResult, error: "", errorStack: "" };
    819      if (e) {
    820        output.error = e.toString();
    821        output.errorStack = e.stack;
    822      }
    823      sendBackResponse(output);
    824    }
    825 
    826    function sendBackResponse(output) {
    827      output = JSON.stringify(output);
    828 
    829      res.setHeader("Content-Length", output.length);
    830      res.setHeader("Content-Type", "application/json");
    831      res.writeHead(200);
    832      res.write(output);
    833      res.end("");
    834    }
    835  });
    836 });
    837 
    838 function forkH3Server(serverPath, dbPath) {
    839  const args = [dbPath];
    840  let process = spawn(serverPath, args);
    841  let id = forkProcessInternal(process);
    842  // Return a promise that resolves when we receive data from stdout
    843  return new Promise((resolve, _) => {
    844    process.stdout.on("data", data => {
    845      console.log(data.toString());
    846      resolve({ id, output: data.toString().trim() });
    847    });
    848  });
    849 }
    850 
    851 function forkProcess() {
    852  let scriptPath = path.resolve(__dirname, "moz-http2-child.js");
    853  let forked = fork(scriptPath);
    854  return forkProcessInternal(forked);
    855 }
    856 
    857 function forkProcessInternal(forked) {
    858  let id = makeid(6);
    859  forked.errors = "";
    860  forked.messageHandlers = {};
    861  globalObjects[id] = forked;
    862  forked.on("message", msg => {
    863    if (msg.messageId && forked.messageHandlers[msg.messageId]) {
    864      let handler = forked.messageHandlers[msg.messageId];
    865      delete forked.messageHandlers[msg.messageId];
    866      handler.resolve(msg);
    867    } else {
    868      console.log(
    869        `forked process without handler sent: ${JSON.stringify(msg)}`
    870      );
    871      forked.errors += `forked process without handler sent: ${JSON.stringify(
    872        msg
    873      )}\n`;
    874    }
    875  });
    876 
    877  let exitFunction = (code, signal) => {
    878    if (globalObjects[id]) {
    879      delete globalObjects[id];
    880    } else {
    881      // already called
    882      return;
    883    }
    884 
    885    let errorMsg = `child process exit closing code: ${code} signal: ${signal}`;
    886    if (forked.errors != "") {
    887      errorMsg = forked.errors;
    888      forked.errors = "";
    889    }
    890 
    891    // Handle /kill/ case where forked.reject is set
    892    if (forked.reject) {
    893      forked.reject(errorMsg);
    894      forked.reject = null;
    895      forked.resolve = null;
    896    }
    897 
    898    if (Object.keys(forked.messageHandlers).length === 0) {
    899      return;
    900    }
    901 
    902    for (let messageId in forked.messageHandlers) {
    903      forked.messageHandlers[messageId].reject(errorMsg);
    904    }
    905    forked.messageHandlers = {};
    906  };
    907 
    908  forked.on("error", exitFunction);
    909  forked.on("close", exitFunction);
    910  forked.on("exit", exitFunction);
    911 
    912  return id;
    913 }
    914 
    915 Promise.all([
    916  listen(server, process.env.MOZHTTP2_PORT).then(port => (serverPort = port)),
    917  listen(httpServer, process.env.MOZNODE_EXEC_PORT),
    918 ]).then(([sPort, nodeExecPort]) => {
    919  console.log(`HTTP2 server listening on ports ${sPort},${nodeExecPort}`);
    920 });