Skip to content

Commit 45fa652

Browse files
committed
http: create an option for setting a maximum size for uri parsing
The http server wasn't able to tell exactly what caused an HPE_HEADER_OVERFLOW, meaning it would yield a 431 error even if what caused it was the request URI being too long. This adds a limit to the URI sizes through a new option called max-http-uri-size, which will be checked against the actual URIs after on_url callback at the node_http_parser_impl file. Fixes: #26296 Refs: expressjs/express#3898
1 parent d73d861 commit 45fa652

7 files changed

Lines changed: 103 additions & 7 deletions

File tree

doc/api/http.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,17 @@ added: v11.6.0
19591959
Read-only property specifying the maximum allowed size of HTTP headers in bytes.
19601960
Defaults to 8KB. Configurable using the [`--max-http-header-size`][] CLI option.
19611961

1962+
## http.maxUriSize
1963+
<!-- YAML
1964+
added: v12.0.0
1965+
-->
1966+
1967+
* {number}
1968+
1969+
Read-only property specifying the maximum allowed size of HTTP request uri
1970+
in bytes. Defaults to 7KB. Configurable using the
1971+
[`--max-http-uri-size`][] CLI option.
1972+
19621973
## http.request(options[, callback])
19631974
## http.request(url[, options][, callback])
19641975
<!-- YAML

