UNPKG

chrome-devtools-frontend

Version:
1,811 lines (1,600 loc) • 55.7 kB
/* * Copyright (C) 2012 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import * as Common from '../common/common.js'; import * as Platform from '../platform/platform.js'; import {ls} from '../platform/platform.js'; import * as TextUtils from '../text_utils/text_utils.js'; import {Attributes, Cookie} from './Cookie.js'; // eslint-disable-line no-unused-vars import {CookieParser} from './CookieParser.js'; import {NetworkManager} from './NetworkManager.js'; import {Type} from './SDKModel.js'; import {ServerTiming} from './ServerTiming.js'; /** @enum {string} */ export const MIME_TYPE = { HTML: 'text/html', XML: 'text/xml', PLAIN: 'text/plain', XHTML: 'application/xhtml+xml', SVG: 'image/svg+xml', CSS: 'text/css', XSL: 'text/xsl', VTT: 'text/vtt', PDF: 'application/pdf', }; /** @type {!Map<!MIME_TYPE, *>} */ export const MIME_TYPE_TO_RESOURCE_TYPE = new Map([ [MIME_TYPE.HTML, {'document': true}], [MIME_TYPE.XML, {'document': true}], [MIME_TYPE.PLAIN, {'document': true}], [MIME_TYPE.XHTML, {'document': true}], [MIME_TYPE.SVG, {'document': true}], [MIME_TYPE.CSS, {'stylesheet': true}], [MIME_TYPE.XSL, {'stylesheet': true}], [MIME_TYPE.VTT, {'texttrack': true}], [MIME_TYPE.PDF, {'document': true}], ]); /** * @implements {TextUtils.ContentProvider.ContentProvider} */ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper { /** * @param {!Protocol.Network.RequestId} requestId * @param {string} url * @param {string} documentURL * @param {!Protocol.Page.FrameId} frameId * @param {!Protocol.Network.LoaderId} loaderId * @param {?Protocol.Network.Initiator} initiator */ constructor(requestId, url, documentURL, frameId, loaderId, initiator) { super(); this._requestId = requestId; this._backendRequestId = requestId; this.setUrl(url); this._documentURL = documentURL; this._frameId = frameId; this._loaderId = loaderId; /** @type {(?Protocol.Network.Initiator|undefined)} */ this._initiator = initiator; /** @type {?NetworkRequest} */ this._redirectSource = null; /** @type {?NetworkRequest} */ this._preflightRequest = null; /** @type {?NetworkRequest} */ this._preflightInitiatorRequest = null; /** @type {boolean} */ this._isRedirect = false; /** @type {?NetworkRequest} */ this._redirectDestination = null; this._issueTime = -1; this._startTime = -1; this._endTime = -1; /** @type {!Protocol.Network.BlockedReason|undefined} */ this._blockedReason = undefined; /** @type {!Protocol.Network.CorsErrorStatus|undefined} */ this._corsErrorStatus = undefined; this.statusCode = 0; this.statusText = ''; this.requestMethod = ''; this.requestTime = 0; /** @type {string} */ this.protocol = ''; /** @type {!Protocol.Security.MixedContentType} */ this.mixedContentType = Protocol.Security.MixedContentType.None; /** @type {?Protocol.Network.ResourcePriority} */ this._initialPriority = null; /** @type {?Protocol.Network.ResourcePriority} */ this._currentPriority = null; /** @type {?Protocol.Network.SignedExchangeInfo} */ this._signedExchangeInfo = null; /** @type {!Common.ResourceType.ResourceType} */ this._resourceType = Common.ResourceType.resourceTypes.Other; /** @type {?Promise<!ContentData>} */ this._contentData = null; /** @type {!Array.<!WebSocketFrame>} */ this._frames = []; /** @type {!Array.<!EventSourceMessage>} */ this._eventSourceMessages = []; /** @type {!Object<string, (string|undefined)>} */ this._responseHeaderValues = {}; this._responseHeadersText = ''; /** @type {!Array<!NameValue>} */ this._requestHeaders = []; /** @type {!Object<string, (string|undefined)>} */ this._requestHeaderValues = {}; this._remoteAddress = ''; this._remoteAddressSpace = Protocol.Network.IPAddressSpace.Unknown; /** @type {?Protocol.Network.RequestReferrerPolicy} */ this._referrerPolicy = null; /** @type {!Protocol.Security.SecurityState} */ this._securityState = Protocol.Security.SecurityState.Unknown; /** @type {?Protocol.Network.SecurityDetails} */ this._securityDetails = null; /** @type {string} */ this.connectionId = '0'; /** @type {boolean} */ this.connectionReused = false; /** @type {boolean} */ this.hasNetworkData = false; /** @type {?Promise<?Array.<!NameValue>>} */ this._formParametersPromise = null; // Assume no body initially /** @type {?Promise<?string>} */ this._requestFormDataPromise = /** @type {?Promise<?string>} */ (Promise.resolve(null)); /** @type {boolean} */ this._hasExtraRequestInfo = false; /** @type {boolean} */ this._hasExtraResponseInfo = false; /** @type {!Array<!BlockedCookieWithReason>} */ this._blockedRequestCookies = []; /** @type {!Array<!Cookie>} */ this._includedRequestCookies = []; /** @type {!Array<!BlockedSetCookieWithReason>} */ this._blockedResponseCookies = []; /** @type {?string} */ this.localizedFailDescription = null; /** @type {string} */ this._url; /** @type {number} */ this._responseReceivedTime; /** @type {number} */ this._transferSize; /** @type {boolean} */ this._finished; /** @type {boolean} */ this._failed; /** @type {boolean} */ this._canceled; /** @type {!MIME_TYPE} */ this._mimeType; /** @type {!Common.ParsedURL.ParsedURL} */ this._parsedURL; /** @type {(string|undefined)} */ this._name; /** @type {(string|undefined)} */ this._path; /** @type {(!Protocol.Network.ClientSecurityState|undefined)} */ this._clientSecurityState; /** @type {(!Protocol.Network.TrustTokenParams|undefined)} */ this._trustTokenParams; /** @type {(!Protocol.Network.TrustTokenOperationDoneEvent|undefined)} */ this._trustTokenOperationDoneEvent; } /** * @param {!NetworkRequest} other * @return {number} */ identityCompare(other) { const thisId = this.requestId(); const thatId = other.requestId(); if (thisId > thatId) { return 1; } if (thisId < thatId) { return -1; } return 0; } /** * @return {!Protocol.Network.RequestId} */ requestId() { return this._requestId; } /** * @return {!Protocol.Network.RequestId} */ backendRequestId() { return this._backendRequestId; } /** * @return {string} */ url() { return this._url; } /** * @return {boolean} */ isBlobRequest() { return this._url.startsWith('blob:'); } /** * @param {string} x */ setUrl(x) { if (this._url === x) { return; } this._url = x; this._parsedURL = new Common.ParsedURL.ParsedURL(x); delete this._queryString; delete this._parsedQueryParameters; delete this._name; delete this._path; } /** * @return {string} */ get documentURL() { return this._documentURL; } get parsedURL() { return this._parsedURL; } /** * @return {!Protocol.Page.FrameId} */ get frameId() { return this._frameId; } /** * @return {!Protocol.Network.LoaderId} */ get loaderId() { return this._loaderId; } /** * @param {string} ip * @param {number} port */ setRemoteAddress(ip, port) { this._remoteAddress = ip + ':' + port; this.dispatchEventToListeners(Events.RemoteAddressChanged, this); } /** * @return {string} */ remoteAddress() { return this._remoteAddress; } /** * @return {Protocol.Network.IPAddressSpace} */ remoteAddressSpace() { return this._remoteAddressSpace; } /** * @return {string | undefined} * The cache name of the CacheStorage from where the response is served via * the ServiceWorker. */ getResponseCacheStorageCacheName() { return this._responseCacheStorageCacheName; } /** * @param {string} x */ setResponseCacheStorageCacheName(x) { this._responseCacheStorageCacheName = x; } /** * @return {!Protocol.Network.ServiceWorkerResponseSource | undefined} */ serviceWorkerResponseSource() { return this._serviceWorkerResponseSource; } /** * @param {!Protocol.Network.ServiceWorkerResponseSource} serviceWorkerResponseSource */ setServiceWorkerResponseSource(serviceWorkerResponseSource) { this._serviceWorkerResponseSource = serviceWorkerResponseSource; } /** * @param {!Protocol.Network.RequestReferrerPolicy} referrerPolicy */ setReferrerPolicy(referrerPolicy) { this._referrerPolicy = referrerPolicy; } /** * @return {?Protocol.Network.RequestReferrerPolicy} */ referrerPolicy() { return this._referrerPolicy; } /** * @return {!Protocol.Security.SecurityState} */ securityState() { return this._securityState; } /** * @param {!Protocol.Security.SecurityState} securityState */ setSecurityState(securityState) { this._securityState = securityState; } /** * @return {?Protocol.Network.SecurityDetails} */ securityDetails() { return this._securityDetails; } /** * @return {string} */ securityOrigin() { return this._parsedURL.securityOrigin(); } /** * @param {!Protocol.Network.SecurityDetails} securityDetails */ setSecurityDetails(securityDetails) { this._securityDetails = securityDetails; } /** * @return {number} */ get startTime() { return this._startTime || -1; } /** * @param {number} monotonicTime * @param {number} wallTime */ setIssueTime(monotonicTime, wallTime) { this._issueTime = monotonicTime; this._wallIssueTime = wallTime; this._startTime = monotonicTime; } /** * @return {number} */ issueTime() { return this._issueTime; } /** * @param {number} monotonicTime * @return {number} */ pseudoWallTime(monotonicTime) { return this._wallIssueTime ? this._wallIssueTime - this._issueTime + monotonicTime : monotonicTime; } /** * @return {number} */ get responseReceivedTime() { return this._responseReceivedTime || -1; } /** * @param {number} x */ set responseReceivedTime(x) { this._responseReceivedTime = x; } /** * @return {!Date | undefined} * The time at which the returned response was generated. For cached * responses, this is the last time the cache entry was validated. */ getResponseRetrievalTime() { return this._responseRetrievalTime; } /** * @param {!Date} x */ setResponseRetrievalTime(x) { this._responseRetrievalTime = x; } /** * @return {number} */ get endTime() { return this._endTime || -1; } /** * @param {number} x */ set endTime(x) { if (this.timing && this.timing.requestTime) { // Check against accurate responseReceivedTime. this._endTime = Math.max(x, this.responseReceivedTime); } else { // Prefer endTime since it might be from the network stack. this._endTime = x; if (this._responseReceivedTime > x) { this._responseReceivedTime = x; } } this.dispatchEventToListeners(Events.TimingChanged, this); } /** * @return {number} */ get duration() { if (this._endTime === -1 || this._startTime === -1) { return -1; } return this._endTime - this._startTime; } /** * @return {number} */ get latency() { if (this._responseReceivedTime === -1 || this._startTime === -1) { return -1; } return this._responseReceivedTime - this._startTime; } /** * @return {number} */ get resourceSize() { return this._resourceSize || 0; } /** * @param {number} x */ set resourceSize(x) { this._resourceSize = x; } /** * @return {number} */ get transferSize() { return this._transferSize || 0; } /** * @param {number} x */ increaseTransferSize(x) { this._transferSize = (this._transferSize || 0) + x; } /** * @param {number} x */ setTransferSize(x) { this._transferSize = x; } /** * @return {boolean} */ get finished() { return this._finished; } /** * @param {boolean} x */ set finished(x) { if (this._finished === x) { return; } this._finished = x; if (x) { this.dispatchEventToListeners(Events.FinishedLoading, this); } } /** * @return {boolean} */ get failed() { return this._failed; } /** * @param {boolean} x */ set failed(x) { this._failed = x; } /** * @return {boolean} */ get canceled() { return this._canceled; } /** * @param {boolean} x */ set canceled(x) { this._canceled = x; } /** * @return {!Protocol.Network.BlockedReason|undefined} */ blockedReason() { return this._blockedReason; } /** * @param {!Protocol.Network.BlockedReason} reason */ setBlockedReason(reason) { this._blockedReason = reason; } /** * @return {!Protocol.Network.CorsErrorStatus|undefined} */ corsErrorStatus() { return this._corsErrorStatus; } /** * @param {!Protocol.Network.CorsErrorStatus} corsErrorStatus */ setCorsErrorStatus(corsErrorStatus) { this._corsErrorStatus = corsErrorStatus; } /** * @return {boolean} */ wasBlocked() { return Boolean(this._blockedReason); } /** * @return {boolean} */ cached() { return (Boolean(this._fromMemoryCache) || Boolean(this._fromDiskCache)) && !this._transferSize; } /** * @return {boolean} */ cachedInMemory() { return Boolean(this._fromMemoryCache) && !this._transferSize; } /** * @return {boolean} */ fromPrefetchCache() { return Boolean(this._fromPrefetchCache); } setFromMemoryCache() { this._fromMemoryCache = true; delete this._timing; } setFromDiskCache() { this._fromDiskCache = true; } setFromPrefetchCache() { this._fromPrefetchCache = true; } /** * Returns true if the request was intercepted by a service worker and it * provided its own response. * @return {boolean} */ get fetchedViaServiceWorker() { return Boolean(this._fetchedViaServiceWorker); } /** * @param {boolean} x */ set fetchedViaServiceWorker(x) { this._fetchedViaServiceWorker = x; } /** * Returns true if the request was sent by a service worker. * @return {boolean} */ initiatedByServiceWorker() { const networkManager = NetworkManager.forRequest(this); if (!networkManager) { return false; } return networkManager.target().type() === Type.ServiceWorker; } /** * @return {!Protocol.Network.ResourceTiming|undefined} */ get timing() { return this._timing; } /** * @param {!Protocol.Network.ResourceTiming|undefined} timingInfo */ set timing(timingInfo) { if (!timingInfo || this._fromMemoryCache) { return; } // Take startTime and responseReceivedTime from timing data for better accuracy. // Timing's requestTime is a baseline in seconds, rest of the numbers there are ticks in millis. this._startTime = timingInfo.requestTime; const headersReceivedTime = timingInfo.requestTime + timingInfo.receiveHeadersEnd / 1000.0; if ((this._responseReceivedTime || -1) < 0 || this._responseReceivedTime > headersReceivedTime) { this._responseReceivedTime = headersReceivedTime; } if (this._startTime > this._responseReceivedTime) { this._responseReceivedTime = this._startTime; } this._timing = timingInfo; this.dispatchEventToListeners(Events.TimingChanged, this); } /** * @return {!MIME_TYPE} */ get mimeType() { return this._mimeType; } /** * @param {!MIME_TYPE} x */ set mimeType(x) { this._mimeType = x; } /** * @return {string} */ get displayName() { return this._parsedURL.displayName; } /** * @return {string} */ name() { if (this._name) { return this._name; } this._parseNameAndPathFromURL(); return /** @type {string} */ (this._name); } /** * @return {string} */ path() { if (this._path) { return this._path; } this._parseNameAndPathFromURL(); return /** @type {string} */ (this._path); } _parseNameAndPathFromURL() { if (this._parsedURL.isDataURL()) { this._name = this._parsedURL.dataURLDisplayName(); this._path = ''; } else if (this._parsedURL.isBlobURL()) { this._name = this._parsedURL.url; this._path = ''; } else if (this._parsedURL.isAboutBlank()) { this._name = this._parsedURL.url; this._path = ''; } else { this._path = this._parsedURL.host + this._parsedURL.folderPathComponents; const networkManager = NetworkManager.forRequest(this); const inspectedURL = networkManager ? Common.ParsedURL.ParsedURL.fromString(networkManager.target().inspectedURL()) : null; this._path = Platform.StringUtilities.trimURL(this._path, inspectedURL ? inspectedURL.host : ''); if (this._parsedURL.lastPathComponent || this._parsedURL.queryParams) { this._name = this._parsedURL.lastPathComponent + (this._parsedURL.queryParams ? '?' + this._parsedURL.queryParams : ''); } else if (this._parsedURL.folderPathComponents) { this._name = this._parsedURL.folderPathComponents.substring(this._parsedURL.folderPathComponents.lastIndexOf('/') + 1) + '/'; this._path = this._path.substring(0, this._path.lastIndexOf('/')); } else { this._name = this._parsedURL.host; this._path = ''; } } } /** * @return {string} */ get folder() { let path = this._parsedURL.path; const indexOfQuery = path.indexOf('?'); if (indexOfQuery !== -1) { path = path.substring(0, indexOfQuery); } const lastSlashIndex = path.lastIndexOf('/'); return lastSlashIndex !== -1 ? path.substring(0, lastSlashIndex) : ''; } /** * @return {string} */ get pathname() { return this._parsedURL.path; } /** * @return {!Common.ResourceType.ResourceType} */ resourceType() { return this._resourceType; } /** * @param {!Common.ResourceType.ResourceType} resourceType */ setResourceType(resourceType) { this._resourceType = resourceType; } /** * @return {string} */ get domain() { return this._parsedURL.host; } /** * @return {string} */ get scheme() { return this._parsedURL.scheme; } /** * @return {?NetworkRequest} */ redirectSource() { return this._redirectSource; } /** * @param {?NetworkRequest} originatingRequest */ setRedirectSource(originatingRequest) { this._redirectSource = originatingRequest; } /** * @return {?NetworkRequest} */ preflightRequest() { return this._preflightRequest; } /** * @param {?NetworkRequest} preflightRequest */ setPreflightRequest(preflightRequest) { this._preflightRequest = preflightRequest; } /** * @return {?NetworkRequest} */ preflightInitiatorRequest() { return this._preflightInitiatorRequest; } /** * @param {?NetworkRequest} preflightInitiatorRequest */ setPreflightInitiatorRequest(preflightInitiatorRequest) { this._preflightInitiatorRequest = preflightInitiatorRequest; } /** * @return {boolean} */ isPreflightRequest() { return this._initiator !== null && this._initiator !== undefined && this._initiator.type === Protocol.Network.InitiatorType.Preflight; } /** * @return {?NetworkRequest} */ redirectDestination() { return this._redirectDestination; } /** * @param {?NetworkRequest} redirectDestination */ setRedirectDestination(redirectDestination) { this._redirectDestination = redirectDestination; } /** * @return {!Array.<!NameValue>} */ requestHeaders() { return this._requestHeaders; } /** * @param {!Array.<!NameValue>} headers */ setRequestHeaders(headers) { this._requestHeaders = headers; delete this._requestCookies; this.dispatchEventToListeners(Events.RequestHeadersChanged); } /** * @return {string|undefined} */ requestHeadersText() { return this._requestHeadersText; } /** * @param {string} text */ setRequestHeadersText(text) { this._requestHeadersText = text; this.dispatchEventToListeners(Events.RequestHeadersChanged); } /** * @param {string} headerName * @return {string|undefined} */ requestHeaderValue(headerName) { if (this._requestHeaderValues[headerName]) { return this._requestHeaderValues[headerName]; } this._requestHeaderValues[headerName] = this._computeHeaderValue(this.requestHeaders(), headerName); return this._requestHeaderValues[headerName]; } /** * @return {!Array.<!Cookie>} */ get requestCookies() { if (!this._requestCookies) { this._requestCookies = CookieParser.parseCookie(this.requestHeaderValue('Cookie')) || []; } return this._requestCookies; } /** * @return {!Promise<?string>} */ requestFormData() { if (!this._requestFormDataPromise) { this._requestFormDataPromise = NetworkManager.requestPostData(this); } return this._requestFormDataPromise; } /** * @param {boolean} hasData * @param {?string} data */ setRequestFormData(hasData, data) { this._requestFormDataPromise = (hasData && data === null) ? null : Promise.resolve(data); this._formParametersPromise = null; } /** * @return {string} */ _filteredProtocolName() { const protocol = this.protocol.toLowerCase(); if (protocol === 'h2') { return 'http/2.0'; } return protocol.replace(/^http\/2(\.0)?\+/, 'http/2.0+'); } /** * @return {string} */ requestHttpVersion() { const headersText = this.requestHeadersText(); if (!headersText) { const version = this.requestHeaderValue('version') || this.requestHeaderValue(':version'); if (version) { return version; } return this._filteredProtocolName(); } const firstLine = headersText.split(/\r\n/)[0]; const match = firstLine.match(/(HTTP\/\d+\.\d+)$/); return match ? match[1] : 'HTTP/0.9'; } /** * @return {!Array.<!NameValue>} */ get responseHeaders() { return this._responseHeaders || []; } /** * @param {!Array.<!NameValue>} x */ set responseHeaders(x) { this._responseHeaders = x; delete this._sortedResponseHeaders; delete this._serverTimings; delete this._responseCookies; this._responseHeaderValues = {}; this.dispatchEventToListeners(Events.ResponseHeadersChanged); } /** * @return {string} */ get responseHeadersText() { return this._responseHeadersText; } /** * @param {string} x */ set responseHeadersText(x) { this._responseHeadersText = x; this.dispatchEventToListeners(Events.ResponseHeadersChanged); } /** * @return {!Array.<!NameValue>} */ get sortedResponseHeaders() { if (this._sortedResponseHeaders !== undefined) { return this._sortedResponseHeaders; } this._sortedResponseHeaders = this.responseHeaders.slice(); this._sortedResponseHeaders.sort(function(a, b) { return Platform.StringUtilities.compare(a.name.toLowerCase(), b.name.toLowerCase()); }); return this._sortedResponseHeaders; } /** * @param {string} headerName * @return {string|undefined} */ responseHeaderValue(headerName) { if (headerName in this._responseHeaderValues) { return this._responseHeaderValues[headerName]; } this._responseHeaderValues[headerName] = this._computeHeaderValue(this.responseHeaders, headerName); return this._responseHeaderValues[headerName]; } /** * @return {!Array.<!Cookie>} */ get responseCookies() { if (!this._responseCookies) { this._responseCookies = CookieParser.parseSetCookie(this.responseHeaderValue('Set-Cookie'), this.domain) || []; } return this._responseCookies; } /** * @return {string|undefined} */ responseLastModified() { return this.responseHeaderValue('last-modified'); } /** * @return {!Array.<!Cookie>} */ allCookiesIncludingBlockedOnes() { return /** @type {!Array.<!Cookie>} */ ([ ...this.includedRequestCookies(), ...this.responseCookies, ...this.blockedRequestCookies().map(blockedRequestCookie => blockedRequestCookie.cookie), ...this.blockedResponseCookies().map(blockedResponseCookie => blockedResponseCookie.cookie), // blockedRequestCookie or blockedResponseCookie might not contain a cookie in case of SyntaxErrors: ].filter(v => Boolean(v))); } /** * @return {?Array.<!ServerTiming>} */ get serverTimings() { if (typeof this._serverTimings === 'undefined') { this._serverTimings = ServerTiming.parseHeaders(this.responseHeaders); } return this._serverTimings; } /** * @return {?string} */ queryString() { if (this._queryString !== undefined) { return this._queryString; } let queryString = null; const url = this.url(); const questionMarkPosition = url.indexOf('?'); if (questionMarkPosition !== -1) { queryString = url.substring(questionMarkPosition + 1); const hashSignPosition = queryString.indexOf('#'); if (hashSignPosition !== -1) { queryString = queryString.substring(0, hashSignPosition); } } this._queryString = queryString; return this._queryString; } /** * @return {?Array.<!NameValue>} */ get queryParameters() { if (this._parsedQueryParameters) { return this._parsedQueryParameters; } const queryString = this.queryString(); if (!queryString) { return null; } this._parsedQueryParameters = this._parseParameters(queryString); return this._parsedQueryParameters; } /** * @return {!Promise<?Array<!NameValue>>} */ async _parseFormParameters() { const requestContentType = this.requestContentType(); if (!requestContentType) { return null; } // Handling application/x-www-form-urlencoded request bodies. if (requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) { const formData = await this.requestFormData(); if (!formData) { return null; } return this._parseParameters(formData); } // Handling multipart/form-data request bodies. const multipartDetails = requestContentType.match(/^multipart\/form-data\s*;\s*boundary\s*=\s*(\S+)\s*$/); if (!multipartDetails) { return null; } const boundary = multipartDetails[1]; if (!boundary) { return null; } const formData = await this.requestFormData(); if (!formData) { return null; } return this._parseMultipartFormDataParameters(formData, boundary); } /** * @return {!Promise<?Array<!NameValue>>} */ formParameters() { if (!this._formParametersPromise) { this._formParametersPromise = this._parseFormParameters(); } return this._formParametersPromise; } /** * @return {string} */ responseHttpVersion() { const headersText = this._responseHeadersText; if (!headersText) { const version = this.responseHeaderValue('version') || this.responseHeaderValue(':version'); if (version) { return version; } return this._filteredProtocolName(); } const firstLine = headersText.split(/\r\n/)[0]; const match = firstLine.match(/^(HTTP\/\d+\.\d+)/); return match ? match[1] : 'HTTP/0.9'; } /** * @param {string} queryString * @return {!Array.<!NameValue>} */ _parseParameters(queryString) { /** * @param {string} pair */ function parseNameValue(pair) { const position = pair.indexOf('='); if (position === -1) { return {name: pair, value: ''}; } return {name: pair.substring(0, position), value: pair.substring(position + 1)}; } return queryString.split('&').map(parseNameValue); } /** * Parses multipart/form-data; boundary=boundaryString request bodies - * --boundaryString * Content-Disposition: form-data; name="field-name"; filename="r.gif" * Content-Type: application/octet-stream * * optionalValue * --boundaryString * Content-Disposition: form-data; name="field-name-2" * * optionalValue2 * --boundaryString-- * * @param {string} data * @param {string} boundary * @return {!Array.<!NameValue>} */ _parseMultipartFormDataParameters(data, boundary) { const sanitizedBoundary = Platform.StringUtilities.escapeForRegExp(boundary); const keyValuePattern = new RegExp( // Header with an optional file name. '^\\r\\ncontent-disposition\\s*:\\s*form-data\\s*;\\s*name="([^"]*)"(?:\\s*;\\s*filename="([^"]*)")?' + // Optional secondary header with the content type. '(?:\\r\\ncontent-type\\s*:\\s*([^\\r\\n]*))?' + // Padding. '\\r\\n\\r\\n' + // Value '(.*)' + // Padding. '\\r\\n$', 'is'); const fields = data.split(new RegExp(`--${sanitizedBoundary}(?:--\s*$)?`, 'g')); return fields.reduce(parseMultipartField, []); /** * @param {!Array.<!NameValue>} result * @param {string} field * @return {!Array.<!NameValue>} */ function parseMultipartField(result, field) { const [match, name, filename, contentType, value] = field.match(keyValuePattern) || []; if (!match) { return result; } const processedValue = (filename || contentType) ? ls`(binary)` : value; result.push({name, value: processedValue}); return result; } } /** * @param {!Array.<!NameValue>} headers * @param {string} headerName * @return {string|undefined} */ _computeHeaderValue(headers, headerName) { headerName = headerName.toLowerCase(); const values = []; for (let i = 0; i < headers.length; ++i) { if (headers[i].name.toLowerCase() === headerName) { values.push(headers[i].value); } } if (!values.length) { return undefined; } // Set-Cookie values should be separated by '\n', not comma, otherwise cookies could not be parsed. if (headerName === 'set-cookie') { return values.join('\n'); } return values.join(', '); } /** * @return {!Promise<!ContentData>} */ contentData() { if (this._contentData) { return this._contentData; } if (this._contentDataProvider) { this._contentData = this._contentDataProvider(); } else { this._contentData = NetworkManager.requestContentData(this); } return this._contentData; } /** * @param {function():!Promise<!ContentData>} dataProvider */ setContentDataProvider(dataProvider) { console.assert(!this._contentData, 'contentData can only be set once.'); this._contentDataProvider = dataProvider; } /** * @override * @return {string} */ contentURL() { return this._url; } /** * @override * @return {!Common.ResourceType.ResourceType} */ contentType() { return this._resourceType; } /** * @override * @return {!Promise<boolean>} */ async contentEncoded() { return (await this.contentData()).encoded; } /** * @override * @return {!Promise<!TextUtils.ContentProvider.DeferredContent>} */ async requestContent() { const {content, error, encoded} = await this.contentData(); return /** @type{!TextUtils.ContentProvider.DeferredContent} */ ({ content, error, isEncoded: encoded, }); } /** * @override * @param {string} query * @param {boolean} caseSensitive * @param {boolean} isRegex * @return {!Promise<!Array<!TextUtils.ContentProvider.SearchMatch>>} */ async searchInContent(query, caseSensitive, isRegex) { if (!this._contentDataProvider) { return NetworkManager.searchInRequest(this, query, caseSensitive, isRegex); } const contentData = await this.contentData(); let content = contentData.content; if (!content) { return []; } if (contentData.encoded) { content = window.atob(content); } return TextUtils.TextUtils.performSearchInContent(content, query, caseSensitive, isRegex); } /** * @return {boolean} */ isHttpFamily() { return Boolean(this.url().match(/^https?:/i)); } /** * @return {string|undefined} */ requestContentType() { return this.requestHeaderValue('Content-Type'); } /** * @return {boolean} */ hasErrorStatusCode() { return this.statusCode >= 400; } /** * @param {!Protocol.Network.ResourcePriority} priority */ setInitialPriority(priority) { this._initialPriority = priority; } /** * @return {?Protocol.Network.ResourcePriority} */ initialPriority() { return this._initialPriority; } /** * @param {!Protocol.Network.ResourcePriority} priority */ setPriority(priority) { this._currentPriority = priority; } /** * @return {?Protocol.Network.ResourcePriority} */ priority() { return this._currentPriority || this._initialPriority || null; } /** * @param {!Protocol.Network.SignedExchangeInfo} info */ setSignedExchangeInfo(info) { this._signedExchangeInfo = info; } /** * @return {?Protocol.Network.SignedExchangeInfo} */ signedExchangeInfo() { return this._signedExchangeInfo; } /** * @param {!HTMLImageElement} image */ async populateImageSource(image) { const {content, encoded} = await this.contentData(); let imageSrc = TextUtils.ContentProvider.contentAsDataURL(content, this._mimeType, encoded); if (imageSrc === null && !this._failed) { const cacheControl = this.responseHeaderValue('cache-control') || ''; if (!cacheControl.includes('no-cache')) { imageSrc = this._url; } } if (imageSrc !== null) { image.src = imageSrc; } } /** * @return {?Protocol.Network.Initiator} */ initiator() { return this._initiator || null; } /** * @return {!Array.<!WebSocketFrame>} */ frames() { return this._frames; } /** * @param {string} errorMessage * @param {number} time */ addProtocolFrameError(errorMessage, time) { this.addFrame( {type: WebSocketFrameType.Error, text: errorMessage, time: this.pseudoWallTime(time), opCode: -1, mask: false}); } /** * @param {!Protocol.Network.WebSocketFrame} response * @param {number} time * @param {boolean} sent */ addProtocolFrame(response, time, sent) { const type = sent ? WebSocketFrameType.Send : WebSocketFrameType.Receive; this.addFrame({ type: type, text: response.payloadData, time: this.pseudoWallTime(time), opCode: response.opcode, mask: response.mask }); } /** * @param {!WebSocketFrame} frame */ addFrame(frame) { this._frames.push(frame); this.dispatchEventToListeners(Events.WebsocketFrameAdded, frame); } /** * @return {!Array.<!EventSourceMessage>} */ eventSourceMessages() { return this._eventSourceMessages; } /** * @param {number} time * @param {string} eventName * @param {string} eventId * @param {string} data */ addEventSourceMessage(time, eventName, eventId, data) { const message = {time: this.pseudoWallTime(time), eventName: eventName, eventId: eventId, data: data}; this._eventSourceMessages.push(message); this.dispatchEventToListeners(Events.EventSourceMessageAdded, message); } /** * @param {number} redirectCount */ markAsRedirect(redirectCount) { this._isRedirect = true; this._requestId = `${this._backendRequestId}:redirected.${redirectCount}`; } /** * @return {boolean} */ isRedirect() { return this._isRedirect; } /** * @param {string} requestId */ setRequestIdForTest(requestId) { this._backendRequestId = requestId; this._requestId = requestId; } /** * @return {?string} */ charset() { const contentTypeHeader = this.responseHeaderValue('content-type'); if (!contentTypeHeader) { return null; } const responseCharsets = contentTypeHeader.replace(/ /g, '') .split(';') .filter(parameter => parameter.toLowerCase().startsWith('charset=')) .map(parameter => parameter.slice('charset='.length)); if (responseCharsets.length) { return responseCharsets[0]; } return null; } /** * @param {!ExtraRequestInfo} extraRequestInfo */ addExtraRequestInfo(extraRequestInfo) { this._blockedRequestCookies = extraRequestInfo.blockedRequestCookies; this._includedRequestCookies = extraRequestInfo.includedRequestCookies; this.setRequestHeaders(extraRequestInfo.requestHeaders); this._hasExtraRequestInfo = true; this.setRequestHeadersText(''); // Mark request headers as non-provisional this._clientSecurityState = extraRequestInfo.clientSecurityState; } /** * @return {boolean} */ hasExtraRequestInfo() { return this._hasExtraRequestInfo; } /** * @return {!Array<!BlockedCookieWithReason>} */ blockedRequestCookies() { return this._blockedRequestCookies; } /** * @return {!Array<!Cookie>} */ includedRequestCookies() { return this._includedRequestCookies; } /** * @return {boolean} */ hasRequestCookies() { return this._includedRequestCookies.length > 0 || this._blockedRequestCookies.length > 0; } /** * @param {!ExtraResponseInfo} extraResponseInfo */ addExtraResponseInfo(extraResponseInfo) { this._blockedResponseCookies = extraResponseInfo.blockedResponseCookies; this.responseHeaders = extraResponseInfo.responseHeaders; if (extraResponseInfo.responseHeadersText) { this.responseHeadersText = extraResponseInfo.responseHeadersText; if (!this.requestHeadersText()) { // Generate request headers text from raw headers in extra request info because // Network.requestWillBeSentExtraInfo doesn't include headers text. let requestHeadersText = `${this.requestMethod} ${this.parsedURL.path}`; if (this.parsedURL.queryParams) { requestHeadersText += `?${this.parsedURL.queryParams}`; } requestHeadersText += ' HTTP/1.1\r\n'; for (const {name, value} of this.requestHeaders()) { requestHeadersText += `${name}: ${value}\r\n`; } this.setRequestHeadersText(requestHeadersText); } } this._remoteAddressSpace = extraResponseInfo.resourceIPAddressSpace; this._hasExtraResponseInfo = true; } /** * @return {boolean} */ hasExtraResponseInfo() { return this._hasExtraResponseInfo; } /** * @return {!Array<!BlockedSetCookieWithReason>} */ blockedResponseCookies() { return this._blockedResponseCookies; } /** * @return {boolean} */ redirectSourceSignedExchangeInfoHasNoErrors() { return this._redirectSource !== null && this._redirectSource._signedExchangeInfo !== null && !this._redirectSource._signedExchangeInfo.errors; } /** * @return {!Protocol.Network.ClientSecurityState|undefined} */ clientSecurityState() { return this._clientSecurityState; } /** @param {!Protocol.Network.TrustTokenParams} trustTokenParams */ setTrustTokenParams(trustTokenParams) { this._trustTokenParams = trustTokenParams; } /** @return {(!Protocol.Network.TrustTokenParams|undefined)} */ trustTokenParams() { return this._trustTokenParams; } /** @param {!Protocol.Network.TrustTokenOperationDoneEvent} doneEvent */ setTrustTokenOperationDoneEvent(doneEvent) { this._trustTokenOperationDoneEvent = doneEvent; this.dispatchEventToListeners(Events.TrustTokenResultAdded); } /** @return {(!Protocol.Network.TrustTokenOperationDoneEvent|undefined)} */ trustTokenOperationDoneEvent() { return this._trustTokenOperationDoneEvent; } } /** @enum {symbol} */ export const Events = { FinishedLoading: Symbol('FinishedLoading'), TimingChanged: Symbol('TimingChanged'), RemoteAddressChanged: Symbol('RemoteAddressChanged'), RequestHeadersChanged: Symbol('RequestHeadersChanged'), ResponseHeadersChanged: Symbol('ResponseHeadersChanged'), WebsocketFrameAdded: Symbol('WebsocketFrameAdded'), EventSourceMessageAdded: Symbol('EventSourceMessageAdded'), TrustTokenResultAdded: Symbol('TrustTokenResultAdded'), }; /** @enum {string} */ export const InitiatorType = { Other: 'other', Parser: 'parser', Redirect: 'redirect', Script: 'script', Preload: 'preload', SignedExchange: 'signedExchange', Preflight: 'preflight', }; /** @enum {string} */ export const WebSocketFrameType = { Send: 'send', Receive: 'receive', Error: 'error' }; /** * @param {!Protocol.Network.CookieBlockedReason} blockedReason * @return {string} */ export const cookieBlockedReasonToUiString = function(blockedReason) { switch (blockedReason) { case Protocol.Network.CookieBlockedReason.SecureOnly: return ls`This cookie was blocked because it had the "Secure" attribute and the connection was not secure.`; case Protocol.Network.CookieBlockedReason.NotOnPath: return ls `This cookie was blocked because its path was not an exact match for or a superdirectory of the request url's path.`; case Protocol.Network.CookieBlockedReason.DomainMismatch: return ls `This cookie was blocked because neither did the request URL's domain exactly match the cookie's domain, nor was the request URL's domain a subdomain of the cookie's Domain attribute value.`; case Protocol.Network.CookieBlockedReason.SameSiteStrict: return ls `This cookie was blocked because it had the "SameSite=Strict" attribute and the request was made from a different site. This includes top-level navigation requests initiated by other sites.`; case Protocol.Network.CookieBlockedReason.SameSiteLax: return ls `This cookie was blocked because it had the "SameSite=Lax" attribute and the request was made from a different site and was not initiated by a top-level navigation.`; case Protocol.Network.CookieBlockedReason.SameSiteUnspecifiedTreatedAsLax: return ls `This cookie didn't specify a "SameSite" attribute when it was stored and was defaulted to "SameSite=Lax," and was blocked because the request was made from a different site and was not initiated by a top-level navigation. The cookie had to have been set with "SameSite=None" to enable cross-site usage.`; case Protocol.Network.CookieBlockedReason.SameSiteNoneInsecure: return ls `This cookie was blocked because it had the "SameSite=None" attribute but was not marked "Secure". Cookies without SameSite restrictions must be marked "Secure" and sent over a secure connection.`; case Protocol.Network.CookieBlockedReason.UserPreferences: return ls`This cookie was blocked due to user preferences.`; case Protocol.Network.CookieBlockedReason.UnknownError: return ls`An unknown error was encountered when trying to send this cookie.`; case Protocol.Network.CookieBlockedReason.SchemefulSameSiteStrict: return ls`This cookie was blocked because it had the "SameSite=Strict" attribute but the request was cross-site. This includes top-level navigation requests initiated by other sites. This request is considered cross-site because the URL has a different scheme than the current site.`; case Protocol.Network.CookieBlockedReason.SchemefulSameSiteLax: return ls`This cookie was blocked because it had the "SameSite=Lax" attribute but the request was cross-site and was not initiated by a top-level navigation. This request is considered cross-site because the URL has a different scheme than the current site.`; case Protocol.Network.CookieBlockedReason.SchemefulSameSiteUnspecifiedTreatedAsLax: return ls`This cookie didn't specify a "SameSite" attribute when it was stored, was defaulted to "SameSite=Lax", and was blocked because the request was cross-site and was not initiated by a top-level navigation. This request is considered cross-site because the URL has a different scheme than the current site.`; case Protocol.Network.CookieBlockedReason.SamePartyFromCrossPartyContext: return ls`This cookie was blocked because it had the "SameParty" attribute but the request was cross-party. The request was considered cross-party because the domain of the resource's URL and the domains of the resource's enclosing frames/documents are neither owners nor members in the same First-Party Set.`; } return ''; }; /** * @param {!Protocol.Network.SetCookieBlockedReason} blockedReason * @return {string} */ export const setCookieBlockedReasonToUiString = function(blockedReason) { switch (blockedReason) { case Protocol.Network.SetCookieBlockedReason.SecureOnly: return ls `This Set-Cookie was blocked because it had the "Secure" attribute but was not received over a secure connection.`; case Protocol.Network.SetCookieBlockedReason.SameSiteStrict: return ls `This Set-Cookie was blocked because it had the "SameSite=Strict" attribute but came from a cross-site response which was not the response to a top-level navigation.`; case Protocol.Network.SetCookieBlockedReason.SameSiteLax: return ls `This Set-Cookie was blocked because it had the "SameSite=Lax" attribute but came from a cross-site response which was not the response to a top-level navigation.`; case Protocol.Network.SetCookieBlockedReason.SameSiteUnspecifiedTreatedAsLax: return ls `This Set-Cookie didn't specify a "SameSite" attribute and was defaulted to "SameSite=Lax," and was blocked because it came from a cross-site response which was not the response to a top-level navigation. The Set-Cookie had to have been set with "SameSite=None" to enable cross-site usage.`; case Protocol.Network.SetCookieBlockedReason.SameSiteNoneInsecure: return ls `This Set-Cookie was blocked because it had the "SameSite=None" attribute but did not have the "Secure" attribute, which is required in order to use "SameSite=None".`; case Protocol.Network.SetCookieBlockedReason.UserPreferences: return ls`This Set-Cookie was blocked due to user preferences.`; case Protocol.Network.SetCookieBlockedReason.SyntaxError: return ls`This Set-Cookie had invalid syntax.`; case Protocol.Network.SetCookieBlockedReason.SchemeNotSupported: return ls`The scheme of this connection is not allowed to store cookies.`; case Protocol.Network.SetCookieBlockedReason.OverwriteSecure: return ls `This Set-Cookie was blocked because it was not sent over a secure connection and would have overwritten a cookie with the Secure attribute.`; case Protocol.Network.SetCookieBlockedReason.InvalidDomain: return ls `This Set-Cookie was blocked because its Domain attribute was invalid with regards to the current host url.`; case Protocol.Network.SetCookieBlockedReason.InvalidPrefix: return ls `This Set-Cookie was blocked because it used the "__Secure-" or "__Host-" prefix in its name and broke the additional rules applied to cookies with these prefixes as defined in https://tools.ietf.org/html/draft-west-cookie-prefixes-05.`; case Protocol.Network.SetCookieBlockedReason.UnknownError: return ls`An unknown error was encountered when trying to store this cookie.`; case Protocol.Network.SetCookieBlockedReason.SchemefulSameSiteStrict: return ls`This Set-Cookie was blocked because it had the "SameSite=Strict" attribute but came from a cross-site response which was not the response to a top-level navigation. This response is considered cross-site because the URL has a different scheme than the current site.`; case Protocol.Network.SetCookieBlockedReason.SchemefulSameSiteLax: return ls`This Set-Cookie was blocked because it had the "SameSite=Lax" attribute but came from a cross-site response which was not the r