diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4a05f3d --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: Google +IndentWidth: 4 +AccessModifierOffset: -4 diff --git a/CMakeLists.txt b/CMakeLists.txt index b4894ff..4273c44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,14 +10,52 @@ project( find_package(CURL REQUIRED) -add_library(Network src/Network.cpp include/wtlgo/Network.hpp) +set(PUBLIC_HEADERS + "include/wtlgo/NetworkOld.hpp" + + "include/wtlgo/network/Auth.hpp" + "include/wtlgo/network/HttpBasicAuth.hpp" + + "include/wtlgo/network/HttpMethod.hpp" + + "include/wtlgo/network/Config.hpp" + "include/wtlgo/network/ClientConfig.hpp" + + "include/wtlgo/network/Headers.hpp" + "include/wtlgo/network/HttpHeaders.hpp" + + "include/wtlgo/network/Client.hpp" + "include/wtlgo/network/CurlClient.hpp" +) + +set(SOURCES + "src/NetworkOld.cpp" + "src/HttpBasicAuth.cpp" + "src/ClientConfig.cpp" + "src/HttpHeaders.cpp" + "src/CurlClient.cpp" + + "src/internal/config/MergedConfig.hpp" + "src/internal/config/MergedConfig.cpp" + "src/internal/config/CopyOnWriteConfig.hpp" + "src/internal/config/CopyOnWriteConfig.cpp" + + "src/internal/curl/Types.hpp" + "src/internal/curl/CurlEasyHandler.hpp" + "src/internal/curl/CurlEasyHandler.cpp" + "src/internal/curl/CurlEasyHandlerBuilder.hpp" + "src/internal/curl/CurlEasyHandlerBuilder.cpp" +) + +add_library(Network ${SOURCES}) target_compile_features(Network PUBLIC cxx_std_17) -target_link_libraries(Network PUBLIC ${CURL_LIBRARIES}) +target_link_libraries(Network PUBLIC CURL::libcurl) target_include_directories(Network PUBLIC include/) -target_include_directories(Network PRIVATE include/wtlgo/ ${CURL_INCLUDE_DIR}) +target_include_directories(Network PRIVATE include/) + set_target_properties(Network PROPERTIES - PUBLIC_HEADER include/wtlgo/Network.hpp + PUBLIC_HEADER "${PUBLIC_HEADERS}" VERSION ${PROJECT_VERSION} ) @@ -28,5 +66,7 @@ install( PUBLIC_HEADER DESTINATION include/wtlgo COMPONENT Development ) -enable_testing() -add_subdirectory(test) +if(DEFINED ENABLE_TESTING) + enable_testing() + add_subdirectory(test) +endif() diff --git a/include/wtlgo/Network.hpp b/include/wtlgo/NetworkOld.hpp similarity index 50% rename from include/wtlgo/Network.hpp rename to include/wtlgo/NetworkOld.hpp index 039c5c3..5e74378 100644 --- a/include/wtlgo/Network.hpp +++ b/include/wtlgo/NetworkOld.hpp @@ -1,5 +1,5 @@ -#ifndef Network_hpp -#define Network_hpp +#ifndef __NETWORK_HPP__ +#define __NETWORK_HPP__ #include #include @@ -7,37 +7,44 @@ #include namespace wtlgo { +namespace old { class Network { public: static Network& instance(); - + Network(Network const&) = delete; void operator=(Network const&) = delete; - + void set_proxy(const std::string& proxy); - - std::string request(std::string url, const std::map& args = {}, bool post = false) const; - - std::string upload(std::string url, const std::string& fieldname, const std::string& filename) const; - - bool download(const std::string& url, const std::string& save_as = "") const; - + + std::string request(std::string url, + const std::map& args = {}, + bool post = false) const; + + std::string upload(std::string url, const std::string& fieldname, + const std::string& filename) const; + + bool download(const std::string& url, + const std::string& save_as = "") const; + private: std::string proxy; Network(); ~Network(); - - static size_t string_writer(char* contents, size_t size, size_t nmemb, std::string* stream); - static size_t file_writer(void* contents, size_t size, size_t nmemb, FILE* stream); - std::string join(const std::vector lst, const std::string& delim) const; + static size_t string_writer(char* contents, size_t size, size_t nmemb, + std::string* stream); + static size_t file_writer(void* contents, size_t size, size_t nmemb, + FILE* stream); + + std::string join(const std::vector lst, + const std::string& delim) const; std::string url_encode(const std::string& str) const; }; extern Network& network; - } - +} #endif /* Network_hpp */ diff --git a/include/wtlgo/network/Auth.hpp b/include/wtlgo/network/Auth.hpp new file mode 100644 index 0000000..c675d5a --- /dev/null +++ b/include/wtlgo/network/Auth.hpp @@ -0,0 +1,29 @@ +#ifndef __WTLGO__NETWORK__AUTH__ +#define __WTLGO__NETWORK__AUTH__ + +#include +#include + +namespace wtlgo { +namespace network { +struct Auth { + using ptr_t = std::shared_ptr; + using cptr_t = std::shared_ptr; + + using username_ref_t = std::string_view; + using password_ref_t = std::string_view; + + virtual ~Auth() = default; + + virtual ptr_t clone() const = 0; + + virtual username_ref_t username() const = 0; + virtual ptr_t username(username_ref_t) = 0; + + virtual password_ref_t password() const = 0; + virtual ptr_t password(password_ref_t) = 0; +}; +} +} + +#endif diff --git a/include/wtlgo/network/Client.hpp b/include/wtlgo/network/Client.hpp new file mode 100644 index 0000000..d456c84 --- /dev/null +++ b/include/wtlgo/network/Client.hpp @@ -0,0 +1,24 @@ +#ifndef __WTLGO__NETWORK__CLIENT__ +#define __WTLGO__NETWORK__CLIENT__ + +#include + +#include + +namespace wtlgo { +namespace network { +struct Client { + using ptr_t = std::shared_ptr; + using cptr_t = std::shared_ptr; + + virtual ~Client() = default; + + using config_ptr_t = Config::cptr_t; + + virtual config_ptr_t config() const = 0; + virtual ptr_t config(config_ptr_t) = 0; +}; +} +} + +#endif diff --git a/include/wtlgo/network/ClientConfig.hpp b/include/wtlgo/network/ClientConfig.hpp new file mode 100644 index 0000000..013febf --- /dev/null +++ b/include/wtlgo/network/ClientConfig.hpp @@ -0,0 +1,51 @@ +#ifndef __WTLGO__NETWORK__CLIENT_CONFIG__ +#define __WTLGO__NETWORK__CLIENT_CONFIG__ + +#include + +#include + +namespace wtlgo { +namespace network { +class ClientConfig : public Config, + public std::enable_shared_from_this { +public: + using ptr_t = std::shared_ptr; + using cptr_t = std::shared_ptr; + + virtual ~ClientConfig(); + + [[nodiscard]] static ClientConfig::ptr_t create(); + [[nodiscard]] static ClientConfig::ptr_t clone(Config::cptr_t); + + Config::ptr_t clone() const override; + Config::ptr_t merge(Config::cptr_t) const override; + + Config::url_opt_ref_t url() const override; + Config::ptr_t url(Config::url_ref_t) override; + Config::ptr_t clear_url() override; + + Config::method_opt_t method() const override; + Config::ptr_t method(Config::method_t) override; + Config::ptr_t clear_method() override; + + Config::url_opt_ref_t base_url() const override; + Config::ptr_t base_url(Config::url_ref_t) override; + Config::ptr_t clear_base_url() override; + + Config::headers_opt_t headers() const override; + Config::ptr_t headers(headers_opt_t) override; + Config::ptr_t clear_headers() override; + +protected: + ClientConfig(); + ClientConfig(const Config::cptr_t); + +private: + struct Impl; + const std::unique_ptr impl; +}; +} +} + +#endif diff --git a/include/wtlgo/network/Config.hpp b/include/wtlgo/network/Config.hpp new file mode 100644 index 0000000..6c6f184 --- /dev/null +++ b/include/wtlgo/network/Config.hpp @@ -0,0 +1,49 @@ +#ifndef __WTLGO__NETWORK__CONFIG__ +#define __WTLGO__NETWORK__CONFIG__ + +#include +#include +#include + +#include +#include + +namespace wtlgo { +namespace network { +struct Config { + using ptr_t = std::shared_ptr; + using cptr_t = std::shared_ptr; + + virtual ~Config() = default; + + [[nodiscard]] virtual ptr_t clone() const = 0; + [[nodiscard]] virtual ptr_t merge(cptr_t) const = 0; + + using url_ref_t = std::string_view; + using url_opt_ref_t = std::optional; + + virtual url_opt_ref_t url() const = 0; + virtual ptr_t url(url_ref_t) = 0; + virtual ptr_t clear_url() = 0; + + using method_t = HttpMethod; + using method_opt_t = std::optional; + + virtual method_opt_t method() const = 0; + virtual ptr_t method(method_t) = 0; + virtual ptr_t clear_method() = 0; + + virtual url_opt_ref_t base_url() const = 0; + virtual ptr_t base_url(url_ref_t) = 0; + virtual ptr_t clear_base_url() = 0; + + using headers_opt_t = Headers::cptr_t; + + virtual headers_opt_t headers() const = 0; + virtual ptr_t headers(headers_opt_t) = 0; + virtual ptr_t clear_headers() = 0; +}; +} +} + +#endif diff --git a/include/wtlgo/network/CurlClient.hpp b/include/wtlgo/network/CurlClient.hpp new file mode 100644 index 0000000..dfc07db --- /dev/null +++ b/include/wtlgo/network/CurlClient.hpp @@ -0,0 +1,33 @@ +#ifndef __WTLGO__NETWORK__CURL_CLIENT__ +#define __WTLGO__NETWORK__CURL_CLIENT__ + +#include + +#include + +namespace wtlgo { +namespace network { +class CurlClient : public Client, + public std::enable_shared_from_this { +public: + using ptr_t = std::shared_ptr; + using cptr_t = std::shared_ptr; + + virtual ~CurlClient(); + + [[nodiscard]] static CurlClient::ptr_t create(); + + Client::config_ptr_t config() const override; + Client::ptr_t config(Client::config_ptr_t) override; + +protected: + CurlClient(); + +private: + struct Impl; + const std::unique_ptr impl; +}; +} +} + +#endif diff --git a/include/wtlgo/network/Headers.hpp b/include/wtlgo/network/Headers.hpp new file mode 100644 index 0000000..c91a2bc --- /dev/null +++ b/include/wtlgo/network/Headers.hpp @@ -0,0 +1,33 @@ +#ifndef __WTLGO__NETWORK__HEADERS__ +#define __WTLGO__NETWORK__HEADERS__ + +#include +#include +#include +#include + +namespace wtlgo { +namespace network { +struct Headers { + using ptr_t = std::shared_ptr; + using cptr_t = std::shared_ptr; + + virtual ~Headers() = default; + + using key_ref_t = std::string_view; + using key_ref_list_t = std::set; + + using value_ref_t = std::string_view; + using value_ref_opt_t = std::optional; + + virtual ptr_t clone() const = 0; + + virtual key_ref_list_t keys() const = 0; + virtual value_ref_opt_t get(key_ref_t header) const = 0; + virtual ptr_t set(key_ref_t header, value_ref_t value) = 0; + virtual ptr_t erase(key_ref_t header) = 0; +}; +} +} + +#endif diff --git a/include/wtlgo/network/HttpBasicAuth.hpp b/include/wtlgo/network/HttpBasicAuth.hpp new file mode 100644 index 0000000..f94955b --- /dev/null +++ b/include/wtlgo/network/HttpBasicAuth.hpp @@ -0,0 +1,41 @@ +#ifndef __WTLGO__NETWORK__DEFAULT_HTTP_BASIC_AUTH__ +#define __WTLGO__NETWORK__DEFAULT_HTTP_BASIC_AUTH__ + +#include + +#include + +namespace wtlgo { +namespace network { +class HttpBasicAuth : public Auth, + public std::enable_shared_from_this { +public: + using ptr_t = std::shared_ptr; + using cptr_t = std::shared_ptr; + + virtual ~HttpBasicAuth(); + + [[nodiscard]] static HttpBasicAuth::ptr_t create( + Auth::username_ref_t username, Auth::password_ref_t password); + + Auth::ptr_t clone() const override; + + Auth::username_ref_t username() const override; + Auth::ptr_t username(Auth::username_ref_t) override; + + Auth::password_ref_t password() const override; + Auth::ptr_t password(Auth::password_ref_t) override; + +protected: + HttpBasicAuth(Auth::username_ref_t username, Auth::password_ref_t password); + +private: + struct Impl; + const std::unique_ptr impl; + + HttpBasicAuth(const std::unique_ptr&); +}; +} +} + +#endif diff --git a/include/wtlgo/network/HttpHeaders.hpp b/include/wtlgo/network/HttpHeaders.hpp new file mode 100644 index 0000000..9741245 --- /dev/null +++ b/include/wtlgo/network/HttpHeaders.hpp @@ -0,0 +1,39 @@ +#ifndef __WTLGO__NETWORK__HTTP_HEADERS__ +#define __WTLGO__NETWORK__HTTP_HEADERS__ + +#include + +#include + +namespace wtlgo { +namespace network { +class HttpHeaders : public Headers, + public std::enable_shared_from_this { +public: + using ptr_t = std::shared_ptr; + using cptr_t = std::shared_ptr; + + virtual ~HttpHeaders(); + + [[nodiscard]] static ptr_t create(); + + Headers::ptr_t clone() const override; + + Headers::key_ref_list_t keys() const override; + Headers::value_ref_opt_t get(Headers::key_ref_t header) const override; + Headers::ptr_t set(Headers::key_ref_t header, value_ref_t value) override; + Headers::ptr_t erase(Headers::key_ref_t header) override; + +protected: + HttpHeaders(); + +private: + struct Impl; + const std::unique_ptr impl; + + HttpHeaders(const std::unique_ptr& impl); +}; +} +} + +#endif diff --git a/include/wtlgo/network/HttpMethod.hpp b/include/wtlgo/network/HttpMethod.hpp new file mode 100644 index 0000000..dc80544 --- /dev/null +++ b/include/wtlgo/network/HttpMethod.hpp @@ -0,0 +1,10 @@ +#ifndef __WTLGO__NETWORK__DEFAULT_HTTP_METHOD__ +#define __WTLGO__NETWORK__DEFAULT_HTTP_METHOD__ + +namespace wtlgo { +namespace network { +enum class HttpMethod { GET, POST, PUT, PATCH, DELETE }; +} +} + +#endif diff --git a/src/ClientConfig.cpp b/src/ClientConfig.cpp new file mode 100644 index 0000000..eb0e338 --- /dev/null +++ b/src/ClientConfig.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include + +#include "./internal/Config/MergedConfig.hpp" +#include + +#include + +using namespace wtlgo::network; + +struct ClientConfig::Impl { +private: + std::optional _url; + Config::method_opt_t _method; + std::optional _base_url; + Config::headers_opt_t _headers; + +public: + Impl() = default; + Impl(const Config::cptr_t config) + : _url{config->url()}, + _method{config->method()}, + _base_url{config->base_url()}, + _headers{([headers = config->headers()] { + return headers ? headers->clone() : nullptr; + })()} {} + + Config::url_opt_ref_t url() const { return _url; } + void url(const Config::url_ref_t url) { _url = url; } + void clear_url() { _url = std::nullopt; } + + Config::method_opt_t method() const { return _method; } + void method(const Config::method_t method) { _method = method; } + void clear_method() { _method = std::nullopt; } + + Config::url_opt_ref_t base_url() const { return _base_url; } + void base_url(const Config::url_ref_t url) { _base_url = url; } + void clear_base_url() { _base_url = std::nullopt; } + + Config::headers_opt_t headers() const { return _headers; } + void headers(const headers_opt_t headers) { + if (headers) { + _headers = headers->clone(); + } else { + _headers = nullptr; + } + } + void clear_headers() { _headers = nullptr; } +}; + +ClientConfig::~ClientConfig() = default; + +ClientConfig::ptr_t ClientConfig::create() { + return std::shared_ptr{new ClientConfig}; +} + +ClientConfig::ptr_t ClientConfig::clone(const Config::cptr_t config) { + return std::shared_ptr{new ClientConfig{config}}; +} + +Config::ptr_t ClientConfig::clone() const { return clone(shared_from_this()); } + +Config::ptr_t ClientConfig::merge(const Config::cptr_t rconfig) const { + using namespace wtlgo::network::internal::config; + return MergedConfig::merge(shared_from_this(), rconfig); +} + +Config::url_opt_ref_t ClientConfig::url() const { return impl->url(); } + +Config::ptr_t ClientConfig::url(const Config::url_ref_t url) { + impl->url(url); + return shared_from_this(); +} + +Config::ptr_t ClientConfig::clear_url() { + impl->clear_url(); + return shared_from_this(); +} + +Config::method_opt_t ClientConfig::method() const { return impl->method(); } + +Config::ptr_t ClientConfig::method(const Config::method_t method) { + impl->method(method); + return shared_from_this(); +} + +Config::ptr_t ClientConfig::clear_method() { + impl->clear_method(); + return shared_from_this(); +} + +Config::url_opt_ref_t ClientConfig::base_url() const { + return impl->base_url(); +} + +Config::ptr_t ClientConfig::base_url(const Config::url_ref_t url) { + impl->base_url(url); + return shared_from_this(); +} + +Config::ptr_t ClientConfig::clear_base_url() { + impl->clear_base_url(); + return shared_from_this(); +} + +Config::headers_opt_t ClientConfig::headers() const { return impl->headers(); } + +Config::ptr_t ClientConfig::headers(const Config::headers_opt_t headers) { + impl->headers(headers); + return shared_from_this(); +} + +Config::ptr_t ClientConfig::clear_headers() { + impl->clear_headers(); + return shared_from_this(); +} + +ClientConfig::ClientConfig() : impl{std::make_unique()} {} + +ClientConfig::ClientConfig(const Config::cptr_t config) + : impl{std::make_unique(config)} {} diff --git a/src/CurlClient.cpp b/src/CurlClient.cpp new file mode 100644 index 0000000..fb3a907 --- /dev/null +++ b/src/CurlClient.cpp @@ -0,0 +1,30 @@ +#include + +#include +#include + +using namespace wtlgo::network; + +struct CurlClient::Impl { +private: + config_ptr_t _config = ClientConfig::create(); + +public: + config_ptr_t config() const { return _config; } + void config(const config_ptr_t config) { _config = config->clone(); } +}; + +CurlClient::~CurlClient() = default; + +CurlClient::ptr_t CurlClient::create() { + return std::shared_ptr{new CurlClient}; +} + +Client::config_ptr_t CurlClient::config() const { return impl->config(); } + +Client::ptr_t CurlClient::config(const Client::config_ptr_t config) { + impl->config(); + return shared_from_this(); +} + +CurlClient::CurlClient() : impl{std::make_unique()} {} diff --git a/src/HttpBasicAuth.cpp b/src/HttpBasicAuth.cpp new file mode 100644 index 0000000..5f6adfb --- /dev/null +++ b/src/HttpBasicAuth.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include +#include + +using namespace wtlgo::network; + +struct HttpBasicAuth::Impl { +private: + std::string _username; + std::string _password; + +public: + Impl() = default; + Impl(const Impl&) = default; + Impl(const Auth::username_ref_t username, + const Auth::password_ref_t password) + : _username{username}, _password{password} {} + + Auth::username_ref_t username() const { return _username; } + void username(const Auth::username_ref_t username) { + _username = username; + } + + Auth::password_ref_t password() const { return _password; } + void password(const Auth::password_ref_t password) { + _password = password; + } +}; + +HttpBasicAuth::~HttpBasicAuth() = default; + +HttpBasicAuth::ptr_t HttpBasicAuth::create( + const Auth::username_ref_t username, + const Auth::password_ref_t password) { + return std::shared_ptr( + new HttpBasicAuth{username, password}); +} + +Auth::ptr_t HttpBasicAuth::clone() const { + return std::shared_ptr( + new HttpBasicAuth{impl}); +} + +Auth::username_ref_t HttpBasicAuth::username() const { + return impl->username(); +} + +Auth::ptr_t HttpBasicAuth::username( + const Auth::username_ref_t username) { + impl->username(username); + return shared_from_this(); +} + +Auth::password_ref_t HttpBasicAuth::password() const { + return impl->password(); +} + +Auth::ptr_t HttpBasicAuth::password( + const Auth::password_ref_t password) { + impl->password(password); + return shared_from_this(); +} + +HttpBasicAuth::HttpBasicAuth( + const Auth::username_ref_t username, + const Auth::password_ref_t password) + : impl{std::make_unique(username, password)} {} + +HttpBasicAuth::HttpBasicAuth(const std::unique_ptr& impl) + : impl{std::make_unique(*impl)} {} diff --git a/src/HttpHeaders.cpp b/src/HttpHeaders.cpp new file mode 100644 index 0000000..af9cbb6 --- /dev/null +++ b/src/HttpHeaders.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include + +#include + +using namespace wtlgo::network; + +struct HttpHeaders::Impl { +private: + std::map> _data; + +public: + Headers::key_ref_list_t keys() const { + Headers::key_ref_list_t res; + std::transform( + _data.cbegin(), _data.cend(), std::inserter(res, res.begin()), + [](const auto& val) -> Headers::key_ref_t { return val.first; }); + return res; + } + + Headers::value_ref_opt_t get(const Headers::key_ref_t header) const { + const auto it = _data.find(header); + + if (it == _data.cend()) { + return std::nullopt; + } + + return it->second; + } + + void set(const Headers::key_ref_t header, const value_ref_t value) { + _data.emplace(header, value); + } + + void clear(const Headers::key_ref_t header) { + const auto it = _data.find(header); + if (it != _data.cend()) { + _data.erase(it); + } + } +}; + +HttpHeaders::~HttpHeaders() = default; + +HttpHeaders::ptr_t HttpHeaders::create() { + return std::shared_ptr{new HttpHeaders{}}; +} + +Headers::ptr_t HttpHeaders::clone() const { + return std::shared_ptr{new HttpHeaders{impl}}; +} + +Headers::key_ref_list_t HttpHeaders::keys() const { return impl->keys(); } + +Headers::value_ref_opt_t HttpHeaders::get( + const Headers::key_ref_t header) const { + return impl->get(header); +} + +Headers::ptr_t HttpHeaders::set(const Headers::key_ref_t header, + const value_ref_t value) { + impl->set(header, value); + return shared_from_this(); +} + +Headers::ptr_t HttpHeaders::erase(const Headers::key_ref_t header) { + impl->clear(header); + return shared_from_this(); +} + +HttpHeaders::HttpHeaders() : impl{std::make_unique()} {} + +HttpHeaders::HttpHeaders(const std::unique_ptr& impl) + : impl{std::make_unique(*impl)} {} diff --git a/src/Network.cpp b/src/NetworkOld.cpp similarity index 51% rename from src/Network.cpp rename to src/NetworkOld.cpp index 10e345f..72626d7 100644 --- a/src/Network.cpp +++ b/src/NetworkOld.cpp @@ -8,15 +8,13 @@ #include -#include +#include -wtlgo::Network& wtlgo::network = wtlgo::Network::instance(); +wtlgo::old::Network& wtlgo::old::network = wtlgo::old::Network::instance(); -using namespace wtlgo; +using namespace wtlgo::old; -Network::Network() { - curl_global_init(CURL_GLOBAL_ALL); -} +Network::Network() { curl_global_init(CURL_GLOBAL_ALL); } Network::~Network() {} @@ -25,48 +23,51 @@ Network& Network::instance() { return instance; } -void Network::set_proxy(const std::string& proxy) { - this->proxy = proxy; -} +void Network::set_proxy(const std::string& proxy) { this->proxy = proxy; } -std::string Network::request(std::string url, const std::map& args, bool post) const { - std::unique_ptr curl { curl_easy_init(), curl_easy_cleanup }; +std::string Network::request(std::string url, + const std::map& args, + bool post) const { + std::unique_ptr curl{curl_easy_init(), + curl_easy_cleanup}; CURLcode res; std::string readBuffer; - + std::string fields; { std::vector tmp_list; - for(const std::pair& row : args) { - tmp_list.push_back(url_encode(row.first) + "=" + url_encode(row.second)); + for (const std::pair& row : args) { + tmp_list.push_back(url_encode(row.first) + "=" + + url_encode(row.second)); } fields = join(tmp_list, "&"); } - - if(curl) { - if(fields.size() > 0) { - if(post) { + + if (curl) { + if (fields.size() > 0) { + if (post) { curl_easy_setopt(curl.get(), CURLOPT_POST, 1); - curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, fields.c_str()); - } - else { + curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, + fields.c_str()); + } else { url += "?" + fields; } } - + curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(curl.get(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_easy_setopt(curl.get(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl.get(), CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_1_1); curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &readBuffer); curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, string_writer); curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1); - if(proxy.size() > 0) + if (proxy.size() > 0) { curl_easy_setopt(curl.get(), CURLOPT_PROXY, proxy.c_str()); - + } + res = curl_easy_perform(curl.get()); - - if(res != 0) { + + if (res != 0) { std::cerr << curl_easy_strerror(res) << std::endl; return ""; } @@ -75,101 +76,115 @@ std::string Network::request(std::string url, const std::map curl { curl_easy_init(), curl_easy_cleanup }; +bool Network::download(const std::string& url, + const std::string& save_as) const { + std::unique_ptr curl{curl_easy_init(), + curl_easy_cleanup}; CURLcode res; - + std::string filename = save_as; - if(filename.size() == 0) { + if (filename.size() == 0) { std::string::size_type slash_p = url.find_last_of('/'); filename = std::string(url.begin() + 1 + slash_p, url.end()); } - - if(curl) { - std::unique_ptr file(fopen(filename.c_str(), "wb"), fclose); - - curl_easy_setopt(curl.get(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + + if (curl) { + std::unique_ptr file( + fopen(filename.c_str(), "wb"), fclose); + + curl_easy_setopt(curl.get(), CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_1_1); curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, file.get()); curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, file_writer); curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1); - if(proxy.size() > 0) + + if (proxy.size() > 0) { curl_easy_setopt(curl.get(), CURLOPT_PROXY, proxy.c_str()); - + } + res = curl_easy_perform(curl.get()); - - if(res != 0){ + + if (res != 0) { std::cerr << curl_easy_strerror(res) << std::endl; remove(filename.c_str()); return false; } - } - else { + } else { return false; } - + return true; } -std::string Network::upload(std::string url, const std::string& fieldname, const std::string& filename) const { - std::unique_ptr curl { curl_easy_init(), curl_easy_cleanup }; +std::string Network::upload(std::string url, const std::string& fieldname, + const std::string& filename) const { + std::unique_ptr curl{curl_easy_init(), + curl_easy_cleanup}; CURLcode res; std::string readBuffer; - + if (curl) { - std::unique_ptr formpost([&fieldname, &filename]{ - curl_httppost *formpost = nullptr; - curl_httppost *lastptr = nullptr; - - curl_formadd(&formpost, - &lastptr, - CURLFORM_COPYNAME, fieldname.c_str(), - CURLFORM_FILE, filename.c_str(), - CURLFORM_END); - - return formpost; - }(), curl_formfree); - - curl_easy_setopt(curl.get(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + std::unique_ptr formpost( + [&fieldname, &filename] { + curl_httppost* formpost = nullptr; + curl_httppost* lastptr = nullptr; + + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, + fieldname.c_str(), CURLFORM_FILE, filename.c_str(), + CURLFORM_END); + + return formpost; + }(), + curl_formfree); + + curl_easy_setopt(curl.get(), CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_1_1); curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, string_writer); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &readBuffer); curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); curl_easy_setopt(curl.get(), CURLOPT_HTTPPOST, formpost.get()); - + res = curl_easy_perform(curl.get()); - - if (res != CURLE_OK){ + + if (res != CURLE_OK) { std::cerr << curl_easy_strerror(res) << std::endl; return ""; } } - + return readBuffer; } -size_t Network::string_writer(char* contents, size_t size, size_t nmemb, std::string* stream) { +size_t Network::string_writer(char* contents, size_t size, size_t nmemb, + std::string* stream) { stream->append(contents, size * nmemb); return size * nmemb; } -size_t Network::file_writer(void* contents, size_t size, size_t nmemb, FILE* stream) { +size_t Network::file_writer(void* contents, size_t size, size_t nmemb, + FILE* stream) { size_t written = fwrite(contents, size, nmemb, stream); return written; } -std::string Network::join(const std::vector lst, const std::string& delim) const { +std::string Network::join(const std::vector lst, + const std::string& delim) const { std::ostringstream ss; - for(size_t i = 0; i < lst.size(); i++) + + for (size_t i = 0; i < lst.size(); i++) { ss << lst[i] << (i + 1 == lst.size() ? "" : delim); + } + return ss.str(); } std::string Network::url_encode(const std::string& str) const { - std::unique_ptr curl { curl_easy_init(), curl_easy_cleanup }; - std::unique_ptr data { + std::unique_ptr curl{curl_easy_init(), + curl_easy_cleanup}; + std::unique_ptr data{ curl_easy_escape(curl.get(), str.c_str(), static_cast(str.size())), - curl_free - }; + curl_free}; return data ? std::string(data.get()) : str; } diff --git a/src/internal/config/CopyOnWriteConfig.cpp b/src/internal/config/CopyOnWriteConfig.cpp new file mode 100644 index 0000000..3a2e396 --- /dev/null +++ b/src/internal/config/CopyOnWriteConfig.cpp @@ -0,0 +1,92 @@ +#include + +#include +#include "./MergedConfig.hpp" + +#include "./CopyOnWriteConfig.hpp" + +using namespace wtlgo::network; +using namespace wtlgo::network::internal::config; + +CopyOnWriteConfig::~CopyOnWriteConfig() = default; + +CopyOnWriteConfig::ptr_t CopyOnWriteConfig::adapt(const Config::cptr_t config) { + return std::shared_ptr{new CopyOnWriteConfig{config}}; +} + +Config::ptr_t CopyOnWriteConfig::clone() const { + return ClientConfig::clone(shared_from_this()); +} + +Config::ptr_t CopyOnWriteConfig::merge(const Config::cptr_t rconfig) const { + return MergedConfig::merge(shared_from_this(), rconfig); +} + +Config::url_opt_ref_t CopyOnWriteConfig::url() const { return cptr()->url(); } +Config::ptr_t CopyOnWriteConfig::url(const Config::url_ref_t url) { + ptr()->url(url); + return shared_from_this(); +} + +Config::ptr_t CopyOnWriteConfig::clear_url() { + ptr()->clear_url(); + return shared_from_this(); +} + +Config::method_opt_t CopyOnWriteConfig::method() const { + return cptr()->method(); +} + +Config::ptr_t CopyOnWriteConfig::method(const Config::method_t method) { + ptr()->method(method); + return shared_from_this(); +} + +Config::ptr_t CopyOnWriteConfig::clear_method() { + ptr()->clear_method(); + return shared_from_this(); +} + +Config::url_opt_ref_t CopyOnWriteConfig::base_url() const { + return cptr()->base_url(); +} + +Config::ptr_t CopyOnWriteConfig::base_url(const Config::url_ref_t url) { + ptr()->base_url(url); + return shared_from_this(); +} + +Config::ptr_t CopyOnWriteConfig::clear_base_url() { + ptr()->clear_base_url(); + return shared_from_this(); +} + +Config::headers_opt_t CopyOnWriteConfig::headers() const { + return cptr()->headers(); +} + +Config::ptr_t CopyOnWriteConfig::headers(const Config::headers_opt_t headers) { + ptr()->headers(headers); + return shared_from_this(); +} + +Config::ptr_t CopyOnWriteConfig::clear_headers() { + ptr()->clear_headers(); + return shared_from_this(); +} + +CopyOnWriteConfig::CopyOnWriteConfig(const Config::cptr_t config) + : solid{config}, liquid{nullptr} {} + +Config::cptr_t CopyOnWriteConfig::cptr() const { + return solid ? solid : liquid; +} + +Config::ptr_t CopyOnWriteConfig::ptr() { + if (solid) { + liquid = ClientConfig::clone(solid); + solid = nullptr; + } + + return liquid; +} diff --git a/src/internal/config/CopyOnWriteConfig.hpp b/src/internal/config/CopyOnWriteConfig.hpp new file mode 100644 index 0000000..9c29986 --- /dev/null +++ b/src/internal/config/CopyOnWriteConfig.hpp @@ -0,0 +1,56 @@ +#ifndef __WTLGO__NETWORK__INTERNAL__CONFIG__COPY_ON_WRITE_CONFIG__ +#define __WTLGO__NETWORK__INTERNAL__CONFIG__COPY_ON_WRITE_CONFIG__ + +#include +#include + +namespace wtlgo { +namespace network { +namespace internal { +namespace config { +class CopyOnWriteConfig + : public Config, + public std::enable_shared_from_this { +public: + using ptr_t = std::shared_ptr; + using cptr_t = std::shared_ptr; + + virtual ~CopyOnWriteConfig(); + + [[nodiscard]] static CopyOnWriteConfig::ptr_t adapt(Config::cptr_t); + + Config::ptr_t clone() const override; + Config::ptr_t merge(Config::cptr_t) const override; + + Config::url_opt_ref_t url() const override; + Config::ptr_t url(Config::url_ref_t) override; + Config::ptr_t clear_url() override; + + Config::method_opt_t method() const override; + Config::ptr_t method(Config::method_t) override; + Config::ptr_t clear_method() override; + + Config::url_opt_ref_t base_url() const override; + Config::ptr_t base_url(Config::url_ref_t) override; + Config::ptr_t clear_base_url() override; + + Config::headers_opt_t headers() const override; + Config::ptr_t headers(headers_opt_t) override; + Config::ptr_t clear_headers() override; + +protected: + CopyOnWriteConfig(Config::cptr_t); + +private: + Config::cptr_t solid; + Config::ptr_t liquid; + + Config::cptr_t cptr() const; + Config::ptr_t ptr(); +}; +} +} +} +} + +#endif diff --git a/src/internal/config/MergedConfig.cpp b/src/internal/config/MergedConfig.cpp new file mode 100644 index 0000000..5c29585 --- /dev/null +++ b/src/internal/config/MergedConfig.cpp @@ -0,0 +1,101 @@ +#include + +#include "./MergedConfig.hpp" +#include "./CopyOnWriteConfig.hpp" + +#include + +using namespace wtlgo::network; +using namespace wtlgo::network::internal::config; + +MergedConfig::~MergedConfig() = default; + +MergedConfig::ptr_t MergedConfig::merge(const Config::cptr_t lconfig, + const Config::cptr_t rconfig) { + return std::shared_ptr{new MergedConfig{lconfig, rconfig}}; +} + +Config::ptr_t MergedConfig::clone() const { + return ClientConfig::clone(shared_from_this()); +} + +Config::ptr_t MergedConfig::merge(const Config::cptr_t rconfig) const { + return merge(shared_from_this(), rconfig); +} + +Config::url_opt_ref_t MergedConfig::url() const { + return rhs->url() ? rhs->url() : lhs->url(); +} + +Config::ptr_t MergedConfig::url(const Config::url_ref_t url) { + lhs->clear_url(); + rhs->url(url); + + return shared_from_this(); +} + +Config::ptr_t MergedConfig::clear_url() { + lhs->clear_url(); + rhs->clear_url(); + + return shared_from_this(); +} + +Config::method_opt_t MergedConfig::method() const { + return rhs->method() ? rhs->method() : lhs->method(); +} + +Config::ptr_t MergedConfig::method(const Config::method_t method) { + lhs->clear_method(); + rhs->method(method); + + return shared_from_this(); +} + +Config::ptr_t MergedConfig::clear_method() { + lhs->clear_method(); + rhs->clear_method(); + + return shared_from_this(); +} + +Config::url_opt_ref_t MergedConfig::base_url() const { + return rhs->base_url() ? rhs->base_url() : lhs->base_url(); +} + +Config::ptr_t MergedConfig::base_url(const Config::url_ref_t url) { + lhs->clear_base_url(); + rhs->base_url(url); + + return shared_from_this(); +} + +Config::ptr_t MergedConfig::clear_base_url() { + lhs->clear_base_url(); + rhs->clear_base_url(); + + return shared_from_this(); +} + +Config::headers_opt_t MergedConfig::headers() const { + return rhs->headers() ? rhs->headers() : lhs->headers(); +} + +Config::ptr_t MergedConfig::headers(const Config::headers_opt_t headers) { + lhs->clear_headers(); + rhs->headers(headers); + + return shared_from_this(); +} + +Config::ptr_t MergedConfig::clear_headers() { + lhs->clear_headers(); + rhs->clear_headers(); + + return shared_from_this(); +} + +MergedConfig::MergedConfig(const Config::cptr_t lconfig, + const Config::cptr_t rconfig) + : lhs{CopyOnWriteConfig::adapt(lconfig)}, + rhs{CopyOnWriteConfig::adapt(rconfig)} {} diff --git a/src/internal/config/MergedConfig.hpp b/src/internal/config/MergedConfig.hpp new file mode 100644 index 0000000..2266753 --- /dev/null +++ b/src/internal/config/MergedConfig.hpp @@ -0,0 +1,53 @@ +#ifndef __WTLGO__NETWORK__INTERNAL__CONFIG__MERGED_CONFIG__ +#define __WTLGO__NETWORK__INTERNAL__CONFIG__MERGED_CONFIG__ + +#include +#include + +namespace wtlgo { +namespace network { +namespace internal { +namespace config { +class MergedConfig : public Config, + public std::enable_shared_from_this { +public: + using ptr_t = std::shared_ptr; + using cptr_t = std::shared_ptr; + + virtual ~MergedConfig(); + + [[nodiscard]] static MergedConfig::ptr_t merge(Config::cptr_t, + Config::cptr_t); + + Config::ptr_t clone() const override; + Config::ptr_t merge(Config::cptr_t) const override; + + Config::url_opt_ref_t url() const override; + Config::ptr_t url(Config::url_ref_t) override; + Config::ptr_t clear_url() override; + + Config::method_opt_t method() const override; + Config::ptr_t method(Config::method_t) override; + Config::ptr_t clear_method() override; + + Config::url_opt_ref_t base_url() const override; + Config::ptr_t base_url(Config::url_ref_t) override; + Config::ptr_t clear_base_url() override; + + Config::headers_opt_t headers() const override; + Config::ptr_t headers(headers_opt_t) override; + Config::ptr_t clear_headers() override; + +protected: + MergedConfig(Config::cptr_t, Config::cptr_t); + +private: + Config::ptr_t lhs; + Config::ptr_t rhs; +}; +} +} +} +} + +#endif diff --git a/src/internal/curl/CurlEasyHandler.cpp b/src/internal/curl/CurlEasyHandler.cpp new file mode 100644 index 0000000..4fe0957 --- /dev/null +++ b/src/internal/curl/CurlEasyHandler.cpp @@ -0,0 +1,18 @@ +#include + +#include "./CurlEasyHandler.hpp" + +using namespace wtlgo::network::internal::curl; + +CurlEasyHandler::CurlEasyHandler() + : _curl{curl_easy_init(), curl_easy_cleanup} {} + +CurlEasyHandler::CurlEasyHandler(const curl_unique_ptr& curl) + : _curl{curl_easy_duphandle(curl.get()), curl_easy_cleanup} {} + +CurlEasyHandler::CurlEasyHandler(const CurlEasyHandler& handler) + : CurlEasyHandler(handler._curl) {} + +CURLcode CurlEasyHandler::perform() const { + return curl_easy_perform(_curl.get()); +} diff --git a/src/internal/curl/CurlEasyHandler.hpp b/src/internal/curl/CurlEasyHandler.hpp new file mode 100644 index 0000000..79dc7c8 --- /dev/null +++ b/src/internal/curl/CurlEasyHandler.hpp @@ -0,0 +1,29 @@ +#ifndef __WTLGO__NETWORK__INTERNAL__CURL__CURL_EASY_HANDLER__ +#define __WTLGO__NETWORK__INTERNAL__CURL__CURL_EASY_HANDLER__ + +#include +#include + +#include "./Types.hpp" + +namespace wtlgo { +namespace network { +namespace internal { +namespace curl { +class CurlEasyHandler { +public: + CurlEasyHandler(); + CurlEasyHandler(const curl_unique_ptr&); + CurlEasyHandler(const CurlEasyHandler&); + + CURLcode perform() const; + +private: + const curl_unique_ptr _curl; +}; +} +} +} +} + +#endif diff --git a/src/internal/curl/CurlEasyHandlerBuilder.cpp b/src/internal/curl/CurlEasyHandlerBuilder.cpp new file mode 100644 index 0000000..ee813ac --- /dev/null +++ b/src/internal/curl/CurlEasyHandlerBuilder.cpp @@ -0,0 +1,359 @@ +#include +#include + +#include + +#include "./CurlEasyHandler.hpp" +#include "./CurlEasyHandlerBuilder.hpp" + +using namespace wtlgo::network::internal::curl; + +CurlEasyHandlerBuilder::CurlEasyHandlerBuilder() + : _curl{curl_easy_init(), curl_easy_cleanup} {} + +CurlEasyHandler CurlEasyHandlerBuilder::build() const { return {_curl}; } + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::verbose(const bool onoff) { + curl_easy_setopt(_curl.get(), CURLOPT_VERBOSE, static_cast(onoff)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::header(const bool onoff) { + curl_easy_setopt(_curl.get(), CURLOPT_HEADER, static_cast(onoff)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::no_progress(const bool onoff) { + curl_easy_setopt(_curl.get(), CURLOPT_NOPROGRESS, static_cast(onoff)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::no_signal(const bool onoff) { + curl_easy_setopt(_curl.get(), CURLOPT_NOSIGNAL, static_cast(onoff)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::wildcard_match( + const bool onoff) { + curl_easy_setopt(_curl.get(), CURLOPT_WILDCARDMATCH, + static_cast(onoff)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::write_function( + const write_function_t write_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_WRITEFUNCTION, write_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::write_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_WRITEDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::read_function( + const read_function_t read_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_READFUNCTION, read_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::read_data(void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_READDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::io_ctl_function( + const io_ctl_function_t ioctl_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_IOCTLFUNCTION, ioctl_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::io_ctl_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_IOCTLDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::seek_function( + const seek_function_t seek_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_SEEKFUNCTION, seek_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::seek_data(void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_SEEKDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::sock_function( + const sock_function_t sock_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_SOCKOPTFUNCTION, sock_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::sock_data(void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_SOCKOPTDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::open_socket_function( + const open_socket_function_t opensocket_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_OPENSOCKETFUNCTION, + opensocket_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::open_socket_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_OPENSOCKETDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::close_socket_function( + const close_socket_function_t closesocket_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_CLOSESOCKETFUNCTION, + closesocket_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::close_socket_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_CLOSESOCKETDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::xfer_info_function( + const xfer_info_function_t progress_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_XFERINFOFUNCTION, progress_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::xfer_info_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_XFERINFODATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::header_function( + const header_function_t header_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_HEADERFUNCTION, header_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::header_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_HEADERDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::debug_function( + const debug_function_t debug_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_DEBUGFUNCTION, debug_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::debug_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_DEBUGDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::ssl_ctx_function( + const ssl_ctx_function_t ssl_ctx_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::ssl_ctx_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_SSL_CTX_DATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::interleave_function( + const interleave_function_t interleave_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_INTERLEAVEFUNCTION, + interleave_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::interleave_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_INTERLEAVEDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::chunk_bgn_function( + const chunk_bgn_function_t chunk_bgn_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_CHUNK_BGN_FUNCTION, + chunk_bgn_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::chunk_end_function( + const chunk_end_function_t chunk_end_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_CHUNK_END_FUNCTION, + chunk_end_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::chunk_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_INTERLEAVEDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::fnmatch_function( + const fnmatch_function_t fnmatch_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_FNMATCH_FUNCTION, fnmatch_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::fnmatch_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_FNMATCH_DATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::supress_connect_headers( + const bool onoff) { + curl_easy_setopt(_curl.get(), CURLOPT_SUPPRESS_CONNECT_HEADERS, + static_cast(onoff)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::resolver_start_function( + const resolver_start_function_t resolver_start_cb) { + curl_easy_setopt(_curl.get(), CURLOPT_RESOLVER_START_FUNCTION, + resolver_start_cb); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::resolver_start_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_RESOLVER_START_DATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::prereq_function( + const prereq_function_t prereq_callback) { + curl_easy_setopt(_curl.get(), CURLOPT_PREREQFUNCTION, prereq_callback); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::prereq_data( + void* const pointer) { + curl_easy_setopt(_curl.get(), CURLOPT_PREREQDATA, pointer); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::error_buffer(char* const buf) { + curl_easy_setopt(_curl.get(), CURLOPT_ERRORBUFFER, buf); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::std_err(FILE* const stream) { + curl_easy_setopt(_curl.get(), CURLOPT_STDERR, stream); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::fail_on_error(const bool fail) { + curl_easy_setopt(_curl.get(), CURLOPT_FAILONERROR, static_cast(fail)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::keep_sending_on_error( + const bool keep_sending) { + curl_easy_setopt(_curl.get(), CURLOPT_KEEP_SENDING_ON_ERROR, + static_cast(keep_sending)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::url(const std::string& url) { + curl_easy_setopt(_curl.get(), CURLOPT_URL, url.c_str()); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::path_as_is(const bool leaveit) { + curl_easy_setopt(_curl.get(), CURLOPT_PATH_AS_IS, + static_cast(leaveit)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::protocols(const long bitmask) { + curl_easy_setopt(_curl.get(), CURLOPT_PROTOCOLS, bitmask); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::protocols_str( + const std::string& spec) { + curl_easy_setopt(_curl.get(), CURLOPT_PROTOCOLS_STR, spec.c_str()); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::redir_protocols( + const long bitmask) { + curl_easy_setopt(_curl.get(), CURLOPT_REDIR_PROTOCOLS, bitmask); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::redir_protocols_str( + const std::string& spec) { + curl_easy_setopt(_curl.get(), CURLOPT_PROTOCOLS_STR, spec.c_str()); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::default_protocol( + const std::string& protocol) { + curl_easy_setopt(_curl.get(), CURLOPT_DEFAULT_PROTOCOL, protocol.c_str()); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::proxy( + const std::string& proxy) { + curl_easy_setopt(_curl.get(), CURLOPT_PROXY, proxy.c_str()); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::pre_proxy( + const std::string& preproxy) { + curl_easy_setopt(_curl.get(), CURLOPT_PRE_PROXY, preproxy.c_str()); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::proxy_port( + const std::uint16_t port) { + curl_easy_setopt(_curl.get(), CURLOPT_PROXYPORT, static_cast(port)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::proxy_type(const long type) { + curl_easy_setopt(_curl.get(), CURLOPT_PROXYPORT, type); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::no_proxy( + const std::string& noproxy) { + curl_easy_setopt(_curl.get(), CURLOPT_NOPROXY, noproxy.c_str()); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::http_proxy_tunnel( + const bool tunnel) { + curl_easy_setopt(_curl.get(), CURLOPT_HTTPPROXYTUNNEL, + static_cast(tunnel)); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::connect_to( + curl_slist* const connect_to) { + curl_easy_setopt(_curl.get(), CURLOPT_CONNECT_TO, connect_to); + return *this; +} + +CurlEasyHandlerBuilder& CurlEasyHandlerBuilder::socks5_auth( + const long bitmask) { + curl_easy_setopt(_curl.get(), CURLOPT_SOCKS5_AUTH, bitmask); + return *this; +} diff --git a/src/internal/curl/CurlEasyHandlerBuilder.hpp b/src/internal/curl/CurlEasyHandlerBuilder.hpp new file mode 100644 index 0000000..2f93ca7 --- /dev/null +++ b/src/internal/curl/CurlEasyHandlerBuilder.hpp @@ -0,0 +1,157 @@ +#ifndef __WTLGO__NETWORK__INTERNAL__CURL__CURL_EASY_HANDLER_BUILDER__ +#define __WTLGO__NETWORK__INTERNAL__CURL__CURL_EASY_HANDLER_BUILDER__ + +#include +#include +#include + +#include "./Types.hpp" +#include "./CurlEasyHandler.hpp" + +namespace wtlgo { +namespace network { +namespace internal { +namespace curl { +class CurlEasyHandlerBuilder { +public: + CurlEasyHandlerBuilder(); + + CurlEasyHandler build() const; + + CurlEasyHandlerBuilder& verbose(bool onoff); + CurlEasyHandlerBuilder& header(bool onoff); + CurlEasyHandlerBuilder& no_progress(bool onoff); + CurlEasyHandlerBuilder& no_signal(bool onoff); + CurlEasyHandlerBuilder& wildcard_match(bool onoff); + + using write_function_t = std::size_t (*)(char* ptr, std::size_t size, + std::size_t nmemb, void* userdata); + CurlEasyHandlerBuilder& write_function(write_function_t write_callback); + CurlEasyHandlerBuilder& write_data(void* pointer); + + using read_function_t = std::size_t (*)(char* buffer, std::size_t size, + std::size_t nitems, void* userdata); + CurlEasyHandlerBuilder& read_function(read_function_t read_callback); + CurlEasyHandlerBuilder& read_data(void* pointer); + + using io_ctl_function_t = curlioerr (*)(CURL* handle, curliocmd cmd, + void* clientp); + CurlEasyHandlerBuilder& io_ctl_function(io_ctl_function_t ioctl_callback); + CurlEasyHandlerBuilder& io_ctl_data(void* pointer); + + using seek_function_t = int (*)(void* userp, curl_off_t offset, int origin); + CurlEasyHandlerBuilder& seek_function(seek_function_t seek_callback); + CurlEasyHandlerBuilder& seek_data(void* pointer); + + using sock_function_t = int (*)(void* clientp, curl_socket_t curlfd, + curlsocktype purpose); + CurlEasyHandlerBuilder& sock_function(sock_function_t sock_callback); + CurlEasyHandlerBuilder& sock_data(void* pointer); + + using open_socket_function_t = curl_socket_t (*)(void* clientp, + curlsocktype purpose, + curl_sockaddr* address); + CurlEasyHandlerBuilder& open_socket_function( + open_socket_function_t opensocket_callback); + CurlEasyHandlerBuilder& open_socket_data(void* pointer); + + using close_socket_function_t = curl_socket_t (*)(void* clientp, + curlsocktype purpose, + curl_sockaddr* address); + CurlEasyHandlerBuilder& close_socket_function( + close_socket_function_t closesocket_callback); + CurlEasyHandlerBuilder& close_socket_data(void* pointer); + + using xfer_info_function_t = int (*)(void* clientp, curl_off_t dltotal, + curl_off_t dlnow, curl_off_t ultotal, + curl_off_t ulnow); + CurlEasyHandlerBuilder& xfer_info_function( + xfer_info_function_t progress_callback); + CurlEasyHandlerBuilder& xfer_info_data(void* pointer); + + using header_function_t = std::size_t (*)(char* buffer, std::size_t size, + std::size_t nitems, + void* userdata); + CurlEasyHandlerBuilder& header_function(header_function_t header_callback); + CurlEasyHandlerBuilder& header_data(void* pointer); + + using debug_function_t = int (*)(CURL* handle, curl_infotype type, + char* data, std::size_t size, + void* userptr); + CurlEasyHandlerBuilder& debug_function(debug_function_t debug_callback); + CurlEasyHandlerBuilder& debug_data(void* pointer); + + using ssl_ctx_function_t = CURLcode (*)(CURL* curl, void* ssl_ctx, + void* userptr); + CurlEasyHandlerBuilder& ssl_ctx_function( + ssl_ctx_function_t ssl_ctx_callback); + CurlEasyHandlerBuilder& ssl_ctx_data(void* pointer); + + using interleave_function_t = std::size_t (*)(void* ptr, std::size_t size, + std::size_t nmemb, + void* userdata); + CurlEasyHandlerBuilder& interleave_function( + interleave_function_t interleave_callback); + CurlEasyHandlerBuilder& interleave_data(void* pointer); + + using chunk_bgn_function_t = long (*)(const void* transfer_info, void* ptr, + int remains); + using chunk_end_function_t = long (*)(void* ptr); + CurlEasyHandlerBuilder& chunk_bgn_function( + chunk_bgn_function_t chunk_bgn_callback); + CurlEasyHandlerBuilder& chunk_end_function( + chunk_end_function_t chunk_end_callback); + CurlEasyHandlerBuilder& chunk_data(void* pointer); + + using fnmatch_function_t = int (*)(void* ptr, const char* pattern, + const char* string); + CurlEasyHandlerBuilder& fnmatch_function( + fnmatch_function_t fnmatch_callback); + CurlEasyHandlerBuilder& fnmatch_data(void* pointer); + + CurlEasyHandlerBuilder& supress_connect_headers(bool onoff); + + using resolver_start_function_t = int (*)(void* resolver_state, + void* reserved, void* userdata); + CurlEasyHandlerBuilder& resolver_start_function( + resolver_start_function_t resolver_start_cb); + CurlEasyHandlerBuilder& resolver_start_data(void* pointer); + + using prereq_function_t = int (*)(void* clientp, char* conn_primary_ip, + char* conn_local_ip, + int conn_primary_port, + int conn_local_port); + CurlEasyHandlerBuilder& prereq_function(prereq_function_t prereq_callback); + CurlEasyHandlerBuilder& prereq_data(void* pointer); + + CurlEasyHandlerBuilder& error_buffer(char* buf); + CurlEasyHandlerBuilder& std_err(FILE* stream); + CurlEasyHandlerBuilder& fail_on_error(bool fail); + CurlEasyHandlerBuilder& keep_sending_on_error(bool keep_sending); + + CurlEasyHandlerBuilder& url(const std::string& url); + CurlEasyHandlerBuilder& path_as_is(bool leaveit); + CurlEasyHandlerBuilder& protocols(long bitmask); + CurlEasyHandlerBuilder& protocols_str(const std::string& spec); + CurlEasyHandlerBuilder& redir_protocols(long bitmask); + CurlEasyHandlerBuilder& redir_protocols_str(const std::string& spec); + CurlEasyHandlerBuilder& default_protocol(const std::string& protocol); + CurlEasyHandlerBuilder& proxy(const std::string& proxy); + CurlEasyHandlerBuilder& pre_proxy(const std::string& preproxy); + CurlEasyHandlerBuilder& proxy_port(std::uint16_t port); + CurlEasyHandlerBuilder& proxy_type(long type); + CurlEasyHandlerBuilder& no_proxy(const std::string& noproxy); + CurlEasyHandlerBuilder& http_proxy_tunnel(bool tunnel); + CurlEasyHandlerBuilder& connect_to(curl_slist* connect_to); + CurlEasyHandlerBuilder& socks5_auth(long bitmask); + + // TODO: https://curl.se/libcurl/c/curl_easy_setopt.html +private: + const curl_unique_ptr _curl; +}; +} +} +} +} + +#endif diff --git a/src/internal/curl/Types.hpp b/src/internal/curl/Types.hpp new file mode 100644 index 0000000..a71de38 --- /dev/null +++ b/src/internal/curl/Types.hpp @@ -0,0 +1,19 @@ +#ifndef __WTLGO__NETWORK__INTERNAL__CURL__TYPES__ +#define __WTLGO__NETWORK__INTERNAL__CURL__TYPES__ + +#include + +#include + +namespace wtlgo { +namespace network { +namespace internal { +namespace curl { +using curl_shared_ptr = std::shared_ptr; +using curl_unique_ptr = std::unique_ptr; +} +} +} +} + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 99a94d6..030ea9b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,26 +1,30 @@ cmake_minimum_required(VERSION 3.13) -find_package(GTest) -find_package(nlohmann_json) -include(GoogleTest) +include(FetchContent) -if(${GTest_FOUND} AND ${nlohmann_json_FOUND}) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") -file(GLOB TEST_SRC - "src/*.hpp" - "src/*.cpp" +FetchContent_Declare( + json + URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz ) +FetchContent_MakeAvailable(json) -add_executable(Network_test ${TEST_SRC}) -target_link_libraries(Network_test Network ${GTEST_LIBRARIES} nlohmann_json) -target_include_directories(Network_test PRIVATE ${GTEST_INCLUDE_DIR}) -gtest_add_tests(TARGET Network_test AUTO) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip +) -else() +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) -message(WARNING "You should install GTest and nlohmann-json to enable tests.") -endif() +file(GLOB TEST_SRC + "src/*.cpp" +) +add_executable(Network_test ${TEST_SRC}) +target_link_libraries(Network_test Network GTest::gtest_main nlohmann_json::nlohmann_json) + +include(GoogleTest) +gtest_discover_tests(Network_test) diff --git a/test/src/ClientConfig.cpp b/test/src/ClientConfig.cpp new file mode 100644 index 0000000..b9928e2 --- /dev/null +++ b/test/src/ClientConfig.cpp @@ -0,0 +1,426 @@ +#include +#include "utility.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +TEST(ClientConfig, Create) { + using namespace wtlgo::network; + + const Config::cptr_t config = ClientConfig::create(); + ASSERT_NE(config, nullptr); +} + +TEST(ClientConfig, Clone) { + using namespace wtlgo::network; + + const Config::cptr_t config = ClientConfig::create(); + + const Config::cptr_t clone = config->clone(); + ASSERT_NE(clone, nullptr); + ASSERT_NE(clone, config); +} + +TEST(ClientConfig, Merge) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = ClientConfig::create(); + const Config::cptr_t rconfig = ClientConfig::create(); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_NE(mconfig, nullptr); + ASSERT_NE(mconfig, lconfig); + ASSERT_NE(mconfig, lconfig); +} + +TEST(ClientConfig, UrlSet) { + using namespace wtlgo::network; + + const Config::ptr_t config = ClientConfig::create(); + + const std::string test_url = random_string(); + + ASSERT_EQ(config, config->url(test_url)); + ASSERT_EQ(config->url(), test_url); +} + +TEST(ClientConfig, UrlClear) { + using namespace wtlgo::network; + + const Config::ptr_t config = ClientConfig::create()->url(random_string()); + + ASSERT_EQ(config, config->clear_url()); + ASSERT_EQ(config->url(), std::nullopt); +} + +TEST(ClientConfig, UrlCloneEmpty) { + using namespace wtlgo::network; + + const Config::cptr_t config = ClientConfig::create()->clear_url(); + const Config::cptr_t clone = config->clone(); + + ASSERT_EQ(clone->url(), std::nullopt); +} + +TEST(ClientConfig, UrlCloneValue) { + using namespace wtlgo::network; + + const Config::cptr_t config = ClientConfig::create()->url(random_string()); + const Config::cptr_t clone = config->clone(); + + ASSERT_EQ(clone->url(), config->url()); + ASSERT_NE(clone->url()->data(), config->url()->data()); +} + +TEST(ClientConfig, UrlMergeEmpty) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = ClientConfig::create()->clear_url(); + const Config::cptr_t rconfig = ClientConfig::create()->clear_url(); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->url(), std::nullopt); +} + +TEST(ClientConfig, UrlMergeRight) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = ClientConfig::create()->clear_url(); + const Config::cptr_t rconfig = ClientConfig::create()->url(random_string()); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->url(), rconfig->url()); +} + +TEST(ClientConfig, UrlMergeLeft) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = ClientConfig::create()->url(random_string()); + const Config::cptr_t rconfig = ClientConfig::create()->clear_url(); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->url(), lconfig->url()); +} + +TEST(ClientConfig, UrlMergeFull) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = ClientConfig::create()->url(random_string()); + const Config::cptr_t rconfig = ClientConfig::create()->url(random_string()); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->url(), rconfig->url()); +} + +TEST(ClientConfig, MethodDefault) { + using namespace wtlgo::network; + + const Config::cptr_t config = ClientConfig::create(); + ASSERT_EQ(config->method(), std::nullopt); +} + +TEST(ClientConfig, MethodSet) { + using namespace wtlgo::network; + + const Config::ptr_t config = ClientConfig::create(); + + for (const HttpMethod test_method : + {HttpMethod::GET, HttpMethod::POST, HttpMethod::PATCH, HttpMethod::PUT, + HttpMethod::DELETE}) { + ASSERT_EQ(config, config->method(test_method)); + ASSERT_EQ(config->method(), test_method); + } +} + +TEST(ClientConfig, MethodClear) { + using namespace wtlgo::network; + + for (const HttpMethod test_method : + {HttpMethod::GET, HttpMethod::POST, HttpMethod::PATCH, HttpMethod::PUT, + HttpMethod::DELETE}) { + const Config::ptr_t config = + ClientConfig::create()->method(test_method); + + ASSERT_EQ(config, config->clear_method()); + ASSERT_EQ(config->method(), std::nullopt); + } +} + +TEST(ClientConfig, MethodCloneEmpty) { + using namespace wtlgo::network; + + const Config::cptr_t config = ClientConfig::create()->clear_method(); + const Config::cptr_t clone = config->clone(); + + ASSERT_EQ(clone->method(), std::nullopt); +} + +TEST(ClientConfig, MethodCloneValue) { + using namespace wtlgo::network; + + for (const HttpMethod test_method : + {HttpMethod::GET, HttpMethod::POST, HttpMethod::PATCH, HttpMethod::PUT, + HttpMethod::DELETE}) { + const Config::cptr_t config = + ClientConfig::create()->method(test_method); + const Config::cptr_t clone = config->clone(); + + ASSERT_EQ(clone->method(), test_method); + } +} + +TEST(ClientConfig, MethodMergeEmpty) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = ClientConfig::create()->clear_method(); + const Config::cptr_t rconfig = ClientConfig::create()->clear_method(); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(rconfig->method(), std::nullopt); +} + +TEST(ClientConfig, MethodMergeLeft) { + using namespace wtlgo::network; + + const Config::cptr_t rconfig = ClientConfig::create()->clear_method(); + + for (const HttpMethod test_method : + {HttpMethod::GET, HttpMethod::POST, HttpMethod::PATCH, HttpMethod::PUT, + HttpMethod::DELETE}) { + const Config::cptr_t lconfig = + ClientConfig::create()->method(test_method); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->method(), test_method); + } +} + +TEST(ClientConfig, MethodMergeRight) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = ClientConfig::create()->clear_method(); + + for (const HttpMethod test_method : + {HttpMethod::GET, HttpMethod::POST, HttpMethod::PATCH, HttpMethod::PUT, + HttpMethod::DELETE}) { + const Config::cptr_t rconfig = + ClientConfig::create()->method(test_method); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->method(), test_method); + } +} + +TEST(ClientConfig, MethodMergeFull) { + using namespace wtlgo::network; + + for (const HttpMethod ltest_method : + {HttpMethod::GET, HttpMethod::POST, HttpMethod::PATCH, HttpMethod::PUT, + HttpMethod::DELETE}) { + const Config::cptr_t lconfig = + ClientConfig::create()->method(ltest_method); + + for (const HttpMethod rtest_method : + {HttpMethod::GET, HttpMethod::POST, HttpMethod::PATCH, + HttpMethod::PUT, HttpMethod::DELETE}) { + const Config::cptr_t rconfig = + ClientConfig::create()->method(rtest_method); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->method(), rtest_method); + } + } +} + +TEST(ClientConfig, BaseUrlSet) { + using namespace wtlgo::network; + + const Config::ptr_t config = ClientConfig::create(); + + const std::string test_url = random_string(); + + ASSERT_EQ(config, config->base_url(test_url)); + ASSERT_EQ(config->base_url(), test_url); +} + +TEST(ClientConfig, BaseUrlClear) { + using namespace wtlgo::network; + + const Config::ptr_t config = + ClientConfig::create()->base_url(random_string()); + + ASSERT_EQ(config, config->clear_base_url()); + ASSERT_EQ(config->base_url(), std::nullopt); +} + +TEST(ClientConfig, BaseUrlCloneEmpty) { + using namespace wtlgo::network; + + const Config::cptr_t config = ClientConfig::create()->clear_base_url(); + const Config::cptr_t clone = config->clone(); + + ASSERT_EQ(clone->base_url(), std::nullopt); +} + +TEST(ClientConfig, BaseUrlCloneValue) { + using namespace wtlgo::network; + + const Config::cptr_t config = + ClientConfig::create()->base_url(random_string()); + const Config::cptr_t clone = config->clone(); + + ASSERT_EQ(clone->base_url(), config->base_url()); + ASSERT_NE(clone->base_url()->data(), config->base_url()->data()); +} + +TEST(ClientConfig, BaseUrlMergeEmpty) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = ClientConfig::create()->clear_base_url(); + const Config::cptr_t rconfig = ClientConfig::create()->clear_base_url(); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->base_url(), std::nullopt); +} + +TEST(ClientConfig, BaseUrlMergeRight) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = ClientConfig::create()->clear_base_url(); + const Config::cptr_t rconfig = + ClientConfig::create()->base_url(random_string()); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->base_url(), rconfig->base_url()); +} + +TEST(ClientConfig, BaseUrlMergeLeft) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = + ClientConfig::create()->base_url(random_string()); + const Config::cptr_t rconfig = ClientConfig::create()->clear_base_url(); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->base_url(), lconfig->base_url()); +} + +TEST(ClientConfig, BaseUrlMergeFull) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = + ClientConfig::create()->base_url(random_string()); + const Config::cptr_t rconfig = + ClientConfig::create()->base_url(random_string()); + const Config::cptr_t mconfig = lconfig->merge(rconfig); + + ASSERT_EQ(mconfig->base_url(), rconfig->base_url()); +} + +TEST(ClientConfig, HeadersSet) { + using namespace wtlgo::network; + + const Config::ptr_t config = ClientConfig::create(); + const Headers::cptr_t test_headers = HttpHeaders::create(); + + ASSERT_EQ(config, config->headers(test_headers)); + ASSERT_NE(config->headers(), nullptr); +} + +TEST(ClientConfig, HeadersClear) { + using namespace wtlgo::network; + + const Config::ptr_t config = + ClientConfig::create()->headers(HttpHeaders::create()); + + ASSERT_EQ(config, config->clear_headers()); + ASSERT_EQ(config->headers(), nullptr); +} + +TEST(ClientConfig, HeadersCloneEmpty) { + using namespace wtlgo::network; + + const Config::cptr_t config = ClientConfig::create()->clear_headers(); + const Config::cptr_t clone = config->clone(); + + ASSERT_EQ(clone->headers(), nullptr); +} + +TEST(ClientConfig, HeadersCloneValue) { + using namespace wtlgo::network; + + const std::string test_key = random_string(); + + const Config::cptr_t config = ClientConfig::create()->headers( + HttpHeaders::create()->set(test_key, random_string())); + const Config::cptr_t clone = config->clone(); + + ASSERT_NE(clone->headers(), nullptr); + ASSERT_NE(config->headers(), clone->headers()); + ASSERT_EQ(config->headers()->get(test_key), + clone->headers()->get(test_key)); +} + +TEST(ClientConfig, HeadersMergeEmpty) { + using namespace wtlgo::network; + + const Config::cptr_t lconfig = ClientConfig::create()->clear_headers(); + const Config::cptr_t rconfig = ClientConfig::create()->clear_headers(); + const Config::cptr_t merge = lconfig->merge(rconfig); + + ASSERT_EQ(merge->headers(), nullptr); +} + +TEST(ClientConfig, HeadersMergeLeft) { + using namespace wtlgo::network; + + const std::string test_key = random_string(); + + const Config::cptr_t lconfig = ClientConfig::create()->headers( + HttpHeaders::create()->set(test_key, random_string())); + const Config::cptr_t rconfig = ClientConfig::create()->clear_headers(); + const Config::cptr_t merge = lconfig->merge(rconfig); + + ASSERT_NE(merge->headers(), nullptr); + ASSERT_EQ(merge->headers()->get(test_key), + lconfig->headers()->get(test_key)); +} + +TEST(ClientConfig, HeadersMergeRight) { + using namespace wtlgo::network; + + const std::string test_key = random_string(); + + const Config::cptr_t lconfig = ClientConfig::create()->clear_headers(); + const Config::cptr_t rconfig = ClientConfig::create()->headers( + HttpHeaders::create()->set(test_key, random_string())); + const Config::cptr_t merge = lconfig->merge(rconfig); + + ASSERT_NE(merge->headers(), nullptr); + ASSERT_EQ(merge->headers()->get(test_key), + rconfig->headers()->get(test_key)); +} + +TEST(ClientConfig, HeadersMergeFull) { + using namespace wtlgo::network; + + const std::string test_key = random_string(); + + const Config::cptr_t lconfig = ClientConfig::create()->headers( + HttpHeaders::create()->set(test_key, random_string())); + const Config::cptr_t rconfig = ClientConfig::create()->headers( + HttpHeaders::create()->set(test_key, random_string())); + const Config::cptr_t merge = lconfig->merge(rconfig); + + ASSERT_NE(merge->headers(), nullptr); + ASSERT_NE(merge->headers()->get(test_key), + lconfig->headers()->get(test_key)); + ASSERT_EQ(merge->headers()->get(test_key), + rconfig->headers()->get(test_key)); +} diff --git a/test/src/CurlClient.cpp b/test/src/CurlClient.cpp new file mode 100644 index 0000000..9885fe4 --- /dev/null +++ b/test/src/CurlClient.cpp @@ -0,0 +1,18 @@ +#include + +#include +#include + +TEST(CurlClient, Create) { + using namespace wtlgo::network; + + const Client::cptr_t client = CurlClient::create(); + ASSERT_NE(client, nullptr); +} + +TEST(CurlClient, InitConfig) { + using namespace wtlgo::network; + + const Client::cptr_t client = CurlClient::create(); + ASSERT_NE(client->config(), nullptr); +} diff --git a/test/src/HttpBasicAuth.cpp b/test/src/HttpBasicAuth.cpp new file mode 100644 index 0000000..be37536 --- /dev/null +++ b/test/src/HttpBasicAuth.cpp @@ -0,0 +1,60 @@ +#include +#include "utility.hpp" + +#include + +#include +#include + +TEST(HttpBasicAuth, Create) { + using namespace wtlgo::network; + + const std::string test_username = random_string(); + const std::string test_password = random_string(); + + const Auth::cptr_t client = + HttpBasicAuth::create(test_username, test_password); + + ASSERT_NE(client, nullptr); + ASSERT_EQ(client->username(), test_username); + ASSERT_EQ(client->password(), test_password); +} + +TEST(HttpBasicAuth, Clone) { + using namespace wtlgo::network; + + const Auth::cptr_t client = + HttpBasicAuth::create(random_string(), random_string()); + + const Auth::cptr_t clone = client->clone(); + ASSERT_NE(clone, nullptr); + ASSERT_NE(clone, client); + + ASSERT_EQ(client->username(), clone->username()); + ASSERT_NE(client->username().data(), clone->username().data()); + + ASSERT_EQ(client->password(), clone->password()); + ASSERT_NE(client->password().data(), clone->password().data()); +} + +TEST(HttpBasicAuth, SetUsername) { + using namespace wtlgo::network; + + const Auth::ptr_t client = + HttpBasicAuth::create(random_string(), random_string()); + + const std::string test_username = random_string(); + ASSERT_EQ(client, client->username(test_username)); + ASSERT_EQ(client->username(), test_username); +} + +TEST(HttpBasicAuth, SetPassword) { + using namespace wtlgo::network; + + const Auth::ptr_t client = + HttpBasicAuth::create(random_string(), random_string()); + + const std::string test_password = random_string(); + ASSERT_EQ(client, client->password(test_password)); + ASSERT_EQ(client->password(), test_password); +} diff --git a/test/src/HttpHeaders.cpp b/test/src/HttpHeaders.cpp new file mode 100644 index 0000000..1df012b --- /dev/null +++ b/test/src/HttpHeaders.cpp @@ -0,0 +1,86 @@ +#include +#include "utility.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +TEST(HttpHeaders, Create) { + using namespace wtlgo::network; + + const Headers::cptr_t headers = HttpHeaders::create(); + ASSERT_NE(headers, nullptr); +} + +TEST(HttpHeaders, Clone) { + using namespace wtlgo::network; + + const Headers::ptr_t headers = HttpHeaders::create(); + for (std::size_t i = 0; i < 50; ++i) { + headers->set(random_string(), random_string()); + } + + Headers::cptr_t clone = headers->clone(); + + ASSERT_NE(clone, nullptr); + ASSERT_NE(clone, headers); + + for (const auto& header : headers->keys()) { + ASSERT_EQ(headers->get(header), clone->get(header)); + ASSERT_NE(headers->get(header)->data(), clone->get(header)->data()); + } +} + +TEST(HttpHeaders, Set) { + using namespace wtlgo::network; + + const Headers::ptr_t headers = HttpHeaders::create(); + + const std::string key = random_string(); + const std::string value = random_string(); + + ASSERT_EQ(headers, headers->set(key, value)); + ASSERT_NE(headers->get(key), std::nullopt); + ASSERT_EQ(headers->get(key), value); +} + +TEST(HttpHeaders, Erase) { + using namespace wtlgo::network; + + const std::string key = random_string(); + Headers::ptr_t headers = HttpHeaders::create()->set(key, random_string()); + + ASSERT_EQ(headers, headers->erase(key)); + ASSERT_EQ(headers->get(key), std::nullopt); +} + +TEST(HttpHeaders, Keys) { + using namespace wtlgo::network; + + const std::set keys = [] { + std::set res; + std::generate_n(std::inserter(res, res.begin()), + random_unsigned() % 100, + []() { return random_string(); }); + return res; + }(); + + const Headers::ptr_t headers = HttpHeaders::create(); + for (const auto& header : keys) { + headers->set(header, random_string()); + } + + const std::set ref_keys = [&keys] { + std::set res; + std::copy(keys.cbegin(), keys.cend(), std::inserter(res, res.begin())); + return res; + }(); + + ASSERT_EQ(ref_keys, headers->keys()); +} diff --git a/test/src/PrimaryTest.cpp b/test/src/PrimaryTest.cpp deleted file mode 100644 index 31cc2b9..0000000 --- a/test/src/PrimaryTest.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include - -TEST(Primary, DidInit) -{ - using namespace wtlgo; - ASSERT_EQ(&network, &Network::instance()); -} diff --git a/test/src/RequestTestsGet.cpp b/test/src/RequestTestsGet.cpp deleted file mode 100644 index 4df64ff..0000000 --- a/test/src/RequestTestsGet.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include -#include -#include -#include - -#include "utility.hpp" - -TEST(Request, Get_NoSSL_Static) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - - string response = network.request("http://" + test_server + "/get", {{"hello", "hello"}}); - ASSERT_NO_THROW({ - auto resp = json::parse(response); - ASSERT_EQ(resp["args"]["hello"], "hello"); - }); -} - -TEST(Request, Get_SSL_Static) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - - string response = network.request("https://" + test_server + "/get", {{"hello", "hello"}}); - ASSERT_NO_THROW({ - auto resp = json::parse(response); - ASSERT_EQ(resp["args"]["hello"], "hello"); - }); -} - -TEST(Request, Get_NoSSL_Random) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - - ASSERT_NO_THROW({ - const string arg = random_string(500); - const string val = random_string(1000); - - string response = network.request("http://" + test_server + "/get", {{arg, val}}); - auto resp = json::parse(response); - - ASSERT_EQ(resp["args"][arg], val); - }); -} - -TEST(Request, Get_SSL_Random) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - - ASSERT_NO_THROW({ - const string arg = random_string(500); - const string val = random_string(500); - - string response = network.request("https://" + test_server + "/get", {{arg, val}}); - auto resp = json::parse(response); - - ASSERT_EQ(resp["args"][arg], val); - }); -} - -TEST(Request, Get_NoSSL_Random_MultipleArgs) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - using map = std::map; - - map args; - for(size_t i = 0; i < 10; ++i) { - args[random_string(50)] = random_string(50); - } - - ASSERT_NO_THROW({ - string response = network.request("http://" + test_server + "/get", args); - auto resp = json::parse(response); - - for(const auto& arg : args) { - ASSERT_EQ(resp["args"][arg.first], arg.second); - } - }); -} - -TEST(Request, Get_SSL_Random_MultipleArgs) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - using map = std::map; - - map args; - for(size_t i = 0; i < 10; ++i) { - args[random_string(50)] = random_string(50); - } - - ASSERT_NO_THROW({ - string response = network.request("https://" + test_server + "/get", args); - auto resp = json::parse(response); - - for(const auto& arg : args) { - ASSERT_EQ(resp["args"][arg.first], arg.second); - } - }); -} diff --git a/test/src/RequestTestsPost.cpp b/test/src/RequestTestsPost.cpp deleted file mode 100644 index 8d2f05d..0000000 --- a/test/src/RequestTestsPost.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include -#include -#include -#include - -#include "utility.hpp" - -TEST(Request, Post_NoSSL_Static) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - - string response = network.request("http://" + test_server + "/post", {{"hello", "hello"}}, true); - ASSERT_NO_THROW({ - auto resp = json::parse(response); - ASSERT_EQ(resp["form"]["hello"], "hello"); - }); -} - -TEST(Request, Post_SSL_Static) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - - string response = network.request("https://" + test_server + "/post", {{"hello", "hello"}}, true); - ASSERT_NO_THROW({ - auto resp = json::parse(response); - ASSERT_EQ(resp["form"]["hello"], "hello"); - }); -} - -TEST(Request, Post_NoSSL_Random) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - - ASSERT_NO_THROW({ - const string arg = random_string(1000); - const string val = random_string(1000); - - string response = network.request("http://" + test_server + "/post", {{arg, val}}, true); - auto resp = json::parse(response); - - ASSERT_EQ(resp["form"][arg], val); - }); -} - -TEST(Request, Post_SSL_Random) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - - ASSERT_NO_THROW({ - const string arg = random_string(1000); - const string val = random_string(1000); - - string response = network.request("https://" + test_server + "/post", {{arg, val}}, true); - auto resp = json::parse(response); - - ASSERT_EQ(resp["form"][arg], val); - }); -} - -TEST(Request, Post_NoSSL_Random_MultipleArgs) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - using map = std::map; - - map args; - for(size_t i = 0; i < 10; ++i) { - args[random_string(1000)] = random_string(1000); - } - - ASSERT_NO_THROW({ - string response = network.request("http://" + test_server + "/post", args, true); - auto resp = json::parse(response); - - for(const auto& arg : args) { - ASSERT_EQ(resp["form"][arg.first], arg.second); - } - }); -} - -TEST(Request, Post_SSL_Random_MultipleArgs) -{ - using namespace wtlgo; - using json = nlohmann::json; - using string = std::string; - using map = std::map; - - map args; - for(size_t i = 0; i < 10; ++i) { - args[random_string(1000)] = random_string(1000); - } - - string response = network.request("https://" + test_server + "/post", args, true); - ASSERT_NO_THROW({ - auto resp = json::parse(response); - - for(const auto& arg : args) { - ASSERT_EQ(resp["form"][arg.first], arg.second); - } - }); -} diff --git a/test/src/test.cpp b/test/src/test.cpp deleted file mode 100644 index f81367c..0000000 --- a/test/src/test.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main(int argc, char* argv[]) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/test/src/utility.cpp b/test/src/utility.cpp index ae25864..0509db3 100644 --- a/test/src/utility.cpp +++ b/test/src/utility.cpp @@ -1,15 +1,28 @@ #include #include +#include #include "utility.hpp" const std::string test_server = "httpbin.org"; -std::string random_string(size_t max_length) { - static std::mt19937_64 engine { (std::random_device())() }; - std::uniform_int_distribution sdist(0, max_length); - std::uniform_int_distribution cdist(0, 127); +namespace { +std::mt19937_64 engine{(std::random_device{})()}; +} + +std::string random_string(const std::size_t max_length, const bool exact) { + std::uniform_int_distribution sdist{1, max_length}; + std::uniform_int_distribution cdist{std::numeric_limits::min(), + std::numeric_limits::max()}; std::string out; - std::generate_n(std::back_inserter(out), sdist(engine), [&cdist]{ return cdist(engine); }); + std::generate_n(std::back_inserter(out), exact ? max_length : sdist(engine), + [&cdist] { return cdist(engine); }); return out; } + +unsigned random_unsigned() { + static std::uniform_int_distribution dist{ + std::numeric_limits::min(), + std::numeric_limits::max()}; + return dist(engine); +} diff --git a/test/src/utility.hpp b/test/src/utility.hpp index c04b9ce..d975f54 100644 --- a/test/src/utility.hpp +++ b/test/src/utility.hpp @@ -3,7 +3,9 @@ #include -std::string random_string(size_t max_length); extern const std::string test_server; -#endif \ No newline at end of file +std::string random_string(size_t max_length = 100, bool exact = false); +unsigned random_unsigned(); + +#endif