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;
 }

Reply via email to