• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayIsArray,
5  ArrayPrototypeIncludes,
6  ArrayPrototypeMap,
7  ArrayPrototypePush,
8  Error,
9  MathMax,
10  Number,
11  ObjectCreate,
12  ObjectDefineProperty,
13  ObjectKeys,
14  SafeSet,
15  String,
16  StringFromCharCode,
17  StringPrototypeIncludes,
18  StringPrototypeToLowerCase,
19  Symbol,
20} = primordials;
21
22const binding = internalBinding('http2');
23const {
24  codes: {
25    ERR_HTTP2_HEADER_SINGLE_VALUE,
26    ERR_HTTP2_INVALID_CONNECTION_HEADERS,
27    ERR_HTTP2_INVALID_PSEUDOHEADER,
28    ERR_HTTP2_INVALID_SETTING_VALUE,
29    ERR_INVALID_ARG_TYPE,
30    ERR_INVALID_HTTP_TOKEN,
31  },
32  captureLargerStackTrace,
33  getMessage,
34  hideStackFrames,
35  kIsNodeError,
36} = require('internal/errors');
37
38const kSensitiveHeaders = Symbol('nodejs.http2.sensitiveHeaders');
39const kSocket = Symbol('socket');
40const kProxySocket = Symbol('proxySocket');
41const kRequest = Symbol('request');
42
43const {
44  NGHTTP2_NV_FLAG_NONE,
45  NGHTTP2_NV_FLAG_NO_INDEX,
46  NGHTTP2_SESSION_CLIENT,
47  NGHTTP2_SESSION_SERVER,
48
49  HTTP2_HEADER_STATUS,
50  HTTP2_HEADER_METHOD,
51  HTTP2_HEADER_AUTHORITY,
52  HTTP2_HEADER_SCHEME,
53  HTTP2_HEADER_PATH,
54  HTTP2_HEADER_PROTOCOL,
55  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
56  HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,
57  HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
58  HTTP2_HEADER_AGE,
59  HTTP2_HEADER_AUTHORIZATION,
60  HTTP2_HEADER_CONTENT_ENCODING,
61  HTTP2_HEADER_CONTENT_LANGUAGE,
62  HTTP2_HEADER_CONTENT_LENGTH,
63  HTTP2_HEADER_CONTENT_LOCATION,
64  HTTP2_HEADER_CONTENT_MD5,
65  HTTP2_HEADER_CONTENT_RANGE,
66  HTTP2_HEADER_CONTENT_TYPE,
67  HTTP2_HEADER_COOKIE,
68  HTTP2_HEADER_DATE,
69  HTTP2_HEADER_DNT,
70  HTTP2_HEADER_ETAG,
71  HTTP2_HEADER_EXPIRES,
72  HTTP2_HEADER_FROM,
73  HTTP2_HEADER_HOST,
74  HTTP2_HEADER_IF_MATCH,
75  HTTP2_HEADER_IF_NONE_MATCH,
76  HTTP2_HEADER_IF_MODIFIED_SINCE,
77  HTTP2_HEADER_IF_RANGE,
78  HTTP2_HEADER_IF_UNMODIFIED_SINCE,
79  HTTP2_HEADER_LAST_MODIFIED,
80  HTTP2_HEADER_LOCATION,
81  HTTP2_HEADER_MAX_FORWARDS,
82  HTTP2_HEADER_PROXY_AUTHORIZATION,
83  HTTP2_HEADER_RANGE,
84  HTTP2_HEADER_REFERER,
85  HTTP2_HEADER_RETRY_AFTER,
86  HTTP2_HEADER_SET_COOKIE,
87  HTTP2_HEADER_TK,
88  HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS,
89  HTTP2_HEADER_USER_AGENT,
90  HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS,
91
92  HTTP2_HEADER_CONNECTION,
93  HTTP2_HEADER_UPGRADE,
94  HTTP2_HEADER_HTTP2_SETTINGS,
95  HTTP2_HEADER_TE,
96  HTTP2_HEADER_TRANSFER_ENCODING,
97  HTTP2_HEADER_KEEP_ALIVE,
98  HTTP2_HEADER_PROXY_CONNECTION,
99
100  HTTP2_METHOD_DELETE,
101  HTTP2_METHOD_GET,
102  HTTP2_METHOD_HEAD,
103} = binding.constants;
104
105// This set is defined strictly by the HTTP/2 specification. Only
106// :-prefixed headers defined by that specification may be added to
107// this set.
108const kValidPseudoHeaders = new SafeSet([
109  HTTP2_HEADER_STATUS,
110  HTTP2_HEADER_METHOD,
111  HTTP2_HEADER_AUTHORITY,
112  HTTP2_HEADER_SCHEME,
113  HTTP2_HEADER_PATH,
114  HTTP2_HEADER_PROTOCOL,
115]);
116
117// This set contains headers that are permitted to have only a single
118// value. Multiple instances must not be specified.
119const kSingleValueHeaders = new SafeSet([
120  HTTP2_HEADER_STATUS,
121  HTTP2_HEADER_METHOD,
122  HTTP2_HEADER_AUTHORITY,
123  HTTP2_HEADER_SCHEME,
124  HTTP2_HEADER_PATH,
125  HTTP2_HEADER_PROTOCOL,
126  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
127  HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,
128  HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
129  HTTP2_HEADER_AGE,
130  HTTP2_HEADER_AUTHORIZATION,
131  HTTP2_HEADER_CONTENT_ENCODING,
132  HTTP2_HEADER_CONTENT_LANGUAGE,
133  HTTP2_HEADER_CONTENT_LENGTH,
134  HTTP2_HEADER_CONTENT_LOCATION,
135  HTTP2_HEADER_CONTENT_MD5,
136  HTTP2_HEADER_CONTENT_RANGE,
137  HTTP2_HEADER_CONTENT_TYPE,
138  HTTP2_HEADER_DATE,
139  HTTP2_HEADER_DNT,
140  HTTP2_HEADER_ETAG,
141  HTTP2_HEADER_EXPIRES,
142  HTTP2_HEADER_FROM,
143  HTTP2_HEADER_HOST,
144  HTTP2_HEADER_IF_MATCH,
145  HTTP2_HEADER_IF_MODIFIED_SINCE,
146  HTTP2_HEADER_IF_NONE_MATCH,
147  HTTP2_HEADER_IF_RANGE,
148  HTTP2_HEADER_IF_UNMODIFIED_SINCE,
149  HTTP2_HEADER_LAST_MODIFIED,
150  HTTP2_HEADER_LOCATION,
151  HTTP2_HEADER_MAX_FORWARDS,
152  HTTP2_HEADER_PROXY_AUTHORIZATION,
153  HTTP2_HEADER_RANGE,
154  HTTP2_HEADER_REFERER,
155  HTTP2_HEADER_RETRY_AFTER,
156  HTTP2_HEADER_TK,
157  HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS,
158  HTTP2_HEADER_USER_AGENT,
159  HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS,
160]);
161
162// The HTTP methods in this set are specifically defined as assigning no
163// meaning to the request payload. By default, unless the user explicitly
164// overrides the endStream option on the request method, the endStream
165// option will be defaulted to true when these methods are used.
166const kNoPayloadMethods = new SafeSet([
167  HTTP2_METHOD_DELETE,
168  HTTP2_METHOD_GET,
169  HTTP2_METHOD_HEAD,
170]);
171
172// The following ArrayBuffer instances are used to share memory more efficiently
173// with the native binding side for a number of methods. These are not intended
174// to be used directly by users in any way. The ArrayBuffers are created on
175// the native side with values that are filled in on demand, the js code then
176// reads those values out. The set of IDX constants that follow identify the
177// relevant data positions within these buffers.
178const { settingsBuffer, optionsBuffer } = binding;
179
180// Note that Float64Array is used here because there is no Int64Array available
181// and these deal with numbers that can be beyond the range of Uint32 and Int32.
182// The values set on the native side will always be integers. This is not a
183// unique example of this, this pattern can be found in use in other parts of
184// Node.js core as a performance optimization.
185const { sessionState, streamState } = binding;
186
187const IDX_SETTINGS_HEADER_TABLE_SIZE = 0;
188const IDX_SETTINGS_ENABLE_PUSH = 1;
189const IDX_SETTINGS_INITIAL_WINDOW_SIZE = 2;
190const IDX_SETTINGS_MAX_FRAME_SIZE = 3;
191const IDX_SETTINGS_MAX_CONCURRENT_STREAMS = 4;
192const IDX_SETTINGS_MAX_HEADER_LIST_SIZE = 5;
193const IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL = 6;
194const IDX_SETTINGS_FLAGS = 7;
195
196const IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE = 0;
197const IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH = 1;
198const IDX_SESSION_STATE_NEXT_STREAM_ID = 2;
199const IDX_SESSION_STATE_LOCAL_WINDOW_SIZE = 3;
200const IDX_SESSION_STATE_LAST_PROC_STREAM_ID = 4;
201const IDX_SESSION_STATE_REMOTE_WINDOW_SIZE = 5;
202const IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE = 6;
203const IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE = 7;
204const IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE = 8;
205const IDX_STREAM_STATE = 0;
206const IDX_STREAM_STATE_WEIGHT = 1;
207const IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT = 2;
208const IDX_STREAM_STATE_LOCAL_CLOSE = 3;
209const IDX_STREAM_STATE_REMOTE_CLOSE = 4;
210const IDX_STREAM_STATE_LOCAL_WINDOW_SIZE = 5;
211
212const IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 0;
213const IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS = 1;
214const IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH = 2;
215const IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS = 3;
216const IDX_OPTIONS_PADDING_STRATEGY = 4;
217const IDX_OPTIONS_MAX_HEADER_LIST_PAIRS = 5;
218const IDX_OPTIONS_MAX_OUTSTANDING_PINGS = 6;
219const IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS = 7;
220const IDX_OPTIONS_MAX_SESSION_MEMORY = 8;
221const IDX_OPTIONS_MAX_SETTINGS = 9;
222const IDX_OPTIONS_FLAGS = 10;
223
224function updateOptionsBuffer(options) {
225  let flags = 0;
226  if (typeof options.maxDeflateDynamicTableSize === 'number') {
227    flags |= (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE);
228    optionsBuffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE] =
229      options.maxDeflateDynamicTableSize;
230  }
231  if (typeof options.maxReservedRemoteStreams === 'number') {
232    flags |= (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS);
233    optionsBuffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS] =
234      options.maxReservedRemoteStreams;
235  }
236  if (typeof options.maxSendHeaderBlockLength === 'number') {
237    flags |= (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH);
238    optionsBuffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH] =
239      options.maxSendHeaderBlockLength;
240  }
241  if (typeof options.peerMaxConcurrentStreams === 'number') {
242    flags |= (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS);
243    optionsBuffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS] =
244      options.peerMaxConcurrentStreams;
245  }
246  if (typeof options.paddingStrategy === 'number') {
247    flags |= (1 << IDX_OPTIONS_PADDING_STRATEGY);
248    optionsBuffer[IDX_OPTIONS_PADDING_STRATEGY] =
249      options.paddingStrategy;
250  }
251  if (typeof options.maxHeaderListPairs === 'number') {
252    flags |= (1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS);
253    optionsBuffer[IDX_OPTIONS_MAX_HEADER_LIST_PAIRS] =
254      options.maxHeaderListPairs;
255  }
256  if (typeof options.maxOutstandingPings === 'number') {
257    flags |= (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS);
258    optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS] =
259      options.maxOutstandingPings;
260  }
261  if (typeof options.maxOutstandingSettings === 'number') {
262    flags |= (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS);
263    optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS] =
264      MathMax(1, options.maxOutstandingSettings);
265  }
266  if (typeof options.maxSessionMemory === 'number') {
267    flags |= (1 << IDX_OPTIONS_MAX_SESSION_MEMORY);
268    optionsBuffer[IDX_OPTIONS_MAX_SESSION_MEMORY] =
269      MathMax(1, options.maxSessionMemory);
270  }
271  if (typeof options.maxSettings === 'number') {
272    flags |= (1 << IDX_OPTIONS_MAX_SETTINGS);
273    optionsBuffer[IDX_OPTIONS_MAX_SETTINGS] =
274      MathMax(1, options.maxSettings);
275  }
276  optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
277}
278
279function getDefaultSettings() {
280  settingsBuffer[IDX_SETTINGS_FLAGS] = 0;
281  binding.refreshDefaultSettings();
282  const holder = ObjectCreate(null);
283
284  const flags = settingsBuffer[IDX_SETTINGS_FLAGS];
285
286  if ((flags & (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) ===
287      (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) {
288    holder.headerTableSize =
289      settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE];
290  }
291
292  if ((flags & (1 << IDX_SETTINGS_ENABLE_PUSH)) ===
293      (1 << IDX_SETTINGS_ENABLE_PUSH)) {
294    holder.enablePush =
295      settingsBuffer[IDX_SETTINGS_ENABLE_PUSH] === 1;
296  }
297
298  if ((flags & (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) ===
299      (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) {
300    holder.initialWindowSize =
301      settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE];
302  }
303
304  if ((flags & (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) ===
305      (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) {
306    holder.maxFrameSize =
307      settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE];
308  }
309
310  if ((flags & (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) ===
311      (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) {
312    holder.maxConcurrentStreams =
313      settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS];
314  }
315
316  if ((flags & (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) ===
317      (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) {
318    holder.maxHeaderListSize = holder.maxHeaderSize =
319      settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE];
320  }
321
322  if ((flags & (1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL)) ===
323      (1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL)) {
324    holder.enableConnectProtocol =
325      settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] === 1;
326  }
327
328  return holder;
329}
330
331// Remote is a boolean. true to fetch remote settings, false to fetch local.
332// this is only called internally
333function getSettings(session, remote) {
334  if (remote)
335    session.remoteSettings();
336  else
337    session.localSettings();
338
339  return {
340    headerTableSize: settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE],
341    enablePush: !!settingsBuffer[IDX_SETTINGS_ENABLE_PUSH],
342    initialWindowSize: settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE],
343    maxFrameSize: settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE],
344    maxConcurrentStreams: settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS],
345    maxHeaderListSize: settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE],
346    maxHeaderSize: settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE],
347    enableConnectProtocol:
348      !!settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL],
349  };
350}
351
352function updateSettingsBuffer(settings) {
353  let flags = 0;
354  if (typeof settings.headerTableSize === 'number') {
355    flags |= (1 << IDX_SETTINGS_HEADER_TABLE_SIZE);
356    settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
357      settings.headerTableSize;
358  }
359  if (typeof settings.maxConcurrentStreams === 'number') {
360    flags |= (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS);
361    settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] =
362      settings.maxConcurrentStreams;
363  }
364  if (typeof settings.initialWindowSize === 'number') {
365    flags |= (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE);
366    settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
367      settings.initialWindowSize;
368  }
369  if (typeof settings.maxFrameSize === 'number') {
370    flags |= (1 << IDX_SETTINGS_MAX_FRAME_SIZE);
371    settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
372      settings.maxFrameSize;
373  }
374  if (typeof settings.maxHeaderListSize === 'number' ||
375      typeof settings.maxHeaderSize === 'number') {
376    flags |= (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE);
377    if (settings.maxHeaderSize !== undefined &&
378      (settings.maxHeaderSize !== settings.maxHeaderListSize)) {
379      process.emitWarning(
380        'settings.maxHeaderSize overwrite settings.maxHeaderListSize',
381      );
382      settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
383        settings.maxHeaderSize;
384    } else {
385      settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
386        settings.maxHeaderListSize;
387    }
388  }
389  if (typeof settings.enablePush === 'boolean') {
390    flags |= (1 << IDX_SETTINGS_ENABLE_PUSH);
391    settingsBuffer[IDX_SETTINGS_ENABLE_PUSH] = Number(settings.enablePush);
392  }
393  if (typeof settings.enableConnectProtocol === 'boolean') {
394    flags |= (1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL);
395    settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] =
396      Number(settings.enableConnectProtocol);
397  }
398
399  settingsBuffer[IDX_SETTINGS_FLAGS] = flags;
400}
401
402function getSessionState(session) {
403  session.refreshState();
404  return {
405    effectiveLocalWindowSize:
406      sessionState[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE],
407    effectiveRecvDataLength:
408      sessionState[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH],
409    nextStreamID:
410      sessionState[IDX_SESSION_STATE_NEXT_STREAM_ID],
411    localWindowSize:
412      sessionState[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE],
413    lastProcStreamID:
414      sessionState[IDX_SESSION_STATE_LAST_PROC_STREAM_ID],
415    remoteWindowSize:
416      sessionState[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE],
417    outboundQueueSize:
418      sessionState[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE],
419    deflateDynamicTableSize:
420      sessionState[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE],
421    inflateDynamicTableSize:
422      sessionState[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE],
423  };
424}
425
426function getStreamState(stream) {
427  stream.refreshState();
428  return {
429    state: streamState[IDX_STREAM_STATE],
430    weight: streamState[IDX_STREAM_STATE_WEIGHT],
431    sumDependencyWeight: streamState[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT],
432    localClose: streamState[IDX_STREAM_STATE_LOCAL_CLOSE],
433    remoteClose: streamState[IDX_STREAM_STATE_REMOTE_CLOSE],
434    localWindowSize: streamState[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE],
435  };
436}
437
438function isIllegalConnectionSpecificHeader(name, value) {
439  switch (name) {
440    case HTTP2_HEADER_CONNECTION:
441    case HTTP2_HEADER_UPGRADE:
442    case HTTP2_HEADER_HTTP2_SETTINGS:
443    case HTTP2_HEADER_KEEP_ALIVE:
444    case HTTP2_HEADER_PROXY_CONNECTION:
445    case HTTP2_HEADER_TRANSFER_ENCODING:
446      return true;
447    case HTTP2_HEADER_TE:
448      return value !== 'trailers';
449    default:
450      return false;
451  }
452}
453
454const assertValidPseudoHeader = hideStackFrames((key) => {
455  if (!kValidPseudoHeaders.has(key)) {
456    throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
457  }
458});
459
460const assertValidPseudoHeaderResponse = hideStackFrames((key) => {
461  if (key !== ':status') {
462    throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
463  }
464});
465
466const assertValidPseudoHeaderTrailer = hideStackFrames((key) => {
467  throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
468});
469
470const emptyArray = [];
471const kNeverIndexFlag = StringFromCharCode(NGHTTP2_NV_FLAG_NO_INDEX);
472const kNoHeaderFlags = StringFromCharCode(NGHTTP2_NV_FLAG_NONE);
473function mapToHeaders(map,
474                      assertValuePseudoHeader = assertValidPseudoHeader) {
475  let headers = '';
476  let pseudoHeaders = '';
477  let count = 0;
478  const keys = ObjectKeys(map);
479  const singles = new SafeSet();
480  let i, j;
481  let isArray;
482  let key;
483  let value;
484  let isSingleValueHeader;
485  let err;
486  const neverIndex =
487    ArrayPrototypeMap(map[kSensitiveHeaders] || emptyArray,
488                      StringPrototypeToLowerCase);
489  for (i = 0; i < keys.length; ++i) {
490    key = keys[i];
491    value = map[key];
492    if (value === undefined || key === '')
493      continue;
494    key = StringPrototypeToLowerCase(key);
495    isSingleValueHeader = kSingleValueHeaders.has(key);
496    isArray = ArrayIsArray(value);
497    if (isArray) {
498      switch (value.length) {
499        case 0:
500          continue;
501        case 1:
502          value = String(value[0]);
503          isArray = false;
504          break;
505        default:
506          if (isSingleValueHeader)
507            throw new ERR_HTTP2_HEADER_SINGLE_VALUE(key);
508      }
509    } else {
510      value = String(value);
511    }
512    if (isSingleValueHeader) {
513      if (singles.has(key))
514        throw new ERR_HTTP2_HEADER_SINGLE_VALUE(key);
515      singles.add(key);
516    }
517    const flags = ArrayPrototypeIncludes(neverIndex, key) ?
518      kNeverIndexFlag :
519      kNoHeaderFlags;
520    if (key[0] === ':') {
521      err = assertValuePseudoHeader(key);
522      if (err !== undefined)
523        throw err;
524      pseudoHeaders += `${key}\0${value}\0${flags}`;
525      count++;
526      continue;
527    }
528    if (StringPrototypeIncludes(key, ' ')) {
529      throw new ERR_INVALID_HTTP_TOKEN('Header name', key);
530    }
531    if (isIllegalConnectionSpecificHeader(key, value)) {
532      throw new ERR_HTTP2_INVALID_CONNECTION_HEADERS(key);
533    }
534    if (isArray) {
535      for (j = 0; j < value.length; ++j) {
536        const val = String(value[j]);
537        headers += `${key}\0${val}\0${flags}`;
538      }
539      count += value.length;
540      continue;
541    }
542    headers += `${key}\0${value}\0${flags}`;
543    count++;
544  }
545
546  return [pseudoHeaders + headers, count];
547}
548
549class NghttpError extends Error {
550  constructor(integerCode, customErrorCode) {
551    super(customErrorCode ?
552      getMessage(customErrorCode, [], null) :
553      binding.nghttp2ErrorString(integerCode));
554    this.code = customErrorCode || 'ERR_HTTP2_ERROR';
555    this.errno = integerCode;
556    captureLargerStackTrace(this);
557    ObjectDefineProperty(this, kIsNodeError, {
558      __proto__: null,
559      value: true,
560      enumerable: false,
561      writable: false,
562      configurable: true,
563    });
564  }
565
566  toString() {
567    return `${this.name} [${this.code}]: ${this.message}`;
568  }
569}
570
571const assertIsObject = hideStackFrames((value, name, types) => {
572  if (value !== undefined &&
573      (value === null ||
574       typeof value !== 'object' ||
575       ArrayIsArray(value))) {
576    throw new ERR_INVALID_ARG_TYPE(name, types || 'Object', value);
577  }
578});
579
580const assertWithinRange = hideStackFrames(
581  (name, value, min = 0, max = Infinity) => {
582    if (value !== undefined &&
583      (typeof value !== 'number' || value < min || value > max)) {
584      throw new ERR_HTTP2_INVALID_SETTING_VALUE.RangeError(
585        name, value, min, max);
586    }
587  },
588);
589
590function toHeaderObject(headers, sensitiveHeaders) {
591  const obj = ObjectCreate(null);
592  for (let n = 0; n < headers.length; n += 2) {
593    const name = headers[n];
594    let value = headers[n + 1];
595    if (name === HTTP2_HEADER_STATUS)
596      value |= 0;
597    const existing = obj[name];
598    if (existing === undefined) {
599      obj[name] = name === HTTP2_HEADER_SET_COOKIE ? [value] : value;
600    } else if (!kSingleValueHeaders.has(name)) {
601      switch (name) {
602        case HTTP2_HEADER_COOKIE:
603          // https://tools.ietf.org/html/rfc7540#section-8.1.2.5
604          // "...If there are multiple Cookie header fields after decompression,
605          //  these MUST be concatenated into a single octet string using the
606          //  two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ") before
607          //  being passed into a non-HTTP/2 context."
608          obj[name] = `${existing}; ${value}`;
609          break;
610        case HTTP2_HEADER_SET_COOKIE:
611          // https://tools.ietf.org/html/rfc7230#section-3.2.2
612          // "Note: In practice, the "Set-Cookie" header field ([RFC6265]) often
613          // appears multiple times in a response message and does not use the
614          // list syntax, violating the above requirements on multiple header
615          // fields with the same name.  Since it cannot be combined into a
616          // single field-value, recipients ought to handle "Set-Cookie" as a
617          // special case while processing header fields."
618          ArrayPrototypePush(existing, value);
619          break;
620        default:
621          // https://tools.ietf.org/html/rfc7230#section-3.2.2
622          // "A recipient MAY combine multiple header fields with the same field
623          // name into one "field-name: field-value" pair, without changing the
624          // semantics of the message, by appending each subsequent field value
625          // to the combined field value in order, separated by a comma."
626          obj[name] = `${existing}, ${value}`;
627          break;
628      }
629    }
630  }
631  obj[kSensitiveHeaders] = sensitiveHeaders;
632  return obj;
633}
634
635function isPayloadMeaningless(method) {
636  return kNoPayloadMethods.has(method);
637}
638
639function sessionName(type) {
640  switch (type) {
641    case NGHTTP2_SESSION_CLIENT:
642      return 'client';
643    case NGHTTP2_SESSION_SERVER:
644      return 'server';
645    default:
646      return '<invalid>';
647  }
648}
649
650function getAuthority(headers) {
651  // For non-CONNECT requests, HTTP/2 allows either :authority
652  // or Host to be used equivalently. The first is preferred
653  // when making HTTP/2 requests, and the latter is preferred
654  // when converting from an HTTP/1 message.
655  if (headers[HTTP2_HEADER_AUTHORITY] !== undefined)
656    return headers[HTTP2_HEADER_AUTHORITY];
657  if (headers[HTTP2_HEADER_HOST] !== undefined)
658    return headers[HTTP2_HEADER_HOST];
659}
660
661module.exports = {
662  assertIsObject,
663  assertValidPseudoHeader,
664  assertValidPseudoHeaderResponse,
665  assertValidPseudoHeaderTrailer,
666  assertWithinRange,
667  getAuthority,
668  getDefaultSettings,
669  getSessionState,
670  getSettings,
671  getStreamState,
672  isPayloadMeaningless,
673  kSensitiveHeaders,
674  kSocket,
675  kProxySocket,
676  kRequest,
677  mapToHeaders,
678  NghttpError,
679  sessionName,
680  toHeaderObject,
681  updateOptionsBuffer,
682  updateSettingsBuffer,
683};
684