hphp/runtime/ext/curl/curl-resource.cpp (1,349 lines of code) (raw):

#include "hphp/runtime/ext/curl/curl-resource.h" #include "hphp/runtime/ext/curl/curl-multi-resource.h" #include "hphp/runtime/ext/curl/curl-share-resource.h" #include "hphp/runtime/ext/curl/ext_curl.h" #include "hphp/runtime/base/array-init.h" #include "hphp/runtime/base/array-iterator.h" #include "hphp/runtime/base/builtin-functions.h" #include "hphp/runtime/base/curl-tls-workarounds.h" #include "hphp/runtime/base/file.h" #include "hphp/runtime/base/file-util.h" #include "hphp/runtime/base/plain-file.h" #include "hphp/runtime/base/stack-logger.h" #include "hphp/runtime/ext/extension.h" #include "hphp/runtime/server/server-stats.h" #include "hphp/runtime/vm/vm-regs.h" #include <curl/curl.h> #include <curl/easy.h> #include <curl/multi.h> #include <folly/portability/OpenSSL.h> #if (LIBCURL_VERSION_NUM >= 0x074600) && (OPENSSL_VERSION_NUMBER >= 0x10101000L) #define CERT_CACHE_SUPPORTED 1 #else #undef CERT_CACHE_SUPPORTED #endif #define PHP_CURL_STDOUT 0 #define PHP_CURL_FILE 1 #define PHP_CURL_USER 2 #define PHP_CURL_DIRECT 3 #define PHP_CURL_RETURN 4 #define PHP_CURL_ASCII 5 #define PHP_CURL_BINARY 6 #define PHP_CURL_IGNORE 7 namespace { const HPHP::StaticString s_name("name"), s_mime("mime"), s_postname("postname"); } namespace HPHP { ///////////////////////////////////////////////////////////////////////////// // CurlResource CurlResource::ToFree::~ToFree() { for (unsigned int i = 0; i < str.size(); i++) { req::free(str[i]); } for (unsigned int i = 0; i < post.size(); i++) { curl_formfree(post[i]); } for (unsigned int i = 0; i < slist.size(); i++) { curl_slist_free_all(slist[i]); } } CurlResource::CurlResource(const String& url) : m_emptyPost(true), m_safeUpload(true) { m_cp = curl_easy_init(); m_multi = nullptr; m_url = url; memset(m_error_str, 0, sizeof(m_error_str)); m_error_no = CURLE_OK; m_to_free = req::make_shared<ToFree>(); m_write.method = PHP_CURL_STDOUT; m_write.type = PHP_CURL_ASCII; m_read.method = PHP_CURL_DIRECT; m_write_header.method = PHP_CURL_IGNORE; setDefaultOptions(); if (!url.empty()) { #if LIBCURL_VERSION_NUM >= 0x071100 /* Strings passed to libcurl as 'char *' arguments, are copied by the library... NOTE: before 7.17.0 strings were not copied. */ curl_easy_setopt(m_cp, CURLOPT_URL, url.c_str()); #else char *urlcopy = req::strndup(url.data(), url.size()); curl_easy_setopt(m_cp, CURLOPT_URL, urlcopy); m_to_free->str.push_back(urlcopy); #endif } } void CurlResource::sweep() { m_write.buf.release(); m_write_header.buf.release(); closeForSweep(); } void CurlResource::close() { if (m_in_callback) { raise_warning("curl_close(): Attempt to close cURL in callback, ignored."); return; } closeForSweep(); m_opts.clear(); m_to_free.reset(); } void CurlResource::closeForSweep() { assertx(!m_exception); assertx(IMPLIES(m_multi, m_cp)); if (!m_cp) return; if (m_multi) m_multi->remove(this, /*leak=*/true); curl_easy_cleanup(m_cp); m_cp = nullptr; } void CurlResource::check_exception() { if (!m_exception) return; auto e = std::move(m_exception); assertx(!m_exception); if (isPhpException(e)) { throw_object(getPhpException(e)); } else { getCppException(e)->throwException(); } } void CurlResource::reseat() { // Note: this is the minimum set of things to point the CURL* // to this CurlHandle curl_easy_setopt(m_cp, CURLOPT_ERRORBUFFER, m_error_str); curl_easy_setopt(m_cp, CURLOPT_FILE, (void*)this); curl_easy_setopt(m_cp, CURLOPT_INFILE, (void*)this); curl_easy_setopt(m_cp, CURLOPT_WRITEHEADER, (void*)this); curl_easy_setopt(m_cp, CURLOPT_SSL_CTX_DATA, (void*)this); } void CurlResource::reset() { curl_easy_reset(m_cp); setDefaultOptions(); } void CurlResource::setDefaultOptions() { curl_easy_setopt(m_cp, CURLOPT_NOPROGRESS, 1); curl_easy_setopt(m_cp, CURLOPT_VERBOSE, 0); curl_easy_setopt(m_cp, CURLOPT_WRITEFUNCTION, curl_write); curl_easy_setopt(m_cp, CURLOPT_READFUNCTION, curl_read); curl_easy_setopt(m_cp, CURLOPT_HEADERFUNCTION, curl_write_header); curl_easy_setopt(m_cp, CURLOPT_DNS_USE_GLOBAL_CACHE, 0); // for thread-safe curl_easy_setopt(m_cp, CURLOPT_DNS_CACHE_TIMEOUT, 120); curl_easy_setopt(m_cp, CURLOPT_MAXREDIRS, 20); // no infinite redirects curl_easy_setopt(m_cp, CURLOPT_NOSIGNAL, 1); // for multithreading mode curl_easy_setopt(m_cp, CURLOPT_SSL_CTX_FUNCTION, CurlResource::ssl_ctx_callback); curl_easy_setopt(m_cp, CURLOPT_TIMEOUT, minTimeout(RuntimeOption::HttpDefaultTimeout)); curl_easy_setopt(m_cp, CURLOPT_CONNECTTIMEOUT, minTimeout(RuntimeOption::HttpDefaultTimeout)); reseat(); } void CurlResource::prepare() { if (!useCertCache()) { if (auto const path = cainfo(false).get()) { curl_easy_setopt(m_cp, CURLOPT_CAINFO, path->data()); } if (auto const path = cainfo(true).get()) { curl_easy_setopt(m_cp, CURLOPT_PROXY_CAINFO, path->data()); } } else { curl_easy_setopt(m_cp, CURLOPT_CAINFO, NULL); curl_easy_setopt(m_cp, CURLOPT_PROXY_CAINFO, NULL); } } Variant CurlResource::execute() { assertx(!m_exception); if (m_cp == nullptr) { return false; } if (m_emptyPost) { // As per curl docs, an empty post must set POSTFIELDSIZE to be 0 or // the reader function will be called curl_easy_setopt(m_cp, CURLOPT_POSTFIELDSIZE, 0); } m_write.buf.clear(); m_write.content.clear(); m_header.clear(); memset(m_error_str, 0, sizeof(m_error_str)); prepare(); { IOStatusHelper io("curl_easy_perform", m_url.data()); SYNC_VM_REGS_SCOPED(); if (m_in_exec) { log_native_stack("unexpected re-entry into curl_exec"); } m_in_exec = true; // T29358191: curl_easy_perform should not throw... trust but verify try { m_error_no = curl_easy_perform(m_cp); } catch (...) { m_in_exec = false; log_native_stack("unexpected exception from curl_easy_perform"); throw; } m_in_exec = false; check_exception(); } set_curl_statuses(m_cp, m_url.data()); /* CURLE_PARTIAL_FILE is returned by HEAD requests */ if (m_error_no != CURLE_OK && m_error_no != CURLE_PARTIAL_FILE) { m_write.buf.clear(); m_write.content.clear(); return false; } if (m_write.method == PHP_CURL_RETURN) { if (!m_write.buf.empty()) { m_write.content = m_write.buf.detach(); } if (!m_write.content.empty()) { return m_write.content; } } if (m_write.method == PHP_CURL_RETURN) { return empty_string_variant(); } return true; } String CurlResource::getContents() { if (m_write.method == PHP_CURL_RETURN) { if (!m_write.buf.empty()) { m_write.content = m_write.buf.detach(); } return m_write.content; } return String(); } bool CurlResource::setOption(long option, const Variant& value) { if (m_cp == nullptr) { return false; } m_error_no = CURLE_OK; bool ret; if (isLongOption(option)) { ret = setLongOption(option, value.toInt64()); } else if (isStringOption(option)) { ret = setStringOption(option, value.toString()); } else if (isNullableStringOption(option)) { ret = setNullableStringOption(option, value); } else if (isFileOption(option)) { auto fp = dyn_cast_or_null<File>(value); if (!fp) return false; ret = setFileOption(option, fp); } else if (isStringListOption(option)) { ret = setStringListOption(option, value); } else if (isNonCurlOption(option)) { ret = setNonCurlOption(option, value); } else if (isBlobOption(option)) { ret = setBlobOption(option, value.toString()); } else if (option == CURLOPT_POSTFIELDS) { ret = setPostFieldsOption(value); } else if (option == CURLOPT_SHARE) { auto curlsh = dyn_cast_or_null<CurlShareResource>(value); if (!curlsh || curlsh->isInvalid()) { return false; } m_error_no = curlsh->attachToCurlHandle(m_cp); ret = true; } else if (option == CURLINFO_HEADER_OUT) { if (value.toInt64() == 1) { curl_easy_setopt(m_cp, CURLOPT_DEBUGFUNCTION, curl_debug); curl_easy_setopt(m_cp, CURLOPT_DEBUGDATA, (void *)this); curl_easy_setopt(m_cp, CURLOPT_VERBOSE, 1); } else { curl_easy_setopt(m_cp, CURLOPT_DEBUGFUNCTION, nullptr); curl_easy_setopt(m_cp, CURLOPT_DEBUGDATA, nullptr); curl_easy_setopt(m_cp, CURLOPT_VERBOSE, 0); } ret = true; } else { m_error_no = CURLE_FAILED_INIT; raise_invalid_argument_warning("option: %ld", option); ret = false; } if (!ret) { return false; } m_opts.set(int64_t(option), value); return m_error_no == CURLE_OK; } Variant CurlResource::getOption(long option) { if (option == 0) { return m_opts; } if (m_opts.exists(int64_t(option))) { return m_opts[int64_t(option)]; } return false; } CURL* CurlResource::get() { if (!m_cp) { throw_null_pointer_exception(); } return m_cp; } bool CurlResource::isLongOption(long option) { switch (option) { case CURLOPT_DNS_USE_GLOBAL_CACHE: // This is not thread-safe when set to true, so pretend we don't know what // it is. return false; // These first few are in their own case statements in PHP case CURLOPT_CLOSEPOLICY: case CURLOPT_SSL_VERIFYHOST: case CURLOPT_FOLLOWLOCATION: #if LIBCURL_VERSION_NUM >= 0x071301 case CURLOPT_POSTREDIR: #endif // Everything else case CURLOPT_AUTOREFERER: case CURLOPT_BUFFERSIZE: case CURLOPT_CONNECTTIMEOUT: case CURLOPT_COOKIESESSION: case CURLOPT_CRLF: case CURLOPT_DNS_CACHE_TIMEOUT: case CURLOPT_FAILONERROR: case CURLOPT_FILETIME: case CURLOPT_FORBID_REUSE: case CURLOPT_FRESH_CONNECT: case CURLOPT_FTP_USE_EPRT: case CURLOPT_FTP_USE_EPSV: case CURLOPT_HEADER: case CURLOPT_HTTPGET: case CURLOPT_HTTPPROXYTUNNEL: case CURLOPT_HTTP_VERSION: case CURLOPT_INFILESIZE: case CURLOPT_LOW_SPEED_LIMIT: case CURLOPT_LOW_SPEED_TIME: case CURLOPT_MAXCONNECTS: case CURLOPT_MAXREDIRS: case CURLOPT_NETRC: case CURLOPT_NOBODY: case CURLOPT_NOPROGRESS: case CURLOPT_NOSIGNAL: case CURLOPT_PORT: case CURLOPT_POST: case CURLOPT_PROXYPORT: case CURLOPT_PROXYTYPE: case CURLOPT_PUT: case CURLOPT_RESUME_FROM: case CURLOPT_SSLVERSION: case CURLOPT_SSL_VERIFYPEER: case CURLOPT_TIMECONDITION: case CURLOPT_TIMEOUT: case CURLOPT_TIMEVALUE: case CURLOPT_TRANSFERTEXT: case CURLOPT_UNRESTRICTED_AUTH: case CURLOPT_UPLOAD: case CURLOPT_VERBOSE: #if LIBCURL_VERSION_NUM >= 0x070a06 /* Available since 7.10.6 */ case CURLOPT_HTTPAUTH: #endif #if LIBCURL_VERSION_NUM >= 0x070a07 /* Available since 7.10.7 */ case CURLOPT_FTP_CREATE_MISSING_DIRS: case CURLOPT_PROXYAUTH: #endif #if LIBCURL_VERSION_NUM >= 0x070a08 /* Available since 7.10.8 */ case CURLOPT_FTP_RESPONSE_TIMEOUT: case CURLOPT_IPRESOLVE: case CURLOPT_MAXFILESIZE: #endif #if LIBCURL_VERSION_NUM >= 0x070b02 /* Available since 7.11.2 */ case CURLOPT_TCP_NODELAY: #endif #if LIBCURL_VERSION_NUM >= 0x070c02 /* Available since 7.12.2 */ case CURLOPT_FTPSSLAUTH: #endif #if LIBCURL_VERSION_NUM >= 0x070e01 /* Available since 7.14.1 */ case CURLOPT_IGNORE_CONTENT_LENGTH: #endif #if LIBCURL_VERSION_NUM >= 0x070f00 /* Available since 7.15.0 */ case CURLOPT_FTP_SKIP_PASV_IP: #endif #if LIBCURL_VERSION_NUM >= 0x070f01 /* Available since 7.15.1 */ case CURLOPT_FTP_FILEMETHOD: #endif #if LIBCURL_VERSION_NUM >= 0x070f02 /* Available since 7.15.2 */ case CURLOPT_CONNECT_ONLY: case CURLOPT_LOCALPORT: case CURLOPT_LOCALPORTRANGE: #endif #if LIBCURL_VERSION_NUM >= 0x071000 /* Available since 7.16.0 */ case CURLOPT_SSL_SESSIONID_CACHE: #endif #if LIBCURL_VERSION_NUM >= 0x071001 /* Available since 7.16.1 */ case CURLOPT_FTP_SSL_CCC: case CURLOPT_SSH_AUTH_TYPES: #endif #if LIBCURL_VERSION_NUM >= 0x071002 /* Available since 7.16.2 */ case CURLOPT_CONNECTTIMEOUT_MS: case CURLOPT_HTTP_CONTENT_DECODING: case CURLOPT_HTTP_TRANSFER_DECODING: case CURLOPT_TIMEOUT_MS: #endif #if LIBCURL_VERSION_NUM >= 0x071004 /* Available since 7.16.4 */ case CURLOPT_NEW_DIRECTORY_PERMS: case CURLOPT_NEW_FILE_PERMS: #endif #if LIBCURL_VERSION_NUM >= 0x071100 /* Available since 7.17.0 */ case CURLOPT_USE_SSL: #elif LIBCURL_VERSION_NUM >= 0x070b00 /* Available since 7.11.0 */ case CURLOPT_FTP_SSL: #endif #if LIBCURL_VERSION_NUM >= 0x071100 /* Available since 7.17.0 */ case CURLOPT_APPEND: case CURLOPT_DIRLISTONLY: #else case CURLOPT_FTPAPPEND: case CURLOPT_FTPLISTONLY: #endif #if LIBCURL_VERSION_NUM >= 0x071200 /* Available since 7.18.0 */ case CURLOPT_PROXY_TRANSFER_MODE: #endif #if LIBCURL_VERSION_NUM >= 0x071300 /* Available since 7.19.0 */ case CURLOPT_ADDRESS_SCOPE: #endif #if LIBCURL_VERSION_NUM > 0x071301 /* Available since 7.19.1 */ case CURLOPT_CERTINFO: #endif #if LIBCURL_VERSION_NUM >= 0x071304 /* Available since 7.19.4 */ case CURLOPT_PROTOCOLS: case CURLOPT_REDIR_PROTOCOLS: case CURLOPT_SOCKS5_GSSAPI_NEC: case CURLOPT_TFTP_BLKSIZE: #endif #if LIBCURL_VERSION_NUM >= 0x071400 /* Available since 7.20.0 */ case CURLOPT_FTP_USE_PRET: case CURLOPT_RTSP_CLIENT_CSEQ: case CURLOPT_RTSP_REQUEST: case CURLOPT_RTSP_SERVER_CSEQ: #endif #if LIBCURL_VERSION_NUM >= 0x071500 /* Available since 7.21.0 */ case CURLOPT_WILDCARDMATCH: #endif #if LIBCURL_VERSION_NUM >= 0x071504 /* Available since 7.21.4 */ case CURLOPT_TLSAUTH_TYPE: #endif #if LIBCURL_VERSION_NUM >= 0x071600 /* Available since 7.22.0 */ case CURLOPT_GSSAPI_DELEGATION: #endif #if LIBCURL_VERSION_NUM >= 0x071800 /* Available since 7.24.0 */ case CURLOPT_ACCEPTTIMEOUT_MS: #endif #if LIBCURL_VERSION_NUM >= 0x071900 /* Available since 7.25.0 */ case CURLOPT_SSL_OPTIONS: case CURLOPT_TCP_KEEPALIVE: case CURLOPT_TCP_KEEPIDLE: case CURLOPT_TCP_KEEPINTVL: #endif #if LIBCURL_VERSION_NUM >= 0x071f00 /* Available since 7.31.0 */ case CURLOPT_SASL_IR: #endif #if LIBCURL_VERSION_NUM >= 0x072400 /* Available since 7.36.0 */ case CURLOPT_EXPECT_100_TIMEOUT_MS: case CURLOPT_SSL_ENABLE_ALPN: case CURLOPT_SSL_ENABLE_NPN: #endif #if LIBCURL_VERSION_NUM >= 0x072500 /* Available since 7.37.0 */ case CURLOPT_HEADEROPT: #endif #if LIBCURL_VERSION_NUM >= 0x072900 /* Available since 7.41.0 */ case CURLOPT_SSL_VERIFYSTATUS: #endif #if LIBCURL_VERSION_NUM >= 0x072a00 /* Available since 7.42.0 */ case CURLOPT_PATH_AS_IS: case CURLOPT_SSL_FALSESTART: #endif #if LIBCURL_VERSION_NUM >= 0x072b00 /* Available since 7.43.0 */ case CURLOPT_PIPEWAIT: #endif #if LIBCURL_VERSION_NUM >= 0x072e00 /* Available since 7.46.0 */ case CURLOPT_STREAM_WEIGHT: #endif #if LIBCURL_VERSION_NUM >= 0x073000 /* Available since 7.48.0 */ case CURLOPT_TFTP_NO_OPTIONS: #endif #if LIBCURL_VERSION_NUM >= 0x073100 /* Available since 7.49.0 */ case CURLOPT_TCP_FASTOPEN: #endif #if LIBCURL_VERSION_NUM >= 0x073300 /* Available since 7.51.0 */ case CURLOPT_KEEP_SENDING_ON_ERROR: #endif #if LIBCURL_VERSION_NUM >= 0x073400 /* Available since 7.52.0 */ case CURLOPT_PROXY_SSLVERSION: case CURLOPT_PROXY_SSL_OPTIONS: case CURLOPT_PROXY_SSL_VERIFYHOST: case CURLOPT_PROXY_SSL_VERIFYPEER: #endif #if LIBCURL_VERSION_NUM >= 0x073600 /* Available since 7.54.0 */ case CURLOPT_SUPPRESS_CONNECT_HEADERS: #endif #if LIBCURL_VERSION_NUM >= 0x073700 /* Available since 7.55.0 */ case CURLOPT_SOCKS5_AUTH: #endif #if LIBCURL_VERSION_NUM >= 0x073800 /* Available since 7.56.0 */ case CURLOPT_SSH_COMPRESSION: #endif #if LIBCURL_VERSION_NUM >= 0x073b00 /* Available since 7.59.0 */ case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS: case CURLOPT_TIMEVALUE_LARGE: #endif #if LIBCURL_VERSION_NUM >= 0x073c00 /* Available since 7.60.0 */ case CURLOPT_DNS_SHUFFLE_ADDRESSES: case CURLOPT_HAPROXYPROTOCOL: #endif #if LIBCURL_VERSION_NUM >= 0x073d00 /* Available since 7.61.0 */ case CURLOPT_DISALLOW_USERNAME_IN_URL: #endif #if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ case CURLOPT_UPKEEP_INTERVAL_MS: case CURLOPT_UPLOAD_BUFFERSIZE: #endif #if CURLOPT_MUTE != 0 case CURLOPT_MUTE: #endif return true; default: return false; } } bool CurlResource::setLongOption(long option, long value) { if (option == CURLOPT_TIMEOUT) { value = minTimeout(value); #if LIBCURL_VERSION_NUM >= 0x071002 } else if (option == CURLOPT_TIMEOUT_MS) { value = minTimeoutMS(value); #endif } else if ((option == CURLOPT_SSL_VERIFYHOST) && (value == 1)) { raise_notice( "curl_setopt(): CURLOPT_SSL_VERIFYHOST set to true which disables " "common name validation " "(setting CURLOPT_SSL_VERIFYHOST to 2 enables common name validation)" ); } m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, value); return m_error_no == CURLE_OK; } bool CurlResource::isStringFilePathOption(long option) { switch (option) { case CURLOPT_COOKIEFILE: case CURLOPT_COOKIEJAR: case CURLOPT_RANDOM_FILE: case CURLOPT_SSLCERT: #if LIBCURL_VERSION_NUM >= 0x070b00 /* Available since 7.11.0 */ case CURLOPT_NETRC_FILE: #endif #if LIBCURL_VERSION_NUM >= 0x071001 /* Available since 7.16.1 */ case CURLOPT_SSH_PRIVATE_KEYFILE: case CURLOPT_SSH_PUBLIC_KEYFILE: #endif #if LIBCURL_VERSION_NUM >= 0x071300 /* Available since 7.19.0 */ case CURLOPT_CRLFILE: case CURLOPT_ISSUERCERT: #endif #if LIBCURL_VERSION_NUM >= 0x071306 /* Available since 7.19.6 */ case CURLOPT_SSH_KNOWNHOSTS: #endif #if LIBCURL_VERSION_NUM >= 0x073400 /* Available since 7.52.0 */ case CURLOPT_PROXY_CRLFILE: case CURLOPT_PROXY_SSLCERT: #endif #if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */ case CURLOPT_PROXY_ISSUERCERT: #endif return true; default: return false; } } bool CurlResource::isStringOption(long option) { switch (option) { // Not in PHP's main string case case CURLOPT_PRIVATE: case CURLOPT_URL: case CURLOPT_KRB4LEVEL: // Everything else case CURLOPT_CAINFO: case CURLOPT_CAPATH: case CURLOPT_COOKIE: case CURLOPT_EGDSOCKET: case CURLOPT_INTERFACE: case CURLOPT_PROXY: case CURLOPT_PROXYUSERPWD: case CURLOPT_REFERER: case CURLOPT_SSLCERTTYPE: case CURLOPT_SSLENGINE: case CURLOPT_SSLENGINE_DEFAULT: case CURLOPT_SSLKEY: case CURLOPT_SSLKEYPASSWD: case CURLOPT_SSLKEYTYPE: case CURLOPT_SSL_CIPHER_LIST: case CURLOPT_USERAGENT: case CURLOPT_USERPWD: #if LIBCURL_VERSION_NUM >= 0x070e01 /* Available since 7.14.1 */ case CURLOPT_COOKIELIST: #endif #if LIBCURL_VERSION_NUM >= 0x070f05 /* Available since 7.15.5 */ case CURLOPT_FTP_ALTERNATIVE_TO_USER: #endif #if LIBCURL_VERSION_NUM >= 0x071101 /* Available since 7.17.1 */ case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: #endif #if LIBCURL_VERSION_NUM >= 0x071301 /* Available since 7.19.1 */ case CURLOPT_PASSWORD: case CURLOPT_PROXYPASSWORD: case CURLOPT_PROXYUSERNAME: case CURLOPT_USERNAME: #endif #if LIBCURL_VERSION_NUM >= 0x071304 /* Available since 7.19.4 */ case CURLOPT_NOPROXY: case CURLOPT_SOCKS5_GSSAPI_SERVICE: #endif #if LIBCURL_VERSION_NUM >= 0x071400 /* Available since 7.20.0 */ case CURLOPT_MAIL_FROM: case CURLOPT_RTSP_STREAM_URI: case CURLOPT_RTSP_TRANSPORT: #endif #if LIBCURL_VERSION_NUM >= 0x071504 /* Available since 7.21.4 */ case CURLOPT_TLSAUTH_PASSWORD: case CURLOPT_TLSAUTH_USERNAME: #endif #if LIBCURL_VERSION_NUM >= 0x071506 /* Available since 7.21.6 */ case CURLOPT_ACCEPT_ENCODING: case CURLOPT_TRANSFER_ENCODING: #else case CURLOPT_ENCODING: #endif #if LIBCURL_VERSION_NUM >= 0x071800 /* Available since 7.24.0 */ case CURLOPT_DNS_SERVERS: #endif #if LIBCURL_VERSION_NUM >= 0x071900 /* Available since 7.25.0 */ case CURLOPT_MAIL_AUTH: #endif #if LIBCURL_VERSION_NUM >= 0x072200 /* Available since 7.34.0 */ case CURLOPT_LOGIN_OPTIONS: #endif #if LIBCURL_VERSION_NUM >= 0x072700 /* Available since 7.39.0 */ case CURLOPT_PINNEDPUBLICKEY: #endif #if LIBCURL_VERSION_NUM >= 0x072b00 /* Available since 7.43.0 */ case CURLOPT_PROXY_SERVICE_NAME: case CURLOPT_SERVICE_NAME: #endif #if LIBCURL_VERSION_NUM >= 0x072d00 /* Available since 7.45.0 */ case CURLOPT_DEFAULT_PROTOCOL: #endif #if LIBCURL_VERSION_NUM >= 0x073400 /* Available since 7.52.0 */ case CURLOPT_PROXY_CAINFO: case CURLOPT_PROXY_CAPATH: case CURLOPT_PROXY_KEYPASSWD: case CURLOPT_PROXY_PINNEDPUBLICKEY: case CURLOPT_PROXY_SSLCERTTYPE: case CURLOPT_PROXY_SSLKEY: case CURLOPT_PROXY_SSLKEYTYPE: case CURLOPT_PROXY_SSL_CIPHER_LIST: case CURLOPT_PROXY_TLSAUTH_PASSWORD: case CURLOPT_PROXY_TLSAUTH_TYPE: case CURLOPT_PROXY_TLSAUTH_USERNAME: #endif #if LIBCURL_VERSION_NUM >= 0x073d00 /* Available since 7.61.0 */ case CURLOPT_PROXY_TLS13_CIPHERS: case CURLOPT_TLS13_CIPHERS: #endif #if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ case CURLOPT_DOH_URL: #endif return true; default: return isStringFilePathOption(option); } } bool CurlResource::setStringOption(long option, const String& value) { assertx(isStringOption(option)); // the following options deal with files, therefore the open_basedir check // is required. if (isStringFilePathOption(option) && !value.empty()) { String filename = File::TranslatePath(value); if (filename.empty()) { raise_warning( "open_basedir restriction in effect. File(%s) is not within " "the allowed paths", value.data() ); return false; } } if (option == CURLOPT_URL) m_url = value; #if LIBCURL_VERSION_NUM >= 0x071100 /* Strings passed to libcurl as 'char *' arguments, are copied by the library... NOTE: before 7.17.0 strings were not copied. */ m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, value.c_str()); #else char *copystr = req::strndup(value.data(), value.size()); m_to_free->str.push_back(copystr); m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, copystr); #endif return m_error_no == CURLE_OK; } bool CurlResource::isNullableStringOption(long option) { switch (option) { case CURLOPT_CUSTOMREQUEST: case CURLOPT_FTPPORT: case CURLOPT_RANGE: #if LIBCURL_VERSION_NUM >= 0x070d00 /* Available since 7.13.0 */ case CURLOPT_FTP_ACCOUNT: #endif #if LIBCURL_VERSION_NUM >= 0x071400 /* Available since 7.20.0 */ case CURLOPT_RTSP_SESSION_ID: #endif #if LIBCURL_VERSION_NUM >= 0x072100 /* Available since 7.33.0 */ case CURLOPT_DNS_INTERFACE: case CURLOPT_DNS_LOCAL_IP4: case CURLOPT_DNS_LOCAL_IP6: case CURLOPT_XOAUTH2_BEARER: #endif #if LIBCURL_VERSION_NUM >= 0x072800 /* Available since 7.40.0 */ case CURLOPT_UNIX_SOCKET_PATH: #endif #if LIBCURL_VERSION_NUM >= 0x073500 /* Available since 7.53.0 */ case CURLOPT_ABSTRACT_UNIX_SOCKET: #endif #if LIBCURL_VERSION_NUM >= 0x073700 /* Available since 7.55.0 */ case CURLOPT_REQUEST_TARGET: #endif #if LIBCURL_VERSION_NUM >= 0x071004 /* Available since 7.16.4 */ case CURLOPT_KRBLEVEL: #else case CURLOPT_KRB4LEVEL: #endif return true; default: return false; } } bool CurlResource::setNullableStringOption(long option, const Variant& value) { assertx(isNullableStringOption(option)); if (value.isNull()) { m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, nullptr); } else { auto const strValue = value.toString(); #if LIBCURL_VERSION_NUM >= 0x071100 /* Strings passed to libcurl as 'char *' arguments, are copied by the library... NOTE: before 7.17.0 strings were not copied. */ m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, strValue.c_str()); #else char *copystr = req::strndup(strValue.data(), strValue.size()); m_to_free->str.push_back(copystr); m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, copystr); #endif } return m_error_no == CURLE_OK; } bool CurlResource::isBlobOption(long option) { #if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */ switch (option) { case CURLOPT_SSLCERT_BLOB: case CURLOPT_SSLKEY_BLOB: case CURLOPT_PROXY_SSLCERT_BLOB: case CURLOPT_PROXY_SSLKEY_BLOB: case CURLOPT_ISSUERCERT_BLOB: case CURLOPT_PROXY_ISSUERCERT_BLOB: return true; } #endif return false; } bool CurlResource::setBlobOption(long option, const String& value) { #if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */ assertx(isBlobOption(option)); struct curl_blob blob; blob.data = (void *)value.data(); blob.len = value.size(); blob.flags = CURL_BLOB_COPY; m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, &blob); return m_error_no == CURLE_OK; #else return false; #endif } bool CurlResource::setPostFieldsOption(const Variant& value) { m_emptyPost = false; if (!value.isArray() && !value.is(KindOfObject)) { String svalue = value.toString(); #if LIBCURL_VERSION_NUM >= 0x071100 /* with curl 7.17.0 and later, we can use COPYPOSTFIELDS, but we have to provide size before */ m_error_no = curl_easy_setopt(m_cp, CURLOPT_POSTFIELDSIZE, svalue.size()); m_error_no = curl_easy_setopt(m_cp, CURLOPT_COPYPOSTFIELDS, svalue.c_str()); #else char *post = req::strndup(svalue.data(), svalue.size()); m_to_free->str.push_back(post); m_error_no = curl_easy_setopt(m_cp, CURLOPT_POSTFIELDS, post); m_error_no = curl_easy_setopt(m_cp, CURLOPT_POSTFIELDSIZE, svalue.size()); #endif return m_error_no == CURLE_OK; } Array arr = value.toArray(); curl_httppost *first = nullptr; curl_httppost *last = nullptr; for (ArrayIter iter(arr); iter; ++iter) { String key = iter.first().toString(); Variant var_val = iter.second(); if (UNLIKELY(var_val.isObject() && var_val.toObject()->instanceof(SystemLib::s_CURLFileClass))) { Object val = var_val.toObject(); String name = val->o_get(s_name).toString(); String mime = val->o_get(s_mime).toString(); String postname = val->o_get(s_postname).toString(); m_error_no = (CURLcode)curl_formadd (&first, &last, CURLFORM_COPYNAME, key.data(), CURLFORM_NAMELENGTH, (long)key.size(), CURLFORM_FILENAME, postname.empty() ? name.c_str() : postname.c_str(), CURLFORM_CONTENTTYPE, mime.empty() ? "application/octet-stream" : mime.c_str(), CURLFORM_FILE, name.c_str(), CURLFORM_END); } else { String val = var_val.toString(); auto postval = val.data(); if (!RuntimeOption::PHP7_DisallowUnsafeCurlUploads && !m_safeUpload && *postval == '@' && strlen(postval) == val.size()) { /* Given a string like: * "@/foo/bar;type=herp/derp;filename=ponies\0" * - Temporarily convert to: * "@/foo/bar\0type=herp/derp\0filename=ponies\0" * - Pass pointers to the relevant null-terminated substrings to * curl_formadd * - Revert changes to postval at the end */ raise_deprecated( "curl_setopt(): The usage of the @filename API for file uploading " "is deprecated. Please use the CURLFile class instead" ); if (val.get()->hasMultipleRefs()) { val = String::attach( StringData::Make(val.data(), val.size(), CopyString)); } auto slice = val.bufferSlice(); char* mutablePostval = slice.begin() + 1; char* type = strstr(mutablePostval, ";type="); char* filename = strstr(mutablePostval, ";filename="); if (type) { *type = '\0'; } if (filename) { *filename = '\0'; } String localName = File::TranslatePath(mutablePostval); /* The arguments after _NAMELENGTH and _CONTENTSLENGTH * must be explicitly cast to long in curl_formadd * use since curl needs a long not an int. */ m_error_no = (CURLcode)curl_formadd (&first, &last, CURLFORM_COPYNAME, key.data(), CURLFORM_NAMELENGTH, (long)key.size(), CURLFORM_FILENAME, filename ? filename + sizeof(";filename=") - 1 : postval, CURLFORM_CONTENTTYPE, type ? type + sizeof(";type=") - 1 : "application/octet-stream", CURLFORM_FILE, localName.c_str(), CURLFORM_END); if (type) { *type = ';'; } if (filename) { *filename = ';'; } } else { m_error_no = (CURLcode)curl_formadd (&first, &last, CURLFORM_COPYNAME, key.data(), CURLFORM_NAMELENGTH, (long)key.size(), CURLFORM_COPYCONTENTS, postval, CURLFORM_CONTENTSLENGTH,(long)val.size(), CURLFORM_END); } } } if (m_error_no != CURLE_OK) { return false; } m_to_free->post.push_back(first); m_error_no = curl_easy_setopt(m_cp, CURLOPT_HTTPPOST, first); return true; } bool CurlResource::isFileOption(long option) { return (option == CURLOPT_FILE) || (option == CURLOPT_INFILE) || (option == CURLOPT_WRITEHEADER) || (option == CURLOPT_STDERR); } inline bool checkWritable(const std::string& mode) { // This check is really hinky, but it's what PHP does. :/ if ((mode.find('r') != 0) || (mode.find('+') == 1)) { return true; } raise_warning("curl_setopt(): the provided file handle is not writable"); return false; } bool CurlResource::setFileOption(long option, const req::ptr<File>& fp) { assertx(isFileOption(option)); if (option == CURLOPT_FILE) { if (!checkWritable(fp->getMode())) return false; m_write.fp = fp; m_write.method = PHP_CURL_FILE; return true; } if (option == CURLOPT_WRITEHEADER) { if (!checkWritable(fp->getMode())) return false; m_write_header.fp = fp; m_write_header.method = PHP_CURL_FILE; return true; } if (option == CURLOPT_INFILE) { m_read.fp = fp; m_emptyPost = false; return true; } assertx(option == CURLOPT_STDERR); auto pf = dyn_cast<PlainFile>(fp); if (!pf) { return false; } auto innerFp = pf->getStream(); if (!innerFp) { return false; } m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, innerFp); return true; } bool CurlResource::isStringListOption(long option) { switch (option) { case CURLOPT_HTTP200ALIASES: case CURLOPT_HTTPHEADER: case CURLOPT_POSTQUOTE: case CURLOPT_PREQUOTE: case CURLOPT_QUOTE: case CURLOPT_TELNETOPTIONS: #if LIBCURL_VERSION_NUM >= 0x071400 /* Available since 7.20.0 */ case CURLOPT_MAIL_RCPT: #endif #if LIBCURL_VERSION_NUM >= 0x071503 /* Available since 7.21.3 */ case CURLOPT_RESOLVE: #endif #if LIBCURL_VERSION_NUM >= 0x072500 /* Available since 7.37.0 */ case CURLOPT_PROXYHEADER: #endif #if LIBCURL_VERSION_NUM >= 0x073100 /* Available since 7.49.0 */ case CURLOPT_CONNECT_TO: #endif return true; default: return false; } } bool CurlResource::setStringListOption(long option, const Variant& value) { if (!value.isArray() && !value.is(KindOfObject)) { raise_warning("You must pass either an object or an array with " "the CURLOPT_HTTPHEADER, CURLOPT_PROXYHEADER, CURLOPT_QUOTE, " "CURLOPT_HTTP200ALIASES, CURLOPT_POSTQUOTE " "and CURLOPT_RESOLVE arguments"); return false; } Array arr = value.toArray(); curl_slist *slist = nullptr; for (ArrayIter iter(arr); iter; ++iter) { String key = iter.first().toString(); String val = iter.second().toString(); slist = curl_slist_append(slist, val.c_str()); if (!slist) { raise_warning("Could not build curl_slist"); return false; } } m_to_free->slist.push_back(slist); m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, slist); return true; } bool CurlResource::isNonCurlOption(long option) { return (option == CURLOPT_RETURNTRANSFER) || (option == CURLOPT_BINARYTRANSFER) || (option == CURLOPT_WRITEFUNCTION) || (option == CURLOPT_READFUNCTION) || (option == CURLOPT_HEADERFUNCTION) || (option == CURLOPT_PROGRESSFUNCTION) || (option == CURLOPT_FB_TLS_VER_MAX) || (option == CURLOPT_FB_TLS_CIPHER_SPEC) || (option == CURLOPT_SAFE_UPLOAD); } bool CurlResource::setNonCurlOption(long option, const Variant& value) { assertx(isNonCurlOption(option)); switch (option) { case CURLOPT_RETURNTRANSFER: m_write.method = value.toBoolean() ? PHP_CURL_RETURN : PHP_CURL_STDOUT; return true; case CURLOPT_BINARYTRANSFER: m_write.type = value.toBoolean() ? PHP_CURL_BINARY : PHP_CURL_ASCII; return true; case CURLOPT_WRITEFUNCTION: m_write.callback = value; m_write.method = PHP_CURL_USER; return true; case CURLOPT_READFUNCTION: m_read.callback = value; m_read.method = PHP_CURL_USER; m_emptyPost = false; return true; case CURLOPT_HEADERFUNCTION: m_write_header.callback = value; m_write_header.method = PHP_CURL_USER; return true; case CURLOPT_PROGRESSFUNCTION: m_progress_callback = value; curl_easy_setopt(m_cp, CURLOPT_PROGRESSDATA, (void*) this); curl_easy_setopt(m_cp, CURLOPT_PROGRESSFUNCTION, curl_progress); return true; case CURLOPT_FB_TLS_VER_MAX: if (value.isInteger()) { auto val = value.toInt64(); if (val == CURLOPT_FB_TLS_VER_MAX_1_0 || val == CURLOPT_FB_TLS_VER_MAX_1_1 || val == CURLOPT_FB_TLS_VER_MAX_NONE) { return true; } } raise_warning("You must pass CURLOPT_FB_TLS_VER_MAX_1_0, " "CURLOPT_FB_TLS_VER_MAX_1_1 or " "CURLOPT_FB_TLS_VER_MAX_NONE with " "CURLOPT_FB_TLS_VER_MAX"); return false; case CURLOPT_FB_TLS_CIPHER_SPEC: if (!value.isString() || value.toString().empty()) { raise_warning("CURLOPT_FB_TLS_CIPHER_SPEC requires a non-empty string"); } return false; case CURLOPT_SAFE_UPLOAD: if (RuntimeOption::PHP7_DisallowUnsafeCurlUploads && value.toInt64() == 0) { raise_warning( "curl_setopt(): Disabling safe uploads is no longer supported" ); return false; } m_safeUpload = value.toBoolean(); return true; } return false; } inline int64_t minTimeoutImpl(int64_t timeout, int64_t multiple) { auto info = RequestInfo::s_requestInfo.getNoCheck(); auto& data = info->m_reqInjectionData; if (!data.getTimeout()) { return timeout; } return std::min<int64_t>(multiple * data.getRemainingTime(), timeout); } int64_t CurlResource::minTimeout(int64_t timeout) { return minTimeoutImpl(timeout, 1); } int64_t CurlResource::minTimeoutMS(int64_t timeout) { return minTimeoutImpl(timeout, 1000); } void CurlResource::handle_exception() { assertx(!m_exception); try { throw; } catch (const Object& e) { m_exception.emplace(e); } catch (Exception& e) { m_exception.emplace(e.clone()); } catch (std::exception& e) { m_exception.emplace( new FatalErrorException(0, "Unexpected error in curl callback: %s", e.what()) ); } catch (...) { m_exception.emplace( new FatalErrorException("Unknown error in curl callback") ); } } size_t CurlResource::curl_read(char *data, size_t size, size_t nmemb, void *ctx) { CurlResource *ch = (CurlResource *)ctx; if (!ch->m_in_exec) { // T29358191: who's calling, and are they dealing with m_exception? log_native_stack("unexpected curl_read"); } ReadHandler *t = &ch->m_read; int length = -1; try { switch (t->method) { case PHP_CURL_DIRECT: if (t->fp) { int data_size = size * nmemb; String ret = t->fp->read(data_size); length = ret.size(); if (length) { memcpy(data, ret.data(), length); } } break; case PHP_CURL_USER: { int data_size = size * nmemb; ch->m_in_callback = true; SCOPE_EXIT { ch->m_in_callback = false; }; Variant ret = vm_call_user_func( t->callback, make_vec_array(Resource(ch), Resource(t->fp), data_size)); if (ret.isString()) { String sret = ret.toString(); length = data_size < sret.size() ? data_size : sret.size(); memcpy(data, sret.data(), length); } break; } } } catch (...) { ch->handle_exception(); return CURL_READFUNC_ABORT; } return length; } size_t CurlResource::curl_write(char *data, size_t size, size_t nmemb, void *ctx) { CurlResource *ch = (CurlResource *)ctx; if (!ch->m_in_exec) { // T29358191: who's calling, and are they dealing with m_exception? log_native_stack("unexpected curl_write"); } WriteHandler *t = &ch->m_write; size_t length = size * nmemb; try { switch (t->method) { case PHP_CURL_STDOUT: g_context->write(data, length); break; case PHP_CURL_FILE: return t->fp->write(String(data, length, CopyString), length); case PHP_CURL_RETURN: if (length > 0) { t->buf.append(data, (int)length); } break; case PHP_CURL_USER: { ch->m_in_callback = true; SCOPE_EXIT { ch->m_in_callback = false; }; Variant ret = vm_call_user_func( t->callback, make_vec_array(Resource(ch), String(data, length, CopyString))); length = ret.toInt64(); break; } } } catch (...) { ch->handle_exception(); return 0; } return length; } size_t CurlResource::curl_write_header(char *data, size_t size, size_t nmemb, void *ctx) { CurlResource *ch = (CurlResource *)ctx; if (!ch->m_in_exec) { // T29358191: who's calling, and are they dealing with m_exception? log_native_stack("unexpected curl_write_header"); } WriteHandler *t = &ch->m_write_header; size_t length = size * nmemb; try { switch (t->method) { case PHP_CURL_STDOUT: // Handle special case write when we're returning the entire transfer if (ch->m_write.method == PHP_CURL_RETURN && length > 0) { ch->m_write.buf.append(data, (int)length); } else { g_context->write(data, length); } break; case PHP_CURL_FILE: return t->fp->write(String(data, length, CopyString), length); case PHP_CURL_USER: { ch->m_in_callback = true; SCOPE_EXIT { ch->m_in_callback = false; }; Variant ret = vm_call_user_func( t->callback, make_vec_array(Resource(ch), String(data, length, CopyString))); length = ret.toInt64(); break; } case PHP_CURL_IGNORE: return length; default: return (size_t)-1; } } catch (...) { ch->handle_exception(); return 0; } return length; } int CurlResource::curl_debug(CURL* /*cp*/, curl_infotype type, char* buf, size_t buf_len, void* ctx) { CurlResource *ch = (CurlResource *)ctx; if (type == CURLINFO_HEADER_OUT && buf_len > 0) { ch->m_header = String(buf, buf_len, CopyString); } return 0; } int CurlResource::curl_progress(void* p, double dltotal, double dlnow, double ultotal, double ulnow) { assertx(p); CurlResource* curl = static_cast<CurlResource*>(p); if (!curl->m_in_exec) { // T29358191: who's calling, and are they dealing with m_exception? log_native_stack("unexpected curl_progress"); } VecInit pai(5); pai.append(Resource(curl)); pai.append(dltotal); pai.append(dlnow); pai.append(ultotal); pai.append(ulnow); try { Variant result = vm_call_user_func( curl->m_progress_callback, pai.toArray() ); // Both PHP and libcurl are documented as return 0 to continue, non-zero // to abort, however this is what Zend actually implements return result.toInt64() == 0 ? 0 : 1; } catch (...) { curl->handle_exception(); return 1; } } namespace { hphp_fast_map<String, X509_STORE*, hphp_string_hash, hphp_string_same> s_certCache; folly::SharedMutex s_mutex; } bool CurlResource::useCertCache() const { #ifdef CERT_CACHE_SUPPORTED auto const isNonEmpty = [&](int64_t option) { if (!m_opts.exists(option)) return false; Variant untyped_value = m_opts[option]; if (untyped_value.isString() && !untyped_value.toString().empty()) { return true; } return false; }; if (isNonEmpty(CURLOPT_PROXY) && cainfo(false) != cainfo(true)) { return false; } if (isNonEmpty(CURLOPT_CRLFILE)) { return false; } if (isNonEmpty(CURLOPT_CAPATH)) { return false; } return true; #else return false; #endif } String CurlResource::cainfo(bool proxy) const { #ifdef CERT_CACHE_SUPPORTED auto const option = proxy ? CURLOPT_PROXY_CAINFO : CURLOPT_CAINFO; static auto const defaultCainfoData = [&] () -> StringData* { auto const curlInfo = curl_version_info(CURLVERSION_NOW); if (!curlInfo->cainfo) return nullptr; return makeStaticString(curlInfo->cainfo); }(); auto const defaultCainfo = defaultCainfoData ? String{defaultCainfoData} : String{}; if (!m_opts.exists(int64_t(option))) return defaultCainfo; Variant untyped_value = m_opts[int64_t(option)]; if (!untyped_value.isString()) return defaultCainfo; auto const string_value = untyped_value.toString(); if (string_value.empty()) return defaultCainfo; return string_value; #else return String{}; #endif } CURLcode CurlResource::ssl_ctx_callback(CURL *curl, void *sslctx, void *parm) { // Set defaults from config.hdf CURLcode r = curl_tls_workarounds_cb(curl, sslctx, parm); if (r != CURLE_OK) { return r; } // Convert params to proper types. SSL_CTX* ctx = (SSL_CTX*)sslctx; if (ctx == nullptr) { raise_warning("supplied argument is not a valid SSL_CTX"); return CURLE_FAILED_INIT; } CurlResource* cp = (CurlResource*)parm; if (cp == nullptr) { raise_warning("supplied argument is not a valid cURL handle resource"); return CURLE_FAILED_INIT; } #ifdef CERT_CACHE_SUPPORTED // Load the CA from the cache. if (cp->useCertCache()) { auto const cainfo = cp->cainfo(false); auto const store = [&] () -> X509_STORE* { { folly::SharedMutex::ReadHolder lock(s_mutex); auto const iter = s_certCache.find(cainfo); if (iter != s_certCache.end()) return iter->second; } STACK_OF(X509_INFO) *stack; BIO *in; in = BIO_new_file(cainfo.data(), "r"); if (!in) return nullptr; stack = PEM_X509_INFO_read_bio(in, nullptr, nullptr, (void*)""); BIO_free(in); if (!stack) return nullptr; auto const store = X509_STORE_new(); if (!store) return nullptr; X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST); X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN); unsigned count = 0; for (int i = 0; i < sk_X509_INFO_num(stack); i++) { X509_INFO* info; info = sk_X509_INFO_value(stack, i); if (info->x509) { if (!X509_STORE_add_cert(store, info->x509)) { X509_STORE_free(store); sk_X509_INFO_pop_free(stack, X509_INFO_free); return nullptr; } count++; } if (info->crl) { if (!X509_STORE_add_crl(store, info->crl)) { X509_STORE_free(store); sk_X509_INFO_pop_free(stack, X509_INFO_free); return nullptr; } count++; } } sk_X509_INFO_pop_free(stack, X509_INFO_free); if (count == 0) { X509_STORE_free(store); return nullptr; } folly::SharedMutex::WriteHolder lock(s_mutex); auto const iter = s_certCache.find(cainfo); if (iter != s_certCache.end()) { X509_STORE_free(store); return iter->second; } else { s_certCache.emplace(cainfo, store); return store; } }(); if (!store) return CURLE_FAILED_INIT; SSL_CTX_set1_cert_store(ctx, store); } #endif // Override cipher specs if necessary. if (cp->m_opts.exists(int64_t(CURLOPT_FB_TLS_CIPHER_SPEC))) { Variant untyped_value = cp->m_opts[int64_t(CURLOPT_FB_TLS_CIPHER_SPEC)]; if (untyped_value.isString() && !untyped_value.toString().empty()) { SSL_CTX_set_cipher_list(ctx, untyped_value.toString().c_str()); } else { raise_warning("CURLOPT_FB_TLS_CIPHER_SPEC requires a non-empty string"); } } // Override the maximum client TLS version if necessary. if (cp->m_opts.exists(int64_t(CURLOPT_FB_TLS_VER_MAX))) { // Get current options, unsetting the NO_TLSv1_* bits. long cur_opts = SSL_CTX_get_options(ctx); #ifdef SSL_OP_NO_TLSv1_1 cur_opts &= ~SSL_OP_NO_TLSv1_1; #endif #ifdef SSL_OP_NO_TLSv1_2 cur_opts &= ~SSL_OP_NO_TLSv1_2; #endif int64_t value = cp->m_opts[int64_t(CURLOPT_FB_TLS_VER_MAX)].toInt64(); if (value == CURLOPT_FB_TLS_VER_MAX_1_0) { #if defined (SSL_OP_NO_TLSv1_1) && defined (SSL_OP_NO_TLSv1_2) cur_opts |= SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2; #else raise_notice("Requesting SSL_OP_NO_TLSv1_1, but this version of " "SSL does not support that option"); #endif } else if (value == CURLOPT_FB_TLS_VER_MAX_1_1) { #ifdef SSL_OP_NO_TLSv1_2 cur_opts |= SSL_OP_NO_TLSv1_2; #else raise_notice("Requesting SSL_OP_NO_TLSv1_2, but this version of " "SSL does not support that option"); #endif } else if (value != CURLOPT_FB_TLS_VER_MAX_NONE) { raise_notice("Invalid CURLOPT_FB_TLS_VER_MAX value"); } SSL_CTX_set_options(ctx, cur_opts); } return CURLE_OK; } ///////////////////////////////////////////////////////////////////////////// }