http2: add support for raw header arrays in h2Stream.respond() · nodejs/node@dafee05
@@ -2541,8 +2541,31 @@ function callStreamClose(stream) {
25412541stream.close();
25422542}
254325432544-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']);
25462569const headers = { __proto__: null };
2547257025482571if (oldHeaders !== null && oldHeaders !== undefined) {
@@ -2563,23 +2586,58 @@ function processHeaders(oldHeaders, options) {
25632586headers[HTTP2_HEADER_DATE] ??= utcDate();
25642587}
256525882589+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) {
25662627// This is intentionally stricter than the HTTP/1 implementation, which
25672628// allows values between 100 and 999 (inclusive) in order to allow for
25682629// backwards compatibility with non-spec compliant code. With HTTP/2,
25692630// we have the opportunity to start fresh with stricter spec compliance.
25702631// This will have an impact on the compatibility layer for anyone using
25712632// non-standard, non-compliant status codes.
25722633if (statusCode < 200 || statusCode > 599)
2573-throw new ERR_HTTP2_STATUS_INVALID(headers[HTTP2_HEADER_STATUS]);
2634+throw new ERR_HTTP2_STATUS_INVALID(statusCode);
2574263525752636const neverIndex = headers[kSensitiveHeaders];
25762637if (neverIndex !== undefined && !ArrayIsArray(neverIndex))
25772638throw new ERR_INVALID_ARG_VALUE('headers[http2.neverIndex]', neverIndex);
2578-2579-return headers;
25802639}
258126402582-25832641function onFileUnpipe() {
25842642const stream = this.sink[kOwner];
25852643if (stream.ownsFd)
@@ -2882,7 +2940,7 @@ class ServerHttp2Stream extends Http2Stream {
28822940}
2883294128842942// Initiate a response on this Http2Stream
2885-respond(headers, options) {
2943+respond(headersParam, options) {
28862944if (this.destroyed || this.closed)
28872945throw new ERR_HTTP2_INVALID_STREAM();
28882946if (this.headersSent)
@@ -2907,15 +2965,16 @@ class ServerHttp2Stream extends Http2Stream {
29072965state.flags |= STREAM_FLAGS_HAS_TRAILERS;
29082966}
290929672910-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);
2913297329142974state.flags |= STREAM_FLAGS_HEADERS_SENT;
2915297529162976// Close the writable side if the endStream option is set or status
29172977// is one of known codes with no payload, or it's a head request
2918-const statusCode = headers[HTTP2_HEADER_STATUS] | 0;
29192978if (!!options.endStream ||
29202979statusCode === HTTP_STATUS_NO_CONTENT ||
29212980statusCode === HTTP_STATUS_RESET_CONTENT ||
@@ -2945,7 +3004,7 @@ class ServerHttp2Stream extends Http2Stream {
29453004// regular file, here the fd is passed directly. If the underlying
29463005// mechanism is not able to read from the fd, then the stream will be
29473006// reset with an error code.
2948-respondWithFD(fd, headers, options) {
3007+respondWithFD(fd, headersParam, options) {
29493008if (this.destroyed || this.closed)
29503009throw new ERR_HTTP2_INVALID_STREAM();
29513010if (this.headersSent)
@@ -2982,8 +3041,11 @@ class ServerHttp2Stream extends Http2Stream {
29823041this[kUpdateTimer]();
29833042this.ownsFd = false;
298430432985-headers = processHeaders(headers, options);
2986-const statusCode = headers[HTTP2_HEADER_STATUS] |= 0;
3044+const {
3045+ headers,
3046+ statusCode,
3047+} = prepareResponseHeadersObject(headersParam, options);
3048+29873049// Payload/DATA frames are not permitted in these cases
29883050if (statusCode === HTTP_STATUS_NO_CONTENT ||
29893051statusCode === HTTP_STATUS_RESET_CONTENT ||
@@ -3011,7 +3073,7 @@ class ServerHttp2Stream extends Http2Stream {
30113073// giving the user an opportunity to verify the details and set additional
30123074// headers. If statCheck returns false, the operation is aborted and no
30133075// file details are sent.
3014-respondWithFile(path, headers, options) {
3076+respondWithFile(path, headersParam, options) {
30153077if (this.destroyed || this.closed)
30163078throw new ERR_HTTP2_INVALID_STREAM();
30173079if (this.headersSent)
@@ -3042,8 +3104,11 @@ class ServerHttp2Stream extends Http2Stream {
30423104this[kUpdateTimer]();
30433105this.ownsFd = true;
304431063045-headers = processHeaders(headers, options);
3046-const statusCode = headers[HTTP2_HEADER_STATUS] |= 0;
3107+const {
3108+ headers,
3109+ statusCode,
3110+} = prepareResponseHeadersObject(headersParam, options);
3111+30473112// Payload/DATA frames are not permitted in these cases
30483113if (statusCode === HTTP_STATUS_NO_CONTENT ||
30493114statusCode === HTTP_STATUS_RESET_CONTENT ||