Skip to content

Commit 44ca874

Browse files
metcoder95targos
authored andcommitted
http2: add lenient flag for RFC-9113
PR-URL: #58116 Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent f8a2a1e commit 44ca874

7 files changed

Lines changed: 196 additions & 2 deletions

File tree

‎doc/api/http2.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2919,6 +2919,10 @@ changes:
29192919
a server should wait when an [`'unknownProtocol'`][] is emitted. If the
29202920
socket has not been destroyed by that time the server will destroy it.
29212921
**Default:**`10000`.
2922+
*`strictFieldWhitespaceValidation` {boolean} If `true`, it turns on strict leading
2923+
and trailing whitespace validation for HTTP/2 header field names and values
2924+
as per [RFC-9113](https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.1).
2925+
**Default:**`true`.
29222926
* ...: Any [`net.createServer()`][] option can be provided.
29232927
*`onRequestHandler` {Function} See [Compatibility API][]
29242928
* Returns: {Http2Server}
@@ -3090,6 +3094,10 @@ changes:
30903094
a server should wait when an [`'unknownProtocol'`][] event is emitted. If
30913095
the socket has not been destroyed by that time the server will destroy it.
30923096
**Default:**`10000`.
3097+
*`strictFieldWhitespaceValidation` {boolean} If `true`, it turns on strict leading
3098+
and trailing whitespace validation for HTTP/2 header field names and values
3099+
as per [RFC-9113](https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.1).
3100+
**Default:**`true`.
30933101
*`onRequestHandler` {Function} See [Compatibility API][]
30943102
* Returns: {Http2SecureServer}
30953103

@@ -3245,6 +3253,10 @@ changes:
32453253
a server should wait when an [`'unknownProtocol'`][] event is emitted. If
32463254
the socket has not been destroyed by that time the server will destroy it.
32473255
**Default:**`10000`.
3256+
*`strictFieldWhitespaceValidation` {boolean} If `true`, it turns on strict leading
3257+
and trailing whitespace validation for HTTP/2 header field names and values
3258+
as per [RFC-9113](https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.1).
3259+
**Default:**`true`.
32483260
*`listener` {Function} Will be registered as a one-time listener of the
32493261
[`'connect'`][] event.
32503262
* Returns: {ClientHttp2Session}

‎lib/internal/http2/util.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ const IDX_OPTIONS_MAX_SESSION_MEMORY = 8;
229229
constIDX_OPTIONS_MAX_SETTINGS=9;
230230
constIDX_OPTIONS_STREAM_RESET_RATE=10;
231231
constIDX_OPTIONS_STREAM_RESET_BURST=11;
232-
constIDX_OPTIONS_FLAGS=12;
232+
constIDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION=12;
233+
constIDX_OPTIONS_FLAGS=13;
233234

234235
functionupdateOptionsBuffer(options){
235236
letflags=0;
@@ -293,6 +294,13 @@ function updateOptionsBuffer(options) {
293294
optionsBuffer[IDX_OPTIONS_STREAM_RESET_BURST]=
294295
MathMax(1,options.streamResetBurst);
295296
}
297+
298+
if(typeofoptions.strictFieldWhitespaceValidation==='boolean'){
299+
flags|=(1<<IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION);
300+
optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION]=
301+
options.strictFieldWhitespaceValidation===true ? 0 : 1;
302+
}
303+
296304
optionsBuffer[IDX_OPTIONS_FLAGS]=flags;
297305
}
298306

‎src/node_http2.cc‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,12 @@ Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
159159
buffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS]);
160160
}
161161

162+
// Validate headers in accordance to RFC-9113
163+
if (flags & (1 << IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION)) {
164+
nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(
165+
option, buffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION]);
166+
}
167+
162168
// The padding strategy sets the mechanism by which we determine how much
163169
// additional frame padding to apply to DATA and HEADERS frames. Currently
164170
// this is set on a per-session basis, but eventually we may switch to

