File max_header_size.patch of Package nodejs4
Ported from:
From 59f83d689641d5030743ee4f3e453e754843e188 Mon Sep 17 00:00:00 2001
From: cjihrig <cjihrig@gmail.com>
Date: Thu, 29 Nov 2018 17:29:53 -0500
Subject: [PATCH] deps: cherry-pick http_parser_set_max_header_size
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This commit adds http_parser_set_max_header_size() to the
http-parser for overriding the compile time maximum HTTP
header size.
Backport-PR-URL: https://github.com/nodejs/node/pull/25173
PR-URL: https://github.com/nodejs/node/pull/24811
Fixes: https://github.com/nodejs/node/issues/24692
Refs: https://github.com/nodejs/http-parser/pull/453
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
From c0c4de71f0fb7b55804a9d2110dded0493fc7c3e Mon Sep 17 00:00:00 2001
From: cjihrig <cjihrig@gmail.com>
Date: Wed, 5 Dec 2018 19:59:12 -0500
Subject: [PATCH] http: add maxHeaderSize property
This commit exposes the value of --max-http-header-size
as a property of the http module.
Backport-PR-URL: https://github.com/nodejs/node/pull/25218
PR-URL: https://github.com/nodejs/node/pull/24860
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Shelley Vohr <codebytere@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
From f233b160c9b8d5126b4e4845d1661c718d14d39f Mon Sep 17 00:00:00 2001
From: cjihrig <cjihrig@gmail.com>
Date: Mon, 3 Dec 2018 12:27:46 -0500
Subject: [PATCH] cli: add --max-http-header-size flag
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Allow the maximum size of HTTP headers to be overridden from
the command line.
Backport-PR-URL: https://github.com/nodejs/node/pull/25173
co-authored-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/24811
Fixes: https://github.com/nodejs/node/issues/24692
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
From 8a3e0c069747cb6a7e1889de92304856224fee72 Mon Sep 17 00:00:00 2001
From: Matteo Collina <hello@matteocollina.com>
Date: Fri, 14 Dec 2018 12:30:01 +0100
Subject: [PATCH] http: fix regression of binary upgrade response body
See: https://github.com/nodejs/node/issues/24958
PR-URL: https://github.com/nodejs/node/pull/25036
Reviewed-By: Myles Borins <myles.borins@gmail.com>
Index: node-v4.9.1/deps/http_parser/http_parser.c
===================================================================
--- node-v4.9.1.orig/deps/http_parser/http_parser.c
+++ node-v4.9.1/deps/http_parser/http_parser.c
@@ -25,6 +25,8 @@
#include <string.h>
#include <limits.h>
+static uint32_t max_header_size = HTTP_MAX_HEADER_SIZE;
+
#ifndef ULLONG_MAX
# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
#endif
@@ -137,20 +139,20 @@ do {
} while (0)
/* Don't allow the total size of the HTTP headers (including the status
- * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect
+ * line) to exceed max_header_size. This check is here to protect
* embedders against denial-of-service attacks where the attacker feeds
* us a never-ending header that the embedder keeps buffering.
*
* This check is arguably the responsibility of embedders but we're doing
* it on the embedder's behalf because most won't bother and this way we
- * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger
+ * make the web a little safer. max_header_size is still far bigger
* than any reasonable request or response so this should never affect
* day-to-day operation.
*/
#define COUNT_HEADER_SIZE(V) \
do { \
parser->nread += (V); \
- if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \
+ if (UNLIKELY(parser->nread > max_header_size)) { \
SET_ERRNO(HPE_HEADER_OVERFLOW); \
goto error; \
} \
@@ -1471,7 +1473,7 @@ reexecute:
const char* p_lf;
size_t limit = data + len - p;
- limit = MIN(limit, HTTP_MAX_HEADER_SIZE);
+ limit = MIN(limit, max_header_size);
p_cr = (const char*) memchr(p, CR, limit);
p_lf = (const char*) memchr(p, LF, limit);
@@ -2438,3 +2440,8 @@ http_parser_version(void) {
HTTP_PARSER_VERSION_MINOR * 0x00100 |
HTTP_PARSER_VERSION_PATCH * 0x00001;
}
+
+void
+http_parser_set_max_header_size(uint32_t size) {
+ max_header_size = size;
+}
Index: node-v4.9.1/deps/http_parser/http_parser.h
===================================================================
--- node-v4.9.1.orig/deps/http_parser/http_parser.h
+++ node-v4.9.1/deps/http_parser/http_parser.h
@@ -427,6 +427,9 @@ void http_parser_pause(http_parser *pars
/* Checks if this is the final chunk of the body. */
int http_body_is_final(const http_parser *parser);
+/* Change the maximum header size provided at compile time. */
+void http_parser_set_max_header_size(uint32_t size);
+
#ifdef __cplusplus
}
#endif
Index: node-v4.9.1/doc/api/http.md
===================================================================
--- node-v4.9.1.orig/doc/api/http.md
+++ node-v4.9.1/doc/api/http.md
@@ -1330,6 +1330,16 @@ added: v0.5.9
Global instance of Agent which is used as the default for all http client
requests.
+## http.maxHeaderSize
+<!-- YAML
+added: REPLACEME
+-->
+
+* {number}
+
+Read-only property specifying the maximum allowed size of HTTP headers in bytes.
+Defaults to 8KB. Configurable using the [`--max-http-header-size`][] CLI option.
+
## http.request(options[, callback])
<!-- YAML
added: v0.3.6
@@ -1439,6 +1449,7 @@ There are a few special headers that sho
* Sending an Authorization header will override using the `auth` option
to compute basic authentication.
+[`--max-http-header-size`]: cli.html#cli_max_http_header_size_size
[`'checkContinue'`]: #http_event_checkcontinue
[`'listening'`]: net.html#net_event_listening
[`'response'`]: #http_event_response
Index: node-v4.9.1/lib/http.js
===================================================================
--- node-v4.9.1.orig/lib/http.js
+++ node-v4.9.1/lib/http.js
@@ -19,6 +19,8 @@ const server = require('_http_server');
exports.ServerResponse = server.ServerResponse;
exports.STATUS_CODES = server.STATUS_CODES;
+let maxHeaderSize;
+
const agent = require('_http_agent');
const Agent = exports.Agent = agent.Agent;
@@ -92,8 +94,21 @@ Client.prototype.request = function(meth
return c;
};
+
exports.Client = internalUtil.deprecate(Client, 'http.Client is deprecated.');
exports.createClient = internalUtil.deprecate(function(port, host) {
return new Client(port, host);
}, 'http.createClient is deprecated. Use http.request instead.');
+
+Object.defineProperty(module.exports, 'maxHeaderSize', {
+ configurable: true,
+ enumerable: true,
+ get() {
+ if (maxHeaderSize === undefined) {
+ maxHeaderSize = process.binding('config').maxHTTPHeaderSize;
+ }
+
+ return maxHeaderSize;
+ }
+});
Index: node-v4.9.1/test/parallel/test-http-max-header-size.js
===================================================================
--- /dev/null
+++ node-v4.9.1/test/parallel/test-http-max-header-size.js
@@ -0,0 +1,11 @@
+'use strict';
+
+require('../common');
+const assert = require('assert');
+const spawnSync = require('child_process').spawnSync;
+const http = require('http');
+
+assert.strictEqual(http.maxHeaderSize, 8 * 1024);
+const child = spawnSync(process.execPath, ['--max-http-header-size=10', '-p',
+ 'require("http").maxHeaderSize']);
+assert.strictEqual(+child.stdout.toString().trim(), 10);
Index: node-v4.9.1/doc/api/cli.md
===================================================================
--- node-v4.9.1.orig/doc/api/cli.md
+++ node-v4.9.1/doc/api/cli.md
@@ -177,6 +177,12 @@ added: v0.11.15
Specify ICU data load path. (overrides `NODE_ICU_DATA`)
+### `--max-http-header-size=size`
+<!-- YAML
+added: REPLACEME
+-->
+
+Specify the maximum size, in bytes, of HTTP headers. Defaults to 8KB.
## Environment Variables
Index: node-v4.9.1/doc/node.1
===================================================================
--- node-v4.9.1.orig/doc/node.1
+++ node-v4.9.1/doc/node.1
@@ -92,6 +92,10 @@ Preload the specified module at startup.
rules. \fImodule\fR may be either a path to a file, or a node module name.
.TP
+.BR \-\-max\-http\-header-size \fI=size\fR
+Specify the maximum size of HTTP headers in bytes. Defaults to 8KB.
+
+.TP
.BR \-\-no\-deprecation
Silence deprecation warnings.
Index: node-v4.9.1/src/node_config.cc
===================================================================
--- node-v4.9.1.orig/src/node_config.cc
+++ node-v4.9.1/src/node_config.cc
@@ -9,6 +9,7 @@ namespace node {
using v8::Context;
using v8::Local;
+using v8::Number;
using v8::Object;
using v8::Value;
using v8::ReadOnly;
@@ -25,13 +26,25 @@ using v8::ReadOnly;
True(env->isolate()), ReadOnly).FromJust(); \
} while (0)
+#define READONLY_PROPERTY(obj, name, value) \
+ do { \
+ obj->DefineOwnProperty(env->context(), \
+ FIXED_ONE_BYTE_STRING(env->isolate(), name), \
+ value, ReadOnly).FromJust(); \
+ } while (0)
+
void InitConfig(Local<Object> target,
Local<Value> unused,
Local<Context> context) {
-#ifdef NODE_FIPS_MODE
Environment* env = Environment::GetCurrent(context);
+#ifdef NODE_FIPS_MODE
READONLY_BOOLEAN_PROPERTY("fipsMode");
#endif
+
+ READONLY_PROPERTY(target,
+ "maxHTTPHeaderSize",
+ Number::New(env->isolate(), max_http_header_size));
+
}
} // namespace node
Index: node-v4.9.1/src/node_http_parser.cc
===================================================================
--- node-v4.9.1.orig/src/node_http_parser.cc
+++ node-v4.9.1/src/node_http_parser.cc
@@ -611,8 +611,6 @@ class Parser : public AsyncWrap {
size_t nparsed =
http_parser_execute(&parser_, &settings, data, len);
- enum http_errno err = HTTP_PARSER_ERRNO(&parser_);
-
Save();
// Unassign the 'buffer_' variable
@@ -627,7 +625,9 @@ class Parser : public AsyncWrap {
Local<Integer> nparsed_obj = Integer::New(env()->isolate(), nparsed);
// If there was a parse error in one of the callbacks
// TODO(bnoordhuis) What if there is an error on EOF?
- if ((!parser_.upgrade && nparsed != len) || err != HPE_OK) {
+ if (!parser_.upgrade && nparsed != len) {
+ enum http_errno err = HTTP_PARSER_ERRNO(&parser_);
+
Local<Value> e = Exception::Error(env()->parse_error_string());
Local<Object> obj = e->ToObject(env()->isolate());
obj->Set(env()->bytes_parsed_string(), nparsed_obj);
@@ -723,6 +723,9 @@ const struct http_parser_settings Parser
nullptr // on_chunk_complete
};
+void InitMaxHttpHeaderSizeOnce() {
+ http_parser_set_max_header_size(max_http_header_size);
+}
void InitHttpParser(Local<Object> target,
Local<Value> unused,
@@ -767,6 +770,8 @@ void InitHttpParser(Local<Object> target
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser"),
t->GetFunction());
+ static uv_once_t init_once = UV_ONCE_INIT;
+ uv_once(&init_once, InitMaxHttpHeaderSizeOnce);
}
} // namespace node
Index: node-v4.9.1/test/sequential/test-http-max-http-headers.js
===================================================================
--- node-v4.9.1.orig/test/sequential/test-http-max-http-headers.js
+++ node-v4.9.1/test/sequential/test-http-max-http-headers.js
@@ -1,10 +1,17 @@
'use strict';
+// Flags: --expose_internals
const assert = require('assert');
const common = require('../common');
const http = require('http');
const net = require('net');
-const MAX = 8 * 1024; // 8KB
+const MAX = +(process.argv[2] || 8 * 1024); // Command line option, or 8KB.
+
+assert(process.binding('config').maxHTTPHeaderSize,
+ 'The option should exist on process.binding(\'config\')');
+
+console.log('pid is', process.pid);
+console.log('max header size is', process.binding('config').maxHTTPHeaderSize);
// Verify that we cannot receive more than 8KB of headers.
@@ -28,19 +35,15 @@ function fillHeaders(headers, currentSiz
headers += 'a'.repeat(MAX - headers.length - 3);
// Generate valid headers
if (valid) {
- // TODO(mcollina): understand why -9 is needed instead of -1
- headers = headers.slice(0, -9);
+ // TODO(mcollina): understand why -32 is needed instead of -1
+ headers = headers.slice(0, -32);
}
return headers + '\r\n\r\n';
}
-const timeout = common.platformTimeout(10);
-
function writeHeaders(socket, headers) {
const array = [];
-
- // this is off from 1024 so that \r\n does not get split
- const chunkSize = 1000;
+ const chunkSize = 100;
let last = 0;
for (let i = 0; i < headers.length / chunkSize; i++) {
@@ -55,19 +58,25 @@ function writeHeaders(socket, headers) {
next();
function next() {
- if (socket.write(array.shift())) {
- if (array.length === 0) {
- socket.end();
- } else {
- setTimeout(next, timeout);
- }
+ if (socket.destroyed) {
+ console.log('socket was destroyed early, data left to write:',
+ array.join('').length);
+ return;
+ }
+
+ const chunk = array.shift();
+
+ if (chunk) {
+ console.log('writing chunk of size', chunk.length);
+ socket.write(chunk, next);
} else {
- socket.once('drain', next);
+ socket.end();
}
}
}
function test1() {
+ console.log('test1');
let headers =
'HTTP/1.1 200 OK\r\n' +
'Content-Length: 0\r\n' +
@@ -82,6 +91,9 @@ function test1() {
writeHeaders(sock, headers);
sock.resume();
});
+
+ // The socket might error but that's ok
+ sock.on('error', () => {});
});
server.listen(0, common.mustCall(() => {
@@ -90,17 +102,17 @@ function test1() {
client.on('error', common.mustCall((err) => {
assert.strictEqual(err.code, 'HPE_HEADER_OVERFLOW');
- server.close();
- setImmediate(test2);
+ server.close(test2);
}));
}));
}
const test2 = common.mustCall(() => {
+ console.log('test2');
let headers =
'GET / HTTP/1.1\r\n' +
'Host: localhost\r\n' +
- 'Agent: node\r\n' +
+ 'Agent: nod2\r\n' +
'X-CRASH: ';
// /, Host, localhost, Agent, node, X-CRASH, a...
@@ -109,7 +121,7 @@ const test2 = common.mustCall(() => {
const server = http.createServer(common.mustNotCall());
- server.on('clientError', common.mustCall((err) => {
+ server.once('clientError', common.mustCall((err) => {
assert.strictEqual(err.code, 'HPE_HEADER_OVERFLOW');
}));
@@ -121,34 +133,46 @@ const test2 = common.mustCall(() => {
});
finished(client, common.mustCall((err) => {
- server.close();
- setImmediate(test3);
+ server.close(test3);
}));
}));
});
const test3 = common.mustCall(() => {
+ console.log('test3');
let headers =
'GET / HTTP/1.1\r\n' +
'Host: localhost\r\n' +
- 'Agent: node\r\n' +
+ 'Agent: nod3\r\n' +
'X-CRASH: ';
// /, Host, localhost, Agent, node, X-CRASH, a...
const currentSize = 1 + 4 + 9 + 5 + 4 + 7;
headers = fillHeaders(headers, currentSize, true);
+ console.log('writing', headers.length);
+
const server = http.createServer(common.mustCall((req, res) => {
- res.end('hello world');
- setImmediate(server.close.bind(server));
+ res.end('hello from test3 server');
+ server.close();
}));
+ server.on('clientError', (err) => {
+ console.log(err.code);
+ if (err.code === 'HPE_HEADER_OVERFLOW') {
+ console.log(err.rawPacket.toString('hex'));
+ }
+ });
+ server.on('clientError', common.mustNotCall());
+
server.listen(0, common.mustCall(() => {
const client = net.connect(server.address().port);
client.on('connect', () => {
writeHeaders(client, headers);
client.resume();
});
+
+ client.pipe(process.stdout);
}));
});
Index: node-v4.9.1/test/sequential/test-set-http-max-http-headers.js
===================================================================
--- /dev/null
+++ node-v4.9.1/test/sequential/test-set-http-max-http-headers.js
@@ -0,0 +1,104 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const spawn = require('child_process').spawn;
+const path = require('path');
+const testName = path.join(__dirname, 'test-http-max-http-headers.js');
+
+const timeout = common.platformTimeout(100);
+
+const tests = [];
+
+function test(fn) {
+ tests.push(fn);
+}
+
+test(function(cb) {
+ console.log('running subtest expecting failure');
+
+ // Validate that the test fails if the max header size is too small.
+ const args = ['--expose-internals',
+ '--max-http-header-size=1024',
+ testName];
+ const cp = spawn(process.execPath, args, { stdio: 'inherit' });
+
+ cp.on('close', common.mustCall((code, signal) => {
+ assert.strictEqual(code, 1);
+ assert.strictEqual(signal, null);
+ cb();
+ }));
+});
+
+test(function(cb) {
+ console.log('running subtest expecting success');
+
+ const env = Object.assign({}, process.env, {
+ NODE_DEBUG: 'http'
+ });
+
+ // Validate that the test fails if the max header size is too small.
+ // Validate that the test now passes if the same limit becomes large enough.
+ const args = ['--expose-internals',
+ '--max-http-header-size=1024',
+ testName,
+ '1024'];
+ const cp = spawn(process.execPath, args, {
+ env,
+ stdio: 'inherit'
+ });
+
+ cp.on('close', common.mustCall((code, signal) => {
+ assert.strictEqual(code, 0);
+ assert.strictEqual(signal, null);
+ cb();
+ }));
+});
+
+// Next, repeat the same checks using NODE_OPTIONS if it is supported.
+if (process.config.variables.node_without_node_options) {
+ const env = Object.assign({}, process.env, {
+ NODE_OPTIONS: '--max-http-header-size=1024'
+ });
+
+ test(function(cb) {
+ console.log('running subtest expecting failure');
+
+ // Validate that the test fails if the max header size is too small.
+ const args = ['--expose-internals', testName];
+ const cp = spawn(process.execPath, args, { env, stdio: 'inherit' });
+
+ cp.on('close', common.mustCall((code, signal) => {
+ assert.strictEqual(code, 1);
+ assert.strictEqual(signal, null);
+ cb();
+ }));
+ });
+
+ test(function(cb) {
+ // Validate that the test now passes if the same limit
+ // becomes large enough.
+ const args = ['--expose-internals', testName, '1024'];
+ const cp = spawn(process.execPath, args, { env, stdio: 'inherit' });
+
+ cp.on('close', common.mustCall((code, signal) => {
+ assert.strictEqual(code, 0);
+ assert.strictEqual(signal, null);
+ cb();
+ }));
+ });
+}
+
+function runTest() {
+ const fn = tests.shift();
+
+ if (!fn) {
+ return;
+ }
+
+ fn(() => {
+ setTimeout(runTest, timeout);
+ });
+}
+
+runTest();
Index: node-v4.9.1/src/node_internals.h
===================================================================
--- node-v4.9.1.orig/src/node_internals.h
+++ node-v4.9.1/src/node_internals.h
@@ -30,6 +30,9 @@ struct sockaddr;
namespace node {
+// Set in node.cc by ParseArgs when --max-http-header-size is used
+extern uint64_t max_http_header_size;
+
// Forward declaration
class Environment;
Index: node-v4.9.1/src/node.cc
===================================================================
--- node-v4.9.1.orig/src/node.cc
+++ node-v4.9.1/src/node.cc
@@ -161,6 +161,8 @@ unsigned int reverted = 0;
static const char* icu_data_dir = nullptr;
#endif
+uint64_t max_http_header_size = 8 * 1024;
+
// used by C++ modules as well
bool no_deprecation = false;
@@ -3409,6 +3411,8 @@ static void PrintHelp() {
" --trace-deprecation show stack traces on deprecations\n"
" --throw-deprecation throw an exception anytime a deprecated "
"function is used\n"
+ " --max-http-header-size Specify the maximum size of HTTP\n"
+ " headers in bytes. Defaults to 8KB.\n"
" --trace-sync-io show stack trace when use of sync IO\n"
" is detected after the first tick\n"
" --track-heap-objects track heap object allocations for heap "
@@ -3558,6 +3562,8 @@ static void ParseArgs(int* argc,
short_circuit = true;
} else if (strcmp(arg, "--zero-fill-buffers") == 0) {
zero_fill_all_buffers = true;
+ } else if (strncmp(arg, "--max-http-header-size=", 23) == 0) {
+ max_http_header_size = atoi(arg + 23);
} else if (strcmp(arg, "--v8-options") == 0) {
new_v8_argv[new_v8_argc] = "--help";
new_v8_argc += 1;
Index: node-v4.9.1/test/common.js
===================================================================
--- node-v4.9.1.orig/test/common.js
+++ node-v4.9.1/test/common.js
@@ -410,6 +410,26 @@ exports.mustCall = function(fn, expected
};
};
+exports.getCallSite = function getCallSite(top) {
+ const originalStackFormatter = Error.prepareStackTrace;
+ Error.prepareStackTrace = (err, stack) =>
+ `${stack[0].getFileName()}:${stack[0].getLineNumber()}`;
+ const err = new Error();
+ Error.captureStackTrace(err, top);
+ // with the V8 Error API, the stack is not formatted until it is accessed
+ err.stack;
+ Error.prepareStackTrace = originalStackFormatter;
+ return err.stack;
+};
+
+exports.mustNotCall = function(msg) {
+ const callSite = exports.getCallSite(exports.mustNotCall);
+ return function mustNotCall() {
+ assert.fail(
+ `${msg || 'function should not have been called'} at ${callSite}`);
+ };
+};
+
var etcServicesFileName = path.join('/etc', 'services');
if (exports.isWindows) {
etcServicesFileName = path.join(process.env.SystemRoot, 'System32', 'drivers',