#pragma once #if !defined(MIJIN_NET_CURL_WRAPPERS_HPP_INCLUDED) #define MIJIN_NET_CURL_WRAPPERS_HPP_INCLUDED 1 #include #include #include #include "./url.hpp" #include "../debug/assert.hpp" #include "../internal/common.hpp" #include "../types/result.hpp" namespace curl { struct [[nodiscard]] Error { CURLcode code = CURLE_OK; [[nodiscard]] bool isSuccess() const MIJIN_NOEXCEPT { return code == CURLE_OK; } }; template using Result = mijin::ResultBase; #define MIJIN_CURL_VERIFY_RESULT(curlResult) \ do \ { \ if ((curlResult) != CURLE_OK) \ { \ return Error{curlResult}; \ } \ } while (0) using curl_write_callback_t = size_t(*)(char*, size_t, size_t, void*); using curl_read_callback_t = curl_write_callback_t; class SList { private: curl_slist* next_ = nullptr; public: SList() MIJIN_NOEXCEPT = default; SList(const SList&) = delete; SList(SList&& other) noexcept : next_(std::exchange(other.next_, nullptr)) {} ~SList() MIJIN_NOEXCEPT { if (next_ != nullptr) { curl_slist_free_all(next_); } } SList& operator=(const SList&) = delete; SList& operator=(SList&& other) MIJIN_NOEXCEPT { if (&other == this) { return *this; } if (next_ != nullptr) { curl_slist_free_all(next_); } next_ = std::exchange(other.next_, nullptr); return *this; } auto operator<=>(const SList&) const noexcept = default; void append(const char* str) MIJIN_NOEXCEPT { next_ = curl_slist_append(next_, str); } friend class CurlEasy; }; class CurlEasy { private: CURL* handle_ = nullptr; SList headers_; public: CurlEasy() MIJIN_NOEXCEPT = default; CurlEasy(const CurlEasy&) = delete; CurlEasy(CurlEasy&& other) MIJIN_NOEXCEPT : handle_(std::exchange(other.handle_, nullptr)), headers_(std::move(other.headers_)) {} ~CurlEasy() MIJIN_NOEXCEPT { reset(); } CurlEasy& operator=(const CurlEasy&) = delete; CurlEasy& operator=(CurlEasy&& other) MIJIN_NOEXCEPT { if (this == &other) { return *this; } reset(); handle_ = std::exchange(other.handle_, nullptr); headers_ = std::move(other.headers_); return *this; } bool init() MIJIN_NOEXCEPT { reset(); handle_ = curl_easy_init(); if (handle_ == nullptr) { return false; } return true; } void reset() MIJIN_NOEXCEPT { if (handle_) { curl_easy_cleanup(handle_); handle_ = nullptr; } } Error setURL(const char* url) MIJIN_THROWS { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_URL, url); return {result}; } Error setURL(const std::string& url) MIJIN_NOEXCEPT { return setURL(url.c_str()); } Error setURL(const mijin::URL& url) MIJIN_NOEXCEPT { return setURL(url.getBase().c_str()); } Error setSSLVerifyPeer(bool verify) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_SSL_VERIFYPEER, verify); return {result}; } Error setSSLVerifyHost(bool verify) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_SSL_VERIFYHOST, verify); return {result}; } Error setWriteFunction(curl_write_callback_t callback) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_WRITEFUNCTION, callback); return {result}; } Error setWriteData(void* data) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_WRITEDATA, data); return {result}; } Error setPostFields(const char* data, bool copy = false) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, copy ? CURLOPT_COPYPOSTFIELDS : CURLOPT_POSTFIELDS, data); return {result}; } Error setUpload(bool upload) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_UPLOAD, upload ? 1 : 0); return {result}; } Error setInFileSize(curl_off_t size) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_INFILESIZE_LARGE, size); return {result}; } Error setReadFunction(curl_read_callback_t callback) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_READFUNCTION, callback); return {result}; } Error setReadData(const void* data) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_READDATA, data); return {result}; } Error setNoBody(bool nobody) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_NOBODY, nobody ? 1 : 0); return {result}; } Error setCustomRequest(bool customRequest) MIJIN_NOEXCEPT { const CURLcode result = curl_easy_setopt(handle_, CURLOPT_CUSTOMREQUEST, customRequest ? 1 : 0); return {result}; } Error setHeaders(SList headers) MIJIN_NOEXCEPT { headers_ = std::move(headers); const CURLcode result = curl_easy_setopt(handle_, CURLOPT_HTTPHEADER, headers_.next_); return {result}; } Error perform() MIJIN_NOEXCEPT { const CURLcode result = curl_easy_perform(handle_); return {result}; } Result getHTTPVersion() MIJIN_NOEXCEPT { return getInfo(CURLINFO_HTTP_VERSION); } Result getStatus() MIJIN_NOEXCEPT { return getInfo(CURLINFO_RESPONSE_CODE); } Result> getResponseHeaders() MIJIN_NOEXCEPT { // TODO: how to detect errors? std::multimap result; curl_header* header = nullptr; curl_header* prevHeader = nullptr; while ((header = curl_easy_nextheader(handle_, CURLH_HEADER, 0, prevHeader)) != nullptr) { result.emplace(header->name, header->value); prevHeader = header; } return result; } private: template Result getInfo(CURLINFO info) MIJIN_NOEXCEPT { TInfo value = 0; const CURLcode result = curl_easy_getinfo(handle_, info, &value); if (result != CURLE_OK) { return Error{result}; } return static_cast(value); } }; } // namespace curl #endif // !defined(MIJIN_NET_CURL_WRAPPERS_HPP_INCLUDED)