diff --git a/README.md b/README.md index 0ffbbc1f..0f17dfd8 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ This will install `http-server` globally so that it may be run from the command |`-e` or `--ext` |Default file extension if none supplied |`html` | |`-s` or `--silent` |Suppress log messages from output | | |`--cors` |Enable CORS via the `Access-Control-Allow-Origin` header | | +|`--private-network-access` |Enable Private Network Access via the `Access-Control-Allow-Private-Network` header | | |`-o [path]` |Open browser window after starting the server. Optionally provide a URL path to open. e.g.: -o /other/dir/ | | |`-c` |Set cache time (in seconds) for cache-control max-age header, e.g. `-c10` for 10 seconds. To disable caching, use `-c-1`.|`3600` | |`-U` or `--utc` |Use UTC time format in log messages.| | diff --git a/bin/http-server b/bin/http-server index 7c597fa8..5d247686 100755 --- a/bin/http-server +++ b/bin/http-server @@ -35,6 +35,8 @@ if (argv.h || argv.help) { ' -s --silent Suppress log messages from output', ' --cors[=headers] Enable CORS via the "Access-Control-Allow-Origin" header', ' Optionally provide CORS headers list separated by commas', + ' --private-network-access Enable Private Network Access via the', + ' "Access-Control-Allow-Private-Network" header', ' -o [path] Open browser window after starting the server.', ' Optionally provide a URL path to open the browser window to.', ' -c Cache time (max-age) in seconds [3600], e.g. -c10 for 10 seconds.', @@ -163,6 +165,10 @@ function listen(port) { } } + if (argv['private-network-access']) { + options.privateNetworkAccess = true; + } + if (proxy) { try { new url.URL(proxy) @@ -210,6 +216,7 @@ function listen(port) { logger.info([ chalk.yellow('\nhttp-server settings: '), ([chalk.yellow('CORS: '), argv.cors ? chalk.cyan(argv.cors) : chalk.red('disabled')].join('')), + ([chalk.yellow('Private Network Access: '), argv['private-network-access'] ? chalk.cyan(argv['private-network-access']) : chalk.red('disabled')].join('')), ([chalk.yellow('Cache: '), argv.c ? (argv.c === '-1' ? chalk.red('disabled') : chalk.cyan(argv.c + ' seconds')) : chalk.cyan('3600 seconds')].join('')), ([chalk.yellow('Connection Timeout: '), argv.t === '0' ? chalk.red('disabled') : (argv.t ? chalk.cyan(argv.t + ' seconds') : chalk.cyan('120 seconds'))].join('')), ([chalk.yellow('Directory Listings: '), argv.d ? chalk.red('not visible') : chalk.cyan('visible')].join('')), diff --git a/doc/http-server.1 b/doc/http-server.1 index bbd87e82..f84e27d9 100644 --- a/doc/http-server.1 +++ b/doc/http-server.1 @@ -62,6 +62,10 @@ Suppress log messages from output. Enable CORS via the "Access-Control-Allow-Origin" header. Optionally provide CORS headers list separated by commas. +.TP +.BI \-\-private-network-access +Enable Private Network Access via the "Access-Control-Allow-Private-Network" header. + .TP .BI \-o " " [\fIPATH\fR] Open default browser window after starting the server. diff --git a/lib/core/aliases.json b/lib/core/aliases.json index 53a22a56..88b50c99 100644 --- a/lib/core/aliases.json +++ b/lib/core/aliases.json @@ -7,6 +7,7 @@ "si": [ "si", "index" ], "handleError": [ "handleError", "handleerror" ], "cors": [ "cors", "CORS" ], + "privateNetworkAccess": [ "privateNetworkAccess", "privatenetworkaccess", "private-network-access" ], "headers": [ "H", "header", "headers" ], "contentType": [ "contentType", "contenttype", "content-type" ], "mimeType": [ diff --git a/lib/core/defaults.json b/lib/core/defaults.json index d919f292..c2a71524 100644 --- a/lib/core/defaults.json +++ b/lib/core/defaults.json @@ -7,6 +7,7 @@ "si": false, "cache": "max-age=3600", "cors": false, + "privateNetworkAccess": false, "gzip": true, "brotli": false, "defaultExt": ".html", diff --git a/lib/core/opts.js b/lib/core/opts.js index ec1b2cbc..4b45f791 100644 --- a/lib/core/opts.js +++ b/lib/core/opts.js @@ -125,6 +125,12 @@ module.exports = (opts) => { } }); + aliases.privateNetworkAccess.forEach((k) => { + if (isDeclared(k) && opts[k]) { + headers['Access-Control-Allow-Private-Network'] = 'true'; + } + }); + aliases.headers.forEach((k) => { if (isDeclared(k)) { if (Array.isArray(opts[k])) { diff --git a/lib/http-server.js b/lib/http-server.js index dfe4c474..19bd9392 100644 --- a/lib/http-server.js +++ b/lib/http-server.js @@ -110,6 +110,10 @@ function HttpServer(options) { } : null)); } + if (options.privateNetworkAccess) { + this.headers['Access-Control-Allow-Private-Network'] = true; + } + if (options.robots) { before.push(function (req, res) { if (req.url === '/robots.txt') { diff --git a/test/private-network-access.test.js b/test/private-network-access.test.js new file mode 100644 index 00000000..16c331b2 --- /dev/null +++ b/test/private-network-access.test.js @@ -0,0 +1,88 @@ +'use strict'; + +const test = require('tap').test; +const server = require('../lib/core'); +const http = require('http'); +const path = require('path'); +const request = require('request'); + +const root = path.join(__dirname, 'public'); + +test('private-network-access defaults to false', (t) => { + t.plan(3); + + const httpServer = http.createServer( + server({ + root, + autoIndex: true, + defaultExt: 'html', + }) + ); + + httpServer.listen(() => { + const port = httpServer.address().port; + const uri = `http://localhost:${port}/subdir/index.html`; + + request.get({ uri }, (err, res) => { + t.ifError(err); + t.equal(res.statusCode, 200); + t.type(res.headers['access-control-allow-private-network'], 'undefined'); + }); + }); + t.once('end', () => { + httpServer.close(); + }); +}); + +test('privateNetworkAccess set to false', (t) => { + t.plan(3); + + const httpServer = http.createServer( + server({ + root, + privateNetworkAccess: false, + autoIndex: true, + defaultExt: 'html', + }) + ); + + httpServer.listen(() => { + const port = httpServer.address().port; + const uri = `http://localhost:${port}/subdir/index.html`; + + request.get({ uri }, (err, res) => { + t.ifError(err); + t.equal(res.statusCode, 200); + t.type(res.headers['access-control-allow-private-network'], 'undefined'); + }); + }); + t.once('end', () => { + httpServer.close(); + }); +}); + +test('privateNetworkAccess set to true', (t) => { + t.plan(3); + + const httpServer = http.createServer( + server({ + root, + privateNetworkAccess: true, + autoIndex: true, + defaultExt: 'html', + }) + ); + + httpServer.listen(() => { + const port = httpServer.address().port; + const uri = `http://localhost:${port}/subdir/index.html`; + request.get({ uri }, (err, res) => { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(res.headers['access-control-allow-private-network'], 'true'); + }); + }); + t.once('end', () => { + httpServer.close(); + }); +});