#include "config.h"
#include "CurlRequest.h"
#if USE(CURL)
#include "CurlRequestClient.h"
#include "CurlRequestScheduler.h"
#include "MIMETypeRegistry.h"
#include "ResourceError.h"
#include "SharedBuffer.h"
#include <wtf/MainThread.h>
namespace WebCore {
CurlRequest::CurlRequest(const ResourceRequest&request, CurlRequestClient* client, bool shouldSuspend)
: m_request(request.isolatedCopy())
, m_shouldSuspend(shouldSuspend)
{
ASSERT(isMainThread());
setClient(client);
resolveBlobReferences(m_request);
}
void CurlRequest::setUserPass(const String& user, const String& password)
{
ASSERT(isMainThread());
m_user = user.isolatedCopy();
m_password = password.isolatedCopy();
}
void CurlRequest::start(bool isSyncRequest)
{
ASSERT(isMainThread());
m_isSyncRequest = isSyncRequest;
auto url = m_request.url().isolatedCopy();
if (!m_isSyncRequest) {
if (url.isLocalFile())
invokeDidReceiveResponseForFile(url);
else
startWithJobManager();
} else {
retain();
if (url.isLocalFile())
invokeDidReceiveResponseForFile(url);
setupTransfer();
CURLcode resultCode = m_curlHandle->perform();
didCompleteTransfer(resultCode);
release();
}
}
void CurlRequest::startWithJobManager()
{
ASSERT(isMainThread());
CurlRequestScheduler::singleton().add(this);
}
void CurlRequest::cancel()
{
ASSERT(isMainThread());
if (isCompletedOrCancelled())
return;
m_cancelled = true;
if (!m_isSyncRequest) {
auto& scheduler = CurlRequestScheduler::singleton();
if (needToInvokeDidCancelTransfer()) {
scheduler.callOnWorkerThread([protectedThis = makeRef(*this)]() {
protectedThis->didCancelTransfer();
});
} else
scheduler.cancel(this);
} else {
if (needToInvokeDidCancelTransfer())
didCancelTransfer();
}
setRequestPaused(false);
setCallbackPaused(false);
}
void CurlRequest::suspend()
{
ASSERT(isMainThread());
setRequestPaused(true);
}
void CurlRequest::resume()
{
ASSERT(isMainThread());
setRequestPaused(false);
}
void CurlRequest::callClient(WTF::Function<void(CurlRequestClient*)> task)
{
if (isMainThread()) {
if (CurlRequestClient* client = m_client)
task(client);
} else {
callOnMainThread([protectedThis = makeRef(*this), task = WTFMove(task)]() mutable {
if (CurlRequestClient* client = protectedThis->m_client)
task(client);
});
}
}
CURL* CurlRequest::setupTransfer()
{
auto& sslHandle = CurlContext::singleton().sslHandle();
m_curlHandle = std::make_unique<CurlHandle>();
m_curlHandle->initialize();
m_curlHandle->setUrl(m_request.url());
m_curlHandle->appendRequestHeaders(m_request.httpHeaderFields());
const auto& method = m_request.httpMethod();
if (method == "GET")
m_curlHandle->enableHttpGetRequest();
else if (method == "POST")
setupPOST(m_request);
else if (method == "PUT")
setupPUT(m_request);
else if (method == "HEAD")
m_curlHandle->enableHttpHeadRequest();
else {
m_curlHandle->setHttpCustomRequest(method);
setupPUT(m_request);
}
if (!m_user.isEmpty() || !m_password.isEmpty()) {
m_curlHandle->enableHttpAuthentication(CURLAUTH_ANY);
m_curlHandle->setHttpAuthUserPass(m_user, m_password);
}
m_curlHandle->setHeaderCallbackFunction(didReceiveHeaderCallback, this);
m_curlHandle->setWriteCallbackFunction(didReceiveDataCallback, this);
m_curlHandle->enableShareHandle();
m_curlHandle->enableAllowedProtocols();
m_curlHandle->enableAcceptEncoding();
m_curlHandle->enableTimeout();
m_curlHandle->enableProxyIfExists();
m_curlHandle->enableCookieJarIfExists();
m_curlHandle->setSslVerifyPeer(CurlHandle::VerifyPeer::Enable);
m_curlHandle->setSslVerifyHost(CurlHandle::VerifyHost::StrictNameCheck);
auto sslClientCertificate = sslHandle.getSSLClientCertificate(m_request.url().host());
if (sslClientCertificate) {
m_curlHandle->setSslCert(sslClientCertificate->first.utf8().data());
m_curlHandle->setSslCertType("P12");
m_curlHandle->setSslKeyPassword(sslClientCertificate->second.utf8().data());
}
if (sslHandle.shouldIgnoreSSLErrors())
m_curlHandle->setSslVerifyPeer(CurlHandle::VerifyPeer::Disable);
else
m_curlHandle->setSslCtxCallbackFunction(willSetupSslCtxCallback, this);
m_curlHandle->setCACertPath(sslHandle.getCACertPath());
if (m_shouldSuspend)
suspend();
#ifndef NDEBUG
m_curlHandle->enableVerboseIfUsed();
m_curlHandle->enableStdErrIfUsed();
#endif
return m_curlHandle->handle();
}
CURLcode CurlRequest::willSetupSslCtx(void* sslCtx)
{
m_sslVerifier.setCurlHandle(m_curlHandle.get());
m_sslVerifier.setHostName(m_request.url().host());
m_sslVerifier.setSslCtx(sslCtx);
return CURLE_OK;
}
size_t CurlRequest::willSendData(char* ptr, size_t blockSize, size_t numberOfBlocks)
{
if (isCompletedOrCancelled())
return CURL_READFUNC_ABORT;
if (!blockSize || !numberOfBlocks)
return 0;
if (!m_formDataStream || !m_formDataStream->hasMoreElements())
return 0;
auto sendBytes = m_formDataStream->read(ptr, blockSize, numberOfBlocks);
if (!sendBytes) {
return CURL_READFUNC_ABORT;
}
return sendBytes;
}
size_t CurlRequest::didReceiveHeader(String&& header)
{
static const auto emptyLineCRLF = "\r\n";
static const auto emptyLineLF = "\n";
if (isCompletedOrCancelled())
return 0;
if (m_didReceiveResponse) {
m_didReceiveResponse = false;
m_response = CurlResponse { };
}
auto receiveBytes = static_cast<size_t>(header.length());
if ((header != emptyLineCRLF) && (header != emptyLineLF)) {
m_response.headers.append(WTFMove(header));
return receiveBytes;
}
long statusCode = 0;
if (auto code = m_curlHandle->getResponseCode())
statusCode = *code;
long httpConnectCode = 0;
if (auto code = m_curlHandle->getHttpConnectCode())
httpConnectCode = *code;
m_didReceiveResponse = true;
m_response.url = m_request.url();
m_response.statusCode = statusCode;
if (auto length = m_curlHandle->getContentLength())
m_response.expectedContentLength = *length;
if (auto port = m_curlHandle->getPrimaryPort())
m_response.connectPort = *port;
if (auto auth = m_curlHandle->getHttpAuthAvail())
m_response.availableHttpAuth = *auth;
if (auto metrics = m_curlHandle->getNetworkLoadMetrics())
m_networkLoadMetrics = *metrics;
return receiveBytes;
}
size_t CurlRequest::didReceiveData(Ref<SharedBuffer>&& buffer)
{
if (isCompletedOrCancelled())
return 0;
if (needToInvokeDidReceiveResponse()) {
if (!m_isSyncRequest) {
setCallbackPaused(true);
invokeDidReceiveResponse(Action::ReceiveData);
return CURL_WRITEFUNC_PAUSE;
}
invokeDidReceiveResponse(Action::None);
}
auto receiveBytes = buffer->size();
writeDataToDownloadFileIfEnabled(buffer);
if (receiveBytes) {
callClient([this, buffer = WTFMove(buffer)](CurlRequestClient* client) mutable {
if (client)
client->curlDidReceiveBuffer(WTFMove(buffer));
});
}
return receiveBytes;
}
void CurlRequest::didCompleteTransfer(CURLcode result)
{
if (m_cancelled) {
m_curlHandle = nullptr;
return;
}
if (result == CURLE_OK) {
if (needToInvokeDidReceiveResponse()) {
m_finishedResultCode = result;
invokeDidReceiveResponse(Action::FinishTransfer);
} else {
if (auto metrics = m_curlHandle->getNetworkLoadMetrics())
m_networkLoadMetrics = *metrics;
finalizeTransfer();
callClient([this](CurlRequestClient* client) {
if (client)
client->curlDidComplete();
});
}
} else {
auto resourceError = ResourceError::httpError(result, m_request.url());
if (m_sslVerifier.sslErrors())
resourceError.setSslErrors(m_sslVerifier.sslErrors());
finalizeTransfer();
callClient([this, error = resourceError.isolatedCopy()](CurlRequestClient* client) {
if (client)
client->curlDidFailWithError(error);
});
}
}
void CurlRequest::didCancelTransfer()
{
finalizeTransfer();
cleanupDownloadFile();
}
void CurlRequest::finalizeTransfer()
{
m_formDataStream = nullptr;
closeDownloadFile();
m_curlHandle = nullptr;
}
void CurlRequest::resolveBlobReferences(ResourceRequest& request)
{
ASSERT(isMainThread());
auto body = request.httpBody();
if (!body || body->isEmpty())
return;
RefPtr<FormData> formData = body->resolveBlobReferences();
request.setHTTPBody(WTFMove(formData));
}
void CurlRequest::setupPUT(ResourceRequest& request)
{
m_curlHandle->enableHttpPutRequest();
m_curlHandle->removeRequestHeader("Expect");
auto body = request.httpBody();
if (!body || body->isEmpty())
return;
setupFormData(request, false);
}
void CurlRequest::setupPOST(ResourceRequest& request)
{
m_curlHandle->enableHttpPostRequest();
auto body = request.httpBody();
if (!body || body->isEmpty())
return;
auto numElements = body->elements().size();
if (!numElements)
return;
if (numElements == 1) {
m_postBuffer = body->flatten();
if (m_postBuffer.size())
m_curlHandle->setPostFields(m_postBuffer.data(), m_postBuffer.size());
} else
setupFormData(request, true);
}
void CurlRequest::setupFormData(ResourceRequest& request, bool isPostRequest)
{
static auto maxCurlOffT = CurlHandle::maxCurlOffT();
curl_off_t size = 0;
bool chunkedTransfer = false;
auto elements = request.httpBody()->elements();
for (auto element : elements) {
if (element.m_type == FormDataElement::Type::EncodedFile) {
long long fileSizeResult;
if (FileSystem::getFileSize(element.m_filename, fileSizeResult)) {
if (fileSizeResult > maxCurlOffT) {
chunkedTransfer = true;
break;
}
size += fileSizeResult;
} else {
chunkedTransfer = true;
break;
}
} else
size += element.m_data.size();
}
if (chunkedTransfer)
m_curlHandle->appendRequestHeader("Transfer-Encoding: chunked");
else {
if (isPostRequest)
m_curlHandle->setPostFieldLarge(size);
else
m_curlHandle->setInFileSizeLarge(size);
}
m_formDataStream = std::make_unique<FormDataStream>();
m_formDataStream->setHTTPBody(request.httpBody());
m_curlHandle->setReadCallbackFunction(willSendDataCallback, this);
}
void CurlRequest::invokeDidReceiveResponseForFile(URL& url)
{
ASSERT(isMainThread());
ASSERT(url.isLocalFile());
m_response.url = url;
m_response.statusCode = 200;
m_response.headers.append(String("Content-Type: " + MIMETypeRegistry::getMIMETypeForPath(m_response.url.path())));
if (!m_isSyncRequest) {
CurlRequestScheduler::singleton().callOnWorkerThread([protectedThis = makeRef(*this)]() {
protectedThis->invokeDidReceiveResponse(Action::StartTransfer);
});
} else {
invokeDidReceiveResponse(Action::None);
}
}
void CurlRequest::invokeDidReceiveResponse(Action behaviorAfterInvoke)
{
ASSERT(!m_didNotifyResponse);
m_didNotifyResponse = true;
m_actionAfterInvoke = behaviorAfterInvoke;
callClient([this, response = m_response.isolatedCopy()](CurlRequestClient* client) {
if (client)
client->curlDidReceiveResponse(response);
});
}
void CurlRequest::completeDidReceiveResponse()
{
ASSERT(isMainThread());
ASSERT(m_didNotifyResponse);
ASSERT(!m_didReturnFromNotify);
if (isCancelled())
return;
if (m_actionAfterInvoke != Action::StartTransfer && isCompleted())
return;
m_didReturnFromNotify = true;
if (m_actionAfterInvoke == Action::ReceiveData) {
setCallbackPaused(false);
} else if (m_actionAfterInvoke == Action::StartTransfer) {
startWithJobManager();
} else if (m_actionAfterInvoke == Action::FinishTransfer) {
if (!m_isSyncRequest) {
CurlRequestScheduler::singleton().callOnWorkerThread([protectedThis = makeRef(*this), finishedResultCode = m_finishedResultCode]() {
protectedThis->didCompleteTransfer(finishedResultCode);
});
} else
didCompleteTransfer(m_finishedResultCode);
}
}
void CurlRequest::setRequestPaused(bool paused)
{
auto wasPaused = isPaused();
m_isPausedOfRequest = paused;
if (isPaused() == wasPaused)
return;
pausedStatusChanged();
}
void CurlRequest::setCallbackPaused(bool paused)
{
auto wasPaused = isPaused();
m_isPausedOfCallback = paused;
if (isPaused() == wasPaused)
return;
if (paused)
return;
pausedStatusChanged();
}
void CurlRequest::pausedStatusChanged()
{
if (isCompletedOrCancelled())
return;
if (!m_isSyncRequest && isMainThread()) {
CurlRequestScheduler::singleton().callOnWorkerThread([protectedThis = makeRef(*this), paused = isPaused()]() {
if (protectedThis->isCompletedOrCancelled())
return;
auto error = protectedThis->m_curlHandle->pause(paused ? CURLPAUSE_ALL : CURLPAUSE_CONT);
if ((error != CURLE_OK) && !paused) {
callOnMainThread([protectedThis = makeRef(protectedThis.get())]() {
protectedThis->cancel();
});
}
});
} else {
auto error = m_curlHandle->pause(isPaused() ? CURLPAUSE_ALL : CURLPAUSE_CONT);
if ((error != CURLE_OK) && !isPaused())
cancel();
}
}
void CurlRequest::enableDownloadToFile()
{
LockHolder locker(m_downloadMutex);
m_isEnabledDownloadToFile = true;
}
const String& CurlRequest::getDownloadedFilePath()
{
LockHolder locker(m_downloadMutex);
return m_downloadFilePath;
}
void CurlRequest::writeDataToDownloadFileIfEnabled(const SharedBuffer& buffer)
{
{
LockHolder locker(m_downloadMutex);
if (!m_isEnabledDownloadToFile)
return;
if (m_downloadFilePath.isEmpty())
m_downloadFilePath = FileSystem::openTemporaryFile("download", m_downloadFileHandle);
}
if (m_downloadFileHandle != FileSystem::invalidPlatformFileHandle)
FileSystem::writeToFile(m_downloadFileHandle, buffer.data(), buffer.size());
}
void CurlRequest::closeDownloadFile()
{
LockHolder locker(m_downloadMutex);
if (m_downloadFileHandle == FileSystem::invalidPlatformFileHandle)
return;
FileSystem::closeFile(m_downloadFileHandle);
m_downloadFileHandle = FileSystem::invalidPlatformFileHandle;
}
void CurlRequest::cleanupDownloadFile()
{
LockHolder locker(m_downloadMutex);
if (!m_downloadFilePath.isEmpty()) {
FileSystem::deleteFile(m_downloadFilePath);
m_downloadFilePath = String();
}
}
CURLcode CurlRequest::willSetupSslCtxCallback(CURL*, void* sslCtx, void* userData)
{
return static_cast<CurlRequest*>(userData)->willSetupSslCtx(sslCtx);
}
size_t CurlRequest::willSendDataCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* userData)
{
return static_cast<CurlRequest*>(userData)->willSendData(ptr, blockSize, numberOfBlocks);
}
size_t CurlRequest::didReceiveHeaderCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* userData)
{
return static_cast<CurlRequest*>(userData)->didReceiveHeader(String(ptr, blockSize * numberOfBlocks));
}
size_t CurlRequest::didReceiveDataCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* userData)
{
return static_cast<CurlRequest*>(userData)->didReceiveData(SharedBuffer::create(ptr, blockSize * numberOfBlocks));
}
}
#endif