UNPKG

31.5 kBJavaScriptView Raw
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 */
16const EventEmitter = require('events');
17const {helper, assert, debugError} = require('./helper');
18const Multimap = require('./Multimap');
19
20class NetworkManager extends EventEmitter {
21 /**
22 * @param {!Puppeteer.CDPSession} client
23 * @param {!Puppeteer.FrameManager} frameManager
24 */
25 constructor(client, frameManager) {
26 super();
27 this._client = client;
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
36 this._offline = false;
37
38 /** @type {?{username: string, password: string}} */
39 this._credentials = null;
40 /** @type {!Set<string>} */
41 this._attemptedAuthentications = new Set();
42 this._userRequestInterceptionEnabled = false;
43 this._protocolRequestInterceptionEnabled = false;
44 /** @type {!Multimap<string, string>} */
45 this._requestHashToRequestIds = new Multimap();
46 /** @type {!Multimap<string, string>} */
47 this._requestHashToInterceptionIds = new Multimap();
48
49 this._client.on('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this));
50 this._client.on('Network.requestIntercepted', this._onRequestIntercepted.bind(this));
51 this._client.on('Network.requestServedFromCache', this._onRequestServedFromCache.bind(this));
52 this._client.on('Network.responseReceived', this._onResponseReceived.bind(this));
53 this._client.on('Network.loadingFinished', this._onLoadingFinished.bind(this));
54 this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this));
55 }
56
57 /**
58 * @param {?{username: string, password: string}} credentials
59 */
60 /* async */ authenticate(credentials) {return (fn => {
61 const gen = fn.call(this);
62 return new Promise((resolve, reject) => {
63 function step(key, arg) {
64 let info, value;
65 try {
66 info = gen[key](arg);
67 value = info.value;
68 } catch (error) {
69 reject(error);
70 return;
71 }
72 if (info.done) {
73 resolve(value);
74 } else {
75 return Promise.resolve(value).then(
76 value => {
77 step('next', value);
78 },
79 err => {
80 step('throw', err);
81 });
82 }
83 }
84 return step('next');
85 });
86})(function*(){
87 this._credentials = credentials;
88 (yield this._updateProtocolRequestInterception());
89 });}
90
91 /**
92 * @param {!Object<string, string>} extraHTTPHeaders
93 */
94 /* async */ setExtraHTTPHeaders(extraHTTPHeaders) {return (fn => {
95 const gen = fn.call(this);
96 return new Promise((resolve, reject) => {
97 function step(key, arg) {
98 let info, value;
99 try {
100 info = gen[key](arg);
101 value = info.value;
102 } catch (error) {
103 reject(error);
104 return;
105 }
106 if (info.done) {
107 resolve(value);
108 } else {
109 return Promise.resolve(value).then(
110 value => {
111 step('next', value);
112 },
113 err => {
114 step('throw', err);
115 });
116 }
117 }
118 return step('next');
119 });
120})(function*(){
121 this._extraHTTPHeaders = {};
122 for (const key of Object.keys(extraHTTPHeaders)) {
123 const value = extraHTTPHeaders[key];
124 assert(helper.isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
125 this._extraHTTPHeaders[key.toLowerCase()] = value;
126 }
127 (yield this._client.send('Network.setExtraHTTPHeaders', { headers: this._extraHTTPHeaders }));
128 });}
129
130 /**
131 * @return {!Object<string, string>}
132 */
133 extraHTTPHeaders() {
134 return Object.assign({}, this._extraHTTPHeaders);
135 }
136
137 /**
138 * @param {boolean} value
139 */
140 /* async */ setOfflineMode(value) {return (fn => {
141 const gen = fn.call(this);
142 return new Promise((resolve, reject) => {
143 function step(key, arg) {
144 let info, value;
145 try {
146 info = gen[key](arg);
147 value = info.value;
148 } catch (error) {
149 reject(error);
150 return;
151 }
152 if (info.done) {
153 resolve(value);
154 } else {
155 return Promise.resolve(value).then(
156 value => {
157 step('next', value);
158 },
159 err => {
160 step('throw', err);
161 });
162 }
163 }
164 return step('next');
165 });
166})(function*(){
167 if (this._offline === value)
168 return;
169 this._offline = value;
170 (yield this._client.send('Network.emulateNetworkConditions', {
171 offline: this._offline,
172 // values of 0 remove any active throttling. crbug.com/456324#c9
173 latency: 0,
174 downloadThroughput: -1,
175 uploadThroughput: -1
176 }));
177 });}
178
179 /**
180 * @param {string} userAgent
181 */
182 /* async */ setUserAgent(userAgent) {return (fn => {
183 const gen = fn.call(this);
184 return new Promise((resolve, reject) => {
185 function step(key, arg) {
186 let info, value;
187 try {
188 info = gen[key](arg);
189 value = info.value;
190 } catch (error) {
191 reject(error);
192 return;
193 }
194 if (info.done) {
195 resolve(value);
196 } else {
197 return Promise.resolve(value).then(
198 value => {
199 step('next', value);
200 },
201 err => {
202 step('throw', err);
203 });
204 }
205 }
206 return step('next');
207 });
208})(function*(){
209 (yield this._client.send('Network.setUserAgentOverride', { userAgent }));
210 });}
211
212 /**
213 * @param {boolean} value
214 */
215 /* async */ setRequestInterception(value) {return (fn => {
216 const gen = fn.call(this);
217 return new Promise((resolve, reject) => {
218 function step(key, arg) {
219 let info, value;
220 try {
221 info = gen[key](arg);
222 value = info.value;
223 } catch (error) {
224 reject(error);
225 return;
226 }
227 if (info.done) {
228 resolve(value);
229 } else {
230 return Promise.resolve(value).then(
231 value => {
232 step('next', value);
233 },
234 err => {
235 step('throw', err);
236 });
237 }
238 }
239 return step('next');
240 });
241})(function*(){
242 this._userRequestInterceptionEnabled = value;
243 (yield this._updateProtocolRequestInterception());
244 });}
245
246 /* async */ _updateProtocolRequestInterception() {return (fn => {
247 const gen = fn.call(this);
248 return new Promise((resolve, reject) => {
249 function step(key, arg) {
250 let info, value;
251 try {
252 info = gen[key](arg);
253 value = info.value;
254 } catch (error) {
255 reject(error);
256 return;
257 }
258 if (info.done) {
259 resolve(value);
260 } else {
261 return Promise.resolve(value).then(
262 value => {
263 step('next', value);
264 },
265 err => {
266 step('throw', err);
267 });
268 }
269 }
270 return step('next');
271 });
272})(function*(){
273 const enabled = this._userRequestInterceptionEnabled || !!this._credentials;
274 if (enabled === this._protocolRequestInterceptionEnabled)
275 return;
276 this._protocolRequestInterceptionEnabled = enabled;
277 const patterns = enabled ? [{urlPattern: '*'}] : [];
278 (yield Promise.all([
279 this._client.send('Network.setCacheDisabled', {cacheDisabled: enabled}),
280 this._client.send('Network.setRequestInterception', {patterns})
281 ]));
282 });}
283
284 /**
285 * @param {!Protocol.Network.requestWillBeSentPayload} event
286 */
287 _onRequestWillBeSent(event) {
288 if (this._protocolRequestInterceptionEnabled) {
289 const requestHash = generateRequestHash(event.request);
290 const interceptionId = this._requestHashToInterceptionIds.firstValue(requestHash);
291 if (interceptionId) {
292 this._onRequest(event, interceptionId);
293 this._requestHashToInterceptionIds.delete(requestHash, interceptionId);
294 } else {
295 this._requestHashToRequestIds.set(requestHash, event.requestId);
296 this._requestIdToRequestWillBeSentEvent.set(event.requestId, event);
297 }
298 return;
299 }
300 this._onRequest(event, null);
301 }
302
303 /**
304 * @param {!Protocol.Network.requestInterceptedPayload} event
305 */
306 _onRequestIntercepted(event) {
307 if (event.authChallenge) {
308 /** @type {"Default"|"CancelAuth"|"ProvideCredentials"} */
309 let response = 'Default';
310 if (this._attemptedAuthentications.has(event.interceptionId)) {
311 response = 'CancelAuth';
312 } else if (this._credentials) {
313 response = 'ProvideCredentials';
314 this._attemptedAuthentications.add(event.interceptionId);
315 }
316 const {username, password} = this._credentials || {username: undefined, password: undefined};
317 this._client.send('Network.continueInterceptedRequest', {
318 interceptionId: event.interceptionId,
319 authChallengeResponse: { response, username, password }
320 }).catch(debugError);
321 return;
322 }
323 if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) {
324 this._client.send('Network.continueInterceptedRequest', {
325 interceptionId: event.interceptionId
326 }).catch(debugError);
327 }
328
329 const requestHash = generateRequestHash(event.request);
330 const requestId = this._requestHashToRequestIds.firstValue(requestHash);
331 if (requestId) {
332 const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId);
333 this._onRequest(requestWillBeSentEvent, event.interceptionId);
334 this._requestHashToRequestIds.delete(requestHash, requestId);
335 this._requestIdToRequestWillBeSentEvent.delete(requestId);
336 } else {
337 this._requestHashToInterceptionIds.set(requestHash, event.interceptionId);
338 }
339 }
340
341 /**
342 * @param {!Protocol.Network.requestWillBeSentPayload} event
343 * @param {?string} interceptionId
344 */
345 _onRequest(event, interceptionId) {
346 let redirectChain = [];
347 if (event.redirectResponse) {
348 const request = this._requestIdToRequest.get(event.requestId);
349 // If we connect late to the target, we could have missed the requestWillBeSent event.
350 if (request) {
351 this._handleRequestRedirect(request, event.redirectResponse.status, event.redirectResponse.headers, event.redirectResponse.fromDiskCache, event.redirectResponse.fromServiceWorker, event.redirectResponse.securityDetails);
352 redirectChain = request._redirectChain;
353 }
354 }
355 const isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document';
356 this._handleRequestStart(event.requestId, interceptionId, event.request.url, isNavigationRequest, event.type, event.request, event.frameId, redirectChain);
357 }
358
359 /**
360 * @param {!Protocol.Network.requestServedFromCachePayload} event
361 */
362 _onRequestServedFromCache(event) {
363 const request = this._requestIdToRequest.get(event.requestId);
364 if (request)
365 request._fromMemoryCache = true;
366 }
367
368 /**
369 * @param {!Request} request
370 * @param {number} redirectStatus
371 * @param {!Object} redirectHeaders
372 * @param {boolean} fromDiskCache
373 * @param {boolean} fromServiceWorker
374 * @param {?Object} securityDetails
375 */
376 _handleRequestRedirect(request, redirectStatus, redirectHeaders, fromDiskCache, fromServiceWorker, securityDetails) {
377 const response = new Response(this._client, request, redirectStatus, redirectHeaders, fromDiskCache, fromServiceWorker, securityDetails);
378 request._response = response;
379 request._redirectChain.push(request);
380 response._bodyLoadedPromiseFulfill.call(null, new Error('Response body is unavailable for redirect responses'));
381 this._requestIdToRequest.delete(request._requestId);
382 this._attemptedAuthentications.delete(request._interceptionId);
383 this.emit(NetworkManager.Events.Response, response);
384 this.emit(NetworkManager.Events.RequestFinished, request);
385 }
386
387 /**
388 * @param {string} requestId
389 * @param {?string} interceptionId
390 * @param {string} url
391 * @param {boolean} isNavigationRequest
392 * @param {string} resourceType
393 * @param {!Protocol.Network.Request} requestPayload
394 * @param {?string} frameId
395 * @param {!Array<!Request>} redirectChain
396 */
397 _handleRequestStart(requestId, interceptionId, url, isNavigationRequest, resourceType, requestPayload, frameId, redirectChain) {
398 let frame = null;
399 if (frameId)
400 frame = this._frameManager.frame(frameId);
401 const request = new Request(this._client, requestId, interceptionId, isNavigationRequest, this._userRequestInterceptionEnabled, url, resourceType, requestPayload, frame, redirectChain);
402 this._requestIdToRequest.set(requestId, request);
403 this.emit(NetworkManager.Events.Request, request);
404 }
405
406 /**
407 * @param {!Protocol.Network.responseReceivedPayload} event
408 */
409 _onResponseReceived(event) {
410 const request = this._requestIdToRequest.get(event.requestId);
411 // FileUpload sends a response without a matching request.
412 if (!request)
413 return;
414 const response = new Response(this._client, request, event.response.status, event.response.headers,
415 event.response.fromDiskCache, event.response.fromServiceWorker, event.response.securityDetails);
416 request._response = response;
417 this.emit(NetworkManager.Events.Response, response);
418 }
419
420 /**
421 * @param {!Protocol.Network.loadingFinishedPayload} event
422 */
423 _onLoadingFinished(event) {
424 const request = this._requestIdToRequest.get(event.requestId);
425 // For certain requestIds we never receive requestWillBeSent event.
426 // @see https://crbug.com/750469
427 if (!request)
428 return;
429 request.response()._bodyLoadedPromiseFulfill.call(null);
430 this._requestIdToRequest.delete(request._requestId);
431 this._attemptedAuthentications.delete(request._interceptionId);
432 this.emit(NetworkManager.Events.RequestFinished, request);
433 }
434
435 /**
436 * @param {!Protocol.Network.loadingFailedPayload} event
437 */
438 _onLoadingFailed(event) {
439 const request = this._requestIdToRequest.get(event.requestId);
440 // For certain requestIds we never receive requestWillBeSent event.
441 // @see https://crbug.com/750469
442 if (!request)
443 return;
444 request._failureText = event.errorText;
445 const response = request.response();
446 if (response)
447 response._bodyLoadedPromiseFulfill.call(null);
448 this._requestIdToRequest.delete(request._requestId);
449 this._attemptedAuthentications.delete(request._interceptionId);
450 this.emit(NetworkManager.Events.RequestFailed, request);
451 }
452}
453
454class Request {
455 /**
456 * @param {!Puppeteer.CDPSession} client
457 * @param {?string} requestId
458 * @param {string} interceptionId
459 * @param {boolean} isNavigationRequest
460 * @param {boolean} allowInterception
461 * @param {string} url
462 * @param {string} resourceType
463 * @param {!Protocol.Network.Request} payload
464 * @param {?Puppeteer.Frame} frame
465 * @param {!Array<!Request>} redirectChain
466 */
467 constructor(client, requestId, interceptionId, isNavigationRequest, allowInterception, url, resourceType, payload, frame, redirectChain) {
468 this._client = client;
469 this._requestId = requestId;
470 this._isNavigationRequest = isNavigationRequest;
471 this._interceptionId = interceptionId;
472 this._allowInterception = allowInterception;
473 this._interceptionHandled = false;
474 this._response = null;
475 this._failureText = null;
476
477 this._url = url;
478 this._resourceType = resourceType.toLowerCase();
479 this._method = payload.method;
480 this._postData = payload.postData;
481 this._headers = {};
482 this._frame = frame;
483 this._redirectChain = redirectChain;
484 for (const key of Object.keys(payload.headers))
485 this._headers[key.toLowerCase()] = payload.headers[key];
486
487 this._fromMemoryCache = false;
488 }
489
490 /**
491 * @return {string}
492 */
493 url() {
494 return this._url;
495 }
496
497 /**
498 * @return {string}
499 */
500 resourceType() {
501 return this._resourceType;
502 }
503
504 /**
505 * @return {string}
506 */
507 method() {
508 return this._method;
509 }
510
511 /**
512 * @return {string}
513 */
514 postData() {
515 return this._postData;
516 }
517
518 /**
519 * @return {!Object}
520 */
521 headers() {
522 return this._headers;
523 }
524
525 /**
526 * @return {?Response}
527 */
528 response() {
529 return this._response;
530 }
531
532 /**
533 * @return {?Puppeteer.Frame}
534 */
535 frame() {
536 return this._frame;
537 }
538
539 /**
540 * @return {boolean}
541 */
542 isNavigationRequest() {
543 return this._isNavigationRequest;
544 }
545
546 /**
547 * @return {!Array<!Request>}
548 */
549 redirectChain() {
550 return this._redirectChain.slice();
551 }
552
553 /**
554 * @return {?{errorText: string}}
555 */
556 failure() {
557 if (!this._failureText)
558 return null;
559 return {
560 errorText: this._failureText
561 };
562 }
563
564 /**
565 * @param {!Object=} overrides
566 */
567 /* async */ continue(overrides = {}) {return (fn => {
568 const gen = fn.call(this);
569 return new Promise((resolve, reject) => {
570 function step(key, arg) {
571 let info, value;
572 try {
573 info = gen[key](arg);
574 value = info.value;
575 } catch (error) {
576 reject(error);
577 return;
578 }
579 if (info.done) {
580 resolve(value);
581 } else {
582 return Promise.resolve(value).then(
583 value => {
584 step('next', value);
585 },
586 err => {
587 step('throw', err);
588 });
589 }
590 }
591 return step('next');
592 });
593})(function*(){
594 assert(this._allowInterception, 'Request Interception is not enabled!');
595 assert(!this._interceptionHandled, 'Request is already handled!');
596 this._interceptionHandled = true;
597 (yield this._client.send('Network.continueInterceptedRequest', {
598 interceptionId: this._interceptionId,
599 url: overrides.url,
600 method: overrides.method,
601 postData: overrides.postData,
602 headers: overrides.headers,
603 }).catch(error => {
604 // In certain cases, protocol will return error if the request was already canceled
605 // or the page was closed. We should tolerate these errors.
606 debugError(error);
607 }));
608 });}
609
610 /**
611 * @param {!{status: number, headers: Object, contentType: string, body: (string|Buffer)}} response
612 */
613 /* async */ respond(response) {return (fn => {
614 const gen = fn.call(this);
615 return new Promise((resolve, reject) => {
616 function step(key, arg) {
617 let info, value;
618 try {
619 info = gen[key](arg);
620 value = info.value;
621 } catch (error) {
622 reject(error);
623 return;
624 }
625 if (info.done) {
626 resolve(value);
627 } else {
628 return Promise.resolve(value).then(
629 value => {
630 step('next', value);
631 },
632 err => {
633 step('throw', err);
634 });
635 }
636 }
637 return step('next');
638 });
639})(function*(){
640 // Mocking responses for dataURL requests is not currently supported.
641 if (this._url.startsWith('data:'))
642 return;
643 assert(this._allowInterception, 'Request Interception is not enabled!');
644 assert(!this._interceptionHandled, 'Request is already handled!');
645 this._interceptionHandled = true;
646
647 const responseBody = response.body && helper.isString(response.body) ? Buffer.from(/** @type {string} */(response.body)) : /** @type {?Buffer} */(response.body || null);
648
649 const responseHeaders = {};
650 if (response.headers) {
651 for (const header of Object.keys(response.headers))
652 responseHeaders[header.toLowerCase()] = response.headers[header];
653 }
654 if (response.contentType)
655 responseHeaders['content-type'] = response.contentType;
656 if (responseBody && !('content-length' in responseHeaders)) {
657 // @ts-ignore
658 responseHeaders['content-length'] = Buffer.byteLength(responseBody);
659 }
660
661 const statusCode = response.status || 200;
662 const statusText = statusTexts[statusCode] || '';
663 const statusLine = `HTTP/1.1 ${statusCode} ${statusText}`;
664
665 const CRLF = '\r\n';
666 let text = statusLine + CRLF;
667 for (const header of Object.keys(responseHeaders))
668 text += header + ': ' + responseHeaders[header] + CRLF;
669 text += CRLF;
670 let responseBuffer = Buffer.from(text, 'utf8');
671 if (responseBody)
672 responseBuffer = Buffer.concat([responseBuffer, responseBody]);
673
674 (yield this._client.send('Network.continueInterceptedRequest', {
675 interceptionId: this._interceptionId,
676 rawResponse: responseBuffer.toString('base64')
677 }).catch(error => {
678 // In certain cases, protocol will return error if the request was already canceled
679 // or the page was closed. We should tolerate these errors.
680 debugError(error);
681 }));
682 });}
683
684 /**
685 * @param {string=} errorCode
686 */
687 /* async */ abort(errorCode = 'failed') {return (fn => {
688 const gen = fn.call(this);
689 return new Promise((resolve, reject) => {
690 function step(key, arg) {
691 let info, value;
692 try {
693 info = gen[key](arg);
694 value = info.value;
695 } catch (error) {
696 reject(error);
697 return;
698 }
699 if (info.done) {
700 resolve(value);
701 } else {
702 return Promise.resolve(value).then(
703 value => {
704 step('next', value);
705 },
706 err => {
707 step('throw', err);
708 });
709 }
710 }
711 return step('next');
712 });
713})(function*(){
714 const errorReason = errorReasons[errorCode];
715 assert(errorReason, 'Unknown error code: ' + errorCode);
716 assert(this._allowInterception, 'Request Interception is not enabled!');
717 assert(!this._interceptionHandled, 'Request is already handled!');
718 this._interceptionHandled = true;
719 (yield this._client.send('Network.continueInterceptedRequest', {
720 interceptionId: this._interceptionId,
721 errorReason
722 }).catch(error => {
723 // In certain cases, protocol will return error if the request was already canceled
724 // or the page was closed. We should tolerate these errors.
725 debugError(error);
726 }));
727 });}
728}
729
730const errorReasons = {
731 'aborted': 'Aborted',
732 'accessdenied': 'AccessDenied',
733 'addressunreachable': 'AddressUnreachable',
734 'blockedbyclient': 'BlockedByClient',
735 'blockedbyresponse': 'BlockedByResponse',
736 'connectionaborted': 'ConnectionAborted',
737 'connectionclosed': 'ConnectionClosed',
738 'connectionfailed': 'ConnectionFailed',
739 'connectionrefused': 'ConnectionRefused',
740 'connectionreset': 'ConnectionReset',
741 'internetdisconnected': 'InternetDisconnected',
742 'namenotresolved': 'NameNotResolved',
743 'timedout': 'TimedOut',
744 'failed': 'Failed',
745};
746
747helper.tracePublicAPI(Request);
748
749class Response {
750 /**
751 * @param {!Puppeteer.CDPSession} client
752 * @param {!Request} request
753 * @param {number} status
754 * @param {!Object} headers
755 * @param {boolean} fromDiskCache
756 * @param {boolean} fromServiceWorker
757 * @param {?Object} securityDetails
758 */
759 constructor(client, request, status, headers, fromDiskCache, fromServiceWorker, securityDetails) {
760 this._client = client;
761 this._request = request;
762 this._contentPromise = null;
763
764 this._bodyLoadedPromise = new Promise(fulfill => {
765 this._bodyLoadedPromiseFulfill = fulfill;
766 });
767
768 this._status = status;
769 this._url = request.url();
770 this._fromDiskCache = fromDiskCache;
771 this._fromServiceWorker = fromServiceWorker;
772 this._headers = {};
773 for (const key of Object.keys(headers))
774 this._headers[key.toLowerCase()] = headers[key];
775 this._securityDetails = null;
776 if (securityDetails) {
777 this._securityDetails = new SecurityDetails(
778 securityDetails['subjectName'],
779 securityDetails['issuer'],
780 securityDetails['validFrom'],
781 securityDetails['validTo'],
782 securityDetails['protocol']);
783 }
784 }
785
786 /**
787 * @return {string}
788 */
789 url() {
790 return this._url;
791 }
792
793 /**
794 * @return {boolean}
795 */
796 ok() {
797 return this._status === 0 || (this._status >= 200 && this._status <= 299);
798 }
799
800 /**
801 * @return {number}
802 */
803 status() {
804 return this._status;
805 }
806
807 /**
808 * @return {!Object}
809 */
810 headers() {
811 return this._headers;
812 }
813
814 /**
815 * @return {?SecurityDetails}
816 */
817 securityDetails() {
818 return this._securityDetails;
819 }
820
821 /**
822 * @return {!Promise<!Buffer>}
823 */
824 buffer() {
825 if (!this._contentPromise) {
826 this._contentPromise = this._bodyLoadedPromise.then(/* async */ error => {return (fn => {
827 const gen = fn.call(this);
828 return new Promise((resolve, reject) => {
829 function step(key, arg) {
830 let info, value;
831 try {
832 info = gen[key](arg);
833 value = info.value;
834 } catch (error) {
835 reject(error);
836 return;
837 }
838 if (info.done) {
839 resolve(value);
840 } else {
841 return Promise.resolve(value).then(
842 value => {
843 step('next', value);
844 },
845 err => {
846 step('throw', err);
847 });
848 }
849 }
850 return step('next');
851 });
852})(function*(){
853 if (error)
854 throw error;
855 const response = (yield this._client.send('Network.getResponseBody', {
856 requestId: this._request._requestId
857 }));
858 return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
859 });});
860 }
861 return this._contentPromise;
862 }
863
864 /**
865 * @return {!Promise<string>}
866 */
867 /* async */ text() {return (fn => {
868 const gen = fn.call(this);
869 return new Promise((resolve, reject) => {
870 function step(key, arg) {
871 let info, value;
872 try {
873 info = gen[key](arg);
874 value = info.value;
875 } catch (error) {
876 reject(error);
877 return;
878 }
879 if (info.done) {
880 resolve(value);
881 } else {
882 return Promise.resolve(value).then(
883 value => {
884 step('next', value);
885 },
886 err => {
887 step('throw', err);
888 });
889 }
890 }
891 return step('next');
892 });
893})(function*(){
894 const content = (yield this.buffer());
895 return content.toString('utf8');
896 });}
897
898 /**
899 * @return {!Promise<!Object>}
900 */
901 /* async */ json() {return (fn => {
902 const gen = fn.call(this);
903 return new Promise((resolve, reject) => {
904 function step(key, arg) {
905 let info, value;
906 try {
907 info = gen[key](arg);
908 value = info.value;
909 } catch (error) {
910 reject(error);
911 return;
912 }
913 if (info.done) {
914 resolve(value);
915 } else {
916 return Promise.resolve(value).then(
917 value => {
918 step('next', value);
919 },
920 err => {
921 step('throw', err);
922 });
923 }
924 }
925 return step('next');
926 });
927})(function*(){
928 const content = (yield this.text());
929 return JSON.parse(content);
930 });}
931
932 /**
933 * @return {!Request}
934 */
935 request() {
936 return this._request;
937 }
938
939 /**
940 * @return {boolean}
941 */
942 fromCache() {
943 return this._fromDiskCache || this._request._fromMemoryCache;
944 }
945
946 /**
947 * @return {boolean}
948 */
949 fromServiceWorker() {
950 return this._fromServiceWorker;
951 }
952}
953helper.tracePublicAPI(Response);
954
955/**
956 * @param {!Protocol.Network.Request} request
957 * @return {string}
958 */
959function generateRequestHash(request) {
960 let normalizedURL = request.url;
961 try {
962 // Decoding is necessary to normalize URLs. @see crbug.com/759388
963 // The method will throw if the URL is malformed. In this case,
964 // consider URL to be normalized as-is.
965 normalizedURL = decodeURI(request.url);
966 } catch (e) {
967 }
968 const hash = {
969 url: normalizedURL,
970 method: request.method,
971 postData: request.postData,
972 headers: {},
973 };
974
975 if (!normalizedURL.startsWith('data:')) {
976 const headers = Object.keys(request.headers);
977 headers.sort();
978 for (let header of headers) {
979 const headerValue = request.headers[header];
980 header = header.toLowerCase();
981 if (header === 'accept' || header === 'referer' || header === 'x-devtools-emulate-network-conditions-client-id' || header === 'cookie')
982 continue;
983 hash.headers[header] = headerValue;
984 }
985 }
986 return JSON.stringify(hash);
987}
988
989class SecurityDetails {
990 /**
991 * @param {string} subjectName
992 * @param {string} issuer
993 * @param {number} validFrom
994 * @param {number} validTo
995 * @param {string} protocol
996 */
997
998 constructor(subjectName, issuer, validFrom, validTo, protocol) {
999 this._subjectName = subjectName;
1000 this._issuer = issuer;
1001 this._validFrom = validFrom;
1002 this._validTo = validTo;
1003 this._protocol = protocol;
1004 }
1005
1006 /**
1007 * @return {string}
1008 */
1009 subjectName() {
1010 return this._subjectName;
1011 }
1012
1013 /**
1014 * @return {string}
1015 */
1016 issuer() {
1017 return this._issuer;
1018 }
1019
1020 /**
1021 * @return {number}
1022 */
1023 validFrom() {
1024 return this._validFrom;
1025 }
1026
1027 /**
1028 * @return {number}
1029 */
1030 validTo() {
1031 return this._validTo;
1032 }
1033
1034 /**
1035 * @return {string}
1036 */
1037 protocol() {
1038 return this._protocol;
1039 }
1040}
1041
1042NetworkManager.Events = {
1043 Request: 'request',
1044 Response: 'response',
1045 RequestFailed: 'requestfailed',
1046 RequestFinished: 'requestfinished',
1047};
1048
1049const statusTexts = {
1050 '100': 'Continue',
1051 '101': 'Switching Protocols',
1052 '102': 'Processing',
1053 '200': 'OK',
1054 '201': 'Created',
1055 '202': 'Accepted',
1056 '203': 'Non-Authoritative Information',
1057 '204': 'No Content',
1058 '206': 'Partial Content',
1059 '207': 'Multi-Status',
1060 '208': 'Already Reported',
1061 '209': 'IM Used',
1062 '300': 'Multiple Choices',
1063 '301': 'Moved Permanently',
1064 '302': 'Found',
1065 '303': 'See Other',
1066 '304': 'Not Modified',
1067 '305': 'Use Proxy',
1068 '306': 'Switch Proxy',
1069 '307': 'Temporary Redirect',
1070 '308': 'Permanent Redirect',
1071 '400': 'Bad Request',
1072 '401': 'Unauthorized',
1073 '402': 'Payment Required',
1074 '403': 'Forbidden',
1075 '404': 'Not Found',
1076 '405': 'Method Not Allowed',
1077 '406': 'Not Acceptable',
1078 '407': 'Proxy Authentication Required',
1079 '408': 'Request Timeout',
1080 '409': 'Conflict',
1081 '410': 'Gone',
1082 '411': 'Length Required',
1083 '412': 'Precondition Failed',
1084 '413': 'Payload Too Large',
1085 '414': 'URI Too Long',
1086 '415': 'Unsupported Media Type',
1087 '416': 'Range Not Satisfiable',
1088 '417': 'Expectation Failed',
1089 '418': 'I\'m a teapot',
1090 '421': 'Misdirected Request',
1091 '422': 'Unprocessable Entity',
1092 '423': 'Locked',
1093 '424': 'Failed Dependency',
1094 '426': 'Upgrade Required',
1095 '428': 'Precondition Required',
1096 '429': 'Too Many Requests',
1097 '431': 'Request Header Fields Too Large',
1098 '451': 'Unavailable For Legal Reasons',
1099 '500': 'Internal Server Error',
1100 '501': 'Not Implemented',
1101 '502': 'Bad Gateway',
1102 '503': 'Service Unavailable',
1103 '504': 'Gateway Timeout',
1104 '505': 'HTTP Version Not Supported',
1105 '506': 'Variant Also Negotiates',
1106 '507': 'Insufficient Storage',
1107 '508': 'Loop Detected',
1108 '510': 'Not Extended',
1109 '511': 'Network Authentication Required',
1110};
1111
1112module.exports = {Request, Response, NetworkManager};