| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1080,6 +1080,11 @@ changes: | |||
| 1080 | 1080 | pr-url: https://github.com/nodejs/node/pull/58313 | |
| 1081 | 1081 | description: Following the deprecation of priority signaling as of RFC 9113, | |
| 1082 | 1082 | `weight` option is deprecated. | |
| 1083 | + - version: | ||
| 1084 | + - v24.0.0 | ||
| 1085 | + - v22.17.0 | ||
| 1086 | + pr-url: https://github.com/nodejs/node/pull/57917 | ||
| 1087 | + description: Allow passing headers in raw array format. | ||
| 1083 | 1088 | --> | |
| 1084 | 1089 | ||
| 1085 | 1090 | * `headers` {HTTP/2 Headers Object|Array} | |
@@ -1850,14 +1855,18 @@ and will throw an error. | |||
| 1850 | 1855 | <!-- YAML | |
| 1851 | 1856 | added: v8.4.0 | |
| 1852 | 1857 | changes: | |
| 1858 | + - version: | ||
| 1859 | + - REPLACEME | ||
| 1860 | + pr-url: https://github.com/nodejs/node/pull/59455 | ||
| 1861 | + description: Allow passing headers in raw array format. | ||
| 1853 | 1862 | - version: | |
| 1854 | 1863 | - v14.5.0 | |
| 1855 | 1864 | - v12.19.0 | |
| 1856 | 1865 | pr-url: https://github.com/nodejs/node/pull/33160 | |
| 1857 | 1866 | description: Allow explicitly setting date headers. | |
| 1858 | 1867 | --> | |
| 1859 | 1868 | ||
| 1860 | - * `headers` {HTTP/2 Headers Object} | ||
| 1869 | + * `headers` {HTTP/2 Headers Object|Array} | ||
| 1861 | 1870 | * `options` {Object} | |
| 1862 | 1871 | * `endStream` {boolean} Set to `true` to indicate that the response will not | |
| 1863 | 1872 | include payload data. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2541,8 +2541,31 @@ function callStreamClose(stream) { | |||
| 2541 | 2541 | stream.close(); | |
| 2542 | 2542 | } | |
| 2543 | 2543 | ||
| 2544 | - function processHeaders(oldHeaders, options) { | ||
| 2545 | - assertIsObject(oldHeaders, 'headers'); | ||
| 2544 | + function prepareResponseHeaders(stream, headersParam, options) { | ||
| 2545 | + let headers; | ||
| 2546 | + let statusCode; | ||
| 2547 | + | ||
| 2548 | + if (ArrayIsArray(headersParam)) { | ||
| 2549 | + ({ | ||
| 2550 | + headers, | ||
| 2551 | + statusCode, | ||
| 2552 | + } = prepareResponseHeadersArray(headersParam, options)); | ||
| 2553 | + stream[kRawHeaders] = headers; | ||
| 2554 | + } else { | ||
| 2555 | + ({ | ||
| 2556 | + headers, | ||
| 2557 | + statusCode, | ||
| 2558 | + } = prepareResponseHeadersObject(headersParam, options)); | ||
| 2559 | + stream[kSentHeaders] = headers; | ||
| 2560 | + } | ||
| 2561 | + | ||
| 2562 | + const headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse); | ||
| 2563 | + | ||
| 2564 | + return { headers, headersList, statusCode }; | ||
| 2565 | + } | ||
| 2566 | + | ||
| 2567 | + function prepareResponseHeadersObject(oldHeaders, options) { | ||
| 2568 | + assertIsObject(oldHeaders, 'headers', ['Object', 'Array']); | ||
| 2546 | 2569 | const headers = { __proto__: null }; | |
| 2547 | 2570 | ||
| 2548 | 2571 | if (oldHeaders !== null && oldHeaders !== undefined) { | |
@@ -2563,23 +2586,58 @@ function processHeaders(oldHeaders, options) { | |||
| 2563 | 2586 | headers[HTTP2_HEADER_DATE] ??= utcDate(); | |
| 2564 | 2587 | } | |
| 2565 | 2588 | ||
| 2589 | + validatePreparedResponseHeaders(headers, statusCode); | ||
| 2590 | + | ||
| 2591 | + return { | ||
| 2592 | + headers, | ||
| 2593 | + statusCode: headers[HTTP2_HEADER_STATUS], | ||
| 2594 | + }; | ||
| 2595 | + } | ||
| 2596 | + | ||
| 2597 | + function prepareResponseHeadersArray(headers, options) { | ||
| 2598 | + let statusCode; | ||
| 2599 | + let isDateSet = false; | ||
| 2600 | + | ||
| 2601 | + for (let i = 0; i < headers.length; i += 2) { | ||
| 2602 | + const header = headers[i].toLowerCase(); | ||
| 2603 | + const value = headers[i + 1]; | ||
| 2604 | + | ||
| 2605 | + if (header === HTTP2_HEADER_STATUS) { | ||
| 2606 | + statusCode = value | 0; | ||
| 2607 | + } else if (header === HTTP2_HEADER_DATE) { | ||
| 2608 | + isDateSet = true; | ||
| 2609 | + } | ||
| 2610 | + } | ||
| 2611 | + | ||
| 2612 | + if (!statusCode) { | ||
| 2613 | + statusCode = HTTP_STATUS_OK; | ||
| 2614 | + headers.unshift(HTTP2_HEADER_STATUS, statusCode); | ||
| 2615 | + } | ||
| 2616 | + | ||
| 2617 | + if (!isDateSet && (options.sendDate == null || options.sendDate)) { | ||
| 2618 | + headers.push(HTTP2_HEADER_DATE, utcDate()); | ||
| 2619 | + } | ||
| 2620 | + | ||
| 2621 | + validatePreparedResponseHeaders(headers, statusCode); | ||
| 2622 | + | ||
| 2623 | + return { headers, statusCode }; | ||
| 2624 | + } | ||
| 2625 | + | ||
| 2626 | + function validatePreparedResponseHeaders(headers, statusCode) { | ||
| 2566 | 2627 | // This is intentionally stricter than the HTTP/1 implementation, which | |
| 2567 | 2628 | // allows values between 100 and 999 (inclusive) in order to allow for | |
| 2568 | 2629 | // backwards compatibility with non-spec compliant code. With HTTP/2, | |
| 2569 | 2630 | // we have the opportunity to start fresh with stricter spec compliance. | |
| 2570 | 2631 | // This will have an impact on the compatibility layer for anyone using | |
| 2571 | 2632 | // non-standard, non-compliant status codes. | |
| 2572 | 2633 | if (statusCode < 200 || statusCode > 599) | |
| 2573 | - throw new ERR_HTTP2_STATUS_INVALID(headers[HTTP2_HEADER_STATUS]); | ||
| 2634 | + throw new ERR_HTTP2_STATUS_INVALID(statusCode); | ||
| 2574 | 2635 | ||
| 2575 | 2636 | const neverIndex = headers[kSensitiveHeaders]; | |
| 2576 | 2637 | if (neverIndex !== undefined && !ArrayIsArray(neverIndex)) | |
| 2577 | 2638 | throw new ERR_INVALID_ARG_VALUE('headers[http2.neverIndex]', neverIndex); | |
| 2578 | - | ||
| 2579 | - return headers; | ||
| 2580 | 2639 | } | |
| 2581 | 2640 | ||
| 2582 | - | ||
| 2583 | 2641 | function onFileUnpipe() { | |
| 2584 | 2642 | const stream = this.sink[kOwner]; | |
| 2585 | 2643 | if (stream.ownsFd) | |
@@ -2882,7 +2940,7 @@ class ServerHttp2Stream extends Http2Stream { | |||
| 2882 | 2940 | } | |
| 2883 | 2941 | ||
| 2884 | 2942 | // Initiate a response on this Http2Stream | |
| 2885 | - respond(headers, options) { | ||
| 2943 | + respond(headersParam, options) { | ||
| 2886 | 2944 | if (this.destroyed || this.closed) | |
| 2887 | 2945 | throw new ERR_HTTP2_INVALID_STREAM(); | |
| 2888 | 2946 | if (this.headersSent) | |
@@ -2907,15 +2965,16 @@ class ServerHttp2Stream extends Http2Stream { | |||
| 2907 | 2965 | state.flags |= STREAM_FLAGS_HAS_TRAILERS; | |
| 2908 | 2966 | } | |
| 2909 | 2967 | ||
| 2910 | - headers = processHeaders(headers, options); | ||
| 2911 | - const headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse); | ||
| 2912 | - this[kSentHeaders] = headers; | ||
| 2968 | + const { | ||
| 2969 | + headers, | ||
| 2970 | + headersList, | ||
| 2971 | + statusCode, | ||
| 2972 | + } = prepareResponseHeaders(this, headersParam, options); | ||
| 2913 | 2973 | ||
| 2914 | 2974 | state.flags |= STREAM_FLAGS_HEADERS_SENT; | |
| 2915 | 2975 | ||
| 2916 | 2976 | // Close the writable side if the endStream option is set or status | |
| 2917 | 2977 | // is one of known codes with no payload, or it's a head request | |
| 2918 | - const statusCode = headers[HTTP2_HEADER_STATUS] | 0; | ||
| 2919 | 2978 | if (!!options.endStream || | |
| 2920 | 2979 | statusCode === HTTP_STATUS_NO_CONTENT || | |
| 2921 | 2980 | statusCode === HTTP_STATUS_RESET_CONTENT || | |
@@ -2945,7 +3004,7 @@ class ServerHttp2Stream extends Http2Stream { | |||
| 2945 | 3004 | // regular file, here the fd is passed directly. If the underlying | |
| 2946 | 3005 | // mechanism is not able to read from the fd, then the stream will be | |
| 2947 | 3006 | // reset with an error code. | |
| 2948 | - respondWithFD(fd, headers, options) { | ||
| 3007 | + respondWithFD(fd, headersParam, options) { | ||
| 2949 | 3008 | if (this.destroyed || this.closed) | |
| 2950 | 3009 | throw new ERR_HTTP2_INVALID_STREAM(); | |
| 2951 | 3010 | if (this.headersSent) | |
@@ -2982,8 +3041,11 @@ class ServerHttp2Stream extends Http2Stream { | |||
| 2982 | 3041 | this[kUpdateTimer](); | |
| 2983 | 3042 | this.ownsFd = false; | |
| 2984 | 3043 | ||
| 2985 | - headers = processHeaders(headers, options); | ||
| 2986 | - const statusCode = headers[HTTP2_HEADER_STATUS] |= 0; | ||
| 3044 | + const { | ||
| 3045 | + headers, | ||
| 3046 | + statusCode, | ||
| 3047 | + } = prepareResponseHeadersObject(headersParam, options); | ||
| 3048 | + | ||
| 2987 | 3049 | // Payload/DATA frames are not permitted in these cases | |
| 2988 | 3050 | if (statusCode === HTTP_STATUS_NO_CONTENT || | |
| 2989 | 3051 | statusCode === HTTP_STATUS_RESET_CONTENT || | |
@@ -3011,7 +3073,7 @@ class ServerHttp2Stream extends Http2Stream { | |||
| 3011 | 3073 | // giving the user an opportunity to verify the details and set additional | |
| 3012 | 3074 | // headers. If statCheck returns false, the operation is aborted and no | |
| 3013 | 3075 | // file details are sent. | |
| 3014 | - respondWithFile(path, headers, options) { | ||
| 3076 | + respondWithFile(path, headersParam, options) { | ||
| 3015 | 3077 | if (this.destroyed || this.closed) | |
| 3016 | 3078 | throw new ERR_HTTP2_INVALID_STREAM(); | |
| 3017 | 3079 | if (this.headersSent) | |
@@ -3042,8 +3104,11 @@ class ServerHttp2Stream extends Http2Stream { | |||
| 3042 | 3104 | this[kUpdateTimer](); | |
| 3043 | 3105 | this.ownsFd = true; | |
| 3044 | 3106 | ||
| 3045 | - headers = processHeaders(headers, options); | ||
| 3046 | - const statusCode = headers[HTTP2_HEADER_STATUS] |= 0; | ||
| 3107 | + const { | ||
| 3108 | + headers, | ||
| 3109 | + statusCode, | ||
| 3110 | + } = prepareResponseHeadersObject(headersParam, options); | ||
| 3111 | + | ||
| 3047 | 3112 | // Payload/DATA frames are not permitted in these cases | |
| 3048 | 3113 | if (statusCode === HTTP_STATUS_NO_CONTENT || | |
| 3049 | 3114 | statusCode === HTTP_STATUS_RESET_CONTENT || | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -690,7 +690,6 @@ function prepareRequestHeadersArray(headers, session) { | |||
| 690 | 690 | const headersList = buildNgHeaderString( | |
| 691 | 691 | rawHeaders, | |
| 692 | 692 | assertValidPseudoHeader, | |
| 693 | - headers[kSensitiveHeaders], | ||
| 694 | 693 | ); | |
| 695 | 694 | ||
| 696 | 695 | return { | |
@@ -755,14 +754,14 @@ const kNoHeaderFlags = StringFromCharCode(NGHTTP2_NV_FLAG_NONE); | |||
| 755 | 754 | * @returns {[string, number]} | |
| 756 | 755 | */ | |
| 757 | 756 | function buildNgHeaderString(arrayOrMap, | |
| 758 | - assertValuePseudoHeader = assertValidPseudoHeader, | ||
| 759 | - sensitiveHeaders = arrayOrMap[kSensitiveHeaders]) { | ||
| 757 | + assertValuePseudoHeader = assertValidPseudoHeader) { | ||
| 760 | 758 | let headers = ''; | |
| 761 | 759 | let pseudoHeaders = ''; | |
| 762 | 760 | let count = 0; | |
| 763 | 761 | ||
| 764 | 762 | const singles = new SafeSet(); | |
| 765 | - const neverIndex = (sensitiveHeaders || emptyArray).map((v) => v.toLowerCase()); | ||
| 763 | + const sensitiveHeaders = arrayOrMap[kSensitiveHeaders] || emptyArray; | ||
| 764 | + const neverIndex = sensitiveHeaders.map((v) => v.toLowerCase()); | ||
| 766 | 765 | ||
| 767 | 766 | function processHeader(key, value) { | |
| 768 | 767 | key = key.toLowerCase(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,76 @@ | |||
| 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 | + | ||
| 9 | + { | ||
| 10 | + const server = http2.createServer(); | ||
| 11 | + server.on('stream', common.mustCall((stream, _headers, _flags, rawHeaders) => { | ||
| 12 | + assert.deepStrictEqual(rawHeaders, [ | ||
| 13 | + ':method', 'GET', | ||
| 14 | + ':authority', `localhost:${server.address().port}`, | ||
| 15 | + ':scheme', 'http', | ||
| 16 | + ':path', '/', | ||
| 17 | + 'a', 'b', | ||
| 18 | + 'x-foo', 'bar', // Lowercased as required for HTTP/2 | ||
| 19 | + 'a', 'c', // Duplicate header order preserved | ||
| 20 | + ]); | ||
| 21 | + stream.respond([ | ||
| 22 | + 'x', '1', | ||
| 23 | + 'x-FOO', 'bar', | ||
| 24 | + 'x', '2', | ||
| 25 | + ]); | ||
| 26 | + | ||
| 27 | + assert.partialDeepStrictEqual(stream.sentHeaders, { | ||
| 28 | + '__proto__': null, | ||
| 29 | + ':status': 200, | ||
| 30 | + 'x': [ '1', '2' ], | ||
| 31 | + 'x-FOO': 'bar', | ||
| 32 | + }); | ||
| 33 | + | ||
| 34 | + assert.strictEqual(typeof stream.sentHeaders.date, 'string'); | ||
| 35 | + | ||
| 36 | + stream.end(); | ||
| 37 | + })); | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + server.listen(0, common.mustCall(() => { | ||
| 41 | + const port = server.address().port; | ||
| 42 | + const client = http2.connect(`http://localhost:${port}`); | ||
| 43 | + | ||
| 44 | + const req = client.request([ | ||
| 45 | + 'a', 'b', | ||
| 46 | + 'x-FOO', 'bar', | ||
| 47 | + 'a', 'c', | ||
| 48 | + ]).end(); | ||
| 49 | + | ||
| 50 | + assert.deepStrictEqual(req.sentHeaders, { | ||
| 51 | + '__proto__': null, | ||
| 52 | + ':path': '/', | ||
| 53 | + ':scheme': 'http', | ||
| 54 | + ':authority': `localhost:${server.address().port}`, | ||
| 55 | + ':method': 'GET', | ||
| 56 | + 'a': [ 'b', 'c' ], | ||
| 57 | + 'x-FOO': 'bar', | ||
| 58 | + }); | ||
| 59 | + | ||
| 60 | + req.on('response', common.mustCall((_headers, _flags, rawHeaders) => { | ||
| 61 | + assert.strictEqual(rawHeaders.length, 10); | ||
| 62 | + assert.deepStrictEqual(rawHeaders.slice(0, 8), [ | ||
| 63 | + ':status', '200', | ||
| 64 | + 'x', '1', | ||
| 65 | + 'x-foo', 'bar', // Lowercased as required for HTTP/2 | ||
| 66 | + 'x', '2', // Duplicate header order preserved | ||
| 67 | + ]); | ||
| 68 | + | ||
| 69 | + assert.strictEqual(rawHeaders[8], 'date'); | ||
| 70 | + assert.strictEqual(typeof rawHeaders[9], 'string'); | ||
| 71 | + | ||
| 72 | + client.close(); | ||
| 73 | + server.close(); | ||
| 74 | + })); | ||
| 75 | + })); | ||
| 76 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,19 +8,33 @@ const http2 = require('http2'); | |||
| 8 | 8 | ||
| 9 | 9 | { | |
| 10 | 10 | const server = http2.createServer(); | |
| 11 | - server.on('stream', common.mustCall((stream, headers, flags, rawHeaders) => { | ||
| 11 | + server.on('stream', common.mustCall((stream, _headers, _flags, rawHeaders) => { | ||
| 12 | 12 | assert.deepStrictEqual(rawHeaders, [ | |
| 13 | 13 | ':path', '/foobar', | |
| 14 | 14 | ':scheme', 'http', | |
| 15 | - ':authority', `localhost:${server.address().port}`, | ||
| 16 | - ':method', 'GET', | ||
| 15 | + ':authority', `test.invalid:${server.address().port}`, | ||
| 16 | + ':method', 'POST', | ||
| 17 | 17 | 'a', 'b', | |
| 18 | - 'x-foo', 'bar', | ||
| 19 | - 'a', 'c', | ||
| 18 | + 'x-foo', 'bar', // Lowercased as required for HTTP/2 | ||
| 19 | + 'a', 'c', // Duplicate header order preserved | ||
| 20 | + ]); | ||
| 21 | + | ||
| 22 | + stream.respond([ | ||
| 23 | + ':status', '404', | ||
| 24 | + 'x', '1', | ||
| 25 | + 'x-FOO', 'bar', | ||
| 26 | + 'x', '2', | ||
| 27 | + 'DATE', '0000', | ||
| 20 | 28 | ]); | |
| 21 | - stream.respond({ | ||
| 22 | - ':status': 200 | ||
| 29 | + | ||
| 30 | + assert.deepStrictEqual(stream.sentHeaders, { | ||
| 31 | + '__proto__': null, | ||
| 32 | + ':status': '404', | ||
| 33 | + 'x': [ '1', '2' ], | ||
| 34 | + 'x-FOO': 'bar', | ||
| 35 | + 'DATE': '0000', | ||
| 23 | 36 | }); | |
| 37 | + | ||
| 24 | 38 | stream.end(); | |
| 25 | 39 | })); | |
| 26 | 40 | ||
@@ -32,8 +46,8 @@ const http2 = require('http2'); | |||
| 32 | 46 | const req = client.request([ | |
| 33 | 47 | ':path', '/foobar', | |
| 34 | 48 | ':scheme', 'http', | |
| 35 | - ':authority', `localhost:${server.address().port}`, | ||
| 36 | - ':method', 'GET', | ||
| 49 | + ':authority', `test.invalid:${server.address().port}`, | ||
| 50 | + ':method', 'POST', | ||
| 37 | 51 | 'a', 'b', | |
| 38 | 52 | 'x-FOO', 'bar', | |
| 39 | 53 | 'a', 'c', | |
@@ -43,14 +57,20 @@ const http2 = require('http2'); | |||
| 43 | 57 | '__proto__': null, | |
| 44 | 58 | ':path': '/foobar', | |
| 45 | 59 | ':scheme': 'http', | |
| 46 | - ':authority': `localhost:${server.address().port}`, | ||
| 47 | - ':method': 'GET', | ||
| 60 | + ':authority': `test.invalid:${server.address().port}`, | ||
| 61 | + ':method': 'POST', | ||
| 48 | 62 | 'a': [ 'b', 'c' ], | |
| 49 | 63 | 'x-FOO': 'bar', | |
| 50 | 64 | }); | |
| 51 | 65 | ||
| 52 | - req.on('response', common.mustCall((headers) => { | ||
| 53 | - assert.strictEqual(headers[':status'], 200); | ||
| 66 | + req.on('response', common.mustCall((_headers, _flags, rawHeaders) => { | ||
| 67 | + assert.deepStrictEqual(rawHeaders, [ | ||
| 68 | + ':status', '404', | ||
| 69 | + 'x', '1', | ||
| 70 | + 'x-foo', 'bar', // Lowercased as required for HTTP/2 | ||
| 71 | + 'x', '2', // Duplicate header order preserved | ||
| 72 | + 'date', '0000', // Server doesn't automatically set its own value | ||
| 73 | + ]); | ||
| 54 | 74 | client.close(); | |
| 55 | 75 | server.close(); | |
| 56 | 76 | })); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments