Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ with the provided Dockerfile.
|`-K` or `--key` |Path to ssl key file |`key.pem` |
|`-r` or `--robots` | Automatically provide a /robots.txt (The content of which defaults to `User-agent: *\nDisallow: /`) | `false` |
|`--no-dotfiles` |Do not show dotfiles| |
|`--hide-permissions` |Do not show file permissions| |
|`--mimetypes` |Path to a .types file for custom mimetype definition| |
|`--hide-permissions` |Do not show file permissions| |
|`--allowed-hosts` |Comma-separated list of hosts allowed to access the server. e.g.: `--allowed-hosts localhost,example.com`| |
|`-h` or `--help` |Print this list and exit. | |
|`-v` or `--version`|Print the version and exit. | |
| `--no-panic` | Don't print error stack in the console, put it in a log file | `false`|
Expand Down
22 changes: 17 additions & 5 deletions bin/http-server
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ if (argv.h || argv.help) {
' -h --help Print this list and exit.',
' -v --version Print the version and exit.',
' --no-panic If error occurs, gracefully shut down and create log file',
' Can also be specified with the env variable NODE_HTTP_SERVER_NO_PANIC'
' Can also be specified with the env variable NODE_HTTP_SERVER_NO_PANIC',
' --allowed-hosts Comma-separated list of hosts allowed to access the server. e.g.: --allowed-hosts localhost,example.com',
].join('\n'));
process.exit();
}
Expand All @@ -106,7 +107,8 @@ var port = argv.p || argv.port || parseInt(process.env.PORT, 10),
utc = argv.U || argv.utc,
version = argv.v || argv.version,
baseDir = argv['base-dir'],
logger;
logger,
allowedHosts = argv['allowed-hosts'];

if (nopanic){
process.on('error', (e)=> {
Expand Down Expand Up @@ -185,6 +187,12 @@ if (!port) {
listen(port);
}

if (allowedHosts && typeof allowedHosts === 'string') {
allowedHosts = allowedHosts.split(',').map((host) => host.trim().toLowerCase());
} else {
allowedHosts = undefined;
}

function listen(port) {
var options = {
root: argv._[0],
Expand All @@ -209,7 +217,8 @@ function listen(port) {
contentType: argv['content-type'],
username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME,
password: argv.password || process.env.NODE_HTTP_SERVER_PASSWORD,
headers: {}
headers: {},
allowedHosts,
};

function setHeader(str) {
Expand Down Expand Up @@ -369,8 +378,11 @@ function listen(port) {

logger.info(chalk.yellow('\nAvailable on:'));


if (argv.a && (host !== '::' || host !== '0.0.0.0')) {
if (allowedHosts) {
for (const host of allowedHosts) {
logger.info(` ${protocol}${host}:${chalk.green(port.toString())}${path}`);
}
} else if (argv.a && (host !== '::' || host !== '0.0.0.0')) {
logger.info(` ${protocol}${host}:${chalk.green(port.toString())}${path}`);
} else {
Object.keys(ifaces).forEach(function (dev) {
Expand Down
4 changes: 4 additions & 0 deletions doc/http-server.1
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ Do not show dotfiles.
.BI \-\-hide\-permissions
Do not show file permissions.

.TP
.BI \-\-allowed\-hosts
Comma-separated list of hosts allowed to access the server. e.g.: \-\-allowed\-hosts localhost,example.com

.TP
.BI \-h ", " \-\-help
Print usage and exit.
Expand Down
18 changes: 18 additions & 0 deletions lib/http-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,24 @@ function HttpServer(options) {
});
}

if (options.allowedHosts) {
before.push(function (req, res) {
let host = req.headers && req.headers.host;
if (host) {
// don't include port number in host check
host = host.split(':')[0];
}

if (!host || !options.allowedHosts.includes(host)) {
res.statusCode = 403;
res.end('Access denied');
return;
}

return res.emit('next');
});
}

if (options.coop) {
this.headers['Cross-Origin-Opener-Policy'] = options.coopHeader || 'same-origin';
this.headers['Cross-Origin-Embedder-Policy'] = 'require-corp';
Expand Down
57 changes: 57 additions & 0 deletions test/allowed-hosts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const test = require('tap').test
const path = require('path')
const httpServer = require('../lib/http-server')
const request = require('request');

// Prevent errors from being swallowed
process.on('uncaughtException', console.error)

test('allowed hosts functionality', (t) => {
t.plan(4);
new Promise((resolve) => {
const server = httpServer.createServer({
root: path.join(__dirname, 'fixtures'),
allowedHosts: ['localhost'],
})

server.listen(0, async () => {
console.log('server listening on port', server.address().port)
const port = server.address().port
const url = `http://localhost:${port}`

try {
await new Promise((resolve, reject) => {
request.get({
url,
headers: {
Host: 'example.com'
}
}, (err, res) => {
console.log('response', err)
t.error(err);
t.equal(res.statusCode, 403);
resolve();
})
})

await new Promise((resolve, reject) => {
request.get({
url,
headers: {
Host: 'localhost'
}
}, (err, res) => {
console.log('response', err)
t.error(err);
t.equal(res.statusCode, 200);
resolve();
})
})
} catch (err) {
t.fail(`allowed hosts test failed: ${err.message}`)
} finally {
server.close()
}
})
})
})
Loading