/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { Component, createFactory, } = require("resource://devtools/client/shared/vendor/react.js"); const { connect, } = require("resource://devtools/client/shared/redux/visibility-handler-connect.js"); const Actions = require("resource://devtools/client/netmonitor/src/actions/index.js"); const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); const { getFormattedIPAndPort, getFormattedSize, getRequestPriorityAsText, } = require("resource://devtools/client/netmonitor/src/utils/format-utils.js"); const { L10N, } = require("resource://devtools/client/netmonitor/src/utils/l10n.js"); const { getHeadersURL, getTrackingProtectionURL, getHTTPStatusCodeURL, } = require("resource://devtools/client/netmonitor/src/utils/doc-utils.js"); const { fetchNetworkUpdatePacket, writeHeaderText, getRequestHeadersRawText, } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); const { HeadersProvider, HeaderList, } = require("resource://devtools/client/netmonitor/src/utils/headers-provider.js"); const { FILTER_SEARCH_DELAY, } = require("resource://devtools/client/netmonitor/src/constants.js"); // Components const PropertiesView = createFactory( require("resource://devtools/client/netmonitor/src/components/request-details/PropertiesView.js") ); const SearchBox = createFactory( require("resource://devtools/client/shared/components/SearchBox.js") ); const Accordion = createFactory( require("resource://devtools/client/shared/components/Accordion.js") ); const UrlPreview = createFactory( require("resource://devtools/client/netmonitor/src/components/previews/UrlPreview.js") ); const HeadersPanelContextMenu = require("resource://devtools/client/netmonitor/src/widgets/HeadersPanelContextMenu.js"); const StatusCode = createFactory( require("resource://devtools/client/netmonitor/src/components/StatusCode.js") ); loader.lazyGetter(this, "MDNLink", function () { return createFactory( require("resource://devtools/client/shared/components/MdnLink.js") ); }); loader.lazyGetter(this, "Rep", function () { return require("resource://devtools/client/shared/components/reps/index.js") .REPS.Rep; }); loader.lazyGetter(this, "MODE", function () { return require("resource://devtools/client/shared/components/reps/index.js") .MODE; }); loader.lazyGetter(this, "TreeRow", function () { return createFactory( require("resource://devtools/client/shared/components/tree/TreeRow.js") ); }); loader.lazyRequireGetter( this, "showMenu", "resource://devtools/client/shared/components/menu/utils.js", true ); loader.lazyRequireGetter( this, "openContentLink", "resource://devtools/client/shared/link.js", true ); const { div, input, label, span, textarea, tr, td, button } = dom; const RESEND = L10N.getStr("netmonitor.context.resend.label"); const EDIT_AND_RESEND = L10N.getStr("netmonitor.summary.editAndResend"); const RAW_HEADERS = L10N.getStr("netmonitor.headers.raw"); const HEADERS_EMPTY_TEXT = L10N.getStr("headersEmptyText"); const HEADERS_FILTER_TEXT = L10N.getStr("headersFilterText"); const REQUEST_HEADERS = L10N.getStr("requestHeaders"); const REQUEST_HEADERS_FROM_UPLOAD = L10N.getStr("requestHeadersFromUpload"); const RESPONSE_HEADERS = L10N.getStr("responseHeaders"); const HEADERS_STATUS = L10N.getStr("netmonitor.headers.status"); const HEADERS_VERSION = L10N.getStr("netmonitor.headers.version"); const HEADERS_TRANSFERRED = L10N.getStr("netmonitor.toolbar.transferred"); const SUMMARY_STATUS_LEARN_MORE = L10N.getStr("netmonitor.summary.learnMore"); const SUMMARY_ETP_LEARN_MORE = L10N.getStr( "netmonitor.enhancedTrackingProtection.learnMore" ); const HEADERS_REFERRER = L10N.getStr("netmonitor.headers.referrerPolicy"); const HEADERS_CONTENT_BLOCKING = L10N.getStr( "netmonitor.headers.contentBlocking" ); const HEADERS_ETP = L10N.getStr( "netmonitor.trackingResource.enhancedTrackingProtection" ); const HEADERS_PRIORITY = L10N.getStr("netmonitor.headers.requestPriority"); /** * Headers panel component * Lists basic information about the request * * In http/2 all response headers are in small case. * See: https://firefox-source-docs.mozilla.org/devtools-user/network_monitor/request_details/index.html#response-headers * RFC: https://tools.ietf.org/html/rfc7540#section-8.1.2 */ class HeadersPanel extends Component { static get propTypes() { return { connector: PropTypes.object.isRequired, cloneSelectedRequest: PropTypes.func.isRequired, member: PropTypes.object, request: PropTypes.object.isRequired, renderValue: PropTypes.func, openLink: PropTypes.func, targetSearchResult: PropTypes.object, openRequestBlockingAndAddUrl: PropTypes.func.isRequired, openHTTPCustomRequestTab: PropTypes.func.isRequired, cloneRequest: PropTypes.func, sendCustomRequest: PropTypes.func, shouldExpandPreview: PropTypes.bool, setHeadersUrlPreviewExpanded: PropTypes.func, }; } constructor(props) { super(props); this.state = { rawRequestHeadersOpened: false, rawResponseHeadersOpened: false, rawUploadHeadersOpened: false, lastToggledRawHeader: "", filterText: null, }; this.getProperties = this.getProperties.bind(this); this.getTargetHeaderPath = this.getTargetHeaderPath.bind(this); this.toggleRawResponseHeaders = this.toggleRawResponseHeaders.bind(this); this.toggleRawRequestHeaders = this.toggleRawRequestHeaders.bind(this); this.toggleRawUploadHeaders = this.toggleRawUploadHeaders.bind(this); this.renderSummary = this.renderSummary.bind(this); this.renderRow = this.renderRow.bind(this); this.renderValue = this.renderValue.bind(this); this.renderRawHeadersBtn = this.renderRawHeadersBtn.bind(this); this.onShowResendMenu = this.onShowResendMenu.bind(this); this.onShowHeadersContextMenu = this.onShowHeadersContextMenu.bind(this); } componentDidMount() { const { request, connector } = this.props; fetchNetworkUpdatePacket(connector.requestData, request, [ "requestHeaders", "responseHeaders", "requestPostData", ]); } // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 UNSAFE_componentWillReceiveProps(nextProps) { const { request, connector } = nextProps; fetchNetworkUpdatePacket(connector.requestData, request, [ "requestHeaders", "responseHeaders", "requestPostData", ]); } getHeadersTitle(headers, title) { let result = ""; let preHeaderText = ""; const { responseHeaders, requestHeaders, httpVersion, status, statusText, method, urlDetails, } = this.props.request; if (headers?.headers.length) { if (!headers.headersSize) { if (title == RESPONSE_HEADERS) { preHeaderText = `${httpVersion} ${status} ${statusText}`; result = `${title} (${getFormattedSize( writeHeaderText(responseHeaders.headers, preHeaderText).length, 3 )})`; } else { const hostHeader = requestHeaders.headers.find( ele => ele.name === "Host" ); if (hostHeader) { preHeaderText = `${method} ${ urlDetails.url.split(hostHeader.value)[1] } ${httpVersion}`; } result = `${title} (${getFormattedSize( writeHeaderText(requestHeaders.headers, preHeaderText).length, 3 )})`; } } else { result = `${title} (${getFormattedSize(headers.headersSize, 3)})`; } } return result; } getProperties(headers, title) { let propertiesResult; if (headers?.headers.length) { const headerKey = this.getHeadersTitle(headers, title); propertiesResult = { [headerKey]: new HeaderList(headers.headers), }; if ( (title === RESPONSE_HEADERS && this.state.rawResponseHeadersOpened) || (title === REQUEST_HEADERS && this.state.rawRequestHeadersOpened) || (title === REQUEST_HEADERS_FROM_UPLOAD && this.state.rawUploadHeadersOpened) ) { propertiesResult = { [headerKey]: { RAW_HEADERS_ID: headers.rawHeaders }, }; } } return propertiesResult; } toggleRawResponseHeaders() { this.setState({ rawResponseHeadersOpened: !this.state.rawResponseHeadersOpened, lastToggledRawHeader: "response", }); } toggleRawRequestHeaders() { this.setState({ rawRequestHeadersOpened: !this.state.rawRequestHeadersOpened, lastToggledRawHeader: "request", }); } toggleRawUploadHeaders() { this.setState({ rawUploadHeadersOpened: !this.state.rawUploadHeadersOpened, lastToggledRawHeader: "upload", }); } /** * Helper method to identify what kind of raw header this is. * Information is in the path variable */ getRawHeaderType(path) { if (path.includes(RESPONSE_HEADERS)) { return "RESPONSE"; } if (path.includes(REQUEST_HEADERS_FROM_UPLOAD)) { return "UPLOAD"; } return "REQUEST"; } /** * Renders the top part of the headers detail panel - Summary. */ renderSummary(summaryLabel, value) { return div( { key: summaryLabel, className: "tabpanel-summary-container headers-summary", }, span( { className: "tabpanel-summary-label headers-summary-label" }, summaryLabel ), span({ className: "tabpanel-summary-value" }, value) ); } /** * Get path for target header if it's set. It's used to select * the header automatically within the tree of headers. * Note that the target header is set by the Search panel. */ getTargetHeaderPath(searchResult) { if (!searchResult) { return null; } if ( searchResult.type !== "requestHeaders" && searchResult.type !== "responseHeaders" && searchResult.type !== "requestHeadersFromUploadStream" ) { return null; } const { request: { requestHeaders, requestHeadersFromUploadStream: uploadHeaders, responseHeaders, }, } = this.props; // Using `HeaderList` ensures that we'll get the same // header index as it's used in the tree. const getPath = (headers, title) => { return ( "/" + this.getHeadersTitle(headers, title) + "/" + new HeaderList(headers.headers).headers.findIndex( header => header.name == searchResult.label ) ); }; // Calculate target header path according to the header type. switch (searchResult.type) { case "requestHeaders": return getPath(requestHeaders, REQUEST_HEADERS); case "responseHeaders": return getPath(responseHeaders, RESPONSE_HEADERS); case "requestHeadersFromUploadStream": return getPath(uploadHeaders, REQUEST_HEADERS_FROM_UPLOAD); } return null; } /** * Custom rendering method passed to PropertiesView. It's responsible * for rendering