Skip to content

Commit f5f25ef

Browse files
authored
Merge branch 'master' into qr
2 parents efcffcd + 96aa6e9 commit f5f25ef

File tree

9 files changed

+233
-6
lines changed

9 files changed

+233
-6
lines changed

.github/release-drafter.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name-template: 'v$RESOLVED_VERSION'
2+
tag-template: 'v$RESOLVED_VERSION'
3+
4+
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
5+
change-title-escapes: '\<*_&'
6+
7+
categories:
8+
- title: 'Breaking changes'
9+
labels:
10+
- 'major version'
11+
- title: 'Features and enhancements'
12+
labels:
13+
- 'feature'
14+
- 'enhancement'
15+
- title: 'Bug Fixes'
16+
labels:
17+
- 'fix'
18+
- 'bug'
19+
- title: 'Other changes'
20+
labels:
21+
- 'dependencies'
22+
- 'documentation'
23+
24+
exclude-labels:
25+
- "skip-changelog"
26+
- "maintenance"
27+
- "trivial"
28+
29+
version-resolver:
30+
major:
31+
labels:
32+
- 'major version'
33+
minor:
34+
labels:
35+
- 'minor version'
36+
patch:
37+
labels:
38+
- 'patch version'
39+
default: patch
40+
41+
template: |
42+
$CHANGES
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: release-drafter
2+
3+
on:
4+
push:
5+
# branches to consider in the event; optional, defaults to all
6+
branches:
7+
- master
8+
9+
jobs:
10+
update_release_draft:
11+
permissions:
12+
contents: write
13+
pull-requests: read
14+
if: github.repository == 'http-party/http-server'
15+
runs-on: ubuntu-latest
16+
steps:
17+
# Drafts your next release notes as pull requests are merged into master
18+
- uses: release-drafter/release-drafter@v5
19+
env:
20+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ This will install `http-server` globally so that it may be run from the command
5858
|`-U` or `--utc` |Use UTC time format in log messages.| |
5959
|`--log-ip` |Enable logging of the client's IP address |`false` |
6060
|`-P` or `--proxy` |Proxies all requests which can't be resolved locally to the given url. e.g.: -P http://someurl.com | |
61-
|`--proxy-options` Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false
62-
61+
|`--proxy-options` |Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false |
6362
|`--username` |Username for basic authentication | |
6463
|`--password` |Password for basic authentication | |
6564
|`-S`, `--tls` or `--ssl` |Enable secure request serving with TLS/SSL (HTTPS)|`false`|

