Created
November 9, 2021 18:01
-
-
Save ITotalJustice/e07f7e20e67c33706b7493f4a9b3826d to your computer and use it in GitHub Desktop.
curl wrapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "curl_wrapper.hpp" | |
#include "../../utils/logger.hpp" | |
// todo: fix dkp curl. complains about fd_set missing | |
#ifdef __SWITCH__ | |
#include <sys/select.h> | |
#endif | |
#include <curl/curl.h> | |
namespace curl { | |
static auto writeListCB(void *contents, std::size_t size, std::size_t nmemb, void *userp) -> std::size_t { | |
auto userdata = static_cast<WriteUserData*>(userp); | |
if (userdata->stop_callback) { | |
if (userdata->stop_callback()) { | |
return 0; | |
} | |
} | |
userdata->data.append((char*)contents, size * nmemb); | |
return size * nmemb; | |
} | |
CurlWrapper::~CurlWrapper() { | |
if (this->header_list) { | |
curl_slist_free_all(this->header_list); | |
} | |
if (curl != nullptr) { | |
curl_easy_cleanup(this->curl); | |
} | |
} | |
auto CurlWrapper::SetHeader(const Header& header) -> void { | |
curl_slist* chunk{}; | |
for (auto& [key, value] : header) { | |
// append value (if any). | |
auto header_str = key; | |
if (value.empty()) { | |
header_str += ";"; | |
} else { | |
header_str += ": " + value; | |
} | |
// try to append header chunk. | |
auto temp = curl_slist_append(chunk, header_str.c_str()); | |
if (temp) { | |
chunk = temp; | |
} | |
} | |
if (this->header_list) { | |
curl_slist_free_all(this->header_list); | |
} | |
this->header_list = chunk; | |
this->new_header = false; | |
} | |
auto CurlWrapper::SetOption(Header&& header) -> void { | |
this->header = header; | |
this->new_header = true; | |
} | |
auto CurlWrapper::SetOption(Fields&& fields) -> void { | |
this->fields = fields; | |
} | |
auto CurlWrapper::SetOption(Url&& url) -> void { | |
this->url = url; | |
} | |
auto CurlWrapper::SetOption(StopCallback&& callback) -> void { | |
this->stop_callback = callback; | |
} | |
struct Result { | |
bool good; | |
long response_code; | |
std::optional<std::string> error_message; | |
std::optional<std::string> data; | |
auto IsGood() const noexcept { | |
return this->good; | |
} | |
auto Response() const noexcept { | |
return this->response_code; | |
} | |
auto HasError() const noexcept { | |
return this->error_message.has_value() && !this->error_message->empty(); | |
} | |
auto HasData() const noexcept { | |
return this->data.has_value() && !this->data->empty(); | |
} | |
}; | |
auto CurlWrapper::InternalDownload() -> std::optional<std::string> { | |
if (url.empty()) { | |
LOG("[CURL] url is empty!\n"); | |
return {}; | |
} | |
if (!curl) { | |
curl = curl_easy_init(); | |
} | |
if (!curl) { | |
LOG("[CURL] failed to init curl easy\n"); | |
return {}; | |
} | |
// curl_global_init(CURL_GLOBAL_ALL); | |
curl_easy_reset(curl); | |
WriteUserData data{this->stop_callback}; | |
curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0"); | |
// old ssl stuff i used. not sure if i need this still... | |
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); | |
// curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); | |
// curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); | |
if (this->new_header == true) { | |
this->SetHeader(*this->header); | |
} | |
if (this->header_list) { | |
curl_easy_setopt(this->curl, CURLOPT_HTTPHEADER, this->header_list); | |
} | |
if (this->fields) { | |
curl_easy_setopt(curl, CURLOPT_POST, 1L); | |
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, this->fields->c_str()); | |
// optional, but saves a call to strlen(). | |
// CURLOPT_POSTFIELDSIZE can also work but for smaller size. | |
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, this->fields->size()); | |
} | |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeListCB); | |
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); | |
long response_code; | |
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); | |
// LOG("%s %s line: %d\n", __FILE__, __func__, __LINE__); | |
if (auto r = curl_easy_perform(curl); r != CURLE_OK) { | |
LOG("[CURL] failed to perform easy %s\n", curl_easy_strerror(r)); | |
return {}; | |
} | |
return data.data; | |
} | |
} // namespace curl |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <string> | |
#include <optional> | |
#include <stop_token> | |
#include <functional> | |
#include <algorithm> | |
#include <numeric> | |
#include <map> | |
extern "C" { | |
typedef void CURL; | |
struct curl_slist; | |
}; | |
namespace curl { | |
using Header = std::map<std::string, std::string>; | |
// using DownloadCallback = std::function<std::size_t(void* contents, std::size_t size, std::size_t nmemb, void* userp)>; | |
// this is called in the internal libcurl's callbacks (download, progress..) | |
// return true to exit. | |
using StopCallback = std::function<bool()>; | |
// below is taken from cpr | |
template <class T> | |
class StringHolder { | |
public: | |
StringHolder() = default; | |
explicit StringHolder(const std::string& str) : str_(str) {} | |
explicit StringHolder(std::string&& str) : str_(std::move(str)) {} | |
explicit StringHolder(const char* str) : str_(str) {} | |
StringHolder(const char* str, size_t len) : str_(str, len) {} | |
StringHolder(const std::initializer_list<std::string> args) { | |
str_ = std::accumulate(args.begin(), args.end(), str_); | |
} | |
StringHolder(const StringHolder& other) = default; | |
StringHolder(StringHolder&& old) noexcept = default; | |
virtual ~StringHolder() = default; | |
StringHolder& operator=(StringHolder&& old) noexcept = default; | |
StringHolder& operator=(const StringHolder& other) = default; | |
explicit operator std::string() const { return str_; } | |
T operator+(const char* rhs) const { return T(str_ + rhs); } | |
T operator+(const std::string& rhs) const { return T(str_ + rhs); } | |
T operator+(const StringHolder<T>& rhs) const { return T(str_ + rhs.str_); } | |
void operator+=(const char* rhs) { str_ += rhs; } | |
void operator+=(const std::string& rhs) { str_ += rhs; } | |
void operator+=(const StringHolder<T>& rhs) { str_ += rhs; } | |
bool operator==(const char* rhs) const { return str_ == rhs; } | |
bool operator==(const std::string& rhs) const { return str_ == rhs; } | |
bool operator==(const StringHolder<T>& rhs) const { return str_ == rhs.str_; } | |
bool operator!=(const char* rhs) const { return str_.c_str() != rhs; } | |
bool operator!=(const std::string& rhs) const { return str_ != rhs; } | |
bool operator!=(const StringHolder<T>& rhs) const { return str_ != rhs.str_; } | |
const std::string& str() { return str_; } | |
const std::string& str() const { return str_; } | |
auto c_str() const noexcept { return str_.c_str(); } | |
auto data() const noexcept { return str_.data(); } | |
auto size() const noexcept { return this->str_.size(); } | |
auto empty() const noexcept { return this->str_.empty(); } | |
protected: | |
std::string str_{}; | |
}; | |
class Url : public StringHolder<Url> { | |
public: | |
Url() = default; | |
Url(const std::string& url) : StringHolder<Url>(url) {} | |
Url(std::string&& url) : StringHolder<Url>(std::move(url)) {} | |
Url(const char* url) : StringHolder<Url>(url) {} | |
Url(const char* str, size_t len) : StringHolder<Url>(std::string(str, len)) {} | |
Url(const std::initializer_list<std::string> args) : StringHolder<Url>(args) {} | |
Url(const Url& other) = default; | |
Url(Url&& old) noexcept = default; | |
~Url() override = default; | |
Url& operator=(Url&& old) noexcept = default; | |
Url& operator=(const Url& other) = default; | |
}; | |
class Fields : public StringHolder<Fields> { | |
public: | |
Fields() = default; | |
Fields(const std::string& fields) : StringHolder<Fields>(fields) {} | |
Fields(std::string&& fields) : StringHolder<Fields>(std::move(fields)) {} | |
Fields(const char* fields) : StringHolder<Fields>(fields) {} | |
Fields(const char* str, size_t len) : StringHolder<Fields>(std::string(str, len)) {} | |
Fields(const std::initializer_list<std::string> args) : StringHolder<Fields>(args) {} | |
Fields(const Fields& other) = default; | |
Fields(Fields&& old) noexcept = default; | |
~Fields() override = default; | |
Fields& operator=(Fields&& old) noexcept = default; | |
Fields& operator=(const Fields& other) = default; | |
}; | |
struct WriteUserData { | |
WriteUserData() = default; | |
WriteUserData(StopCallback _cb) : stop_callback{_cb} {} | |
StopCallback stop_callback{}; | |
std::string data; | |
}; | |
struct CurlWrapper final { | |
CurlWrapper() = default; | |
CurlWrapper(CURL* _curl) : curl{_curl} { } | |
~CurlWrapper(); | |
operator CURL*() { return this->curl; } | |
CURL* operator=(CURL* f) { return this->curl = f; } | |
CURL* curl{nullptr}; | |
template <typename... Ts> | |
auto Download(Ts&&... ts) -> std::optional<std::string> { | |
CurlWrapper::set_option(std::forward<Ts>(ts)...); | |
return this->InternalDownload(); | |
} | |
private: | |
auto SetOption(Header&& header) -> void; | |
auto SetOption(Fields&& fields) -> void; | |
auto SetOption(Url&& url) -> void; | |
auto SetOption(StopCallback&& callback) -> void; | |
template <typename T> | |
auto set_option(T&& t) -> void { | |
this->SetOption(std::forward<T>(t)); | |
} | |
template <typename T, typename... Ts> | |
auto set_option(T&& t, Ts&&... ts) -> void { | |
set_option(std::forward<T>(t)); | |
set_option(std::forward<Ts>(ts)...); | |
} | |
auto SetHeader(const Header& header) -> void; | |
auto InternalDownload() -> std::optional<std::string>; | |
private: | |
curl_slist* header_list{nullptr}; | |
Url url; | |
std::optional<Header> header; | |
bool new_header{false}; | |
std::optional<Fields> fields; | |
bool new_fields{false}; | |
StopCallback stop_callback{}; | |
}; | |
} // namespace curl |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment