[Content Filtering] Crash when allowing a 0-byte resource to load
[WebKit-https.git] / Source / WebCore / loader / ContentFilter.cpp
index 27ec0da7b448de276c8c6c45e961d7c12912df2b..2f600cb816b4f46d662efa745bc37c83f9a1750b 100644 (file)
 
 #if ENABLE(CONTENT_FILTERING)
 
+#include "CachedRawResource.h"
+#include "ContentFilterUnblockHandler.h"
 #include "DocumentLoader.h"
-#include "Frame.h"
+#include "Logging.h"
 #include "NetworkExtensionContentFilter.h"
 #include "ParentalControlsContentFilter.h"
-#include "ScriptController.h"
-#include <bindings/ScriptValue.h>
+#include "SharedBuffer.h"
 #include <wtf/NeverDestroyed.h>
 #include <wtf/Vector.h>
 
+#if !LOG_DISABLED
+#include <wtf/text/CString.h>
+#endif
+
 namespace WebCore {
 
 Vector<ContentFilter::Type>& ContentFilter::types()
 {
     static NeverDestroyed<Vector<ContentFilter::Type>> types {
         Vector<ContentFilter::Type> {
+#if HAVE(PARENTAL_CONTROLS)
             type<ParentalControlsContentFilter>(),
+#endif
 #if HAVE(NETWORK_EXTENSION)
             type<NetworkExtensionContentFilter>()
 #endif
@@ -52,12 +59,16 @@ Vector<ContentFilter::Type>& ContentFilter::types()
     return types;
 }
 
-std::unique_ptr<ContentFilter> ContentFilter::createIfNeeded(const ResourceResponse& response, DocumentLoader& documentLoader)
+std::unique_ptr<ContentFilter> ContentFilter::createIfEnabled(DocumentLoader& documentLoader)
 {
     Container filters;
     for (auto& type : types()) {
-        if (type.canHandleResponse(response))
-            filters.append(type.create(response));
+        if (!type.enabled())
+            continue;
+
+        auto filter = type.create();
+        ASSERT(filter);
+        filters.append(WTF::move(filter));
     }
 
     if (filters.isEmpty())
@@ -70,89 +81,186 @@ ContentFilter::ContentFilter(Container contentFilters, DocumentLoader& documentL
     : m_contentFilters { WTF::move(contentFilters) }
     , m_documentLoader { documentLoader }
 {
+    LOG(ContentFiltering, "Creating ContentFilter with %zu platform content filter(s).\n", m_contentFilters.size());
     ASSERT(!m_contentFilters.isEmpty());
 }
 
-void ContentFilter::addData(const char* data, int length)
+ContentFilter::~ContentFilter()
 {
-    ASSERT(needsMoreData());
+    LOG(ContentFiltering, "Destroying ContentFilter.\n");
+    if (!m_mainResource)
+        return;
+    ASSERT(m_mainResource->hasClient(this));
+    m_mainResource->removeClient(this);
+}
 
-    for (auto& contentFilter : m_contentFilters)
-        contentFilter->addData(data, length);
+void ContentFilter::willSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse)
+{
+    LOG(ContentFiltering, "ContentFilter received request for <%s> with redirect response from <%s>.\n", request.url().string().ascii().data(), redirectResponse.url().string().ascii().data());
+    ResourceRequest requestCopy { request };
+    ASSERT(m_state == State::Initialized || m_state == State::Filtering);
+    forEachContentFilterUntilBlocked([&requestCopy, &redirectResponse](PlatformContentFilter& contentFilter) {
+        contentFilter.willSendRequest(requestCopy, redirectResponse);
+        if (contentFilter.didBlockData())
+            requestCopy = ResourceRequest();
+    });
+#if !LOG_DISABLED
+    if (request != requestCopy)
+        LOG(ContentFiltering, "ContentFilter changed request url to <%s>.\n", requestCopy.url().string().ascii().data());
+#endif
+    request = requestCopy;
 }
-    
-void ContentFilter::finishedAddingData()
+
+void ContentFilter::startFilteringMainResource(CachedRawResource& resource)
 {
-    ASSERT(needsMoreData());
+    LOG(ContentFiltering, "ContentFilter will start filtering main resource at <%s>.\n", resource.url().string().ascii().data());
+    ASSERT(m_state == State::Initialized);
+    m_state = State::Filtering;
+    ASSERT(!m_mainResource);
+    m_mainResource = &resource;
+    ASSERT(!m_mainResource->hasClient(this));
+    m_mainResource->addClient(this);
+}
 
-    for (auto& contentFilter : m_contentFilters)
-        contentFilter->finishedAddingData();
+ContentFilterUnblockHandler ContentFilter::unblockHandler() const
+{
+    ASSERT(m_state == State::Blocked);
+    ASSERT(m_blockingContentFilter);
+    ASSERT(m_blockingContentFilter->didBlockData());
+    return m_blockingContentFilter->unblockHandler();
+}
 
-    ASSERT(!needsMoreData());
+Ref<SharedBuffer> ContentFilter::replacementData() const
+{
+    ASSERT(m_state == State::Blocked);
+    ASSERT(m_blockingContentFilter);
+    ASSERT(m_blockingContentFilter->didBlockData());
+    return m_blockingContentFilter->replacementData();
 }
 
-bool ContentFilter::needsMoreData() const
+String ContentFilter::unblockRequestDeniedScript() const
 {
-    for (auto& contentFilter : m_contentFilters) {
-        if (contentFilter->needsMoreData())
-            return true;
+    ASSERT(m_state == State::Blocked);
+    ASSERT(m_blockingContentFilter);
+    ASSERT(m_blockingContentFilter->didBlockData());
+    return m_blockingContentFilter->unblockRequestDeniedScript();
+}
+
+void ContentFilter::responseReceived(CachedResource* resource, const ResourceResponse& response)
+{
+    ASSERT(resource);
+    ASSERT(resource == m_mainResource);
+    ASSERT(m_state != State::Initialized);
+    LOG(ContentFiltering, "ContentFilter received response from <%s>.\n", response.url().string().ascii().data());
+
+    if (m_state == State::Filtering) {
+        forEachContentFilterUntilBlocked([&response](PlatformContentFilter& contentFilter) {
+            contentFilter.responseReceived(response);
+        });
     }
 
-    return false;
+    if (m_state != State::Blocked)
+        m_documentLoader.responseReceived(resource, response);
 }
 
-bool ContentFilter::didBlockData() const
+void ContentFilter::dataReceived(CachedResource* resource, const char* data, int length)
 {
-    for (auto& contentFilter : m_contentFilters) {
-        if (contentFilter->didBlockData())
-            return true;
+    ASSERT(resource);
+    ASSERT(resource == m_mainResource);
+    ASSERT(m_state != State::Initialized);
+    LOG(ContentFiltering, "ContentFilter received %d bytes of data from <%s>.\n", length, resource->url().string().ascii().data());
+
+    if (m_state == State::Filtering) {
+        forEachContentFilterUntilBlocked([data, length](PlatformContentFilter& contentFilter) {
+            contentFilter.addData(data, length);
+        });
+
+        if (m_state == State::Allowed)
+            deliverResourceData(*resource);
+        return;
     }
 
-    return false;
+    if (m_state == State::Allowed)
+        m_documentLoader.dataReceived(resource, data, length);
 }
 
-const char* ContentFilter::getReplacementData(int& length) const
+void ContentFilter::redirectReceived(CachedResource* resource, ResourceRequest& request, const ResourceResponse& redirectResponse)
 {
-    ASSERT(!needsMoreData());
+    ASSERT(resource);
+    ASSERT(resource == m_mainResource);
+    ASSERT(m_state != State::Initialized);
 
-    for (auto& contentFilter : m_contentFilters) {
-        if (contentFilter->didBlockData())
-            return contentFilter->getReplacementData(length);
-    }
+    if (m_state == State::Filtering)
+        willSendRequest(request, redirectResponse);
 
-    return m_contentFilters[0]->getReplacementData(length);
+    if (m_state != State::Blocked)
+        m_documentLoader.redirectReceived(resource, request, redirectResponse);
 }
 
-ContentFilterUnblockHandler ContentFilter::unblockHandler() const
+void ContentFilter::notifyFinished(CachedResource* resource)
 {
-    ASSERT(didBlockData());
+    ASSERT(resource);
+    ASSERT(resource == m_mainResource);
+    ASSERT(m_state != State::Initialized);
+    LOG(ContentFiltering, "ContentFilter will finish filtering main resource at <%s>.\n", resource->url().string().ascii().data());
+
+    if (resource->errorOccurred()) {
+        m_documentLoader.notifyFinished(resource);
+        return;
+    }
+
+    if (m_state == State::Filtering) {
+        forEachContentFilterUntilBlocked([](PlatformContentFilter& contentFilter) {
+            contentFilter.finishedAddingData();
+        });
+
+        if (m_state != State::Blocked)
+            deliverResourceData(*resource);
+    }
 
-    PlatformContentFilter* blockingFilter = nullptr;
+    if (m_state != State::Blocked)
+        m_documentLoader.notifyFinished(resource);
+}
+
+void ContentFilter::forEachContentFilterUntilBlocked(std::function<void(PlatformContentFilter&)> function)
+{
+    bool allFiltersAllowedLoad { true };
     for (auto& contentFilter : m_contentFilters) {
-        if (contentFilter->didBlockData()) {
-            blockingFilter = contentFilter.get();
-            break;
+        if (!contentFilter->needsMoreData()) {
+            ASSERT(!contentFilter->didBlockData());
+            continue;
         }
+
+        function(*contentFilter);
+
+        if (contentFilter->didBlockData()) {
+            ASSERT(!m_blockingContentFilter);
+            m_blockingContentFilter = contentFilter.get();
+            didDecide(State::Blocked);
+            return;
+        } else if (contentFilter->needsMoreData())
+            allFiltersAllowedLoad = false;
     }
-    ASSERT(blockingFilter);
-
-    StringCapture unblockRequestDeniedScript { blockingFilter->unblockRequestDeniedScript() };
-    if (unblockRequestDeniedScript.string().isEmpty())
-        return blockingFilter->unblockHandler();
-
-    // It would be a layering violation for the unblock handler to access its frame,
-    // so we will execute the unblock denied script on its behalf.
-    ContentFilterUnblockHandler unblockHandler { blockingFilter->unblockHandler() };
-    RefPtr<Frame> frame { m_documentLoader.frame() };
-    return ContentFilterUnblockHandler {
-        unblockHandler.unblockURLHost(), [unblockHandler, frame, unblockRequestDeniedScript](ContentFilterUnblockHandler::DecisionHandlerFunction decisionHandler) {
-            unblockHandler.requestUnblockAsync([decisionHandler, frame, unblockRequestDeniedScript](bool unblocked) {
-                decisionHandler(unblocked);
-                if (!unblocked && frame)
-                    frame->script().executeScript(unblockRequestDeniedScript.string());
-            });
-        }
-    };
+
+    if (allFiltersAllowedLoad)
+        didDecide(State::Allowed);
+}
+
+void ContentFilter::didDecide(State state)
+{
+    ASSERT(m_state != State::Allowed);
+    ASSERT(m_state != State::Blocked);
+    ASSERT(state == State::Allowed || state == State::Blocked);
+    LOG(ContentFiltering, "ContentFilter decided load should be %s for main resource at <%s>.\n", state == State::Allowed ? "allowed" : "blocked", m_mainResource ? m_mainResource->url().string().ascii().data() : "");
+    m_state = state;
+    m_documentLoader.contentFilterDidDecide();
+}
+
+void ContentFilter::deliverResourceData(CachedResource& resource)
+{
+    ASSERT(resource.dataBufferingPolicy() == BufferData);
+    if (auto* resourceBuffer = resource.resourceBuffer())
+        m_documentLoader.dataReceived(&resource, resourceBuffer->data(), resourceBuffer->size());
 }
 
 } // namespace WebCore