Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9b2ffc8

Browse filesBrowse files
cjihrigmcollina
authored andcommitted
cli: add --max-http-header-size flag
Allow the maximum size of HTTP headers to be overridden from the command line. Backport-PR-URL: #25168 co-authored-by: Matteo Collina <hello@matteocollina.com> PR-URL: #24811 Fixes: #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>
1 parent a57aed1 commit 9b2ffc8
Copy full SHA for 9b2ffc8

File tree

Expand file treeCollapse file tree

7 files changed

+174
-25
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+174
-25
lines changed
Open diff view settings
Collapse file

‎doc/api/cli.md‎

Copy file name to clipboardExpand all lines: doc/api/cli.md
+8Lines changed: 8 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ added: v9.0.0
181181

182182
Specify the `file` of the custom [experimental ECMAScript Module][] loader.
183183

184+
### `--max-http-header-size=size`
185+
<!-- YAML
186+
added: REPLACEME
187+
-->
188+
189+
Specify the maximum size, in bytes, of HTTP headers. Defaults to 8KB.
190+
184191
### `--napi-modules`
185192
<!-- YAML
186193
added: v7.10.0
@@ -584,6 +591,7 @@ Node.js options that are allowed are:
584591
- `--inspect-brk`
585592
- `--inspect-port`
586593
- `--loader`
594+
- `--max-http-header-size`
587595
- `--napi-modules`
588596
- `--no-deprecation`
589597
- `--no-force-async-hooks-checks`
Collapse file

‎doc/node.1‎

Copy file name to clipboardExpand all lines: doc/node.1
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ Specify the
133133
as a custom loader, to load
134134
.Fl -experimental-modules .
135135
.
136+
.It Fl -max-http-header-size Ns = Ns Ar size
137+
Specify the maximum size of HTTP headers in bytes. Defaults to 8KB.
138+
.
136139
.It Fl -napi-modules
137140
This option is a no-op.
138141
It is kept for compatibility.
Collapse file

‎src/node_http_parser.cc‎

Copy file name to clipboardExpand all lines: src/node_http_parser.cc
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,10 @@ const struct http_parser_settings Parser::settings = {
738738
nullptr // on_chunk_complete
739739
};
740740

741+
void InitMaxHttpHeaderSizeOnce() {
742+
const uint32_t max_http_header_size = per_process_opts->max_http_header_size;
743+
http_parser_set_max_header_size(max_http_header_size);
744+
}
741745

742746
void Initialize(Local<Object> target,
743747
Local<Value> unused,
@@ -784,6 +788,9 @@ void Initialize(Local<Object> target,
784788

785789
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser"),
786790
t->GetFunction(env->context()).ToLocalChecked());
791+
792+
static uv_once_t init_once = UV_ONCE_INIT;
793+
uv_once(&init_once, InitMaxHttpHeaderSizeOnce);
787794
}
788795

789796
} // anonymous namespace
Collapse file

‎src/node_options.cc‎

