diff --git a/CHANGELOG.md b/CHANGELOG.md index da4ce08..f651478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 2024.6.4 (next) +### Breaking Changes +#### App +- `Nickvision::App::Aura::init()` will no longer initialize `libcurl` +#### Helpers +- Renamed `Nickvision::CodeHelpers` namespace to `Nickvision::Helpers::CodeHelpers` +- Renamed `Nickvision::StringHelpers` namespace to `Nickvision::Helpers::StringHelpers` +- Removed `Nickvision::WebHelpers` namespace +### New APIs +#### Network +- `Nickvision::Network::CurlEasy` class +- `Nickvision::Network::WebClient` class +### Fixes +None + ## 2024.6.3 ### Breaking Changes - Removed `enumflags.h` diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ccc5df..b66e0ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") #libnick Definition -project ("libnick" LANGUAGES C CXX VERSION 2024.6.3 DESCRIPTION "A cross-platform base for native Nickvision applications.") +project ("libnick" LANGUAGES C CXX VERSION 2024.6.4 DESCRIPTION "A cross-platform base for native Nickvision applications.") include(CMakePackageConfigHelpers) include(GNUInstallDirs) include(CTest) @@ -46,7 +46,6 @@ add_library (${PROJECT_NAME} "src/filesystem/systemdirectories.cpp" "src/filesystem/userdirectories.cpp" "src/helpers/stringhelpers.cpp" - "src/helpers/webhelpers.cpp" "src/keyring/credential.cpp" "src/keyring/keyring.cpp" "src/keyring/keyringdialogcontroller.cpp" @@ -56,8 +55,10 @@ add_library (${PROJECT_NAME} "src/keyring/systemcredentials.cpp" "src/localization/gettext.cpp" "src/logging/logger.cpp" + "src/network/curleasy.cpp" "src/network/networkmonitor.cpp" "src/network/networkstatechangedeventargs.cpp" + "src/network/webclient.cpp" "src/notifications/notificationsenteventargs.cpp" "src/notifications/notifyicon.cpp" "src/notifications/notifyiconmenu.cpp" @@ -145,8 +146,7 @@ if (BUILD_TESTING) "tests/systemtests.cpp" "tests/taskbartests.cpp" "tests/updatertests.cpp" - "tests/versiontests.cpp" - "tests/webtests.cpp") + "tests/versiontests.cpp") find_package(GTest CONFIG REQUIRED) target_link_libraries(${PROJECT_NAME}_test PRIVATE GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main ${PROJECT_NAME}) endif() diff --git a/docs/app.md b/docs/app.md index 44435b5..0e66868 100644 --- a/docs/app.md +++ b/docs/app.md @@ -183,7 +183,6 @@ Path: `Nickvision::App::Aura` ``` - Accepts: An application id, id, an application name, name, an application english short name, englishShortName, and the application log level, logLevel. - Returns: True if initialized, else false - - Throws: `std::runtime_error` if libcurl fails to initialize - Throws: `std::runtime_error` if the gettext system fails to initialize - Throws: `std::runtime_error` if unable to get the executable directory path - Note: This also calls curl_global_init(). diff --git a/docs/helpers.md b/docs/helpers.md index a2bc65a..d550de6 100644 --- a/docs/helpers.md +++ b/docs/helpers.md @@ -5,7 +5,6 @@ This module contains various helper namespaces that making working with C++ a bi ## Table of Contents - [CodeHelpers](#codehelpers) - [StringHelpers](#stringhelpers) -- [WebHelpers](#webhelpers) ## CodeHelpers Description: Helper functions for working with C++ @@ -14,7 +13,7 @@ Interface: [codehelpers.h](/include/helpers/codehelpers.h) Type: `namespace` -Path: `Nickvision::CodeHelpers` +Path: `Nickvision::Helpers::CodeHelpers` ### Functions - ```cpp @@ -30,7 +29,7 @@ Interface: [stringhelpers.h](/include/helpers/stringhelpers.h) Type: `namespace` -Path: `Nickvision::StringHelpers` +Path: `Nickvision::Helpers::StringHelpers` ### Functions - ```cpp @@ -120,38 +119,3 @@ Path: `Nickvision::StringHelpers` - Accepts: A non-wide string parameter, s. - Returns: s as a wide string. - Ex: `StringHelpers::wstr("abc")` will return `L"abc"`. - -## WebHelpers -Description: Helper functions for working with websites - -Interface: [webhelpers.h](/include/helpers/webhelpers.h) - -Type: `namespace` - -Path: `Nickvision::WebHelpers` - -### Functions -- ```cpp - bool downloadFile(const std::string& url, const std::filesystem::path& path, const CurlProgressFunction& progress = {}, bool overwrite = true) - ``` - - Accepts: A url string, url, a path of disk, path, a CurlProgressFunction, progress, and a overwrite boolean, overwrite. - - Returns: `true` if the file provided by url was successfully downloaded to path. - - Returns: `false` if the download failed. - - Note: If progress points to a valid CurlProgressFunction, progress will be called as the file is downloaded to provide the caller with download progress. - - Note: If a file at path exists and overwrite is true, the file will be overwritten, else downloadFile will fail and return false. - - Note: `CurlProgressFunction` is a `std::function` with the signature `int func(curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)` - - Ex: `WebHelpers::downloadFile(https://raw.githubusercontent.com/nlogozzo/nlogozzo/main/README.md, "readme.md")` will return `true` and download the file to path `./readme.md`. -- ```cpp - std::string fetchJsonString(const std::string& url) - ``` - - Accepts: A url string, url. - - Returns: The json string hosted at url. - - Note: If this function fails, an empty string will be returned. - - Ex: `WebHelpers::fetchJsonString("https://api.github.com/repos/nickvisionapps/denaro/tags")` will return [this json string](https://api.github.com/repos/nickvisionapps/denaro/tags). -- ```cpp - bool isValidWebsite(const std::string& url) - ``` - - Accepts: A url string, url. - - Returns: `true` if url points to a valid, live website. - - Returns: `false` if url does not point to a valid website. - - Ex: `WebHelpers::isValidWebsite("https://www.sdfhjsdkfhsjdf.dfh")` will return `false`. diff --git a/docs/network.md b/docs/network.md index 9f43e93..6a30f6e 100644 --- a/docs/network.md +++ b/docs/network.md @@ -3,9 +3,67 @@ This module contains objects for managing a system's network state. ## Table of Contents +- [CurlEasy](#curleasy) - [NetworkMonitor](#networkmonitor) - [NetworkState](#networkstate) - [NetworkStateChangedEventArgs](#networkstatechangedeventargs) +- [WebClient](#webclient) + +## CurlEasy +Description: An object for making easy curl requests. + +Interface: [curleasy.h](/include/network/curleasy.h) + +Type: `class` + +Path: `Nickvision::Network::CurlEasy` + +### Member Variables +- ``` + std::string Url: get, set + ``` + - The url to make requests to. +- ``` + bool NoBody: set + ``` + - Whether or not to include the body in the response. +- ``` + std::vector Headers: set + ``` + - The headers to include in the request. +- ``` + std::string UserAgent: set + ``` + - The user agent to use in the request. +- ``` + std::basic_ostream* Stream: set + ``` + - The stream to write the response to. +- ``` + std::function Progress: set + ``` + - The progress function to use during the request. + +### Methods +- ```cpp + WebClient(const std::string& url) + ``` + - Constructs a CurlEasy. + - Accepts: An optional url to make requests to, url. + - Throws: std::runtime_error if curl fails to initialize. +- ```cpp + ~WebClient() + ``` + - Destructs a CurlEasy. +- ```cpp + void reset(const std::string& url) + ``` + - Accept: An optional url to make requests to on the reset handle, url. + - Throws: std::runtime_error if curl fails to initialize. +- ```cpp + CURLcode perform() + ``` + - Returns: The code returned by the performed the curl request. ## NetworkMonitor Description: An object to monitor the state of the system's network connection. @@ -100,15 +158,51 @@ Type: `class` Path: `Nickvision::Network::NetworkStateChangedEventArgs` -## Member Variables +### Member Variables - ``` NetworkState State: get ``` - The new state of the network. -## Methods +### Methods - ```cpp NetworkStateChangedEventArgs(NetworkState state) ``` - Constructs a NetworkStateChangedEventArgs. - - Accepts: The new NetworkState for the system, state. \ No newline at end of file + - Accepts: The new NetworkState for the system, state. + +## WebClient +Description: An object for interacting with the web. + +Interface: [webclient.h](/include/network/webclient.h) + +Type: `class` + +Path: `Nickvision::Network::WebClient` + +### Methods +- ```cpp + WebClient() + ``` + - Constructs a WebClient. +- ```cpp + ~WebClient() + ``` + - Destructs a WebClient. +- ```cpp + bool getWebsiteExists(const std::string& url) + ``` + - Accepts: The url to check if it exists (i.e. points to a valid domain) or not, url. + - Returns: True if url exists. + - Returns: False if url does not exist. +- ```cpp + std::string fetchJson(const std::string& url) + ``` + - Accepts: The url to request a json string from, url. + - Returns: The fetched json string +- ```cpp + bool downloadFile(const std::string& url, const std::filesystem::path& path, const std::function& progress, bool overwrite) + ``` + - Accepts: The url of the file to download, url, the path to save the downloaded file to on disk, path, an optional function to track download progress, progress, and whether or not to overwrite existing files on disk, overwrite. + - Returns: True if the file was downloaded and saved successfully. + - Returns: False if the file was not downloaded successfully. \ No newline at end of file diff --git a/include/app/aura.h b/include/app/aura.h index 3504578..8e74bfe 100644 --- a/include/app/aura.h +++ b/include/app/aura.h @@ -38,7 +38,6 @@ namespace Nickvision::App * @param englishShortName The application short name in English * @param logLevel The application log level * @throw std::runtime_error Thrown if unable to get the executable directory path - * @throw std::runtime_error Thrown if libcurl fails to initialize * @throw std::runtime_error Thrown if the gettext system fails to initialize * @return True if initialized, else false */ diff --git a/include/helpers/codehelpers.h b/include/helpers/codehelpers.h index 1f7f184..98c7321 100644 --- a/include/helpers/codehelpers.h +++ b/include/helpers/codehelpers.h @@ -1,7 +1,7 @@ #ifndef CODEHELPERS_H #define CODEHELPERS_H -namespace Nickvision::CodeHelpers +namespace Nickvision::Helpers::CodeHelpers { template const T& unmove(T&& t) diff --git a/include/helpers/stringhelpers.h b/include/helpers/stringhelpers.h index e918aef..41306eb 100644 --- a/include/helpers/stringhelpers.h +++ b/include/helpers/stringhelpers.h @@ -8,7 +8,7 @@ #include #include -namespace Nickvision::StringHelpers +namespace Nickvision::Helpers::StringHelpers { template concept StringImplicitlyConstructible = std::is_constructible_v && std::is_convertible_v; diff --git a/include/helpers/webhelpers.h b/include/helpers/webhelpers.h deleted file mode 100644 index 9529f38..0000000 --- a/include/helpers/webhelpers.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef WEBHELPERS_H -#define WEBHELPERS_H - -#include -#include -#include -#include - -namespace Nickvision::WebHelpers -{ - typedef std::function CurlProgressFunction; - - /** - * @brief Downloads a file to disk. - * @param url The url of the file to download - * @param path The path on disk to save the file - * @param progress An optional function to receive progress on the download \n - * std::function \n - * dltotal - The total number of bytes to be downloaded \n - * dlnow - The total number of bytes currently downloaded \n - * ultotal - The total number of bytes to be uploaded (will always be 0) \n - * ulnow - The total number of bytes currently uploaded (will always be 0) \n - * @param overwrite Whether or not to overwrite existing files on disk - */ - bool downloadFile(const std::string& url, const std::filesystem::path& path, const CurlProgressFunction& progress = {}, bool overwrite = true); - /** - * @brief Fetches a json string from a url. - * @param url The url of the json string - * @return The fetched json string - */ - std::string fetchJsonString(const std::string& url); - /** - * @brief Gets whether or not a url points to a valid website. - * @param url The url to check - * @return True if valid website, else false - */ - bool isValidWebsite(const std::string& url); -} - -#endif //WEBHELPERS_H \ No newline at end of file diff --git a/include/network/curleasy.h b/include/network/curleasy.h new file mode 100644 index 0000000..74f80fe --- /dev/null +++ b/include/network/curleasy.h @@ -0,0 +1,92 @@ +#ifndef CURLEASY_H +#define CURLEASY_H + +#include +#include +#include +#include +#include + +namespace Nickvision::Network +{ + using CurlProgressFunction = std::function; + + /** + * @brief An object for making easy curl requests. + */ + class CurlEasy + { + public: + /** + * @brief Constructs a CurlEasy. + * @param url The url to make requests to + * @throw std::runtime_error If curl fails to initialize + */ + CurlEasy(const std::string& url = ""); + /** + * @brief Destructs a CurlEasy. + */ + ~CurlEasy(); + /** + * @brief Gets the url to make requests to. + * @return The url to make requests to + */ + const std::string& getUrl() const; + /** + * @brief Sets the url to make requests to. + * @param url The url to make requests to + */ + void setUrl(const std::string& url); + /** + * @brief Sets whether or not to include the body in the response. + * @param value True to exclude the body, else false + */ + void setNoBody(bool value); + /** + * @brief Sets the headers to include in the request. + * @param headers The headers to include + * @throw std::runtime_error If curl fails to append headers to the list + */ + void setHeaders(const std::vector& headers); + /** + * @brief Sets the user agent to use in the request. + * @param userAgent The user agent to use + */ + void setUserAgent(const std::string& userAgent); + /** + * @brief Sets the stream to write the response to. + * @param stream The stream to write the response to + */ + void setStream(std::basic_ostream* stream); + /** + * @brief Sets the progress function to use during the request. + * @param progress The progress function to use + */ + void setProgressFunction(const CurlProgressFunction& progress); + /** + * @brief Resets the curl request. + * @param url The url to make requests to + * @throw std::runtime_error If curl fails to initialize + */ + void reset(const std::string& url = ""); + /** + * @brief Performs the curl request. + * @return The code returned by the curl request + */ + CURLcode perform(); + + private: + /** + * @brief Initializes the curl request handle. + * @param url The url to make requests to + * @throw std::runtime_error If curl fails to initialize + */ + void init(const std::string& url); + CURL* m_curl; + struct curl_slist* m_headersList; + std::string m_url; + CurlProgressFunction m_progress; + }; +} + +#endif //CURLEASY_H \ No newline at end of file diff --git a/include/network/webclient.h b/include/network/webclient.h new file mode 100644 index 0000000..449f5dc --- /dev/null +++ b/include/network/webclient.h @@ -0,0 +1,57 @@ +#ifndef WEBCLIENT_H +#define WEBCLIENT_H + +#include +#include +#include +#include "curleasy.h" + +namespace Nickvision::Network +{ + /** + * @brief An object for interacting with the web. + */ + class WebClient + { + public: + /** + * @brief Constructs a WebClient. + */ + WebClient(); + /** + * @brief Destructs a WebClient. + */ + ~WebClient(); + /** + * @brief Gets whether or not a url points to a valid website. + * @param url The url to check + * @return True if valid website, else false + */ + bool getWebsiteExists(const std::string& url) const; + /** + * @brief Fetches a json string from a url. + * @param url The url of the json string + * @return The fetched json string + */ + std::string fetchJson(const std::string& url) const; + /** + * @brief Downloads a file to disk. + * @param url The url of the file to download + * @param path The path on disk to save the file + * @param progress An optional function to receive progress on the download \n + * std::function \n + * dltotal - The total number of bytes to be downloaded \n + * dlnow - The total number of bytes currently downloaded \n + * ultotal - The total number of bytes to be uploaded (will always be 0) \n + * ulnow - The total number of bytes currently uploaded (will always be 0) \n + * @param overwrite Whether or not to overwrite existing files on disk + */ + bool downloadFile(const std::string& url, const std::filesystem::path& path, const CurlProgressFunction& progress = {}, bool overwrite = true) const; + + private: + static bool m_initialized; + static unsigned int m_instanceCount; + }; +} + +#endif //WEBCLIENT_H \ No newline at end of file diff --git a/include/update/updater.h b/include/update/updater.h index 2d3bf1c..0c1f8e6 100644 --- a/include/update/updater.h +++ b/include/update/updater.h @@ -3,6 +3,7 @@ #include #include +#include "network/webclient.h" #include "version.h" namespace Nickvision::Update @@ -60,6 +61,7 @@ namespace Nickvision::Update private: mutable std::mutex m_mutex; + Network::WebClient m_webClient; std::string m_repoOwner; std::string m_repoName; int m_latestStableReleaseId; diff --git a/src/app/appinfo.cpp b/src/app/appinfo.cpp index c31ada9..b5a4759 100644 --- a/src/app/appinfo.cpp +++ b/src/app/appinfo.cpp @@ -3,6 +3,7 @@ #include #include "helpers/stringhelpers.h" +using namespace Nickvision::Helpers; using namespace Nickvision::Update; namespace Nickvision::App diff --git a/src/app/aura.cpp b/src/app/aura.cpp index 02b7f52..d9ae2e0 100644 --- a/src/app/aura.cpp +++ b/src/app/aura.cpp @@ -1,6 +1,5 @@ #include "app/aura.h" #include -#include #include "filesystem/systemdirectories.h" #include "filesystem/userdirectories.h" #include "helpers/stringhelpers.h" @@ -11,6 +10,7 @@ #endif using namespace Nickvision::Filesystem; +using namespace Nickvision::Helpers; using namespace Nickvision::Localization; using namespace Nickvision::Logging; using namespace Nickvision::System; @@ -25,10 +25,6 @@ namespace Nickvision::App Aura::~Aura() { - if(m_initialized) - { - curl_global_cleanup(); - } } bool Aura::init(const std::string& id, const std::string& name, const std::string& englishShortName, LogLevel logLevel) @@ -54,11 +50,6 @@ namespace Nickvision::App m_appInfo.setId(id); m_appInfo.setName(name); m_appInfo.setEnglishShortName(englishShortName); - //Setup curl - if (curl_global_init(CURL_GLOBAL_DEFAULT) != 0) - { - throw std::runtime_error("Unable to initialize curl."); - } //Setup gettext std::string domainName{ StringHelpers::lower(StringHelpers::replace(m_appInfo.getEnglishShortName(), " ", "")) }; if (!Localization::Gettext::init(domainName)) diff --git a/src/app/interprocesscommunicator.cpp b/src/app/interprocesscommunicator.cpp index 892d179..0c6621e 100644 --- a/src/app/interprocesscommunicator.cpp +++ b/src/app/interprocesscommunicator.cpp @@ -7,6 +7,8 @@ #include #endif +using namespace Nickvision::Helpers; + namespace Nickvision::App { InterProcessCommunicator::InterProcessCommunicator(const std::string& id) diff --git a/src/filesystem/systemdirectories.cpp b/src/filesystem/systemdirectories.cpp index 32db1b0..684884b 100644 --- a/src/filesystem/systemdirectories.cpp +++ b/src/filesystem/systemdirectories.cpp @@ -2,6 +2,7 @@ #include "helpers/stringhelpers.h" #include "system/environment.h" +using namespace Nickvision::Helpers; using namespace Nickvision::System; namespace Nickvision::Filesystem diff --git a/src/filesystem/userdirectories.cpp b/src/filesystem/userdirectories.cpp index 7619e7c..a7da98b 100644 --- a/src/filesystem/userdirectories.cpp +++ b/src/filesystem/userdirectories.cpp @@ -14,6 +14,7 @@ #endif using namespace Nickvision::App; +using namespace Nickvision::Helpers; using namespace Nickvision::System; namespace Nickvision::Filesystem diff --git a/src/helpers/stringhelpers.cpp b/src/helpers/stringhelpers.cpp index 1555055..6a6f034 100644 --- a/src/helpers/stringhelpers.cpp +++ b/src/helpers/stringhelpers.cpp @@ -13,7 +13,7 @@ #include #endif -namespace Nickvision +namespace Nickvision::Helpers { std::vector StringHelpers::decode(const std::string& base64) { diff --git a/src/helpers/webhelpers.cpp b/src/helpers/webhelpers.cpp deleted file mode 100644 index f62bb8a..0000000 --- a/src/helpers/webhelpers.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "helpers/webhelpers.h" -#include - -namespace Nickvision -{ - bool WebHelpers::downloadFile(const std::string& url, const std::filesystem::path& path, const CurlProgressFunction& progress, bool overwrite) - { - if (url.empty()) - { - return false; - } - if (std::filesystem::exists(path) && !overwrite) - { - return false; - } - CURL* curl{ curl_easy_init() }; - if (!curl) - { - return false; - } - std::ofstream out{ path, std::ios::binary | std::ios::trunc }; - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +[](char* ptr, size_t size, size_t nmemb, void* data) - { - std::ofstream* stream{ static_cast(data) }; - stream->write(ptr, size * nmemb); - return size * nmemb; - }); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out); -#ifdef _WIN32 - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); -#endif - if (progress) - { - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, false); - curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress); - curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, +[](void* data, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) - { - CurlProgressFunction& func{ *(static_cast(data)) }; - return func(dltotal, dlnow, ultotal, ulnow); - }); - } - CURLcode code{ curl_easy_perform(curl) }; - curl_easy_cleanup(curl); - return code == CURLE_OK; - } - - std::string WebHelpers::fetchJsonString(const std::string& url) - { - if (url.empty()) - { - return ""; - } - CURL* curl{ curl_easy_init() }; - if (!curl) - { - return ""; - } - std::stringstream out; - struct curl_slist* listHttpHeader{ curl_slist_append(nullptr, "Content-Type: application/json") }; - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/120.0"); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, listHttpHeader); - curl_easy_setopt(curl, CURLOPT_HEADER, false); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +[](char* ptr, size_t size, size_t nmemb, void* data) - { - std::stringstream* stream{ static_cast(data) }; - stream->write(ptr, size * nmemb); - return size * nmemb; - }); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out); -#ifdef _WIN32 - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); -#endif - CURLcode code{ curl_easy_perform(curl) }; - curl_easy_cleanup(curl); - curl_slist_free_all(listHttpHeader); - return code == CURLE_OK ? out.str() : ""; - } - - bool WebHelpers::isValidWebsite(const std::string& url) - { - if (url.empty()) - { - return false; - } - CURL* curl{ curl_easy_init() }; - if (!curl) - { - return false; - } - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); - curl_easy_setopt(curl, CURLOPT_NOBODY, 1); - curl_easy_setopt(curl, CURLOPT_HEADER, false); -#ifdef _WIN32 - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); -#endif - CURLcode code{ curl_easy_perform(curl) }; - curl_easy_cleanup(curl); - return code == CURLE_OK; - } -} \ No newline at end of file diff --git a/src/keyring/credential.cpp b/src/keyring/credential.cpp index 3dea212..cbcd5c1 100644 --- a/src/keyring/credential.cpp +++ b/src/keyring/credential.cpp @@ -2,6 +2,8 @@ #include "keyring/passwordstrength.h" #include "helpers/stringhelpers.h" +using namespace Nickvision::Helpers; + namespace Nickvision::Keyring { Credential::Credential(int id, const std::string& name, const std::string& uri, const std::string& username, const std::string& password) diff --git a/src/keyring/keyringdialogcontroller.cpp b/src/keyring/keyringdialogcontroller.cpp index df5e80e..8cd341b 100644 --- a/src/keyring/keyringdialogcontroller.cpp +++ b/src/keyring/keyringdialogcontroller.cpp @@ -1,6 +1,8 @@ #include "keyring/keyringdialogcontroller.h" #include "helpers/stringhelpers.h" +using namespace Nickvision::Helpers; + namespace Nickvision::Keyring { KeyringDialogController::KeyringDialogController(const std::string& name, const std::optional& keyring) diff --git a/src/keyring/systemcredentials.cpp b/src/keyring/systemcredentials.cpp index 81455e0..4bcf4f7 100644 --- a/src/keyring/systemcredentials.cpp +++ b/src/keyring/systemcredentials.cpp @@ -9,6 +9,8 @@ #include #endif +using namespace Nickvision::Helpers; + namespace Nickvision::Keyring { #ifdef __linux__ diff --git a/src/network/curleasy.cpp b/src/network/curleasy.cpp new file mode 100644 index 0000000..a4f2d41 --- /dev/null +++ b/src/network/curleasy.cpp @@ -0,0 +1,135 @@ +#include "network/curleasy.h" +#include + +namespace Nickvision::Network +{ + static size_t writeDataCallback(char* ptr, size_t size, size_t nmemb, void* data) + { + std::basic_ostream* stream{ static_cast*>(data) }; + stream->write(ptr, size * nmemb); + return size * nmemb; + } + + static int progressCallback(void* data, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) + { + CurlProgressFunction& func{ *(static_cast(data)) }; + return func(dltotal, dlnow, ultotal, ulnow); + } + + CurlEasy::CurlEasy(const std::string& url) + : m_curl{ curl_easy_init() }, + m_headersList{ nullptr }, + m_url{ url } + { + init(url); + } + + CurlEasy::~CurlEasy() + { + curl_easy_cleanup(m_curl); + if(m_headersList) + { + curl_slist_free_all(m_headersList); + } + } + + const std::string& CurlEasy::getUrl() const + { + return m_url; + } + + void CurlEasy::setUrl(const std::string& url) + { + m_url = url; + curl_easy_setopt(m_curl, CURLOPT_URL, m_url.c_str()); + } + + void CurlEasy::setNoBody(bool value) + { + curl_easy_setopt(m_curl, CURLOPT_NOBODY, value); + } + + void CurlEasy::setHeaders(const std::vector& headers) + { + for(const std::string& header : headers) + { + struct curl_slist* temp{ curl_slist_append(m_headersList, header.c_str()) }; + if(!temp) + { + throw std::runtime_error("Failed to append header to list"); + } + m_headersList = temp; + } + curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_headersList); + } + + void CurlEasy::setUserAgent(const std::string& userAgent) + { + curl_easy_setopt(m_curl, CURLOPT_USERAGENT, userAgent.c_str()); + } + + void CurlEasy::setStream(std::basic_ostream* stream) + { + if(stream) + { + curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, stream); + curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, writeDataCallback); + } + else + { + curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, nullptr); + curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, nullptr); + } + } + + void CurlEasy::setProgressFunction(const CurlProgressFunction& progress) + { + m_progress = progress; + if(m_progress) + { + curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, false); + curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, &m_progress); + curl_easy_setopt(m_curl, CURLOPT_XFERINFOFUNCTION, progressCallback); + } + else + { + curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, true); + curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, nullptr); + curl_easy_setopt(m_curl, CURLOPT_XFERINFOFUNCTION, nullptr); + } + } + + void CurlEasy::reset(const std::string& url) + { + if(m_headersList) + { + curl_slist_free_all(m_headersList); + m_headersList = nullptr; + } + m_progress = {}; + curl_easy_reset(m_curl); + init(url); + } + + CURLcode CurlEasy::perform() + { + return curl_easy_perform(m_curl); + } + + void CurlEasy::init(const std::string& url) + { + if(!m_curl) + { + throw std::runtime_error("Failed to initialize curl"); + } + if(!m_url.empty()) + { + curl_easy_setopt(m_curl, CURLOPT_URL, m_url.c_str()); + } + curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, true); +#ifdef _WIN32 + curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif + } +} \ No newline at end of file diff --git a/src/network/networkmonitor.cpp b/src/network/networkmonitor.cpp index c276337..de35915 100644 --- a/src/network/networkmonitor.cpp +++ b/src/network/networkmonitor.cpp @@ -6,6 +6,7 @@ #include #endif +using namespace Nickvision::Helpers; using namespace Nickvision::System; namespace Nickvision::Network diff --git a/src/network/webclient.cpp b/src/network/webclient.cpp new file mode 100644 index 0000000..da322f5 --- /dev/null +++ b/src/network/webclient.cpp @@ -0,0 +1,71 @@ +#include "network/webclient.h" +#include +#include + +namespace Nickvision::Network +{ + bool WebClient::m_initialized{ false }; + unsigned int WebClient::m_instanceCount{ 0 }; + + WebClient::WebClient() + { + if(!m_initialized) + { + curl_global_init(CURL_GLOBAL_ALL); + m_initialized = true; + } + m_instanceCount++; + } + + WebClient::~WebClient() + { + m_instanceCount--; + if(m_initialized && m_instanceCount == 0) + { + curl_global_cleanup(); + m_initialized = false; + } + } + + bool WebClient::getWebsiteExists(const std::string& url) const + { + if(url.empty()) + { + return false; + } + CurlEasy curl{ url }; + curl.setNoBody(true); + return curl.perform() == CURLE_OK; + } + + std::string WebClient::fetchJson(const std::string& url) const + { + if(url.empty()) + { + return ""; + } + CurlEasy curl{ url }; + std::stringstream out; + curl.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/120.0"); + curl.setHeaders({ "Content-Type: application/json" }); + curl.setStream(&out); + return curl.perform() == CURLE_OK ? out.str() : ""; + } + + bool WebClient::downloadFile(const std::string& url, const std::filesystem::path& path, const CurlProgressFunction& progress, bool overwrite) const + { + if(url.empty()) + { + return false; + } + if(std::filesystem::exists(path) && !overwrite) + { + return false; + } + CurlEasy curl{ url }; + std::ofstream out{ path, std::ios::binary | std::ios::trunc }; + curl.setStream(&out); + curl.setProgressFunction(progress); + return curl.perform() == CURLE_OK; + } +} \ No newline at end of file diff --git a/src/notifications/notifyicon.cpp b/src/notifications/notifyicon.cpp index 10472ed..353facd 100644 --- a/src/notifications/notifyicon.cpp +++ b/src/notifications/notifyicon.cpp @@ -6,6 +6,7 @@ #include "helpers/stringhelpers.h" using namespace Nickvision::App; +using namespace Nickvision::Helpers; namespace Nickvision::Notifications { diff --git a/src/system/environment.cpp b/src/system/environment.cpp index 676487a..ba196a1 100644 --- a/src/system/environment.cpp +++ b/src/system/environment.cpp @@ -6,6 +6,8 @@ #include #endif +using namespace Nickvision::Helpers; + namespace Nickvision::System { std::string Environment::getVariable(const std::string& key) diff --git a/src/system/process.cpp b/src/system/process.cpp index 5c597b0..51b3378 100644 --- a/src/system/process.cpp +++ b/src/system/process.cpp @@ -18,6 +18,7 @@ using namespace Nickvision::Filesystem; using namespace Nickvision::Events; +using namespace Nickvision::Helpers; namespace Nickvision::System { diff --git a/src/update/updater.cpp b/src/update/updater.cpp index 20ab0fd..7ddbb9e 100644 --- a/src/update/updater.cpp +++ b/src/update/updater.cpp @@ -7,13 +7,13 @@ #include "app/aura.h" #include "filesystem/userdirectories.h" #include "helpers/stringhelpers.h" -#include "helpers/webhelpers.h" #ifdef _WIN32 #include #endif using namespace Nickvision::App; using namespace Nickvision::Filesystem; +using namespace Nickvision::Helpers; namespace Nickvision::Update { @@ -58,7 +58,7 @@ namespace Nickvision::Update Version Updater::fetchCurrentVersion(VersionType versionType) { std::lock_guard lock{ m_mutex }; - std::string releases{ WebHelpers::fetchJsonString("https://api.github.com/repos/" + m_repoOwner + "/" + m_repoName + "/releases") }; + std::string releases{ m_webClient.fetchJson("https://api.github.com/repos/" + m_repoOwner + "/" + m_repoName + "/releases") }; if (!releases.empty()) { Json::Value root; @@ -96,7 +96,7 @@ namespace Nickvision::Update { return false; } - std::string release{ WebHelpers::fetchJsonString("https://api.github.com/repos/" + m_repoOwner + "/" + m_repoName + "/releases/" + std::to_string(versionType == VersionType::Stable ? m_latestStableReleaseId : m_latestPreviewReleaseId)) }; + std::string release{ m_webClient.fetchJson("https://api.github.com/repos/" + m_repoOwner + "/" + m_repoName + "/releases/" + std::to_string(versionType == VersionType::Stable ? m_latestStableReleaseId : m_latestPreviewReleaseId)) }; if (!release.empty()) { Json::Value root; @@ -110,7 +110,7 @@ namespace Nickvision::Update { std::filesystem::path setupPath{ UserDirectories::getCache() / name }; std::wstring quotedSetupPath{ L"\"" + setupPath.wstring() + L"\""}; - if (WebHelpers::downloadFile(asset.get("browser_download_url", "").asString(), setupPath)) + if (m_webClient.downloadFile(asset.get("browser_download_url", "").asString(), setupPath)) { if ((INT_PTR)ShellExecuteW(nullptr, L"open", quotedSetupPath.c_str(), nullptr, nullptr, SW_SHOWDEFAULT) > 32) { diff --git a/tests/networktests.cpp b/tests/networktests.cpp index bd9aeee..37eb2bd 100644 --- a/tests/networktests.cpp +++ b/tests/networktests.cpp @@ -1,19 +1,55 @@ #include #include "network/networkmonitor.h" +#include "network/webclient.h" #include "system/environment.h" using namespace Nickvision::Network; using namespace Nickvision::System; -TEST(NetworkTests, ConnectedGlobal) +class NetworkTest : public testing::Test +{ +public: + static std::unique_ptr m_webClient; + + static void SetUpTestSuite() + { + m_webClient = std::make_unique(); + } +}; + +std::unique_ptr NetworkTest::m_webClient = nullptr; + +TEST_F(NetworkTest, ConnectedGlobal) { NetworkMonitor netmon; ASSERT_EQ(netmon.getConnectionState(), NetworkState::ConnectedGlobal); } -TEST(NetworkTests, DisableNetCheck) +TEST_F(NetworkTest, DisableNetCheck) { ASSERT_TRUE(Environment::setVariable("AURA_DISABLE_NETCHECK", "true")); NetworkMonitor netmon; ASSERT_EQ(netmon.getConnectionState(), NetworkState::ConnectedGlobal); +} + +TEST_F(NetworkTest, ValidWebsite1) +{ + ASSERT_TRUE(m_webClient->getWebsiteExists("https://example.com")); +} + +TEST_F(NetworkTest, ValidWebsite2) +{ + ASSERT_FALSE(m_webClient->getWebsiteExists("https://www.sdfjsdfj.com")); +} + +TEST_F(NetworkTest, DownloadFile1) +{ + ASSERT_TRUE(m_webClient->downloadFile("https://raw.githubusercontent.com/nlogozzo/nlogozzo/main/README.md", "readme.md")); + ASSERT_TRUE(std::filesystem::remove("readme.md")); +} + +TEST_F(NetworkTest, FetchJsonString1) +{ + std::string s{ m_webClient->fetchJson("https://api.github.com/repos/nickvisionapps/denaro/tags") }; + ASSERT_TRUE(!s.empty()); } \ No newline at end of file diff --git a/tests/stringtests.cpp b/tests/stringtests.cpp index d532a75..0fa5a68 100644 --- a/tests/stringtests.cpp +++ b/tests/stringtests.cpp @@ -2,9 +2,10 @@ #include #include #include "helpers/stringhelpers.h" -#include "helpers/webhelpers.h" +#include "network/webclient.h" -using namespace Nickvision; +using namespace Nickvision::Helpers; +using namespace Nickvision::Network; TEST(StringTests, Upper1) { @@ -136,7 +137,8 @@ TEST(StringTests, Base642) TEST(StringTests, Base643) { - WebHelpers::downloadFile("https://www.freeiconspng.com/thumbs/pin-png/pin-png-24.png", "img.png"); + WebClient client; + client.downloadFile("https://www.freeiconspng.com/thumbs/pin-png/pin-png-24.png", "img.png"); std::ifstream img{ "img.png", std::ios_base::binary }; std::vector s{ std::istreambuf_iterator(img), std::istreambuf_iterator() }; std::string base64; diff --git a/tests/webtests.cpp b/tests/webtests.cpp deleted file mode 100644 index c15c95b..0000000 --- a/tests/webtests.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include "helpers/webhelpers.h" - -using namespace Nickvision; - -TEST(WebTests, ValidWebsite1) -{ - ASSERT_TRUE(WebHelpers::isValidWebsite("https://example.com")); -} - -TEST(WebTests, ValidWebsite2) -{ - ASSERT_FALSE(WebHelpers::isValidWebsite("https://www.sdfjsdfj.com")); -} - -TEST(WebTests, DownloadFile1) -{ - ASSERT_TRUE(WebHelpers::downloadFile("https://raw.githubusercontent.com/nlogozzo/nlogozzo/main/README.md", "readme.md")); - ASSERT_TRUE(std::filesystem::remove("readme.md")); -} - -TEST(WebTests, FetchJsonString1) -{ - std::string s{ WebHelpers::fetchJsonString("https://api.github.com/repos/nickvisionapps/denaro/tags") }; - ASSERT_TRUE(!s.empty()); -} \ No newline at end of file