| '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(); |
| }; |