tor-browser

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

http2_server.js (4502B)


      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 "use strict";
      6 
      7 /* globals require, __dirname, global, Buffer, process */
      8 
      9 const fs = require("fs");
     10 const options = {
     11  key: fs.readFileSync(__dirname + "/mochitest-cert.key.pem"),
     12  cert: fs.readFileSync(__dirname + "/mochitest-cert.pem"),
     13  settings: {
     14    enableConnectProtocol: true,
     15  },
     16 };
     17 const http2 = require("http2");
     18 const http = require("http");
     19 const url = require("url");
     20 const path = require("path");
     21 
     22 // This is the path of node-ws when running mochitest locally.
     23 let node_ws_root = path.join(__dirname, "../../xpcshell/node-ws");
     24 if (!fs.existsSync(node_ws_root)) {
     25  // This path is for running mochitest on try.
     26  node_ws_root = path.join(__dirname, "./node_ws");
     27 }
     28 
     29 const WebSocket = require(`${node_ws_root}/lib/websocket`);
     30 
     31 let listeningPort = parseInt(process.argv[3].split("=")[1]);
     32 let log = function () {};
     33 
     34 function handle_h2_non_connect(stream, headers) {
     35  const session = stream.session;
     36  const uri = new URL(
     37    `${headers[":scheme"]}://${headers[":authority"]}${headers[":path"]}`
     38  );
     39  const url = uri.toString();
     40 
     41  log("REQUEST:", url);
     42  log("REQUEST HEADER:", JSON.stringify(headers));
     43 
     44  stream.on("close", () => {
     45    log("REQUEST STREAM CLOSED:", stream.rstCode);
     46  });
     47  stream.on("error", error => {
     48    log("RESPONSE STREAM ERROR", error, url, "on session:", session.__id);
     49  });
     50 
     51  let newHeaders = {};
     52  for (let key in headers) {
     53    if (!key.startsWith(":")) {
     54      newHeaders[key] = headers[key];
     55    }
     56  }
     57 
     58  const options = {
     59    protocol: "http:",
     60    hostname: "127.0.0.1",
     61    port: 8888,
     62    path: headers[":path"],
     63    method: headers[":method"],
     64    headers: newHeaders,
     65  };
     66 
     67  log("OPTION:", JSON.stringify(options));
     68  const request = http.request(options);
     69 
     70  stream.pipe(request);
     71 
     72  request.on("response", response => {
     73    const headers = Object.fromEntries(
     74      Object.entries(response.headers).filter(
     75        ([key]) =>
     76          !["connection", "transfer-encoding", "keep-alive"].includes(key)
     77      )
     78    );
     79    headers[":status"] = response.statusCode;
     80    log("RESPONSE BEGIN", url, headers, "on session:", session.__id);
     81 
     82    try {
     83      stream.respond(headers);
     84 
     85      response.on("data", data => {
     86        log("RESPONSE DATA", data.length, url);
     87        stream.write(data);
     88      });
     89      response.on("error", error => {
     90        log("RESPONSE ERROR", error, url, "on session:", session.__id);
     91        stream.close(http2.constants.NGHTTP2_REFUSED_STREAM);
     92      });
     93      response.on("end", () => {
     94        log("RESPONSE END", url, "on session:", session.__id);
     95        stream.end();
     96      });
     97    } catch (exception) {
     98      log("RESPONSE EXCEPTION", exception, url, "on session:", session.__id);
     99      stream.close();
    100    }
    101  });
    102  request.on("error", error => {
    103    console.error("REQUEST ERROR", error, url, "on session:", session.__id);
    104    try {
    105      stream.respond({
    106        ":status": 502,
    107        "content-type": "application/proxy-explanation+json",
    108      });
    109      stream.end(
    110        JSON.stringify({
    111          title: "request error",
    112          description: error.toString(),
    113        })
    114      );
    115    } catch (exception) {
    116      stream.close();
    117    }
    118  });
    119 }
    120 
    121 let server = http2.createSecureServer(options);
    122 
    123 let session_count = 0;
    124 let session_id = 0;
    125 
    126 server.on("session", session => {
    127  session.__id = ++session_id;
    128  session.__tunnel_count = 0;
    129 
    130  ++session_count;
    131  if (session_count === 1) {
    132    log(`\n\n>>> FIRST SESSION OPENING\n`);
    133  }
    134  log(`*** NEW SESSION`, session.__id, "( sessions:", session_count, ")");
    135 
    136  session.on("error", error => {
    137    console.error("SESSION ERROR", session.__id, error);
    138  });
    139  session.on("close", () => {
    140    --session_count;
    141    log(`*** CLOSED SESSION`, session.__id, "( sessions:", session_count, ")");
    142    if (!session_count) {
    143      log(`\n\n<<< LAST SESSION CLOSED\n`);
    144    }
    145  });
    146 });
    147 
    148 server.on("stream", (stream, headers) => {
    149  if (headers[":method"] === "CONNECT") {
    150    stream.respond();
    151 
    152    const ws = new WebSocket(null);
    153    stream.setNoDelay = () => {};
    154    ws.setSocket(stream, Buffer.from(""), 100 * 1024 * 1024);
    155 
    156    ws.on("message", data => {
    157      ws.send(data);
    158    });
    159  } else {
    160    handle_h2_non_connect(stream, headers);
    161  }
    162 });
    163 
    164 server.listen(listeningPort);
    165 
    166 console.log(`Http2 server listening on ports ${server.address().port}`);