Skip to content
Permalink
Browse files

http: make --insecure-http-parser configurable per-stream or per-server

From the issue:

> Some servers deviate from HTTP spec enougth that Node.js can't
> communicate with them, but "work" when `--insecure-http-parser`
> is enabled globally. It would be useful to be able to use this
> mode, as a client, only when connecting to known bad servers.

This is largely equivalent to #31446
in terms of code changes.

Fixes: #31440
Refs: #31446

Backport-PR-URL: #31500
PR-URL: #31448
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
addaleax authored and sam-github committed Jan 21, 2020
1 parent ab1fcb8 commit 7fc565666c3257b93ee7b2db1639acfd0ee7a628
Showing with 117 additions and 2 deletions.
  1. +15 −0 doc/api/http.md
  2. +10 −1 lib/_http_client.js
  3. +10 −1 lib/_http_server.js
  4. +82 −0 test/parallel/test-http-insecure-parser-per-stream.js
@@ -1917,6 +1917,9 @@ Found'`.
<!-- YAML
added: v0.1.13
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/31448
description: The `insecureHTTPParser` option is supported now.
- version: v9.6.0, v8.12.0
pr-url: https://github.com/nodejs/node/pull/15752
description: The `options` argument is supported now.
@@ -1929,6 +1932,10 @@ changes:
* `ServerResponse` {http.ServerResponse} Specifies the `ServerResponse` class
to be used. Useful for extending the original `ServerResponse`. **Default:**
`ServerResponse`.
* `insecureHTTPParser` {boolean} Use an insecure HTTP parser that accepts
invalid HTTP headers when `true`. Using the insecure parser should be
avoided. See [`--insecure-http-parser`][] for more information.
**Default:** `false`
* `requestListener` {Function}

* Returns: {http.Server}
@@ -2031,6 +2038,9 @@ Defaults to 8KB. Configurable using the [`--max-http-header-size`][] CLI option.
<!-- YAML
added: v0.3.6
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/31448
description: The `insecureHTTPParser` option is supported now.
- version: v10.9.0
pr-url: https://github.com/nodejs/node/pull/21616
description: The `url` parameter can now be passed along with a separate
@@ -2064,6 +2074,10 @@ changes:
request to. **Default:** `'localhost'`.
* `hostname` {string} Alias for `host`. To support [`url.parse()`][],
`hostname` will be used if both `host` and `hostname` are specified.
* `insecureHTTPParser` {boolean} Use an insecure HTTP parser that accepts
invalid HTTP headers when `true`. Using the insecure parser should be
avoided. See [`--insecure-http-parser`][] for more information.
**Default:** `false`
* `localAddress` {string} Local interface to bind for network connections.
* `lookup` {Function} Custom lookup function. **Default:** [`dns.lookup()`][].
* `method` {string} A string specifying the HTTP request method. **Default:**
@@ -2216,6 +2230,7 @@ Setting the `timeout` option or using the `setTimeout()` function will
not abort the request or do anything besides add a `'timeout'` event.

[`--http-server-default-timeout`]: cli.html#cli_http_server_default_timeout_milliseconds
[`--insecure-http-parser`]: cli.html#cli_insecure_http_parser
[`--max-http-header-size`]: cli.html#cli_max_http_header_size_size
[`'checkContinue'`]: #http_event_checkcontinue
[`'request'`]: #http_event_request
@@ -173,6 +173,14 @@ function ClientRequest(input, options, cb) {
method = this.method = 'GET';
}

const insecureHTTPParser = options.insecureHTTPParser;
if (insecureHTTPParser !== undefined &&
typeof insecureHTTPParser !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE(
'insecureHTTPParser', 'boolean', insecureHTTPParser);
}
this.insecureHTTPParser = insecureHTTPParser;

this.path = options.path || '/';
if (cb) {
this.once('response', cb);
@@ -656,7 +664,8 @@ function tickOnSocket(req, socket) {
req.connection = socket;
parser.initialize(HTTPParser.RESPONSE,
new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req),
isLenient());
req.insecureHTTPParser === undefined ?
isLenient() : req.insecureHTTPParser);
parser.socket = socket;
parser.outgoing = req;
req.parser = parser;
@@ -329,6 +329,14 @@ function Server(options, requestListener) {
this[kIncomingMessage] = options.IncomingMessage || IncomingMessage;
this[kServerResponse] = options.ServerResponse || ServerResponse;

const insecureHTTPParser = options.insecureHTTPParser;
if (insecureHTTPParser !== undefined &&
typeof insecureHTTPParser !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE(
'insecureHTTPParser', 'boolean', insecureHTTPParser);
}
this.insecureHTTPParser = insecureHTTPParser;

net.Server.call(this, { allowHalfOpen: true });

if (requestListener) {
@@ -387,7 +395,8 @@ function connectionListenerInternal(server, socket) {
parser.initialize(
HTTPParser.REQUEST,
new HTTPServerAsyncResource('HTTPINCOMINGMESSAGE', socket),
isLenient(),
server.insecureHTTPParser === undefined ?
isLenient() : server.insecureHTTPParser,
);
parser.socket = socket;

@@ -0,0 +1,82 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const http = require('http');
const MakeDuplexPair = require('../common/duplexpair');

// Test that setting the `maxHeaderSize` option works on a per-stream-basis.

// Test 1: The server sends an invalid header.
{
const { clientSide, serverSide } = MakeDuplexPair();

const req = http.request({
createConnection: common.mustCall(() => clientSide),
insecureHTTPParser: true
}, common.mustCall((res) => {
assert.strictEqual(res.headers.hello, 'foo\x08foo');
res.resume(); // We don’t actually care about contents.
res.on('end', common.mustCall());
}));
req.end();

serverSide.resume(); // Dump the request
serverSide.end('HTTP/1.1 200 OK\r\n' +
'Hello: foo\x08foo\r\n' +
'Content-Length: 0\r\n' +
'\r\n\r\n');
}

// Test 2: The same as Test 1 except without the option, to make sure it fails.
{
const { clientSide, serverSide } = MakeDuplexPair();

const req = http.request({
createConnection: common.mustCall(() => clientSide)
}, common.mustNotCall());
req.end();
req.on('error', common.mustCall());

serverSide.resume(); // Dump the request
serverSide.end('HTTP/1.1 200 OK\r\n' +
'Hello: foo\x08foo\r\n' +
'Content-Length: 0\r\n' +
'\r\n\r\n');
}

// Test 3: The client sends an invalid header.
{
const testData = 'Hello, World!\n';
const server = http.createServer(
{ insecureHTTPParser: true },
common.mustCall((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(testData);
}));

server.on('clientError', common.mustNotCall());

const { clientSide, serverSide } = MakeDuplexPair();
serverSide.server = server;
server.emit('connection', serverSide);

clientSide.write('GET / HTTP/1.1\r\n' +
'Hello: foo\x08foo\r\n' +
'\r\n\r\n');
}

// Test 4: The same as Test 3 except without the option, to make sure it fails.
{
const server = http.createServer(common.mustNotCall());

server.on('clientError', common.mustCall());

const { clientSide, serverSide } = MakeDuplexPair();
serverSide.server = server;
server.emit('connection', serverSide);

clientSide.write('GET / HTTP/1.1\r\n' +
'Hello: foo\x08foo\r\n' +
'\r\n\r\n');
}

0 comments on commit 7fc5656

Please sign in to comment.
You can’t perform that action at this time.