blob: dfe4c474cc1cc1354973d0531cac953af042233e [file] [log] [blame]
'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();
};