tor-browser

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

server.js (15644B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 // We expect these to be defined in the global scope by runtest.py.
      8 /* global __LOCATION__, _PROFILE_PATH, _SERVER_PORT, _SERVER_ADDR, _DISPLAY_RESULTS,
      9          _TEST_PREFIX, _HTTPD_PATH */
     10 // Defined by xpcshell
     11 /* global quit */
     12 
     13 /* eslint-disable mozilla/use-chromeutils-generateqi */
     14 
     15 // Set up a protocol substituion so that we can load the httpd.js file.
     16 let protocolHandler = Services.io
     17  .getProtocolHandler("resource")
     18  .QueryInterface(Ci.nsIResProtocolHandler);
     19 let httpdJSPath = PathUtils.toFileURI(_HTTPD_PATH);
     20 
     21 protocolHandler.setSubstitution(
     22  "httpd-server",
     23  Services.io.newURI(httpdJSPath)
     24 );
     25 const { HttpServer, dumpn, setDebuggingStatus } = ChromeUtils.importESModule(
     26  "resource://httpd-server/httpd.sys.mjs"
     27 );
     28 
     29 protocolHandler.setSubstitution(
     30  "mochitest-server",
     31  Services.io.newFileURI(__LOCATION__.parent)
     32 );
     33 /* import-globals-from mochitestListingsUtils.js */
     34 Services.scriptloader.loadSubScript(
     35  "resource://mochitest-server/mochitestListingsUtils.js",
     36  this
     37 );
     38 
     39 const CC = Components.Constructor;
     40 
     41 const FileInputStream = CC(
     42  "@mozilla.org/network/file-input-stream;1",
     43  "nsIFileInputStream",
     44  "init"
     45 );
     46 const ConverterInputStream = CC(
     47  "@mozilla.org/intl/converter-input-stream;1",
     48  "nsIConverterInputStream",
     49  "init"
     50 );
     51 
     52 // Disable automatic network detection, so tests work correctly when
     53 // not connected to a network.
     54 // eslint-disable-next-line mozilla/use-services
     55 var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
     56 ios.manageOfflineStatus = false;
     57 ios.offline = false;
     58 
     59 var server; // for use in the shutdown handler, if necessary
     60 
     61 var _quitting = false;
     62 
     63 /** Quit when all activity has completed. */
     64 function serverStopped() {
     65  _quitting = true;
     66 }
     67 
     68 //
     69 // SCRIPT CODE
     70 //
     71 runServer();
     72 
     73 // We can only have gotten here if the /server/shutdown path was requested.
     74 if (_quitting) {
     75  dumpn("HTTP server stopped, all pending requests complete");
     76  quit(0);
     77 }
     78 
     79 // Impossible as the stop callback should have been called, but to be safe...
     80 dumpn("TEST-UNEXPECTED-FAIL | failure to correctly shut down HTTP server");
     81 quit(1);
     82 
     83 var serverBasePath;
     84 var displayResults = true;
     85 
     86 var gServerAddress;
     87 var SERVER_PORT;
     88 
     89 //
     90 // SERVER SETUP
     91 //
     92 function runServer() {
     93  serverBasePath = __LOCATION__.parent;
     94  server = createMochitestServer(serverBasePath);
     95 
     96  // verify server address
     97  // if a.b.c.d or 'localhost'
     98  if (typeof _SERVER_ADDR != "undefined") {
     99    if (_SERVER_ADDR == "localhost") {
    100      gServerAddress = _SERVER_ADDR;
    101    } else {
    102      var quads = _SERVER_ADDR.split(".");
    103      if (quads.length == 4) {
    104        var invalid = false;
    105        for (var i = 0; i < 4; i++) {
    106          if (quads[i] < 0 || quads[i] > 255) {
    107            invalid = true;
    108          }
    109        }
    110        if (!invalid) {
    111          gServerAddress = _SERVER_ADDR;
    112        } else {
    113          throw new Error(
    114            "invalid _SERVER_ADDR, please specify a valid IP Address"
    115          );
    116        }
    117      }
    118    }
    119  } else {
    120    throw new Error(
    121      "please define _SERVER_ADDR (as an ip address) before running server.js"
    122    );
    123  }
    124 
    125  if (typeof _SERVER_PORT != "undefined") {
    126    if (parseInt(_SERVER_PORT) > 0 && parseInt(_SERVER_PORT) < 65536) {
    127      SERVER_PORT = _SERVER_PORT;
    128    }
    129  } else {
    130    throw new Error(
    131      "please define _SERVER_PORT (as a port number) before running server.js"
    132    );
    133  }
    134 
    135  // If DISPLAY_RESULTS is not specified, it defaults to true
    136  if (typeof _DISPLAY_RESULTS != "undefined") {
    137    displayResults = _DISPLAY_RESULTS;
    138  }
    139 
    140  server._start(SERVER_PORT, gServerAddress);
    141 
    142  // touch a file in the profile directory to indicate we're alive
    143  var foStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
    144    Ci.nsIFileOutputStream
    145  );
    146  var serverAlive = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
    147 
    148  if (typeof _PROFILE_PATH == "undefined") {
    149    serverAlive.initWithFile(serverBasePath);
    150    serverAlive.append("mochitesttestingprofile");
    151  } else {
    152    serverAlive.initWithPath(_PROFILE_PATH);
    153  }
    154 
    155  // Create a file to inform the harness that the server is ready
    156  if (serverAlive.exists()) {
    157    serverAlive.append("server_alive.txt");
    158    foStream.init(serverAlive, 0x02 | 0x08 | 0x20, 436, 0); // write, create, truncate
    159    var data = "It's alive!";
    160    foStream.write(data, data.length);
    161    foStream.close();
    162  } else {
    163    throw new Error(
    164      "Failed to create server_alive.txt because " +
    165        serverAlive.path +
    166        " could not be found."
    167    );
    168  }
    169 
    170  makeTags();
    171 
    172  //
    173  // The following is threading magic to spin an event loop -- this has to
    174  // happen manually in xpcshell for the server to actually work.
    175  //
    176  var thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
    177  while (!server.isStopped()) {
    178    thread.processNextEvent(true);
    179  }
    180 
    181  // Server stopped by /server/shutdown handler -- go through pending events
    182  // and return.
    183 
    184  // get rid of any pending requests
    185  while (thread.hasPendingEvents()) {
    186    thread.processNextEvent(true);
    187  }
    188 }
    189 
    190 /** Creates and returns an HTTP server configured to serve Mochitests. */
    191 function createMochitestServer(serverBasePath) {
    192  var server = new HttpServer();
    193 
    194  server.registerDirectory("/", serverBasePath);
    195  server.registerPathHandler("/server/shutdown", serverShutdown);
    196  server.registerPathHandler("/server/debug", serverDebug);
    197  server.registerContentType("sjs", "sjs"); // .sjs == CGI-like functionality
    198  server.registerContentType("jar", "application/x-jar");
    199  server.registerContentType("ogg", "application/ogg");
    200  server.registerContentType("pdf", "application/pdf");
    201  server.registerContentType("ogv", "video/ogg");
    202  server.registerContentType("oga", "audio/ogg");
    203  server.registerContentType("opus", "audio/ogg; codecs=opus");
    204  server.registerContentType("dat", "text/plain; charset=utf-8");
    205  server.registerContentType("frag", "text/plain"); // .frag == WebGL fragment shader
    206  server.registerContentType("vert", "text/plain"); // .vert == WebGL vertex shader
    207  server.registerContentType("wasm", "application/wasm");
    208  server.setIndexHandler(defaultDirHandler);
    209 
    210  var serverRoot = {
    211    getFile: function getFile(path) {
    212      var file = serverBasePath.clone().QueryInterface(Ci.nsIFile);
    213      path.split("/").forEach(function (p) {
    214        file.appendRelativePath(p);
    215      });
    216      return file;
    217    },
    218    QueryInterface() {
    219      return this;
    220    },
    221  };
    222 
    223  server.setObjectState("SERVER_ROOT", serverRoot);
    224 
    225  processLocations(server);
    226 
    227  return server;
    228 }
    229 
    230 /**
    231 * Notifies the HTTP server about all the locations at which it might receive
    232 * requests, so that it can properly respond to requests on any of the hosts it
    233 * serves.
    234 */
    235 function processLocations(server) {
    236  var serverLocations = serverBasePath.clone();
    237  serverLocations.append("server-locations.txt");
    238 
    239  const PR_RDONLY = 0x01;
    240  var fis = new FileInputStream(
    241    serverLocations,
    242    PR_RDONLY,
    243    292 /* 0444 */,
    244    Ci.nsIFileInputStream.CLOSE_ON_EOF
    245  );
    246 
    247  var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
    248  lis.QueryInterface(Ci.nsIUnicharLineInputStream);
    249 
    250  const LINE_REGEXP = new RegExp(
    251    "^([a-z][-a-z0-9+.]*)" +
    252      "://" +
    253      "(" +
    254      "\\d+\\.\\d+\\.\\d+\\.\\d+" +
    255      "|" +
    256      "(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\\.)*" +
    257      "[a-z](?:[-a-z0-9]*[a-z0-9])?" +
    258      ")" +
    259      ":" +
    260      "(\\d+)" +
    261      "(?:" +
    262      "\\s+" +
    263      "(\\S+(?:,\\S+)*)" +
    264      ")?$"
    265  );
    266 
    267  var line = {};
    268  var lineno = 0;
    269  var seenPrimary = false;
    270  do {
    271    var more = lis.readLine(line);
    272    lineno++;
    273 
    274    var lineValue = line.value;
    275    if (lineValue.charAt(0) == "#" || lineValue == "") {
    276      continue;
    277    }
    278 
    279    var match = LINE_REGEXP.exec(lineValue);
    280    if (!match) {
    281      throw new Error("Syntax error in server-locations.txt, line " + lineno);
    282    }
    283 
    284    var [, scheme, host, port, options] = match;
    285    if (options) {
    286      if (options.split(",").includes("primary")) {
    287        if (seenPrimary) {
    288          throw new Error(
    289            "Multiple primary locations in server-locations.txt, " +
    290              "line " +
    291              lineno
    292          );
    293        }
    294 
    295        server.identity.setPrimary(scheme, host, port);
    296        seenPrimary = true;
    297        continue;
    298      }
    299    }
    300 
    301    server.identity.add(scheme, host, port);
    302  } while (more);
    303 }
    304 
    305 // PATH HANDLERS
    306 
    307 // /server/shutdown
    308 function serverShutdown(metadata, response) {
    309  response.setStatusLine("1.1", 200, "OK");
    310  response.setHeader("Content-type", "text/plain", false);
    311 
    312  var body = "Server shut down.";
    313  response.bodyOutputStream.write(body, body.length);
    314 
    315  dumpn("Server shutting down now...");
    316  server.stop(serverStopped);
    317 }
    318 
    319 // /server/debug?[012]
    320 function serverDebug(metadata, response) {
    321  response.setStatusLine(metadata.httpVersion, 400, "Bad debugging level");
    322  if (metadata.queryString.length !== 1) {
    323    return;
    324  }
    325 
    326  var mode;
    327  if (metadata.queryString === "0") {
    328    // do this now so it gets logged with the old mode
    329    dumpn("Server debug logs disabled.");
    330    setDebuggingStatus(false, false);
    331    mode = "disabled";
    332  } else if (metadata.queryString === "1") {
    333    setDebuggingStatus(true, false);
    334    mode = "enabled";
    335  } else if (metadata.queryString === "2") {
    336    setDebuggingStatus(true, true);
    337    mode = "enabled, with timestamps";
    338  } else {
    339    return;
    340  }
    341 
    342  response.setStatusLine(metadata.httpVersion, 200, "OK");
    343  response.setHeader("Content-type", "text/plain", false);
    344  var body = "Server debug logs " + mode + ".";
    345  response.bodyOutputStream.write(body, body.length);
    346  dumpn(body);
    347 }
    348 
    349 /**
    350 * Produce a normal directory listing.
    351 */
    352 function regularListing(metadata, response) {
    353  var [links] = list(metadata.path, metadata.getProperty("directory"), false);
    354  response.write(
    355    "<!DOCTYPE html>\n" +
    356      HTML(
    357        HEAD(TITLE("mochitest index ", metadata.path)),
    358        BODY(BR(), A({ href: ".." }, "Up a level"), UL(linksToListItems(links)))
    359      )
    360  );
    361 }
    362 
    363 /**
    364 * Read a manifestFile located at the root of the server's directory and turn
    365 * it into an object for creating a table of clickable links for each test.
    366 */
    367 function convertManifestToTestLinks(root, manifest) {
    368  const { NetUtil } = ChromeUtils.importESModule(
    369    "resource://gre/modules/NetUtil.sys.mjs"
    370  );
    371 
    372  var manifestFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
    373  manifestFile.initWithFile(serverBasePath);
    374  manifestFile.append(manifest);
    375 
    376  var manifestStream = Cc[
    377    "@mozilla.org/network/file-input-stream;1"
    378  ].createInstance(Ci.nsIFileInputStream);
    379  manifestStream.init(manifestFile, -1, 0, 0);
    380 
    381  var manifestObj = JSON.parse(
    382    NetUtil.readInputStreamToString(manifestStream, manifestStream.available())
    383  );
    384  var paths = manifestObj.tests;
    385  var pathPrefix = "/" + root + "/";
    386  return [
    387    paths.reduce(function (t, p) {
    388      t[pathPrefix + p.path] = true;
    389      return t;
    390    }, {}),
    391    paths.length,
    392  ];
    393 }
    394 
    395 /**
    396 * Produce a test harness page containing all the test cases
    397 * below it, recursively.
    398 */
    399 function testListing(metadata, response) {
    400  var links = {};
    401  var count = 0;
    402  if (!metadata.queryString.includes("manifestFile")) {
    403    [links, count] = list(
    404      metadata.path,
    405      metadata.getProperty("directory"),
    406      true
    407    );
    408  } else if (typeof Components != "undefined") {
    409    var manifest = metadata.queryString.match(/manifestFile=([^&]+)/)[1];
    410 
    411    [links, count] = convertManifestToTestLinks(
    412      metadata.path.split("/")[1],
    413      manifest
    414    );
    415  }
    416 
    417  var table_class =
    418    metadata.queryString.indexOf("hideResultsTable=1") > -1 ? "invisible" : "";
    419 
    420  let testname =
    421    metadata.queryString.indexOf("testname=") > -1
    422      ? metadata.queryString.match(/testname=([^&]+)/)[1]
    423      : "";
    424 
    425  dumpn("count: " + count);
    426  var tests = testname ? "['/" + testname + "']" : jsonArrayOfTestFiles(links);
    427  response.write(
    428    HTML(
    429      HEAD(
    430        TITLE("MochiTest | ", metadata.path),
    431        LINK({
    432          rel: "stylesheet",
    433          type: "text/css",
    434          href: "/static/harness.css",
    435        }),
    436        SCRIPT({
    437          type: "text/javascript",
    438          src: "/tests/SimpleTest/LogController.js",
    439        }),
    440        SCRIPT({
    441          type: "text/javascript",
    442          src: "/tests/SimpleTest/MemoryStats.js",
    443        }),
    444        SCRIPT({
    445          type: "text/javascript",
    446          src: "/tests/SimpleTest/TestRunner.js",
    447        }),
    448        SCRIPT({
    449          type: "text/javascript",
    450          src: "/tests/SimpleTest/MozillaLogger.js",
    451        }),
    452        SCRIPT({ type: "text/javascript", src: "/chunkifyTests.js" }),
    453        SCRIPT({ type: "text/javascript", src: "/manifestLibrary.js" }),
    454        SCRIPT({ type: "text/javascript", src: "/tests/SimpleTest/setup.js" }),
    455        SCRIPT(
    456          { type: "text/javascript" },
    457          "window.onload =  hookup; gTestList=" + tests + ";"
    458        )
    459      ),
    460      BODY(
    461        DIV(
    462          { class: "container" },
    463          H2("--> ", A({ href: "#", id: "runtests" }, "Run Tests"), " <--"),
    464          P(
    465            { style: "float: right;" },
    466            SMALL(
    467              "Based on the ",
    468              A({ href: "http://www.mochikit.com/" }, "MochiKit"),
    469              " unit tests."
    470            )
    471          ),
    472          DIV(
    473            { class: "status" },
    474            H1({ id: "indicator" }, "Status"),
    475            H2({ id: "pass" }, "Passed: ", SPAN({ id: "pass-count" }, "0")),
    476            H2({ id: "fail" }, "Failed: ", SPAN({ id: "fail-count" }, "0")),
    477            H2({ id: "fail" }, "Todo: ", SPAN({ id: "todo-count" }, "0"))
    478          ),
    479          DIV({ class: "clear" }),
    480          DIV(
    481            { id: "current-test" },
    482            B("Currently Executing: ", SPAN({ id: "current-test-path" }, "_"))
    483          ),
    484          DIV({ class: "clear" }),
    485          DIV(
    486            { class: "frameholder" },
    487            IFRAME({
    488              scrolling: "no",
    489              id: "testframe",
    490              allow: "geolocation 'src'",
    491              allowfullscreen: true,
    492            })
    493          ),
    494          DIV({ class: "clear" }),
    495          DIV(
    496            { class: "toggle" },
    497            A({ href: "#", id: "toggleNonTests" }, "Show Non-Tests"),
    498            BR()
    499          ),
    500 
    501          displayResults
    502            ? TABLE(
    503                {
    504                  cellpadding: 0,
    505                  cellspacing: 0,
    506                  class: table_class,
    507                  id: "test-table",
    508                },
    509                TR(TD("Passed"), TD("Failed"), TD("Todo"), TD("Test Files")),
    510                linksToTableRows(links, 0)
    511              )
    512            : "",
    513          BR(),
    514          TABLE({
    515            cellpadding: 0,
    516            cellspacing: 0,
    517            border: 1,
    518            bordercolor: "red",
    519            id: "fail-table",
    520          }),
    521 
    522          DIV({ class: "clear" })
    523        )
    524      )
    525    )
    526  );
    527 }
    528 
    529 /**
    530 * Respond to requests that match a file system directory.
    531 * Under the tests/ directory, return a test harness page.
    532 */
    533 function defaultDirHandler(metadata, response) {
    534  response.setStatusLine("1.1", 200, "OK");
    535  response.setHeader("Content-type", "text/html;charset=utf-8", false);
    536  try {
    537    if (metadata.path.indexOf("/tests") != 0) {
    538      regularListing(metadata, response);
    539    } else {
    540      testListing(metadata, response);
    541    }
    542  } catch (ex) {
    543    response.write(ex);
    544  }
    545 }