Web Inspector: Network: show secure certificate details per-request
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 13 Nov 2018 07:07:21 +0000 (07:07 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 13 Nov 2018 07:07:21 +0000 (07:07 +0000)
https://bugs.webkit.org/show_bug.cgi?id=191447
<rdar://problem/30019476>

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

Add Security domain to hold security related protocol types.

* CMakeLists.txt:
* DerivedSources.make:
* inspector/protocol/Network.json:
* inspector/protocol/Security.json: Added.
* inspector/scripts/codegen/objc_generator.py:
(ObjCGenerator):

Source/WebCore:

Test: http/tests/inspector/network/resource-response-security.html

* loader/ResourceLoader.h:
(WebCore::ResourceLoader::shouldIncludeCertificateInfo const):
* loader/ResourceLoader.cpp:
(WebCore::ResourceLoader::shouldIncludeCertificateInfo const): Added.
Always save certificate information when WebInspector is open.

* platform/network/CertificateInfoBase.h: Added.
(WebCore::CertificateInfoBase::containsNonRootSHA1SignedCertificate const):
(WebCore::CertificateInfoBase::summaryInfo const):
(WebCore::CertificateInfoBase::isEmpty const):
* platform/network/cf/CertificateInfo.h:
(WebCore::CertificateInfo::summaryInfo const): Added.
* platform/network/cf/CertificateInfoCFNet.cpp: Renamed from Source/WebCore/platform/network/mac/CertificateInfoMac.mm.
(WebCore::CertificateInfo::containsNonRootSHA1SignedCertificate):
(WebCore::CertificateInfo::summaryInfo const): Added.
* platform/network/curl/CertificateInfo.h:
(WebCore::CertificateInfo::summaryInfo const): Added.
(WebCore::CertificateInfo::isEmpty const): Added.
* platform/network/soup/CertificateInfo.h:
(WebCore::CertificateInfo::summaryInfo const): Added.
(WebCore::CertificateInfo::isEmpty const): Added.
Create base class for `CertificateInfo` so that `InspectorNetworkAgent` doesn't need to have
platform-specific code in its implementation.

* platform/network/cocoa/CertificateInfoCocoa.mm: Renamed from Source/WebCore/platform/network/mac/CertificateInfoMac.mm.
* platform/network/curl/CertificateInfoCFNet.cpp: Renamed from Source/WebCore/platform/network/curl/CertificateInfo.cpp.
* platform/network/soup/CertificateInfoSoup.cpp: Renamed from Source/WebCore/platform/network/soup/CertificateInfo.cpp.

* inspector/NetworkResourcesData.h:
(WebCore::NetworkResourcesData::ResourceData::certificateInfo const): Added.
(WebCore::NetworkResourcesData::ResourceData::setCertificateInfo): Added.
* inspector/NetworkResourcesData.cpp:
(WebCore::NetworkResourcesData::responseReceived):

* inspector/agents/InspectorNetworkAgent.cpp:
(WebCore::InspectorNetworkAgent::buildObjectForResourceResponse):

* PlatformAppleWin.cmake:
* PlatformMac.cmake:
* SourcesCocoa.txt:
* WebCore.xcodeproj/project.pbxproj:
* platform/Curl.cmake:
* platform/SourcesSoup.txt:

Source/WebInspectorUI:

* UserInterface/Controllers/NetworkManager.js:
(WI.NetworkManager.prototype.resourceRequestWasServedFromMemoryCache):
(WI.NetworkManager.prototype.resourceRequestDidReceiveResponse):

* UserInterface/Models/Resource.js:
(WI.Resource.prototype.get responseSecurity): Added.
(WI.Resource.prototype.get loadedSecurely): Added.
(WI.Resource.prototype.updateForResponse):

* UserInterface/Views/NetworkResourceDetailView.js:
(WI.NetworkResourceDetailView):
(WI.NetworkResourceDetailView.prototype.initialLayout):
(WI.NetworkResourceDetailView.prototype.showContentViewForIdentifier):
* UserInterface/Views/NetworkResourceDetailView.css:
(.content-view.resource-details .go-to-arrow): Added.
(.content-view.resource-details.showing-find-banner .search-highlight): Added.

* UserInterface/Views/ResourceSecurityContentView.js: Added.
(WI.ResourceSecurityContentView):
(WI.ResourceSecurityContentView.prototype.initialLayout):
(WI.ResourceSecurityContentView.prototype.layout):
(WI.ResourceSecurityContentView.prototype.closed):
(WI.ResourceSecurityContentView.prototype.get supportsSearch):
(WI.ResourceSecurityContentView.prototype.get numberOfSearchResults):
(WI.ResourceSecurityContentView.prototype.get hasPerformedSearch):
(WI.ResourceSecurityContentView.prototype.set automaticallyRevealFirstSearchResult):
(WI.ResourceSecurityContentView.prototype.performSearch):
(WI.ResourceSecurityContentView.prototype.searchCleared):
(WI.ResourceSecurityContentView.prototype.revealPreviousSearchResult):
(WI.ResourceSecurityContentView.prototype.revealNextSearchResult):
(WI.ResourceSecurityContentView.prototype._refreshCetificateSection):
(WI.ResourceSecurityContentView.prototype._perfomSearchOnKeyValuePairs):
(WI.ResourceSecurityContentView.prototype._revealSearchResult):
(WI.ResourceSecurityContentView.prototype._handleResourceResponseReceived):
* UserInterface/Views/ResourceSecurityContentView.css: Added.
(body[dir] .resource-security > section.certificate > .details):
(.resource-security .details .key):
(.resource-security .dns-name + .dns-name > .key,):
(.resource-security .show-more):
(@media (prefers-dark-interface) body[dir] .resource-security > section.certificate > .details):
(@media (prefers-dark-interface) .resource-security .details .key):

* UserInterface/Views/ResourceCookiesContentView.js:
(WI.ResourceCookiesContentView.prototype._refreshRequestCookiesSection):
(WI.ResourceCookiesContentView.prototype._refreshResponseCookiesSection):
(WI.ResourceCookiesContentView.prototype._markIncompleteSectionWithMessage): Deleted.
(WI.ResourceCookiesContentView.prototype._markIncompleteSectionWithLoadingIndicator): Deleted.
* UserInterface/Views/ResourceHeadersContentView.js:
(WI.ResourceHeadersContentView.prototype._refreshSummarySection):
(WI.ResourceHeadersContentView.prototype._refreshRedirectHeadersSections):
(WI.ResourceHeadersContentView.prototype._refreshRequestHeadersSection):
(WI.ResourceHeadersContentView.prototype._refreshResponseHeadersSection):
(WI.ResourceHeadersContentView.prototype._refreshQueryStringSection):
(WI.ResourceHeadersContentView.prototype._refreshRequestDataSection):
(WI.ResourceHeadersContentView.prototype._markIncompleteSectionWithMessage): Deleted.
(WI.ResourceHeadersContentView.prototype._markIncompleteSectionWithLoadingIndicator): Deleted.
(WI.ResourceHeadersContentView.prototype._appendKeyValuePair): Deleted.
* UserInterface/Views/ResourceHeadersContentView.css:
(.resource-headers .h1-status > .key,):
(body[dir] .resource-headers > section.error > .details): Deleted.
(.resource-headers > section.error .key): Deleted.
(.resource-headers .details): Deleted.
(.resource-headers .details .pair): Deleted.
(body[dir=rtl] .resource-headers .details .pair): Deleted.
(.resource-headers .details .key): Deleted.
(.resource-headers .value): Deleted.
(.resource-headers .go-to-arrow): Deleted.
(.resource-headers.showing-find-banner .search-highlight): Deleted.
* UserInterface/Views/ResourceDetailsSection.js:
(WI.ResourceDetailsSection.prototype.markIncompleteSectionWithMessage): Added.
(WI.ResourceDetailsSection.prototype.markIncompleteSectionWithLoadingIndicator): Added.
(WI.ResourceDetailsSection.prototype.appendKeyValuePair): Added.
* UserInterface/Views/ResourceDetailsSection.css:
(.resource-details > section > .details): Added.
(.resource-details > section > .details > .pair): Added.
(body[dir=rtl] .resource-details > section > .details > .pair): Added.
(.resource-details > section > .details > .pair > .key): Added.
(.resource-details > section > .details > .pair > .value): Added.
(body[dir] .resource-details > section.error > .details): Added.
(.resource-details > section.error > .details > .pair > .key): Added.
Move commonly used functions/styles from container classes onto this object.

* UserInterface/Main.html:
* Localizations/en.lproj/localizedStrings.js:

LayoutTests:

* http/tests/inspector/network/resource-response-security-expected.txt: Added.
* http/tests/inspector/network/resource-response-security.html: Added.

* platform/gtk/TestExpectations:
* platform/wincairo/TestExpectations:
* platform/wpe/TestExpectations:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@238122 268f45cc-cd09-0410-ab3c-d52691b4dbfc

46 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/inspector/network/resource-response-security-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/resource-response-security.html [new file with mode: 0644]
LayoutTests/platform/gtk/TestExpectations
LayoutTests/platform/wincairo/TestExpectations
LayoutTests/platform/wpe/TestExpectations
Source/JavaScriptCore/CMakeLists.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/DerivedSources.make
Source/JavaScriptCore/inspector/protocol/Network.json
Source/JavaScriptCore/inspector/protocol/Security.json [new file with mode: 0644]
Source/JavaScriptCore/inspector/scripts/codegen/objc_generator.py
Source/WebCore/ChangeLog
Source/WebCore/PlatformAppleWin.cmake
Source/WebCore/PlatformMac.cmake
Source/WebCore/SourcesCocoa.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/inspector/NetworkResourcesData.cpp
Source/WebCore/inspector/NetworkResourcesData.h
Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp
Source/WebCore/loader/ResourceLoader.cpp
Source/WebCore/loader/ResourceLoader.h
Source/WebCore/platform/Curl.cmake
Source/WebCore/platform/SourcesSoup.txt
Source/WebCore/platform/network/CertificateInfoBase.h [new file with mode: 0644]
Source/WebCore/platform/network/cf/CertificateInfo.h
Source/WebCore/platform/network/cf/CertificateInfoCFNet.cpp [new file with mode: 0644]
Source/WebCore/platform/network/cocoa/CertificateInfoCocoa.mm [moved from Source/WebCore/platform/network/mac/CertificateInfoMac.mm with 53% similarity]
Source/WebCore/platform/network/curl/CertificateInfo.h
Source/WebCore/platform/network/curl/CertificateInfoCurl.cpp [moved from Source/WebCore/platform/network/curl/CertificateInfo.cpp with 100% similarity]
Source/WebCore/platform/network/soup/CertificateInfo.h
Source/WebCore/platform/network/soup/CertificateInfoSoup.cpp [moved from Source/WebCore/platform/network/soup/CertificateInfo.cpp with 100% similarity]
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/Resource.js
Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.css
Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.js
Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.js
Source/WebInspectorUI/UserInterface/Views/ResourceDetailsSection.css
Source/WebInspectorUI/UserInterface/Views/ResourceDetailsSection.js
Source/WebInspectorUI/UserInterface/Views/ResourceHeadersContentView.css
Source/WebInspectorUI/UserInterface/Views/ResourceHeadersContentView.js
Source/WebInspectorUI/UserInterface/Views/ResourceSecurityContentView.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ResourceSecurityContentView.js [new file with mode: 0644]

index c7af5fb..9a4486e 100644 (file)
@@ -1,3 +1,18 @@
+2018-11-12  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Network: show secure certificate details per-request
+        https://bugs.webkit.org/show_bug.cgi?id=191447
+        <rdar://problem/30019476>
+
+        Reviewed by Joseph Pecoraro.
+
+        * http/tests/inspector/network/resource-response-security-expected.txt: Added.
+        * http/tests/inspector/network/resource-response-security.html: Added.
+
+        * platform/gtk/TestExpectations:
+        * platform/wincairo/TestExpectations:
+        * platform/wpe/TestExpectations:
+
 2018-11-12  Matt Baker  <mattbaker@apple.com>
 
         Web Inspector: Table should support shift-extending the row selection
diff --git a/LayoutTests/http/tests/inspector/network/resource-response-security-expected.txt b/LayoutTests/http/tests/inspector/network/resource-response-security-expected.txt
new file mode 100644 (file)
index 0000000..ddcd7f2
--- /dev/null
@@ -0,0 +1,12 @@
+Tests that a resource has security information.
+
+
+== Running test suite: Resource.Security
+-- Running test case: Resource.Security.Certificate
+PASS: Resource should have been loaded securely.
+PASS: Resource should have security information.
+PASS: Security information should include certificate information.
+PASS: Certificate should have subject
+PASS: Certificate should have a validFrom date.
+PASS: Certificate should have a validUntil date.
+
diff --git a/LayoutTests/http/tests/inspector/network/resource-response-security.html b/LayoutTests/http/tests/inspector/network/resource-response-security.html
new file mode 100644 (file)
index 0000000..4482ecf
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="../resources/inspector-test.js"></script>
+<script>
+function createSecureRequest() {
+    let img = document.createElement("img");
+    img.src = "https://localhost:8443/resources/square100.png";
+    document.body.appendChild(img);
+}
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("Resource.Security");
+
+    suite.addTestCase({
+        name: "Resource.Security.Certificate",
+        description: "Check if a resource has security certificate information.",
+        test(resolve, reject) {
+            WI.Resource.awaitEvent(WI.Resource.Event.ResponseReceived)
+            .then((event) => {
+                let resource = event.target;
+                InspectorTest.expectThat(resource.loadedSecurely, "Resource should have been loaded securely.");
+
+                let responseSecurity = resource.responseSecurity;
+                InspectorTest.expectNotNull(responseSecurity, "Resource should have security information.");
+
+                let certificate = responseSecurity.certificate;
+                InspectorTest.expectNotNull(certificate, "Security information should include certificate information.");
+                InspectorTest.expectGreaterThan(certificate.subject.length, 0, "Certificate should have subject");
+                InspectorTest.expectGreaterThan(certificate.validFrom, 0, "Certificate should have a validFrom date.");
+                InspectorTest.expectGreaterThan(certificate.validUntil, 0, "Certificate should have a validUntil date.");
+            })
+            .then(resolve, reject);
+
+            InspectorTest.evaluateInPage(`createSecureRequest()`)
+            .catch(reject);
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Tests that a resource has security information.</p>
+</body>
+</html>
index 3186f95..e99992f 100644 (file)
@@ -1977,6 +1977,7 @@ webkit.org/b/186750 imported/w3c/web-platform-tests/WebCryptoAPI/derive_bits_key
 webkit.org/b/186750 imported/w3c/web-platform-tests/WebCryptoAPI/derive_bits_keys/test_pbkdf2_short_long.https.html [ Pass Failure ]
 
 webkit.org/b/186847 http/tests/inspector/network/resource-sizes-memory-cache.html [ Pass Failure ]
+webkit.org/b/191497 http/tests/inspector/network/resource-response-security.html [ Skip ]
 
 webkit.org/b/186851 imported/w3c/web-platform-tests/xhr/formdata.htm [ Pass Failure ]
 webkit.org/b/186851 imported/w3c/web-platform-tests/xhr/formdata-blob.htm [ Pass Failure ]
index c0df196..9c89fdb 100644 (file)
@@ -998,6 +998,8 @@ http/tests/xmlhttprequest/upload-progress-events.html [ Failure ]
 
 http/tests/xmlviewer [ Skip ]
 
+webkit.org/b/191498 http/tests/inspector/network/resource-response-security.html [ Skip ]
+
 #///////////////////////////////////////////////////////////////////////////////
 # Issue categories below are shared with other platforms (primarily AppleWin)
 #///////////////////////////////////////////////////////////////////////////////
index ee112e3..bafcbb3 100644 (file)
@@ -533,6 +533,8 @@ Bug(WPE) fast/history/page-cache-notification-suspendable.html [ Skip ]
 
 Bug(WPE) fast/dom/HTMLAnchorElement [ Skip ]
 
+webkit.org/b/191497 http/tests/inspector/network/resource-response-security.html [ Skip ]
+
 #////////////////////////////////////////////////////////////////////////////////////////
 # 4. TESTS PASSING
 #////////////////////////////////////////////////////////////////////////////////////////
index 2617007..485072f 100644 (file)
@@ -1083,6 +1083,7 @@ set(JavaScriptCore_INSPECTOR_DOMAINS
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/Recording.json
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/Runtime.json
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/ScriptProfiler.json
+    ${JAVASCRIPTCORE_DIR}/inspector/protocol/Security.json
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/Timeline.json
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/Worker.json
 )
index ac13950..10158d4 100644 (file)
@@ -1,3 +1,20 @@
+2018-11-12  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Network: show secure certificate details per-request
+        https://bugs.webkit.org/show_bug.cgi?id=191447
+        <rdar://problem/30019476>
+
+        Reviewed by Joseph Pecoraro.
+
+        Add Security domain to hold security related protocol types.
+
+        * CMakeLists.txt:
+        * DerivedSources.make:
+        * inspector/protocol/Network.json:
+        * inspector/protocol/Security.json: Added.
+        * inspector/scripts/codegen/objc_generator.py:
+        (ObjCGenerator):
+
 2018-11-12  Saam barati  <sbarati@apple.com>
 
         Unreviewed. Rollout 238026: It caused ~8% JetStream 2 regressions on some iOS devices
index fcbf4b5..85362e6 100644 (file)
@@ -238,6 +238,7 @@ INSPECTOR_DOMAINS = \
     $(JavaScriptCore)/inspector/protocol/Recording.json \
     $(JavaScriptCore)/inspector/protocol/Runtime.json \
     $(JavaScriptCore)/inspector/protocol/ScriptProfiler.json \
+    $(JavaScriptCore)/inspector/protocol/Security.json \
     $(JavaScriptCore)/inspector/protocol/Timeline.json \
     $(JavaScriptCore)/inspector/protocol/Worker.json \
 #
index d469597..03aebce 100644 (file)
                 { "name": "status", "type": "integer", "description": "HTTP response status code." },
                 { "name": "statusText", "type": "string", "description": "HTTP response status text." },
                 { "name": "headers", "$ref": "Headers", "description": "HTTP response headers." },
-                { "name": "headersText", "type": "string", "optional": true, "description": "HTTP response headers text." },
                 { "name": "mimeType", "type": "string", "description": "Resource mimeType as determined by the browser." },
                 { "name": "source", "type": "string", "enum": ["unknown", "network", "memory-cache", "disk-cache", "service-worker"], "description": "Specifies where the response came from." },
                 { "name": "requestHeaders", "$ref": "Headers", "optional": true, "description": "Refined HTTP request headers that were actually transmitted over the network." },
-                { "name": "requestHeadersText", "type": "string", "optional": true, "description": "HTTP request headers text." },
-                { "name": "timing", "$ref": "ResourceTiming", "optional": true, "description": "Timing information for the given request." }
+                { "name": "timing", "$ref": "ResourceTiming", "optional": true, "description": "Timing information for the given request." },
+                { "name": "security", "$ref": "Security.Security", "optional": true, "description": "The security information for the given request." }
             ]
         },
         {
diff --git a/Source/JavaScriptCore/inspector/protocol/Security.json b/Source/JavaScriptCore/inspector/protocol/Security.json
new file mode 100644 (file)
index 0000000..4794aaf
--- /dev/null
@@ -0,0 +1,26 @@
+{
+    "domain": "Security",
+    "description": "Security domain allows the frontend to query for information relating to the security of the page (e.g. HTTPS info, TLS info, user activity, etc.).",
+    "types": [
+        {
+            "id": "Certificate",
+            "type": "object",
+            "description": "Information about a SSL certificate to display in the frontend.",
+            "properties": [
+                { "name": "subject", "type": "string", "optional": true },
+                { "name": "validFrom", "$ref": "Network.Walltime", "optional": true },
+                { "name": "validUntil", "$ref": "Network.Walltime", "optional": true },
+                { "name": "dnsNames", "type": "array", "items": { "type": "string" }, "optional": true, "description": "DNS names listed on the certificate."},
+                { "name": "ipAddresses", "type": "array", "items": { "type": "string" }, "optional": true, "description": "IP addresses listed on the certificate."}
+            ]
+        },
+        {
+            "id": "Security",
+            "type": "object",
+            "description": "Security information for a given Network.Response.",
+            "properties": [
+                { "name": "certificate", "$ref": "Certificate", "optional": true }
+            ]
+        }
+    ]
+}
index 25e25c6..3508503 100755 (executable)
@@ -122,7 +122,7 @@ class ObjCGenerator(Generator):
 
     # Generate ObjC types, command handlers, and event dispatchers for a subset of domains.
 
-    DOMAINS_TO_GENERATE = ['CSS', 'DOM', 'DOMStorage', 'Network', 'Page', 'Automation', 'GenericTypes']
+    DOMAINS_TO_GENERATE = ['CSS', 'DOM', 'DOMStorage', 'Network', 'Security', 'Page', 'Automation', 'GenericTypes']
 
     def should_generate_types_for_domain(self, domain):
         if not len(self.type_declarations_for_domain(domain)):
index 06a24e9..02a5705 100644 (file)
@@ -1,3 +1,57 @@
+2018-11-12  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Network: show secure certificate details per-request
+        https://bugs.webkit.org/show_bug.cgi?id=191447
+        <rdar://problem/30019476>
+
+        Reviewed by Joseph Pecoraro.
+
+        Test: http/tests/inspector/network/resource-response-security.html
+
+        * loader/ResourceLoader.h:
+        (WebCore::ResourceLoader::shouldIncludeCertificateInfo const):
+        * loader/ResourceLoader.cpp:
+        (WebCore::ResourceLoader::shouldIncludeCertificateInfo const): Added.
+        Always save certificate information when WebInspector is open.
+
+        * platform/network/CertificateInfoBase.h: Added.
+        (WebCore::CertificateInfoBase::containsNonRootSHA1SignedCertificate const):
+        (WebCore::CertificateInfoBase::summaryInfo const):
+        (WebCore::CertificateInfoBase::isEmpty const):
+        * platform/network/cf/CertificateInfo.h:
+        (WebCore::CertificateInfo::summaryInfo const): Added.
+        * platform/network/cf/CertificateInfoCFNet.cpp: Renamed from Source/WebCore/platform/network/mac/CertificateInfoMac.mm.
+        (WebCore::CertificateInfo::containsNonRootSHA1SignedCertificate):
+        (WebCore::CertificateInfo::summaryInfo const): Added.
+        * platform/network/curl/CertificateInfo.h:
+        (WebCore::CertificateInfo::summaryInfo const): Added.
+        (WebCore::CertificateInfo::isEmpty const): Added.
+        * platform/network/soup/CertificateInfo.h:
+        (WebCore::CertificateInfo::summaryInfo const): Added.
+        (WebCore::CertificateInfo::isEmpty const): Added.
+        Create base class for `CertificateInfo` so that `InspectorNetworkAgent` doesn't need to have
+        platform-specific code in its implementation.
+
+        * platform/network/cocoa/CertificateInfoCocoa.mm: Renamed from Source/WebCore/platform/network/mac/CertificateInfoMac.mm.
+        * platform/network/curl/CertificateInfoCFNet.cpp: Renamed from Source/WebCore/platform/network/curl/CertificateInfo.cpp.
+        * platform/network/soup/CertificateInfoSoup.cpp: Renamed from Source/WebCore/platform/network/soup/CertificateInfo.cpp.
+
+        * inspector/NetworkResourcesData.h:
+        (WebCore::NetworkResourcesData::ResourceData::certificateInfo const): Added.
+        (WebCore::NetworkResourcesData::ResourceData::setCertificateInfo): Added.
+        * inspector/NetworkResourcesData.cpp:
+        (WebCore::NetworkResourcesData::responseReceived):
+
+        * inspector/agents/InspectorNetworkAgent.cpp:
+        (WebCore::InspectorNetworkAgent::buildObjectForResourceResponse):
+
+        * PlatformAppleWin.cmake:
+        * PlatformMac.cmake:
+        * SourcesCocoa.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * platform/Curl.cmake:
+        * platform/SourcesSoup.txt:
+
 2018-11-12  Zalan Bujtas  <zalan@apple.com>
 
         Do not collapse the soon-to-be-parent anon block when we shuffle around the marker item renderer.
index 19b665b..7d483bf 100644 (file)
@@ -47,6 +47,7 @@ list(APPEND WebCore_SOURCES
     platform/graphics/win/FontCustomPlatformData.cpp
 
     platform/network/cf/AuthenticationCF.cpp
+    platform/network/cf/CertificateInfoCFNet.cpp
     platform/network/cf/CookieStorageCFNet.cpp
     platform/network/cf/CredentialStorageCFNet.cpp
     platform/network/cf/DNSResolveQueueCFNet.cpp
index 57fa917..5b03987 100644 (file)
@@ -414,6 +414,7 @@ list(APPEND WebCore_SOURCES
 
     platform/mediastream/mac/MockRealtimeVideoSourceMac.mm
 
+    platform/network/cf/CertificateInfoCFNet.cpp
     platform/network/cf/DNSResolveQueueCFNet.cpp
     platform/network/cf/FormDataStreamCFNet.cpp
     platform/network/cf/NetworkStorageSessionCFNet.cpp
@@ -433,7 +434,6 @@ list(APPEND WebCore_SOURCES
 
     platform/network/mac/AuthenticationMac.mm
     platform/network/mac/BlobDataFileReferenceMac.mm
-    platform/network/mac/CertificateInfoMac.mm
     platform/network/mac/CookieStorageMac.mm
     platform/network/mac/CredentialStorageMac.mm
     platform/network/mac/FormDataStreamMac.mm
index 61ec2bc..d1b1441 100644 (file)
@@ -494,6 +494,7 @@ platform/mediastream/mac/MockRealtimeVideoSourceMac.mm
 platform/mock/MediaPlaybackTargetPickerMock.cpp
 platform/mock/MediaPlaybackTargetMock.cpp
 
+platform/network/cf/CertificateInfoCFNet.cpp
 platform/network/cf/DNSResolveQueueCFNet.cpp
 platform/network/cf/FormDataStreamCFNet.cpp
 platform/network/cf/NetworkStorageSessionCFNet.cpp
@@ -501,6 +502,7 @@ platform/network/cf/ProxyServerCFNet.cpp
 platform/network/cf/ResourceRequestCFNet.cpp
 platform/network/cf/SocketStreamHandleImplCFNet.cpp
 
+platform/network/cocoa/CertificateInfoCocoa.mm
 platform/network/cocoa/CookieCocoa.mm
 platform/network/cocoa/CookieStorageObserver.mm
 platform/network/cocoa/CredentialCocoa.mm
@@ -517,7 +519,6 @@ platform/network/ios/WebCoreURLResponseIOS.mm
 
 platform/network/mac/AuthenticationMac.mm
 platform/network/mac/BlobDataFileReferenceMac.mm
-platform/network/mac/CertificateInfoMac.mm
 platform/network/mac/CookieStorageMac.mm
 platform/network/mac/CredentialStorageMac.mm
 platform/network/mac/FormDataStreamMac.mm
index 79495d7..f8b90da 100644 (file)
                9001774012E0347800648462 /* OESStandardDerivatives.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9001773D12E0347800648462 /* OESStandardDerivatives.cpp */; };
                9001774112E0347800648462 /* OESStandardDerivatives.h in Headers */ = {isa = PBXBuildFile; fileRef = 9001773E12E0347800648462 /* OESStandardDerivatives.h */; };
                9001788112E0370700648462 /* JSOESStandardDerivatives.h in Headers */ = {isa = PBXBuildFile; fileRef = 9001787F12E0370700648462 /* JSOESStandardDerivatives.h */; };
+               91B8F0B521953D65000C2B00 /* CertificateInfoBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 91B8F0B321953D65000C2B00 /* CertificateInfoBase.h */; settings = {ATTRIBUTES = (Private, ); }; };
                91B952241F58A58F00931DC2 /* RecordingSwizzleTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 91B952221F58A58000931DC2 /* RecordingSwizzleTypes.h */; };
                91C9F2F91AE3BEB00095B61C /* AXTextStateChangeIntent.h in Headers */ = {isa = PBXBuildFile; fileRef = 91C9F2F81AE3BE240095B61C /* AXTextStateChangeIntent.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9302B0BF0D79F82C00C7EE83 /* PageGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 9302B0BE0D79F82C00C7EE83 /* PageGroup.h */; settings = {ATTRIBUTES = (Private, ); }; };
                5EBB892F1C7777D000C65D41 /* MediaPayload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaPayload.h; sourceTree = "<group>"; };
                5EBB89301C7777E100C65D41 /* IceCandidate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IceCandidate.h; sourceTree = "<group>"; };
                5EBB89381C77BDA400C65D41 /* PeerMediaDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PeerMediaDescription.h; sourceTree = "<group>"; };
-               5F2DBBE7178E332D00141486 /* CertificateInfoMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CertificateInfoMac.mm; sourceTree = "<group>"; };
+               5F2DBBE7178E332D00141486 /* CertificateInfoCFNet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CertificateInfoCFNet.cpp; sourceTree = "<group>"; };
                5F2DBBE8178E336900141486 /* CertificateInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CertificateInfo.h; sourceTree = "<group>"; };
                5FC7DC26CFE2563200B85AE5 /* JSEventTarget.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = JSEventTarget.h; sourceTree = "<group>"; };
                5FE1D291178FD1F3001AA3C3 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
                9001773F12E0347800648462 /* OESStandardDerivatives.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = OESStandardDerivatives.idl; sourceTree = "<group>"; };
                9001787E12E0370700648462 /* JSOESStandardDerivatives.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSOESStandardDerivatives.cpp; sourceTree = "<group>"; };
                9001787F12E0370700648462 /* JSOESStandardDerivatives.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSOESStandardDerivatives.h; sourceTree = "<group>"; };
+               91B8F0B321953D65000C2B00 /* CertificateInfoBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CertificateInfoBase.h; sourceTree = "<group>"; };
                91B952221F58A58000931DC2 /* RecordingSwizzleTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RecordingSwizzleTypes.h; sourceTree = "<group>"; };
                91C9F2F81AE3BE240095B61C /* AXTextStateChangeIntent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AXTextStateChangeIntent.h; sourceTree = "<group>"; };
                9302B0BC0D79F82900C7EE83 /* PageGroup.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PageGroup.cpp; sourceTree = "<group>"; };
                                2EB4BCD1121F03E300EC4885 /* BlobResourceHandle.h */,
                                E43AF8E41AC5B7DD00CA717E /* CacheValidation.cpp */,
                                E43AF8E51AC5B7DD00CA717E /* CacheValidation.h */,
+                               91B8F0B321953D65000C2B00 /* CertificateInfoBase.h */,
                                7A56996E2086C618000E0433 /* CookieRequestHeaderFieldProxy.h */,
                                E13F01EA1270E10D00DFBA71 /* CookieStorage.h */,
                                514C76590CE923A1007EF3CD /* Credential.h */,
                                514C76420CE9234E007EF3CD /* AuthenticationMac.h */,
                                514C76430CE9234E007EF3CD /* AuthenticationMac.mm */,
                                E164A2EB191AE6350010737D /* BlobDataFileReferenceMac.mm */,
-                               5F2DBBE7178E332D00141486 /* CertificateInfoMac.mm */,
                                E13F01F01270E19000DFBA71 /* CookieStorageMac.mm */,
                                E1B4CD2410B322E200BFFD7E /* CredentialStorageMac.mm */,
                                514C76440CE9234E007EF3CD /* FormDataStreamMac.h */,
                        children = (
                                7EE6844E12D26E3800E79415 /* AuthenticationChallenge.h */,
                                5F2DBBE8178E336900141486 /* CertificateInfo.h */,
+                               5F2DBBE7178E332D00141486 /* CertificateInfoCFNet.cpp */,
                                B2F34FE80E82F82700F648CD /* DNSResolveQueueCFNet.cpp */,
                                7EE6845C12D26E3800FF9415 /* DNSResolveQueueCFNet.h */,
                                7EE6845312D26E3800E79415 /* FormDataStreamCFNet.cpp */,
                                CD318623199F1E2A0030A0F7 /* CDMPrivateMediaSourceAVFObjC.h in Headers */,
                                CDE595971BF26E2100A1CBE8 /* CDMSessionMediaSourceAVFObjC.h in Headers */,
                                5FA904CA178E61F5004C8A2D /* CertificateInfo.h in Headers */,
+                               91B8F0B521953D65000C2B00 /* CertificateInfoBase.h in Headers */,
                                E1A8E56717552B2A007488E7 /* CFURLExtras.h in Headers */,
                                FE36FD1516C7826500F887C1 /* ChangeVersionData.h in Headers */,
                                97BC69DD1505F076001B74AC /* ChangeVersionWrapper.h in Headers */,
index 3ab6ef4..90520f3 100644 (file)
@@ -164,6 +164,9 @@ void NetworkResourcesData::responseReceived(const String& requestId, const Strin
 
     if (InspectorNetworkAgent::shouldTreatAsText(response.mimeType()))
         resourceData->setDecoder(InspectorNetworkAgent::createTextDecoder(response.mimeType(), response.textEncodingName()));
+
+    if (auto& certificateInfo = response.certificateInfo())
+        resourceData->setCertificateInfo(certificateInfo);
 }
 
 void NetworkResourcesData::setResourceType(const String& requestId, InspectorPageAgent::ResourceType type)
index 782aa38..9a5fc2d 100644 (file)
@@ -84,6 +84,9 @@ public:
         RefPtr<SharedBuffer> buffer() const { return m_buffer.copyRef(); }
         void setBuffer(RefPtr<SharedBuffer>&& buffer) { m_buffer = WTFMove(buffer); }
 
+        const std::optional<CertificateInfo>& certificateInfo() const { return m_certificateInfo; }
+        void setCertificateInfo(const std::optional<CertificateInfo>& certificateInfo) { m_certificateInfo = certificateInfo; }
+
         CachedResource* cachedResource() const { return m_cachedResource; }
         void setCachedResource(CachedResource* cachedResource) { m_cachedResource = cachedResource; }
 
@@ -107,6 +110,7 @@ public:
         RefPtr<TextResourceDecoder> m_decoder;
         RefPtr<SharedBuffer> m_dataBuffer;
         RefPtr<SharedBuffer> m_buffer;
+        std::optional<CertificateInfo> m_certificateInfo;
         CachedResource* m_cachedResource { nullptr };
         InspectorPageAgent::ResourceType m_type { InspectorPageAgent::OtherResource };
         int m_httpStatusCode { 0 };
index 0feb005..ad9c8ad 100644 (file)
@@ -38,6 +38,7 @@
 #include "CachedResourceLoader.h"
 #include "CachedResourceRequestInitiators.h"
 #include "CachedScript.h"
+#include "CertificateInfo.h"
 #include "Document.h"
 #include "DocumentLoader.h"
 #include "DocumentThreadableLoader.h"
@@ -321,6 +322,40 @@ RefPtr<Inspector::Protocol::Network::Response> InspectorNetworkAgent::buildObjec
     if (resourceLoader)
         responseObject->setTiming(buildObjectForTiming(response.deprecatedNetworkLoadMetrics(), *resourceLoader));
 
+    if (auto& certificateInfo = response.certificateInfo()) {
+        auto securityPayload = Inspector::Protocol::Security::Security::create()
+            .release();
+
+        if (auto certificateSummaryInfo = certificateInfo.value().summaryInfo()) {
+            auto certificatePayload = Inspector::Protocol::Security::Certificate::create()
+                .release();
+
+            certificatePayload->setSubject(certificateSummaryInfo.value().subject);
+
+            if (auto validFrom = certificateSummaryInfo.value().validFrom)
+                certificatePayload->setValidFrom(validFrom.seconds());
+
+            if (auto validUntil = certificateSummaryInfo.value().validUntil)
+                certificatePayload->setValidUntil(validUntil.seconds());
+
+            auto dnsNamesPayload = JSON::ArrayOf<String>::create();
+            for (auto& dnsName : certificateSummaryInfo.value().dnsNames)
+                dnsNamesPayload->addItem(dnsName);
+            if (dnsNamesPayload->length())
+                certificatePayload->setDnsNames(WTFMove(dnsNamesPayload));
+
+            auto ipAddressesPayload = JSON::ArrayOf<String>::create();
+            for (auto& ipAddress : certificateSummaryInfo.value().ipAddresses)
+                ipAddressesPayload->addItem(ipAddress);
+            if (ipAddressesPayload->length())
+                certificatePayload->setDnsNames(WTFMove(ipAddressesPayload));
+
+            securityPayload->setCertificate(WTFMove(certificatePayload));
+        }
+
+        responseObject->setSecurity(WTFMove(securityPayload));
+    }
+
     return WTFMove(responseObject);
 }
 
index d5a5a08..ef3baac 100644 (file)
@@ -717,6 +717,15 @@ bool ResourceLoader::isAllowedToAskUserForCredentials() const
     return m_options.credentials == FetchOptions::Credentials::Include || (m_options.credentials == FetchOptions::Credentials::SameOrigin && m_frame->document()->securityOrigin().canRequest(originalRequest().url()));
 }
 
+bool ResourceLoader::shouldIncludeCertificateInfo() const
+{
+    if (m_options.certificateInfoPolicy == CertificateInfoPolicy::IncludeCertificateInfo)
+        return true;
+    if (UNLIKELY(InspectorInstrumentation::hasFrontends()))
+        return true;
+    return false;
+}
+
 void ResourceLoader::didReceiveAuthenticationChallenge(ResourceHandle* handle, const AuthenticationChallenge& challenge)
 {
     ASSERT_UNUSED(handle, handle == m_handle);
index ef35e5c..7119310 100644 (file)
@@ -127,7 +127,7 @@ public:
     bool shouldSniffContent() const { return m_options.sniffContent == ContentSniffingPolicy::SniffContent; }
     bool shouldSniffContentEncoding() const { return m_options.sniffContentEncoding == ContentEncodingSniffingPolicy::Sniff; }
     WEBCORE_EXPORT bool isAllowedToAskUserForCredentials() const;
-    bool shouldIncludeCertificateInfo() const { return m_options.certificateInfoPolicy == CertificateInfoPolicy::IncludeCertificateInfo; }
+    WEBCORE_EXPORT bool shouldIncludeCertificateInfo() const;
 
     bool reachedTerminalState() const { return m_reachedTerminalState; }
 
index 1cd8d9d..723c42b 100644 (file)
@@ -4,7 +4,7 @@ list(APPEND WebCore_INCLUDE_DIRECTORIES
 
 list(APPEND WebCore_SOURCES
     platform/network/curl/AuthenticationChallengeCurl.cpp
-    platform/network/curl/CertificateInfo.cpp
+    platform/network/curl/CertificateInfoCurl.cpp
     platform/network/curl/CookieJarCurlDatabase.cpp
     platform/network/curl/CookieJarDB.cpp
     platform/network/curl/CookieStorageCurl.cpp
index c02e49e..91b9f30 100644 (file)
@@ -22,7 +22,7 @@
 // THE POSSIBILITY OF SUCH DAMAGE.
 
 platform/network/soup/AuthenticationChallengeSoup.cpp
-platform/network/soup/CertificateInfo.cpp
+platform/network/soup/CertificateInfoSoup.cpp
 platform/network/soup/CookieSoup.cpp
 platform/network/soup/CookieStorageSoup.cpp
 platform/network/soup/CredentialStorageSoup.cpp
diff --git a/Source/WebCore/platform/network/CertificateInfoBase.h b/Source/WebCore/platform/network/CertificateInfoBase.h
new file mode 100644 (file)
index 0000000..b01b97f
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 Apple 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:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
+ */
+
+#pragma once
+
+#include <wtf/Optional.h>
+#include <wtf/Seconds.h>
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class CertificateInfoBase {
+public:
+    bool containsNonRootSHA1SignedCertificate() const { return false; };
+
+    struct SummaryInfo {
+        String subject;
+        Seconds validFrom;
+        Seconds validUntil;
+        Vector<String> dnsNames;
+        Vector<String> ipAddresses;
+    };
+    std::optional<SummaryInfo> summaryInfo() const { return std::nullopt; };
+
+    bool isEmpty() const { return true; };
+};
+
+} // namespace WebCore
index 61e0daa..3907960 100644 (file)
@@ -26,6 +26,7 @@
 #ifndef CertificateInfo_h
 #define CertificateInfo_h
 
+#include "CertificateInfoBase.h"
 #include <wtf/RetainPtr.h>
 
 #if PLATFORM(COCOA)
@@ -34,7 +35,7 @@
 
 namespace WebCore {
 
-class CertificateInfo {
+class CertificateInfo : public CertificateInfoBase {
 public:
      CertificateInfo() = default;
  
@@ -65,6 +66,8 @@ public:
     WEBCORE_EXPORT Type type() const;
     WEBCORE_EXPORT bool containsNonRootSHA1SignedCertificate() const;
 
+    std::optional<SummaryInfo> summaryInfo() const;
+
     bool isEmpty() const { return type() == Type::None; }
 
 #if PLATFORM(COCOA)
@@ -72,8 +75,10 @@ public:
 #endif
 
 #ifndef NDEBUG
+#if PLATFORM(COCOA)
     void dump() const;
 #endif
+#endif
 
 private:
 #if HAVE(SEC_TRUST_SERIALIZATION)
diff --git a/Source/WebCore/platform/network/cf/CertificateInfoCFNet.cpp b/Source/WebCore/platform/network/cf/CertificateInfoCFNet.cpp
new file mode 100644 (file)
index 0000000..b103445
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2010, 2015 Apple 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:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
+ */
+
+#include "config.h"
+#include "CertificateInfo.h"
+
+#include "NotImplemented.h"
+#include <wtf/cf/TypeCastsCF.h>
+
+#if PLATFORM(COCOA)
+#include <Security/SecCertificate.h>
+#include <wtf/spi/cocoa/SecuritySPI.h>
+
+WTF_DECLARE_CF_TYPE_TRAIT(SecCertificate);
+#endif
+
+namespace WebCore {
+
+#if PLATFORM(COCOA)
+RetainPtr<CFArrayRef> CertificateInfo::certificateChainFromSecTrust(SecTrustRef trust)
+{
+    auto count = SecTrustGetCertificateCount(trust);
+    auto certificateChain = CFArrayCreateMutable(0, count, &kCFTypeArrayCallBacks);
+    for (CFIndex i = 0; i < count; i++)
+        CFArrayAppendValue(certificateChain, SecTrustGetCertificateAtIndex(trust, i));
+    return adoptCF((CFArrayRef)certificateChain);
+}
+#endif
+
+CertificateInfo::Type CertificateInfo::type() const
+{
+#if HAVE(SEC_TRUST_SERIALIZATION)
+    if (m_trust)
+        return Type::Trust;
+#endif
+    if (m_certificateChain)
+        return Type::CertificateChain;
+    return Type::None;
+}
+
+CFArrayRef CertificateInfo::certificateChain() const
+{
+#if HAVE(SEC_TRUST_SERIALIZATION)
+    if (m_certificateChain)
+        return m_certificateChain.get();
+
+    if (m_trust) 
+        m_certificateChain = CertificateInfo::certificateChainFromSecTrust(m_trust.get());
+#endif
+
+    return m_certificateChain.get();
+}
+
+bool CertificateInfo::containsNonRootSHA1SignedCertificate() const
+{
+#if HAVE(SEC_TRUST_SERIALIZATION)
+    if (m_trust) {
+        // Allow only the root certificate (the last in the chain) to be SHA1.
+        for (CFIndex i = 0, size = SecTrustGetCertificateCount(trust()) - 1; i < size; ++i) {
+            auto certificate = SecTrustGetCertificateAtIndex(trust(), i);
+            if (SecCertificateGetSignatureHashAlgorithm(certificate) == kSecSignatureHashAlgorithmSHA1)
+                return true;
+        }
+
+        return false;
+    }
+#endif
+
+#if PLATFORM(COCOA)
+    if (m_certificateChain) {
+        // Allow only the root certificate (the last in the chain) to be SHA1.
+        for (CFIndex i = 0, size = CFArrayGetCount(m_certificateChain.get()) - 1; i < size; ++i) {
+            auto certificate = checked_cf_cast<SecCertificateRef>(CFArrayGetValueAtIndex(m_certificateChain.get(), i));
+            if (SecCertificateGetSignatureHashAlgorithm(certificate) == kSecSignatureHashAlgorithmSHA1)
+                return true;
+        }
+        return false;
+    }
+#endif
+
+    return false;
+}
+
+std::optional<CertificateInfo::SummaryInfo> CertificateInfo::summaryInfo() const
+{
+    auto chain = certificateChain();
+    if (!chain)
+        return std::nullopt;
+
+    SummaryInfo summaryInfo;
+
+#if PLATFORM(COCOA) && !PLATFORM(IOS_FAMILY_SIMULATOR) && !PLATFORM(IOSMAC)
+    auto leafCertificate = checked_cf_cast<SecCertificateRef>(CFArrayGetValueAtIndex(chain, 0));
+
+    auto subjectCF = adoptCF(SecCertificateCopySubjectSummary(leafCertificate));
+    summaryInfo.subject = subjectCF.get();
+#endif
+
+#if PLATFORM(MAC)
+    if (auto certificateDictionary = adoptCF(SecCertificateCopyValues(leafCertificate, nullptr, nullptr))) {
+        // CFAbsoluteTime is relative to 01/01/1970 00:00:00 GMT.
+        const Seconds absoluteReferenceDate(978307200);
+
+        if (auto validNotBefore = checked_cf_cast<CFDictionaryRef>(CFDictionaryGetValue(certificateDictionary.get(), kSecOIDX509V1ValidityNotBefore))) {
+            if (auto number = checked_cf_cast<CFNumberRef>(CFDictionaryGetValue(validNotBefore, CFSTR("value")))) {
+                double numberValue;
+                if (CFNumberGetValue(number, kCFNumberDoubleType, &numberValue))
+                    summaryInfo.validFrom = absoluteReferenceDate + Seconds(numberValue);
+            }
+        }
+
+        if (auto validNotAfter = checked_cf_cast<CFDictionaryRef>(CFDictionaryGetValue(certificateDictionary.get(), kSecOIDX509V1ValidityNotAfter))) {
+            if (auto number = checked_cf_cast<CFNumberRef>(CFDictionaryGetValue(validNotAfter, CFSTR("value")))) {
+                double numberValue;
+                if (CFNumberGetValue(number, kCFNumberDoubleType, &numberValue))
+                    summaryInfo.validUntil = absoluteReferenceDate + Seconds(numberValue);
+            }
+        }
+
+        if (auto dnsNames = checked_cf_cast<CFDictionaryRef>(CFDictionaryGetValue(certificateDictionary.get(), CFSTR("DNSNAMES")))) {
+            if (auto dnsNamesArray = checked_cf_cast<CFArrayRef>(CFDictionaryGetValue(dnsNames, CFSTR("value")))) {
+                for (CFIndex i = 0, count = CFArrayGetCount(dnsNamesArray); i < count; ++i) {
+                    if (auto dnsName = checked_cf_cast<CFStringRef>(CFArrayGetValueAtIndex(dnsNamesArray, i)))
+                        summaryInfo.dnsNames.append(dnsName);
+                }
+            }
+        }
+
+        if (auto ipAddresses = checked_cf_cast<CFDictionaryRef>(CFDictionaryGetValue(certificateDictionary.get(), CFSTR("IPADDRESSES")))) {
+            if (auto ipAddressesArray = checked_cf_cast<CFArrayRef>(CFDictionaryGetValue(ipAddresses, CFSTR("value")))) {
+                for (CFIndex i = 0, count = CFArrayGetCount(ipAddressesArray); i < count; ++i) {
+                    if (auto ipAddress = checked_cf_cast<CFStringRef>(CFArrayGetValueAtIndex(ipAddressesArray, i)))
+                        summaryInfo.ipAddresses.append(ipAddress);
+                }
+            }
+        }
+    }
+#endif
+
+    return summaryInfo;
+}
+
+} // namespace WebCore
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import "config.h"
-#import "CertificateInfo.h"
+#include "config.h"
+#include "CertificateInfo.h"
 
-#import "NotImplemented.h"
-#import <wtf/cf/TypeCastsCF.h>
-#import <wtf/spi/cocoa/SecuritySPI.h>
+#include <Security/SecCertificate.h>
+#include <wtf/cf/TypeCastsCF.h>
+#include <wtf/spi/cocoa/SecuritySPI.h>
 
 WTF_DECLARE_CF_TYPE_TRAIT(SecCertificate);
 
 namespace WebCore {
 
-#if PLATFORM(COCOA)
-RetainPtr<CFArrayRef> CertificateInfo::certificateChainFromSecTrust(SecTrustRef trust)
-{
-    auto count = SecTrustGetCertificateCount(trust);
-    auto certificateChain = CFArrayCreateMutable(0, count, &kCFTypeArrayCallBacks);
-    for (CFIndex i = 0; i < count; i++)
-        CFArrayAppendValue(certificateChain, SecTrustGetCertificateAtIndex(trust, i));
-    return adoptCF((CFArrayRef)certificateChain);
-}
-#endif
-
-CertificateInfo::Type CertificateInfo::type() const
-{
-#if HAVE(SEC_TRUST_SERIALIZATION)
-    if (m_trust)
-        return Type::Trust;
-#endif
-    if (m_certificateChain)
-        return Type::CertificateChain;
-    return Type::None;
-}
-
-CFArrayRef CertificateInfo::certificateChain() const
-{
-#if HAVE(SEC_TRUST_SERIALIZATION)
-    if (m_certificateChain)
-        return m_certificateChain.get();
-
-    if (m_trust) 
-        m_certificateChain = CertificateInfo::certificateChainFromSecTrust(m_trust.get());
-#endif
-
-    return m_certificateChain.get();
-}
-
-bool CertificateInfo::containsNonRootSHA1SignedCertificate() const
-{
-#if HAVE(SEC_TRUST_SERIALIZATION)
-    if (m_trust) {
-        // Allow only the root certificate (the last in the chain) to be SHA1.
-        for (CFIndex i = 0, size = SecTrustGetCertificateCount(trust()) - 1; i < size; ++i) {
-            auto certificate = SecTrustGetCertificateAtIndex(trust(), i);
-            if (SecCertificateGetSignatureHashAlgorithm(certificate) == kSecSignatureHashAlgorithmSHA1)
-                return true;
-        }
-
-        return false;
-    }
-#endif
-
-    if (m_certificateChain) {
-        // Allow only the root certificate (the last in the chain) to be SHA1.
-        for (CFIndex i = 0, size = CFArrayGetCount(m_certificateChain.get()) - 1; i < size; ++i) {
-            auto certificate = checked_cf_cast<SecCertificateRef>(CFArrayGetValueAtIndex(m_certificateChain.get(), i));
-            if (SecCertificateGetSignatureHashAlgorithm(certificate) == kSecSignatureHashAlgorithmSHA1)
-                return true;
-        }
-        return false;
-    }
-
-    return false;
-}
-
 #ifndef NDEBUG
 void CertificateInfo::dump() const
 {
@@ -126,7 +63,7 @@ void CertificateInfo::dump() const
 
         return;
     }
-    
+
     NSLog(@"CertificateInfo (Empty)\n");
 }
 #endif
index faf1aba..274c8f5 100644 (file)
 
 #pragma once
 
+#include "CertificateInfoBase.h"
 #include "NotImplemented.h"
 #include <wtf/Vector.h>
 
 namespace WebCore {
 
-class CertificateInfo {
+class CertificateInfo : public CertificateInfoBase {
 public:
     using Certificate = Vector<uint8_t>;
 
@@ -44,6 +45,10 @@ public:
 
     bool containsNonRootSHA1SignedCertificate() const { notImplemented(); return false; }
 
+    std::optional<SummaryInfo> summaryInfo() const { notImplemented(); return std::nullopt; }
+
+    bool isEmpty() const { return m_certificateChain.isEmpty(); }
+
     static Certificate makeCertificate(const char*, size_t);
 
 private:
index 206cdef..d0f6e22 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef CertificateInfo_h
 #define CertificateInfo_h
 
+#include "CertificateInfoBase.h"
 #include "NotImplemented.h"
 #include <libsoup/soup.h>
 #include <wtf/glib/GRefPtr.h>
@@ -36,7 +37,7 @@ namespace WebCore {
 class ResourceError;
 class ResourceResponse;
 
-class CertificateInfo {
+class CertificateInfo  : public CertificateInfoBase {
 public:
     CertificateInfo();
     explicit CertificateInfo(const WebCore::ResourceResponse&);
@@ -51,6 +52,10 @@ public:
 
     bool containsNonRootSHA1SignedCertificate() const { notImplemented(); return false; }
 
+    std::optional<SummaryInfo> summaryInfo() const { notImplemented(); return std::nullopt; }
+
+    bool isEmpty() const { return !m_certificate; }
+
 private:
     GRefPtr<GTlsCertificate> m_certificate;
     GTlsCertificateFlags m_tlsErrors;
index 7d849a9..c4198ae 100644 (file)
@@ -1,3 +1,96 @@
+2018-11-12  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Network: show secure certificate details per-request
+        https://bugs.webkit.org/show_bug.cgi?id=191447
+        <rdar://problem/30019476>
+
+        Reviewed by Joseph Pecoraro.
+
+        * UserInterface/Controllers/NetworkManager.js:
+        (WI.NetworkManager.prototype.resourceRequestWasServedFromMemoryCache):
+        (WI.NetworkManager.prototype.resourceRequestDidReceiveResponse):
+
+        * UserInterface/Models/Resource.js:
+        (WI.Resource.prototype.get responseSecurity): Added.
+        (WI.Resource.prototype.get loadedSecurely): Added.
+        (WI.Resource.prototype.updateForResponse):
+
+        * UserInterface/Views/NetworkResourceDetailView.js:
+        (WI.NetworkResourceDetailView):
+        (WI.NetworkResourceDetailView.prototype.initialLayout):
+        (WI.NetworkResourceDetailView.prototype.showContentViewForIdentifier):
+        * UserInterface/Views/NetworkResourceDetailView.css:
+        (.content-view.resource-details .go-to-arrow): Added.
+        (.content-view.resource-details.showing-find-banner .search-highlight): Added.
+
+        * UserInterface/Views/ResourceSecurityContentView.js: Added.
+        (WI.ResourceSecurityContentView):
+        (WI.ResourceSecurityContentView.prototype.initialLayout):
+        (WI.ResourceSecurityContentView.prototype.layout):
+        (WI.ResourceSecurityContentView.prototype.closed):
+        (WI.ResourceSecurityContentView.prototype.get supportsSearch):
+        (WI.ResourceSecurityContentView.prototype.get numberOfSearchResults):
+        (WI.ResourceSecurityContentView.prototype.get hasPerformedSearch):
+        (WI.ResourceSecurityContentView.prototype.set automaticallyRevealFirstSearchResult):
+        (WI.ResourceSecurityContentView.prototype.performSearch):
+        (WI.ResourceSecurityContentView.prototype.searchCleared):
+        (WI.ResourceSecurityContentView.prototype.revealPreviousSearchResult):
+        (WI.ResourceSecurityContentView.prototype.revealNextSearchResult):
+        (WI.ResourceSecurityContentView.prototype._refreshCetificateSection):
+        (WI.ResourceSecurityContentView.prototype._perfomSearchOnKeyValuePairs):
+        (WI.ResourceSecurityContentView.prototype._revealSearchResult):
+        (WI.ResourceSecurityContentView.prototype._handleResourceResponseReceived):
+        * UserInterface/Views/ResourceSecurityContentView.css: Added.
+        (body[dir] .resource-security > section.certificate > .details):
+        (.resource-security .details .key):
+        (.resource-security .dns-name + .dns-name > .key,):
+        (.resource-security .show-more):
+        (@media (prefers-dark-interface) body[dir] .resource-security > section.certificate > .details):
+        (@media (prefers-dark-interface) .resource-security .details .key):
+
+        * UserInterface/Views/ResourceCookiesContentView.js:
+        (WI.ResourceCookiesContentView.prototype._refreshRequestCookiesSection):
+        (WI.ResourceCookiesContentView.prototype._refreshResponseCookiesSection):
+        (WI.ResourceCookiesContentView.prototype._markIncompleteSectionWithMessage): Deleted.
+        (WI.ResourceCookiesContentView.prototype._markIncompleteSectionWithLoadingIndicator): Deleted.
+        * UserInterface/Views/ResourceHeadersContentView.js:
+        (WI.ResourceHeadersContentView.prototype._refreshSummarySection):
+        (WI.ResourceHeadersContentView.prototype._refreshRedirectHeadersSections):
+        (WI.ResourceHeadersContentView.prototype._refreshRequestHeadersSection):
+        (WI.ResourceHeadersContentView.prototype._refreshResponseHeadersSection):
+        (WI.ResourceHeadersContentView.prototype._refreshQueryStringSection):
+        (WI.ResourceHeadersContentView.prototype._refreshRequestDataSection):
+        (WI.ResourceHeadersContentView.prototype._markIncompleteSectionWithMessage): Deleted.
+        (WI.ResourceHeadersContentView.prototype._markIncompleteSectionWithLoadingIndicator): Deleted.
+        (WI.ResourceHeadersContentView.prototype._appendKeyValuePair): Deleted.
+        * UserInterface/Views/ResourceHeadersContentView.css:
+        (.resource-headers .h1-status > .key,):
+        (body[dir] .resource-headers > section.error > .details): Deleted.
+        (.resource-headers > section.error .key): Deleted.
+        (.resource-headers .details): Deleted.
+        (.resource-headers .details .pair): Deleted.
+        (body[dir=rtl] .resource-headers .details .pair): Deleted.
+        (.resource-headers .details .key): Deleted.
+        (.resource-headers .value): Deleted.
+        (.resource-headers .go-to-arrow): Deleted.
+        (.resource-headers.showing-find-banner .search-highlight): Deleted.
+        * UserInterface/Views/ResourceDetailsSection.js:
+        (WI.ResourceDetailsSection.prototype.markIncompleteSectionWithMessage): Added.
+        (WI.ResourceDetailsSection.prototype.markIncompleteSectionWithLoadingIndicator): Added.
+        (WI.ResourceDetailsSection.prototype.appendKeyValuePair): Added.
+        * UserInterface/Views/ResourceDetailsSection.css:
+        (.resource-details > section > .details): Added.
+        (.resource-details > section > .details > .pair): Added.
+        (body[dir=rtl] .resource-details > section > .details > .pair): Added.
+        (.resource-details > section > .details > .pair > .key): Added.
+        (.resource-details > section > .details > .pair > .value): Added.
+        (body[dir] .resource-details > section.error > .details): Added.
+        (.resource-details > section.error > .details > .pair > .key): Added.
+        Move commonly used functions/styles from container classes onto this object.
+
+        * UserInterface/Main.html:
+        * Localizations/en.lproj/localizedStrings.js:
+
 2018-11-12  Matt Baker  <mattbaker@apple.com>
 
         Web Inspector: Table should support shift-extending the row selection
index df07f4d..224efca 100644 (file)
@@ -176,6 +176,7 @@ localizedStrings["Capture Screenshot"] = "Capture Screenshot";
 localizedStrings["Capturing"] = "Capturing";
 localizedStrings["Catch Variables"] = "Catch Variables";
 localizedStrings["Categories"] = "Categories";
+localizedStrings["Certificate"] = "Certificate";
 localizedStrings["Character Data"] = "Character Data";
 localizedStrings["Charge ‘%s’ to Callers"] = "Charge ‘%s’ to Callers";
 localizedStrings["Checked"] = "Checked";
@@ -461,6 +462,7 @@ localizedStrings["Hierarchy Level"] = "Hierarchy Level";
 localizedStrings["High"] = "High";
 localizedStrings["Highest: %s"] = "Highest: %s";
 localizedStrings["Host"] = "Host";
+localizedStrings["IP"] = "IP";
 localizedStrings["IP Address"] = "IP Address";
 localizedStrings["Identity"] = "Identity";
 localizedStrings["Idle"] = "Idle";
@@ -601,6 +603,8 @@ localizedStrings["No request, served from the disk cache."] = "No request, serve
 localizedStrings["No request, served from the memory cache."] = "No request, served from the memory cache.";
 localizedStrings["No response cookies."] = "No response cookies.";
 localizedStrings["No response headers"] = "No response headers";
+localizedStrings["No response security certificate."] = "No response security certificate.";
+localizedStrings["No response security information."] = "No response security information.";
 localizedStrings["Node"] = "Node";
 localizedStrings["Node Removed"] = "Node Removed";
 localizedStrings["Nodes"] = "Nodes";
@@ -766,6 +770,7 @@ localizedStrings["Search"] = "Search";
 localizedStrings["Search Again"] = "Search Again";
 localizedStrings["Search Resource Content"] = "Search Resource Content";
 localizedStrings["Secure"] = "Secure";
+localizedStrings["Security"] = "Security";
 localizedStrings["Security Issue"] = "Security Issue";
 localizedStrings["Security Origin"] = "Security Origin";
 localizedStrings["Select baseline snapshot"] = "Select baseline snapshot";
@@ -875,6 +880,7 @@ localizedStrings["Styles \u2014 Computed"] = "Styles \u2014 Computed";
 localizedStrings["Styles \u2014 Rules"] = "Styles \u2014 Rules";
 localizedStrings["Stylesheet"] = "Stylesheet";
 localizedStrings["Stylesheets"] = "Stylesheets";
+localizedStrings["Subject"] = "Subject";
 localizedStrings["Subtree Modified"] = "Subtree Modified";
 localizedStrings["Summary"] = "Summary";
 localizedStrings["TCP"] = "TCP";
@@ -887,6 +893,7 @@ localizedStrings["Text"] = "Text";
 localizedStrings["Text Frame"] = "Text Frame";
 localizedStrings["Text Node"] = "Text Node";
 localizedStrings["The page's content has changed"] = "The page's content has changed";
+localizedStrings["The resource was requested insecurely."] = "The resource was requested insecurely.";
 localizedStrings["The “%s“ audit failed"] = "The “%s“ audit failed";
 localizedStrings["The “%s“ audit is unsupported"] = "The “%s“ audit is unsupported";
 localizedStrings["The “%s“ audit passed"] = "The “%s“ audit passed";
@@ -945,6 +952,8 @@ localizedStrings["User Agent"] = "User Agent";
 localizedStrings["User Agent Stylesheet"] = "User Agent Stylesheet";
 localizedStrings["User Interface:"] = "User Interface:";
 localizedStrings["User Stylesheet"] = "User Stylesheet";
+localizedStrings["Valid From"] = "Valid From";
+localizedStrings["Valid Until"] = "Valid Until";
 localizedStrings["Value"] = "Value";
 localizedStrings["Variables"] = "Variables";
 localizedStrings["Verbose"] = "Verbose";
index 94d89c7..be4bd17 100644 (file)
@@ -419,7 +419,7 @@ WI.NetworkManager = class NetworkManager extends WI.Object
             initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator),
             initiatorNode: this._initiatorNodeFromPayload(initiator),
         });
-        resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime, response.timing, responseSource);
+        resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime, response.timing, responseSource, response.security);
         resource.increaseSize(cachedResourcePayload.bodySize, elapsedTime);
         resource.increaseTransferSize(cachedResourcePayload.bodySize);
         resource.setCachedResponseBodySize(cachedResourcePayload.bodySize);
@@ -481,7 +481,7 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         if (response.fromDiskCache)
             resource.legacyMarkServedFromDiskCache();
 
-        resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime, response.timing, response.source);
+        resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime, response.timing, response.source, response.security);
     }
 
     resourceRequestDidReceiveData(requestIdentifier, dataLength, encodedDataLength, timestamp)
index 00b5cff..7c37e46 100644 (file)
     <link rel="stylesheet" href="Views/ResourceDetailsSidebarPanel.css">
     <link rel="stylesheet" href="Views/ResourceHeadersContentView.css">
     <link rel="stylesheet" href="Views/ResourceIcons.css">
+    <link rel="stylesheet" href="Views/ResourceSecurityContentView.css">
     <link rel="stylesheet" href="Views/ResourceSizesContentView.css">
     <link rel="stylesheet" href="Views/ResourceSidebarPanel.css">
     <link rel="stylesheet" href="Views/ResourceTimelineDataGridNode.css">
     <script src="Views/ResourceDetailsSection.js"></script>
     <script src="Views/ResourceDetailsSidebarPanel.js"></script>
     <script src="Views/ResourceHeadersContentView.js"></script>
+    <script src="Views/ResourceSecurityContentView.js"></script>
     <script src="Views/ResourceSidebarPanel.js"></script>
     <script src="Views/ResourceSizesContentView.js"></script>
     <script src="Views/ResourceTimelineDataGridNode.js"></script>
index 44a78f4..6a647d2 100644 (file)
@@ -70,6 +70,7 @@ WI.Resource = class Resource extends WI.SourceCode
         this._failureReasonText = null;
         this._receivedNetworkLoadMetrics = false;
         this._responseSource = WI.Resource.ResponseSource.Unknown;
+        this._responseSecurity = null;
         this._timingData = new WI.ResourceTimingData(this);
         this._protocol = null;
         this._priority = WI.Resource.NetworkPriority.Unknown;
@@ -306,6 +307,7 @@ WI.Resource = class Resource extends WI.SourceCode
     get statusCode() { return this._statusCode; }
     get statusText() { return this._statusText; }
     get responseSource() { return this._responseSource; }
+    get responseSecurity() { return this._responseSecurity; }
     get timingData() { return this._timingData; }
     get protocol() { return this._protocol; }
     get priority() { return this._priority; }
@@ -338,6 +340,15 @@ WI.Resource = class Resource extends WI.SourceCode
         return this._urlComponents;
     }
 
+    get loadedSecurely()
+    {
+        if (this.urlComponents.scheme !== "https" && this.urlComponents.scheme !== "wss" && this.urlComponents.scheme !== "sftp")
+            return false;
+        if (isNaN(this._timingData.secureConnectionStart) && !isNaN(this._timingData.connectionStart))
+            return false;
+        return true;
+    }
+
     get displayName()
     {
         return WI.displayNameForURL(this._url, this.urlComponents);
@@ -675,7 +686,7 @@ WI.Resource = class Resource extends WI.SourceCode
         return requestDataContentType && requestDataContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i);
     }
 
-    updateForResponse(url, mimeType, type, responseHeaders, statusCode, statusText, elapsedTime, timingData, source)
+    updateForResponse(url, mimeType, type, responseHeaders, statusCode, statusText, elapsedTime, timingData, source, security)
     {
         console.assert(!this._finished);
         console.assert(!this._failed);
@@ -704,6 +715,9 @@ WI.Resource = class Resource extends WI.SourceCode
         if (source)
             this._responseSource = WI.Resource.responseSourceFromPayload(source);
 
+        if (security)
+            this._responseSecurity = security;
+
         const headerBaseSize = 12; // Length of "HTTP/1.1 ", " ", and "\r\n".
         const headerPad = 4; // Length of ": " and "\r\n".
         this._estimatedResponseHeadersSize = String(this._statusCode).length + this._statusText.length + headerBaseSize;
index 47c1fc7..2beb0e5 100644 (file)
     -webkit-user-select: text;
     white-space: nowrap;
 }
+
+.content-view.resource-details .go-to-arrow {
+    vertical-align: top;
+    bottom: 1px;
+}
+
+.content-view.resource-details.showing-find-banner .search-highlight {
+    color: var(--text-color);
+    background-color: hsla(53, 83%, 53%, 0.2);
+    border-bottom: 1px solid hsl(47, 82%, 60%);
+}
index 2d0230f..4d912e3 100644 (file)
@@ -38,6 +38,7 @@ WI.NetworkResourceDetailView = class NetworkResourceDetailView extends WI.Networ
         this._cookiesContentView = null;
         this._sizesContentView = null;
         this._timingContentView = null;
+        this._securityContentView = null;
     }
 
     // Public
@@ -89,6 +90,7 @@ WI.NetworkResourceDetailView = class NetworkResourceDetailView extends WI.Networ
         this.createDetailNavigationItem("cookies", WI.UIString("Cookies"));
         this.createDetailNavigationItem("sizes", WI.UIString("Sizes"));
         this.createDetailNavigationItem("timing", WI.UIString("Timing"));
+        this.createDetailNavigationItem("security", WI.UIString("Security"));
 
         super.initialLayout();
     }
@@ -128,6 +130,11 @@ WI.NetworkResourceDetailView = class NetworkResourceDetailView extends WI.Networ
                 this._timingContentView = new WI.ResourceTimingContentView(this.representedObject);
             this._contentBrowser.showContentView(this._timingContentView, this._contentViewCookie);
             break;
+        case "security":
+            if (!this._securityContentView)
+                this._securityContentView = new WI.ResourceSecurityContentView(this.representedObject);
+            this._contentBrowser.showContentView(this._securityContentView, this._contentViewCookie);
+            break;
         }
 
         this._contentViewCookie = null;