bin/http-server

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var colors = require('colors/safe'),
99
opener = require('opener'),
1010
fs = require('fs'),
1111
qrcode = require('qrcode-terminal');
12+
url = require('url');
1213
var argv = require('minimist')(process.argv.slice(2), {
1314
alias: {
1415
tls: 'ssl'
@@ -163,6 +164,16 @@ function listen(port) {
163164
}
164165
}
165166

167+
if (proxy) {
168+
try {
169+
new url.URL(proxy)
170+
}
171+
catch (err) {
172+
logger.info(colors.red('Error: Invalid proxy url'));
173+
process.exit(1);
174+
}
175+
}
176+
166177
if (tls) {
167178
options.https = {
168179
cert: argv.C || argv.cert || 'cert.pem',

lib/core/index.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ function decodePathname(pathname) {
3131
? normalized.replace(/\\/g, '/') : normalized;
3232
}
3333

34+
const nonUrlSafeCharsRgx = /[\x00-\x1F\x20\x7F-\uFFFF]+/g;
35+
function ensureUriEncoded(text) {
36+
return text
37+
return String(text).replace(nonUrlSafeCharsRgx, encodeURIComponent);
38+
}
3439

3540
// Check to see if we should try to compress a file with gzip.
3641
function shouldCompressGzip(req) {
@@ -161,7 +166,8 @@ module.exports = function createMiddleware(_dir, _options) {
161166
if (opts.weakCompare && clientEtag !== serverEtag
162167
&& clientEtag !== `W/${serverEtag}` && `W/${clientEtag}` !== serverEtag) {
163168
return false;
164-
} else if (!opts.weakCompare && (clientEtag !== serverEtag || clientEtag.indexOf('W/') === 0)) {
169+
}
170+
if (!opts.weakCompare && (clientEtag !== serverEtag || clientEtag.indexOf('W/') === 0)) {
165171
return false;
166172
}
167173
}
@@ -340,8 +346,10 @@ module.exports = function createMiddleware(_dir, _options) {
340346
}, res, next);
341347
} else {
342348
// Try to serve default ./404.html
349+
const rawUrl = (handleError ? `/${path.join(baseDir, `404.${defaultExt}`)}` : req.url);
350+
const encodedUrl = ensureUriEncoded(rawUrl);
343351
middleware({
344-
url: (handleError ? `/${path.join(baseDir, `404.${defaultExt}`)}` : req.url),
352+
url: encodedUrl,
345353
headers: req.headers,
346354
statusCode: 404,
347355
}, res, next);
@@ -359,7 +367,10 @@ module.exports = function createMiddleware(_dir, _options) {
359367
if (!pathname.match(/\/$/)) {
360368
res.statusCode = 302;
361369
const q = parsed.query ? `?${parsed.query}` : '';
362-
res.setHeader('location', `${parsed.pathname}/${q}`);
370+
res.setHeader(
371+
'location',
372+
ensureUriEncoded(`${parsed.pathname}/${q}`)
373+
);
363374
res.end();
364375
return;
365376
}

lib/core/opts.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ module.exports = (opts) => {
118118
});
119119

120120
aliases.cors.forEach((k) => {
121-
if (isDeclared(k) && k) {
121+
if (isDeclared(k) && opts[k]) {
122122
handleOptionsMethod = true;
123123
headers['Access-Control-Allow-Origin'] = '*';
124124
headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since';

test/cli.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,16 @@ test('setting mimeTypes via cli - directly', (t) => {
101101
});
102102
});
103103
});
104+
105+
test('--proxy requires you to specify a protocol', (t) => {
106+
t.plan(1);
107+
108+
const options = ['.', '--proxy', 'google.com'];
109+
const server = startServer(options);
110+
111+
tearDown(server, t);
112+
113+
server.on('exit', (code) => {
114+
t.equal(code, 1);
115+
});
116+
});

test/cors.test.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
'use strict';
2+
3+
const test = require('tap').test;
4+
const server = require('../lib/core');
5+
const http = require('http');
6+
const path = require('path');
7+
const request = require('request');
8+
9+
const root = path.join(__dirname, 'public');
10+
11+
test('cors defaults to false', (t) => {
12+
t.plan(4);
13+
14+
const httpServer = http.createServer(
15+
server({
16+
root,
17+
autoIndex: true,
18+
defaultExt: 'html',
19+
})
20+
);
21+
22+
httpServer.listen(() => {
23+
const port = httpServer.address().port;
24+
const uri = `http://localhost:${port}/subdir/index.html`;
25+
26+
request.get({ uri }, (err, res) => {
27+
t.ifError(err);
28+
t.equal(res.statusCode, 200);
29+
t.type(res.headers['access-control-allow-origin'], 'undefined');
30+
t.type(res.headers['access-control-allow-headers'], 'undefined');
31+
});
32+
});
33+
t.once('end', () => {
34+
httpServer.close();
35+
});
36+
});
37+
38+
test('cors set to false', (t) => {
39+
t.plan(4);
40+
41+
const httpServer = http.createServer(
42+
server({
43+
root,
44+
cors: false,
45+
autoIndex: true,
46+
defaultExt: 'html',
47+
})
48+
);
49+
50+
httpServer.listen(() => {
51+
const port = httpServer.address().port;
52+
const uri = `http://localhost:${port}/subdir/index.html`;
53+
54+
request.get({ uri }, (err, res) => {
55+
t.ifError(err);
56+
t.equal(res.statusCode, 200);
57+
t.type(res.headers['access-control-allow-origin'], 'undefined');
58+
t.type(res.headers['access-control-allow-headers'], 'undefined');
59+
});
60+
});
61+
t.once('end', () => {
62+
httpServer.close();
63+
});
64+
});
65+
66+
test('cors set to true', (t) => {
67+
t.plan(4);
68+
69+
const httpServer = http.createServer(
70+
server({
71+
root,
72+
cors: true,
73+
autoIndex: true,
74+
defaultExt: 'html',
75+
})
76+
);
77+
78+
httpServer.listen(() => {
79+
const port = httpServer.address().port;
80+
const uri = `http://localhost:${port}/subdir/index.html`;
81+
request.get({ uri }, (err, res) => {
82+
t.ifError(err);
83+
t.equal(res.statusCode, 200);
84+
t.equal(res.headers['access-control-allow-origin'], '*');
85+
t.equal(res.headers['access-control-allow-headers'], 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since');
86+
});
87+
});
88+
t.once('end', () => {
89+
httpServer.close();
90+
});
91+
});
92+
93+
test('CORS set to true', (t) => {
94+
t.plan(4);
95+
96+
const httpServer = http.createServer(
97+
server({
98+
root,
99+
CORS: true,
100+
autoIndex: true,
101+
defaultExt: 'html',
102+
})
103+
);
104+
105+
httpServer.listen(() => {
106+
const port = httpServer.address().port;
107+
const uri = `http://localhost:${port}/subdir/index.html`;
108+
request.get({ uri }, (err, res) => {
109+
t.ifError(err);
110+
t.equal(res.statusCode, 200);
111+
t.equal(res.headers['access-control-allow-origin'], '*');
112+
t.equal(res.headers['access-control-allow-headers'], 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since');
113+
});
114+
});
115+
t.once('end', () => {
116+
httpServer.close();
117+
});
118+
});

test/main.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ test('http-server main', (t) => {
8787
.indexOf('X-Test') >= 0, 204);
8888
}).catch(err => t.fail(err.toString())),
8989

90+
t.test(
91+
"Regression: don't crash on control characters in query strings",
92+
{},
93+
(t) => {
94+
requestAsync({
95+
uri: encodeURI('http://localhost:8080/file?\x0cfoo'),
96+
}).then(res => {
97+
t.equal(res.statusCode, 200);
98+
}).catch(err => t.fail(err.toString()))
99+
.finally(() => t.end());
100+
}
101+
),
102+
90103
// Light compression testing. Heavier compression tests exist in
91104
// compression.test.js
92105
requestAsync({

0 commit comments

Comments
 (0)