remwharead/src/curl_wrapper/src/curl_wrapper.cpp

241 lines
6.6 KiB
C++
Raw Normal View History

/* 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 "types.hpp"
#include <curl/curl.h>
#include <atomic>
#include <cstdint>
#include <exception>
#include <stdexcept>
#include <string>
namespace curl_wrapper
{
inline static std::atomic<std::uint64_t> curlwrapper_instances{0};
CURLWrapper::CURLWrapper()
{
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() noexcept
{
curl_easy_cleanup(_connection);
--curlwrapper_instances;
if (curlwrapper_instances == 0)
{
curl_global_cleanup();
}
}
string CURLWrapper::escape_url(const string_view url) const
{
char *cbuf{curl_easy_escape(_connection, url.data(),
static_cast<int>(url.size()))};
string sbuf{cbuf};
curl_free(cbuf);
return sbuf;
}
string CURLWrapper::unescape_url(const string_view url) const
{
char *cbuf{curl_easy_unescape(_connection, url.data(),
static_cast<int>(url.size()), nullptr)};
string sbuf{cbuf};
curl_free(cbuf);
return sbuf;
}
void CURLWrapper::set_useragent(const string_view useragent)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
check(curl_easy_setopt(_connection, CURLOPT_USERAGENT, useragent.data()));
}
void CURLWrapper::set_proxy(const string_view proxy)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
check(curl_easy_setopt(_connection, CURLOPT_PROXY, proxy.data()));
}
answer 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;
}
case http_method::HEAD:
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
check(curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "HEAD"));
break;
}
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{0}; // NOLINT(google-runtime-int)
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
check(curl_easy_getinfo(_connection, CURLINFO_RESPONSE_CODE, &http_status));
return {static_cast<std::uint16_t>(http_status), _buffer_headers,
_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() const noexcept
{
// NOTE: The string has to be static, or it'll vanish before it can be
// used. Couldn't find good documentation on that.
static string error_string;
error_string = _error_message;
if (!error_string.empty())
{
error_string = " " + error_string;
}
error_string = "libcurl error: " + std::to_string(error_code)
+ error_string;
return error_string.c_str();
}
} // namespace curl_wrapper