Allow some schemes to opt-out of CORS
https://bugs.webkit.org/show_bug.cgi?id=167795

Patch by Youenn Fablet <youennf@gmail.com> on 2017-02-06
Reviewed by Alex Christensen.

Source/WebCore:

Test: http/tests/security/bypassing-cors-checks-for-extension-urls.html

Adding the possibility to opt out of CORS for DocumentThreadableLoader clients (fetch and XHR).
This is made specific to the case of user extension URLs for pages running user scripts.
Introducing a boolean flag in Page for that purpose.
Introducing a helper routine in SchemeRegistry to centralize the various user script extension schemes.

* loader/DocumentThreadableLoader.cpp:
(WebCore::DocumentThreadableLoader::DocumentThreadableLoader):
* page/Frame.cpp:
(WebCore::Frame::injectUserScripts):
* page/Page.h:
(WebCore::Page::setAsRunningUserScripts):
(WebCore::Page::isRunningUserScripts):
* platform/SchemeRegistry.cpp:
(WebCore::SchemeRegistry::isUserExtensionScheme):
* platform/SchemeRegistry.h:
* testing/Internals.cpp:
(WebCore::Internals::setAsRunningUserScripts):
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

* http/tests/security/bypassing-cors-checks-for-extension-urls-expected.txt: Added.
* http/tests/security/bypassing-cors-checks-for-extension-urls.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@211758 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 722cc50..47ebd37 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,32 @@
+2017-02-06  Youenn Fablet  <youennf@gmail.com>
+
+        Allow some schemes to opt-out of CORS
+        https://bugs.webkit.org/show_bug.cgi?id=167795
+
+        Reviewed by Alex Christensen.
+
+        Test: http/tests/security/bypassing-cors-checks-for-extension-urls.html
+
+        Adding the possibility to opt out of CORS for DocumentThreadableLoader clients (fetch and XHR).
+        This is made specific to the case of user extension URLs for pages running user scripts.
+        Introducing a boolean flag in Page for that purpose.
+        Introducing a helper routine in SchemeRegistry to centralize the various user script extension schemes.
+
+        * loader/DocumentThreadableLoader.cpp:
+        (WebCore::DocumentThreadableLoader::DocumentThreadableLoader):
+        * page/Frame.cpp:
+        (WebCore::Frame::injectUserScripts):
+        * page/Page.h:
+        (WebCore::Page::setAsRunningUserScripts):
+        (WebCore::Page::isRunningUserScripts):
+        * platform/SchemeRegistry.cpp:
+        (WebCore::SchemeRegistry::isUserExtensionScheme):
+        * platform/SchemeRegistry.h:
+        * testing/Internals.cpp:
+        (WebCore::Internals::setAsRunningUserScripts):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2017-02-06  Chris Dumez  <cdumez@apple.com>
 
         Align [[OwnPropertyKeys]] with the HTML specification for cross-origin Window / Location objects
diff --git a/Source/WebCore/loader/DocumentThreadableLoader.cpp b/Source/WebCore/loader/DocumentThreadableLoader.cpp
index 0c970a1..508fb73 100644
--- a/Source/WebCore/loader/DocumentThreadableLoader.cpp
+++ b/Source/WebCore/loader/DocumentThreadableLoader.cpp
@@ -110,6 +110,11 @@
     if (m_async && m_options.mode == FetchOptions::Mode::Cors)
         m_originalHeaders = request.httpHeaderFields();
 
+    if (document.page() && document.page()->isRunningUserScripts() && SchemeRegistry::isUserExtensionScheme(request.url().protocol().toStringWithoutCopying())) {
+        m_options.mode = FetchOptions::Mode::NoCors;
+        m_options.filteringPolicy = ResponseFilteringPolicy::Disable;
+    }
+
     // As per step 11 of https://fetch.spec.whatwg.org/#main-fetch, data scheme (if same-origin data-URL flag is set) and about scheme are considered same-origin.
     if (request.url().protocolIsData())
         m_sameOriginRequest = options.sameOriginDataURLFlag == SameOriginDataURLFlag::Set;
