diff --git a/lib/http.js b/lib/http.js index ea54054124..38a8027256 100644 --- a/lib/http.js +++ b/lib/http.js @@ -43,17 +43,17 @@ var parsers = new FreeList('parsers', 1000, function() { parser._headers = []; parser._url = ''; - // Limit incoming headers count as it may cause - // hash collision DoS - parser.maxHeadersCount = 1000; - // Only called in the slow case where slow means // that the request headers were either fragmented // across multiple TCP packets or too large to be // processed in a single run. This method is also // called to process trailing HTTP headers. parser.onHeaders = function(headers, url) { - parser._headers = parser._headers.concat(headers); + // Once we exceeded headers limit - stop collecting them + if (parser.maxHeaderPairs <= 0 || + parser._headers.length < parser.maxHeaderPairs) { + parser._headers = parser._headers.concat(headers); + } parser._url += url; }; @@ -84,9 +84,9 @@ var parsers = new FreeList('parsers', 1000, function() { var n = headers.length; - // If parser.maxHeadersCount <= 0 - assume that there're no limit - if (parser.maxHeadersCount > 0) { - n = Math.min(n, parser.maxHeadersCount << 1); + // If parser.maxHeaderPairs <= 0 - assume that there're no limit + if (parser.maxHeaderPairs > 0) { + n = Math.min(n, parser.maxHeaderPairs); } for (var i = 0; i < n; i += 2) { @@ -1170,8 +1170,11 @@ ClientRequest.prototype.onSocket = function(socket) { req.parser = parser; // Propagate headers limit from request object to parser - if (req.maxHeadersCount) { - parser.maxHeadersCount = req.maxHeadersCount; + if (typeof req.maxHeadersCount === 'number') { + parser.maxHeaderPairs = req.maxHeadersCount << 1; + } else { + // Set default value because parser may be reused from FreeList + parser.maxHeaderPairs = 2000; } socket._httpMessage = req; @@ -1461,8 +1464,11 @@ function connectionListener(socket) { parser.incoming = null; // Propagate headers limit from server instance to parser - if (this.maxHeadersCount) { - parser.maxHeadersCount = this.maxHeadersCount; + if (typeof this.maxHeadersCount === 'number') { + parser.maxHeaderPairs = this.maxHeadersCount << 1; + } else { + // Set default value because parser may be reused from FreeList + parser.maxHeaderPairs = 2000; } socket.addListener('error', function(e) { diff --git a/test/simple/test-http-max-headers-count.js b/test/simple/test-http-max-headers-count.js new file mode 100644 index 0000000000..8d5b97d7c3 --- /dev/null +++ b/test/simple/test-http-max-headers-count.js @@ -0,0 +1,87 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var http = require('http'); + +var requests = 0; +var responses = 0; + +var headers = {}; +var N = 2000; +for (var i = 0; i < N; ++i) { + headers['key' + i] = i; +} + +var maxAndExpected = [ // for server + [50, 50], + [1500, 1500], + [0, N + 2], // Host and Connection +]; +var max = maxAndExpected[requests][0]; +var expected = maxAndExpected[requests][1]; + +var server = http.createServer(function(req, res) { + assert.equal(Object.keys(req.headers).length, expected); + if (++requests < maxAndExpected.length) { + max = maxAndExpected[requests][0]; + expected = maxAndExpected[requests][1]; + server.maxHeadersCount = max; + } + res.writeHead(200, headers); + res.end(); +}); +server.maxHeadersCount = max; + +server.listen(common.PORT, function() { + var maxAndExpected = [ // for client + [20, 20], + [1200, 1200], + [0, N + 2], // Connection and Transfer-Encoding + ]; + doRequest(); + + function doRequest() { + var max = maxAndExpected[responses][0]; + var expected = maxAndExpected[responses][1]; + var req = http.request({ + port: common.PORT, + headers: headers + }, function(res) { + assert.equal(Object.keys(res.headers).length, expected); + res.on('end', function() { + if (++responses < maxAndExpected.length) { + doRequest(); + } else { + server.close(); + } + }); + }); + req.maxHeadersCount = max; + req.end(); + } +}); + +process.on('exit', function() { + assert.equal(requests, maxAndExpected.length); + assert.equal(responses, maxAndExpected.length); +});