NetworkManager.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. /**
  2. * Copyright 2017 Google Inc. All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. const EventEmitter = require('events');
  17. const {helper, assert, debugError} = require('./helper');
  18. const {Events} = require('./Events');
  19. class NetworkManager extends EventEmitter {
  20. /**
  21. * @param {!Puppeteer.CDPSession} client
  22. * @param {!Puppeteer.FrameManager} frameManager
  23. */
  24. constructor(client, ignoreHTTPSErrors, frameManager) {
  25. super();
  26. this._client = client;
  27. this._ignoreHTTPSErrors = ignoreHTTPSErrors;
  28. this._frameManager = frameManager;
  29. /** @type {!Map<string, !Request>} */
  30. this._requestIdToRequest = new Map();
  31. /** @type {!Map<string, !Protocol.Network.requestWillBeSentPayload>} */
  32. this._requestIdToRequestWillBeSentEvent = new Map();
  33. /** @type {!Object<string, string>} */
  34. this._extraHTTPHeaders = {};
  35. this._offline = false;
  36. /** @type {?{username: string, password: string}} */
  37. this._credentials = null;
  38. /** @type {!Set<string>} */
  39. this._attemptedAuthentications = new Set();
  40. this._userRequestInterceptionEnabled = false;
  41. this._protocolRequestInterceptionEnabled = false;
  42. this._userCacheDisabled = false;
  43. /** @type {!Map<string, string>} */
  44. this._requestIdToInterceptionId = new Map();
  45. this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this));
  46. this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this));
  47. this._client.on('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this));
  48. this._client.on('Network.requestServedFromCache', this._onRequestServedFromCache.bind(this));
  49. this._client.on('Network.responseReceived', this._onResponseReceived.bind(this));
  50. this._client.on('Network.loadingFinished', this._onLoadingFinished.bind(this));
  51. this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this));
  52. }
  53. async initialize() {
  54. await this._client.send('Network.enable');
  55. if (this._ignoreHTTPSErrors)
  56. await this._client.send('Security.setIgnoreCertificateErrors', {ignore: true});
  57. }
  58. /**
  59. * @param {?{username: string, password: string}} credentials
  60. */
  61. async authenticate(credentials) {
  62. this._credentials = credentials;
  63. await this._updateProtocolRequestInterception();
  64. }
  65. /**
  66. * @param {!Object<string, string>} extraHTTPHeaders
  67. */
  68. async setExtraHTTPHeaders(extraHTTPHeaders) {
  69. this._extraHTTPHeaders = {};
  70. for (const key of Object.keys(extraHTTPHeaders)) {
  71. const value = extraHTTPHeaders[key];
  72. assert(helper.isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
  73. this._extraHTTPHeaders[key.toLowerCase()] = value;
  74. }
  75. await this._client.send('Network.setExtraHTTPHeaders', { headers: this._extraHTTPHeaders });
  76. }
  77. /**
  78. * @return {!Object<string, string>}
  79. */
  80. extraHTTPHeaders() {
  81. return Object.assign({}, this._extraHTTPHeaders);
  82. }
  83. /**
  84. * @param {boolean} value
  85. */
  86. async setOfflineMode(value) {
  87. if (this._offline === value)
  88. return;
  89. this._offline = value;
  90. await this._client.send('Network.emulateNetworkConditions', {
  91. offline: this._offline,
  92. // values of 0 remove any active throttling. crbug.com/456324#c9
  93. latency: 0,
  94. downloadThroughput: -1,
  95. uploadThroughput: -1
  96. });
  97. }
  98. /**
  99. * @param {string} userAgent
  100. */
  101. async setUserAgent(userAgent) {
  102. await this._client.send('Network.setUserAgentOverride', { userAgent });
  103. }
  104. /**
  105. * @param {boolean} enabled
  106. */
  107. async setCacheEnabled(enabled) {
  108. this._userCacheDisabled = !enabled;
  109. await this._updateProtocolCacheDisabled();
  110. }
  111. /**
  112. * @param {boolean} value
  113. */
  114. async setRequestInterception(value) {
  115. this._userRequestInterceptionEnabled = value;
  116. await this._updateProtocolRequestInterception();
  117. }
  118. async _updateProtocolRequestInterception() {
  119. const enabled = this._userRequestInterceptionEnabled || !!this._credentials;
  120. if (enabled === this._protocolRequestInterceptionEnabled)
  121. return;
  122. this._protocolRequestInterceptionEnabled = enabled;
  123. if (enabled) {
  124. await Promise.all([
  125. this._updateProtocolCacheDisabled(),
  126. this._client.send('Fetch.enable', {
  127. handleAuthRequests: true,
  128. patterns: [{urlPattern: '*'}],
  129. }),
  130. ]);
  131. } else {
  132. await Promise.all([
  133. this._updateProtocolCacheDisabled(),
  134. this._client.send('Fetch.disable')
  135. ]);
  136. }
  137. }
  138. async _updateProtocolCacheDisabled() {
  139. await this._client.send('Network.setCacheDisabled', {
  140. cacheDisabled: this._userCacheDisabled || this._protocolRequestInterceptionEnabled
  141. });
  142. }
  143. /**
  144. * @param {!Protocol.Network.requestWillBeSentPayload} event
  145. */
  146. _onRequestWillBeSent(event) {
  147. // Request interception doesn't happen for data URLs with Network Service.
  148. if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) {
  149. const requestId = event.requestId;
  150. const interceptionId = this._requestIdToInterceptionId.get(requestId);
  151. if (interceptionId) {
  152. this._onRequest(event, interceptionId);
  153. this._requestIdToInterceptionId.delete(requestId);
  154. } else {
  155. this._requestIdToRequestWillBeSentEvent.set(event.requestId, event);
  156. }
  157. return;
  158. }
  159. this._onRequest(event, null);
  160. }
  161. /**
  162. * @param {!Protocol.Fetch.authRequiredPayload} event
  163. */
  164. _onAuthRequired(event) {
  165. /** @type {"Default"|"CancelAuth"|"ProvideCredentials"} */
  166. let response = 'Default';
  167. if (this._attemptedAuthentications.has(event.requestId)) {
  168. response = 'CancelAuth';
  169. } else if (this._credentials) {
  170. response = 'ProvideCredentials';
  171. this._attemptedAuthentications.add(event.requestId);
  172. }
  173. const {username, password} = this._credentials || {username: undefined, password: undefined};
  174. this._client.send('Fetch.continueWithAuth', {
  175. requestId: event.requestId,
  176. authChallengeResponse: { response, username, password },
  177. }).catch(debugError);
  178. }
  179. /**
  180. * @param {!Protocol.Fetch.requestPausedPayload} event
  181. */
  182. _onRequestPaused(event) {
  183. if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) {
  184. this._client.send('Fetch.continueRequest', {
  185. requestId: event.requestId
  186. }).catch(debugError);
  187. }
  188. const requestId = event.networkId;
  189. const interceptionId = event.requestId;
  190. if (requestId && this._requestIdToRequestWillBeSentEvent.has(requestId)) {
  191. const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId);
  192. this._onRequest(requestWillBeSentEvent, interceptionId);
  193. this._requestIdToRequestWillBeSentEvent.delete(requestId);
  194. } else {
  195. this._requestIdToInterceptionId.set(requestId, interceptionId);
  196. }
  197. }
  198. /**
  199. * @param {!Protocol.Network.requestWillBeSentPayload} event
  200. * @param {?string} interceptionId
  201. */
  202. _onRequest(event, interceptionId) {
  203. let redirectChain = [];
  204. if (event.redirectResponse) {
  205. const request = this._requestIdToRequest.get(event.requestId);
  206. // If we connect late to the target, we could have missed the requestWillBeSent event.
  207. if (request) {
  208. this._handleRequestRedirect(request, event.redirectResponse);
  209. redirectChain = request._redirectChain;
  210. }
  211. }
  212. const frame = event.frameId ? this._frameManager.frame(event.frameId) : null;
  213. const request = new Request(this._client, frame, interceptionId, this._userRequestInterceptionEnabled, event, redirectChain);
  214. this._requestIdToRequest.set(event.requestId, request);
  215. this.emit(Events.NetworkManager.Request, request);
  216. }
  217. /**
  218. * @param {!Protocol.Network.requestServedFromCachePayload} event
  219. */
  220. _onRequestServedFromCache(event) {
  221. const request = this._requestIdToRequest.get(event.requestId);
  222. if (request)
  223. request._fromMemoryCache = true;
  224. }
  225. /**
  226. * @param {!Request} request
  227. * @param {!Protocol.Network.Response} responsePayload
  228. */
  229. _handleRequestRedirect(request, responsePayload) {
  230. const response = new Response(this._client, request, responsePayload);
  231. request._response = response;
  232. request._redirectChain.push(request);
  233. response._bodyLoadedPromiseFulfill.call(null, new Error('Response body is unavailable for redirect responses'));
  234. this._requestIdToRequest.delete(request._requestId);
  235. this._attemptedAuthentications.delete(request._interceptionId);
  236. this.emit(Events.NetworkManager.Response, response);
  237. this.emit(Events.NetworkManager.RequestFinished, request);
  238. }
  239. /**
  240. * @param {!Protocol.Network.responseReceivedPayload} event
  241. */
  242. _onResponseReceived(event) {
  243. const request = this._requestIdToRequest.get(event.requestId);
  244. // FileUpload sends a response without a matching request.
  245. if (!request)
  246. return;
  247. const response = new Response(this._client, request, event.response);
  248. request._response = response;
  249. this.emit(Events.NetworkManager.Response, response);
  250. }
  251. /**
  252. * @param {!Protocol.Network.loadingFinishedPayload} event
  253. */
  254. _onLoadingFinished(event) {
  255. const request = this._requestIdToRequest.get(event.requestId);
  256. // For certain requestIds we never receive requestWillBeSent event.
  257. // @see https://crbug.com/750469
  258. if (!request)
  259. return;
  260. // Under certain conditions we never get the Network.responseReceived
  261. // event from protocol. @see https://crbug.com/883475
  262. if (request.response())
  263. request.response()._bodyLoadedPromiseFulfill.call(null);
  264. this._requestIdToRequest.delete(request._requestId);
  265. this._attemptedAuthentications.delete(request._interceptionId);
  266. this.emit(Events.NetworkManager.RequestFinished, request);
  267. }
  268. /**
  269. * @param {!Protocol.Network.loadingFailedPayload} event
  270. */
  271. _onLoadingFailed(event) {
  272. const request = this._requestIdToRequest.get(event.requestId);
  273. // For certain requestIds we never receive requestWillBeSent event.
  274. // @see https://crbug.com/750469
  275. if (!request)
  276. return;
  277. request._failureText = event.errorText;
  278. const response = request.response();
  279. if (response)
  280. response._bodyLoadedPromiseFulfill.call(null);
  281. this._requestIdToRequest.delete(request._requestId);
  282. this._attemptedAuthentications.delete(request._interceptionId);
  283. this.emit(Events.NetworkManager.RequestFailed, request);
  284. }
  285. }
  286. class Request {
  287. /**
  288. * @param {!Puppeteer.CDPSession} client
  289. * @param {?Puppeteer.Frame} frame
  290. * @param {string} interceptionId
  291. * @param {boolean} allowInterception
  292. * @param {!Protocol.Network.requestWillBeSentPayload} event
  293. * @param {!Array<!Request>} redirectChain
  294. */
  295. constructor(client, frame, interceptionId, allowInterception, event, redirectChain) {
  296. this._client = client;
  297. this._requestId = event.requestId;
  298. this._isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document';
  299. this._interceptionId = interceptionId;
  300. this._allowInterception = allowInterception;
  301. this._interceptionHandled = false;
  302. this._response = null;
  303. this._failureText = null;
  304. this._url = event.request.url;
  305. this._resourceType = event.type.toLowerCase();
  306. this._method = event.request.method;
  307. this._postData = event.request.postData;
  308. this._headers = {};
  309. this._frame = frame;
  310. this._redirectChain = redirectChain;
  311. for (const key of Object.keys(event.request.headers))
  312. this._headers[key.toLowerCase()] = event.request.headers[key];
  313. this._fromMemoryCache = false;
  314. }
  315. /**
  316. * @return {string}
  317. */
  318. url() {
  319. return this._url;
  320. }
  321. /**
  322. * @return {string}
  323. */
  324. resourceType() {
  325. return this._resourceType;
  326. }
  327. /**
  328. * @return {string}
  329. */
  330. method() {
  331. return this._method;
  332. }
  333. /**
  334. * @return {string|undefined}
  335. */
  336. postData() {
  337. return this._postData;
  338. }
  339. /**
  340. * @return {!Object}
  341. */
  342. headers() {
  343. return this._headers;
  344. }
  345. /**
  346. * @return {?Response}
  347. */
  348. response() {
  349. return this._response;
  350. }
  351. /**
  352. * @return {?Puppeteer.Frame}
  353. */
  354. frame() {
  355. return this._frame;
  356. }
  357. /**
  358. * @return {boolean}
  359. */
  360. isNavigationRequest() {
  361. return this._isNavigationRequest;
  362. }
  363. /**
  364. * @return {!Array<!Request>}
  365. */
  366. redirectChain() {
  367. return this._redirectChain.slice();
  368. }
  369. /**
  370. * @return {?{errorText: string}}
  371. */
  372. failure() {
  373. if (!this._failureText)
  374. return null;
  375. return {
  376. errorText: this._failureText
  377. };
  378. }
  379. /**
  380. * @param {!{url?: string, method?:string, postData?: string, headers?: !Object}} overrides
  381. */
  382. async continue(overrides = {}) {
  383. // Request interception is not supported for data: urls.
  384. if (this._url.startsWith('data:'))
  385. return;
  386. assert(this._allowInterception, 'Request Interception is not enabled!');
  387. assert(!this._interceptionHandled, 'Request is already handled!');
  388. const {
  389. url,
  390. method,
  391. postData,
  392. headers
  393. } = overrides;
  394. this._interceptionHandled = true;
  395. await this._client.send('Fetch.continueRequest', {
  396. requestId: this._interceptionId,
  397. url,
  398. method,
  399. postData,
  400. headers: headers ? headersArray(headers) : undefined,
  401. }).catch(error => {
  402. // In certain cases, protocol will return error if the request was already canceled
  403. // or the page was closed. We should tolerate these errors.
  404. debugError(error);
  405. });
  406. }
  407. /**
  408. * @param {!{status: number, headers: Object, contentType: string, body: (string|Buffer)}} response
  409. */
  410. async respond(response) {
  411. // Mocking responses for dataURL requests is not currently supported.
  412. if (this._url.startsWith('data:'))
  413. return;
  414. assert(this._allowInterception, 'Request Interception is not enabled!');
  415. assert(!this._interceptionHandled, 'Request is already handled!');
  416. this._interceptionHandled = true;
  417. const responseBody = response.body && helper.isString(response.body) ? Buffer.from(/** @type {string} */(response.body)) : /** @type {?Buffer} */(response.body || null);
  418. /** @type {!Object<string, string>} */
  419. const responseHeaders = {};
  420. if (response.headers) {
  421. for (const header of Object.keys(response.headers))
  422. responseHeaders[header.toLowerCase()] = response.headers[header];
  423. }
  424. if (response.contentType)
  425. responseHeaders['content-type'] = response.contentType;
  426. if (responseBody && !('content-length' in responseHeaders))
  427. responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
  428. await this._client.send('Fetch.fulfillRequest', {
  429. requestId: this._interceptionId,
  430. responseCode: response.status || 200,
  431. responsePhrase: STATUS_TEXTS[response.status || 200],
  432. responseHeaders: headersArray(responseHeaders),
  433. body: responseBody ? responseBody.toString('base64') : undefined,
  434. }).catch(error => {
  435. // In certain cases, protocol will return error if the request was already canceled
  436. // or the page was closed. We should tolerate these errors.
  437. debugError(error);
  438. });
  439. }
  440. /**
  441. * @param {string=} errorCode
  442. */
  443. async abort(errorCode = 'failed') {
  444. // Request interception is not supported for data: urls.
  445. if (this._url.startsWith('data:'))
  446. return;
  447. const errorReason = errorReasons[errorCode];
  448. assert(errorReason, 'Unknown error code: ' + errorCode);
  449. assert(this._allowInterception, 'Request Interception is not enabled!');
  450. assert(!this._interceptionHandled, 'Request is already handled!');
  451. this._interceptionHandled = true;
  452. await this._client.send('Fetch.failRequest', {
  453. requestId: this._interceptionId,
  454. errorReason
  455. }).catch(error => {
  456. // In certain cases, protocol will return error if the request was already canceled
  457. // or the page was closed. We should tolerate these errors.
  458. debugError(error);
  459. });
  460. }
  461. }
  462. const errorReasons = {
  463. 'aborted': 'Aborted',
  464. 'accessdenied': 'AccessDenied',
  465. 'addressunreachable': 'AddressUnreachable',
  466. 'blockedbyclient': 'BlockedByClient',
  467. 'blockedbyresponse': 'BlockedByResponse',
  468. 'connectionaborted': 'ConnectionAborted',
  469. 'connectionclosed': 'ConnectionClosed',
  470. 'connectionfailed': 'ConnectionFailed',
  471. 'connectionrefused': 'ConnectionRefused',
  472. 'connectionreset': 'ConnectionReset',
  473. 'internetdisconnected': 'InternetDisconnected',
  474. 'namenotresolved': 'NameNotResolved',
  475. 'timedout': 'TimedOut',
  476. 'failed': 'Failed',
  477. };
  478. class Response {
  479. /**
  480. * @param {!Puppeteer.CDPSession} client
  481. * @param {!Request} request
  482. * @param {!Protocol.Network.Response} responsePayload
  483. */
  484. constructor(client, request, responsePayload) {
  485. this._client = client;
  486. this._request = request;
  487. this._contentPromise = null;
  488. this._bodyLoadedPromise = new Promise(fulfill => {
  489. this._bodyLoadedPromiseFulfill = fulfill;
  490. });
  491. this._remoteAddress = {
  492. ip: responsePayload.remoteIPAddress,
  493. port: responsePayload.remotePort,
  494. };
  495. this._status = responsePayload.status;
  496. this._statusText = responsePayload.statusText;
  497. this._url = request.url();
  498. this._fromDiskCache = !!responsePayload.fromDiskCache;
  499. this._fromServiceWorker = !!responsePayload.fromServiceWorker;
  500. this._headers = {};
  501. for (const key of Object.keys(responsePayload.headers))
  502. this._headers[key.toLowerCase()] = responsePayload.headers[key];
  503. this._securityDetails = responsePayload.securityDetails ? new SecurityDetails(responsePayload.securityDetails) : null;
  504. }
  505. /**
  506. * @return {{ip: string, port: number}}
  507. */
  508. remoteAddress() {
  509. return this._remoteAddress;
  510. }
  511. /**
  512. * @return {string}
  513. */
  514. url() {
  515. return this._url;
  516. }
  517. /**
  518. * @return {boolean}
  519. */
  520. ok() {
  521. return this._status === 0 || (this._status >= 200 && this._status <= 299);
  522. }
  523. /**
  524. * @return {number}
  525. */
  526. status() {
  527. return this._status;
  528. }
  529. /**
  530. * @return {string}
  531. */
  532. statusText() {
  533. return this._statusText;
  534. }
  535. /**
  536. * @return {!Object}
  537. */
  538. headers() {
  539. return this._headers;
  540. }
  541. /**
  542. * @return {?SecurityDetails}
  543. */
  544. securityDetails() {
  545. return this._securityDetails;
  546. }
  547. /**
  548. * @return {!Promise<!Buffer>}
  549. */
  550. buffer() {
  551. if (!this._contentPromise) {
  552. this._contentPromise = this._bodyLoadedPromise.then(async error => {
  553. if (error)
  554. throw error;
  555. const response = await this._client.send('Network.getResponseBody', {
  556. requestId: this._request._requestId
  557. });
  558. return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
  559. });
  560. }
  561. return this._contentPromise;
  562. }
  563. /**
  564. * @return {!Promise<string>}
  565. */
  566. async text() {
  567. const content = await this.buffer();
  568. return content.toString('utf8');
  569. }
  570. /**
  571. * @return {!Promise<!Object>}
  572. */
  573. async json() {
  574. const content = await this.text();
  575. return JSON.parse(content);
  576. }
  577. /**
  578. * @return {!Request}
  579. */
  580. request() {
  581. return this._request;
  582. }
  583. /**
  584. * @return {boolean}
  585. */
  586. fromCache() {
  587. return this._fromDiskCache || this._request._fromMemoryCache;
  588. }
  589. /**
  590. * @return {boolean}
  591. */
  592. fromServiceWorker() {
  593. return this._fromServiceWorker;
  594. }
  595. /**
  596. * @return {?Puppeteer.Frame}
  597. */
  598. frame() {
  599. return this._request.frame();
  600. }
  601. }
  602. class SecurityDetails {
  603. /**
  604. * @param {!Protocol.Network.SecurityDetails} securityPayload
  605. */
  606. constructor(securityPayload) {
  607. this._subjectName = securityPayload['subjectName'];
  608. this._issuer = securityPayload['issuer'];
  609. this._validFrom = securityPayload['validFrom'];
  610. this._validTo = securityPayload['validTo'];
  611. this._protocol = securityPayload['protocol'];
  612. }
  613. /**
  614. * @return {string}
  615. */
  616. subjectName() {
  617. return this._subjectName;
  618. }
  619. /**
  620. * @return {string}
  621. */
  622. issuer() {
  623. return this._issuer;
  624. }
  625. /**
  626. * @return {number}
  627. */
  628. validFrom() {
  629. return this._validFrom;
  630. }
  631. /**
  632. * @return {number}
  633. */
  634. validTo() {
  635. return this._validTo;
  636. }
  637. /**
  638. * @return {string}
  639. */
  640. protocol() {
  641. return this._protocol;
  642. }
  643. }
  644. /**
  645. * @param {Object<string, string>} headers
  646. * @return {!Array<{name: string, value: string}>}
  647. */
  648. function headersArray(headers) {
  649. const result = [];
  650. for (const name in headers) {
  651. if (!Object.is(headers[name], undefined))
  652. result.push({name, value: headers[name] + ''});
  653. }
  654. return result;
  655. }
  656. // List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.
  657. const STATUS_TEXTS = {
  658. '100': 'Continue',
  659. '101': 'Switching Protocols',
  660. '102': 'Processing',
  661. '103': 'Early Hints',
  662. '200': 'OK',
  663. '201': 'Created',
  664. '202': 'Accepted',
  665. '203': 'Non-Authoritative Information',
  666. '204': 'No Content',
  667. '205': 'Reset Content',
  668. '206': 'Partial Content',
  669. '207': 'Multi-Status',
  670. '208': 'Already Reported',
  671. '226': 'IM Used',
  672. '300': 'Multiple Choices',
  673. '301': 'Moved Permanently',
  674. '302': 'Found',
  675. '303': 'See Other',
  676. '304': 'Not Modified',
  677. '305': 'Use Proxy',
  678. '306': 'Switch Proxy',
  679. '307': 'Temporary Redirect',
  680. '308': 'Permanent Redirect',
  681. '400': 'Bad Request',
  682. '401': 'Unauthorized',
  683. '402': 'Payment Required',
  684. '403': 'Forbidden',
  685. '404': 'Not Found',
  686. '405': 'Method Not Allowed',
  687. '406': 'Not Acceptable',
  688. '407': 'Proxy Authentication Required',
  689. '408': 'Request Timeout',
  690. '409': 'Conflict',
  691. '410': 'Gone',
  692. '411': 'Length Required',
  693. '412': 'Precondition Failed',
  694. '413': 'Payload Too Large',
  695. '414': 'URI Too Long',
  696. '415': 'Unsupported Media Type',
  697. '416': 'Range Not Satisfiable',
  698. '417': 'Expectation Failed',
  699. '418': 'I\'m a teapot',
  700. '421': 'Misdirected Request',
  701. '422': 'Unprocessable Entity',
  702. '423': 'Locked',
  703. '424': 'Failed Dependency',
  704. '425': 'Too Early',
  705. '426': 'Upgrade Required',
  706. '428': 'Precondition Required',
  707. '429': 'Too Many Requests',
  708. '431': 'Request Header Fields Too Large',
  709. '451': 'Unavailable For Legal Reasons',
  710. '500': 'Internal Server Error',
  711. '501': 'Not Implemented',
  712. '502': 'Bad Gateway',
  713. '503': 'Service Unavailable',
  714. '504': 'Gateway Timeout',
  715. '505': 'HTTP Version Not Supported',
  716. '506': 'Variant Also Negotiates',
  717. '507': 'Insufficient Storage',
  718. '508': 'Loop Detected',
  719. '510': 'Not Extended',
  720. '511': 'Network Authentication Required',
  721. };
  722. module.exports = {Request, Response, NetworkManager, SecurityDetails};