Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions benchmark/http/bench-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ function main({ len, n }) {
function newParser(type) {
const parser = new HTTPParser();
parser.initialize(type, {});
// Direct parsers bypass cleanParser(); use its production default.
parser.maxHeaderPairs = 2000;

parser.headers = [];

Expand Down
26 changes: 14 additions & 12 deletions src/node_http_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1053,21 +1053,22 @@ class Parser : public AsyncWrap, public StreamListener {

header_pairs_ += 2;

Local<Value> max_header_pairs_v;
if (!object()
->Get(env()->context(),
FIXED_ONE_BYTE_STRING(env()->isolate(), "maxHeaderPairs"))
.ToLocal(&max_header_pairs_v)) {
got_exception_ = true;
return -1;
}
if (header_pairs_ == 2) {
Local<Value> max_header_pairs_v;
if (!object()
->Get(env()->context(),
FIXED_ONE_BYTE_STRING(env()->isolate(), "maxHeaderPairs"))
.ToLocal(&max_header_pairs_v)) {
got_exception_ = true;
return -1;
}

if (!max_header_pairs_v->IsNumber()) {
return 0;
max_header_pairs_ = max_header_pairs_v->IsNumber()
? max_header_pairs_v.As<Number>()->Value()
: 0;
}

const double max_header_pairs = max_header_pairs_v.As<Number>()->Value();
if (max_header_pairs > 0 && header_pairs_ > max_header_pairs) {
if (max_header_pairs_ > 0 && header_pairs_ > max_header_pairs_) {
llhttp_set_error_reason(&parser_, "HPE_HEADER_OVERFLOW:Header overflow");
return HPE_USER;
}
Expand Down Expand Up @@ -1109,6 +1110,7 @@ class Parser : public AsyncWrap, public StreamListener {
const char* current_buffer_data_;
bool headers_completed_ = false;
size_t header_pairs_ = 0;
double max_header_pairs_ = 0;
bool pending_pause_ = false;
bool received_data_ = false;
uint64_t header_nread_ = 0;
Expand Down
63 changes: 63 additions & 0 deletions test/parallel/test-http-parser-max-header-pairs-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const { HTTPParser } = require('_http_common');

const { REQUEST } = HTTPParser;
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;

function createParser() {
const parser = new HTTPParser();
parser.initialize(REQUEST, {});
parser[kOnHeadersComplete] = () => {};
parser[kOnMessageComplete] = () => {};
return parser;
}

// maxHeaderPairs is cached once for each independent header section. Main
// headers, trailers, the next message, and a reinitialized parser must each
// observe a fresh value.
{
const parser = createParser();
const limits = [2, 4, 2, 2];

Object.defineProperty(parser, 'maxHeaderPairs', {
configurable: true,
get: common.mustCall(() => limits.shift(), limits.length),
});

parser[kOnHeadersComplete] = common.mustCall(undefined, 3);
parser[kOnMessageComplete] = common.mustCall(undefined, 3);

const pipelined = Buffer.from(
'POST /first HTTP/1.1\r\n' +
'Transfer-Encoding: chunked\r\n' +
'\r\n' +
'0\r\n' +
'X-A: a\r\n' +
'X-B: b\r\n' +
'\r\n' +
'GET /second HTTP/1.1\r\n' +
'X-C: c\r\n' +
'\r\n'
);
assert.strictEqual(parser.execute(pipelined, 0, pipelined.length), pipelined.length);

parser.initialize(REQUEST, {});
const reused = Buffer.from('GET /reused HTTP/1.1\r\nX-D: d\r\n\r\n');
assert.strictEqual(parser.execute(reused, 0, reused.length), reused.length);
assert.deepStrictEqual(limits, []);
}

// Preserve the existing exception behavior for the first property lookup.
{
const parser = createParser();
const expected = new Error('maxHeaderPairs getter');
Object.defineProperty(parser, 'maxHeaderPairs', {
get: common.mustCall(() => { throw expected; }),
});
const request = Buffer.from('GET / HTTP/1.1\r\nX-A: a\r\n\r\n');
assert.throws(() => parser.execute(request, 0, request.length), expected);
}
Loading