index ac030c4..f0b02fe 100644 (file)
@@ -119,23 +119,6 @@ WI.ResourceCookiesContentView = class ResourceCookiesContentView extends WI.Cont
 
     // Private
 
-    _markIncompleteSectionWithMessage(section, message)
-    {
-        section.toggleIncomplete(true);
-
-        let p = section.detailsElement.appendChild(document.createElement("p"));
-        p.textContent = message;
-    }
-
-    _markIncompleteSectionWithLoadingIndicator(section)
-    {
-        section.toggleIncomplete(true);
-
-        let p = section.detailsElement.appendChild(document.createElement("p"));
-        let spinner = new WI.IndeterminateProgressSpinner;
-        p.appendChild(spinner.element);
-    }
-
     _dataSourceForTable(table)
     {
         return table === this._requestCookiesTable ? this._requestCookiesDataSource : this._responseCookiesDataSource;
@@ -206,7 +189,7 @@ WI.ResourceCookiesContentView = class ResourceCookiesContentView extends WI.Cont
         detailsElement.removeChildren();
 
         if (this._resource.responseSource === WI.Resource.ResponseSource.MemoryCache) {
-            this._markIncompleteSectionWithMessage(this._requestCookiesSection, WI.UIString("No request, served from the memory cache."));
+            this._requestCookiesSection.markIncompleteSectionWithMessage(WI.UIString("No request, served from the memory cache."));
             return;
         }
 
@@ -225,7 +208,7 @@ WI.ResourceCookiesContentView = class ResourceCookiesContentView extends WI.Cont
         if (!this._requestCookiesDataSource.length) {
             if (this._requestCookiesTable.isAttached)
                 this.removeSubview(this._requestCookiesTable);
-            this._markIncompleteSectionWithMessage(this._requestCookiesSection, WI.UIString("No request cookies."));
+            this._requestCookiesSection.markIncompleteSectionWithMessage(WI.UIString("No request cookies."));
         } else {
             this._requestCookiesSection.toggleIncomplete(false);
             this._requestCookiesTable.element.style.height = this._sizeForTable(this._requestCookiesTable) + "px";
@@ -241,7 +224,7 @@ WI.ResourceCookiesContentView = class ResourceCookiesContentView extends WI.Cont
         detailsElement.removeChildren();
 
         if (!this._resource.hasResponse()) {
-            this._markIncompleteSectionWithLoadingIndicator(this._responseCookiesSection);
+            this._responseCookiesSection.markIncompleteSectionWithLoadingIndicator();
             return;
         }
 
@@ -267,7 +250,7 @@ WI.ResourceCookiesContentView = class ResourceCookiesContentView extends WI.Cont
         if (!this._responseCookiesDataSource.length) {
             if (this._responseCookiesTable.isAttached)
                 this.removeSubview(this._responseCookiesTable);
-            this._markIncompleteSectionWithMessage(this._responseCookiesSection, WI.UIString("No response cookies."));
+            this._responseCookiesSection.markIncompleteSectionWithMessage(WI.UIString("No response cookies."));
         } else {
             this._responseCookiesSection.toggleIncomplete(false);
             this._responseCookiesTable.element.style.height = this._sizeForTable(this._responseCookiesTable) + "px";
index cf39cd2..b6cf470 100644 (file)
@@ -34,6 +34,8 @@
 
 .resource-details > section > .details {
     -webkit-margin-start: 10px;
+    white-space: normal;
+    word-break: break-all;
 }
 
 body[dir=ltr] .resource-details > section > .details {
@@ -54,3 +56,30 @@ body[dir=rtl] .resource-details > section > .details {
     color: var(--console-secondary-text-color) !important;
     border-color: var(--console-secondary-text-color) !important;
 }
+
+.resource-details > section > .details > .pair {
+    --resource-details-value-indent: 15px;
+    -webkit-margin-start: var(--resource-details-value-indent);
+}
+
+body[dir=rtl] .resource-details > section > .details > .pair {
+    /* Don't include indents in RTL */
+    --resource-details-value-indent: 0px;
+}
+
+.resource-details > section > .details > .pair > .key {
+    font-weight: 500;
+    -webkit-margin-start: calc(var(--resource-details-value-indent) * -1);
+}
+
+.resource-details > section > .details > .pair > .value {
+    color: var(--text-color);
+}
+
+body[dir] .resource-details > section.error > .details {
+    border-color: var(--network-error-color);
+}
+
+.resource-details > section.error > .details > .pair > .key {
+    color: var(--network-error-color);
+}
index e63c58b..9643ffb 100644 (file)
@@ -59,4 +59,49 @@ WI.ResourceDetailsSection = class ResourceDetailsSection
         console.assert(typeof isError === "boolean");
         this.element.classList.toggle("error", isError);
     }
+
+    markIncompleteSectionWithMessage(message)
+    {
+        this.toggleIncomplete(true);
+
+        let p = this._detailsElement.appendChild(document.createElement("p"));
+        p.textContent = message;
+    }
+
+    markIncompleteSectionWithLoadingIndicator()
+    {
+        this.toggleIncomplete(true);
+
+        let p = this._detailsElement.appendChild(document.createElement("p"));
+        let spinner = new WI.IndeterminateProgressSpinner;
+        p.appendChild(spinner.element);
+    }
+
+    appendKeyValuePair(key, value, className)
+    {
+        let p = this._detailsElement.appendChild(document.createElement("p"));
+        p.className = "pair";
+        if (className)
+            p.classList.add(className);
+
+        let keyElement = p.appendChild(document.createElement("span"));
+        keyElement.className = "key";
+
+        console.assert(typeof key === "string" || key instanceof Node);
+        if (key instanceof Node)
+            keyElement.appendChild(key);
+        else {
+            // Don't include a colon if no value.
+            keyElement.textContent = key + (value ? ": " : "");
+
+            let valueElement = p.appendChild(document.createElement("span"));
+            valueElement.className = "value";
+            if (value instanceof Node)
+                valueElement.appendChild(value);
+            else
+                valueElement.textContent = value;
+        }
+
+        return p;
+    }
 };
index 6b04f37..601f8c3 100644 (file)
@@ -31,38 +31,6 @@ body[dir] .resource-headers > section:matches(.redirect, .headers) > .details {
     border-color: var(--network-header-color);
 }
 
-body[dir] .resource-headers > section.error > .details {
-    border-color: var(--network-error-color);
-}
-
-.resource-headers > section.error .key {
-    color: var(--network-error-color);
-}
-
-.resource-headers .details {
-    white-space: normal;
-    word-break: break-all;
-}
-
-.resource-headers .details .pair {
-    --resource-headers-value-indent: 15px;
-    -webkit-margin-start: var(--resource-headers-value-indent);
-}
-
-body[dir=rtl] .resource-headers .details .pair {
-    /* Don't include indents in RTL */
-    --resource-headers-value-indent: 0px;
-}
-
-.resource-headers .details .key {
-    font-weight: 500;
-    -webkit-margin-start: calc(var(--resource-headers-value-indent) * -1);
-}
-
-.resource-headers .value {
-    color: var(--text-color);
-}
-
 .resource-headers .url + .url > .key {
     color: transparent;
 }
@@ -79,14 +47,3 @@ body[dir=rtl] .resource-headers .details .pair {
 .resource-headers .h2-pseudo-header > .key {
     color: var(--network-pseudo-header-color);
 }
-
-.resource-headers .go-to-arrow {
-    vertical-align: top;
-    bottom: 1px;
-}
-
-.resource-headers.showing-find-banner .search-highlight {
-    color: var(--text-color);
-    background-color: hsla(53, 83%, 53%, 0.2);
-    border-bottom: 1px solid hsl(47, 82%, 60%);
-}
index cc1be61..d1a7e09 100644 (file)
@@ -211,46 +211,6 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
 
     // Private
 
-    _markIncompleteSectionWithMessage(section, message)
-    {
-        section.toggleIncomplete(true);
-
-        let p = section.detailsElement.appendChild(document.createElement("p"));
-        p.textContent = message;
-    }
-
-    _markIncompleteSectionWithLoadingIndicator(section)
-    {
-        section.toggleIncomplete(true);
-
-        let p = section.detailsElement.appendChild(document.createElement("p"));
-        let spinner = new WI.IndeterminateProgressSpinner;
-        p.appendChild(spinner.element);
-    }
-
-    _appendKeyValuePair(parentElement, key, value, className)
-    {
-        let p = parentElement.appendChild(document.createElement("p"));
-        p.className = "pair";
-        if (className)
-            p.classList.add(className);
-
-        // Don't include a colon if no value.
-        console.assert(typeof key === "string");
-        let displayKey = key + (value ? ": " : "");
-
-        let keyElement = p.appendChild(document.createElement("span"));
-        keyElement.className = "key";
-        keyElement.textContent = displayKey;
-
-        let valueElement = p.appendChild(document.createElement("span"));
-        valueElement.className = "value";
-        if (value instanceof Node)
-            valueElement.appendChild(value);
-        else
-            valueElement.textContent = value;
-    }
-
     _responseSourceDisplayString(responseSource)
     {
         switch (responseSource) {
@@ -276,21 +236,21 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
         this._summarySection.toggleError(this._resource.hadLoadingError());
 
         for (let redirect of this._resource.redirects)
-            this._appendKeyValuePair(detailsElement, WI.UIString("URL"), redirect.url.insertWordBreakCharacters(), "url");
-        this._appendKeyValuePair(detailsElement, WI.UIString("URL"), this._resource.url.insertWordBreakCharacters(), "url");
+            this._summarySection.appendKeyValuePair(WI.UIString("URL"), redirect.url.insertWordBreakCharacters(), "url");
+        this._summarySection.appendKeyValuePair(WI.UIString("URL"), this._resource.url.insertWordBreakCharacters(), "url");
 
         let status = emDash;
         if (!isNaN(this._resource.statusCode))
             status = this._resource.statusCode + (this._resource.statusText ? " " + this._resource.statusText : "");
-        this._appendKeyValuePair(detailsElement, WI.UIString("Status"), status);
+        this._summarySection.appendKeyValuePair(WI.UIString("Status"), status);
 
         // FIXME: <https://webkit.org/b/178827> Web Inspector: Should be able to link directly to the ServiceWorker that handled a particular load
 
         let source = this._responseSourceDisplayString(this._resource.responseSource) || emDash;
-        this._appendKeyValuePair(detailsElement, WI.UIString("Source"), source);
+        this._summarySection.appendKeyValuePair(WI.UIString("Source"), source);
 
         if (this._resource.remoteAddress)
-            this._appendKeyValuePair(detailsElement, WI.UIString("Address"), this._resource.remoteAddress);
+            this._summarySection.appendKeyValuePair(WI.UIString("Address"), this._resource.remoteAddress);
     }
 
     _refreshRedirectHeadersSections()
@@ -303,10 +263,10 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
             let redirectRequestSection = new WI.ResourceDetailsSection(WI.UIString("Request"), "redirect");
 
             // FIXME: <https://webkit.org/b/190214> Web Inspector: expose full load metrics for redirect requests
-            this._appendKeyValuePair(redirectRequestSection.detailsElement, `${redirect.requestMethod} ${redirect.urlComponents.path}`, null, "h1-status");
+            redirectRequestSection.appendKeyValuePair(`${redirect.requestMethod} ${redirect.urlComponents.path}`, null, "h1-status");
 
             for (let key in redirect.requestHeaders)
-                this._appendKeyValuePair(redirectRequestSection.detailsElement, key, redirect.requestHeaders[key], "header");
+                redirectRequestSection.appendKeyValuePair(key, redirect.requestHeaders[key], "header");
 
             referenceElement = this.element.insertBefore(redirectRequestSection.element, referenceElement.nextElementSibling);
             this._redirectDetailsSections.push(redirectRequestSection);
@@ -314,10 +274,10 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
             let redirectResponseSection = new WI.ResourceDetailsSection(WI.UIString("Redirect Response"), "redirect");
 
             // FIXME: <https://webkit.org/b/190214> Web Inspector: expose full load metrics for redirect requests
-            this._appendKeyValuePair(redirectResponseSection.detailsElement, `${redirect.responseStatusCode} ${redirect.responseStatusText}`, null, "h1-status");
+            redirectResponseSection.appendKeyValuePair(`${redirect.responseStatusCode} ${redirect.responseStatusText}`, null, "h1-status");
 
             for (let key in redirect.responseHeaders)
-                this._appendKeyValuePair(redirectResponseSection.detailsElement, key, redirect.responseHeaders[key], "header");
+                redirectResponseSection.appendKeyValuePair(key, redirect.responseHeaders[key], "header");
 
             referenceElement = this.element.insertBefore(redirectResponseSection.element, referenceElement.nextElementSibling);
             this._redirectDetailsSections.push(redirectResponseSection);
@@ -332,11 +292,11 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
         // A revalidation request still sends a request even though we served from cache, so show the request.
         if (this._resource.statusCode !== 304) {
             if (this._resource.responseSource === WI.Resource.ResponseSource.MemoryCache) {
-                this._markIncompleteSectionWithMessage(this._requestHeadersSection, WI.UIString("No request, served from the memory cache."));
+                this._requestHeadersSection.markIncompleteSectionWithMessage(WI.UIString("No request, served from the memory cache."));
                 return;
             }
             if (this._resource.responseSource === WI.Resource.ResponseSource.DiskCache) {
-                this._markIncompleteSectionWithMessage(this._requestHeadersSection, WI.UIString("No request, served from the disk cache."));
+                this._requestHeadersSection.markIncompleteSectionWithMessage(WI.UIString("No request, served from the disk cache."));
                 return;
             }
         }
@@ -347,22 +307,22 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
             // HTTP/1.1 request line:
             // https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1
             let requestLine = `${this._resource.requestMethod} ${urlComponents.path} ${protocol.toUpperCase()}`;
-            this._appendKeyValuePair(detailsElement, requestLine, null, "h1-status");
+            this._requestHeadersSection.appendKeyValuePair(requestLine, null, "h1-status");
         } else if (protocol === "h2") {
             // HTTP/2 Request pseudo headers:
             // https://tools.ietf.org/html/rfc7540#section-8.1.2.3
-            this._appendKeyValuePair(detailsElement, ":method", this._resource.requestMethod, "h2-pseudo-header");
-            this._appendKeyValuePair(detailsElement, ":scheme", urlComponents.scheme, "h2-pseudo-header");
-            this._appendKeyValuePair(detailsElement, ":authority", WI.h2Authority(urlComponents), "h2-pseudo-header");
-            this._appendKeyValuePair(detailsElement, ":path", WI.h2Path(urlComponents), "h2-pseudo-header");
+            this._requestHeadersSection.appendKeyValuePair(":method", this._resource.requestMethod, "h2-pseudo-header");
+            this._requestHeadersSection.appendKeyValuePair(":scheme", urlComponents.scheme, "h2-pseudo-header");
+            this._requestHeadersSection.appendKeyValuePair(":authority", WI.h2Authority(urlComponents), "h2-pseudo-header");
+            this._requestHeadersSection.appendKeyValuePair(":path", WI.h2Path(urlComponents), "h2-pseudo-header");
         }
 
         let requestHeaders = this._resource.requestHeaders;
         for (let key in requestHeaders)
-            this._appendKeyValuePair(detailsElement, key, requestHeaders[key], "header");
+            this._requestHeadersSection.appendKeyValuePair(key, requestHeaders[key], "header");
 
         if (!detailsElement.firstChild)
-            this._markIncompleteSectionWithMessage(this._requestHeadersSection, WI.UIString("No request headers"));
+            this._requestHeadersSection.markIncompleteSectionWithMessage(WI.UIString("No request headers"));
     }
 
     _refreshResponseHeadersSection()
@@ -371,7 +331,7 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
         detailsElement.removeChildren();
 
         if (!this._resource.hasResponse()) {
-            this._markIncompleteSectionWithLoadingIndicator(this._responseHeadersSection);
+            this._responseHeadersSection.markIncompleteSectionWithLoadingIndicator();
             return;
         }
 
@@ -382,11 +342,11 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
             // HTTP/1.1 response status line:
             // https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1
             let responseLine = `${protocol.toUpperCase()} ${this._resource.statusCode} ${this._resource.statusText}`;
-            this._appendKeyValuePair(detailsElement, responseLine, null, "h1-status");
+            this._responseHeadersSection.appendKeyValuePair(responseLine, null, "h1-status");
         } else if (protocol === "h2") {
             // HTTP/2 Response pseudo headers:
             // https://tools.ietf.org/html/rfc7540#section-8.1.2.4
-            this._appendKeyValuePair(detailsElement, ":status", this._resource.statusCode, "h2-pseudo-header");
+            this._responseHeadersSection.appendKeyValuePair(":status", this._resource.statusCode, "h2-pseudo-header");
         }
 
         let responseHeaders = this._resource.responseHeaders;
@@ -396,15 +356,15 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
                 let responseCookies = this._resource.responseCookies;
                 console.assert(responseCookies.length > 0);
                 for (let cookie of responseCookies)
-                    this._appendKeyValuePair(detailsElement, key, cookie.rawHeader, "header");
+                    this._responseHeadersSection.appendKeyValuePair(key, cookie.rawHeader, "header");
                 continue;
             }
 