diff --git a/Source/WebCore/page/Frame.cpp b/Source/WebCore/page/Frame.cpp
index a5ae52b..2dc23a1 100644
--- a/Source/WebCore/page/Frame.cpp
+++ b/Source/WebCore/page/Frame.cpp
@@ -710,8 +710,10 @@
         if (script.injectedFrames() == InjectInTopFrameOnly && ownerElement())
             return;
 
-        if (script.injectionTime() == injectionTime && UserContentURLPattern::matchesPatterns(document->url(), script.whitelist(), script.blacklist()))
+        if (script.injectionTime() == injectionTime && UserContentURLPattern::matchesPatterns(document->url(), script.whitelist(), script.blacklist())) {
+            m_page->setAsRunningUserScripts();
             m_script->evaluateInWorld(ScriptSourceCode(script.source(), script.url()), world);
+        }
     });
 }
 
diff --git a/Source/WebCore/page/Page.h b/Source/WebCore/page/Page.h
index c9ca095..958c2dd 100644
--- a/Source/WebCore/page/Page.h
+++ b/Source/WebCore/page/Page.h
@@ -391,6 +391,9 @@
     void setResourceUsageOverlayVisible(bool);
 #endif
 
+    void setAsRunningUserScripts() { m_isRunningUserScripts = true; }
+    bool isRunningUserScripts() const { return m_isRunningUserScripts; }
+
     void setDebugger(JSC::Debugger*);
     JSC::Debugger* debugger() const { return m_debugger; }
 
@@ -775,6 +778,8 @@
     std::optional<EventThrottlingBehavior> m_eventThrottlingBehaviorOverride;
 
     std::unique_ptr<PerformanceMonitor> m_performanceMonitor;
+
+    bool m_isRunningUserScripts { false };
 };
 
 inline PageGroup& Page::group()
diff --git a/Source/WebCore/platform/SchemeRegistry.cpp b/Source/WebCore/platform/SchemeRegistry.cpp
index 00c8898..ea25db8 100644
--- a/Source/WebCore/platform/SchemeRegistry.cpp
+++ b/Source/WebCore/platform/SchemeRegistry.cpp
@@ -357,4 +357,14 @@
 }
 #endif
 
+bool SchemeRegistry::isUserExtensionScheme(const String& scheme)
+{
+    UNUSED_PARAM(scheme);
+#if PLATFORM(MAC)
+    if (scheme == "safari-extension")
+        return true;
+#endif
+    return false;
+}
+
 } // namespace WebCore
diff --git a/Source/WebCore/platform/SchemeRegistry.h b/Source/WebCore/platform/SchemeRegistry.h
index ec041b1..578a49d 100644
--- a/Source/WebCore/platform/SchemeRegistry.h
+++ b/Source/WebCore/platform/SchemeRegistry.h
@@ -99,6 +99,8 @@
     WEBCORE_EXPORT static void registerURLSchemeAsCachePartitioned(const String& scheme);
     static bool shouldPartitionCacheForURLScheme(const String& scheme);
 #endif
+
+    static bool isUserExtensionScheme(const String& scheme);
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp
index affacac..9351e01 100644
--- a/Source/WebCore/testing/Internals.cpp
+++ b/Source/WebCore/testing/Internals.cpp
@@ -3690,4 +3690,10 @@
 }
 #endif
 
+void Internals::setAsRunningUserScripts(Document& document)
+{
+    if (document.page())
+        document.page()->setAsRunningUserScripts();
+}
+
 } // namespace WebCore
diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h
index 24659f3..9e5c84d 100644
--- a/Source/WebCore/testing/Internals.h
+++ b/Source/WebCore/testing/Internals.h
@@ -526,6 +526,8 @@
     void setQuickLookPassword(const String&);
 #endif
 
+    void setAsRunningUserScripts(Document&);
+
 private:
     explicit Internals(Document&);
     Document* contextDocument() const;
diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl
index 21a8153..8f10cb8 100644
--- a/Source/WebCore/testing/Internals.idl
+++ b/Source/WebCore/testing/Internals.idl
@@ -499,4 +499,6 @@
 #if defined(WTF_PLATFORM_IOS) && WTF_PLATFORM_IOS
     void setQuickLookPassword(DOMString password);
 #endif
+
+    [CallWith=Document] void setAsRunningUserScripts();
 };