/* eslint-disable complexity */ /* 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.mjs"); const { connect, } = require("resource://devtools/client/shared/vendor/react-redux.js"); const Actions = require("resource://devtools/client/netmonitor/src/actions/index.js"); const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 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 ChromeUtils.importESModule( "resource://devtools/client/shared/components/reps/index.mjs" ).REPS.Rep; }); loader.lazyGetter(this, "MODE", function () { return ChromeUtils.importESModule( "resource://devtools/client/shared/components/reps/index.mjs" ).MODE; }); loader.lazyGetter(this, "TreeRow", function () { return createFactory( ChromeUtils.importESModule( "resource://devtools/client/shared/components/tree/TreeRow.mjs", { global: "current" } ).default ); }); loader.lazyRequireGetter( this, "showMenu", "resource://devtools/client/shared/components/menu/utils.js", true ); loader.lazyRequireGetter( this, "openContentLink", "resource://devtools/client/shared/link.js", true ); loader.lazyGetter(this, "HEADERS_PROXY_STATUS", function () { return L10N.getStr("netmonitor.headers.proxyStatus"); }); loader.lazyGetter(this, "HEADERS_PROXY_VERSION", function () { return L10N.getStr("netmonitor.headers.proxyVersion"); }); 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 HEADERS_STATUS = L10N.getStr("netmonitor.headers.status"); const HEADERS_EARLYHINT_STATUS = L10N.getStr( "netmonitor.headers.earlyHintsStatus" ); 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"); const HEADERS_DNS = L10N.getStr("netmonitor.headers.dns"); // Order is as displayed const HEADERS_CONFIG = { earlyHintsResponseHeaders: { // Key for fetching the data from the backend fetchKey: "earlyHintsResponseHeaders", title: L10N.getStr("earlyHintsResponseHeaders"), // Gets the content to be displayed when switched to the raw headers view rawHeaderValue: ({ headerData }) => { const preHeaderText = headerData.rawHeaders.split("\r\n")[0]; return writeHeaderText(headerData.headers, preHeaderText).trim(); }, // Gets the full display text to be displayed in the header title bar(before the raw toggle button) displayTitle: ({ headerData }) => { const title = HEADERS_CONFIG.earlyHintsResponseHeaders.title; if (headerData.headersSize) { return `${title} (${getFormattedSize(headerData.headersSize, 3)})`; } const rawHeaderValue = HEADERS_CONFIG.earlyHintsResponseHeaders.rawHeaderValue({ headerData }); return `${title} (${getFormattedSize(rawHeaderValue.length, 3)})`; }, }, responseHeaders: { // Key for fetching the data from the backend fetchKey: "responseHeaders", title: L10N.getStr("responseHeaders"), // Gets the content to be displayed when switched to the raw headers view rawHeaderValue: ({ status, statusText, httpVersion, headerData }) => { const preHeaderText = `${httpVersion} ${status} ${statusText}`; return writeHeaderText(headerData.headers, preHeaderText).trim(); }, // Gets the full display text to be displayed in the header title bar(before the raw toggle button) displayTitle: ({ status, statusText, httpVersion, headerData }) => { const title = HEADERS_CONFIG.responseHeaders.title; if (headerData.headersSize) { return `${title} (${getFormattedSize(headerData.headersSize, 3)})`; } const rawHeaderValue = HEADERS_CONFIG.responseHeaders.rawHeaderValue({ httpVersion, status, statusText, headerData, }); return `${title} (${getFormattedSize(rawHeaderValue.length, 3)})`; }, }, requestHeaders: { // See comment above fetchKey: "requestHeaders", title: L10N.getStr("requestHeaders"), // See comment above rawHeaderValue: ({ method, httpVersion, headerData, urlDetails }) => { return getRequestHeadersRawText( method, httpVersion, headerData, urlDetails ); }, // See comment above displayTitle: ({ method, httpVersion, headerData, urlDetails }) => { const title = HEADERS_CONFIG.requestHeaders.title; if (headerData.headersSize) { return `${title} (${getFormattedSize(headerData.headersSize, 3)})`; } const rawHeaderValue = HEADERS_CONFIG.requestHeaders.rawHeaderValue({ method, httpVersion, headerData, urlDetails, }); return `${title} (${getFormattedSize(rawHeaderValue.length, 3)})`; }, }, requestHeadersFromUploadStream: { // See comment above fetchKey: "requestPostData", title: L10N.getStr("requestHeadersFromUpload"), // See comment above rawHeaderValue: ({ headerData, preHeaderText = "" }) => { return writeHeaderText(headerData.headers, preHeaderText).trim(); }, // See comment above displayTitle: ({ method, httpVersion, headerData, urlDetails }) => { const title = HEADERS_CONFIG.requestHeadersFromUploadStream.title; if (headerData.headersSize) { return `${title} (${getFormattedSize(headerData.headersSize, 3)})`; } let preHeaderText = ""; const hostHeader = headerData.headers.find(ele => ele.name === "Host"); if (hostHeader) { preHeaderText = `${method} ${ urlDetails.url.split(hostHeader.value)[1] } ${httpVersion}`; } const rawHeaderValue = HEADERS_CONFIG.requestHeadersFromUploadStream.rawHeaderValue({ headerData, preHeaderText, }); return `${title} (${getFormattedSize(rawHeaderValue.length, 3)})`; }, }, }; const HEADERS_TO_FETCH = Object.values(HEADERS_CONFIG).map( headers => headers.fetchKey ); /** * 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 = { openedRawHeaders: new Set(), lastToggledRawHeader: "", filterText: null, }; this.getProperties = this.getProperties.bind(this); this.getTargetHeaderPath = this.getTargetHeaderPath.bind(this); this.toggleRawHeader = this.toggleRawHeader.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, HEADERS_TO_FETCH); } // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 UNSAFE_componentWillReceiveProps(nextProps) { const { request, connector } = nextProps; fetchNetworkUpdatePacket(connector.requestData, request, HEADERS_TO_FETCH); } // The title to be display in the heading getHeadersDisplayTitle(headerKey) { const headerData = this.props.request[headerKey]; if (!headerData?.headers.length) { return ""; } return HEADERS_CONFIG[headerKey].displayTitle({ ...this.props.request, headerData, }); } getProperties(headerKey) { let propertiesResult; const headerData = this.props.request[headerKey]; if (headerData?.headers.length) { propertiesResult = { [headerKey]: this.state.openedRawHeaders.has(headerKey) ? { RAW_HEADERS_ID: headerData.rawHeaders } : new HeaderList(headerData.headers), }; } return propertiesResult; } // Toggles the raw headers view on / off toggleRawHeader(headerKey) { const newOpenedRawHeaders = new Set([...this.state.openedRawHeaders]); if (newOpenedRawHeaders.has(headerKey)) { newOpenedRawHeaders.delete(headerKey); } else { newOpenedRawHeaders.add(headerKey); } this.setState({ openedRawHeaders: newOpenedRawHeaders, lastToggledRawHeader: headerKey, }); } /** * Renders the top part of the headers detail panel - Summary. */ renderSummary(summaryLabel, value, summaryClass = "") { return div( { key: summaryLabel, className: "tabpanel-summary-container headers-summary " + summaryClass, }, 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 || !(searchResult.type in HEADERS_CONFIG)) { return null; } // Using `HeaderList` ensures that we'll get the same // header index as it's used in the tree. const headerData = this.props.request[searchResult.type]; return ( "/" + searchResult.type + "/" + new HeaderList(headerData.headers).headers.findIndex( header => header.name == searchResult.label ) ); } /** * Custom rendering method passed to PropertiesView. It's responsible * for rendering