-            this._appendKeyValuePair(detailsElement, key, responseHeaders[key], "header");
+            this._responseHeadersSection.appendKeyValuePair(key, responseHeaders[key], "header");
         }
 
         if (!detailsElement.firstChild)
-            this._markIncompleteSectionWithMessage(this._responseHeadersSection, WI.UIString("No response headers"));
+            this._responseHeadersSection.markIncompleteSectionWithMessage(WI.UIString("No response headers"));
     }
 
     _refreshQueryStringSection()
@@ -418,7 +378,7 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
         let queryString = this._resource.urlComponents.queryString;
         let queryStringPairs = parseQueryString(queryString, true);
         for (let {name, value} of queryStringPairs)
-            this._appendKeyValuePair(detailsElement, name, value);
+            this._queryStringSection.appendKeyValuePair(name, value);
     }
 
     _refreshRequestDataSection()
@@ -434,10 +394,10 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
 
         if (requestDataContentType && requestDataContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) {
             // Simple form data that should be parsable like a query string.
-            this._appendKeyValuePair(detailsElement, WI.UIString("MIME Type"), requestDataContentType);
+            this._requestDataSection.appendKeyValuePair(WI.UIString("MIME Type"), requestDataContentType);
             let queryStringPairs = parseQueryString(requestData, true);
             for (let {name, value} of queryStringPairs)
-                this._appendKeyValuePair(detailsElement, name, value);
+                this._requestDataSection.appendKeyValuePair(name, value);
             return;
         }
 
