Skip to content

Instantly share code, notes, and snippets.

@ITotalJustice
Created November 9, 2021 18:01
Show Gist options
  • Save ITotalJustice/e07f7e20e67c33706b7493f4a9b3826d to your computer and use it in GitHub Desktop.
Save ITotalJustice/e07f7e20e67c33706b7493f4a9b3826d to your computer and use it in GitHub Desktop.
curl wrapper
#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
#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