This is an automated email from the ASF dual-hosted git repository.
xyz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pulsar-client-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 787bfd0 [fix] Add the curl wrapper to avoid inconsistent curl options
(#313)
787bfd0 is described below
commit 787bfd0e450427ff753f6a54b63f7fe18a684529
Author: Yunze Xu <[email protected]>
AuthorDate: Wed Sep 13 10:53:27 2023 +0800
[fix] Add the curl wrapper to avoid inconsistent curl options (#313)
### Motivation
When libcurl is used in `AuthOauth2`, the `CURLOPT_NOSIGNAL` option is
not set, i.e. it will be the default value so that the
`Curl_resolv_timeout` function might crash in multi-threading
environment.
```
#2 0xf630 in _L_unlock_13 from /lib64/libpthread.so.0 (0x34)
#3 0x2e6c7f in Curl_failf from /usr/local/bin/***/libpulsar.so (0x6f)
#4 0x30a285 in Curl_resolv_timeout from /usr/local/bin/***/libpulsar.so
(0x95)
```
Since there are many duplicated code when calling curl C APIs, it's hard to
notice that `CURLOPT_NOSIGNAL` is not configured in `AuthOauth2`.
### Modifications
Introduce a `CurlWrapper` class that sets the same options to reduce the
duplicated code and adapting consistent behaviors unless a few options.
---
lib/CurlWrapper.h | 180 +++++++++++++++++++++++++++++++++++++++++++
lib/HTTPLookupService.cc | 132 ++++++++-----------------------
lib/HTTPLookupService.h | 7 --
lib/auth/AuthOauth2.cc | 120 ++++++++++-------------------
lib/auth/athenz/ZTSClient.cc | 81 ++++++++-----------
5 files changed, 283 insertions(+), 237 deletions(-)
diff --git a/lib/CurlWrapper.h b/lib/CurlWrapper.h
new file mode 100644
index 0000000..89b7919
--- /dev/null
+++ b/lib/CurlWrapper.h
@@ -0,0 +1,180 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+#pragma once
+
+#include <assert.h>
+#include <curl/curl.h>
+
+#include <string>
+
+namespace pulsar {
+
+struct CurlInitializer {
+ CurlInitializer() { curl_global_init(CURL_GLOBAL_ALL); }
+ ~CurlInitializer() { curl_global_cleanup(); }
+};
+static CurlInitializer curlInitializer;
+
+class CurlWrapper {
+ public:
+ CurlWrapper() noexcept {}
+ ~CurlWrapper() {
+ if (handle_) {
+ curl_easy_cleanup(handle_);
+ }
+ }
+
+ char* escape(const std::string& s) const {
+ assert(handle_);
+ return curl_easy_escape(handle_, s.c_str(), s.length());
+ }
+
+ // It must be called before calling other methods
+ bool init() {
+ handle_ = curl_easy_init();
+ return handle_ != nullptr;
+ }
+
+ struct Options {
+ std::string method;
+ std::string postFields;
+ std::string userAgent;
+ int timeoutInSeconds{0};
+ int maxLookupRedirects{-1};
+ };
+
+ struct TlsContext {
+ std::string trustCertsFilePath;
+ bool validateHostname{true};
+ bool allowInsecure{false};
+ std::string certPath;
+ std::string keyPath;
+ };
+
+ struct Result {
+ CURLcode code;
+ std::string responseData;
+ long responseCode;
+ std::string redirectUrl;
+ std::string error;
+ std::string serverError;
+ };
+
+ Result get(const std::string& url, const std::string& header, const
Options& options,
+ const TlsContext* tlsContext) const;
+
+ private:
+ CURL* handle_;
+
+ struct CurlListGuard {
+ curl_slist*& headers;
+
+ CurlListGuard(curl_slist*& headers) : headers(headers) {}
+ ~CurlListGuard() {
+ if (headers) {
+ curl_slist_free_all(headers);
+ }
+ }
+ };
+};
+
+inline CurlWrapper::Result CurlWrapper::get(const std::string& url, const
std::string& header,
+ const Options& options, const
TlsContext* tlsContext) const {
+ assert(handle_);
+ curl_easy_setopt(handle_, CURLOPT_URL, url.c_str());
+
+ if (!options.postFields.empty()) {
+ curl_easy_setopt(handle_, CURLOPT_CUSTOMREQUEST, "POST");
+ curl_easy_setopt(handle_, CURLOPT_POSTFIELDS,
options.postFields.c_str());
+ }
+
+ // Write response
+ curl_easy_setopt(
+ handle_, CURLOPT_WRITEFUNCTION,
+ +[](char* buffer, size_t size, size_t nitems, void* outstream) ->
size_t {
+ static_cast<std::string*>(outstream)->append(buffer, size *
nitems);
+ return size * nitems;
+ });
+ std::string response;
+ curl_easy_setopt(handle_, CURLOPT_WRITEDATA, &response);
+
+ // New connection is made for each call
+ curl_easy_setopt(handle_, CURLOPT_FRESH_CONNECT, 1L);
+ curl_easy_setopt(handle_, CURLOPT_FORBID_REUSE, 1L);
+
+ // Skipping signal handling - results in timeouts not honored during the
DNS lookup
+ // Without this config, Curl_resolv_timeout might crash in multi-threads
environment
+ curl_easy_setopt(handle_, CURLOPT_NOSIGNAL, 1L);
+
+ curl_easy_setopt(handle_, CURLOPT_TIMEOUT, options.timeoutInSeconds);
+ if (!options.userAgent.empty()) {
+ curl_easy_setopt(handle_, CURLOPT_USERAGENT,
options.userAgent.c_str());
+ }
+ curl_easy_setopt(handle_, CURLOPT_FAILONERROR, 1L);
+
+ // Redirects
+ curl_easy_setopt(handle_, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(handle_, CURLOPT_MAXREDIRS, options.maxLookupRedirects);
+
+ char errorBuffer[CURL_ERROR_SIZE] = "";
+ curl_easy_setopt(handle_, CURLOPT_ERRORBUFFER, errorBuffer);
+
+ curl_slist* headers = nullptr;
+ CurlListGuard headersGuard{headers};
+ if (!header.empty()) {
+ headers = curl_slist_append(headers, header.c_str());
+ curl_easy_setopt(handle_, CURLOPT_HTTPHEADER, headers);
+ }
+
+ if (tlsContext) {
+ CURLcode code;
+ code = curl_easy_setopt(handle_, CURLOPT_SSLENGINE, nullptr);
+ if (code != CURLE_OK) {
+ return {code, "", -1, "",
+ "Unable to load SSL engine for url " + url + ": " +
curl_easy_strerror(code)};
+ }
+ code = curl_easy_setopt(handle_, CURLOPT_SSLENGINE_DEFAULT, 1L);
+ if (code != CURLE_OK) {
+ return {code, "", -1, "",
+ "Unable to load SSL engine as default for url " + url + ":
" + curl_easy_strerror(code)};
+ }
+ curl_easy_setopt(handle_, CURLOPT_SSL_VERIFYHOST,
tlsContext->validateHostname ? 1L : 0L);
+ curl_easy_setopt(handle_, CURLOPT_SSL_VERIFYPEER,
tlsContext->allowInsecure ? 0L : 1L);
+ if (!tlsContext->trustCertsFilePath.empty()) {
+ curl_easy_setopt(handle_, CURLOPT_CAINFO,
tlsContext->trustCertsFilePath.c_str());
+ }
+ if (!tlsContext->certPath.empty() && !tlsContext->keyPath.empty()) {
+ curl_easy_setopt(handle_, CURLOPT_SSLCERT,
tlsContext->certPath.c_str());
+ curl_easy_setopt(handle_, CURLOPT_SSLKEY,
tlsContext->keyPath.c_str());
+ }
+ }
+
+ auto res = curl_easy_perform(handle_);
+ long responseCode;
+ curl_easy_getinfo(handle_, CURLINFO_RESPONSE_CODE, &responseCode);
+ Result result{res, response, responseCode, "", "",
std::string(errorBuffer)};
+ if (responseCode == 307 || responseCode == 302 || responseCode == 301) {
+ char* url;
+ curl_easy_getinfo(handle_, CURLINFO_REDIRECT_URL, &url);
+ result.redirectUrl = url;
+ }
+ return result;
+}
+
+} // namespace pulsar
diff --git a/lib/HTTPLookupService.cc b/lib/HTTPLookupService.cc
index 1956e01..4ec72c1 100644
--- a/lib/HTTPLookupService.cc
+++ b/lib/HTTPLookupService.cc
@@ -18,12 +18,12 @@
*/
#include "HTTPLookupService.h"
-#include <curl/curl.h>
#include <pulsar/Version.h>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
+#include "CurlWrapper.h"
#include "ExecutorService.h"
#include "Int64SerDes.h"
#include "LogUtils.h"
@@ -46,16 +46,6 @@ const static std::string ADMIN_PATH_V2 = "/admin/v2/";
const static std::string PARTITION_METHOD_NAME = "partitions";
const static int NUMBER_OF_LOOKUP_THREADS = 1;
-static inline bool needRedirection(long code) { return (code == 307 || code ==
302 || code == 301); }
-
-HTTPLookupService::CurlInitializer::CurlInitializer() {
- // Once per application - https://curl.haxx.se/mail/lib-2015-11/0052.html
- curl_global_init(CURL_GLOBAL_ALL);
-}
-HTTPLookupService::CurlInitializer::~CurlInitializer() {
curl_global_cleanup(); }
-
-HTTPLookupService::CurlInitializer HTTPLookupService::curlInitializer;
-
HTTPLookupService::HTTPLookupService(ServiceNameResolver &serviceNameResolver,
const ClientConfiguration
&clientConfiguration,
const AuthenticationPtr &authData)
@@ -182,11 +172,6 @@ Future<Result, SchemaInfo>
HTTPLookupService::getSchema(const TopicNamePtr &topi
return promise.getFuture();
}
-static size_t curlWriteCallback(void *contents, size_t size, size_t nmemb,
void *responseDataPtr) {
- ((std::string *)responseDataPtr)->append((char *)contents, size * nmemb);
- return size * nmemb;
-}
-
void
HTTPLookupService::handleNamespaceTopicsHTTPRequest(NamespaceTopicsPromise
promise,
const std::string
completeUrl) {
std::string responseData;
@@ -209,111 +194,61 @@ Result HTTPLookupService::sendHTTPRequest(std::string
completeUrl, std::string &
uint16_t reqCount = 0;
Result retResult = ResultOk;
while (++reqCount <= maxLookupRedirects_) {
- CURL *handle;
- CURLcode res;
- std::string version = std::string("Pulsar-CPP-v") + PULSAR_VERSION_STR;
- handle = curl_easy_init();
-
- if (!handle) {
- LOG_ERROR("Unable to curl_easy_init for url " << completeUrl);
- // No curl_easy_cleanup required since handle not initialized
- return ResultLookupError;
- }
- // set URL
- curl_easy_setopt(handle, CURLOPT_URL, completeUrl.c_str());
-
- // Write callback
- curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curlWriteCallback);
- curl_easy_setopt(handle, CURLOPT_WRITEDATA, &responseData);
-
- // New connection is made for each call
- curl_easy_setopt(handle, CURLOPT_FRESH_CONNECT, 1L);
- curl_easy_setopt(handle, CURLOPT_FORBID_REUSE, 1L);
-
- // Skipping signal handling - results in timeouts not honored during
the DNS lookup
- curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L);
-
- // Timer
- curl_easy_setopt(handle, CURLOPT_TIMEOUT, lookupTimeoutInSeconds_);
-
- // Set User Agent
- curl_easy_setopt(handle, CURLOPT_USERAGENT, version.c_str());
-
- // Fail if HTTP return code >=400
- curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1L);
-
// Authorization data
AuthenticationDataPtr authDataContent;
Result authResult = authenticationPtr_->getAuthData(authDataContent);
if (authResult != ResultOk) {
LOG_ERROR("Failed to getAuthData: " << authResult);
- curl_easy_cleanup(handle);
return authResult;
}
- struct curl_slist *list = NULL;
- if (authDataContent->hasDataForHttp()) {
- list = curl_slist_append(list,
authDataContent->getHttpHeaders().c_str());
+
+ CurlWrapper curl;
+ if (!curl.init()) {
+ LOG_ERROR("Unable to curl_easy_init for url " << completeUrl);
+ return ResultLookupError;
}
- curl_easy_setopt(handle, CURLOPT_HTTPHEADER, list);
- // TLS
+ std::unique_ptr<CurlWrapper::TlsContext> tlsContext;
if (isUseTls_) {
- if (curl_easy_setopt(handle, CURLOPT_SSLENGINE, NULL) != CURLE_OK)
{
- LOG_ERROR("Unable to load SSL engine for url " << completeUrl);
- curl_easy_cleanup(handle);
- return ResultConnectError;
- }
- if (curl_easy_setopt(handle, CURLOPT_SSLENGINE_DEFAULT, 1L) !=
CURLE_OK) {
- LOG_ERROR("Unable to load SSL engine as default, for url " <<
completeUrl);
- curl_easy_cleanup(handle);
- return ResultConnectError;
- }
- curl_easy_setopt(handle, CURLOPT_SSLCERTTYPE, "PEM");
-
- if (tlsAllowInsecure_) {
- curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
- } else {
- curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1L);
- }
-
- if (!tlsTrustCertsFilePath_.empty()) {
- curl_easy_setopt(handle, CURLOPT_CAINFO,
tlsTrustCertsFilePath_.c_str());
- }
-
- curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST,
tlsValidateHostname_ ? 1L : 0L);
-
+ tlsContext.reset(new CurlWrapper::TlsContext);
+ tlsContext->trustCertsFilePath = tlsTrustCertsFilePath_;
+ tlsContext->validateHostname = tlsValidateHostname_;
+ tlsContext->allowInsecure = tlsAllowInsecure_;
if (authDataContent->hasDataForTls()) {
- curl_easy_setopt(handle, CURLOPT_SSLCERT,
authDataContent->getTlsCertificates().c_str());
- curl_easy_setopt(handle, CURLOPT_SSLKEY,
authDataContent->getTlsPrivateKey().c_str());
+ tlsContext->certPath = authDataContent->getTlsCertificates();
+ tlsContext->keyPath = authDataContent->getTlsPrivateKey();
} else {
- if (!tlsPrivateFilePath_.empty() &&
!tlsCertificateFilePath_.empty()) {
- curl_easy_setopt(handle, CURLOPT_SSLCERT,
tlsCertificateFilePath_.c_str());
- curl_easy_setopt(handle, CURLOPT_SSLKEY,
tlsPrivateFilePath_.c_str());
- }
+ tlsContext->certPath = tlsCertificateFilePath_;
+ tlsContext->keyPath = tlsPrivateFilePath_;
}
}
LOG_INFO("Curl [" << reqCount << "] Lookup Request sent for " <<
completeUrl);
+ CurlWrapper::Options options;
+ options.timeoutInSeconds = lookupTimeoutInSeconds_;
+ options.userAgent = std::string("Pulsar-CPP-v") + PULSAR_VERSION_STR;
+ options.maxLookupRedirects = 1; // redirection is implemented by the
outer loop
+ auto result = curl.get(completeUrl, authDataContent->getHttpHeaders(),
options, tlsContext.get());
+ const auto &error = result.error;
+ if (!error.empty()) {
+ LOG_ERROR(completeUrl << " failed: " << error);
+ return ResultConnectError;
+ }
- // Make get call to server
- res = curl_easy_perform(handle);
-
- curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode);
+ responseData = result.responseData;
+ responseCode = result.responseCode;
+ auto res = result.code;
LOG_INFO("Response received for url " << completeUrl << " responseCode
" << responseCode
<< " curl res " << res);
- // Free header list
- curl_slist_free_all(list);
-
+ const auto &redirectUrl = result.redirectUrl;
switch (res) {
case CURLE_OK:
if (responseCode == 200) {
retResult = ResultOk;
- } else if (needRedirection(responseCode)) {
- char *url = NULL;
- curl_easy_getinfo(handle, CURLINFO_REDIRECT_URL, &url);
- LOG_INFO("Response from url " << completeUrl << " to new
url " << url);
- completeUrl = url;
+ } else if (!redirectUrl.empty()) {
+ LOG_INFO("Response from url " << completeUrl << " to new
url " << redirectUrl);
+ completeUrl = redirectUrl;
retResult = ResultLookupError;
} else {
retResult = ResultLookupError;
@@ -342,8 +277,7 @@ Result HTTPLookupService::sendHTTPRequest(std::string
completeUrl, std::string &
retResult = ResultLookupError;
break;
}
- curl_easy_cleanup(handle);
- if (!needRedirection(responseCode)) {
+ if (redirectUrl.empty()) {
break;
}
}
diff --git a/lib/HTTPLookupService.h b/lib/HTTPLookupService.h
index a1e12fc..cf0b0ad 100644
--- a/lib/HTTPLookupService.h
+++ b/lib/HTTPLookupService.h
@@ -31,13 +31,6 @@ using NamespaceTopicsPromisePtr =
std::shared_ptr<NamespaceTopicsPromise>;
using GetSchemaPromise = Promise<Result, SchemaInfo>;
class HTTPLookupService : public LookupService, public
std::enable_shared_from_this<HTTPLookupService> {
- class CurlInitializer {
- public:
- CurlInitializer();
- ~CurlInitializer();
- };
- static CurlInitializer curlInitializer;
-
enum RequestType
{
Lookup,
diff --git a/lib/auth/AuthOauth2.cc b/lib/auth/AuthOauth2.cc
index 919f2bf..799843b 100644
--- a/lib/auth/AuthOauth2.cc
+++ b/lib/auth/AuthOauth2.cc
@@ -18,8 +18,6 @@
*/
#include "AuthOauth2.h"
-#include <curl/curl.h>
-
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <sstream>
@@ -27,6 +25,7 @@
#include "InitialAuthData.h"
#include "lib/Base64Utils.h"
+#include "lib/CurlWrapper.h"
#include "lib/LogUtils.h"
DECLARE_LOG_OBJECT()
@@ -208,11 +207,6 @@ ClientCredentialFlow::ClientCredentialFlow(ParamMap&
params)
std::string ClientCredentialFlow::getTokenEndPoint() const { return
tokenEndPoint_; }
-static size_t curlWriteCallback(void* contents, size_t size, size_t nmemb,
void* responseDataPtr) {
- ((std::string*)responseDataPtr)->append((char*)contents, size * nmemb);
- return size * nmemb;
-}
-
void ClientCredentialFlow::initialize() {
if (issuerUrl_.empty()) {
LOG_ERROR("Failed to initialize ClientCredentialFlow: issuer_url is
not set");
@@ -222,48 +216,37 @@ void ClientCredentialFlow::initialize() {
return;
}
- CURL* handle = curl_easy_init();
- CURLcode res;
- std::string responseData;
-
- // set header: json, request type: post
- struct curl_slist* list = NULL;
- list = curl_slist_append(list, "Accept: application/json");
- curl_easy_setopt(handle, CURLOPT_HTTPHEADER, list);
- curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "GET");
-
// set URL: well-know endpoint
std::string wellKnownUrl = issuerUrl_;
if (wellKnownUrl.back() == '/') {
wellKnownUrl.pop_back();
}
wellKnownUrl.append("/.well-known/openid-configuration");
- curl_easy_setopt(handle, CURLOPT_URL, wellKnownUrl.c_str());
-
- // Write callback
- curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curlWriteCallback);
- curl_easy_setopt(handle, CURLOPT_WRITEDATA, &responseData);
-
- // New connection is made for each call
- curl_easy_setopt(handle, CURLOPT_FRESH_CONNECT, 1L);
- curl_easy_setopt(handle, CURLOPT_FORBID_REUSE, 1L);
-
- curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
-
- char errorBuffer[CURL_ERROR_SIZE];
- curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errorBuffer);
+ CurlWrapper curl;
+ if (!curl.init()) {
+ LOG_ERROR("Failed to initialize curl");
+ return;
+ }
+ std::unique_ptr<CurlWrapper::TlsContext> tlsContext;
if (!tlsTrustCertsFilePath_.empty()) {
- curl_easy_setopt(handle, CURLOPT_CAINFO,
tlsTrustCertsFilePath_.c_str());
+ tlsContext.reset(new CurlWrapper::TlsContext);
+ tlsContext->trustCertsFilePath = tlsTrustCertsFilePath_;
+ }
+
+ auto result = curl.get(wellKnownUrl, "Accept: application/json", {},
tlsContext.get());
+ if (!result.error.empty()) {
+ LOG_ERROR("Failed to get the well-known configuration " << issuerUrl_
<< ": " << result.error);
+ return;
}
- // Make get call to server
- res = curl_easy_perform(handle);
+ const auto res = result.code;
+ const auto response_code = result.responseCode;
+ const auto& responseData = result.responseData;
+ const auto& errorBuffer = result.serverError;
switch (res) {
case CURLE_OK:
- long response_code;
- curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code);
LOG_DEBUG("Received well-known configuration data " << issuerUrl_
<< " code " << response_code);
if (response_code == 200) {
boost::property_tree::ptree root;
@@ -290,9 +273,6 @@ void ClientCredentialFlow::initialize() {
<< issuerUrl_ << ". Error Code " << res << ": " <<
errorBuffer);
break;
}
- // Free header list
- curl_slist_free_all(list);
- curl_easy_cleanup(handle);
}
void ClientCredentialFlow::close() {}
@@ -312,7 +292,7 @@ ParamMap ClientCredentialFlow::generateParamMap() const {
return params;
}
-static std::string buildClientCredentialsBody(CURL* curl, const ParamMap&
params) {
+static std::string buildClientCredentialsBody(CurlWrapper& curl, const
ParamMap& params) {
std::ostringstream oss;
bool addSeparater = false;
@@ -323,12 +303,12 @@ static std::string buildClientCredentialsBody(CURL* curl,
const ParamMap& params
addSeparater = true;
}
- char* encodedKey = curl_easy_escape(curl, kv.first.c_str(),
kv.first.length());
+ char* encodedKey = curl.escape(kv.first);
if (!encodedKey) {
LOG_ERROR("curl_easy_escape for " << kv.first << " failed");
continue;
}
- char* encodedValue = curl_easy_escape(curl, kv.second.c_str(),
kv.second.length());
+ char* encodedValue = curl.escape(kv.second);
if (!encodedValue) {
LOG_ERROR("curl_easy_escape for " << kv.second << " failed");
continue;
@@ -349,51 +329,32 @@ Oauth2TokenResultPtr ClientCredentialFlow::authenticate()
{
return resultPtr;
}
- CURL* handle = curl_easy_init();
- const auto postData = buildClientCredentialsBody(handle,
generateParamMap());
+ CurlWrapper curl;
+ if (!curl.init()) {
+ LOG_ERROR("Failed to initialize curl");
+ return resultPtr;
+ }
+ auto postData = buildClientCredentialsBody(curl, generateParamMap());
if (postData.empty()) {
- curl_easy_cleanup(handle);
return resultPtr;
}
LOG_DEBUG("Generate URL encoded body for ClientCredentialFlow: " <<
postData);
- CURLcode res;
- std::string responseData;
-
- struct curl_slist* list = NULL;
- list = curl_slist_append(list, "Content-Type:
application/x-www-form-urlencoded");
- curl_easy_setopt(handle, CURLOPT_HTTPHEADER, list);
- curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "POST");
-
- // set URL: issuerUrl
- curl_easy_setopt(handle, CURLOPT_URL, tokenEndPoint_.c_str());
-
- // Write callback
- curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curlWriteCallback);
- curl_easy_setopt(handle, CURLOPT_WRITEDATA, &responseData);
-
- // New connection is made for each call
- curl_easy_setopt(handle, CURLOPT_FRESH_CONNECT, 1L);
- curl_easy_setopt(handle, CURLOPT_FORBID_REUSE, 1L);
-
- curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
-
- curl_easy_setopt(handle, CURLOPT_POSTFIELDS, postData.c_str());
-
- char errorBuffer[CURL_ERROR_SIZE];
- curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errorBuffer);
-
- if (!tlsTrustCertsFilePath_.empty()) {
- curl_easy_setopt(handle, CURLOPT_CAINFO,
tlsTrustCertsFilePath_.c_str());
+ CurlWrapper::Options options;
+ options.postFields = std::move(postData);
+ auto result =
+ curl.get(tokenEndPoint_, "Content-Type:
application/x-www-form-urlencoded", options, nullptr);
+ if (!result.error.empty()) {
+ LOG_ERROR("Failed to get the well-known configuration " << issuerUrl_
<< ": " << result.error);
+ return resultPtr;
}
-
- // Make get call to server
- res = curl_easy_perform(handle);
+ const auto res = result.code;
+ const auto response_code = result.responseCode;
+ const auto& responseData = result.responseData;
+ const auto& errorBuffer = result.serverError;
switch (res) {
case CURLE_OK:
- long response_code;
- curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code);
LOG_DEBUG("Response received for issuerurl " << issuerUrl_ << "
code " << response_code);
if (response_code == 200) {
boost::property_tree::ptree root;
@@ -429,9 +390,6 @@ Oauth2TokenResultPtr ClientCredentialFlow::authenticate() {
<< errorBuffer << "
passedin: " << postData);
break;
}
- // Free header list
- curl_slist_free_all(list);
- curl_easy_cleanup(handle);
return resultPtr;
}
diff --git a/lib/auth/athenz/ZTSClient.cc b/lib/auth/athenz/ZTSClient.cc
index 00681c3..230713e 100644
--- a/lib/auth/athenz/ZTSClient.cc
+++ b/lib/auth/athenz/ZTSClient.cc
@@ -20,6 +20,7 @@
#include <sstream>
+#include "lib/CurlWrapper.h"
#include "lib/LogUtils.h"
#ifndef _MSC_VER
@@ -27,7 +28,6 @@
#else
#include <stdio.h>
#endif
-#include <curl/curl.h>
#include <openssl/ec.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
@@ -287,11 +287,6 @@ const std::string ZTSClient::getPrincipalToken() const {
return principalToken;
}
-static size_t curlWriteCallback(void *contents, size_t size, size_t nmemb,
void *responseDataPtr) {
- ((std::string *)responseDataPtr)->append((char *)contents, size * nmemb);
- return size * nmemb;
-}
-
std::mutex cacheMtx_;
const std::string ZTSClient::getRoleToken() {
RoleToken roleToken;
@@ -311,72 +306,59 @@ const std::string ZTSClient::getRoleToken() {
completeUrl += "?minExpiryTime=" +
std::to_string(ROLE_TOKEN_EXPIRATION_MIN_TIME_SEC);
completeUrl += "&maxExpiryTime=" +
std::to_string(ROLE_TOKEN_EXPIRATION_MAX_TIME_SEC);
- CURL *handle;
- CURLcode res;
- std::string responseData;
-
- handle = curl_easy_init();
-
- // set URL
- curl_easy_setopt(handle, CURLOPT_URL, completeUrl.c_str());
-
- // Write callback
- curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curlWriteCallback);
- curl_easy_setopt(handle, CURLOPT_WRITEDATA, &responseData);
-
- // New connection is made for each call
- curl_easy_setopt(handle, CURLOPT_FRESH_CONNECT, 1L);
- curl_easy_setopt(handle, CURLOPT_FORBID_REUSE, 1L);
-
- // Skipping signal handling - results in timeouts not honored during the
DNS lookup
- curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L);
-
- // Timer
- curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, REQUEST_TIMEOUT);
-
- // Redirects
- curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
- curl_easy_setopt(handle, CURLOPT_MAXREDIRS, MAX_HTTP_REDIRECTS);
-
- // Fail if HTTP return code >= 400
- curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1L);
-
+ std::string trustCertsFilePath;
+ std::unique_ptr<CurlWrapper::TlsContext> tlsContext;
if (!caCert_.scheme.empty()) {
if (caCert_.scheme == "file") {
- curl_easy_setopt(handle, CURLOPT_CAINFO, caCert_.path.c_str());
+ tlsContext.reset(new CurlWrapper::TlsContext);
+ tlsContext->trustCertsFilePath = caCert_.path;
} else {
LOG_ERROR("URI scheme not supported in caCert: " <<
caCert_.scheme);
}
}
- struct curl_slist *list = NULL;
+ std::string header;
if (enableX509CertChain_) {
if (x509CertChain_.scheme == "file") {
- curl_easy_setopt(handle, CURLOPT_SSLCERT,
x509CertChain_.path.c_str());
+ if (!tlsContext) {
+ tlsContext.reset(new CurlWrapper::TlsContext);
+ }
+ tlsContext->certPath = x509CertChain_.path;
} else {
LOG_ERROR("URI scheme not supported in x509CertChain: " <<
x509CertChain_.scheme);
}
if (privateKeyUri_.scheme == "file") {
- curl_easy_setopt(handle, CURLOPT_SSLKEY,
privateKeyUri_.path.c_str());
+ if (!tlsContext) {
+ tlsContext.reset(new CurlWrapper::TlsContext);
+ }
+ tlsContext->keyPath = privateKeyUri_.path;
} else {
LOG_ERROR("URI scheme not supported in privateKey: " <<
privateKeyUri_.scheme);
}
} else {
- std::string httpHeader = principalHeader_ + ": " + getPrincipalToken();
- list = curl_slist_append(list, httpHeader.c_str());
- curl_easy_setopt(handle, CURLOPT_HTTPHEADER, list);
+ header = principalHeader_ + ": " + getPrincipalToken();
}
- // Make get call to server
- res = curl_easy_perform(handle);
+ CurlWrapper curl;
+ if (!curl.init()) {
+ LOG_ERROR("Failed to init curl");
+ return "";
+ }
- // Free header list
- curl_slist_free_all(list);
+ CurlWrapper::Options options;
+ options.timeoutInSeconds = REQUEST_TIMEOUT;
+ options.maxLookupRedirects = MAX_HTTP_REDIRECTS;
+ auto result = curl.get(completeUrl, header, options, tlsContext.get());
+ if (!result.error.empty()) {
+ LOG_ERROR(completeUrl << " failed: " << result.error);
+ return "";
+ }
+ auto res = result.code;
+ const auto &responseData = result.responseData;
+ long response_code = result.responseCode;
switch (res) {
case CURLE_OK:
- long response_code;
- curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code);
LOG_DEBUG("Response received for url " << completeUrl << " code "
<< response_code);
if (response_code == 200) {
ptree::ptree root;
@@ -403,7 +385,6 @@ const std::string ZTSClient::getRoleToken() {
LOG_ERROR("Response failed for url " << completeUrl << ". Error
Code " << res);
break;
}
- curl_easy_cleanup(handle);
return roleToken.token;
}