-
Notifications
You must be signed in to change notification settings - Fork 30.6k
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
[v8.x] cli: add --max-http-header-size flag #25171
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -201,6 +201,8 @@ unsigned int reverted = 0; | |
std::string icu_data_dir; // NOLINT(runtime/string) | ||
#endif | ||
|
||
uint64_t max_http_header_size = 8 * 1024; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is now exposed the way options were exposed in the past, PTAL |
||
|
||
// used by C++ modules as well | ||
bool no_deprecation = false; | ||
|
||
|
@@ -3120,6 +3122,8 @@ static void PrintHelp() { | |
" set host:port for inspector\n" | ||
#endif | ||
" --no-deprecation silence deprecation warnings\n" | ||
" --max-http-header-size Specify the maximum size of HTTP\n" | ||
" headers in bytes. Defaults to 8KB.\n" | ||
" --trace-deprecation show stack traces on deprecations\n" | ||
" --throw-deprecation throw an exception on deprecations\n" | ||
" --pending-deprecation emit pending deprecation warnings\n" | ||
|
@@ -3264,6 +3268,7 @@ static void CheckIfAllowedInEnv(const char* exe, bool is_env, | |
"--expose-http2", | ||
"--experimental-modules", | ||
"--loader", | ||
"--max-http-header-size", | ||
"--trace-warnings", | ||
"--redirect-warnings", | ||
"--trace-sync-io", | ||
|
@@ -3468,6 +3473,8 @@ static void ParseArgs(int* argc, | |
new_v8_argc += 1; | ||
} else if (strncmp(arg, "--v8-pool-size=", 15) == 0) { | ||
v8_thread_pool_size = atoi(arg + 15); | ||
} else if (strncmp(arg, "--max-http-header-size=", 23) == 0) { | ||
max_http_header_size = atoi(arg + 23); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this might be strtoul() instead? |
||
#if HAVE_OPENSSL | ||
} else if (strncmp(arg, "--tls-cipher-list=", 18) == 0) { | ||
default_cipher_list = arg + 18; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -88,6 +88,10 @@ static void InitConfig(Local<Object> target, | |
if (env->abort_on_uncaught_exception()) | ||
READONLY_BOOLEAN_PROPERTY("shouldAbortOnUncaughtException"); | ||
|
||
READONLY_PROPERTY(target, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as there is no longer getOptions I have appended the property of max_http_header_size to the process.bindings('config')... is this the best way to do it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we need to expose this, then yes, this is probably the best way (do we need to, though?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the option is exposed in 10.x and higher via getOptions... so was trying to keep the behavior equivalent. Don't need to though, open to reverting this |
||
"maxHTTPHeaderSize", | ||
Number::New(env->isolate(), max_http_header_size)); | ||
|
||
READONLY_PROPERTY(target, | ||
"bits", | ||
Number::New(env->isolate(), 8 * sizeof(intptr_t))); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -755,6 +755,9 @@ const struct http_parser_settings Parser::settings = { | |
nullptr // on_chunk_complete | ||
}; | ||
|
||
void InitMaxHttpHeaderSizeOnce() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is the exact same as the 10.x PR aside from the options parsing |
||
http_parser_set_max_header_size(max_http_header_size); | ||
} | ||
|
||
void InitHttpParser(Local<Object> target, | ||
Local<Value> unused, | ||
|
@@ -801,6 +804,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); | ||
} | ||
|
||
} // anonymous namespace | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. extra assert here to confirm that the value is on the process.binding('config') |
||
'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, currentSize, valid = false) { | |
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); | ||
})); | ||
}); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this needed to be rewritten to match style in 8.x PTAL and lmk if it looks right