| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2916,6 +2916,10 @@ changes: | |||
| 2916 | 2916 | a server should wait when an [`'unknownProtocol'`][] is emitted. If the | |
| 2917 | 2917 | socket has not been destroyed by that time the server will destroy it. | |
| 2918 | 2918 | **Default:** `10000`. | |
| 2919 | + * `strictFieldWhitespaceValidation` {boolean} If `true`, it turns on strict leading | ||
| 2920 | + and trailing whitespace validation for HTTP/2 header field names and values | ||
| 2921 | + as per [RFC-9113](https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.1). | ||
| 2922 | + **Default:** `true`. | ||
| 2919 | 2923 | * ...: Any [`net.createServer()`][] option can be provided. | |
| 2920 | 2924 | * `onRequestHandler` {Function} See [Compatibility API][] | |
| 2921 | 2925 | * Returns: {Http2Server} | |
@@ -3087,6 +3091,10 @@ changes: | |||
| 3087 | 3091 | a server should wait when an [`'unknownProtocol'`][] event is emitted. If | |
| 3088 | 3092 | the socket has not been destroyed by that time the server will destroy it. | |
| 3089 | 3093 | **Default:** `10000`. | |
| 3094 | + * `strictFieldWhitespaceValidation` {boolean} If `true`, it turns on strict leading | ||
| 3095 | + and trailing whitespace validation for HTTP/2 header field names and values | ||
| 3096 | + as per [RFC-9113](https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.1). | ||
| 3097 | + **Default:** `true`. | ||
| 3090 | 3098 | * `onRequestHandler` {Function} See [Compatibility API][] | |
| 3091 | 3099 | * Returns: {Http2SecureServer} | |
| 3092 | 3100 | ||
@@ -3242,6 +3250,10 @@ changes: | |||
| 3242 | 3250 | a server should wait when an [`'unknownProtocol'`][] event is emitted. If | |
| 3243 | 3251 | the socket has not been destroyed by that time the server will destroy it. | |
| 3244 | 3252 | **Default:** `10000`. | |
| 3253 | + * `strictFieldWhitespaceValidation` {boolean} If `true`, it turns on strict leading | ||
| 3254 | + and trailing whitespace validation for HTTP/2 header field names and values | ||
| 3255 | + as per [RFC-9113](https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.1). | ||
| 3256 | + **Default:** `true`. | ||
| 3245 | 3257 | * `listener` {Function} Will be registered as a one-time listener of the | |
| 3246 | 3258 | [`'connect'`][] event. | |
| 3247 | 3259 | * Returns: {ClientHttp2Session} | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -218,7 +218,8 @@ const IDX_OPTIONS_MAX_SESSION_MEMORY = 8; | |||
| 218 | 218 | const IDX_OPTIONS_MAX_SETTINGS = 9; | |
| 219 | 219 | const IDX_OPTIONS_STREAM_RESET_RATE = 10; | |
| 220 | 220 | const IDX_OPTIONS_STREAM_RESET_BURST = 11; | |
| 221 | - const IDX_OPTIONS_FLAGS = 12; | ||
| 221 | + const IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION = 12; | ||
| 222 | + const IDX_OPTIONS_FLAGS = 13; | ||
| 222 | 223 | ||
| 223 | 224 | function updateOptionsBuffer(options) { | |
| 224 | 225 | let flags = 0; | |
@@ -282,6 +283,13 @@ function updateOptionsBuffer(options) { | |||
| 282 | 283 | optionsBuffer[IDX_OPTIONS_STREAM_RESET_BURST] = | |
| 283 | 284 | MathMax(1, options.streamResetBurst); | |
| 284 | 285 | } | |
| 286 | + | ||
| 287 | + if (typeof options.strictFieldWhitespaceValidation === 'boolean') { | ||
| 288 | + flags |= (1 << IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION); | ||
| 289 | + optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION] = | ||
| 290 | + options.strictFieldWhitespaceValidation === true ? 0 : 1; | ||
| 291 | + } | ||
| 292 | + | ||
| 285 | 293 | optionsBuffer[IDX_OPTIONS_FLAGS] = flags; | |
| 286 | 294 | } | |
| 287 | 295 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -158,6 +158,12 @@ Http2Options::Http2Options(Http2State* http2_state, SessionType type) { | |||
| 158 | 158 | buffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS]); | |
| 159 | 159 | } | |
| 160 | 160 | ||
| 161 | + // Validate headers in accordance to RFC-9113 | ||
| 162 | + if (flags & (1 << IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION)) { | ||
| 163 | + nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation( | ||
| 164 | + option, buffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION]); | ||
| 165 | + } | ||
| 166 | + | ||
| 161 | 167 | // The padding strategy sets the mechanism by which we determine how much | |
| 162 | 168 | // additional frame padding to apply to DATA and HEADERS frames. Currently | |
| 163 | 169 | // this is set on a per-session basis, but eventually we may switch to | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -60,6 +60,7 @@ namespace http2 { | |||
| 60 | 60 | IDX_OPTIONS_MAX_SETTINGS, | |
| 61 | 61 | IDX_OPTIONS_STREAM_RESET_RATE, | |
| 62 | 62 | IDX_OPTIONS_STREAM_RESET_BURST, | |
| 63 | + IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION, | ||
| 63 | 64 | IDX_OPTIONS_FLAGS | |
| 64 | 65 | }; | |
| 65 | 66 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,80 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + if (!common.hasCrypto) | ||
| 5 | + common.skip('missing crypto'); | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + const http2 = require('http2'); | ||
| 8 | + const body = | ||
| 9 | + '<html><head></head><body><h1>this is some data</h2></body></html>'; | ||
| 10 | + | ||
| 11 | + const server = 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 | + const server2 = 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 | + const client = http2.connect(`http://localhost:${server.address().port}`); | ||
| 32 | + const client2 = http2.connect(`http://localhost:${server2.address().port}`, { strictFieldWhitespaceValidation: false }); | ||
| 33 | + const headers = { ':path': '/' }; | ||
| 34 | + const req = 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 | + let data = ''; | ||
| 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 | + const req2 = client2.request(headers); | ||
| 56 | + let data2 = ''; | ||
| 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()); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,83 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + if (!common.hasCrypto) | ||
| 5 | + common.skip('missing crypto'); | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + const http2 = require('http2'); | ||
| 8 | + const body = | ||
| 9 | + '<html><head></head><body><h1>this is some data</h2></body></html>'; | ||
| 10 | + | ||
| 11 | + const server = 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 | + const server2 = 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 | + const client = http2.connect(`http://localhost:${server.address().port}`); | ||
| 36 | + const client2 = http2.connect(`http://localhost:${server2.address().port}`); | ||
| 37 | + const headers = { | ||
| 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 | + const req = 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 | + let data = ''; | ||
| 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 | + const req2 = client2.request(headers); | ||
| 63 | + let data2 = ''; | ||
| 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()); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,7 +25,8 @@ const IDX_OPTIONS_MAX_SESSION_MEMORY = 8; | |||
| 25 | 25 | const IDX_OPTIONS_MAX_SETTINGS = 9; | |
| 26 | 26 | const IDX_OPTIONS_STREAM_RESET_RATE = 10; | |
| 27 | 27 | const IDX_OPTIONS_STREAM_RESET_BURST = 11; | |
| 28 | - const IDX_OPTIONS_FLAGS = 12; | ||
| 28 | + const IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION = 12; | ||
| 29 | + const IDX_OPTIONS_FLAGS = 13; | ||
| 29 | 30 | ||
| 30 | 31 | { | |
| 31 | 32 | updateOptionsBuffer({ | |
@@ -41,6 +42,7 @@ const IDX_OPTIONS_FLAGS = 12; | |||
| 41 | 42 | maxSettings: 10, | |
| 42 | 43 | streamResetRate: 11, | |
| 43 | 44 | streamResetBurst: 12, | |
| 45 | + strictFieldWhitespaceValidation: false | ||
| 44 | 46 | }); | |
| 45 | 47 | ||
| 46 | 48 | strictEqual(optionsBuffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE], 1); | |
@@ -55,6 +57,7 @@ const IDX_OPTIONS_FLAGS = 12; | |||
| 55 | 57 | strictEqual(optionsBuffer[IDX_OPTIONS_MAX_SETTINGS], 10); | |
| 56 | 58 | strictEqual(optionsBuffer[IDX_OPTIONS_STREAM_RESET_RATE], 11); | |
| 57 | 59 | strictEqual(optionsBuffer[IDX_OPTIONS_STREAM_RESET_BURST], 12); | |
| 60 | + strictEqual(optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION], 1); | ||
| 58 | 61 | ||
| 59 | 62 | const flags = optionsBuffer[IDX_OPTIONS_FLAGS]; | |
| 60 | 63 | ||
@@ -69,6 +72,7 @@ const IDX_OPTIONS_FLAGS = 12; | |||
| 69 | 72 | ok(flags & (1 << IDX_OPTIONS_MAX_SETTINGS)); | |
| 70 | 73 | ok(flags & (1 << IDX_OPTIONS_STREAM_RESET_RATE)); | |
| 71 | 74 | ok(flags & (1 << IDX_OPTIONS_STREAM_RESET_BURST)); | |
| 75 | + ok(flags & (1 << IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION)); | ||
| 72 | 76 | } | |
| 73 | 77 | ||
| 74 | 78 | { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments