Skip to content

Commit 6de8abe

Browse files
committed
feat: avoid serving hidden files by default (reenable with --serve-hidden); fixes #211
Also: - fix: ensure `--default-extension` and `--server-info` are settable by CLI
1 parent 0fca18b commit 6de8abe

7 files changed

Lines changed: 177 additions & 6 deletions

File tree

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- **Breaking change** (npm): Set `engines` to 20.11.0+
88
- **Breaking change**: Add `type: 'module'` and `exports` to `package.json`;
99
change internal CJS path
10+
- **Breaking change**: avoid serving hidden files by default (reenable with `--serve-hidden`/`serveHidden`)
1011
- Security: Fix dependency vulnerabilities by switching from `optimist` to
1112
`command-line-basics` (@brettz9)
1213
- Security: Update `mime` and `colors` (@fidian) and pin `colors`
@@ -27,6 +28,7 @@
2728
- Fix: path should be more generous in unescaping anything valid in a
2829
path (such as a hash)
2930
- Fix: Avoid logging range errors to console
31+
- Fix: ensure `--default-extension` and `--server-info` are settable by CLI
3032
- Fix: change `fs.createReadStream()` mode to integer (@pixcai)
3133
- Enhancement: TypeScript support
3234
- Enhancement: Allow access with local ip (@flyingsky)

bin/cli.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ if (args['index-file']) {
7474
options.indexFile = args['index-file'];
7575
}
7676

77+
if (args['default-extension']) {
78+
options.defaultExtension = args['default-extension'];
79+
}
80+
81+
if (args['server-info']) {
82+
options.serverInfo = args['server-info'];
83+
}
84+
85+
if (args['serve-hidden']) {
86+
options.serveHidden = args['serve-hidden'];
87+
}
88+
7789
const file = new(statik.Server)(dir, options);
7890

