| /* |
| * Copyright (C) 2013 Apple Inc. All rights reserved. |
| * Copyright (C) 2018 Sony Interactive Entertainment Inc. |
| * |
| * 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 "CurlContext.h" |
| |
| #if USE(CURL) |
| #include "CertificateInfo.h" |
| #include "CurlRequestScheduler.h" |
| #include "CurlSSLHandle.h" |
| #include "CurlSSLVerifier.h" |
| #include "HTTPHeaderMap.h" |
| #include <NetworkLoadMetrics.h> |
| #include <mutex> |
| #include <wtf/MainThread.h> |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/text/CString.h> |
| |
| #if OS(WINDOWS) |
| #include "WebCoreBundleWin.h" |
| #include <shlobj.h> |
| #include <shlwapi.h> |
| #endif |
| |
| namespace WebCore { |
| |
| class EnvironmentVariableReader { |
| public: |
| const char* read(const char* name) { return ::getenv(name); } |
| bool defined(const char* name) { return read(name) != nullptr; } |
| |
| template<typename T> Optional<T> readAs(const char* name) |
| { |
| if (const char* valueStr = read(name)) { |
| T value; |
| if (sscanf(valueStr, sscanTemplate<T>(), &value) == 1) |
| return value; |
| } |
| |
| return WTF::nullopt; |
| } |
| |
| private: |
| template<typename T> const char* sscanTemplate() |
| { |
| ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| }; |
| |
| template<> |
| constexpr const char* EnvironmentVariableReader::sscanTemplate<signed>() { return "%d"; } |
| |
| template<> |
| constexpr const char* EnvironmentVariableReader::sscanTemplate<unsigned>() { return "%u"; } |
| |
| // ALPN Protocol ID (RFC7301) https://tools.ietf.org/html/rfc7301 |
| static const ASCIILiteral httpVersion10 { "http/1.0"_s }; |
| static const ASCIILiteral httpVersion11 { "http/1.1"_s }; |
| static const ASCIILiteral httpVersion2 { "h2"_s }; |
| |
| // CurlContext ------------------------------------------------------------------- |
| |
| CurlContext& CurlContext::singleton() |
| { |
| static NeverDestroyed<CurlContext> sharedInstance; |
| return sharedInstance; |
| } |
| |
| CurlContext::CurlContext() |
| { |
| initShareHandle(); |
| |
| EnvironmentVariableReader envVar; |
| |
| if (auto value = envVar.readAs<unsigned>("WEBKIT_CURL_DNS_CACHE_TIMEOUT")) |
| m_dnsCacheTimeout = Seconds(*value); |
| |
| if (auto value = envVar.readAs<unsigned>("WEBKIT_CURL_CONNECT_TIMEOUT")) |
| m_connectTimeout = Seconds(*value); |
| |
| long maxConnects { CurlDefaultMaxConnects }; |
| long maxTotalConnections { CurlDefaultMaxTotalConnections }; |
| long maxHostConnections { CurlDefaultMaxHostConnections }; |
| |
| if (auto value = envVar.readAs<signed>("WEBKIT_CURL_MAXCONNECTS")) |
| maxConnects = *value; |
| |
| if (auto value = envVar.readAs<signed>("WEBKIT_CURL_MAX_TOTAL_CONNECTIONS")) |
| maxTotalConnections = *value; |
| |
| if (auto value = envVar.readAs<signed>("WEBKIT_CURL_MAX_HOST_CONNECTIONS")) |
| maxHostConnections = *value; |
| |
| m_scheduler = makeUnique<CurlRequestScheduler>(maxConnects, maxTotalConnections, maxHostConnections); |
| |
| #ifndef NDEBUG |
| m_verbose = envVar.defined("DEBUG_CURL"); |
| |
| auto logFile = envVar.read("CURL_LOG_FILE"); |
| if (logFile) |
| m_logFile = fopen(logFile, "a"); |
| #endif |
| } |
| |
| CurlContext::~CurlContext() |
| { |
| #ifndef NDEBUG |
| if (m_logFile) |
| fclose(m_logFile); |
| #endif |
| } |
| |
| void CurlContext::initShareHandle() |
| { |
| CURL* curl = curl_easy_init(); |
| |
| if (!curl) |
| return; |
| |
| curl_easy_setopt(curl, CURLOPT_SHARE, m_shareHandle.handle()); |
| |
| curl_easy_cleanup(curl); |
| } |
| |
| bool CurlContext::isHttp2Enabled() const |
| { |
| curl_version_info_data* data = curl_version_info(CURLVERSION_NOW); |
| return data->features & CURL_VERSION_HTTP2; |
| } |
| |
| // CurlShareHandle -------------------------------------------- |
| |
| CurlShareHandle::CurlShareHandle() |
| { |
| m_shareHandle = curl_share_init(); |
| curl_share_setopt(m_shareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); |
| curl_share_setopt(m_shareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); |
| curl_share_setopt(m_shareHandle, CURLSHOPT_LOCKFUNC, lockCallback); |
| curl_share_setopt(m_shareHandle, CURLSHOPT_UNLOCKFUNC, unlockCallback); |
| } |
| |
| CurlShareHandle::~CurlShareHandle() |
| { |
| if (m_shareHandle) |
| curl_share_cleanup(m_shareHandle); |
| } |
| |
| void CurlShareHandle::lockCallback(CURL*, curl_lock_data data, curl_lock_access, void*) |
| { |
| if (auto* mutex = mutexFor(data)) |
| mutex->lock(); |
| } |
| |
| void CurlShareHandle::unlockCallback(CURL*, curl_lock_data data, void*) |
| { |
| if (auto* mutex = mutexFor(data)) |
| mutex->unlock(); |
| } |
| |
| Lock* CurlShareHandle::mutexFor(curl_lock_data data) |
| { |
| static Lock cookieMutex; |
| static Lock dnsMutex; |
| static Lock shareMutex; |
| |
| switch (data) { |
| case CURL_LOCK_DATA_COOKIE: |
| return &cookieMutex; |
| case CURL_LOCK_DATA_DNS: |
| return &dnsMutex; |
| case CURL_LOCK_DATA_SHARE: |
| return &shareMutex; |
| default: |
| ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| } |
| |
| // CurlMultiHandle -------------------------------------------- |
| |
| CurlMultiHandle::CurlMultiHandle() |
| { |
| m_multiHandle = curl_multi_init(); |
| |
| if (CurlContext::singleton().isHttp2Enabled()) |
| curl_multi_setopt(m_multiHandle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); |
| } |
| |
| CurlMultiHandle::~CurlMultiHandle() |
| { |
| if (m_multiHandle) |
| curl_multi_cleanup(m_multiHandle); |
| } |
| |
| void CurlMultiHandle::setMaxConnects(long maxConnects) |
| { |
| if (maxConnects < 0) |
| return; |
| |
| curl_multi_setopt(m_multiHandle, CURLMOPT_MAXCONNECTS, maxConnects); |
| } |
| |
| void CurlMultiHandle::setMaxTotalConnections(long maxTotalConnections) |
| { |
| curl_multi_setopt(m_multiHandle, CURLMOPT_MAX_TOTAL_CONNECTIONS, maxTotalConnections); |
| } |
| |
| void CurlMultiHandle::setMaxHostConnections(long maxHostConnections) |
| { |
| curl_multi_setopt(m_multiHandle, CURLMOPT_MAX_HOST_CONNECTIONS, maxHostConnections); |
| } |
| |
| CURLMcode CurlMultiHandle::addHandle(CURL* handle) |
| { |
| return curl_multi_add_handle(m_multiHandle, handle); |
| } |
| |
| CURLMcode CurlMultiHandle::removeHandle(CURL* handle) |
| { |
| return curl_multi_remove_handle(m_multiHandle, handle); |
| } |
| |
| CURLMcode CurlMultiHandle::getFdSet(fd_set& readFdSet, fd_set& writeFdSet, fd_set& excFdSet, int& maxFd) |
| { |
| FD_ZERO(&readFdSet); |
| FD_ZERO(&writeFdSet); |
| FD_ZERO(&excFdSet); |
| maxFd = 0; |
| |
| return curl_multi_fdset(m_multiHandle, &readFdSet, &writeFdSet, &excFdSet, &maxFd); |
| } |
| |
| CURLMcode CurlMultiHandle::perform(int& runningHandles) |
| { |
| return curl_multi_perform(m_multiHandle, &runningHandles); |
| } |
| |
| CURLMsg* CurlMultiHandle::readInfo(int& messagesInQueue) |
| { |
| return curl_multi_info_read(m_multiHandle, &messagesInQueue); |
| } |
| |
| // CurlHandle ------------------------------------------------- |
| |
| CurlHandle::CurlHandle() |
| { |
| m_handle = curl_easy_init(); |
| curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_errorBuffer); |
| |
| enableShareHandle(); |
| enableAllowedProtocols(); |
| enableAcceptEncoding(); |
| |
| setDnsCacheTimeout(CurlContext::singleton().dnsCacheTimeout()); |
| setConnectTimeout(CurlContext::singleton().connectTimeout()); |
| |
| enableProxyIfExists(); |
| |
| #ifndef NDEBUG |
| enableVerboseIfUsed(); |
| enableStdErrIfUsed(); |
| #endif |
| } |
| |
| CurlHandle::~CurlHandle() |
| { |
| if (m_handle) |
| curl_easy_cleanup(m_handle); |
| } |
| |
| const String CurlHandle::errorDescription(CURLcode errorCode) |
| { |
| return String(curl_easy_strerror(errorCode)); |
| } |
| |
| void CurlHandle::enableSSLForHost(const String& host) |
| { |
| auto& sslHandle = CurlContext::singleton().sslHandle(); |
| if (auto sslClientCertificate = sslHandle.getSSLClientCertificate(host)) { |
| setSslCert(sslClientCertificate->first.utf8().data()); |
| setSslCertType("P12"); |
| setSslKeyPassword(sslClientCertificate->second.utf8().data()); |
| } |
| |
| if (sslHandle.canIgnoreAnyHTTPSCertificatesForHost(host) || sslHandle.shouldIgnoreSSLErrors()) { |
| setSslVerifyPeer(CurlHandle::VerifyPeer::Disable); |
| setSslVerifyHost(CurlHandle::VerifyHost::LooseNameCheck); |
| } else { |
| setSslVerifyPeer(CurlHandle::VerifyPeer::Enable); |
| setSslVerifyHost(CurlHandle::VerifyHost::StrictNameCheck); |
| } |
| |
| const auto& cipherList = sslHandle.getCipherList(); |
| if (!cipherList.isEmpty()) |
| setSslCipherList(cipherList.utf8().data()); |
| |
| setSslCtxCallbackFunction(willSetupSslCtxCallback, this); |
| |
| if (auto* path = WTF::get_if<String>(sslHandle.getCACertInfo())) |
| setCACertPath(path->utf8().data()); |
| } |
| |
| void CurlHandle::disableServerTrustEvaluation() |
| { |
| setSslVerifyPeer(CurlHandle::VerifyPeer::Disable); |
| setSslVerifyHost(CurlHandle::VerifyHost::LooseNameCheck); |
| } |
| |
| CURLcode CurlHandle::willSetupSslCtx(void* sslCtx) |
| { |
| if (!sslCtx) |
| return CURLE_ABORTED_BY_CALLBACK; |
| |
| if (!m_sslVerifier) |
| m_sslVerifier = makeUnique<CurlSSLVerifier>(sslCtx); |
| |
| return CURLE_OK; |
| } |
| |
| CURLcode CurlHandle::willSetupSslCtxCallback(CURL*, void* sslCtx, void* userData) |
| { |
| return static_cast<CurlHandle*>(userData)->willSetupSslCtx(sslCtx); |
| } |
| |
| int CurlHandle::sslErrors() const |
| { |
| return m_sslVerifier ? m_sslVerifier->sslErrors() : 0; |
| } |
| |
| CURLcode CurlHandle::perform() |
| { |
| return curl_easy_perform(m_handle); |
| } |
| |
| CURLcode CurlHandle::pause(int bitmask) |
| { |
| return curl_easy_pause(m_handle, bitmask); |
| } |
| |
| void CurlHandle::enableShareHandle() |
| { |
| curl_easy_setopt(m_handle, CURLOPT_SHARE, CurlContext::singleton().shareHandle().handle()); |
| } |
| |
| void CurlHandle::setUrl(const URL& url) |
| { |
| m_url = url.isolatedCopy(); |
| |
| URL curlUrl = url; |
| |
| // Remove any fragment part, otherwise curl will send it as part of the request. |
| curlUrl.removeFragmentIdentifier(); |
| |
| // Remove any query part sent to a local file. |
| if (curlUrl.isLocalFile()) { |
| // By setting the query to a null string it'll be removed. |
| if (!curlUrl.query().isEmpty()) |
| curlUrl.setQuery(String()); |
| } |
| |
| // url is in ASCII so latin1() will only convert it to char* without character translation. |
| curl_easy_setopt(m_handle, CURLOPT_URL, curlUrl.string().latin1().data()); |
| |
| if (url.protocolIs("https")) |
| enableSSLForHost(m_url.host().toString()); |
| } |
| |
| void CurlHandle::appendRequestHeaders(const HTTPHeaderMap& headers) |
| { |
| if (headers.size()) { |
| for (auto& entry : headers) |
| appendRequestHeader(entry.key, entry.value); |
| } |
| } |
| |
| void CurlHandle::appendRequestHeader(const String& name, const String& value) |
| { |
| String header(name); |
| |
| if (value.isEmpty()) { |
| // Insert the ; to tell curl that this header has an empty value. |
| header.append(";"); |
| } else { |
| header.append(": "); |
| header.append(value); |
| } |
| |
| appendRequestHeader(header); |
| } |
| |
| void CurlHandle::removeRequestHeader(const String& name) |
| { |
| // Add a header with no content, the internally used header will get disabled. |
| String header(name); |
| header.append(":"); |
| |
| appendRequestHeader(header); |
| } |
| |
| void CurlHandle::appendRequestHeader(const String& header) |
| { |
| bool needToEnable = m_requestHeaders.isEmpty(); |
| |
| m_requestHeaders.append(header); |
| |
| if (needToEnable) |
| enableRequestHeaders(); |
| } |
| |
| void CurlHandle::enableRequestHeaders() |
| { |
| if (m_requestHeaders.isEmpty()) |
| return; |
| |
| const struct curl_slist* headers = m_requestHeaders.head(); |
| curl_easy_setopt(m_handle, CURLOPT_HTTPHEADER, headers); |
| } |
| |
| void CurlHandle::enableHttp() |
| { |
| if (m_url.protocolIs("https") && CurlContext::singleton().isHttp2Enabled()) { |
| curl_easy_setopt(m_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); |
| curl_easy_setopt(m_handle, CURLOPT_PIPEWAIT, 1L); |
| curl_easy_setopt(m_handle, CURLOPT_SSL_ENABLE_ALPN, 1L); |
| curl_easy_setopt(m_handle, CURLOPT_SSL_ENABLE_NPN, 0L); |
| } else |
| curl_easy_setopt(m_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); |
| } |
| |
| void CurlHandle::enableHttpGetRequest() |
| { |
| enableHttp(); |
| curl_easy_setopt(m_handle, CURLOPT_HTTPGET, 1L); |
| } |
| |
| void CurlHandle::enableHttpHeadRequest() |
| { |
| enableHttp(); |
| curl_easy_setopt(m_handle, CURLOPT_NOBODY, 1L); |
| } |
| |
| void CurlHandle::enableHttpPostRequest() |
| { |
| enableHttp(); |
| curl_easy_setopt(m_handle, CURLOPT_POST, 1L); |
| curl_easy_setopt(m_handle, CURLOPT_POSTFIELDSIZE, 0L); |
| } |
| |
| void CurlHandle::setPostFields(const char* data, long size) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_POSTFIELDS, data); |
| curl_easy_setopt(m_handle, CURLOPT_POSTFIELDSIZE, size); |
| } |
| |
| void CurlHandle::setPostFieldLarge(curl_off_t size) |
| { |
| if (expectedSizeOfCurlOffT() != sizeof(long long)) |
| size = static_cast<int>(size); |
| |
| curl_easy_setopt(m_handle, CURLOPT_POSTFIELDSIZE_LARGE, size); |
| } |
| |
| void CurlHandle::enableHttpPutRequest() |
| { |
| enableHttp(); |
| curl_easy_setopt(m_handle, CURLOPT_UPLOAD, 1L); |
| curl_easy_setopt(m_handle, CURLOPT_INFILESIZE, 0L); |
| } |
| |
| void CurlHandle::setInFileSizeLarge(curl_off_t size) |
| { |
| if (expectedSizeOfCurlOffT() != sizeof(long long)) |
| size = static_cast<int>(size); |
| |
| curl_easy_setopt(m_handle, CURLOPT_INFILESIZE_LARGE, size); |
| } |
| |
| void CurlHandle::setHttpCustomRequest(const String& method) |
| { |
| enableHttp(); |
| curl_easy_setopt(m_handle, CURLOPT_CUSTOMREQUEST, method.ascii().data()); |
| } |
| |
| void CurlHandle::enableAcceptEncoding() |
| { |
| // enable all supported built-in compressions (gzip and deflate) through Accept-Encoding: |
| curl_easy_setopt(m_handle, CURLOPT_ENCODING, ""); |
| } |
| |
| void CurlHandle::enableAllowedProtocols() |
| { |
| static const long allowedProtocols = CURLPROTO_FILE | CURLPROTO_FTP | CURLPROTO_FTPS | CURLPROTO_HTTP | CURLPROTO_HTTPS; |
| |
| curl_easy_setopt(m_handle, CURLOPT_PROTOCOLS, allowedProtocols); |
| } |
| |
| void CurlHandle::setHttpAuthUserPass(const String& user, const String& password, long authType) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_USERNAME, user.utf8().data()); |
| curl_easy_setopt(m_handle, CURLOPT_PASSWORD, password.utf8().data()); |
| curl_easy_setopt(m_handle, CURLOPT_HTTPAUTH, authType); |
| } |
| |
| void CurlHandle::setCACertPath(const char* path) |
| { |
| if (path) |
| curl_easy_setopt(m_handle, CURLOPT_CAINFO, path); |
| } |
| |
| void CurlHandle::setSslVerifyPeer(VerifyPeer verifyPeer) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYPEER, static_cast<long>(verifyPeer)); |
| } |
| |
| void CurlHandle::setSslVerifyHost(VerifyHost verifyHost) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYHOST, static_cast<long>(verifyHost)); |
| } |
| |
| void CurlHandle::setSslCert(const char* cert) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_SSLCERT, cert); |
| } |
| |
| void CurlHandle::setSslCertType(const char* type) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_SSLCERTTYPE, type); |
| } |
| |
| void CurlHandle::setSslKeyPassword(const char* password) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_KEYPASSWD, password); |
| } |
| |
| void CurlHandle::setSslCipherList(const char* cipherList) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_SSL_CIPHER_LIST, cipherList); |
| } |
| |
| void CurlHandle::enableProxyIfExists() |
| { |
| auto& proxy = CurlContext::singleton().proxySettings(); |
| |
| switch (proxy.mode()) { |
| case CurlProxySettings::Mode::Default : |
| // For the proxy set by environment variable |
| if (!proxy.user().isEmpty()) |
| curl_easy_setopt(m_handle, CURLOPT_PROXYUSERNAME, proxy.user().utf8().data()); |
| if (!proxy.password().isEmpty()) |
| curl_easy_setopt(m_handle, CURLOPT_PROXYPASSWORD, proxy.password().utf8().data()); |
| curl_easy_setopt(m_handle, CURLOPT_PROXYAUTH, proxy.authMethod()); |
| break; |
| case CurlProxySettings::Mode::NoProxy : |
| // Disable the use of a proxy, even if there is an environment variable set for it. |
| curl_easy_setopt(m_handle, CURLOPT_PROXY, ""); |
| break; |
| case CurlProxySettings::Mode::Custom : |
| curl_easy_setopt(m_handle, CURLOPT_PROXY, proxy.url().utf8().data()); |
| curl_easy_setopt(m_handle, CURLOPT_NOPROXY, proxy.ignoreHosts().utf8().data()); |
| curl_easy_setopt(m_handle, CURLOPT_PROXYAUTH, proxy.authMethod()); |
| break; |
| } |
| } |
| |
| static CURLoption safeTimeValue(double time) |
| { |
| auto value = static_cast<unsigned>(time >= 0.0 ? time : 0); |
| return static_cast<CURLoption>(value); |
| } |
| |
| void CurlHandle::setDnsCacheTimeout(Seconds timeout) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_DNS_CACHE_TIMEOUT, safeTimeValue(timeout.seconds())); |
| } |
| |
| void CurlHandle::setConnectTimeout(Seconds timeout) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_CONNECTTIMEOUT, safeTimeValue(timeout.seconds())); |
| } |
| |
| void CurlHandle::setTimeout(Seconds timeout) |
| { |
| // Originally CURLOPT_TIMEOUT_MS was used here, but that is not the |
| // idle timeout, but entire duration time limit. It's not safe to specify |
| // such a time limit for communications, such as downloading. |
| // CURLOPT_LOW_SPEED_LIMIT is used instead. It enables the speed watcher |
| // and if the speed is below specified limit and last for specified duration, |
| // it invokes timeout error. |
| curl_easy_setopt(m_handle, CURLOPT_LOW_SPEED_LIMIT, 1L); |
| curl_easy_setopt(m_handle, CURLOPT_LOW_SPEED_TIME, safeTimeValue(timeout.seconds())); |
| } |
| |
| void CurlHandle::setHeaderCallbackFunction(curl_write_callback callbackFunc, void* userData) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_HEADERFUNCTION, callbackFunc); |
| curl_easy_setopt(m_handle, CURLOPT_HEADERDATA, userData); |
| } |
| |
| void CurlHandle::setWriteCallbackFunction(curl_write_callback callbackFunc, void* userData) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, callbackFunc); |
| curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, userData); |
| } |
| |
| void CurlHandle::setReadCallbackFunction(curl_read_callback callbackFunc, void* userData) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_READFUNCTION, callbackFunc); |
| curl_easy_setopt(m_handle, CURLOPT_READDATA, userData); |
| } |
| |
| void CurlHandle::setSslCtxCallbackFunction(curl_ssl_ctx_callback callbackFunc, void* userData) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_SSL_CTX_DATA, userData); |
| curl_easy_setopt(m_handle, CURLOPT_SSL_CTX_FUNCTION, callbackFunc); |
| } |
| |
| void CurlHandle::setDebugCallbackFunction(curl_debug_callback callbackFunc, void* userData) |
| { |
| curl_easy_setopt(m_handle, CURLOPT_DEBUGFUNCTION, callbackFunc); |
| curl_easy_setopt(m_handle, CURLOPT_DEBUGDATA, userData); |
| curl_easy_setopt(m_handle, CURLOPT_VERBOSE, 1L); |
| } |
| |
| void CurlHandle::enableConnectionOnly() |
| { |
| curl_easy_setopt(m_handle, CURLOPT_CONNECT_ONLY, 1L); |
| } |
| |
| Optional<String> CurlHandle::getProxyUrl() |
| { |
| auto& proxy = CurlContext::singleton().proxySettings(); |
| if (proxy.mode() == CurlProxySettings::Mode::Default) |
| return WTF::nullopt; |
| |
| return proxy.url(); |
| } |
| |
| Optional<long> CurlHandle::getResponseCode() |
| { |
| if (!m_handle) |
| return WTF::nullopt; |
| |
| long responseCode; |
| CURLcode errorCode = curl_easy_getinfo(m_handle, CURLINFO_RESPONSE_CODE, &responseCode); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| return responseCode; |
| } |
| |
| Optional<long> CurlHandle::getHttpConnectCode() |
| { |
| if (!m_handle) |
| return WTF::nullopt; |
| |
| long httpConnectCode; |
| CURLcode errorCode = curl_easy_getinfo(m_handle, CURLINFO_HTTP_CONNECTCODE, &httpConnectCode); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| return httpConnectCode; |
| } |
| |
| Optional<long long> CurlHandle::getContentLength() |
| { |
| if (!m_handle) |
| return WTF::nullopt; |
| |
| double contentLength; |
| |
| CURLcode errorCode = curl_easy_getinfo(m_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| return static_cast<long long>(contentLength); |
| } |
| |
| Optional<long> CurlHandle::getHttpAuthAvail() |
| { |
| if (!m_handle) |
| return WTF::nullopt; |
| |
| long httpAuthAvailable; |
| CURLcode errorCode = curl_easy_getinfo(m_handle, CURLINFO_HTTPAUTH_AVAIL, &httpAuthAvailable); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| return httpAuthAvailable; |
| } |
| |
| Optional<long> CurlHandle::getProxyAuthAvail() |
| { |
| if (!m_handle) |
| return WTF::nullopt; |
| |
| long proxyAuthAvailable; |
| CURLcode errorCode = curl_easy_getinfo(m_handle, CURLINFO_PROXYAUTH_AVAIL, &proxyAuthAvailable); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| return proxyAuthAvailable; |
| } |
| |
| Optional<long> CurlHandle::getHttpVersion() |
| { |
| if (!m_handle) |
| return WTF::nullopt; |
| |
| long version; |
| CURLcode errorCode = curl_easy_getinfo(m_handle, CURLINFO_HTTP_VERSION, &version); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| return version; |
| } |
| |
| Optional<NetworkLoadMetrics> CurlHandle::getNetworkLoadMetrics(const WTF::Seconds& domainLookupStart) |
| { |
| double nameLookup = 0.0; |
| double connect = 0.0; |
| double appConnect = 0.0; |
| double startTransfer = 0.0; |
| long version = 0; |
| |
| if (!m_handle) |
| return WTF::nullopt; |
| |
| CURLcode errorCode = curl_easy_getinfo(m_handle, CURLINFO_NAMELOOKUP_TIME, &nameLookup); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| errorCode = curl_easy_getinfo(m_handle, CURLINFO_CONNECT_TIME, &connect); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| errorCode = curl_easy_getinfo(m_handle, CURLINFO_APPCONNECT_TIME, &appConnect); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| errorCode = curl_easy_getinfo(m_handle, CURLINFO_STARTTRANSFER_TIME, &startTransfer); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| errorCode = curl_easy_getinfo(m_handle, CURLINFO_HTTP_VERSION, &version); |
| if (errorCode != CURLE_OK) |
| return WTF::nullopt; |
| |
| NetworkLoadMetrics networkLoadMetrics; |
| |
| networkLoadMetrics.domainLookupStart = domainLookupStart; |
| networkLoadMetrics.domainLookupEnd = domainLookupStart + Seconds(nameLookup); |
| networkLoadMetrics.connectStart = domainLookupStart + Seconds(nameLookup); |
| networkLoadMetrics.connectEnd = domainLookupStart + Seconds(connect); |
| |
| if (appConnect > 0.0) { |
| networkLoadMetrics.secureConnectionStart = domainLookupStart + Seconds(connect); |
| networkLoadMetrics.connectEnd = domainLookupStart + Seconds(appConnect); |
| } |
| |
| networkLoadMetrics.requestStart = networkLoadMetrics.connectEnd; |
| networkLoadMetrics.responseStart = domainLookupStart + Seconds(startTransfer); |
| |
| if (version == CURL_HTTP_VERSION_1_0) |
| networkLoadMetrics.protocol = httpVersion10; |
| else if (version == CURL_HTTP_VERSION_1_1) |
| networkLoadMetrics.protocol = httpVersion11; |
| else if (version == CURL_HTTP_VERSION_2) |
| networkLoadMetrics.protocol = httpVersion2; |
| |
| return networkLoadMetrics; |
| } |
| |
| void CurlHandle::addExtraNetworkLoadMetrics(NetworkLoadMetrics& networkLoadMetrics) |
| { |
| long requestHeaderSize = 0; |
| curl_off_t requestBodySize = 0; |
| long responseHeaderSize = 0; |
| curl_off_t responseBodySize = 0; |
| char* ip = nullptr; |
| long port = 0; |
| |
| // FIXME: Gets total request size not just headers https://bugs.webkit.org/show_bug.cgi?id=188363 |
| CURLcode errorCode = curl_easy_getinfo(m_handle, CURLINFO_REQUEST_SIZE, &requestHeaderSize); |
| if (errorCode != CURLE_OK) |
| return; |
| |
| errorCode = curl_easy_getinfo(m_handle, CURLINFO_SIZE_UPLOAD_T, &requestBodySize); |
| if (errorCode != CURLE_OK) |
| return; |
| |
| errorCode = curl_easy_getinfo(m_handle, CURLINFO_HEADER_SIZE, &responseHeaderSize); |
| if (errorCode != CURLE_OK) |
| return; |
| |
| errorCode = curl_easy_getinfo(m_handle, CURLINFO_SIZE_DOWNLOAD_T, &responseBodySize); |
| if (errorCode != CURLE_OK) |
| return; |
| |
| errorCode = curl_easy_getinfo(m_handle, CURLINFO_PRIMARY_IP, &ip); |
| if (errorCode != CURLE_OK) |
| return; |
| |
| errorCode = curl_easy_getinfo(m_handle, CURLINFO_PRIMARY_PORT, &port); |
| if (errorCode != CURLE_OK) |
| return; |
| |
| networkLoadMetrics.requestHeaderBytesSent = requestHeaderSize; |
| networkLoadMetrics.requestBodyBytesSent = requestBodySize; |
| networkLoadMetrics.responseHeaderBytesReceived = responseHeaderSize; |
| networkLoadMetrics.responseBodyBytesReceived = responseBodySize; |
| |
| if (ip) { |
| networkLoadMetrics.remoteAddress = String(ip); |
| if (port) |
| networkLoadMetrics.remoteAddress.append(":" + String::number(port)); |
| } |
| } |
| |
| Optional<CertificateInfo> CurlHandle::certificateInfo() const |
| { |
| if (!m_sslVerifier) |
| return WTF::nullopt; |
| |
| return m_sslVerifier->certificateInfo(); |
| } |
| |
| long long CurlHandle::maxCurlOffT() |
| { |
| static const long long maxCurlOffT = (1LL << (expectedSizeOfCurlOffT() * 8 - 1)) - 1; |
| |
| return maxCurlOffT; |
| } |
| |
| int CurlHandle::expectedSizeOfCurlOffT() |
| { |
| // The size of a curl_off_t could be different in WebKit and in cURL depending on |
| // compilation flags of both. |
| static int expectedSizeOfCurlOffT = 0; |
| if (!expectedSizeOfCurlOffT) { |
| curl_version_info_data* infoData = curl_version_info(CURLVERSION_NOW); |
| if (infoData->features & CURL_VERSION_LARGEFILE) |
| expectedSizeOfCurlOffT = sizeof(long long); |
| else |
| expectedSizeOfCurlOffT = sizeof(int); |
| } |
| |
| return expectedSizeOfCurlOffT; |
| } |
| |
| #ifndef NDEBUG |
| |
| void CurlHandle::enableVerboseIfUsed() |
| { |
| if (CurlContext::singleton().isVerbose()) |
| curl_easy_setopt(m_handle, CURLOPT_VERBOSE, 1); |
| } |
| |
| void CurlHandle::enableStdErrIfUsed() |
| { |
| if (auto log = CurlContext::singleton().getLogFile()) |
| curl_easy_setopt(m_handle, CURLOPT_STDERR, log); |
| } |
| |
| #endif |
| |
| // CurlSocketHandle |
| |
| CurlSocketHandle::CurlSocketHandle(const URL& url, Function<void(CURLcode)>&& errorHandler) |
| : m_errorHandler(WTFMove(errorHandler)) |
| { |
| // Libcurl is not responsible for the protocol handling. It just handles connection. |
| // Only scheme, host and port is required. |
| URL urlForConnection; |
| urlForConnection.setProtocol(url.protocolIs("wss") ? "https" : "http"); |
| urlForConnection.setHostAndPort(url.hostAndPort()); |
| setUrl(urlForConnection); |
| |
| enableConnectionOnly(); |
| } |
| |
| bool CurlSocketHandle::connect() |
| { |
| CURLcode errorCode = perform(); |
| if (errorCode != CURLE_OK) { |
| m_errorHandler(errorCode); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| size_t CurlSocketHandle::send(const uint8_t* buffer, size_t size) |
| { |
| size_t totalBytesSent = 0; |
| |
| while (totalBytesSent < size) { |
| size_t bytesSent = 0; |
| CURLcode errorCode = curl_easy_send(handle(), buffer + totalBytesSent, size - totalBytesSent, &bytesSent); |
| if (errorCode != CURLE_OK) { |
| if (errorCode != CURLE_AGAIN) |
| m_errorHandler(errorCode); |
| break; |
| } |
| |
| totalBytesSent += bytesSent; |
| } |
| |
| return totalBytesSent; |
| } |
| |
| Optional<size_t> CurlSocketHandle::receive(uint8_t* buffer, size_t bufferSize) |
| { |
| size_t bytesRead = 0; |
| |
| CURLcode errorCode = curl_easy_recv(handle(), buffer, bufferSize, &bytesRead); |
| if (errorCode != CURLE_OK) { |
| if (errorCode != CURLE_AGAIN) |
| m_errorHandler(errorCode); |
| |
| return WTF::nullopt; |
| } |
| |
| return bytesRead; |
| } |
| |
| Optional<CurlSocketHandle::WaitResult> CurlSocketHandle::wait(const Seconds& timeout, bool alsoWaitForWrite) |
| { |
| curl_socket_t socket; |
| CURLcode errorCode = curl_easy_getinfo(handle(), CURLINFO_ACTIVESOCKET, &socket); |
| if (errorCode != CURLE_OK) { |
| m_errorHandler(errorCode); |
| return WTF::nullopt; |
| } |
| |
| int64_t usec = timeout.microsecondsAs<int64_t>(); |
| |
| struct timeval selectTimeout; |
| if (usec <= 0) { |
| selectTimeout.tv_sec = 0; |
| selectTimeout.tv_usec = 0; |
| } else { |
| selectTimeout.tv_sec = usec / 1000000; |
| selectTimeout.tv_usec = usec % 1000000; |
| } |
| |
| int rc = 0; |
| int maxfd = static_cast<int>(socket) + 1; |
| fd_set fdread; |
| fd_set fdwrite; |
| fd_set fderr; |
| |
| // Retry 'select' if it was interrupted by a process signal. |
| do { |
| FD_ZERO(&fdread); |
| FD_SET(socket, &fdread); |
| |
| FD_ZERO(&fdwrite); |
| if (alsoWaitForWrite) |
| FD_SET(socket, &fdwrite); |
| |
| FD_ZERO(&fderr); |
| FD_SET(socket, &fderr); |
| |
| rc = ::select(maxfd, &fdread, &fdwrite, &fderr, &selectTimeout); |
| } while (rc == -1 && errno == EINTR); |
| |
| if (rc <= 0) |
| return WTF::nullopt; |
| |
| WaitResult result; |
| result.readable = FD_ISSET(socket, &fdread) || FD_ISSET(socket, &fderr); |
| result.writable = FD_ISSET(socket, &fdwrite); |
| return result; |
| } |
| |
| } |
| |
| #endif |