Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 1 | // Copyright 2018 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Tim van der Lippe | 119690c | 2020-01-13 12:31:30 | [diff] [blame] | 5 | export class SignedExchangeInfoView extends UI.VBox { |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 6 | /** |
Tsuyoshi Horo | 02266c3 | 2018-05-21 17:01:18 | [diff] [blame] | 7 | * @param {!SDK.NetworkRequest} request |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 8 | */ |
Tsuyoshi Horo | 02266c3 | 2018-05-21 17:01:18 | [diff] [blame] | 9 | constructor(request) { |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 10 | super(); |
Tsuyoshi Horo | 02266c3 | 2018-05-21 17:01:18 | [diff] [blame] | 11 | const signedExchangeInfo = request.signedExchangeInfo(); |
| 12 | console.assert(signedExchangeInfo); |
| 13 | |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 14 | this.registerRequiredCSS('network/signedExchangeInfoView.css'); |
| 15 | this.element.classList.add('signed-exchange-info-view'); |
| 16 | |
| 17 | const root = new UI.TreeOutlineInShadow(); |
| 18 | root.registerRequiredCSS('network/signedExchangeInfoTree.css'); |
| 19 | root.element.classList.add('signed-exchange-info-tree'); |
| 20 | root.setFocusable(false); |
| 21 | root.makeDense(); |
| 22 | root.expandTreeElementsWhenArrowing = true; |
| 23 | this.element.appendChild(root.element); |
| 24 | |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 25 | /** @type {!Map<number|undefined, !Set<string>>} */ |
| 26 | const errorFieldSetMap = new Map(); |
| 27 | |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 28 | if (signedExchangeInfo.errors && signedExchangeInfo.errors.length) { |
Tim van der Lippe | 119690c | 2020-01-13 12:31:30 | [diff] [blame] | 29 | const errorMessagesCategory = new Category(root, Common.UIString('Errors')); |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 30 | for (const error of signedExchangeInfo.errors) { |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 31 | const fragment = createDocumentFragment(); |
| 32 | fragment.appendChild(UI.Icon.create('smallicon-error', 'prompt-icon')); |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 33 | fragment.createChild('div', 'error-log').textContent = error.message; |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 34 | errorMessagesCategory.createLeaf(fragment); |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 35 | if (error.errorField) { |
| 36 | let errorFieldSet = errorFieldSetMap.get(error.signatureIndex); |
| 37 | if (!errorFieldSet) { |
| 38 | errorFieldSet = new Set(); |
| 39 | errorFieldSetMap.set(error.signatureIndex, errorFieldSet); |
| 40 | } |
| 41 | errorFieldSet.add(error.errorField); |
| 42 | } |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 43 | } |
| 44 | } |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 45 | |
| 46 | const titleElement = createDocumentFragment(); |
| 47 | titleElement.createChild('div', 'header-name').textContent = Common.UIString('Signed HTTP exchange'); |
| 48 | const learnMoreNode = |
| 49 | UI.XLink.create('https://github.com/WICG/webpackage', Common.UIString('Learn\xa0more'), 'header-toggle'); |
| 50 | titleElement.appendChild(learnMoreNode); |
Tim van der Lippe | 119690c | 2020-01-13 12:31:30 | [diff] [blame] | 51 | const headerCategory = new Category(root, titleElement); |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 52 | if (signedExchangeInfo.header) { |
| 53 | const header = signedExchangeInfo.header; |
Tsuyoshi Horo | 02266c3 | 2018-05-21 17:01:18 | [diff] [blame] | 54 | const redirectDestination = request.redirectDestination(); |
| 55 | const requestURLElement = this._formatHeader(Common.UIString('Request URL'), header.requestUrl); |
| 56 | if (redirectDestination) { |
| 57 | const viewRequestLink = Components.Linkifier.linkifyRevealable(redirectDestination, 'View request'); |
| 58 | viewRequestLink.classList.add('header-toggle'); |
| 59 | requestURLElement.appendChild(viewRequestLink); |
| 60 | } |
| 61 | headerCategory.createLeaf(requestURLElement); |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 62 | headerCategory.createLeaf(this._formatHeader(Common.UIString('Response code'), header.responseCode + '')); |
Tsuyoshi Horo | 720ff72 | 2019-07-01 04:54:39 | [diff] [blame] | 63 | headerCategory.createLeaf(this._formatHeader(Common.UIString('Header integrity hash'), header.headerIntegrity)); |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 64 | |
| 65 | this._responseHeadersItem = |
| 66 | headerCategory.createLeaf(this._formatHeader(Common.UIString('Response headers'), '')); |
| 67 | const responseHeaders = header.responseHeaders; |
| 68 | for (const name in responseHeaders) { |
| 69 | const headerTreeElement = new UI.TreeElement(this._formatHeader(name, responseHeaders[name])); |
| 70 | headerTreeElement.selectable = false; |
| 71 | this._responseHeadersItem.appendChild(headerTreeElement); |
| 72 | } |
| 73 | this._responseHeadersItem.expand(); |
| 74 | |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 75 | for (let i = 0; i < header.signatures.length; ++i) { |
| 76 | const errorFieldSet = errorFieldSetMap.get(i) || new Set(); |
| 77 | const signature = header.signatures[i]; |
Tim van der Lippe | 119690c | 2020-01-13 12:31:30 | [diff] [blame] | 78 | const signatureCategory = new Category(root, Common.UIString('Signature')); |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 79 | signatureCategory.createLeaf(this._formatHeader(Common.UIString('Label'), signature.label)); |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 80 | signatureCategory.createLeaf(this._formatHeaderForHexData( |
| 81 | Common.UIString('Signature'), signature.signature, |
| 82 | errorFieldSet.has(Protocol.Network.SignedExchangeErrorField.SignatureSig))); |
Tsuyoshi Horo | 32b85e7 | 2018-05-25 03:54:16 | [diff] [blame] | 83 | |
| 84 | if (signature.certUrl) { |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 85 | const certURLElement = this._formatHeader( |
| 86 | Common.UIString('Certificate URL'), signature.certUrl, |
| 87 | errorFieldSet.has(Protocol.Network.SignedExchangeErrorField.SignatureCertUrl)); |
Tsuyoshi Horo | 32b85e7 | 2018-05-25 03:54:16 | [diff] [blame] | 88 | if (signature.certificates) { |
| 89 | const viewCertLink = certURLElement.createChild('span', 'devtools-link header-toggle'); |
| 90 | viewCertLink.textContent = Common.UIString('View certificate'); |
| 91 | viewCertLink.addEventListener( |
Tim van der Lippe | 50cfa9b | 2019-10-01 10:40:58 | [diff] [blame] | 92 | 'click', Host.InspectorFrontendHost.showCertificateViewer.bind(null, signature.certificates), false); |
Tsuyoshi Horo | 32b85e7 | 2018-05-25 03:54:16 | [diff] [blame] | 93 | } |
| 94 | signatureCategory.createLeaf(certURLElement); |
| 95 | } |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 96 | signatureCategory.createLeaf(this._formatHeader( |
| 97 | Common.UIString('Integrity'), signature.integrity, |
| 98 | errorFieldSet.has(Protocol.Network.SignedExchangeErrorField.SignatureIntegrity))); |
Tsuyoshi Horo | 03fbafe | 2018-05-25 03:59:58 | [diff] [blame] | 99 | if (signature.certSha256) { |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 100 | signatureCategory.createLeaf(this._formatHeaderForHexData( |
| 101 | Common.UIString('Certificate SHA256'), signature.certSha256, |
| 102 | errorFieldSet.has(Protocol.Network.SignedExchangeErrorField.SignatureCertSha256))); |
Tsuyoshi Horo | 03fbafe | 2018-05-25 03:59:58 | [diff] [blame] | 103 | } |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 104 | signatureCategory.createLeaf(this._formatHeader( |
| 105 | Common.UIString('Validity URL'), signature.validityUrl, |
| 106 | errorFieldSet.has(Protocol.Network.SignedExchangeErrorField.SignatureValidityUrl))); |
| 107 | signatureCategory.createLeaf().title = this._formatHeader( |
| 108 | Common.UIString('Date'), new Date(1000 * signature.date).toUTCString(), |
| 109 | errorFieldSet.has(Protocol.Network.SignedExchangeErrorField.SignatureTimestamps)); |
| 110 | signatureCategory.createLeaf().title = this._formatHeader( |
| 111 | Common.UIString('Expires'), new Date(1000 * signature.expires).toUTCString(), |
| 112 | errorFieldSet.has(Protocol.Network.SignedExchangeErrorField.SignatureTimestamps)); |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 113 | } |
| 114 | } |
| 115 | if (signedExchangeInfo.securityDetails) { |
| 116 | const securityDetails = signedExchangeInfo.securityDetails; |
Tim van der Lippe | 119690c | 2020-01-13 12:31:30 | [diff] [blame] | 117 | const securityCategory = new Category(root, Common.UIString('Certificate')); |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 118 | securityCategory.createLeaf(this._formatHeader(Common.UIString('Subject'), securityDetails.subjectName)); |
| 119 | securityCategory.createLeaf( |
| 120 | this._formatHeader(Common.UIString('Valid from'), new Date(1000 * securityDetails.validFrom).toUTCString())); |
| 121 | securityCategory.createLeaf( |
| 122 | this._formatHeader(Common.UIString('Valid until'), new Date(1000 * securityDetails.validTo).toUTCString())); |
| 123 | securityCategory.createLeaf(this._formatHeader(Common.UIString('Issuer'), securityDetails.issuer)); |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * @param {string} name |
| 129 | * @param {string} value |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 130 | * @param {boolean=} highlighted |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 131 | * @return {!DocumentFragment} |
| 132 | */ |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 133 | _formatHeader(name, value, highlighted) { |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 134 | const fragment = createDocumentFragment(); |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 135 | const nameElement = fragment.createChild('div', 'header-name'); |
| 136 | nameElement.textContent = name + ': '; |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 137 | fragment.createChild('span', 'header-separator'); |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 138 | const valueElement = fragment.createChild('div', 'header-value source-code'); |
| 139 | valueElement.textContent = value; |
| 140 | if (highlighted) { |
| 141 | nameElement.classList.add('error-field'); |
| 142 | valueElement.classList.add('error-field'); |
| 143 | } |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 144 | return fragment; |
| 145 | } |
Tsuyoshi Horo | 03fbafe | 2018-05-25 03:59:58 | [diff] [blame] | 146 | |
| 147 | /** |
| 148 | * @param {string} name |
| 149 | * @param {string} value |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 150 | * @param {boolean=} highlighted |
Tsuyoshi Horo | 03fbafe | 2018-05-25 03:59:58 | [diff] [blame] | 151 | * @return {!DocumentFragment} |
| 152 | */ |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 153 | _formatHeaderForHexData(name, value, highlighted) { |
Tsuyoshi Horo | 03fbafe | 2018-05-25 03:59:58 | [diff] [blame] | 154 | const fragment = createDocumentFragment(); |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 155 | const nameElement = fragment.createChild('div', 'header-name'); |
| 156 | nameElement.textContent = name + ': '; |
Tsuyoshi Horo | 03fbafe | 2018-05-25 03:59:58 | [diff] [blame] | 157 | fragment.createChild('span', 'header-separator'); |
Tsuyoshi Horo | 06db447 | 2018-05-31 07:32:45 | [diff] [blame] | 158 | const valueElement = fragment.createChild('div', 'header-value source-code hex-data'); |
| 159 | valueElement.textContent = value.replace(/(.{2})/g, '$1 '); |
| 160 | if (highlighted) { |
| 161 | nameElement.classList.add('error-field'); |
| 162 | valueElement.classList.add('error-field'); |
| 163 | } |
Tsuyoshi Horo | 03fbafe | 2018-05-25 03:59:58 | [diff] [blame] | 164 | return fragment; |
| 165 | } |
Paul Lewis | 5650965 | 2019-12-06 12:51:58 | [diff] [blame] | 166 | } |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 167 | |
| 168 | /** |
| 169 | * @unrestricted |
| 170 | */ |
Paul Lewis | 5650965 | 2019-12-06 12:51:58 | [diff] [blame] | 171 | export class Category extends UI.TreeElement { |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 172 | /** |
| 173 | * @param {!UI.TreeOutline} root |
Tsuyoshi Horo | 02266c3 | 2018-05-21 17:01:18 | [diff] [blame] | 174 | * @param {(string|!Node)=} title |
Tsuyoshi Horo | 625e92a | 2018-05-17 00:36:11 | [diff] [blame] | 175 | */ |
| 176 | constructor(root, title) { |
| 177 | super(title, true); |
| 178 | this.selectable = false; |
| 179 | this.toggleOnClick = true; |
| 180 | this.expanded = true; |
| 181 | root.appendChild(this); |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * @param {(string|!Node)=} title |
| 186 | */ |
| 187 | createLeaf(title) { |
| 188 | const leaf = new UI.TreeElement(title); |
| 189 | leaf.selectable = false; |
| 190 | this.appendChild(leaf); |
| 191 | return leaf; |
| 192 | } |
Paul Lewis | 5650965 | 2019-12-06 12:51:58 | [diff] [blame] | 193 | } |