‎src/node_http2_state.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ namespace http2 {
6060
IDX_OPTIONS_MAX_SETTINGS,
6161
IDX_OPTIONS_STREAM_RESET_RATE,
6262
IDX_OPTIONS_STREAM_RESET_BURST,
63+
IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION,
6364
IDX_OPTIONS_FLAGS
6465
};
6566

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
constassert=require('assert');
7+
consthttp2=require('http2');
8+
constbody=
9+
'<html><head></head><body><h1>this is some data</h2></body></html>';
10+
11+
constserver=http2.createServer((req,res)=>{
12+
res.setHeader('foobar','baz ');
13+
res.setHeader('X-POWERED-BY','node-test\t');
14+
res.setHeader('x-h2-header','\tconnection-test');
15+
res.setHeader('x-h2-header-2',' connection-test');
16+
res.setHeader('x-h2-header-3','connection-test ');
17+
res.end(body);
18+
});
19+
20+
constserver2=http2.createServer((req,res)=>{
21+
res.setHeader('foobar','baz ');
22+
res.setHeader('X-POWERED-BY','node-test\t');
23+
res.setHeader('x-h2-header','\tconnection-test');
24+
res.setHeader('x-h2-header-2',' connection-test');
25+
res.setHeader('x-h2-header-3','connection-test ');
26+
res.end(body);
27+
});
28+
29+
server.listen(0,common.mustCall(()=>{
30+
server2.listen(0,common.mustCall(()=>{
31+
constclient=http2.connect(`http://localhost:${server.address().port}`);
32+
constclient2=http2.connect(`http://localhost:${server2.address().port}`,{strictFieldWhitespaceValidation: false});
33+
constheaders={':path': '/'};
34+
constreq=client.request(headers);
35+
36+
req.setEncoding('utf8');
37+
req.on('response',common.mustCall(function(headers){
38+
assert.strictEqual(headers.foobar,undefined);
39+
assert.strictEqual(headers['x-powered-by'],undefined);
40+
assert.strictEqual(headers['x-powered-by'],undefined);
41+
assert.strictEqual(headers['x-h2-header'],undefined);
42+
assert.strictEqual(headers['x-h2-header-2'],undefined);
43+
assert.strictEqual(headers['x-h2-header-3'],undefined);
44+
}));
45+
46+
letdata='';
47+
req.on('data',(d)=>data+=d);
48+
req.on('end',()=>{
49+
assert.strictEqual(body,data);
50+
client.close();
51+
client.on('close',common.mustCall(()=>{
52+
server.close();
53+
}));
54+
55+
constreq2=client2.request(headers);
56+
letdata2='';
57+
req2.setEncoding('utf8');
58+
req2.on('response',common.mustCall(function(headers){
59+
assert.strictEqual(headers.foobar,'baz ');
60+
assert.strictEqual(headers['x-powered-by'],'node-test\t');
61+
assert.strictEqual(headers['x-h2-header'],'\tconnection-test');
62+
assert.strictEqual(headers['x-h2-header-2'],' connection-test');
63+
assert.strictEqual(headers['x-h2-header-3'],'connection-test ');
64+
}));
65+
req2.on('data',(d)=>data2+=d);
66+
req2.on('end',()=>{
67+
assert.strictEqual(body,data2);
68+
client2.close();
69+
client2.on('close',common.mustCall(()=>{
70+
server2.close();
71+
}));
72+
});
73+
req2.end();
74+
});
75+
76+
req.end();
77+
}));
78+
}));
79+
80+
server.on('error',common.mustNotCall());
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
constassert=require('assert');
7+
consthttp2=require('http2');
8+
constbody=
9+
'<html><head></head><body><h1>this is some data</h2></body></html>';
10+
11+
constserver=http2.createServer((req,res)=>{
12+
assert.strictEqual(req.headers['x-powered-by'],undefined);
13+
assert.strictEqual(req.headers.foobar,undefined);
14+
assert.strictEqual(req.headers['x-h2-header'],undefined);
15+
assert.strictEqual(req.headers['x-h2-header-2'],undefined);
16+
assert.strictEqual(req.headers['x-h2-header-3'],undefined);
17+
assert.strictEqual(req.headers['x-h2-header-4'],undefined);
18+
res.writeHead(200);
19+
res.end(body);
20+
});
21+
22+
constserver2=http2.createServer({strictFieldWhitespaceValidation: false},(req,res)=>{
23+
assert.strictEqual(req.headers.foobar,'baz ');
24+
assert.strictEqual(req.headers['x-powered-by'],'node-test\t');
25+
assert.strictEqual(req.headers['x-h2-header'],'\tconnection-test');
26+
assert.strictEqual(req.headers['x-h2-header-2'],' connection-test');
27+
assert.strictEqual(req.headers['x-h2-header-3'],'connection-test ');
28+
assert.strictEqual(req.headers['x-h2-header-4'],'connection-test\t');
29+
res.writeHead(200);
30+
res.end(body);
31+
});
32+
33+
server.listen(0,common.mustCall(()=>{
34+
server2.listen(0,common.mustCall(()=>{
35+
constclient=http2.connect(`http://localhost:${server.address().port}`);
36+
constclient2=http2.connect(`http://localhost:${server2.address().port}`);
37+
constheaders={
38+
'foobar': 'baz ',
39+
':path': '/',
40+
'x-powered-by': 'node-test\t',
41+
'x-h2-header': '\tconnection-test',
42+
'x-h2-header-2': ' connection-test',
43+
'x-h2-header-3': 'connection-test ',
44+
'x-h2-header-4': 'connection-test\t'
45+
};
46+
constreq=client.request(headers);
47+
48+
req.setEncoding('utf8');
49+
req.on('response',common.mustCall(function(headers){
50+
assert.strictEqual(headers[':status'],200);
51+
}));
52+
53+
letdata='';
54+
req.on('data',(d)=>data+=d);
55+
req.on('end',()=>{
56+
assert.strictEqual(body,data);
57+
client.close();
58+
client.on('close',common.mustCall(()=>{
59+
server.close();
60+
}));
61+
62+
constreq2=client2.request(headers);
63+
letdata2='';
64+
req2.setEncoding('utf8');
65+
req2.on('response',common.mustCall(function(headers){
66+
assert.strictEqual(headers[':status'],200);
67+
}));
68+
req2.on('data',(d)=>data2+=d);
69+
req2.on('end',()=>{
70+
assert.strictEqual(body,data2);
71+
client2.close();
72+
client2.on('close',common.mustCall(()=>{
73+
server2.close();
74+
}));
75+
});
76+
req2.end();
77+
});
78+
79+
req.end();
80+
}));
81+
}));
82+
83+
server.on('error',common.mustNotCall());