lib/_http_server.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,10 @@ const badRequestResponse = Buffer.from(
509509
`HTTP/1.1 400 ${STATUS_CODES[400]}${CRLF}` +
510510
`Connection: close${CRLF}${CRLF}`, 'ascii'
511511
);
512+
const uriTooLongResponse = Buffer.from(
513+
`HTTP/1.1 414 ${STATUS_CODES[414]}${CRLF}` +
514+
`Connection: close${CRLF}${CRLF}`, 'ascii'
515+
);
512516
const requestHeaderFieldsTooLargeResponse = Buffer.from(
513517
`HTTP/1.1 431 ${STATUS_CODES[431]}${CRLF}` +
514518
`Connection: close${CRLF}${CRLF}`, 'ascii'
@@ -520,9 +524,16 @@ function socketOnError(e) {
520524

521525
if (!this.server.emit('clientError', e, this)) {
522526
if (this.writable) {
523-
const response = e.code === 'HPE_HEADER_OVERFLOW' ?
524-
requestHeaderFieldsTooLargeResponse : badRequestResponse;
525-
this.write(response);
527+
switch (e.code) {
528+
case 'HPE_URI_OVERFLOW':
529+
this.write(uriTooLongResponse);
530+
break;
531+
case 'HPE_HEADER_OVERFLOW':
532+
this.write(requestHeaderFieldsTooLargeResponse);
533+
break;
534+
default:
535+
this.write(badRequestResponse);
536+
}
526537
}
527538
this.destroy(e);
528539
}

lib/http.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const {
3333
ServerResponse
3434
} = require('_http_server');
3535
let maxHeaderSize;
36+
let maxUriSize;
3637

3738
function createServer(opts, requestListener) {
3839
return new Server(opts, requestListener);
@@ -76,6 +77,19 @@ Object.defineProperty(module.exports, 'maxHeaderSize', {
7677
}
7778
});
7879

80+
Object.defineProperty(module.exports, 'maxUriSize', {
81+
configurable: true,
82+
enumerable: true,
83+
get() {
84+
if (maxUriSize === undefined) {
85+
const { getOptionValue } = require('internal/options');
86+
maxUriSize = getOptionValue('--max-http-uri-size');
87+
}
88+
89+
return maxUriSize;
90+
}
91+
});
92+
7993
Object.defineProperty(module.exports, 'globalAgent', {
8094
configurable: true,
8195
enumerable: true,

src/node_http_parser_impl.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ class Parser : public AsyncWrap, public StreamListener {
180180

181181

182182
int on_url(const char* at, size_t length) {
183-
int rv = TrackHeader(length);
183+
int rv = TrackHeader(length, before_headers);
184184
if (rv != 0) {
185185
return rv;
186186
}
@@ -825,11 +825,20 @@ class Parser : public AsyncWrap, public StreamListener {
825825
got_exception_ = false;
826826
}
827827

828-
829-
int TrackHeader(size_t len) {
828+
enum tracking_position {
829+
before_headers,
830+
after_request_line
831+
};
832+
int TrackHeader(size_t len, enum tracking_position pos = after_request_line) {
830833
#ifdef NODE_EXPERIMENTAL_HTTP
831834
header_nread_ += len;
832-
if (header_nread_ >= per_process::cli_options->max_http_header_size) {
835+
if (pos == before_headers &&
836+
header_nread_ >= per_process::cli_options->max_http_uri_size) {
837+
llhttp_set_error_reason(&parser_,
838+
"HPE_URI_OVERFLOW:URI overflow");
839+
return HPE_USER;
840+
} else if (pos == after_request_line &&
841+
header_nread_ >= per_process::cli_options->max_http_header_size) {
833842
llhttp_set_error_reason(&parser_, "HPE_HEADER_OVERFLOW:Header overflow");
834843
return HPE_USER;
835844
}

src/node_options.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,10 @@ PerProcessOptionsParser::PerProcessOptionsParser(
426426
"set the maximum size of HTTP headers (default: 8KB)",
427427
&PerProcessOptions::max_http_header_size,
428428
kAllowedInEnvironment);
429+
AddOption("--max-http-uri-size",
430+
"set the maximum size of HTTP request uri (default: 7KB)",
431+
&PerProcessOptions::max_http_uri_size,
432+
kAllowedInEnvironment);
429433
AddOption("--v8-pool-size",
430434
"set V8's thread pool size",
431435
&PerProcessOptions::v8_thread_pool_size,

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ class PerProcessOptions : public Options {
157157
std::string trace_event_categories;
158158
std::string trace_event_file_pattern = "node_trace.${rotation}.log";
159159
uint64_t max_http_header_size = 8 * 1024;
160+
uint64_t max_http_uri_size = 7 * 1024;
160161
int64_t v8_thread_pool_size = 4;
161162
bool zero_fill_all_buffers = false;
162163
bool debug_arraybuffer_allocations = false;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
const assert = require('assert');
3+
const { createServer, maxUriSize } = require('http');
4+
const { createConnection } = require('net');
5+
const { expectsError, mustCall } = require('../common');
6+
7+
const CRLF = '\r\n';
8+
const REQUEST_METHOD_AND_SPACE = 'GET ';
9+
const DUMMY_URI = '/' + 'a'.repeat(
10+
maxUriSize
11+
); // the slash makes it just 1 byte too long
12+
13+
const PAYLOAD = REQUEST_METHOD_AND_SPACE + DUMMY_URI + CRLF;
14+
15+
const server = createServer();
16+
17+
server.on('connection', mustCall((socket) => {
18+
socket.on('error', expectsError({
19+
type: Error,
20+
message: 'Parse Error',
21+
code: 'HPE_URI_OVERFLOW',
22+
bytesParsed: REQUEST_METHOD_AND_SPACE.length + DUMMY_URI.length,
23+
rawPacket: Buffer.from(PAYLOAD)
24+
}));
25+
}));
26+
27+
server.listen(0, mustCall(() => {
28+
const c = createConnection(server.address().port);
29+
let received = '';
30+
31+
c.on('connect', mustCall(() => {
32+
c.write(PAYLOAD);
33+
}));
34+
c.on('data', mustCall((data) => {
35+
received += data.toString();
36+
}));
37+
c.on('end', mustCall(() => {
38+
assert.strictEqual(
39+
received,
40+
'HTTP/1.1 414 URI Too Long\r\n' +
41+
'Connection: close\r\n\r\n'
42+
);
43+
c.end();
44+
}));
45+
c.on('close', mustCall(() => server.close()));
46+
}));

0 commit comments

Comments
 (0)