2020-11-03 22:53:20 +01:00
|
|
|
/* This file is part of curl_wrapper.
|
|
|
|
* Copyright © 2020 tastytea <tastytea@tastytea.de>
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
|
|
* the Free Software Foundation, version 3.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "curl_wrapper.hpp"
|
|
|
|
|
|
|
|
#include <curl/curl.h>
|
|
|
|
|
|
|
|
#include <atomic>
|
|
|
|
#include <cstdint>
|
|
|
|
#include <exception>
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
namespace curl_wrapper
|
|
|
|
{
|
|
|
|
|
|
|
|
static std::atomic<std::uint64_t> curlwrapper_instances{0};
|
|
|
|
|
|
|
|
void CURLWrapper::init()
|
|
|
|
{
|
|
|
|
if (curlwrapper_instances == 0)
|
|
|
|
{
|
|
|
|
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
|
|
|
check(curl_global_init(CURL_GLOBAL_ALL));
|
|
|
|
}
|
|
|
|
++curlwrapper_instances;
|
|
|
|
|
|
|
|
_connection = curl_easy_init();
|
|
|
|
if (_connection == nullptr)
|
|
|
|
{
|
|
|
|
throw std::runtime_error{"Failed to initialize curl."};
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
curl_easy_setopt(_connection, CURLOPT_ERRORBUFFER, _buffer_error);
|
|
|
|
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
curl_easy_setopt(_connection, CURLOPT_WRITEFUNCTION, writer_body_wrapper);
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
curl_easy_setopt(_connection, CURLOPT_WRITEDATA, this);
|
|
|
|
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
curl_easy_setopt(_connection, CURLOPT_HEADERFUNCTION,
|
|
|
|
writer_headers_wrapper);
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
curl_easy_setopt(_connection, CURLOPT_HEADERDATA, this);
|
|
|
|
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
check(curl_easy_setopt(_connection, CURLOPT_FOLLOWLOCATION, 1L));
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
curl_easy_setopt(_connection, CURLOPT_MAXREDIRS, 5L);
|
|
|
|
}
|
|
|
|
|
|
|
|
CURLWrapper::CURLWrapper()
|
|
|
|
: _connection{nullptr}
|
|
|
|
{
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
CURLWrapper::CURLWrapper(const CURLWrapper &)
|
|
|
|
: _connection{nullptr}
|
|
|
|
{
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
CURLWrapper::~CURLWrapper() noexcept
|
|
|
|
{
|
|
|
|
curl_easy_cleanup(_connection);
|
|
|
|
--curlwrapper_instances;
|
|
|
|
|
|
|
|
if (curlwrapper_instances == 0)
|
|
|
|
{
|
|
|
|
curl_global_cleanup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CURLWrapper::set_useragent(string_view useragent)
|
|
|
|
{
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
check(curl_easy_setopt(_connection, CURLOPT_USERAGENT, useragent.data()));
|
|
|
|
}
|
|
|
|
|
|
|
|
string CURLWrapper::make_http_request(http_method method, string_view uri)
|
|
|
|
{
|
|
|
|
_buffer_headers.clear();
|
|
|
|
_buffer_body.clear();
|
|
|
|
|
|
|
|
switch (method)
|
|
|
|
{
|
|
|
|
case http_method::DELETE:
|
|
|
|
{
|
|
|
|
// NOTE: Use CURLOPT_MIMEPOST, then set to DELETE to send data.
|
|
|
|
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
check(curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "DELETE"));
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case http_method::GET:
|
|
|
|
{
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
curl_easy_setopt(_connection, CURLOPT_HTTPGET, 1L);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2020-11-07 23:43:58 +01:00
|
|
|
case http_method::HEAD:
|
|
|
|
{
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
check(curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "HEAD"));
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2020-11-03 22:53:20 +01:00
|
|
|
case http_method::PATCH:
|
|
|
|
{
|
|
|
|
// NOTE: Use CURLOPT_MIMEPOST, then set to PATCH to send data.
|
|
|
|
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
check(curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "PATCH"));
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case http_method::POST:
|
|
|
|
{
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
curl_easy_setopt(_connection, CURLOPT_POST, 1L);
|
|
|
|
|
|
|
|
// NOTE: Use CURLOPT_MIMEPOST to send data.
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case http_method::PUT:
|
|
|
|
{
|
|
|
|
// NOTE: Use CURLOPT_MIMEPOST, then set to PUT to send data.
|
|
|
|
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
check(curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "PUT"));
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
check(curl_easy_setopt(_connection, CURLOPT_URL, uri.data()));
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
check(curl_easy_perform(_connection));
|
|
|
|
}
|
|
|
|
catch (const CURLException &e)
|
|
|
|
{
|
|
|
|
// PARTIAL_FILE error seems to be normal for HEAD requests.
|
|
|
|
if (!(method == http_method::HEAD
|
|
|
|
&& e.error_code == CURLE_PARTIAL_FILE))
|
|
|
|
{
|
|
|
|
std::rethrow_exception(std::current_exception());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
long http_status; // NOLINT(google-runtime-int)
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
|
|
curl_easy_getinfo(_connection, CURLINFO_RESPONSE_CODE, &http_status);
|
|
|
|
// status code is in http_status, headers are in _buffer_headers, body is in
|
|
|
|
// _buffer_body.
|
|
|
|
// TODO: Communicate the above.
|
|
|
|
return _buffer_body;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t CURLWrapper::writer_body(char *data, size_t size, size_t nmemb)
|
|
|
|
{
|
|
|
|
if (data == nullptr)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
_buffer_body.append(data, size * nmemb);
|
|
|
|
|
|
|
|
return size * nmemb;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t CURLWrapper::writer_headers(char *data, size_t size, size_t nmemb)
|
|
|
|
{
|
|
|
|
if (data == nullptr)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
_buffer_headers.append(data, size * nmemb);
|
|
|
|
|
|
|
|
return size * nmemb;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CURLWrapper::check(const CURLcode code)
|
|
|
|
{
|
|
|
|
if (code != CURLE_OK)
|
|
|
|
{
|
|
|
|
throw CURLException{code, _buffer_error};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *CURLException::what() noexcept
|
|
|
|
{
|
|
|
|
if (!_error_message.empty())
|
|
|
|
{
|
|
|
|
_error_message = ": " + _error_message;
|
|
|
|
}
|
|
|
|
_error_message = "libcurl_error: " + std::to_string(error_code)
|
|
|
|
+ _error_message;
|
|
|
|
return _error_message.c_str();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace curl_wrapper
|