Copy file name to clipboardExpand all lines: src/node_options.cc
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ PerProcessOptionsParser::PerProcessOptionsParser() {
236236
kAllowedInEnvironment);
237237
AddAlias("--trace-events-enabled", {
238238
"--trace-event-categories", "v8,node,node.async_hooks" });
239+
AddOption("--max-http-header-size",
240+
"set the maximum size of HTTP headers (default: 8KB)",
241+
&PerProcessOptions::max_http_header_size,
242+
kAllowedInEnvironment);
239243
AddOption("--v8-pool-size",
240244
"set V8's thread pool size",
241245
&PerProcessOptions::v8_thread_pool_size,
Collapse file

‎src/node_options.h‎

Copy file name to clipboardExpand all lines: src/node_options.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class PerProcessOptions : public Options {
116116
std::string title;
117117
std::string trace_event_categories;
118118
std::string trace_event_file_pattern = "node_trace.${rotation}.log";
119+
uint64_t max_http_header_size = 8 * 1024;
119120
int64_t v8_thread_pool_size = 4;
120121
bool zero_fill_all_buffers = false;
121122

Collapse file

‎test/sequential/test-http-max-http-headers.js‎

Copy file name to clipboardExpand all lines: test/sequential/test-http-max-http-headers.js
+47-25Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
// Flags: --expose-internals
12
'use strict';
2-
33
const assert = require('assert');
44
const common = require('../common');
55
const http = require('http');
66
const net = require('net');
7-
const MAX = 8 * 1024; // 8KB
7+
const MAX = +(process.argv[2] || 8 * 1024); // Command line option, or 8KB.
8+
9+
const { getOptionValue } = require('internal/options');
10+
11+
console.log('pid is', process.pid);
12+
console.log('max header size is', getOptionValue('--max-http-header-size'));
813

914
// Verify that we cannot receive more than 8KB of headers.
1015

@@ -28,19 +33,15 @@ function fillHeaders(headers, currentSize, valid = false) {
2833
headers += 'a'.repeat(MAX - headers.length - 3);
2934
// Generate valid headers
3035
if (valid) {
31-
// TODO(mcollina): understand why -9 is needed instead of -1
32-
headers = headers.slice(0, -9);
36+
// TODO(mcollina): understand why -32 is needed instead of -1
37+
headers = headers.slice(0, -32);
3338
}
3439
return headers + '\r\n\r\n';
3540
}
3641

37-
const timeout = common.platformTimeout(10);
38-
3942
function writeHeaders(socket, headers) {
4043
const array = [];
41-
42-
// this is off from 1024 so that \r\n does not get split
43-
const chunkSize = 1000;
44+
const chunkSize = 100;
4445
let last = 0;
4546

4647
for (let i = 0; i < headers.length / chunkSize; i++) {
@@ -55,19 +56,25 @@ function writeHeaders(socket, headers) {
5556
next();
5657

5758
function next() {
58-
if (socket.write(array.shift())) {
59-
if (array.length === 0) {
60-
socket.end();
61-
} else {
62-
setTimeout(next, timeout);
63-
}
59+
if (socket.destroyed) {
60+
console.log('socket was destroyed early, data left to write:',
61+
array.join('').length);
62+
return;
63+
}
64+
65+
const chunk = array.shift();
66+
67+
if (chunk) {
68+
console.log('writing chunk of size', chunk.length);
69+
socket.write(chunk, next);
6470
} else {
65-
socket.once('drain', next);
71+
socket.end();
6672
}
6773
}
6874
}
6975

7076
function test1() {
77+
console.log('test1');
7178
let headers =
7279
'HTTP/1.1 200 OK\r\n' +
7380
'Content-Length: 0\r\n' +
@@ -82,6 +89,9 @@ function test1() {
8289
writeHeaders(sock, headers);
8390
sock.resume();
8491
});
92+
93+
// The socket might error but that's ok
94+
sock.on('error', () => {});
8595
});
8696

8797
server.listen(0, common.mustCall(() => {
@@ -90,17 +100,17 @@ function test1() {
90100

91101
client.on('error', common.mustCall((err) => {
92102
assert.strictEqual(err.code, 'HPE_HEADER_OVERFLOW');
93-
server.close();
94-
setImmediate(test2);
103+
server.close(test2);
95104
}));
96105
}));
97106
}
98107

99108
const test2 = common.mustCall(() => {
109+
console.log('test2');
100110
let headers =
101111
'GET / HTTP/1.1\r\n' +
102112
'Host: localhost\r\n' +
103-
'Agent: node\r\n' +
113+
'Agent: nod2\r\n' +
104114
'X-CRASH: ';
105115

106116
// /, Host, localhost, Agent, node, X-CRASH, a...
@@ -109,7 +119,7 @@ const test2 = common.mustCall(() => {
109119

110120
const server = http.createServer(common.mustNotCall());
111121

112-
server.on('clientError', common.mustCall((err) => {
122+
server.once('clientError', common.mustCall((err) => {
113123
assert.strictEqual(err.code, 'HPE_HEADER_OVERFLOW');
114124
}));
115125

@@ -121,34 +131,46 @@ const test2 = common.mustCall(() => {
121131
});
122132

123133
finished(client, common.mustCall((err) => {
124-
server.close();
125-
setImmediate(test3);
134+
server.close(test3);
126135
}));
127136
}));
128137
});
129138

130139
const test3 = common.mustCall(() => {
140+
console.log('test3');
131141
let headers =
132142
'GET / HTTP/1.1\r\n' +
133143
'Host: localhost\r\n' +
134-
'Agent: node\r\n' +
144+
'Agent: nod3\r\n' +
135145
'X-CRASH: ';
136146

137147
// /, Host, localhost, Agent, node, X-CRASH, a...
138148
const currentSize = 1 + 4 + 9 + 5 + 4 + 7;
139149
headers = fillHeaders(headers, currentSize, true);
140150

151+
console.log('writing', headers.length);
152+
141153
const server = http.createServer(common.mustCall((req, res) => {
142-
res.end('hello world');
143-
setImmediate(server.close.bind(server));
154+
res.end('hello from test3 server');
155+
server.close();
144156
}));
145157

158+
server.on('clientError', (err) => {
159+
console.log(err.code);
160+
if (err.code === 'HPE_HEADER_OVERFLOW') {
161+
console.log(err.rawPacket.toString('hex'));
162+
}
163+
});
164+
server.on('clientError', common.mustNotCall());
165+
146166
server.listen(0, common.mustCall(() => {
147167
const client = net.connect(server.address().port);
148168
client.on('connect', () => {
149169
writeHeaders(client, headers);
150170
client.resume();
151171
});
172+
173+
client.pipe(process.stdout);
152174
}));
153175
});
154176

Collapse file
+104Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { spawn } = require('child_process');
6+
const path = require('path');
7+
const testName = path.join(__dirname, 'test-http-max-http-headers.js');
8+
9+
const timeout = common.platformTimeout(100);
10+
11+
const tests = [];
12+
13+
function test(fn) {
14+
tests.push(fn);
15+
}
16+
17+
test(function(cb) {
18+
console.log('running subtest expecting failure');
19+
20+
// Validate that the test fails if the max header size is too small.
21+
const args = ['--expose-internals',
22+
'--max-http-header-size=1024',
23+
testName];
24+
const cp = spawn(process.execPath, args, { stdio: 'inherit' });
25+
26+
cp.on('close', common.mustCall((code, signal) => {
27+
assert.strictEqual(code, 1);
28+
assert.strictEqual(signal, null);
29+
cb();
30+
}));
31+
});
32+
33+
test(function(cb) {
34+
console.log('running subtest expecting success');
35+
36+
const env = Object.assign({}, process.env, {
37+
NODE_DEBUG: 'http'
38+
});
39+
40+
// Validate that the test fails if the max header size is too small.
41+
// Validate that the test now passes if the same limit becomes large enough.
42+
const args = ['--expose-internals',
43+
'--max-http-header-size=1024',
44+
testName,
45+
'1024'];
46+
const cp = spawn(process.execPath, args, {
47+
env,
48+
stdio: 'inherit'
49+
});
50+
51+
cp.on('close', common.mustCall((code, signal) => {
52+
assert.strictEqual(code, 0);
53+
assert.strictEqual(signal, null);
54+
cb();
55+
}));
56+
});
57+
58+
// Next, repeat the same checks using NODE_OPTIONS if it is supported.
59+
if (process.config.variables.node_without_node_options) {
60+
const env = Object.assign({}, process.env, {
61+
NODE_OPTIONS: '--max-http-header-size=1024'
62+
});
63+
64+
test(function(cb) {
65+
console.log('running subtest expecting failure');
66+
67+
// Validate that the test fails if the max header size is too small.
68+
const args = ['--expose-internals', testName];
69+
const cp = spawn(process.execPath, args, { env, stdio: 'inherit' });
70+
71+
cp.on('close', common.mustCall((code, signal) => {
72+
assert.strictEqual(code, 1);
73+
assert.strictEqual(signal, null);
74+
cb();
75+
}));
76+
});
77+
78+
test(function(cb) {
79+
// Validate that the test now passes if the same limit
80+
// becomes large enough.
81+
const args = ['--expose-internals', testName, '1024'];
82+
const cp = spawn(process.execPath, args, { env, stdio: 'inherit' });
83+
84+
cp.on('close', common.mustCall((code, signal) => {
85+
assert.strictEqual(code, 0);
86+
assert.strictEqual(signal, null);
87+
cb();
88+
}));
89+
});
90+
}
91+
92+
function runTest() {
93+
const fn = tests.shift();
94+
95+
if (!fn) {
96+
return;
97+
}
98+
99+
fn(() => {
100+
setTimeout(runTest, timeout);
101+
});
102+
}
103+
104+
runTest();

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.