‎test/parallel/test-http2-util-update-options-buffer.js‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const IDX_OPTIONS_MAX_SESSION_MEMORY = 8;
2525
constIDX_OPTIONS_MAX_SETTINGS=9;
2626
constIDX_OPTIONS_STREAM_RESET_RATE=10;
2727
constIDX_OPTIONS_STREAM_RESET_BURST=11;
28-
constIDX_OPTIONS_FLAGS=12;
28+
constIDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION=12;
29+
constIDX_OPTIONS_FLAGS=13;
2930

3031
{
3132
updateOptionsBuffer({
@@ -41,6 +42,7 @@ const IDX_OPTIONS_FLAGS = 12;
4142
maxSettings: 10,
4243
streamResetRate: 11,
4344
streamResetBurst: 12,
45+
strictFieldWhitespaceValidation: false
4446
});
4547

4648
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE],1);
@@ -55,6 +57,7 @@ const IDX_OPTIONS_FLAGS = 12;
5557
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_SETTINGS],10);
5658
strictEqual(optionsBuffer[IDX_OPTIONS_STREAM_RESET_RATE],11);
5759
strictEqual(optionsBuffer[IDX_OPTIONS_STREAM_RESET_BURST],12);
60+
strictEqual(optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION],1);
5861

5962
constflags=optionsBuffer[IDX_OPTIONS_FLAGS];
6063

@@ -69,6 +72,7 @@ const IDX_OPTIONS_FLAGS = 12;
6972
ok(flags&(1<<IDX_OPTIONS_MAX_SETTINGS));
7073
ok(flags&(1<<IDX_OPTIONS_STREAM_RESET_RATE));
7174
ok(flags&(1<<IDX_OPTIONS_STREAM_RESET_BURST));
75+
ok(flags&(1<<IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION));
7276
}
7377

7478
{

0 commit comments

Comments
 (0)