summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/cli.md11
-rw-r--r--doc/node.16
-rw-r--r--lib/_http_client.js4
-rw-r--r--lib/_http_common.js13
-rw-r--r--lib/_http_server.js4
-rw-r--r--src/node_http_parser.cc7
-rw-r--r--src/node_options.cc4
-rw-r--r--src/node_options.h2
8 files changed, 47 insertions, 4 deletions
diff --git a/doc/api/cli.md b/doc/api/cli.md
index d4b4da5249..171f6b8ad2 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -419,6 +419,16 @@ added: v9.0.0
Specify the `module` of a custom [experimental ECMAScript Module loader][].
`module` may be either a path to a file, or an ECMAScript Module name.
+### `--insecure-http-parser`
+<!-- YAML
+added: REPLACEME
+-->
+
+Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
+interoperability with non-conformant HTTP implementations. It may also allow
+request smuggling and other HTTP attacks that rely on invalid headers being
+accepted. Avoid using this option.
+
### `--max-http-header-size=size`
<!-- YAML
added: v11.6.0
@@ -1064,6 +1074,7 @@ Node.js options that are allowed are:
* `--http-parser`
* `--icu-data-dir`
* `--input-type`
+* `--insecure-http-parser`
* `--inspect-brk`
* `--inspect-port`, `--debug-port`
* `--inspect-publish-uid`
diff --git a/doc/node.1 b/doc/node.1
index 5f98ba7091..5ea38be56e 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -213,6 +213,12 @@ Specify the
.Ar module
to use as a custom module loader.
.
+.It Fl -insecure-http-parser
+Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
+interoperability with non-conformant HTTP implementations. It may also allow
+request smuggling and other HTTP attacks that rely on invalid headers being
+accepted. Avoid using this option.
+.
.It Fl -max-http-header-size Ns = Ns Ar size
Specify the maximum size of HTTP headers in bytes. Defaults to 8KB.
.
diff --git a/lib/_http_client.js b/lib/_http_client.js
index ece93d14e0..7888ce27d5 100644
--- a/lib/_http_client.js
+++ b/lib/_http_client.js
@@ -39,6 +39,7 @@ const {
freeParser,
parsers,
HTTPParser,
+ isLenient,
prepareError,
} = require('_http_common');
const { OutgoingMessage } = require('_http_outgoing');
@@ -676,7 +677,8 @@ function tickOnSocket(req, socket) {
req.socket = socket;
parser.initialize(HTTPParser.RESPONSE,
new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req),
- req.maxHeaderSize || 0);
+ req.maxHeaderSize || 0,
+ isLenient());
parser.socket = socket;
parser.outgoing = req;
req.parser = parser;
diff --git a/lib/_http_common.js b/lib/_http_common.js
index dc6eba6333..f1386e1a09 100644
--- a/lib/_http_common.js
+++ b/lib/_http_common.js
@@ -28,6 +28,8 @@ const {
const { setImmediate } = require('timers');
const { methods, HTTPParser } = internalBinding('http_parser');
+const { getOptionValue } = require('internal/options');
+const insecureHTTPParser = getOptionValue('--insecure-http-parser');
const FreeList = require('internal/freelist');
const incoming = require('_http_incoming');
@@ -237,6 +239,16 @@ function prepareError(err, parser, rawPacket) {
err.message = `Parse Error: ${err.reason}`;
}
+let warnedLenient = false;
+
+function isLenient() {
+ if (insecureHTTPParser && !warnedLenient) {
+ warnedLenient = true;
+ process.emitWarning('Using insecure HTTP parsing');
+ }
+ return insecureHTTPParser;
+}
+
module.exports = {
_checkInvalidHeaderChar: checkInvalidHeaderChar,
_checkIsHttpToken: checkIsHttpToken,
@@ -249,5 +261,6 @@ module.exports = {
parsers,
kIncomingMessage,
HTTPParser,
+ isLenient,
prepareError,
};
diff --git a/lib/_http_server.js b/lib/_http_server.js
index 192d0ddfb7..18b4e8d4a2 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -39,6 +39,7 @@ const {
chunkExpression,
kIncomingMessage,
HTTPParser,
+ isLenient,
_checkInvalidHeaderChar: checkInvalidHeaderChar,
prepareError,
} = require('_http_common');
@@ -410,7 +411,8 @@ function connectionListenerInternal(server, socket) {
parser.initialize(
HTTPParser.REQUEST,
new HTTPServerAsyncResource('HTTPINCOMINGMESSAGE', socket),
- server.maxHeaderSize || 0
+ server.maxHeaderSize || 0,
+ isLenient(),
);
parser.socket = socket;
diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc
index 0328dc7c0f..5e1da912e0 100644
--- a/src/node_http_parser.cc
+++ b/src/node_http_parser.cc
@@ -486,11 +486,13 @@ class Parser : public AsyncWrap, public StreamListener {
static void Initialize(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
+ bool lenient = args[3]->IsTrue();
uint64_t max_http_header_size = 0;
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsObject());
+
if (args.Length() > 2) {
CHECK(args[2]->IsNumber());
max_http_header_size = args[2].As<Number>()->Value();
@@ -515,7 +517,7 @@ class Parser : public AsyncWrap, public StreamListener {
parser->set_provider_type(provider);
parser->AsyncReset(args[1].As<Object>());
- parser->Init(type, max_http_header_size);
+ parser->Init(type, max_http_header_size, lenient);
}
template <bool should_pause>
@@ -762,8 +764,9 @@ class Parser : public AsyncWrap, public StreamListener {
}
- void Init(llhttp_type_t type, uint64_t max_http_header_size) {
+ void Init(llhttp_type_t type, uint64_t max_http_header_size, bool lenient) {
llhttp_init(&parser_, type, &settings);
+ llhttp_set_lenient(&parser_, lenient);
header_nread_ = 0;
url_.Reset();
status_message_.Reset();
diff --git a/src/node_options.cc b/src/node_options.cc
index abf26fb781..831540f993 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -375,6 +375,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
&EnvironmentOptions::heap_snapshot_signal,
kAllowedInEnvironment);
AddOption("--http-parser", "", NoOp{}, kAllowedInEnvironment);
+ AddOption("--insecure-http-parser",
+ "use an insecure HTTP parser that accepts invalid HTTP headers",
+ &EnvironmentOptions::insecure_http_parser,
+ kAllowedInEnvironment);
AddOption("--input-type",
"set module type for string input",
&EnvironmentOptions::module_type,
diff --git a/src/node_options.h b/src/node_options.h
index c4cb5dc04f..7b3ae19fe6 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -158,6 +158,8 @@ class EnvironmentOptions : public Options {
bool print_eval = false;
bool force_repl = false;
+ bool insecure_http_parser = false;
+
bool tls_min_v1_0 = false;
bool tls_min_v1_1 = false;
bool tls_min_v1_2 = false;