7991
const server = http.createServer(function (request, response) {

bin/optionDefinitions.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,20 @@ const optionDefinitions = [
2828
description: '"Cache-Control" header setting. [default: 3600]',
2929
typeLabel: '{underline SECONDS}'
3030
},
31+
{
32+
name: 'default-extension', alias: 'e', type: String,
33+
description: 'Optional default extension',
34+
typeLabel: '{underline extension name}'
35+
},
36+
{
37+
name: 'server-info', type: String,
38+
description: 'Info to indicate in a header about the server',
39+
typeLabel: '{underline server info}'
40+
},
41+
{
42+
name: 'serve-hidden', type: Boolean,
43+
description: 'Whether to serve hidden files. Defaults to `false`.',
44+
},
3145
{
3246
name: 'headers', alias: 'H', type: String,
3347
description: 'Additional headers in JSON format.',

lib/node-static.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import http from 'http';
44
import path from 'path';
55
// import buffer from 'buffer';
66

7+
import {isHiddenFile as isHiddenFileOrDirectory} from 'is-hidden-file';
78
import mime from 'mime';
89
import {minimatch} from 'minimatch';
910
import {mstat} from './node-static/util.js';
1011

12+
1113
/**
1214
* @typedef {{
1315
* status: number,
@@ -52,6 +54,7 @@ function tryStat(p, callback) {
5254
* headers?: http.OutgoingHttpHeaders
5355
* serverInfo?: string|null,
5456
* cache?: boolean|number|Record<string, number>,
57+
* serveHidden?: boolean,
5558
* defaultExtension?: string
5659
* }} ServerOptions
5760
*/
@@ -266,6 +269,8 @@ class Server extends events.EventEmitter {
266269
} else {
267270
finish(404, {});
268271
}
272+
} else if (this.options.serveHidden !== true && isHiddenFileOrDirectory(pathname)) {
273+
finish(404, {});
269274
} else if (stat.isFile()) { // Stream a single file.
270275
this.respond(null, status, headers, [pathname], stat, req, res, finish);
271276
} else if (stat.isDirectory()) { // Stream a directory of files.

package-lock.json

Lines changed: 16 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/.hidden-hello.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello world

test/integration/binary.js

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,133 @@ describe('node-static (CLI)', function () {
6767
assert.equal(text, 'hello world', 'should respond with hello world');
6868
});
6969

70+
it('serving file within directory with server info', async function () {
71+
const {response /* , stdout */} =
72+
/**
73+
* @type {{
74+
* response: Response,
75+
* stdout: string
76+
* }}
77+
*/ (await spawnConditional(
78+
binFile,
79+
[
80+
'-p', this.port, fixturePath,
81+
'--server-info', 'my-server'
82+
],
83+
timeout - 9000,
84+
{
85+
condition: /serving ".*?"/,
86+
action: (/* err, stdout */) => {
87+
return fetch(
88+
`http://localhost:${this.port}/hello.txt`
89+
);
90+
}
91+
}));
92+
93+
const {status} = response;
94+
const contentType = response.headers.get('content-type');
95+
const server = response.headers.get('server');
96+
const text = await response.text();
97+
98+
assert.equal(status, 200, 'should respond with 200');
99+
assert.equal(contentType, 'text/plain', 'should respond with text/plain');
100+
assert.equal(server, 'my-server', 'should respond with my-server')
101+
assert.equal(text, 'hello world', 'should respond with hello world');
102+
});
103+
104+
it('serving file within directory with default extension', async function () {
105+
const {response /* , stdout */} =
106+
/**
107+
* @type {{
108+
* response: Response,
109+
* stdout: string
110+
* }}
111+
*/ (await spawnConditional(
112+
binFile,
113+
[
114+
'-p', this.port, fixturePath,
115+
'--default-extension', 'txt'
116+
],
117+
timeout - 9000,
118+
{
119+
condition: /serving ".*?"/,
120+
action: (/* err, stdout */) => {
121+
return fetch(
122+
`http://localhost:${this.port}/hello`
123+
);
124+
}
125+
}));
126+
127+
const {status} = response;
128+
const contentType = response.headers.get('content-type');
129+
const text = await response.text();
130+
131+
assert.equal(status, 200, 'should respond with 200');
132+
assert.equal(contentType, 'text/plain', 'should respond with text/plain');
133+
assert.equal(text, 'hello world', 'should respond with hello world');
134+
});
135+
136+
it('serving file within directory with hidden extension', async function () {
137+
const {response /* , stdout */} =
138+
/**
139+
* @type {{
140+
* response: Response,
141+
* stdout: string
142+
* }}
143+
*/ (await spawnConditional(
144+
binFile,
145+
[
146+
'-p', this.port, fixturePath,
147+
'--serve-hidden'
148+
],
149+
timeout - 9000,
150+
{
151+
condition: /serving ".*?"/,
152+
action: (/* err, stdout */) => {
153+
return fetch(
154+
`http://localhost:${this.port}/.hidden-hello.txt`
155+
);
156+
}
157+
}));
158+
159+
const {status} = response;
160+
const contentType = response.headers.get('content-type');
161+
const text = await response.text();
162+
163+
assert.equal(status, 200, 'should respond with 200');
164+
assert.equal(contentType, 'text/plain', 'should respond with text/plain');
165+
assert.equal(text, 'hello world', 'should respond with hello world');
166+
});
167+
168+
it('serving 404 for file with hidden extension', async function () {
169+
const {response /* , stdout */} =
170+
/**
171+
* @type {{
172+
* response: Response,
173+
* stdout: string
174+
* }}
175+
*/ (await spawnConditional(
176+
binFile,
177+
[
178+
'-p', this.port, fixturePath
179+
],
180+
timeout - 9000,
181+
{
182+
condition: /serving ".*?"/,
183+
action: (/* err, stdout */) => {
184+
return fetch(
185+
`http://localhost:${this.port}/.hidden-hello.txt`
186+
);
187+
}
188+
}));
189+
190+
const {status} = response;
191+
const text = await response.text();
192+
193+
assert.equal(status, 404, 'should respond with 404');
194+
assert.equal(text, 'Not Found', 'should respond with Not Found');
195+
});
196+
70197
it('serving file without directory', async function () {
71198
const {response /* , stdout */} =
72199
/**

0 commit comments

Comments
 (0)