network.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.WebSocket = exports.STATUS_TEXTS = exports.Route = exports.Response = exports.Request = void 0;
  6. exports.filterCookies = filterCookies;
  7. exports.kMaxCookieExpiresDateInSeconds = void 0;
  8. exports.mergeHeaders = mergeHeaders;
  9. exports.parsedURL = parsedURL;
  10. exports.rewriteCookies = rewriteCookies;
  11. exports.singleHeader = singleHeader;
  12. exports.stripFragmentFromUrl = stripFragmentFromUrl;
  13. var _utils = require("../utils");
  14. var _manualPromise = require("../utils/manualPromise");
  15. var _instrumentation = require("./instrumentation");
  16. var _fetch = require("./fetch");
  17. var _browserContext = require("./browserContext");
  18. /**
  19. * Copyright (c) Microsoft Corporation.
  20. *
  21. * Licensed under the Apache License, Version 2.0 (the "License");
  22. * you may not use this file except in compliance with the License.
  23. * You may obtain a copy of the License at
  24. *
  25. * http://www.apache.org/licenses/LICENSE-2.0
  26. *
  27. * Unless required by applicable law or agreed to in writing, software
  28. * distributed under the License is distributed on an "AS IS" BASIS,
  29. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30. * See the License for the specific language governing permissions and
  31. * limitations under the License.
  32. */
  33. function filterCookies(cookies, urls) {
  34. const parsedURLs = urls.map(s => new URL(s));
  35. // Chromiums's cookies are missing sameSite when it is 'None'
  36. return cookies.filter(c => {
  37. if (!parsedURLs.length) return true;
  38. for (const parsedURL of parsedURLs) {
  39. let domain = c.domain;
  40. if (!domain.startsWith('.')) domain = '.' + domain;
  41. if (!('.' + parsedURL.hostname).endsWith(domain)) continue;
  42. if (!parsedURL.pathname.startsWith(c.path)) continue;
  43. if (parsedURL.protocol !== 'https:' && parsedURL.hostname !== 'localhost' && c.secure) continue;
  44. return true;
  45. }
  46. return false;
  47. });
  48. }
  49. // Rollover to 5-digit year:
  50. // 253402300799 == Fri, 31 Dec 9999 23:59:59 +0000 (UTC)
  51. // 253402300800 == Sat, 1 Jan 1000 00:00:00 +0000 (UTC)
  52. const kMaxCookieExpiresDateInSeconds = exports.kMaxCookieExpiresDateInSeconds = 253402300799;
  53. function rewriteCookies(cookies) {
  54. return cookies.map(c => {
  55. (0, _utils.assert)(c.url || c.domain && c.path, 'Cookie should have a url or a domain/path pair');
  56. (0, _utils.assert)(!(c.url && c.domain), 'Cookie should have either url or domain');
  57. (0, _utils.assert)(!(c.url && c.path), 'Cookie should have either url or path');
  58. (0, _utils.assert)(!(c.expires && c.expires < 0 && c.expires !== -1), 'Cookie should have a valid expires, only -1 or a positive number for the unix timestamp in seconds is allowed');
  59. (0, _utils.assert)(!(c.expires && c.expires > 0 && c.expires > kMaxCookieExpiresDateInSeconds), 'Cookie should have a valid expires, only -1 or a positive number for the unix timestamp in seconds is allowed');
  60. const copy = {
  61. ...c
  62. };
  63. if (copy.url) {
  64. (0, _utils.assert)(copy.url !== 'about:blank', `Blank page can not have cookie "${c.name}"`);
  65. (0, _utils.assert)(!copy.url.startsWith('data:'), `Data URL page can not have cookie "${c.name}"`);
  66. const url = new URL(copy.url);
  67. copy.domain = url.hostname;
  68. copy.path = url.pathname.substring(0, url.pathname.lastIndexOf('/') + 1);
  69. copy.secure = url.protocol === 'https:';
  70. }
  71. return copy;
  72. });
  73. }
  74. function parsedURL(url) {
  75. try {
  76. return new URL(url);
  77. } catch (e) {
  78. return null;
  79. }
  80. }
  81. function stripFragmentFromUrl(url) {
  82. if (!url.includes('#')) return url;
  83. return url.substring(0, url.indexOf('#'));
  84. }
  85. class Request extends _instrumentation.SdkObject {
  86. constructor(context, frame, serviceWorker, redirectedFrom, documentId, url, resourceType, method, postData, headers) {
  87. super(frame || context, 'request');
  88. this._response = null;
  89. this._redirectedFrom = void 0;
  90. this._redirectedTo = null;
  91. this._documentId = void 0;
  92. this._isFavicon = void 0;
  93. this._failureText = null;
  94. this._url = void 0;
  95. this._resourceType = void 0;
  96. this._method = void 0;
  97. this._postData = void 0;
  98. this._headers = void 0;
  99. this._headersMap = new Map();
  100. this._frame = null;
  101. this._serviceWorker = null;
  102. this._context = void 0;
  103. this._rawRequestHeadersPromise = new _manualPromise.ManualPromise();
  104. this._waitForResponsePromise = new _manualPromise.ManualPromise();
  105. this._responseEndTiming = -1;
  106. this._overrides = void 0;
  107. (0, _utils.assert)(!url.startsWith('data:'), 'Data urls should not fire requests');
  108. this._context = context;
  109. this._frame = frame;
  110. this._serviceWorker = serviceWorker;
  111. this._redirectedFrom = redirectedFrom;
  112. if (redirectedFrom) redirectedFrom._redirectedTo = this;
  113. this._documentId = documentId;
  114. this._url = stripFragmentFromUrl(url);
  115. this._resourceType = resourceType;
  116. this._method = method;
  117. this._postData = postData;
  118. this._headers = headers;
  119. this._updateHeadersMap();
  120. this._isFavicon = url.endsWith('/favicon.ico') || !!(redirectedFrom !== null && redirectedFrom !== void 0 && redirectedFrom._isFavicon);
  121. }
  122. _setFailureText(failureText) {
  123. this._failureText = failureText;
  124. this._waitForResponsePromise.resolve(null);
  125. }
  126. _setOverrides(overrides) {
  127. this._overrides = overrides;
  128. this._updateHeadersMap();
  129. }
  130. _updateHeadersMap() {
  131. for (const {
  132. name,
  133. value
  134. } of this.headers()) this._headersMap.set(name.toLowerCase(), value);
  135. }
  136. _hasOverrides() {
  137. return !!this._overrides;
  138. }
  139. url() {
  140. var _this$_overrides;
  141. return ((_this$_overrides = this._overrides) === null || _this$_overrides === void 0 ? void 0 : _this$_overrides.url) || this._url;
  142. }
  143. resourceType() {
  144. return this._resourceType;
  145. }
  146. method() {
  147. var _this$_overrides2;
  148. return ((_this$_overrides2 = this._overrides) === null || _this$_overrides2 === void 0 ? void 0 : _this$_overrides2.method) || this._method;
  149. }
  150. postDataBuffer() {
  151. var _this$_overrides3;
  152. return ((_this$_overrides3 = this._overrides) === null || _this$_overrides3 === void 0 ? void 0 : _this$_overrides3.postData) || this._postData;
  153. }
  154. headers() {
  155. var _this$_overrides4;
  156. return ((_this$_overrides4 = this._overrides) === null || _this$_overrides4 === void 0 ? void 0 : _this$_overrides4.headers) || this._headers;
  157. }
  158. headerValue(name) {
  159. return this._headersMap.get(name);
  160. }
  161. // "null" means no raw headers available - we'll use provisional headers as raw headers.
  162. setRawRequestHeaders(headers) {
  163. if (!this._rawRequestHeadersPromise.isDone()) this._rawRequestHeadersPromise.resolve(headers || this._headers);
  164. }
  165. async rawRequestHeaders() {
  166. var _this$_overrides5;
  167. return ((_this$_overrides5 = this._overrides) === null || _this$_overrides5 === void 0 ? void 0 : _this$_overrides5.headers) || this._rawRequestHeadersPromise;
  168. }
  169. response() {
  170. return this._waitForResponsePromise;
  171. }
  172. _existingResponse() {
  173. return this._response;
  174. }
  175. _setResponse(response) {
  176. this._response = response;
  177. this._waitForResponsePromise.resolve(response);
  178. }
  179. _finalRequest() {
  180. return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
  181. }
  182. frame() {
  183. return this._frame;
  184. }
  185. serviceWorker() {
  186. return this._serviceWorker;
  187. }
  188. isNavigationRequest() {
  189. return !!this._documentId;
  190. }
  191. redirectedFrom() {
  192. return this._redirectedFrom;
  193. }
  194. failure() {
  195. if (this._failureText === null) return null;
  196. return {
  197. errorText: this._failureText
  198. };
  199. }
  200. bodySize() {
  201. var _this$postDataBuffer;
  202. return ((_this$postDataBuffer = this.postDataBuffer()) === null || _this$postDataBuffer === void 0 ? void 0 : _this$postDataBuffer.length) || 0;
  203. }
  204. async requestHeadersSize() {
  205. let headersSize = 4; // 4 = 2 spaces + 2 line breaks (GET /path \r\n)
  206. headersSize += this.method().length;
  207. headersSize += new URL(this.url()).pathname.length;
  208. headersSize += 8; // httpVersion
  209. const headers = await this.rawRequestHeaders();
  210. for (const header of headers) headersSize += header.name.length + header.value.length + 4; // 4 = ': ' + '\r\n'
  211. return headersSize;
  212. }
  213. }
  214. exports.Request = Request;
  215. class Route extends _instrumentation.SdkObject {
  216. constructor(request, delegate) {
  217. super(request._frame || request._context, 'route');
  218. this._request = void 0;
  219. this._delegate = void 0;
  220. this._handled = false;
  221. this._request = request;
  222. this._delegate = delegate;
  223. this._request._context.addRouteInFlight(this);
  224. }
  225. request() {
  226. return this._request;
  227. }
  228. async abort(errorCode = 'failed') {
  229. this._startHandling();
  230. this._request._context.emit(_browserContext.BrowserContext.Events.RequestAborted, this._request);
  231. await this._delegate.abort(errorCode);
  232. this._endHandling();
  233. }
  234. async redirectNavigationRequest(url) {
  235. this._startHandling();
  236. (0, _utils.assert)(this._request.isNavigationRequest());
  237. this._request.frame().redirectNavigation(url, this._request._documentId, this._request.headerValue('referer'));
  238. }
  239. async fulfill(overrides) {
  240. this._startHandling();
  241. let body = overrides.body;
  242. let isBase64 = overrides.isBase64 || false;
  243. if (body === undefined) {
  244. if (overrides.fetchResponseUid) {
  245. const buffer = this._request._context.fetchRequest.fetchResponses.get(overrides.fetchResponseUid) || _fetch.APIRequestContext.findResponseBody(overrides.fetchResponseUid);
  246. (0, _utils.assert)(buffer, 'Fetch response has been disposed');
  247. body = buffer.toString('base64');
  248. isBase64 = true;
  249. } else {
  250. body = '';
  251. isBase64 = false;
  252. }
  253. }
  254. const headers = [...(overrides.headers || [])];
  255. this._maybeAddCorsHeaders(headers);
  256. this._request._context.emit(_browserContext.BrowserContext.Events.RequestFulfilled, this._request);
  257. await this._delegate.fulfill({
  258. status: overrides.status || 200,
  259. headers,
  260. body: body,
  261. isBase64
  262. });
  263. this._endHandling();
  264. }
  265. // See https://github.com/microsoft/playwright/issues/12929
  266. _maybeAddCorsHeaders(headers) {
  267. const origin = this._request.headerValue('origin');
  268. if (!origin) return;
  269. const requestUrl = new URL(this._request.url());
  270. if (!requestUrl.protocol.startsWith('http')) return;
  271. if (requestUrl.origin === origin.trim()) return;
  272. const corsHeader = headers.find(({
  273. name
  274. }) => name === 'access-control-allow-origin');
  275. if (corsHeader) return;
  276. headers.push({
  277. name: 'access-control-allow-origin',
  278. value: origin
  279. });
  280. headers.push({
  281. name: 'access-control-allow-credentials',
  282. value: 'true'
  283. });
  284. headers.push({
  285. name: 'vary',
  286. value: 'Origin'
  287. });
  288. }
  289. async continue(overrides) {
  290. this._startHandling();
  291. if (overrides.url) {
  292. const newUrl = new URL(overrides.url);
  293. const oldUrl = new URL(this._request.url());
  294. if (oldUrl.protocol !== newUrl.protocol) throw new Error('New URL must have same protocol as overridden URL');
  295. }
  296. this._request._setOverrides(overrides);
  297. if (!overrides.isFallback) this._request._context.emit(_browserContext.BrowserContext.Events.RequestContinued, this._request);
  298. await this._delegate.continue(this._request, overrides);
  299. this._endHandling();
  300. }
  301. _startHandling() {
  302. (0, _utils.assert)(!this._handled, 'Route is already handled!');
  303. this._handled = true;
  304. }
  305. _endHandling() {
  306. this._request._context.removeRouteInFlight(this);
  307. }
  308. }
  309. exports.Route = Route;
  310. class Response extends _instrumentation.SdkObject {
  311. constructor(request, status, statusText, headers, timing, getResponseBodyCallback, fromServiceWorker, httpVersion) {
  312. super(request.frame() || request._context, 'response');
  313. this._request = void 0;
  314. this._contentPromise = null;
  315. this._finishedPromise = new _manualPromise.ManualPromise();
  316. this._status = void 0;
  317. this._statusText = void 0;
  318. this._url = void 0;
  319. this._headers = void 0;
  320. this._headersMap = new Map();
  321. this._getResponseBodyCallback = void 0;
  322. this._timing = void 0;
  323. this._serverAddrPromise = new _manualPromise.ManualPromise();
  324. this._securityDetailsPromise = new _manualPromise.ManualPromise();
  325. this._rawResponseHeadersPromise = new _manualPromise.ManualPromise();
  326. this._httpVersion = void 0;
  327. this._fromServiceWorker = void 0;
  328. this._encodedBodySizePromise = new _manualPromise.ManualPromise();
  329. this._transferSizePromise = new _manualPromise.ManualPromise();
  330. this._responseHeadersSizePromise = new _manualPromise.ManualPromise();
  331. this._request = request;
  332. this._timing = timing;
  333. this._status = status;
  334. this._statusText = statusText;
  335. this._url = request.url();
  336. this._headers = headers;
  337. for (const {
  338. name,
  339. value
  340. } of this._headers) this._headersMap.set(name.toLowerCase(), value);
  341. this._getResponseBodyCallback = getResponseBodyCallback;
  342. this._request._setResponse(this);
  343. this._httpVersion = httpVersion;
  344. this._fromServiceWorker = fromServiceWorker;
  345. }
  346. _serverAddrFinished(addr) {
  347. this._serverAddrPromise.resolve(addr);
  348. }
  349. _securityDetailsFinished(securityDetails) {
  350. this._securityDetailsPromise.resolve(securityDetails);
  351. }
  352. _requestFinished(responseEndTiming) {
  353. this._request._responseEndTiming = Math.max(responseEndTiming, this._timing.responseStart);
  354. // Set start time equal to end when request is served from memory cache.
  355. if (this._timing.requestStart === -1) this._timing.requestStart = this._request._responseEndTiming;
  356. this._finishedPromise.resolve();
  357. }
  358. _setHttpVersion(httpVersion) {
  359. this._httpVersion = httpVersion;
  360. }
  361. url() {
  362. return this._url;
  363. }
  364. status() {
  365. return this._status;
  366. }
  367. statusText() {
  368. return this._statusText;
  369. }
  370. headers() {
  371. return this._headers;
  372. }
  373. headerValue(name) {
  374. return this._headersMap.get(name);
  375. }
  376. async rawResponseHeaders() {
  377. return this._rawResponseHeadersPromise;
  378. }
  379. // "null" means no raw headers available - we'll use provisional headers as raw headers.
  380. setRawResponseHeaders(headers) {
  381. if (!this._rawResponseHeadersPromise.isDone()) this._rawResponseHeadersPromise.resolve(headers || this._headers);
  382. }
  383. setTransferSize(size) {
  384. this._transferSizePromise.resolve(size);
  385. }
  386. setEncodedBodySize(size) {
  387. this._encodedBodySizePromise.resolve(size);
  388. }
  389. setResponseHeadersSize(size) {
  390. this._responseHeadersSizePromise.resolve(size);
  391. }
  392. timing() {
  393. return this._timing;
  394. }
  395. async serverAddr() {
  396. return (await this._serverAddrPromise) || null;
  397. }
  398. async securityDetails() {
  399. return (await this._securityDetailsPromise) || null;
  400. }
  401. body() {
  402. if (!this._contentPromise) {
  403. this._contentPromise = this._finishedPromise.then(async () => {
  404. if (this._status >= 300 && this._status <= 399) throw new Error('Response body is unavailable for redirect responses');
  405. return this._getResponseBodyCallback();
  406. });
  407. }
  408. return this._contentPromise;
  409. }
  410. request() {
  411. return this._request;
  412. }
  413. frame() {
  414. return this._request.frame();
  415. }
  416. httpVersion() {
  417. if (!this._httpVersion) return 'HTTP/1.1';
  418. if (this._httpVersion === 'http/1.1') return 'HTTP/1.1';
  419. if (this._httpVersion === 'h2') return 'HTTP/2.0';
  420. return this._httpVersion;
  421. }
  422. fromServiceWorker() {
  423. return this._fromServiceWorker;
  424. }
  425. async responseHeadersSize() {
  426. const availableSize = await this._responseHeadersSizePromise;
  427. if (availableSize !== null) return availableSize;
  428. // Fallback to calculating it manually.
  429. let headersSize = 4; // 4 = 2 spaces + 2 line breaks (HTTP/1.1 200 Ok\r\n)
  430. headersSize += 8; // httpVersion;
  431. headersSize += 3; // statusCode;
  432. headersSize += this.statusText().length;
  433. const headers = await this._rawResponseHeadersPromise;
  434. for (const header of headers) headersSize += header.name.length + header.value.length + 4; // 4 = ': ' + '\r\n'
  435. headersSize += 2; // '\r\n'
  436. return headersSize;
  437. }
  438. async sizes() {
  439. const requestHeadersSize = await this._request.requestHeadersSize();
  440. const responseHeadersSize = await this.responseHeadersSize();
  441. let encodedBodySize = await this._encodedBodySizePromise;
  442. if (encodedBodySize === null) {
  443. var _headers$find;
  444. // Fallback to calculating it manually.
  445. const headers = await this._rawResponseHeadersPromise;
  446. const contentLength = (_headers$find = headers.find(h => h.name.toLowerCase() === 'content-length')) === null || _headers$find === void 0 ? void 0 : _headers$find.value;
  447. encodedBodySize = contentLength ? +contentLength : 0;
  448. }
  449. let transferSize = await this._transferSizePromise;
  450. if (transferSize === null) {
  451. // Fallback to calculating it manually.
  452. transferSize = responseHeadersSize + encodedBodySize;
  453. }
  454. return {
  455. requestBodySize: this._request.bodySize(),
  456. requestHeadersSize,
  457. responseBodySize: encodedBodySize,
  458. responseHeadersSize,
  459. transferSize
  460. };
  461. }
  462. }
  463. exports.Response = Response;
  464. class WebSocket extends _instrumentation.SdkObject {
  465. constructor(parent, url) {
  466. super(parent, 'ws');
  467. this._url = void 0;
  468. this._notified = false;
  469. this._url = url;
  470. }
  471. markAsNotified() {
  472. // Sometimes we get "onWebSocketRequest" twice, at least in Chromium.
  473. // Perhaps websocket is restarted because of chrome.webRequest extensions api?
  474. // Or maybe the handshake response was a redirect?
  475. if (this._notified) return false;
  476. this._notified = true;
  477. return true;
  478. }
  479. url() {
  480. return this._url;
  481. }
  482. frameSent(opcode, data) {
  483. this.emit(WebSocket.Events.FrameSent, {
  484. opcode,
  485. data
  486. });
  487. }
  488. frameReceived(opcode, data) {
  489. this.emit(WebSocket.Events.FrameReceived, {
  490. opcode,
  491. data
  492. });
  493. }
  494. error(errorMessage) {
  495. this.emit(WebSocket.Events.SocketError, errorMessage);
  496. }
  497. closed() {
  498. this.emit(WebSocket.Events.Close);
  499. }
  500. }
  501. exports.WebSocket = WebSocket;
  502. WebSocket.Events = {
  503. Close: 'close',
  504. SocketError: 'socketerror',
  505. FrameReceived: 'framereceived',
  506. FrameSent: 'framesent'
  507. };
  508. // List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.
  509. const STATUS_TEXTS = exports.STATUS_TEXTS = {
  510. '100': 'Continue',
  511. '101': 'Switching Protocols',
  512. '102': 'Processing',
  513. '103': 'Early Hints',
  514. '200': 'OK',
  515. '201': 'Created',
  516. '202': 'Accepted',
  517. '203': 'Non-Authoritative Information',
  518. '204': 'No Content',
  519. '205': 'Reset Content',
  520. '206': 'Partial Content',
  521. '207': 'Multi-Status',
  522. '208': 'Already Reported',
  523. '226': 'IM Used',
  524. '300': 'Multiple Choices',
  525. '301': 'Moved Permanently',
  526. '302': 'Found',
  527. '303': 'See Other',
  528. '304': 'Not Modified',
  529. '305': 'Use Proxy',
  530. '306': 'Switch Proxy',
  531. '307': 'Temporary Redirect',
  532. '308': 'Permanent Redirect',
  533. '400': 'Bad Request',
  534. '401': 'Unauthorized',
  535. '402': 'Payment Required',
  536. '403': 'Forbidden',
  537. '404': 'Not Found',
  538. '405': 'Method Not Allowed',
  539. '406': 'Not Acceptable',
  540. '407': 'Proxy Authentication Required',
  541. '408': 'Request Timeout',
  542. '409': 'Conflict',
  543. '410': 'Gone',
  544. '411': 'Length Required',
  545. '412': 'Precondition Failed',
  546. '413': 'Payload Too Large',
  547. '414': 'URI Too Long',
  548. '415': 'Unsupported Media Type',
  549. '416': 'Range Not Satisfiable',
  550. '417': 'Expectation Failed',
  551. '418': 'I\'m a teapot',
  552. '421': 'Misdirected Request',
  553. '422': 'Unprocessable Entity',
  554. '423': 'Locked',
  555. '424': 'Failed Dependency',
  556. '425': 'Too Early',
  557. '426': 'Upgrade Required',
  558. '428': 'Precondition Required',
  559. '429': 'Too Many Requests',
  560. '431': 'Request Header Fields Too Large',
  561. '451': 'Unavailable For Legal Reasons',
  562. '500': 'Internal Server Error',
  563. '501': 'Not Implemented',
  564. '502': 'Bad Gateway',
  565. '503': 'Service Unavailable',
  566. '504': 'Gateway Timeout',
  567. '505': 'HTTP Version Not Supported',
  568. '506': 'Variant Also Negotiates',
  569. '507': 'Insufficient Storage',
  570. '508': 'Loop Detected',
  571. '510': 'Not Extended',
  572. '511': 'Network Authentication Required'
  573. };
  574. function singleHeader(name, value) {
  575. return [{
  576. name,
  577. value
  578. }];
  579. }
  580. function mergeHeaders(headers) {
  581. const lowerCaseToValue = new Map();
  582. const lowerCaseToOriginalCase = new Map();
  583. for (const h of headers) {
  584. if (!h) continue;
  585. for (const {
  586. name,
  587. value
  588. } of h) {
  589. const lower = name.toLowerCase();
  590. lowerCaseToOriginalCase.set(lower, name);
  591. lowerCaseToValue.set(lower, value);
  592. }
  593. }
  594. const result = [];
  595. for (const [lower, value] of lowerCaseToValue) result.push({
  596. name: lowerCaseToOriginalCase.get(lower),
  597. value
  598. });
  599. return result;
  600. }