blob: 713b4dfb30b2afdfbeaff4ee5370a2546fad3a47 [file] [log] [blame]
/*
* Copyright (C) 2013-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. ``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
* 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 "ResourceHandleCFURLConnectionDelegateWithOperationQueue.h"
#if USE(CFURLCONNECTION)
#include "AuthenticationCF.h"
#include "AuthenticationChallenge.h"
#include "Logging.h"
#include "MIMETypeRegistry.h"
#include "ResourceHandle.h"
#include "ResourceHandleClient.h"
#include "ResourceResponse.h"
#include "SharedBuffer.h"
#if !PLATFORM(WIN)
#include "WebCoreURLResponse.h"
#endif
#include <pal/spi/cf/CFNetworkSPI.h>
#include <wtf/CompletionHandler.h>
#include <wtf/MainThread.h>
#include <wtf/Threading.h>
#include <wtf/text/CString.h>
#include <wtf/text/WTFString.h>
namespace WebCore {
ResourceHandleCFURLConnectionDelegateWithOperationQueue::ResourceHandleCFURLConnectionDelegateWithOperationQueue(ResourceHandle* handle, MessageQueue<Function<void()>>* messageQueue)
: ResourceHandleCFURLConnectionDelegate(handle)
, m_messageQueue(messageQueue)
{
}
ResourceHandleCFURLConnectionDelegateWithOperationQueue::~ResourceHandleCFURLConnectionDelegateWithOperationQueue()
{
}
bool ResourceHandleCFURLConnectionDelegateWithOperationQueue::hasHandle() const
{
return !!m_handle;
}
void ResourceHandleCFURLConnectionDelegateWithOperationQueue::releaseHandle()
{
ResourceHandleCFURLConnectionDelegate::releaseHandle();
m_requestResult = nullptr;
m_cachedResponseResult = nullptr;
m_semaphore.signal();
}
void ResourceHandleCFURLConnectionDelegateWithOperationQueue::setupRequest(CFMutableURLRequestRef request)
{
CFURLRef requestURL = CFURLRequestGetURL(request);
if (!requestURL)
return;
m_originalScheme = adoptCF(CFURLCopyScheme(requestURL));
}
LRESULT CALLBACK hookToRemoveCFNetworkMessage(int code, WPARAM wParam, LPARAM lParam)
{
MSG* msg = reinterpret_cast<MSG*>(lParam);
// This message which CFNetwork sends to itself, will block the main thread, remove it.
if (msg->message == WM_USER + 0xcf)
msg->message = WM_NULL;
return CallNextHookEx(nullptr, code, wParam, lParam);
}
static void installHookToRemoveCFNetworkMessageBlockingMainThread()
{
static HHOOK hook = nullptr;
if (!hook) {
DWORD threadID = ::GetCurrentThreadId();
hook = ::SetWindowsHookExW(WH_GETMESSAGE, hookToRemoveCFNetworkMessage, 0, threadID);
}
}
static void emptyPerform(void*)
{
}
static CFRunLoopRef getRunLoop()
{
static CFRunLoopRef runLoop = nullptr;
if (!runLoop) {
BinarySemaphore sem;
Thread::create("CFNetwork Loader", [&] {
runLoop = CFRunLoopGetCurrent();
// Must add a source to the run loop to prevent CFRunLoopRun() from exiting.
CFRunLoopSourceContext ctxt = { 0, (void*)1 /*must be non-null*/, 0, 0, 0, 0, 0, 0, 0, emptyPerform };
CFRunLoopSourceRef bogusSource = CFRunLoopSourceCreate(0, 0, &ctxt);
CFRunLoopAddSource(runLoop, bogusSource, kCFRunLoopDefaultMode);
sem.signal();
while (true)
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1E30, true);
});
sem.wait();
}
return runLoop;
}
void ResourceHandleCFURLConnectionDelegateWithOperationQueue::setupConnectionScheduling(CFURLConnectionRef connection)
{
installHookToRemoveCFNetworkMessageBlockingMainThread();
CFRunLoopRef runLoop = getRunLoop();
CFURLConnectionScheduleWithRunLoop(connection, runLoop, kCFRunLoopDefaultMode);
CFURLConnectionScheduleDownloadWithRunLoop(connection, runLoop, kCFRunLoopDefaultMode);
}
CFURLRequestRef ResourceHandleCFURLConnectionDelegateWithOperationQueue::willSendRequest(CFURLRequestRef cfRequest, CFURLResponseRef originalRedirectResponse)
{
// If the protocols of the new request and the current request match, this is not an HSTS redirect and we don't need to synthesize a redirect response.
if (!originalRedirectResponse) {
RetainPtr<CFStringRef> newScheme = adoptCF(CFURLCopyScheme(CFURLRequestGetURL(cfRequest)));
if (CFStringCompare(newScheme.get(), m_originalScheme.get(), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
CFRetain(cfRequest);
return cfRequest;
}
}
ASSERT(!isMainThread());
auto protectedThis = makeRef(*this);
auto work = [this, protectedThis = makeRef(*this), cfRequest = RetainPtr<CFURLRequestRef>(cfRequest), originalRedirectResponse = RetainPtr<CFURLResponseRef>(originalRedirectResponse)] () mutable {
auto& handle = protectedThis->m_handle;
auto completionHandler = [this, protectedThis = WTFMove(protectedThis)] (ResourceRequest&& request) {
m_requestResult = request.cfURLRequest(UpdateHTTPBody);
m_semaphore.signal();
};
if (!hasHandle()) {
completionHandler({ });
return;
}
LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::willSendRequest(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
RetainPtr<CFURLResponseRef> redirectResponse = synthesizeRedirectResponseIfNecessary(cfRequest.get(), originalRedirectResponse.get());
ASSERT(redirectResponse);
ResourceRequest request = createResourceRequest(cfRequest.get(), redirectResponse.get());
handle->willSendRequest(WTFMove(request), redirectResponse.get(), WTFMove(completionHandler));
};
if (m_messageQueue)
m_messageQueue->append(makeUnique<Function<void()>>(WTFMove(work)));
else
callOnMainThread(WTFMove(work));
m_semaphore.wait();
return m_requestResult.leakRef();
}
void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveResponse(CFURLConnectionRef connection, CFURLResponseRef cfResponse)
{
auto protectedThis = makeRef(*this);
auto work = [this, protectedThis = makeRef(*this), cfResponse = RetainPtr<CFURLResponseRef>(cfResponse), connection = RetainPtr<CFURLConnectionRef>(connection)] () mutable {
if (!hasHandle() || !m_handle->client() || !m_handle->connection()) {
m_semaphore.signal();
return;
}
LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveResponse(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
// Avoid MIME type sniffing if the response comes back as 304 Not Modified.
auto msg = CFURLResponseGetHTTPResponse(cfResponse.get());
int statusCode = msg ? CFHTTPMessageGetResponseStatusCode(msg) : 0;
if (statusCode != 304) {
bool isMainResourceLoad = m_handle->firstRequest().requester() == ResourceRequest::Requester::Main;
}
if (_CFURLRequestCopyProtocolPropertyForKey(m_handle->firstRequest().cfURLRequest(DoNotUpdateHTTPBody), CFSTR("ForceHTMLMIMEType")))
CFURLResponseSetMIMEType(cfResponse.get(), CFSTR("text/html"));
ResourceResponse resourceResponse(cfResponse.get());
resourceResponse.setSource(ResourceResponse::Source::Network);
m_handle->didReceiveResponse(WTFMove(resourceResponse), [this, protectedThis = WTFMove(protectedThis)] {
m_semaphore.signal();
});
};
if (m_messageQueue)
m_messageQueue->append(makeUnique<Function<void()>>(WTFMove(work)));
else
callOnMainThread(WTFMove(work));
m_semaphore.wait();
}
void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveData(CFDataRef data, CFIndex originalLength)
{
auto work = [protectedThis = makeRef(*this), data = RetainPtr<CFDataRef>(data), originalLength = originalLength] () mutable {
auto& handle = protectedThis->m_handle;
if (!protectedThis->hasHandle() || !handle->client() || !handle->connection())
return;
LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveData(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
handle->client()->didReceiveBuffer(handle, SharedBuffer::create(data.get()), originalLength);
};
if (m_messageQueue)
m_messageQueue->append(makeUnique<Function<void()>>(WTFMove(work)));
else
callOnMainThread(WTFMove(work));
}
void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFinishLoading()
{
auto work = [protectedThis = makeRef(*this)] () mutable {
auto& handle = protectedThis->m_handle;
if (!protectedThis->hasHandle() || !handle->client() || !handle->connection()) {
protectedThis->m_handle->deref();
return;
}
LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFinishLoading(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
handle->client()->didFinishLoading(handle);
if (protectedThis->m_messageQueue) {
protectedThis->m_messageQueue->kill();
protectedThis->m_messageQueue = nullptr;
}
protectedThis->m_handle->deref();
};
if (m_messageQueue)
m_messageQueue->append(makeUnique<Function<void()>>(WTFMove(work)));
else
callOnMainThread(WTFMove(work));
}
void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFail(CFErrorRef error)
{
auto work = [protectedThis = makeRef(*this), error = RetainPtr<CFErrorRef>(error)] () mutable {
auto& handle = protectedThis->m_handle;
if (!protectedThis->hasHandle() || !handle->client() || !handle->connection()) {
protectedThis->m_handle->deref();
return;
}
LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFail(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
handle->client()->didFail(handle, ResourceError(error.get()));
if (protectedThis->m_messageQueue) {
protectedThis->m_messageQueue->kill();
protectedThis->m_messageQueue = nullptr;
}
protectedThis->m_handle->deref();
};
if (m_messageQueue)
m_messageQueue->append(makeUnique<Function<void()>>(WTFMove(work)));
else
callOnMainThread(WTFMove(work));
}
CFCachedURLResponseRef ResourceHandleCFURLConnectionDelegateWithOperationQueue::willCacheResponse(CFCachedURLResponseRef cachedResponse)
{
// Workaround for <rdar://problem/6300990> Caching does not respect Vary HTTP header.
// FIXME: WebCore cache has issues with Vary, too (bug 58797, bug 71509).
CFURLResponseRef wrappedResponse = CFCachedURLResponseGetWrappedResponse(cachedResponse);
if (CFHTTPMessageRef httpResponse = CFURLResponseGetHTTPResponse(wrappedResponse)) {
ASSERT(CFHTTPMessageIsHeaderComplete(httpResponse));
RetainPtr<CFStringRef> varyValue = adoptCF(CFHTTPMessageCopyHeaderFieldValue(httpResponse, CFSTR("Vary")));
if (varyValue)
return nullptr;
}
auto protectedThis = makeRef(*this);
auto work = [protectedThis = makeRef(*this), cachedResponse = RetainPtr<CFCachedURLResponseRef>(cachedResponse)] () mutable {
auto& handle = protectedThis->m_handle;
auto completionHandler = [protectedThis = WTFMove(protectedThis)] (CFCachedURLResponseRef response) mutable {
protectedThis->m_cachedResponseResult = response;
protectedThis->m_semaphore.signal();
};
if (!handle || !handle->client() || !handle->connection())
return completionHandler(nullptr);
LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::willCacheResponse(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
handle->client()->willCacheResponseAsync(handle, cachedResponse.get(), WTFMove(completionHandler));
};
if (m_messageQueue)
m_messageQueue->append(makeUnique<Function<void()>>(WTFMove(work)));
else
callOnMainThread(WTFMove(work));
m_semaphore.wait();
return m_cachedResponseResult.leakRef();
}
void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveChallenge(CFURLAuthChallengeRef challenge)
{
auto work = [protectedThis = makeRef(*this), challenge = RetainPtr<CFURLAuthChallengeRef>(challenge)] () mutable {
auto& handle = protectedThis->m_handle;
if (!protectedThis->hasHandle())
return;
LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveChallenge(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
handle->didReceiveAuthenticationChallenge(AuthenticationChallenge(challenge.get(), handle));
};
if (m_messageQueue)
m_messageQueue->append(makeUnique<Function<void()>>(WTFMove(work)));
else
callOnMainThread(WTFMove(work));
}
void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didSendBodyData(CFIndex totalBytesWritten, CFIndex totalBytesExpectedToWrite)
{
auto work = [protectedThis = makeRef(*this), totalBytesWritten, totalBytesExpectedToWrite] () mutable {
auto& handle = protectedThis->m_handle;
if (!protectedThis->hasHandle() || !handle->client() || !handle->connection())
return;
LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didSendBodyData(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
handle->client()->didSendData(handle, totalBytesWritten, totalBytesExpectedToWrite);
};
if (m_messageQueue)
m_messageQueue->append(makeUnique<Function<void()>>(WTFMove(work)));
else
callOnMainThread(WTFMove(work));
}
Boolean ResourceHandleCFURLConnectionDelegateWithOperationQueue::shouldUseCredentialStorage()
{
return false;
}
#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
Boolean ResourceHandleCFURLConnectionDelegateWithOperationQueue::canRespondToProtectionSpace(CFURLProtectionSpaceRef protectionSpace)
{
auto protectedThis = makeRef(*this);
auto work = [protectedThis = makeRef(*this), protectionSpace = RetainPtr<CFURLProtectionSpaceRef>(protectionSpace)] () mutable {
auto& handle = protectedThis->m_handle;
auto completionHandler = [protectedThis = WTFMove(protectedThis)] (bool result) mutable {
protectedThis->m_boolResult = canAuthenticate;
protectedThis->m_semaphore.signal();
}
if (!handle)
return completionHandler(false);
LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::canRespondToProtectionSpace(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
handle->canAuthenticateAgainstProtectionSpace(ProtectionSpace(protectionSpace.get()), WTFMove(completionHandler));
};
if (m_messageQueue)
m_messageQueue->append(makeUnique<Function<void()>>(WTFMove(work)));
else
callOnMainThread(WTFMove(work));
m_semaphore.wait();
return m_boolResult;
}
void ResourceHandleCFURLConnectionDelegateWithOperationQueue::continueCanAuthenticateAgainstProtectionSpace(bool canAuthenticate)
{
m_boolResult = canAuthenticate;
m_semaphore.signal();
}
#endif // USE(PROTECTION_SPACE_AUTH_CALLBACK)
} // namespace WebCore
#endif // USE(CFURLCONNECTION)