ResourceHandleCurl.cpp [plain text]
#include "config.h"
#include "ResourceHandle.h"
#if USE(CURL)
#include "CachedResourceLoader.h"
#include "CredentialStorage.h"
#include "CurlCacheManager.h"
#include "CurlContext.h"
#include "FileSystem.h"
#include "Logging.h"
#include "MIMETypeRegistry.h"
#include "NetworkingContext.h"
#include "NotImplemented.h"
#include "ResourceHandleInternal.h"
#include "ResourceHandleManager.h"
#include "SSLHandle.h"
#include "SynchronousLoaderClient.h"
#include <wtf/text/Base64.h>
namespace WebCore {
ResourceHandleInternal::~ResourceHandleInternal()
{
}
ResourceHandle::~ResourceHandle()
{
cancel();
}
bool ResourceHandle::start()
{
if (d->m_context && !d->m_context->isValid())
return false;
ResourceHandleManager::sharedInstance()->add(this);
return true;
}
void ResourceHandle::cancel()
{
ResourceHandleManager::sharedInstance()->cancel(this);
}
#if OS(WINDOWS)
void ResourceHandle::setHostAllowsAnyHTTPSCertificate(const String& host)
{
allowsAnyHTTPSCertificateHosts(host);
}
void ResourceHandle::setClientCertificateInfo(const String& host, const String& certificate, const String& key)
{
if (fileExists(certificate))
addAllowedClientCertificate(host, certificate, key);
else
LOG(Network, "Invalid client certificate file: %s!\n", certificate.latin1().data());
}
#endif
#if OS(WINDOWS) && USE(CF)
void ResourceHandle::setClientCertificate(const String&, CFDataRef)
{
}
#endif
void ResourceHandle::platformSetDefersLoading(bool defers)
{
if (!d->m_curlHandle.handle())
return;
if (defers) {
CURLcode error = d->m_curlHandle.pause(CURLPAUSE_ALL);
if (error != CURLE_OK)
return;
} else {
CURLcode error = d->m_curlHandle.pause(CURLPAUSE_CONT);
if (error != CURLE_OK)
cancel();
}
}
bool ResourceHandle::shouldUseCredentialStorage()
{
return (!client() || client()->shouldUseCredentialStorage(this)) && firstRequest().url().protocolIsInHTTPFamily();
}
void ResourceHandle::platformLoadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentials, ResourceError& error, ResourceResponse& response, Vector<char>& data)
{
SynchronousLoaderClient client;
RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(context, request, &client, false, false));
handle.get()->dispatchSynchronousJob();
error = client.error();
data.swap(client.mutableData());
response = client.response();
}
void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
{
String partition = firstRequest().cachePartition();
if (!d->m_user.isNull() && !d->m_pass.isNull()) {
Credential credential(d->m_user, d->m_pass, CredentialPersistenceNone);
URL urlToStore;
if (challenge.failureResponse().httpStatusCode() == 401)
urlToStore = challenge.failureResponse().url();
CredentialStorage::defaultCredentialStorage().set(partition, credential, challenge.protectionSpace(), urlToStore);
d->m_curlHandle.setHttpAuthUserPass(credential.user(), credential.password());
d->m_user = String();
d->m_pass = String();
return;
}
if (shouldUseCredentialStorage()) {
if (!d->m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
CredentialStorage::defaultCredentialStorage().remove(partition, challenge.protectionSpace());
}
if (!challenge.previousFailureCount()) {
Credential credential = CredentialStorage::defaultCredentialStorage().get(partition, challenge.protectionSpace());
if (!credential.isEmpty() && credential != d->m_initialCredential) {
ASSERT(credential.persistence() == CredentialPersistenceNone);
if (challenge.failureResponse().httpStatusCode() == 401) {
CredentialStorage::defaultCredentialStorage().set(partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
}
d->m_curlHandle.setHttpAuthUserPass(credential.user(), credential.password());
return;
}
}
}
d->m_currentWebChallenge = challenge;
if (client())
client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge);
}
void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
{
if (challenge != d->m_currentWebChallenge)
return;
if (credential.isEmpty()) {
receivedRequestToContinueWithoutCredential(challenge);
return;
}
String partition = firstRequest().cachePartition();
if (shouldUseCredentialStorage()) {
if (challenge.failureResponse().httpStatusCode() == 401) {
URL urlToStore = challenge.failureResponse().url();
CredentialStorage::defaultCredentialStorage().set(partition, credential, challenge.protectionSpace(), urlToStore);
}
}
d->m_curlHandle.setHttpAuthUserPass(credential.user(), credential.password());
clearAuthentication();
}
void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
{
if (challenge != d->m_currentWebChallenge)
return;
d->m_curlHandle.setHttpAuthUserPass("", "");
clearAuthentication();
}
void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
{
if (challenge != d->m_currentWebChallenge)
return;
if (client())
client()->receivedCancellation(this, challenge);
}
void ResourceHandle::receivedRequestToPerformDefaultHandling(const AuthenticationChallenge&)
{
ASSERT_NOT_REACHED();
}
void ResourceHandle::receivedChallengeRejection(const AuthenticationChallenge&)
{
ASSERT_NOT_REACHED();
}
const char* const errorDomainCurl = "CurlErrorDomain";
#if ENABLE(WEB_TIMING)
static void calculateWebTimingInformations(ResourceHandleInternal* d)
{
double preTransferTime = 0;
double dnslookupTime = 0;
double connectTime = 0;
double appConnectTime = 0;
d->m_curlHandle.getTimes(preTransferTime, dnslookupTime, connectTime, appConnectTime);
d->m_response.deprecatedNetworkLoadMetrics().domainLookupStart = Seconds(0);
d->m_response.deprecatedNetworkLoadMetrics().domainLookupEnd = Seconds(dnslookupTime);
d->m_response.deprecatedNetworkLoadMetrics().connectStart = Seconds(dnslookupTime);
d->m_response.deprecatedNetworkLoadMetrics().connectEnd = Seconds(connectTime);
d->m_response.deprecatedNetworkLoadMetrics().requestStart = Seconds(connectTime);
d->m_response.deprecatedNetworkLoadMetrics().responseStart = Seconds(preTransferTime);
if (appConnectTime)
d->m_response.deprecatedNetworkLoadMetrics().secureConnectionStart = Seconds(connectTime);
}
#endif
static void handleLocalReceiveResponse(ResourceHandle* job, ResourceHandleInternal* d)
{
URL url = d->m_curlHandle.getEffectiveURL();
ASSERT(url.isValid());
d->m_response.setURL(url);
if (d->client())
d->client()->didReceiveResponse(job, ResourceResponse(d->m_response));
d->m_response.setResponseFired(true);
}
static size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* data)
{
ResourceHandle* job = static_cast<ResourceHandle*>(data);
ResourceHandleInternal* d = job->getInternal();
if (d->m_cancelled)
return 0;
ASSERT(!d->m_defersLoading);
size_t totalSize = size * nmemb;
long httpCode = 0;
CURLcode errCd = d->m_curlHandle.getResponseCode(httpCode);
if (CURLE_OK == errCd && httpCode >= 300 && httpCode < 400)
return totalSize;
if (!d->m_response.responseFired()) {
handleLocalReceiveResponse(job, d);
if (d->m_cancelled)
return 0;
}
if (d->m_multipartHandle)
d->m_multipartHandle->contentReceived(static_cast<const char*>(ptr), totalSize);
else if (d->client()) {
d->client()->didReceiveData(job, ptr, totalSize, 0);
CurlCacheManager::getInstance().didReceiveData(*job, ptr, totalSize);
}
return totalSize;
}
inline static bool isHttpInfo(int statusCode)
{
return 100 <= statusCode && statusCode < 200;
}
inline static bool isHttpRedirect(int statusCode)
{
return 300 <= statusCode && statusCode < 400 && statusCode != 304;
}
inline static bool isHttpAuthentication(int statusCode)
{
return statusCode == 401;
}
inline static bool isHttpNotModified(int statusCode)
{
return statusCode == 304;
}
static bool isAppendableHeader(const String &key)
{
static const char* appendableHeaders[] = {
"access-control-allow-headers",
"access-control-allow-methods",
"access-control-allow-origin",
"access-control-expose-headers",
"allow",
"cache-control",
"connection",
"content-encoding",
"content-language",
"if-match",
"if-none-match",
"keep-alive",
"pragma",
"proxy-authenticate",
"public",
"server",
"set-cookie",
"te",
"trailer",
"transfer-encoding",
"upgrade",
"user-agent",
"vary",
"via",
"warning",
"www-authenticate"
};
if (key.startsWith("x-", false))
return true;
for (auto& header : appendableHeaders) {
if (equalIgnoringASCIICase(key, header))
return true;
}
return false;
}
static void removeLeadingAndTrailingQuotes(String& value)
{
unsigned length = value.length();
if (value.startsWith('"') && value.endsWith('"') && length > 1)
value = value.substring(1, length - 2);
}
static bool getProtectionSpace(ResourceHandle* job, const ResourceResponse& response, ProtectionSpace& protectionSpace)
{
ResourceHandleInternal* d = job->getInternal();
CURLcode err;
long port = 0;
err = d->m_curlHandle.getPrimaryPort(port);
if (err != CURLE_OK)
return false;
long availableAuth = CURLAUTH_NONE;
err = d->m_curlHandle.getHttpAuthAvail(availableAuth);
if (err != CURLE_OK)
return false;
URL url = d->m_curlHandle.getEffectiveURL();
if (!url.isValid())
return false;
String host = url.host();
StringView protocol = url.protocol();
String realm;
const String authHeader = response.httpHeaderField(HTTPHeaderName::Authorization);
const String realmString = "realm=";
int realmPos = authHeader.find(realmString);
if (realmPos > 0) {
realm = authHeader.substring(realmPos + realmString.length());
realm = realm.left(realm.find(','));
removeLeadingAndTrailingQuotes(realm);
}
ProtectionSpaceServerType serverType = ProtectionSpaceServerHTTP;
if (protocol == "https")
serverType = ProtectionSpaceServerHTTPS;
ProtectionSpaceAuthenticationScheme authScheme = ProtectionSpaceAuthenticationSchemeUnknown;
if (availableAuth & CURLAUTH_BASIC)
authScheme = ProtectionSpaceAuthenticationSchemeHTTPBasic;
if (availableAuth & CURLAUTH_DIGEST)
authScheme = ProtectionSpaceAuthenticationSchemeHTTPDigest;
if (availableAuth & CURLAUTH_GSSNEGOTIATE)
authScheme = ProtectionSpaceAuthenticationSchemeNegotiate;
if (availableAuth & CURLAUTH_NTLM)
authScheme = ProtectionSpaceAuthenticationSchemeNTLM;
protectionSpace = ProtectionSpace(host, port, serverType, realm, authScheme);
return true;
}
static size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* data)
{
ResourceHandle* job = static_cast<ResourceHandle*>(data);
ResourceHandleInternal* d = job->getInternal();
if (d->m_cancelled)
return 0;
ASSERT(!d->m_defersLoading);
size_t totalSize = size * nmemb;
ResourceHandleClient* client = d->client();
String header(static_cast<const char*>(ptr), totalSize);
if (header == String("\r\n") || header == String("\n")) {
long httpCode = 0;
d->m_curlHandle.getResponseCode(httpCode);
if (!httpCode) {
return totalSize;
}
if (isHttpInfo(httpCode)) {
return totalSize;
}
long long contentLength = 0;
d->m_curlHandle.getContentLenghtDownload(contentLength);
d->m_response.setExpectedContentLength(contentLength);
d->m_response.setURL(d->m_curlHandle.getEffectiveURL());
d->m_response.setHTTPStatusCode(httpCode);
d->m_response.setMimeType(extractMIMETypeFromMediaType(d->m_response.httpHeaderField(HTTPHeaderName::ContentType)).convertToASCIILowercase());
d->m_response.setTextEncodingName(extractCharsetFromMediaType(d->m_response.httpHeaderField(HTTPHeaderName::ContentType)));
if (d->m_response.isMultipart()) {
String boundary;
bool parsed = MultipartHandle::extractBoundary(d->m_response.httpHeaderField(HTTPHeaderName::ContentType), boundary);
if (parsed)
d->m_multipartHandle = std::make_unique<MultipartHandle>(job, boundary);
}
if (isHttpRedirect(httpCode)) {
String location = d->m_response.httpHeaderField(HTTPHeaderName::Location);
if (!location.isEmpty()) {
URL newURL = URL(job->firstRequest().url(), location);
ResourceRequest redirectedRequest = job->firstRequest();
redirectedRequest.setURL(newURL);
ResourceResponse response = d->m_response;
if (client)
client->willSendRequest(job, WTFMove(redirectedRequest), WTFMove(response));
d->m_firstRequest.setURL(newURL);
return totalSize;
}
} else if (isHttpAuthentication(httpCode)) {
ProtectionSpace protectionSpace;
if (getProtectionSpace(job, d->m_response, protectionSpace)) {
Credential credential;
AuthenticationChallenge challenge(protectionSpace, credential, d->m_authFailureCount, d->m_response, ResourceError());
challenge.setAuthenticationClient(job);
job->didReceiveAuthenticationChallenge(challenge);
d->m_authFailureCount++;
return totalSize;
}
}
if (client) {
if (isHttpNotModified(httpCode)) {
const String& url = job->firstRequest().url().string();
if (CurlCacheManager::getInstance().getCachedResponse(url, d->m_response)) {
if (d->m_addedCacheValidationHeaders) {
d->m_response.setHTTPStatusCode(200);
d->m_response.setHTTPStatusText("OK");
}
}
}
client->didReceiveResponse(job, ResourceResponse(d->m_response));
CurlCacheManager::getInstance().didReceiveResponse(*job, d->m_response);
}
d->m_response.setResponseFired(true);
} else {
int splitPos = header.find(":");
if (splitPos != -1) {
String key = header.left(splitPos).stripWhiteSpace();
String value = header.substring(splitPos + 1).stripWhiteSpace();
if (isAppendableHeader(key))
d->m_response.addHTTPHeaderField(key, value);
else
d->m_response.setHTTPHeaderField(key, value);
} else if (header.startsWith("HTTP", false)) {
long httpCode = 0;
d->m_curlHandle.getResponseCode(httpCode);
String httpCodeString = String::number(httpCode);
int statusCodePos = header.find(httpCodeString);
if (statusCodePos != -1) {
String status = header.substring(statusCodePos + httpCodeString.length());
d->m_response.setHTTPStatusText(status.stripWhiteSpace());
}
}
}
return totalSize;
}
size_t readCallback(char* ptr, size_t size, size_t nmemb, void* data)
{
ResourceHandle* job = static_cast<ResourceHandle*>(data);
ResourceHandleInternal* d = job->getInternal();
if (d->m_cancelled)
return 0;
ASSERT(!d->m_defersLoading);
if (!size || !nmemb)
return 0;
if (!d->m_formDataStream.hasMoreElements())
return 0;
size_t sent = d->m_formDataStream.read(ptr, size, nmemb);
if (!sent)
job->cancel();
return sent;
}
static inline size_t getFormElementsCount(ResourceHandle* job)
{
RefPtr<FormData> formData = job->firstRequest().httpBody();
if (!formData)
return 0;
formData = formData->resolveBlobReferences();
size_t size = formData->elements().size();
job->firstRequest().setHTTPBody(WTFMove(formData));
return size;
}
static void setupFormData(ResourceHandle* job, bool isPostRequest)
{
ResourceHandleInternal* d = job->getInternal();
Vector<FormDataElement> elements = job->firstRequest().httpBody()->elements();
size_t numElements = elements.size();
static const long long maxCurlOffT = d->m_curlHandle.maxCurlOffT();
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) {
chunkedTransfer = true;
break;
}
size += fileSizeResult;
} else {
chunkedTransfer = true;
break;
}
} else
size += elements[i].m_data.size();
}
if (chunkedTransfer)
d->m_curlHandle.appendRequestHeader("Transfer-Encoding: chunked");
else {
if (isPostRequest)
d->m_curlHandle.setPostFieldLarge(size);
else
d->m_curlHandle.setInFileSizeLarge(size);
}
d->m_curlHandle.setReadCallbackFunction(readCallback, job);
}
void ResourceHandle::setupPUT()
{
ResourceHandleInternal* d = getInternal();
d->m_curlHandle.enableHttpPutRequest();
d->m_curlHandle.appendRequestHeader("Expect:");
size_t numElements = getFormElementsCount(this);
if (!numElements)
return;
setupFormData(this, false);
}
void ResourceHandle::setupPOST()
{
ResourceHandleInternal* d = getInternal();
d->m_curlHandle.enableHttpPostRequest();
size_t numElements = getFormElementsCount(this);
if (!numElements)
return;
if (numElements == 1) {
firstRequest().httpBody()->flatten(d->m_postBytes);
if (d->m_postBytes.size())
d->m_curlHandle.setPostFields(d->m_postBytes.data(), d->m_postBytes.size());
return;
}
setupFormData(this, true);
}
void ResourceHandle::handleDataURL()
{
ASSERT(firstRequest().url().protocolIsData());
String url = firstRequest().url().string();
ASSERT(client());
int index = url.find(',');
if (index == -1) {
client()->cannotShowURL(this);
return;
}
String mediaType = url.substring(5, index - 5);
String data = url.substring(index + 1);
bool base64 = mediaType.endsWith(";base64", false);
if (base64)
mediaType = mediaType.left(mediaType.length() - 7);
if (mediaType.isEmpty())
mediaType = "text/plain";
String mimeType = extractMIMETypeFromMediaType(mediaType);
String charset = extractCharsetFromMediaType(mediaType);
if (charset.isEmpty())
charset = "US-ASCII";
ResourceResponse response;
response.setMimeType(mimeType);
response.setTextEncodingName(charset);
response.setURL(firstRequest().url());
if (base64) {
data = decodeURLEscapeSequences(data);
client()->didReceiveResponse(this, WTFMove(response));
if (client()) {
Vector<char> out;
if (base64Decode(data, out, Base64IgnoreSpacesAndNewLines) && out.size() > 0)
client()->didReceiveData(this, out.data(), out.size(), 0);
}
} else {
TextEncoding encoding(charset);
data = decodeURLEscapeSequences(data, encoding);
client()->didReceiveResponse(this, WTFMove(response));
if (client()) {
CString encodedData = encoding.encode(data, URLEncodedEntitiesForUnencodables);
if (encodedData.length())
client()->didReceiveData(this, encodedData.data(), encodedData.length(), 0);
}
}
if (client())
client()->didFinishLoading(this);
}
void ResourceHandle::dispatchSynchronousJob()
{
URL kurl = firstRequest().url();
if (kurl.protocolIsData()) {
handleDataURL();
return;
}
ResourceHandleInternal* d = getInternal();
d->m_defersLoading = false;
initialize();
CURLcode ret = d->m_curlHandle.perform();
if (ret != CURLE_OK) {
ResourceError error(ASCIILiteral(errorDomainCurl), ret, kurl, String(curl_easy_strerror(ret)));
error.setSSLErrors(d->m_sslErrors);
d->client()->didFail(this, error);
} else {
if (d->client())
d->client()->didReceiveResponse(this, ResourceResponse(d->m_response));
}
#if ENABLE(WEB_TIMING)
calculateWebTimingInformations(d);
#endif
}
void ResourceHandle::applyAuthentication()
{
ResourceRequest& request = firstRequest();
ResourceHandleInternal* d = getInternal();
String partition = request.cachePartition();
if (shouldUseCredentialStorage()) {
if (d->m_user.isEmpty() && d->m_pass.isEmpty()) {
d->m_initialCredential = CredentialStorage::defaultCredentialStorage().get(partition, request.url());
} else {
CredentialStorage::defaultCredentialStorage().set(partition, Credential(d->m_user, d->m_pass, CredentialPersistenceNone), request.url());
}
}
String user = d->m_user;
String password = d->m_pass;
if (!d->m_initialCredential.isEmpty()) {
user = d->m_initialCredential.user();
password = d->m_initialCredential.password();
d->m_curlHandle.enableHttpAuthentication(CURLAUTH_BASIC);
}
d->m_curlHandle.setHttpAuthUserPass(user, password);
}
void ResourceHandle::initialize()
{
CurlContext& context = CurlContext::singleton();
URL url = firstRequest().url();
url.removeFragmentIdentifier();
ResourceHandleInternal* d = getInternal();
String urlString = url.string();
if (url.isLocalFile()) {
if (!url.query().isEmpty()) {
url.setQuery(String());
urlString = url.string();
}
d->m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(url));
}
if (d->m_defersLoading) {
CURLcode error = d->m_curlHandle.pause(CURLPAUSE_ALL);
ASSERT_UNUSED(error, error == CURLE_OK);
}
#ifndef NDEBUG
d->m_curlHandle.enableVerboseIfUsed();
d->m_curlHandle.enableStdErrIfUsed();
#endif
d->m_curlHandle.setSslVerifyPeer(CurlHandle::VerifyPeerEnable);
d->m_curlHandle.setSslVerifyHost(CurlHandle::VerifyHostStrictNameCheck);
d->m_curlHandle.setPrivateData(this);
d->m_curlHandle.setWriteCallbackFunction(writeCallback, this);
d->m_curlHandle.setHeaderCallbackFunction(headerCallback, this);
d->m_curlHandle.enableAutoReferer();
d->m_curlHandle.enableFollowLocation();
d->m_curlHandle.enableHttpAuthentication(CURLAUTH_ANY);
d->m_curlHandle.enableShareHandle();
d->m_curlHandle.enableTimeout();
d->m_curlHandle.enableAllowedProtocols();
setSSLClientCertificate(this);
if (CurlContext::singleton().shouldIgnoreSSLErrors())
d->m_curlHandle.setSslVerifyPeer(CurlHandle::VerifyPeerDisable);
else
setSSLVerifyOptions(this);
d->m_curlHandle.enableCAInfoIfExists();
d->m_curlHandle.enableAcceptEncoding();
d->m_curlHandle.setUrl(urlString);
d->m_curlHandle.enableCookieJarIfExists();
d->m_curlHandle.clearRequestHeaders();
if (firstRequest().httpHeaderFields().size() > 0) {
HTTPHeaderMap customHeaders = firstRequest().httpHeaderFields();
bool hasCacheHeaders = customHeaders.contains(HTTPHeaderName::IfModifiedSince) || customHeaders.contains(HTTPHeaderName::IfNoneMatch);
if (!hasCacheHeaders && CurlCacheManager::getInstance().isCached(url)) {
CurlCacheManager::getInstance().addCacheEntryClient(url, this);
HTTPHeaderMap& requestHeaders = CurlCacheManager::getInstance().requestHeaders(url);
HTTPHeaderMap::const_iterator it = requestHeaders.begin();
HTTPHeaderMap::const_iterator end = requestHeaders.end();
while (it != end) {
customHeaders.set(it->key, it->value);
++it;
}
d->m_addedCacheValidationHeaders = true;
}
for (auto customHeader : customHeaders)
d->m_curlHandle.appendRequestHeader(customHeader.key, customHeader.value);
}
String method = firstRequest().httpMethod();
if ("GET" == method)
d->m_curlHandle.enableHttpGetRequest();
else if ("POST" == method)
setupPOST();
else if ("PUT" == method)
setupPUT();
else if ("HEAD" == method)
d->m_curlHandle.enableHttpHeadRequest();
else {
d->m_curlHandle.setHttpCustomRequest(method);
setupPUT();
}
d->m_curlHandle.enableRequestHeaders();
applyAuthentication();
d->m_curlHandle.enableProxyIfExists();
}
void ResourceHandle::handleCurlMsg(CURLMsg* msg)
{
ResourceHandleInternal* d = getInternal();
if (CURLE_OK == msg->data.result) {
#if ENABLE(WEB_TIMING)
calculateWebTimingInformations(d);
#endif
if (!d->m_response.responseFired()) {
handleLocalReceiveResponse(this, d);
if (d->m_cancelled)
return;
}
if (d->m_multipartHandle)
d->m_multipartHandle->contentEnded();
if (d->client()) {
d->client()->didFinishLoading(this);
CurlCacheManager::getInstance().didFinishLoading(*this);
}
} else {
URL url = d->m_curlHandle.getEffectiveURL();
#ifndef NDEBUG
fprintf(stderr, "Curl ERROR for url='%s', error: '%s'\n", url.string().utf8().data(), curl_easy_strerror(msg->data.result));
#endif
if (d->client()) {
ResourceError resourceError(ASCIILiteral(errorDomainCurl), msg->data.result, url, String(curl_easy_strerror(msg->data.result)));
resourceError.setSSLErrors(d->m_sslErrors);
d->client()->didFail(this, resourceError);
CurlCacheManager::getInstance().didFail(*this);
}
}
}
}
#endif