Use CompletionHandlers for ResourceHandleClient::didReceiveResponseAsync
[WebKit-https.git] / Source / WebCore / platform / network / curl / ResourceHandleCurlDelegate.cpp
index e1f4e63..d373af6 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2004, 2006 Apple Inc.  All rights reserved.
  * Copyright (C) 2005, 2006 Michael Emmel mike.emmel@gmail.com
- * Copyright (C) 2017 Sony Interactive Entertainment Inc.
+ * Copyright (C) 2018 Sony Interactive Entertainment Inc.
  * All rights reserved.
  * Copyright (C) 2017 NAVER Corp. All rights reserved.
  *
 
 #if USE(CURL)
 
+#include "AuthenticationChallenge.h"
 #include "CredentialStorage.h"
 #include "CurlCacheManager.h"
+#include "CurlRequest.h"
 #include "HTTPParsers.h"
-#include "MIMETypeRegistry.h"
-#include "MultipartHandle.h"
-#include "ResourceHandle.h"
 #include "ResourceHandleInternal.h"
+#include "SharedBuffer.h"
 #include "TextEncoding.h"
-#include "ThreadSafeDataBuffer.h"
-#include "URL.h"
-#include <wtf/MainThread.h>
+#include <wtf/CompletionHandler.h>
 #include <wtf/text/Base64.h>
 
 namespace WebCore {
 
 ResourceHandleCurlDelegate::ResourceHandleCurlDelegate(ResourceHandle* handle)
     : m_handle(handle)
-    , m_formDataStream(handle)
     , m_firstRequest(handle->firstRequest().isolatedCopy())
-    , m_customHTTPHeaderFields(m_firstRequest.httpHeaderFields().isolatedCopy())
+    , m_currentRequest(handle->firstRequest().isolatedCopy())
     , m_shouldUseCredentialStorage(handle->shouldUseCredentialStorage())
     , m_user(handle->getInternal()->m_user.isolatedCopy())
     , m_pass(handle->getInternal()->m_pass.isolatedCopy())
     , m_initialCredential(handle->getInternal()->m_initialCredential)
     , m_defersLoading(handle->getInternal()->m_defersLoading)
 {
-    const URL& url = m_firstRequest.url();
 
-    if (url.isLocalFile()) {
-        // Determine the MIME type based on the path.
-        response().setMimeType(MIMETypeRegistry::getMIMETypeForPath(url));
-    }
-
-    if (m_customHTTPHeaderFields.size()) {
-        auto& cache = CurlCacheManager::getInstance();
-        bool hasCacheHeaders = m_customHTTPHeaderFields.contains(HTTPHeaderName::IfModifiedSince) || m_customHTTPHeaderFields.contains(HTTPHeaderName::IfNoneMatch);
-        if (!hasCacheHeaders && cache.isCached(url)) {
-            cache.addCacheEntryClient(url, m_handle);
-            // append additional cache information
-            for (auto entry : cache.requestHeaders(url))
-                m_customHTTPHeaderFields.set(entry.key, entry.value);
-            m_addedCacheValidationHeaders = true;
-        }
-    }
-
-    setupAuthentication();
 }
 
 ResourceHandleCurlDelegate::~ResourceHandleCurlDelegate()
 {
+    if (m_curlRequest)
+        m_curlRequest->setClient(nullptr);
 }
 
 bool ResourceHandleCurlDelegate::hasHandle() const
@@ -94,59 +74,64 @@ void ResourceHandleCurlDelegate::releaseHandle()
     m_handle = nullptr;
 }
 
-bool ResourceHandleCurlDelegate::start()
+void ResourceHandleCurlDelegate::start()
 {
-    m_job = CurlJobManager::singleton().add(m_curlHandle, *this);
-    return !!m_job;
+    ASSERT(isMainThread());
+
+    auto credential = getCredential(m_currentRequest, false);
+
+    m_curlRequest = createCurlRequest(m_currentRequest);
+    m_curlRequest->setUserPass(credential.first, credential.second);
+    m_curlRequest->start();
 }
 
 void ResourceHandleCurlDelegate::cancel()
 {
+    ASSERT(isMainThread());
+
     releaseHandle();
-    CurlJobManager::singleton().cancel(m_job);
+
+    if (!m_curlRequest)
+        m_curlRequest->cancel();
 }
 
 void ResourceHandleCurlDelegate::setDefersLoading(bool defers)
 {
+    ASSERT(isMainThread());
+
     if (defers == m_defersLoading)
         return;
 
     m_defersLoading = defers;
 
-    auto action = [protectedThis = makeRef(*this)]() {
-        if (protectedThis->m_defersLoading) {
-            CURLcode error = protectedThis->m_curlHandle.pause(CURLPAUSE_ALL);
-            // If we could not defer the handle, so don't do it.
-            if (error != CURLE_OK)
-                return;
-        } else {
-            CURLcode error = protectedThis->m_curlHandle.pause(CURLPAUSE_CONT);
-            if (error != CURLE_OK) {
-                // Restarting the handle has failed so just cancel it.
-                protectedThis->m_handle->cancel();
-            }
-        }
-    };
+    if (!m_curlRequest)
+        return;
 
-    CurlJobManager::singleton().callOnJobThread(WTFMove(action));
+    if (m_defersLoading)
+        m_curlRequest->suspend();
+    else
+        m_curlRequest->resume();
 }
 
