| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 | /* * Copyright 2014 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *//* eslint-disable no-var */"use strict";var http = require("http");var path = require("path");var fs = require("fs");var mimeTypes = {  ".css": "text/css",  ".html": "text/html",  ".js": "application/javascript",  ".json": "application/json",  ".svg": "image/svg+xml",  ".pdf": "application/pdf",  ".xhtml": "application/xhtml+xml",  ".gif": "image/gif",  ".ico": "image/x-icon",  ".png": "image/png",  ".log": "text/plain",  ".bcmap": "application/octet-stream",  ".properties": "text/plain",};var defaultMimeType = "application/octet-stream";function WebServer() {  this.root = ".";  this.host = "localhost";  this.port = 0;  this.server = null;  this.verbose = false;  this.cacheExpirationTime = 0;  this.disableRangeRequests = false;  this.hooks = {    GET: [crossOriginHandler],    POST: [],  };}WebServer.prototype = {  start(callback) {    this._ensureNonZeroPort();    this.server = http.createServer(this._handler.bind(this));    this.server.listen(this.port, this.host, callback);    console.log(      "Server running at http://" + this.host + ":" + this.port + "/"    );  },  stop(callback) {    this.server.close(callback);    this.server = null;  },  _ensureNonZeroPort() {    if (!this.port) {      // If port is 0, a random port will be chosen instead. Do not set a host      // name to make sure that the port is synchronously set by .listen().      var server = http.createServer().listen(0);      var address = server.address();      // .address().port being available synchronously is merely an      // implementation detail. So we are defensive here and fall back to some      // fixed port when the address is not available yet.      this.port = address ? address.port : 8000;      server.close();    }  },  _handler(req, res) {    var url = req.url.replace(/\/\//g, "/");    var urlParts = /([^?]*)((?:\?(.*))?)/.exec(url);    try {      // Guard against directory traversal attacks such as      // `/../../../../../../../etc/passwd`, which let you make GET requests      // for files outside of `this.root`.      var pathPart = path.normalize(decodeURI(urlParts[1]));      // path.normalize returns a path on the basis of the current platform.      // Windows paths cause issues in statFile and serverDirectoryIndex.      // Converting to unix path would avoid platform checks in said functions.      pathPart = pathPart.replace(/\\/g, "/");    } catch (ex) {      // If the URI cannot be decoded, a `URIError` is thrown. This happens for      // malformed URIs such as `http://localhost:8888/%s%s` and should be      // handled as a bad request.      res.writeHead(400);      res.end("Bad request", "utf8");      return;    }    var queryPart = urlParts[3];    var verbose = this.verbose;    var methodHooks = this.hooks[req.method];    if (!methodHooks) {      res.writeHead(405);      res.end("Unsupported request method", "utf8");      return;    }    var handled = methodHooks.some(function (hook) {      return hook(req, res);    });    if (handled) {      return;    }    if (pathPart === "/favicon.ico") {      fs.realpath(        path.join(this.root, "test/resources/favicon.ico"),        checkFile      );      return;    }    var disableRangeRequests = this.disableRangeRequests;    var cacheExpirationTime = this.cacheExpirationTime;    var filePath;    fs.realpath(path.join(this.root, pathPart), checkFile);    function checkFile(err, file) {      if (err) {        res.writeHead(404);        res.end();        if (verbose) {          console.error(url + ": not found");        }        return;      }      filePath = file;      fs.stat(filePath, statFile);    }    var fileSize;    function statFile(err, stats) {      if (err) {        res.writeHead(500);        res.end();        return;      }      fileSize = stats.size;      var isDir = stats.isDirectory();      if (isDir && !/\/$/.test(pathPart)) {        res.setHeader("Location", pathPart + "/" + urlParts[2]);        res.writeHead(301);        res.end("Redirected", "utf8");        return;      }      if (isDir) {        serveDirectoryIndex(filePath);        return;      }      var range = req.headers.range;      if (range && !disableRangeRequests) {        var rangesMatches = /^bytes=(\d+)-(\d+)?/.exec(range);        if (!rangesMatches) {          res.writeHead(501);          res.end("Bad range", "utf8");          if (verbose) {            console.error(url + ': bad range: "' + range + '"');          }          return;        }        var start = +rangesMatches[1];        var end = +rangesMatches[2];        if (verbose) {          console.log(url + ": range " + start + " - " + end);        }        serveRequestedFileRange(          filePath,          start,          isNaN(end) ? fileSize : end + 1        );        return;      }      if (verbose) {        console.log(url);      }      serveRequestedFile(filePath);    }    function escapeHTML(untrusted) {      // Escape untrusted input so that it can safely be used in a HTML response      // in HTML and in HTML attributes.      return untrusted        .replace(/&/g, "&")        .replace(/</g, "<")        .replace(/>/g, ">")        .replace(/"/g, """)        .replace(/'/g, "'");    }    function serveDirectoryIndex(dir) {      res.setHeader("Content-Type", "text/html");      res.writeHead(200);      if (queryPart === "frame") {        res.end(          "<html><frameset cols=*,200><frame name=pdf>" +            '<frame src="' +            encodeURI(pathPart) +            '?side"></frameset></html>',          "utf8"        );        return;      }      var all = queryPart === "all";      fs.readdir(dir, function (err, files) {        if (err) {          res.end();          return;        }        res.write(          '<html><head><meta charset="utf-8"></head><body>' +            "<h1>PDFs of " +            pathPart +            "</h1>\n"        );        if (pathPart !== "/") {          res.write('<a href="..">..</a><br>\n');        }        files.forEach(function (file) {          var stat;          var item = pathPart + file;          var href = "";          var label = "";          var extraAttributes = "";          try {            stat = fs.statSync(path.join(dir, file));          } catch (e) {            href = encodeURI(item);            label = file + " (" + e + ")";            extraAttributes = ' style="color:red"';          }          if (stat) {            if (stat.isDirectory()) {              href = encodeURI(item);              label = file;            } else if (path.extname(file).toLowerCase() === ".pdf") {              href = "/web/viewer.html?file=" + encodeURIComponent(item);              label = file;              extraAttributes = ' target="pdf"';            } else if (all) {              href = encodeURI(item);              label = file;            }          }          if (label) {            res.write(              '<a href="' +                escapeHTML(href) +                '"' +                extraAttributes +                ">" +                escapeHTML(label) +                "</a><br>\n"            );          }        });        if (files.length === 0) {          res.write("<p>no files found</p>\n");        }        if (!all && queryPart !== "side") {          res.write(            "<hr><p>(only PDF files are shown, " +              '<a href="?all">show all</a>)</p>\n'          );        }        res.end("</body></html>");      });    }    function serveRequestedFile(reqFilePath) {      var stream = fs.createReadStream(reqFilePath, { flags: "rs" });      stream.on("error", function (error) {        res.writeHead(500);        res.end();      });      var ext = path.extname(reqFilePath).toLowerCase();      var contentType = mimeTypes[ext] || defaultMimeType;      if (!disableRangeRequests) {        res.setHeader("Accept-Ranges", "bytes");      }      res.setHeader("Content-Type", contentType);      res.setHeader("Content-Length", fileSize);      if (cacheExpirationTime > 0) {        var expireTime = new Date();        expireTime.setSeconds(expireTime.getSeconds() + cacheExpirationTime);        res.setHeader("Expires", expireTime.toUTCString());      }      res.writeHead(200);      stream.pipe(res);    }    function serveRequestedFileRange(reqFilePath, start, end) {      var stream = fs.createReadStream(reqFilePath, {        flags: "rs",        start,        end: end - 1,      });      stream.on("error", function (error) {        res.writeHead(500);        res.end();      });      var ext = path.extname(reqFilePath).toLowerCase();      var contentType = mimeTypes[ext] || defaultMimeType;      res.setHeader("Accept-Ranges", "bytes");      res.setHeader("Content-Type", contentType);      res.setHeader("Content-Length", end - start);      res.setHeader(        "Content-Range",        "bytes " + start + "-" + (end - 1) + "/" + fileSize      );      res.writeHead(206);      stream.pipe(res);    }  },};// This supports the "Cross-origin" test in test/unit/api_spec.js// It is here instead of test.js so that when the test will still complete as// expected if the user does "gulp server" and then visits// http://localhost:8888/test/unit/unit_test.html?spec=Cross-originfunction crossOriginHandler(req, res) {  if (req.url === "/test/pdfs/basicapi.pdf?cors=withCredentials") {    res.setHeader("Access-Control-Allow-Origin", req.headers.origin);    res.setHeader("Access-Control-Allow-Credentials", "true");  }  if (req.url === "/test/pdfs/basicapi.pdf?cors=withoutCredentials") {    res.setHeader("Access-Control-Allow-Origin", req.headers.origin);  }}exports.WebServer = WebServer;
 |