ServiceWorkerClientFetch.cpp [plain text]
#include "config.h"
#include "ServiceWorkerClientFetch.h"
#if ENABLE(SERVICE_WORKER)
#include "DataReference.h"
#include "WebSWClientConnection.h"
#include "WebServiceWorkerProvider.h"
#include <WebCore/CrossOriginAccessControl.h>
#include <WebCore/Document.h>
#include <WebCore/Frame.h>
#include <WebCore/MIMETypeRegistry.h>
#include <WebCore/NotImplemented.h>
#include <WebCore/ResourceError.h>
using namespace WebCore;
namespace WebKit {
Ref<ServiceWorkerClientFetch> ServiceWorkerClientFetch::create(WebServiceWorkerProvider& serviceWorkerProvider, Ref<WebCore::ResourceLoader>&& loader, FetchIdentifier identifier, Ref<WebSWClientConnection>&& connection, bool shouldClearReferrerOnHTTPSToHTTPRedirect, Callback&& callback)
{
auto fetch = adoptRef(*new ServiceWorkerClientFetch { serviceWorkerProvider, WTFMove(loader), identifier, WTFMove(connection), shouldClearReferrerOnHTTPSToHTTPRedirect, WTFMove(callback) });
fetch->start();
return fetch;
}
ServiceWorkerClientFetch::~ServiceWorkerClientFetch()
{
}
ServiceWorkerClientFetch::ServiceWorkerClientFetch(WebServiceWorkerProvider& serviceWorkerProvider, Ref<WebCore::ResourceLoader>&& loader, FetchIdentifier identifier, Ref<WebSWClientConnection>&& connection, bool shouldClearReferrerOnHTTPSToHTTPRedirect, Callback&& callback)
: m_serviceWorkerProvider(serviceWorkerProvider)
, m_loader(WTFMove(loader))
, m_identifier(identifier)
, m_connection(WTFMove(connection))
, m_callback(WTFMove(callback))
, m_shouldClearReferrerOnHTTPSToHTTPRedirect(shouldClearReferrerOnHTTPSToHTTPRedirect)
{
}
void ServiceWorkerClientFetch::start()
{
auto request = m_loader->request();
auto& options = m_loader->options();
auto referrer = request.httpReferrer();
m_didFail = false;
m_didFinish = false;
cleanHTTPRequestHeadersForAccessControl(request, options.httpHeadersToKeep);
ASSERT(options.serviceWorkersMode != ServiceWorkersMode::None);
m_serviceWorkerRegistrationIdentifier = options.serviceWorkerRegistrationIdentifier.value();
m_connection->startFetch(m_identifier, m_serviceWorkerRegistrationIdentifier, request, options, referrer);
m_redirectionStatus = RedirectionStatus::None;
}
std::optional<ResourceError> ServiceWorkerClientFetch::validateResponse(const ResourceResponse& response)
{
if (response.type() == ResourceResponse::Type::Error)
return ResourceError { ResourceError::Type::General };
auto& options = m_loader->options();
if (options.mode != FetchOptions::Mode::NoCors && response.tainting() == ResourceResponse::Tainting::Opaque)
return ResourceError { errorDomainWebKitInternal, 0, response.url(), "Response served by service worker is opaque"_s, ResourceError::Type::AccessControl };
if (options.redirect != FetchOptions::Redirect::Manual && options.mode != FetchOptions::Mode::Navigate && response.tainting() == ResourceResponse::Tainting::Opaqueredirect)
return ResourceError { errorDomainWebKitInternal, 0, response.url(), "Response served by service worker is opaque redirect"_s, ResourceError::Type::AccessControl };
if ((options.redirect != FetchOptions::Redirect::Follow || options.mode == FetchOptions::Mode::Navigate) && response.isRedirected())
return ResourceError { errorDomainWebKitInternal, 0, response.url(), "Response served by service worker has redirections"_s, ResourceError::Type::AccessControl };
return std::nullopt;
}
void ServiceWorkerClientFetch::didReceiveResponse(ResourceResponse&& response)
{
m_isCheckingResponse = true;
callOnMainThread([this, protectedThis = makeRef(*this), response = WTFMove(response)]() mutable {
if (!m_loader) {
m_isCheckingResponse = false;
return;
}
if (auto error = validateResponse(response)) {
m_isCheckingResponse = false;
m_loader->didFail(error.value());
ASSERT(!m_loader);
if (auto callback = WTFMove(m_callback))
callback(Result::Succeeded);
return;
}
response.setSource(ResourceResponse::Source::ServiceWorker);
if (response.isRedirection() && response.httpHeaderFields().contains(HTTPHeaderName::Location)) {
m_isCheckingResponse = false;
continueLoadingAfterCheckingResponse();
m_redirectionStatus = RedirectionStatus::Receiving;
m_loader->willSendRequest(m_loader->request().redirectedRequest(response, m_shouldClearReferrerOnHTTPSToHTTPRedirect), response, [protectedThis = makeRef(*this), this](ResourceRequest&& request) {
if (request.isNull() || !m_callback)
return;
ASSERT(request == m_loader->request());
if (m_redirectionStatus == RedirectionStatus::Received) {
start();
return;
}
m_redirectionStatus = RedirectionStatus::Following;
});
return;
}
if (m_loader->originalRequest().requester() == ResourceRequest::Requester::Main) {
if (response.mimeType() == defaultMIMEType()) {
response.setMimeType("text/html"_s);
response.setTextEncodingName("UTF-8"_s);
}
}
if (response.url().isNull())
response.setURL(m_loader->request().url());
m_loader->didReceiveResponse(response, [this, protectedThis = WTFMove(protectedThis)] {
m_isCheckingResponse = false;
continueLoadingAfterCheckingResponse();
if (auto callback = WTFMove(m_callback))
callback(Result::Succeeded);
});
});
}
void ServiceWorkerClientFetch::didReceiveData(const IPC::DataReference& dataReference, int64_t encodedDataLength)
{
auto* data = reinterpret_cast<const char*>(dataReference.data());
if (!m_buffer) {
m_buffer = SharedBuffer::create(data, dataReference.size());
m_encodedDataLength = encodedDataLength;
} else {
m_buffer->append(data, dataReference.size());
m_encodedDataLength += encodedDataLength;
}
if (m_isCheckingResponse)
return;
callOnMainThread([this, protectedThis = makeRef(*this)] {
if (!m_loader || !m_encodedDataLength)
return;
m_loader->didReceiveBuffer(m_buffer.releaseNonNull(), m_encodedDataLength, DataPayloadBytes);
m_encodedDataLength = 0;
});
}
void ServiceWorkerClientFetch::didReceiveFormData(const IPC::FormDataReference&)
{
}
void ServiceWorkerClientFetch::didFinish()
{
m_didFinish = true;
if (m_isCheckingResponse)
return;
callOnMainThread([this, protectedThis = makeRef(*this)] {
if (!m_loader)
return;
switch (m_redirectionStatus) {
case RedirectionStatus::None:
break;
case RedirectionStatus::Receiving:
m_redirectionStatus = RedirectionStatus::Received;
return;
case RedirectionStatus::Following:
start();
return;
case RedirectionStatus::Received:
ASSERT_NOT_REACHED();
m_redirectionStatus = RedirectionStatus::None;
}
ASSERT(!m_callback);
m_loader->didFinishLoading(NetworkLoadMetrics { });
m_serviceWorkerProvider.fetchFinished(m_identifier);
});
}
void ServiceWorkerClientFetch::didFail(ResourceError&& error)
{
m_didFail = true;
m_error = WTFMove(error);
if (m_isCheckingResponse)
return;
callOnMainThread([this, protectedThis = makeRef(*this)] {
if (!m_loader)
return;
auto* document = m_loader->frame() ? m_loader->frame()->document() : nullptr;
if (document) {
document->addConsoleMessage(MessageSource::JS, MessageLevel::Error, m_error.localizedDescription());
if (m_loader->options().destination != FetchOptions::Destination::EmptyString)
document->addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("Cannot load ", m_error.failingURL().string(), "."));
}
m_loader->didFail(m_error);
if (auto callback = WTFMove(m_callback))
callback(Result::Succeeded);
m_serviceWorkerProvider.fetchFinished(m_identifier);
});
}
void ServiceWorkerClientFetch::didNotHandle()
{
ASSERT(!m_isCheckingResponse);
callOnMainThread([this, protectedThis = makeRef(*this)] {
if (!m_loader)
return;
if (auto callback = WTFMove(m_callback))
callback(Result::Unhandled);
m_serviceWorkerProvider.fetchFinished(m_identifier);
});
}
void ServiceWorkerClientFetch::cancel()
{
if (auto callback = WTFMove(m_callback))
callback(Result::Cancelled);
if (!m_didFinish && !m_didFail) {
m_connection->cancelFetch(m_identifier, m_serviceWorkerRegistrationIdentifier);
}
m_loader = nullptr;
m_buffer = nullptr;
}
void ServiceWorkerClientFetch::continueLoadingAfterCheckingResponse()
{
ASSERT(!m_isCheckingResponse);
if (!m_loader)
return;
if (m_encodedDataLength) {
callOnMainThread([this, protectedThis = makeRef(*this)] {
if (!m_loader || !m_encodedDataLength)
return;
m_loader->didReceiveBuffer(m_buffer.releaseNonNull(), m_encodedDataLength, DataPayloadBytes);
m_encodedDataLength = 0;
});
}
if (m_didFail) {
didFail(WTFMove(m_error));
return;
}
if (m_didFinish)
didFinish();
}
}
#endif // ENABLE(SERVICE_WORKER)