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