'use strict'; var fs = require('fs'), union = require('union'), httpServerCore = require('./core'), auth = require('basic-auth'), httpProxy = require('http-proxy'), corser = require('corser'), secureCompare = require('secure-compare'); // // Remark: backwards compatibility for previous // case convention of HTTP // exports.HttpServer = exports.HTTPServer = HttpServer; /** * Returns a new instance of HttpServer with the * specified `options`. */ exports.createServer = function (options) { return new HttpServer(options); }; /** * Constructor function for the HttpServer object * which is responsible for serving static files along * with other HTTP-related features. */ function HttpServer(options) { options = options || {}; if (options.root) { this.root = options.root; } else { try { // eslint-disable-next-line no-sync fs.lstatSync('./public'); this.root = './public'; } catch (err) { this.root = './'; } } this.headers = options.headers || {}; this.headers['Accept-Ranges'] = 'bytes'; this.cache = ( // eslint-disable-next-line no-nested-ternary options.cache === undefined ? 3600 : // -1 is a special case to turn off caching. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Preventing_caching options.cache === -1 ? 'no-cache, no-store, must-revalidate' : options.cache // in seconds. ); this.showDir = options.showDir !== 'false'; this.autoIndex = options.autoIndex !== 'false'; this.showDotfiles = options.showDotfiles; this.gzip = options.gzip === true; this.brotli = options.brotli === true; if (options.ext) { this.ext = options.ext === true ? 'html' : options.ext; } this.contentType = options.contentType || this.ext === 'html' ? 'text/html' : 'application/octet-stream'; var before = options.before ? options.before.slice() : []; if (options.logFn) { before.push(function (req, res) { options.logFn(req, res); res.emit('next'); }); } if (options.username || options.password) { before.push(function (req, res) { var credentials = auth(req); // We perform these outside the if to avoid short-circuiting and giving // an attacker knowledge of whether the username is correct via a timing // attack. if (credentials) { // if credentials is defined, name and pass are guaranteed to be string // type var usernameEqual = secureCompare(options.username.toString(), credentials.name); var passwordEqual = secureCompare(options.password.toString(), credentials.pass); if (usernameEqual && passwordEqual) { return res.emit('next'); } } res.statusCode = 401; res.setHeader('WWW-Authenticate', 'Basic realm=""'); res.end('Access denied'); }); } if (options.cors) { this.headers['Access-Control-Allow-Origin'] = '*'; this.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Range'; if (options.corsHeaders) { options.corsHeaders.split(/\s*,\s*/) .forEach(function (h) { this.headers['Access-Control-Allow-Headers'] += ', ' + h; }, this); } before.push(corser.create(options.corsHeaders ? { requestHeaders: this.headers['Access-Control-Allow-Headers'].split(/\s*,\s*/) } : null)); } if (options.robots) { before.push(function (req, res) { if (req.url === '/robots.txt') { res.setHeader('Content-Type', 'text/plain'); var robots = options.robots === true ? 'User-agent: *\nDisallow: /' : options.robots.replace(/\\n/, '\n'); return res.end(robots); } res.emit('next'); }); } before.push(httpServerCore({ root: this.root, cache: this.cache, showDir: this.showDir, showDotfiles: this.showDotfiles, autoIndex: this.autoIndex, defaultExt: this.ext, gzip: this.gzip, brotli: this.brotli, contentType: this.contentType, mimetypes: options.mimetypes, handleError: typeof options.proxy !== 'string' })); if (typeof options.proxy === 'string') { var proxyOptions = options.proxyOptions || {}; var proxy = httpProxy.createProxyServer(proxyOptions); before.push(function (req, res) { proxy.web(req, res, { target: options.proxy, changeOrigin: true }, function (err, req, res) { if (options.logFn) { options.logFn(req, res, { message: err.message, status: res.statusCode }); } res.emit('next'); }); }); } var serverOptions = { before: before, headers: this.headers, onError: function (err, req, res) { if (options.logFn) { options.logFn(req, res, err); } res.end(); } }; if (options.https) { serverOptions.https = options.https; } this.server = serverOptions.https && serverOptions.https.passphrase // if passphrase is set, shim must be used as union does not support ? require('./shims/https-server-shim')(serverOptions) : union.createServer(serverOptions); if (options.timeout !== undefined) { this.server.setTimeout(options.timeout); } } HttpServer.prototype.listen = function () { this.server.listen.apply(this.server, arguments); }; HttpServer.prototype.close = function () { return this.server.close(); };