Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http: disallow sending obviously invalid status codes #6291

Closed
wants to merge 1 commit into from
Closed
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
4 changes: 4 additions & 0 deletions lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ ServerResponse.prototype.writeHead = function(statusCode, reason, obj) {
headers = obj;
}

statusCode |= 0;
if (statusCode < 100 || statusCode > 999)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I am not sure if this really matters but the expected port range is just 100-599 http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml

Copy link
Contributor Author

@mscdex mscdex Apr 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, @jasnell mentioned this already. However I think it might be better to be a bit more lenient in case of custom/unofficial/whatever status codes out in the wild.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry. I didn't read the comments completely.

throw new RangeError(`Invalid status code: ${statusCode}`);

var statusLine = 'HTTP/1.1 ' + statusCode.toString() + ' ' +
this.statusMessage + CRLF;

Expand Down
92 changes: 92 additions & 0 deletions test/parallel/test-http-response-statuscode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const http = require('http');

const MAX_REQUESTS = 12;
var reqNum = 0;

const server = http.Server(common.mustCall(function(req, res) {
switch (reqNum) {
case 0:
assert.throws(common.mustCall(() => {
res.writeHead(-1);
}, /invalid status code/i));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The closing parenthesis for common.mustCall() is misplaced here and on the other lines. It should read:

}), /invalid status code/i);

With the current code, the RegExp is passed to mustCall as the expected paramater, and then silently replaced with 1 because it's non-numeric. Then the assert.throws call doesn't validate the error message and passes the test as long as any error is thrown.

I'll fix this along with my patch for #9027 unless you'd prefer to see it in a separate PR.

break;
case 1:
assert.throws(common.mustCall(() => {
res.writeHead(Infinity);
}, /invalid status code/i));
break;
case 2:
assert.throws(common.mustCall(() => {
res.writeHead(NaN);
}, /invalid status code/i));
break;
case 3:
assert.throws(common.mustCall(() => {
res.writeHead({});
}, /invalid status code/i));
break;
case 4:
assert.throws(common.mustCall(() => {
res.writeHead(99);
}, /invalid status code/i));
break;
case 5:
assert.throws(common.mustCall(() => {
res.writeHead(1000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps also have a case for a non-numeric string?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 ... null, 'this is not invalid, [], true, etc could all be included in the tests here, just to be overly careful ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested tests added.

}, /invalid status code/i));
break;
case 6:
assert.throws(common.mustCall(() => {
res.writeHead('1000');
}, /invalid status code/i));
break;
case 7:
assert.throws(common.mustCall(() => {
res.writeHead(null);
}, /invalid status code/i));
break;
case 8:
assert.throws(common.mustCall(() => {
res.writeHead(true);
}, /invalid status code/i));
break;
case 9:
assert.throws(common.mustCall(() => {
res.writeHead([]);
}, /invalid status code/i));
break;
case 10:
assert.throws(common.mustCall(() => {
res.writeHead('this is not valid');
}, /invalid status code/i));
break;
case 11:
assert.throws(common.mustCall(() => {
res.writeHead('404 this is not valid either');
}, /invalid status code/i));
this.close();
break;
default:
throw new Error('Unexpected request');
}
res.statusCode = 200;
res.end();
}, MAX_REQUESTS));
server.listen();

server.on('listening', function makeRequest() {
http.get({
port: this.address().port
}, (res) => {
assert.strictEqual(res.statusCode, 200);
res.on('end', () => {
if (++reqNum < MAX_REQUESTS)
makeRequest.call(this);
});
res.resume();
});
});