@@ -446,15 +406,15 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
         let boundary = mimeTypeComponents.boundary;
         let encoding = mimeTypeComponents.encoding;
 
-        this._appendKeyValuePair(detailsElement, WI.UIString("MIME Type"), mimeType);
+        this._requestDataSection.appendKeyValuePair(WI.UIString("MIME Type"), mimeType);
         if (boundary)
-            this._appendKeyValuePair(detailsElement, WI.UIString("Boundary"), boundary);
+            this._requestDataSection.appendKeyValuePair(WI.UIString("Boundary"), boundary);
         if (encoding)
-            this._appendKeyValuePair(detailsElement, WI.UIString("Encoding"), encoding);
+            this._requestDataSection.appendKeyValuePair(WI.UIString("Encoding"), encoding);
 
         let goToButton = detailsElement.appendChild(WI.createGoToArrowButton());
         goToButton.addEventListener("click", () => { this._delegate.headersContentViewGoToRequestData(this); });
-        this._appendKeyValuePair(detailsElement, WI.UIString("Request Data"), goToButton);
+        this._requestDataSection.appendKeyValuePair(WI.UIString("Request Data"), goToButton);
     }
 
     _perfomSearchOnKeyValuePairs()
diff --git a/Source/WebInspectorUI/UserInterface/Views/ResourceSecurityContentView.css b/Source/WebInspectorUI/UserInterface/Views/ResourceSecurityContentView.css
new file mode 100644 (file)
index 0000000..a676016
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 Apple 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:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
+ */
+
+body[dir] .resource-security > section.certificate > .details {
+    border-color: var(--network-dns-color);
+}
+
+.resource-security .details .key {
+    color: var(--network-dns-color);
+}
+
+.resource-security .dns-name + .dns-name > .key,
+.resource-security .ip-address + .ip-address > .key {
+    color: transparent;
+}
+
+.resource-security .show-more {
+    text-decoration: underline;
+    cursor: pointer;
+    color: var(--text-color);
+}
+
+@media (prefers-dark-interface) {
+    body[dir] .resource-security > section.certificate > .details {
+        border-color: var(--network-pseudo-header-color);
+    }
+
+    .resource-security .details .key {
+        color: var(--network-pseudo-header-color);
+    }
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/ResourceSecurityContentView.js b/Source/WebInspectorUI/UserInterface/Views/ResourceSecurityContentView.js
new file mode 100644 (file)
index 0000000..24fc166
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2018 Apple 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:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
+ */
+
+WI.ResourceSecurityContentView = class ResourceSecurityContentView extends WI.ContentView
+{
+    constructor(resource)
+    {
+        console.assert(resource instanceof WI.Resource);
+
+        super();
+
+        this._resource = resource;
+
+        this._insecureMessageElement = null;
+        this._needsCertificateRefresh = true;
+
+        this._searchQuery = null;
+        this._searchResults = null;
+        this._searchDOMChanges = [];
+        this._searchIndex = -1;
+        this._automaticallyRevealFirstSearchResult = false;
+        this._bouncyHighlightElement = null;
+
+        this.element.classList.add("resource-details", "resource-security");
+    }
+
+    // Protected
+
+    initialLayout()
+    {
+        super.initialLayout();
+
+        this._certificateSection = new WI.ResourceDetailsSection(WI.UIString("Certificate"), "certificate");
+        this.element.appendChild(this._certificateSection.element);
+
+        this._resource.addEventListener(WI.Resource.Event.ResponseReceived, this._handleResourceResponseReceived, this);
+    }
+
+    layout()
+    {
+        super.layout();
+
+        if (!this._resource.loadedSecurely) {
+            if (!this._insecureMessageElement)
+                this._insecureMessageElement = WI.createMessageTextView(WI.UIString("The resource was requested insecurely."), true);
+            this.element.appendChild(this._insecureMessageElement);
+            return;
+        }
+
+        if (this._needsCertificateRefresh) {
+            this._needsCertificateRefresh = false;
+            this._refreshCetificateSection();
+        }
+    }
+
+    closed()
+    {
+        this._resource.removeEventListener(null, null, this);
+
+        super.closed();
+    }
+
+    get supportsSearch()
+    {
+        return true;
+    }
+
+    get numberOfSearchResults()
+    {
+        return this._searchResults ? this._searchResults.length : null;
+    }
+
+    get hasPerformedSearch()
+    {
+        return this._searchResults !== null;
+    }
+
+    set automaticallyRevealFirstSearchResult(reveal)
+    {
+        this._automaticallyRevealFirstSearchResult = reveal;
+
+        // If we haven't shown a search result yet, reveal one now.
+        if (this._automaticallyRevealFirstSearchResult && this.numberOfSearchResults > 0) {
+            if (this._searchIndex === -1)
+                this.revealNextSearchResult();
+        }
+    }
+
+    performSearch(query)
+    {
+        if (query === this._searchQuery)
+            return;
+
+        WI.revertDOMChanges(this._searchDOMChanges);
+
+        this._searchQuery = query;
+        this._searchResults = [];
+        this._searchDOMChanges = [];
+        this._searchIndex = -1;
+
+        this._perfomSearchOnKeyValuePairs();
+
+        this.dispatchEventToListeners(WI.ContentView.Event.NumberOfSearchResultsDidChange);
+
+        if (this._automaticallyRevealFirstSearchResult && this._searchResults.length > 0)
+            this.revealNextSearchResult();
+    }
+
+    searchCleared()
+    {
+        WI.revertDOMChanges(this._searchDOMChanges);
+
+        this._searchQuery = null;
+        this._searchResults = null;
+        this._searchDOMChanges = [];
+        this._searchIndex = -1;
+    }
+
+    revealPreviousSearchResult(changeFocus)
+    {
+        if (!this.numberOfSearchResults)
+            return;
+
+        if (this._searchIndex > 0)
+            --this._searchIndex;
+        else
+            this._searchIndex = this._searchResults.length - 1;
+
+        this._revealSearchResult(this._searchIndex, changeFocus);
+    }
+
+    revealNextSearchResult(changeFocus)
+    {
+        if (!this.numberOfSearchResults)
+            return;
+
+        if (this._searchIndex < this._searchResults.length - 1)
+            ++this._searchIndex;
+        else
+            this._searchIndex = 0;
+
+        this._revealSearchResult(this._searchIndex, changeFocus);
+    }
+
+    // Private
+
+    _refreshCetificateSection()
+    {
+        let detailsElement = this._certificateSection.detailsElement;
+        detailsElement.removeChildren();
+
+        let responseSecurity = this._resource.responseSecurity;
+        if (!responseSecurity) {
+            this._certificateSection.markIncompleteSectionWithMessage(WI.UIString("No response security information."));
+            return;
+        }
+
+        let certificate = responseSecurity.certificate;
+        if (!certificate) {
+            this._certificateSection.markIncompleteSectionWithMessage(WI.UIString("No response security certificate."));
+            return;
+        }
+
+        this._certificateSection.appendKeyValuePair(WI.UIString("Subject"), certificate.subject);
+
+        let appendFormattedDate = (key, timestamp) => {
+            if (isNaN(timestamp))
+                return;
+
+            let date = new Date(timestamp * 1000);
+
+            let timeElement = document.createElement("time");
+            timeElement.datetime = date.toISOString();
+            timeElement.textContent = date.toLocaleString();
+            this._certificateSection.appendKeyValuePair(key, timeElement);
+
+        };
+        appendFormattedDate(WI.UIString("Valid From"), certificate.validFrom);
+        appendFormattedDate(WI.UIString("Valid Until"), certificate.validUntil);
+
+        let appendList = (key, values, className) => {
+            if (!Array.isArray(values))
+                return;
+
+            const initialCount = 5;
+            for (let i = 0; i < Math.min(values.length, initialCount); ++i)
+                this._certificateSection.appendKeyValuePair(key, values[i], className);
+
+            let remaining = values.length - initialCount;
+            if (remaining <= 0)
+                return;
+
+            let showMoreElement = document.createElement("a");
+            showMoreElement.classList.add("show-more");
+            showMoreElement.textContent = WI.UIString("Show %d More").format(remaining);
+
+            let showMorePair = this._certificateSection.appendKeyValuePair(key, showMoreElement, className);
+
+            showMoreElement.addEventListener("click", (event) => {
+                showMorePair.remove();
+
+                for (let i = initialCount; i < values.length; ++i)
+                    this._certificateSection.appendKeyValuePair(key, values[i], className);
+            }, {once: true});
+        };
+        appendList(WI.UIString("DNS"), certificate.dnsNames, "dns-name");
+        appendList(WI.UIString("IP"), certificate.ipAddresses, "ip-address");
+    }
+
+    _perfomSearchOnKeyValuePairs()
+    {
+        let searchRegex = new RegExp(this._searchQuery.escapeForRegExp(), "gi");
+
+        let elements = this.element.querySelectorAll(".key, .value");
+        for (let element of elements) {
+            let matchRanges = [];
+            let text = element.textContent;
+            let match;
+            while (match = searchRegex.exec(text))
+                matchRanges.push({offset: match.index, length: match[0].length});
+
+            if (matchRanges.length) {
+                let highlightedNodes = WI.highlightRangesWithStyleClass(element, matchRanges, "search-highlight", this._searchDOMChanges);
+                this._searchResults = this._searchResults.concat(highlightedNodes);
+            }
+        }
+    }
+
+    _revealSearchResult(index, changeFocus)
+    {
+        let highlightElement = this._searchResults[index];
+        if (!highlightElement)
+            return;
+
+        highlightElement.scrollIntoViewIfNeeded();
+
+        if (!this._bouncyHighlightElement) {
+            this._bouncyHighlightElement = document.createElement("div");
+            this._bouncyHighlightElement.className = "bouncy-highlight";
+            this._bouncyHighlightElement.addEventListener("animationend", (event) => {
+                this._bouncyHighlightElement.remove();
+            });
+        }
+
+        this._bouncyHighlightElement.remove();
+
+        let computedStyles = window.getComputedStyle(highlightElement);
+        let highlightElementRect = highlightElement.getBoundingClientRect();
+        let contentViewRect = this.element.getBoundingClientRect();
+        let contentViewScrollTop = this.element.scrollTop;
+        let contentViewScrollLeft = this.element.scrollLeft;
+
+        this._bouncyHighlightElement.textContent = highlightElement.textContent;
+        this._bouncyHighlightElement.style.top = (highlightElementRect.top - contentViewRect.top + contentViewScrollTop) + "px";
+        this._bouncyHighlightElement.style.left = (highlightElementRect.left - contentViewRect.left + contentViewScrollLeft) + "px";
+        this._bouncyHighlightElement.style.fontWeight = computedStyles.fontWeight;
+
+        this.element.appendChild(this._bouncyHighlightElement);
+    }
+
+    _handleResourceResponseReceived(event)
+    {
+        this._needsCertificateRefresh = true;
+        this.needsLayout();
+    }
+};