-void ResourceHandleCurlDelegate::setAuthentication(const String& user, const String& pass)
+void ResourceHandleCurlDelegate::setAuthentication(const String& user, const String& password)
 {
-    auto action = [protectedThis = makeRef(*this), user = user.isolatedCopy(), pass = pass.isolatedCopy()]() {
-        protectedThis->m_user = user;
-        protectedThis->m_pass = pass;
-        protectedThis->m_curlHandle.setHttpAuthUserPass(user, pass);
-    };
+    ASSERT(isMainThread());
+
+    if (!m_curlRequest)
+        return;
+
+    bool isSyncRequest = m_curlRequest->isSyncRequest();
+    m_curlRequest->cancel();
+    m_curlRequest->setClient(nullptr);
 
-    CurlJobManager::singleton().callOnJobThread(WTFMove(action));
+    m_curlRequest = createCurlRequest(m_currentRequest);
+    m_curlRequest->setUserPass(user, password);
+    m_curlRequest->start(isSyncRequest);
 }
 
 void ResourceHandleCurlDelegate::dispatchSynchronousJob()
 {
-    URL kurl = m_firstRequest.url();
-
-    if (kurl.protocolIsData()) {
+    if (m_currentRequest.url().protocolIsData()) {
         handleDataURL();
         return;
     }
@@ -156,322 +141,254 @@ void ResourceHandleCurlDelegate::dispatchSynchronousJob()
     // and we would assert so force defersLoading to be false.
     m_defersLoading = false;
 
-    setupRequest();
-
-    // curl_easy_perform blocks until the transfer is finished.
-    CURLcode ret = m_curlHandle.perform();
-
-    if (ret != CURLE_OK)
-        notifyFail();
-    else
-        notifyFinish();
+    m_curlRequest = createCurlRequest(m_currentRequest);
+    m_curlRequest->start(true);
 }
 
-void ResourceHandleCurlDelegate::retain()
+Ref<CurlRequest> ResourceHandleCurlDelegate::createCurlRequest(ResourceRequest& request)
 {
-    ref();
+    ASSERT(isMainThread());
+
+    // CurlCache : append additional cache information
+    m_addedCacheValidationHeaders = false;
+
+    bool hasCacheHeaders = request.httpHeaderFields().contains(HTTPHeaderName::IfModifiedSince) || request.httpHeaderFields().contains(HTTPHeaderName::IfNoneMatch);
+    if (!hasCacheHeaders) {
+        auto& cache = CurlCacheManager::singleton();
+        URL cacheUrl = request.url();
+        cacheUrl.removeFragmentIdentifier();
+
+        if (cache.isCached(cacheUrl)) {
+            cache.addCacheEntryClient(cacheUrl, m_handle);
+
+            for (const auto& entry : cache.requestHeaders(cacheUrl))
+                request.addHTTPHeaderField(entry.key, entry.value);
+
+            m_addedCacheValidationHeaders = true;
+        }
+    }
+
+    CurlRequest::ShouldSuspend shouldSuspend = m_defersLoading ? CurlRequest::ShouldSuspend::Yes : CurlRequest::ShouldSuspend::No;
+    return CurlRequest::create(request, this, shouldSuspend, CurlRequest::EnableMultipart::Yes);
 }
 
-void ResourceHandleCurlDelegate::release()
+bool ResourceHandleCurlDelegate::cancelledOrClientless()
 {
-    deref();
+    if (!m_handle)
+        return true;
+
+    return !m_handle->client();
 }
 
-void ResourceHandleCurlDelegate::setupRequest()
+void ResourceHandleCurlDelegate::curlDidReceiveResponse(const CurlResponse& receivedResponse)
 {
-    CurlContext& context = CurlContext::singleton();
-
-    URL url = m_firstRequest.url();
+    ASSERT(isMainThread());
+    ASSERT(!m_defersLoading);
 
-    // Remove any fragment part, otherwise curl will send it as part of the request.
-    url.removeFragmentIdentifier();
+    if (cancelledOrClientless())
+        return;
 
-    String urlString = url.string();
+    m_handle->getInternal()->m_response = ResourceResponse(receivedResponse);
 
-    m_curlHandle.initialize();
+    if (m_curlRequest)
+        m_handle->getInternal()->m_response.setDeprecatedNetworkLoadMetrics(m_curlRequest->getNetworkLoadMetrics());
 
-    if (url.isLocalFile()) {
-        // Remove any query part sent to a local file.
-        if (!url.query().isEmpty()) {
-            // By setting the query to a null string it'll be removed.
-            url.setQuery(String());
-            urlString = url.string();
-        }
+    if (response().shouldRedirect()) {
+        willSendRequest();
+        return;
     }
 
-    if (m_defersLoading) {
-        CURLcode error = m_curlHandle.pause(CURLPAUSE_ALL);
-        // If we did not pause the handle, we would ASSERT in the
-        // header callback. So just assert here.
-        ASSERT_UNUSED(error, error == CURLE_OK);
+    if (response().isUnauthorized()) {
+        AuthenticationChallenge challenge(receivedResponse, m_authFailureCount, response(), m_handle);
+        m_handle->didReceiveAuthenticationChallenge(challenge);
+        m_authFailureCount++;
+        return;
     }
 
-#ifndef NDEBUG
-    m_curlHandle.enableVerboseIfUsed();
-    m_curlHandle.enableStdErrIfUsed();
-#endif
+    if (m_handle->client()) {
+        if (response().isNotModified()) {
+            URL cacheUrl = m_currentRequest.url();
+            cacheUrl.removeFragmentIdentifier();
 
-    auto& sslHandle = CurlContext::singleton().sslHandle();
-
-    m_curlHandle.setSslVerifyPeer(CurlHandle::VerifyPeerEnable);
-    m_curlHandle.setSslVerifyHost(CurlHandle::VerifyHostStrictNameCheck);
-    m_curlHandle.setPrivateData(this);
-    m_curlHandle.setWriteCallbackFunction(didReceiveDataCallback, this);
-    m_curlHandle.setHeaderCallbackFunction(didReceiveHeaderCallback, this);
-    m_curlHandle.enableAutoReferer();
-    m_curlHandle.enableFollowLocation();
-    m_curlHandle.enableHttpAuthentication(CURLAUTH_ANY);
-    m_curlHandle.enableShareHandle();
-    m_curlHandle.enableTimeout();
-    m_curlHandle.enableAllowedProtocols();
-
-    auto sslClientCertificate = sslHandle.getSSLClientCertificate(url.host());
-    if (sslClientCertificate) {
-        m_curlHandle.setSslCert(sslClientCertificate->first.utf8().data());
-        m_curlHandle.setSslCertType("P12");
-        m_curlHandle.setSslKeyPassword(sslClientCertificate->second.utf8().data());
-    }
+            if (CurlCacheManager::singleton().getCachedResponse(cacheUrl, response())) {
+                if (m_addedCacheValidationHeaders) {
+                    response().setHTTPStatusCode(200);
+                    response().setHTTPStatusText("OK");
+                }
+            }
+        }
 
-    if (sslHandle.shouldIgnoreSSLErrors())
-        m_curlHandle.setSslVerifyPeer(CurlHandle::VerifyPeerDisable);
-    else
-        m_curlHandle.setSslCtxCallbackFunction(willSetupSslCtxCallback, this);
-
-    m_curlHandle.setCACertPath(sslHandle.getCACertPath());
-
-    m_curlHandle.enableAcceptEncoding();
-    m_curlHandle.setUrl(urlString);
-    m_curlHandle.enableCookieJarIfExists();
-
-    if (m_customHTTPHeaderFields.size())
-        m_curlHandle.appendRequestHeaders(m_customHTTPHeaderFields);
-
-    String method = m_firstRequest.httpMethod();
-    if ("GET" == method)
-        m_curlHandle.enableHttpGetRequest();
-    else if ("POST" == method)
-        setupPOST();
-    else if ("PUT" == method)
-        setupPUT();
-    else if ("HEAD" == method)
-        m_curlHandle.enableHttpHeadRequest();
-    else {
-        m_curlHandle.setHttpCustomRequest(method);
-        setupPUT();
+        CurlCacheManager::singleton().didReceiveResponse(*m_handle, response());
+
+        auto protectedThis = makeRef(*m_handle);
+        m_handle->didReceiveResponse(ResourceResponse(response()), [this, protectedThis = makeRef(*this)] {
+            continueAfterDidReceiveResponse();
+        });
     }
+}
 
-    m_curlHandle.enableRequestHeaders();
+void ResourceHandleCurlDelegate::curlDidReceiveBuffer(Ref<SharedBuffer>&& buffer)
+{
+    ASSERT(isMainThread());
 
-    applyAuthentication();
+    if (cancelledOrClientless())
+        return;
 
-    m_curlHandle.enableProxyIfExists();
+    CurlCacheManager::singleton().didReceiveData(*m_handle, buffer->data(), buffer->size());
+    m_handle->client()->didReceiveBuffer(m_handle, WTFMove(buffer), buffer->size());
 }
 
-void ResourceHandleCurlDelegate::notifyFinish()
+void ResourceHandleCurlDelegate::curlDidComplete()
 {
-    NetworkLoadMetrics networkLoadMetrics = getNetworkLoadMetrics();
-
-    if (isMainThread())
-        didFinish(networkLoadMetrics);
-    else {
-        callOnMainThread([protectedThis = makeRef(*this), metrics = networkLoadMetrics.isolatedCopy()] {
-            if (!protectedThis->m_handle)
-                return;
-            protectedThis->didFinish(metrics);
-        });
+    ASSERT(isMainThread());
+
+    if (cancelledOrClientless())
+        return;
+
+    if (m_curlRequest)
+        m_handle->getInternal()->m_response.setDeprecatedNetworkLoadMetrics(m_curlRequest->getNetworkLoadMetrics());
+
+    if (m_handle->client()) {
+        CurlCacheManager::singleton().didFinishLoading(*m_handle);
+        m_handle->client()->didFinishLoading(m_handle);
     }
 }
 
-void ResourceHandleCurlDelegate::notifyFail()
+void ResourceHandleCurlDelegate::curlDidFailWithError(const ResourceError& resourceError)
 {
-    ResourceError resourceError = ResourceError::httpError(m_curlHandle.errorCode(), m_firstRequest.url());
-    if (m_sslVerifier.sslErrors())
-        resourceError.setSslErrors(m_sslVerifier.sslErrors());
-
-    if (isMainThread())
-        didFail(resourceError);
-    else {
-        callOnMainThread([protectedThis = makeRef(*this), error = resourceError.isolatedCopy()] {
-            if (!protectedThis->m_handle)
-                return;
-            protectedThis->didFail(error);
-        });
-    }
+    ASSERT(isMainThread());
+
+    if (cancelledOrClientless())
+        return;
+
+    CurlCacheManager::singleton().didFail(*m_handle);
+    m_handle->client()->didFail(m_handle, resourceError);
 }
 
-ResourceResponse& ResourceHandleCurlDelegate::response()
+void ResourceHandleCurlDelegate::continueDidReceiveResponse()
 {
-    return m_handle->getInternal()->m_response;
+    ASSERT(isMainThread());
+
+    continueAfterDidReceiveResponse();
 }
 
-void ResourceHandleCurlDelegate::setupAuthentication()
+void ResourceHandleCurlDelegate::platformContinueSynchronousDidReceiveResponse()
 {
-    // m_user/m_pass are credentials given manually, for instance, by the arguments passed to XMLHttpRequest.open().
-    String partition = m_firstRequest.cachePartition();
+    ASSERT(isMainThread());
 
-    if (m_shouldUseCredentialStorage) {
-        if (m_user.isEmpty() && m_pass.isEmpty()) {
-            // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 
-            // try and reuse the credential preemptively, as allowed by RFC 2617.
-            m_initialCredential = CredentialStorage::defaultCredentialStorage().get(partition, m_firstRequest.url());
-        } else {
-            // If there is already a protection space known for the URL, update stored credentials
-            // before sending a request. This makes it possible to implement logout by sending an
-            // XMLHttpRequest with known incorrect credentials, and aborting it immediately (so that
-            // an authentication dialog doesn't pop up).
-            CredentialStorage::defaultCredentialStorage().set(partition, Credential(m_user, m_pass, CredentialPersistenceNone), m_firstRequest.url());
-        }
-    }
+    continueAfterDidReceiveResponse();
 }
 
-inline static bool isHttpInfo(int statusCode)
+void ResourceHandleCurlDelegate::continueAfterDidReceiveResponse()
 {
-    return 100 <= statusCode && statusCode < 200;
+    ASSERT(isMainThread());
+
+    // continueDidReceiveResponse might cancel the load.
+    if (cancelledOrClientless() || !m_curlRequest)
+        return;
+
+    m_curlRequest->completeDidReceiveResponse();
 }
 
-void ResourceHandleCurlDelegate::didReceiveAllHeaders(long httpCode, long long contentLength, uint16_t connectPort, long availableHttpAuth)
+bool ResourceHandleCurlDelegate::shouldRedirectAsGET(const ResourceRequest& request, bool crossOrigin)
 {
-    ASSERT(isMainThread());
+    if (request.httpMethod() == "GET" || request.httpMethod() == "HEAD")
+        return false;
 
-    response().setURL(m_curlHandle.getEffectiveURL());
+    if (!request.url().protocolIsInHTTPFamily())
+        return true;
 
-    response().setExpectedContentLength(contentLength);
-    response().setHTTPStatusCode(httpCode);
-    response().setMimeType(extractMIMETypeFromMediaType(response().httpHeaderField(HTTPHeaderName::ContentType)).convertToASCIILowercase());
-    response().setTextEncodingName(extractCharsetFromMediaType(response().httpHeaderField(HTTPHeaderName::ContentType)));
+    if (response().isSeeOther())
+        return true;
 
-    if (response().isMultipart()) {
-        String boundary;
-        bool parsed = MultipartHandle::extractBoundary(response().httpHeaderField(HTTPHeaderName::ContentType), boundary);
-        if (parsed)
-            m_multipartHandle = std::make_unique<MultipartHandle>(m_handle, boundary);
-    }
+    if ((response().isMovedPermanently() || response().isFound()) && (request.httpMethod() == "POST"))
+        return true;
 
-    // HTTP redirection
-    if (response().isRedirection()) {
-        String location = response().httpHeaderField(HTTPHeaderName::Location);
-        if (!location.isEmpty()) {
-            URL newURL = URL(m_firstRequest.url(), location);
+    if (crossOrigin && (request.httpMethod() == "DELETE"))
+        return true;
 
-            ResourceRequest redirectedRequest = m_firstRequest;
-            redirectedRequest.setURL(newURL);
-            ResourceResponse localResponse = response();
-            if (m_handle->client())
-                m_handle->client()->willSendRequest(m_handle, WTFMove(redirectedRequest), WTFMove(localResponse));
+    return false;
+}
 
-            m_firstRequest.setURL(newURL);
+void ResourceHandleCurlDelegate::willSendRequest()
+{
+    ASSERT(isMainThread());
 
-            return;
-        }
-    } else if (response().isUnauthorized()) {
-        AuthenticationChallenge challenge(connectPort, availableHttpAuth, m_authFailureCount, response(), m_handle);
-        m_handle->didReceiveAuthenticationChallenge(challenge);
-        m_authFailureCount++;
+    static const int maxRedirects = 20;
+
+    if (m_redirectCount++ > maxRedirects) {
+        m_handle->client()->didFail(m_handle, ResourceError::httpError(CURLE_TOO_MANY_REDIRECTS, response().url()));
         return;
     }
 
-    response().setResponseFired(true);
+    String location = response().httpHeaderField(HTTPHeaderName::Location);
+    URL newURL = URL(m_firstRequest.url(), location);
+    bool crossOrigin = !protocolHostAndPortAreEqual(m_firstRequest.url(), newURL);
 
-    if (m_handle->client()) {
-        if (response().isNotModified()) {
-            const String& url = m_firstRequest.url().string();
-            if (CurlCacheManager::getInstance().getCachedResponse(url, response())) {
-                if (m_addedCacheValidationHeaders) {
-                    response().setHTTPStatusCode(200);
-                    response().setHTTPStatusText("OK");
-                }
-            }
-        }
-        CurlCacheManager::getInstance().didReceiveResponse(*m_handle, response());
-        m_handle->client()->didReceiveResponse(m_handle, ResourceResponse(response()));
-    }
-}
+    ResourceRequest newRequest = m_firstRequest;
+    newRequest.setURL(newURL);
 
-void ResourceHandleCurlDelegate::didReceiveContentData(ThreadSafeDataBuffer buffer)
-{
-    ASSERT(isMainThread());
+    if (shouldRedirectAsGET(newRequest, crossOrigin)) {
+        newRequest.setHTTPMethod("GET");
+        newRequest.setHTTPBody(nullptr);
+        newRequest.clearHTTPContentType();
+    }
 
-    if (!response().responseFired())
-        handleLocalReceiveResponse();
+    // Should not set Referer after a redirect from a secure resource to non-secure one.
+    if (!newURL.protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https") && m_handle->context()->shouldClearReferrerOnHTTPSToHTTPRedirect())
+        newRequest.clearHTTPReferrer();
 
-    const char* ptr = reinterpret_cast<const char*>(buffer.data()->begin());
-    size_t size = buffer.size();
+    m_user = newURL.user();
+    m_pass = newURL.pass();
+    newRequest.removeCredentials();
 
-    if (m_multipartHandle)
-        m_multipartHandle->contentReceived(ptr, size);
-    else if (m_handle->client()) {
-        CurlCacheManager::getInstance().didReceiveData(*m_handle, ptr, size);
-        m_handle->client()->didReceiveData(m_handle, ptr, size, 0);
+    if (crossOrigin) {
+        // If the network layer carries over authentication headers from the original request
+        // in a cross-origin redirect, we want to clear those headers here. 
+        newRequest.clearHTTPAuthorization();
+        newRequest.clearHTTPOrigin();
     }
+
+    ResourceResponse responseCopy = response();
+    m_handle->client()->willSendRequestAsync(m_handle, WTFMove(newRequest), WTFMove(responseCopy), [this, protectedThis = makeRef(*this)] (ResourceRequest&& request) {
+        continueWillSendRequest(WTFMove(request));
+    });
 }
 
-void ResourceHandleCurlDelegate::handleLocalReceiveResponse()
+void ResourceHandleCurlDelegate::continueWillSendRequest(ResourceRequest&& request)
 {
     ASSERT(isMainThread());
 
-    // since the code in headerCallback will not have run for local files
-    // the code to set the URL and fire didReceiveResponse is never run,
-    // which means the ResourceLoader's response does not contain the URL.
-    // Run the code here for local files to resolve the issue.
-    // TODO: See if there is a better approach for handling this.
-    response().setURL(m_curlHandle.getEffectiveURL());
-    response().setResponseFired(true);
-    if (m_handle->client())
-        m_handle->client()->didReceiveResponse(m_handle, ResourceResponse(response()));
+    continueAfterWillSendRequest(WTFMove(request));
 }
 
-void ResourceHandleCurlDelegate::prepareSendData(char* buffer, size_t blockSize, size_t numberOfBlocks)
+void ResourceHandleCurlDelegate::continueAfterWillSendRequest(ResourceRequest&& request)
 {
     ASSERT(isMainThread());
-    ASSERT(!m_sendBytes);
 
-    std::unique_lock<Lock> lock(m_workerThreadMutex);
-
-    if (!m_formDataStream.hasMoreElements())
+    // willSendRequest might cancel the load.
+    if (cancelledOrClientless() || !m_curlRequest)
         return;
 
-    size_t size = m_formDataStream.read(buffer, blockSize, numberOfBlocks);
-    if (!size) {
-        // Something went wrong so cancel the job.
-        m_handle->cancel();
-        return;
-    }
+    m_currentRequest = WTFMove(request);
 
-    m_sendBytes = size;
-    m_workerThreadConditionVariable.notifyOne();
-}
+    bool isSyncRequest = m_curlRequest->isSyncRequest();
+    m_curlRequest->cancel();
+    m_curlRequest->setClient(nullptr);
 
-void ResourceHandleCurlDelegate::didFinish(NetworkLoadMetrics networkLoadMetrics)
-{
-    response().setDeprecatedNetworkLoadMetrics(networkLoadMetrics);
+    m_curlRequest = createCurlRequest(m_currentRequest);
 
-    if (!m_handle)
-        return;
-
-    if (!response().responseFired()) {
-        handleLocalReceiveResponse();
-        if (!m_handle)
-            return;
+    if (protocolHostAndPortAreEqual(m_currentRequest.url(), response().url())) {
+        auto credential = getCredential(m_currentRequest, true);
+        m_curlRequest->setUserPass(credential.first, credential.second);
     }
 
-    if (m_multipartHandle)
-        m_multipartHandle->contentEnded();
-
-    if (m_handle->client()) {
-        CurlCacheManager::getInstance().didFinishLoading(*m_handle);
-        m_handle->client()->didFinishLoading(m_handle);
-    }
+    m_curlRequest->start(isSyncRequest);
 }
 
-void ResourceHandleCurlDelegate::didFail(const ResourceError& resourceError)
+ResourceResponse& ResourceHandleCurlDelegate::response()
 {
-    if (!m_handle)
-        return;
-
-    if (m_handle->client()) {
-        CurlCacheManager::getInstance().didFail(*m_handle);
-        m_handle->client()->didFail(m_handle, resourceError);
-    }
+    return m_handle->getInternal()->m_response;
 }
 
 void ResourceHandleCurlDelegate::handleDataURL()
@@ -481,16 +398,17 @@ void ResourceHandleCurlDelegate::handleDataURL()
 
     ASSERT(m_handle->client());
 
-    int index = url.find(',');
-    if (index == -1) {
+    auto index = url.find(',');
+    if (index == notFound) {
         m_handle->client()->cannotShowURL(m_handle);
         return;
     }
 
     String mediaType = url.substring(5, index - 5);
     String data = url.substring(index + 1);
+    auto originalSize = data.length();
 
-    bool base64 = mediaType.endsWith(";base64", false);
+    bool base64 = mediaType.endsWithIgnoringASCIICase(";base64");
     if (base64)
         mediaType = mediaType.left(mediaType.length() - 7);
 
@@ -510,24 +428,28 @@ void ResourceHandleCurlDelegate::handleDataURL()
 
     if (base64) {
         data = decodeURLEscapeSequences(data);
-        m_handle->client()->didReceiveResponse(m_handle, WTFMove(response));
+        m_handle->didReceiveResponse(WTFMove(response), [this, protectedThis = makeRef(*this)] {
+            continueAfterDidReceiveResponse();
+        });
 
         // didReceiveResponse might cause the client to be deleted.
         if (m_handle->client()) {
             Vector<char> out;
             if (base64Decode(data, out, Base64IgnoreSpacesAndNewLines) && out.size() > 0)
-                m_handle->client()->didReceiveData(m_handle, out.data(), out.size(), 0);
+                m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::create(out.data(), out.size()), originalSize);
         }
     } else {
         TextEncoding encoding(charset);
         data = decodeURLEscapeSequences(data, encoding);
-        m_handle->client()->didReceiveResponse(m_handle, WTFMove(response));
+        m_handle->didReceiveResponse(WTFMove(response), [this, protectedThis = makeRef(*this)] {
+            continueAfterDidReceiveResponse();
+        });
 
         // didReceiveResponse might cause the client to be deleted.
         if (m_handle->client()) {
-            CString encodedData = encoding.encode(data, URLEncodedEntitiesForUnencodables);
-            if (encodedData.length())
-                m_handle->client()->didReceiveData(m_handle, encodedData.data(), encodedData.length(), 0);
+            auto encodedData = encoding.encode(data, UnencodableHandling::URLEncodedEntities);
+            if (encodedData.size())
+                m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::create(WTFMove(encodedData)), originalSize);
         }
     }
 
@@ -535,300 +457,37 @@ void ResourceHandleCurlDelegate::handleDataURL()
         m_handle->client()->didFinishLoading(m_handle);
 }
 
-void ResourceHandleCurlDelegate::setupPOST()
-{
-    m_curlHandle.enableHttpPostRequest();
-
-    size_t numElements = getFormElementsCount();
-    if (!numElements)
-        return;
-
-    // Do not stream for simple POST data
-    if (numElements == 1) {
-        m_postBytes = m_firstRequest.httpBody()->flatten();
-        if (m_postBytes.size())
-            m_curlHandle.setPostFields(m_postBytes.data(), m_postBytes.size());
-        return;
-    }
-
-    setupFormData(true);
-}
-
-void ResourceHandleCurlDelegate::setupPUT()
+std::pair<String, String> ResourceHandleCurlDelegate::getCredential(ResourceRequest& request, bool redirect)
 {
-    m_curlHandle.enableHttpPutRequest();
-
-    // Disable the Expect: 100 continue header
-    m_curlHandle.appendRequestHeader("Expect:");
-
-    size_t numElements = getFormElementsCount();
-    if (!numElements)
-        return;
-
-    setupFormData(false);
-}
-
-size_t ResourceHandleCurlDelegate::getFormElementsCount()
-{
-    RefPtr<FormData> formData = m_firstRequest.httpBody();
-    if (!formData)
-        return 0;
-
-    // Resolve the blob elements so the formData can correctly report it's size.
-    formData = formData->resolveBlobReferences();
-    size_t size = formData->elements().size();
-    m_firstRequest.setHTTPBody(WTFMove(formData));
-    return size;
-}
-
-void ResourceHandleCurlDelegate::setupFormData(bool isPostRequest)
-{
-    Vector<FormDataElement> elements = m_firstRequest.httpBody()->elements();
-    size_t numElements = elements.size();
-
-    static const long long maxCurlOffT = m_curlHandle.maxCurlOffT();
-
-    // Obtain the total size of the form data
-    curl_off_t size = 0;
-    bool chunkedTransfer = false;
-    for (size_t i = 0; i < numElements; i++) {
-        FormDataElement element = elements[i];
-        if (element.m_type == FormDataElement::Type::EncodedFile) {
-            long long fileSizeResult;
-            if (getFileSize(element.m_filename, fileSizeResult)) {
-                if (fileSizeResult > maxCurlOffT) {
-                    // File size is too big for specifying it to cURL
-                    chunkedTransfer = true;
-                    break;
-                }
-                size += fileSizeResult;
-            } else {
-                chunkedTransfer = true;
-                break;
-            }
-        } else
-            size += elements[i].m_data.size();
-    }
+    // m_user/m_pass are credentials given manually, for instance, by the arguments passed to XMLHttpRequest.open().
+    String partition = request.cachePartition();
 
-    // cURL guesses that we want chunked encoding as long as we specify the header
-    if (chunkedTransfer)
-        m_curlHandle.appendRequestHeader("Transfer-Encoding: chunked");
-    else {
-        if (isPostRequest)
-            m_curlHandle.setPostFieldLarge(size);
-        else
-            m_curlHandle.setInFileSizeLarge(size);
+    if (m_shouldUseCredentialStorage) {
+        if (m_user.isEmpty() && m_pass.isEmpty()) {
+            // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 
+            // try and reuse the credential preemptively, as allowed by RFC 2617.
+            m_initialCredential = CredentialStorage::defaultCredentialStorage().get(partition, request.url());
+        } else if (!redirect) {
+            // If there is already a protection space known for the URL, update stored credentials
+            // before sending a request. This makes it possible to implement logout by sending an
+            // XMLHttpRequest with known incorrect credentials, and aborting it immediately (so that
+            // an authentication dialog doesn't pop up).
+            CredentialStorage::defaultCredentialStorage().set(partition, Credential(m_user, m_pass, CredentialPersistenceNone), request.url());
+        }
     }
 
-    m_curlHandle.setReadCallbackFunction(willSendDataCallback, this);
-}
-
-void ResourceHandleCurlDelegate::applyAuthentication()
-{
     String user = m_user;
     String password = m_pass;
 
     if (!m_initialCredential.isEmpty()) {
         user = m_initialCredential.user();
         password = m_initialCredential.password();
-        m_curlHandle.enableHttpAuthentication(CURLAUTH_BASIC);
-    }
-
-    // It seems we need to set CURLOPT_USERPWD even if username and password is empty.
-    // Otherwise cURL will not automatically continue with a new request after a 401 response.
-
-    // curl CURLOPT_USERPWD expects username:password
-    m_curlHandle.setHttpAuthUserPass(user, password);
-}
-
-NetworkLoadMetrics ResourceHandleCurlDelegate::getNetworkLoadMetrics()
-{
-    NetworkLoadMetrics networkLoadMetrics;
-    if (auto metrics = m_curlHandle.getTimes())
-        networkLoadMetrics = *metrics;
-
-    return networkLoadMetrics;
-}
-
-CURLcode ResourceHandleCurlDelegate::willSetupSslCtx(void* sslCtx)
-{
-    m_sslVerifier.setCurlHandle(&m_curlHandle);
-    m_sslVerifier.setHostName(m_firstRequest.url().host());
-    m_sslVerifier.setSslCtx(sslCtx);
-
-    return CURLE_OK;
-}
-
-/*
-* This is being called for each HTTP header in the response. This includes '\r\n'
-* for the last line of the header.
-*
-* We will add each HTTP Header to the ResourceResponse and on the termination
-* of the header (\r\n) we will parse Content-Type and Content-Disposition and
-* update the ResourceResponse and then send it away.
-*
-*/
-size_t ResourceHandleCurlDelegate::didReceiveHeader(String&& header)
-{
-    if (!m_handle)
-        return 0;
-
-    if (m_defersLoading)
-        return 0;
-
-    /*
-    * a) We can finish and send the ResourceResponse
-    * b) We will add the current header to the HTTPHeaderMap of the ResourceResponse
-    *
-    * The HTTP standard requires to use \r\n but for compatibility it recommends to
-    * accept also \n.
-    */
-    if (header == AtomicString("\r\n") || header == AtomicString("\n")) {
-        long httpCode = 0;
-        if (auto code = m_curlHandle.getResponseCode())
-            httpCode = *code;
-
-        if (!httpCode) {
-            // Comes here when receiving 200 Connection Established. Just return.
-            return header.length();
-        }
-        if (isHttpInfo(httpCode)) {
-            // Just return when receiving http info, e.g. HTTP/1.1 100 Continue.
-            // If not, the request might be cancelled, because the MIME type will be empty for this response.
-            return header.length();
-        }
-
-        long long contentLength = 0;
-        if (auto length = m_curlHandle.getContentLenghtDownload())
-            contentLength = *length;
-
-        uint16_t connectPort = 0;
-        if (auto port = m_curlHandle.getPrimaryPort())
-            connectPort = *port;
-
-        long availableAuth = CURLAUTH_NONE;
-        if (auto auth = m_curlHandle.getHttpAuthAvail())
-            availableAuth = *auth;
-
-        if (isMainThread())
-            didReceiveAllHeaders(httpCode, contentLength, connectPort, availableAuth);
-        else {
-            callOnMainThread([protectedThis = makeRef(*this), httpCode, contentLength, connectPort, availableAuth] {
-                if (!protectedThis->m_handle)
-                    return;
-                protectedThis->didReceiveAllHeaders(httpCode, contentLength, connectPort, availableAuth);
-            });
-        }
-    } else {
-        // If the FOLLOWLOCATION option is enabled for the curl handle then
-        // curl will follow the redirections internally. Thus this header callback
-        // will be called more than one time with the line starting "HTTP" for one job.
-        if (isMainThread())
-            response().appendHTTPHeaderField(header);
-        else {
-            callOnMainThread([protectedThis = makeRef(*this), copyHeader = header.isolatedCopy() ] {
-                if (!protectedThis->m_handle)
-                    return;
-
-                protectedThis->response().appendHTTPHeaderField(copyHeader);
-            });
-        }
-    }
-
-    return header.length();
-}
-
-// called with data after all headers have been processed via headerCallback
-size_t ResourceHandleCurlDelegate::didReceiveData(ThreadSafeDataBuffer data)
-{
-    if (!m_handle)
-        return 0;
-
-    if (m_defersLoading)
-        return 0;
-
-    // this shouldn't be necessary but apparently is. CURL writes the data
-    // of html page even if it is a redirect that was handled internally
-    // can be observed e.g. on gmail.com
-    if (auto httpCode = m_curlHandle.getResponseCode()) {
-        if (*httpCode >= 300 && *httpCode < 400)
-            return data.size();
     }
 
-    if (!data.size())
-        return 0;
+    if (user.isEmpty() && password.isEmpty())
+        return std::pair<String, String>("", "");
 
-    if (isMainThread())
-        didReceiveContentData(data);
-    else {
-        callOnMainThread([protectedThis = makeRef(*this), data] {
-            if (!protectedThis->m_handle)
-                return;
-            protectedThis->didReceiveContentData(data);
-        });
-    }
-
-    return data.size();
-}
-
-/* This is called to obtain HTTP POST or PUT data.
-Iterate through FormData elements and upload files.
-Carefully respect the given buffer blockSize and fill the rest of the data at the next calls.
-*/
-size_t ResourceHandleCurlDelegate::willSendData(char* buffer, size_t blockSize, size_t numberOfBlocks)
-{
-    ASSERT(!isMainThread());
-
-    if (!m_handle)
-        return 0;
-
-    if (m_defersLoading)
-        return 0;
-
-    if (!blockSize || !numberOfBlocks)
-        return 0;
-
-    {
-        std::unique_lock<Lock> lock(m_workerThreadMutex);
-
-        m_sendBytes = 0;
-
-        if (isMainThread())
-            prepareSendData(buffer, blockSize, numberOfBlocks);
-        else {
-            callOnMainThread([protectedThis = makeRef(*this), buffer, blockSize, numberOfBlocks] {
-                if (!protectedThis->m_handle)
-                    return;
-                protectedThis->prepareSendData(buffer, blockSize, numberOfBlocks);
-            });
-
-            m_workerThreadConditionVariable.wait(lock, [this] {
-                return m_sendBytes;
-            });
-        }
-    }
-
-    return m_sendBytes;
-}
-
-CURLcode ResourceHandleCurlDelegate::willSetupSslCtxCallback(CURL*, void* sslCtx, void* userData)
-{
-    return static_cast<ResourceHandleCurlDelegate*>(userData)->willSetupSslCtx(sslCtx);
-}
-
-size_t ResourceHandleCurlDelegate::didReceiveHeaderCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* data)
-{
-    return static_cast<ResourceHandleCurlDelegate*>(const_cast<void*>(data))->didReceiveHeader(String(static_cast<const char*>(ptr), blockSize * numberOfBlocks));
-}
-
-size_t ResourceHandleCurlDelegate::didReceiveDataCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* data)
-{
-    return static_cast<ResourceHandleCurlDelegate*>(const_cast<void*>(data))->didReceiveData(ThreadSafeDataBuffer::copyData(static_cast<const char*>(ptr), blockSize * numberOfBlocks));
-}
-
-size_t ResourceHandleCurlDelegate::willSendDataCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* data)
-{
-    return static_cast<ResourceHandleCurlDelegate*>(const_cast<void*>(data))->willSendData(ptr, blockSize, numberOfBlocks);
+    return std::pair<String, String>(user, password);
 }
 
 } // namespace WebCore