Skip to content

Commit a9849c0

Browse files
committed
http: opt-in insecure HTTP header parsing
Allow insecure HTTP header parsing. Make clear it is insecure. See: - #30553 - #27711 (comment) - #30515 Backport-PR-URL: #30471 PR-URL: #30567 Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Denys Otrishko <shishugi@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent a28e5cc commit a9849c0

File tree

8 files changed

+52
-9
lines changed

8 files changed

+52
-9
lines changed

doc/api/cli.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,16 @@ added: v9.0.0
181181

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

184+
### `--insecure-http-parser`
185+
<!-- YAML
186+
added: REPLACEME
187+
-->
188+
189+
Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
190+
interoperability with non-conformant HTTP implementations. It may also allow
191+
request smuggling and other HTTP attacks that rely on invalid headers being
192+
accepted. Avoid using this option.
193+
184194
### `--max-http-header-size=size`
185195
<!-- YAML
186196
added: v10.15.0
@@ -608,6 +618,7 @@ Node.js options that are allowed are:
608618
- `--experimental-worker`
609619
- `--force-fips`
610620
- `--icu-data-dir`
621+
- `--insecure-http-parser`
611622
- `--inspect`
612623
- `--inspect-brk`
613624
- `--inspect-port`

doc/node.1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ Specify the
139139
as a custom loader, to load
140140
.Fl -experimental-modules .
141141
.
142+
.It Fl -insecure-http-parser
143+
Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
144+
interoperability with non-conformant HTTP implementations. It may also allow
145+
request smuggling and other HTTP attacks that rely on invalid headers being
146+
accepted. Avoid using this option.
147+
.
142148
.It Fl -max-http-header-size Ns = Ns Ar size
143149
Specify the maximum size of HTTP headers in bytes. Defaults to 8KB.
144150
.

lib/_http_client.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const {
3131
debug,
3232
freeParser,
3333
httpSocketSetup,
34+
isLenient,
3435
parsers
3536
} = require('_http_common');
3637
const { OutgoingMessage } = require('_http_outgoing');
@@ -626,7 +627,8 @@ function tickOnSocket(req, socket) {
626627
var parser = parsers.alloc();
627628
req.socket = socket;
628629
req.connection = socket;
629-
parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol]);
630+
parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol],
631+
isLenient());
630632
parser.socket = socket;
631633
parser.outgoing = req;
632634
req.parser = parser;

lib/_http_common.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const { methods, HTTPParser } = internalBinding('http_parser');
2525

2626
const { FreeList } = require('internal/freelist');
2727
const { ondrain } = require('internal/http');
28+
const { getOptionValue } = require('internal/options');
29+
const insecureHTTPParser = getOptionValue('--insecure-http-parser');
2830
const incoming = require('_http_incoming');
2931
const {
3032
IncomingMessage,
@@ -149,7 +151,7 @@ function parserOnMessageComplete() {
149151

150152

151153
const parsers = new FreeList('parsers', 1000, function parsersCb() {
152-
const parser = new HTTPParser(HTTPParser.REQUEST);
154+
const parser = new HTTPParser(HTTPParser.REQUEST, isLenient());
153155

154156
cleanParser(parser);
155157

@@ -232,6 +234,16 @@ function cleanParser(parser) {
232234
parser._consumed = false;
233235
}
234236

237+
let warnedLenient = false;
238+
239+
function isLenient() {
240+
if (insecureHTTPParser && !warnedLenient) {
241+
warnedLenient = true;
242+
process.emitWarning('Using insecure HTTP parsing');
243+
}
244+
return insecureHTTPParser;
245+
}
246+
235247
module.exports = {
236248
_checkInvalidHeaderChar: checkInvalidHeaderChar,
237249
_checkIsHttpToken: checkIsHttpToken,
@@ -243,5 +255,6 @@ module.exports = {
243255
httpSocketSetup,
244256
methods,
245257
parsers,
246-
kIncomingMessage
258+
kIncomingMessage,
259+
isLenient
247260
};

lib/_http_server.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const {
3434
chunkExpression,
3535
httpSocketSetup,
3636
kIncomingMessage,
37+
isLenient,
3738
_checkInvalidHeaderChar: checkInvalidHeaderChar
3839
} = require('_http_common');
3940
const { OutgoingMessage } = require('_http_outgoing');
@@ -342,7 +343,8 @@ function connectionListenerInternal(server, socket) {
342343
socket.on('timeout', socketOnTimeout);
343344

344345
var parser = parsers.alloc();
345-
parser.reinitialize(HTTPParser.REQUEST, parser[is_reused_symbol]);
346+
parser.reinitialize(HTTPParser.REQUEST, parser[is_reused_symbol],
347+
isLenient());
346348
parser.socket = socket;
347349

348350
// We are starting to wait for our headers.

src/node_http_parser.cc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,12 @@ struct StringPtr {
161161

162162
class Parser : public AsyncWrap, public StreamListener {
163163
public:
164-
Parser(Environment* env, Local<Object> wrap, enum http_parser_type type)
164+
Parser(Environment* env, Local<Object> wrap, enum http_parser_type type,
165+
bool lenient)
165166
: AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTPPARSER),
166167
current_buffer_len_(0),
167168
current_buffer_data_(nullptr) {
168-
Init(type);
169+
Init(type, lenient);
169170
}
170171

171172

@@ -383,7 +384,7 @@ class Parser : public AsyncWrap, public StreamListener {
383384
http_parser_type type =
384385
static_cast<http_parser_type>(args[0].As<Int32>()->Value());
385386
CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE);
386-
new Parser(env, args.This(), type);
387+
new Parser(env, args.This(), type, args[1]->IsTrue());
387388
}
388389

389390

@@ -475,6 +476,7 @@ class Parser : public AsyncWrap, public StreamListener {
475476

476477
static void Reinitialize(const FunctionCallbackInfo<Value>& args) {
477478
Environment* env = Environment::GetCurrent(args);
479+
bool lenient = args[2]->IsTrue();
478480

479481
CHECK(args[0]->IsInt32());
480482
CHECK(args[1]->IsBoolean());
@@ -493,7 +495,7 @@ class Parser : public AsyncWrap, public StreamListener {
493495
if (isReused) {
494496
parser->AsyncReset();
495497
}
496-
parser->Init(type);
498+
parser->Init(type, lenient);
497499
}
498500

499501

@@ -722,8 +724,9 @@ class Parser : public AsyncWrap, public StreamListener {
722724
}
723725

724726

725-
void Init(enum http_parser_type type) {
727+
void Init(enum http_parser_type type, bool lenient) {
726728
http_parser_init(&parser_, type);
729+
parser_.lenient_http_headers = lenient;
727730
url_.Reset();
728731
status_message_.Reset();
729732
num_fields_ = 0;

src/node_options.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
110110
&EnvironmentOptions::experimental_worker,
111111
kAllowedInEnvironment);
112112
AddOption("--expose-internals", "", &EnvironmentOptions::expose_internals);
113+
AddOption("--insecure-http-parser",
114+
"Use an insecure HTTP parser that accepts invalid HTTP headers",
115+
&EnvironmentOptions::insecure_http_parser,
116+
kAllowedInEnvironment);
113117
AddOption("--loader",
114118
"(with --experimental-modules) use the specified file as a "
115119
"custom loader",

src/node_options.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ class EnvironmentOptions : public Options {
9393
bool print_eval = false;
9494
bool force_repl = false;
9595

96+
bool insecure_http_parser = false;
97+
9698
std::vector<std::string> preload_modules;
9799

98100
std::vector<std::string> user_argv;

0 commit comments

Comments
 (0)