From 9e9d3342b3bde52665678cad2a08e86b37d74f67 Mon Sep 17 00:00:00 2001
From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com>
Date: Fri, 28 Jul 2023 14:47:20 +0200
Subject: [PATCH] CURL tools improvement

- Added wrapper for downloading files.
- Moved many function arguments to dedicated parameters structure.
---
 r5dev/networksystem/pylon.cpp  |  10 ++-
 r5dev/public/tier2/curlutils.h |  39 +++++++++--
 r5dev/tier2/curlutils.cpp      | 117 +++++++++++++++++++++++++++------
 3 files changed, 141 insertions(+), 25 deletions(-)

diff --git a/r5dev/networksystem/pylon.cpp b/r5dev/networksystem/pylon.cpp
index 0ce2823f..56825a25 100644
--- a/r5dev/networksystem/pylon.cpp
+++ b/r5dev/networksystem/pylon.cpp
@@ -361,9 +361,15 @@ bool CPylon::QueryServer(const char* endpoint, const char* request,
     string finalUrl;
     CURLFormatUrl(finalUrl, hostName, endpoint);
 
+    CURLParams params;
+
+    params.writeFunction = CURLWriteStringCallback;
+    params.timeout = curl_timeout->GetInt();
+    params.verifyPeer = ssl_verify_peer->GetBool();
+    params.verbose = curl_debug->GetBool();
+
     curl_slist* sList = nullptr;
-    CURL* curl = CURLInitRequest(finalUrl.c_str(), request, outResponse, sList,
-        curl_timeout->GetInt(), ssl_verify_peer->GetBool(), curl_debug->GetBool());
+    CURL* curl = CURLInitRequest(finalUrl.c_str(), request, outResponse, sList, params);
     if (!curl)
     {
         return false;
diff --git a/r5dev/public/tier2/curlutils.h b/r5dev/public/tier2/curlutils.h
index a70310aa..e1aed636 100644
--- a/r5dev/public/tier2/curlutils.h
+++ b/r5dev/public/tier2/curlutils.h
@@ -3,14 +3,45 @@
 
 struct CURLProgress
 {
-	CURL* priv;
+	CURLProgress()
+		: curl(nullptr)
+		, name(nullptr)
+		, cust(nullptr)
+		, size(0)
+	{}
+
+	CURL* curl;
+	const char* name;
+	void* cust; // custom pointer to anything.
 	size_t size;
 };
 
-size_t CURLWriteStringCallback(char* contents, const size_t size, const size_t nmemb, void* userp);
+struct CURLParams
+{
+	CURLParams()
+		: writeFunction(nullptr)
+		, statusFunction(nullptr)
+		, timeout(0)
+		, verifyPeer(false)
+		, verbose(false)
+	{}
+
+	void* writeFunction;
+	void* statusFunction;
+
+	int timeout;
+	bool verifyPeer;
+	bool verbose;
+};
+
+size_t CURLWriteStringCallback(char* contents, const size_t size, const size_t nmemb, string* userp);
+size_t CURLWriteFileCallback(void* data, const size_t size, const size_t nmemb, FILE* userp);
+
+bool CURLDownloadFile(const char* remote, const char* savePath, const char* fileName,
+	const char* options, curl_off_t dataSize, void* customPointer, CURLParams& params);
+
+CURL* CURLInitRequest(const char* remote, const char* request, string& outResponse, curl_slist*& slist, CURLParams& params);
 
-CURL* CURLInitRequest(const char* remote, const char* request, string& outResponse, curl_slist*& slist,
-	const int timeOut, const bool verifyPeer, const bool debug, const void* writeFunction = CURLWriteStringCallback);
 CURLcode CURLSubmitRequest(CURL* curl, curl_slist*& slist);
 CURLINFO CURLRetrieveInfo(CURL* curl);
 
diff --git a/r5dev/tier2/curlutils.cpp b/r5dev/tier2/curlutils.cpp
index 943e1f62..864e4e93 100644
--- a/r5dev/tier2/curlutils.cpp
+++ b/r5dev/tier2/curlutils.cpp
@@ -6,14 +6,99 @@
 #include "tier1/cvar.h"
 #include "tier2/curlutils.h"
 
-size_t CURLWriteStringCallback(char* contents, const size_t size, const size_t nmemb, void* userp)
+size_t CURLWriteStringCallback(char* data, const size_t size, const size_t nmemb, string* userp)
 {
-    reinterpret_cast<string*>(userp)->append(contents, size * nmemb);
+    userp->append(data, size * nmemb);
     return size * nmemb;
 }
 
-CURL* CURLInitRequest(const char* remote, const char* request, string& outResponse, curl_slist*& slist,
-    const int timeOut, const bool verifyPeer, const bool debug, const void* writeFunction)
+size_t CURLWriteFileCallback(void* data, const size_t size, const size_t nmemb, FILE* userp)
+{
+    const size_t numBytesWritten = fwrite(data, size, nmemb, userp);
+    return numBytesWritten;
+}
+
+void CURLInitCommonOptions(CURL* curl, const char* remote, const void* writeData,
+    const void* writeFunction, const int timeout, const bool verifyPeer, const bool debug)
+{
+    curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
+    curl_easy_setopt(curl, CURLOPT_VERBOSE, debug);
+    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, verifyPeer);
+    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
+    curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
+    curl_easy_setopt(curl, CURLOPT_URL, remote);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, writeData);
+    curl_easy_setopt(curl, CURLOPT_USERAGENT, "R5R HTTPS/1.0");
+    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction);
+}
+
+bool CURLDownloadFile(const char* remote, const char* savePath, const char* fileName,
+    const char* options, curl_off_t dataSize, void* customPointer, CURLParams& params)
+{
+    CURL* curl = curl_easy_init();
+    if (!curl)
+    {
+        Error(eDLL_T::COMMON, NO_ERROR, "CURL: %s\n", "Easy init failed");
+        return false;
+    }
+
+    string filePath = savePath;
+
+    AppendSlash(filePath);
+    filePath.append(fileName);
+
+    FILE* file = fopen(filePath.c_str(), options);
+    if (!file)
+    {
+        Error(eDLL_T::COMMON, NO_ERROR, "CURL: %s\n", "Open file failed");
+        curl_easy_cleanup(curl);
+
+        return false;
+    }
+
+    CURLProgress progressData;
+
+    progressData.curl = curl;
+    progressData.name = fileName;
+    progressData.cust = customPointer;
+    progressData.size = dataSize;
+
+    CURLInitCommonOptions(curl, remote, file,
+        params.writeFunction,
+        params.timeout,
+        params.verifyPeer,
+        params.verbose);
+
+    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1l);
+
+    if (params.statusFunction)
+    {
+        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0l);
+        curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progressData);
+        curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, params.statusFunction);
+    }
+
+    CURLcode res = curl_easy_perform(curl);
+
+    if (res != CURLE_OK)
+    {
+        Error(eDLL_T::COMMON, NO_ERROR, "CURL: Download of file '%s' failed; %s\n",
+            fileName, curl_easy_strerror(res));
+
+        curl_easy_cleanup(curl);
+        fclose(file);
+
+        return false;
+    }
+
+    curl_easy_cleanup(curl);
+    fclose(file);
+
+    return true;
+}
+
+CURL* CURLInitRequest(const char* remote, const char* request,
+    string& outResponse, curl_slist*& slist, CURLParams& params)
 {
     std::function<void(const char*)> fnError = [&](const char* errorMsg)
     {
@@ -35,23 +120,17 @@ CURL* CURLInitRequest(const char* remote, const char* request, string& outRespon
         return nullptr;
     }
 
+    CURLInitCommonOptions(curl, remote, &outResponse,
+        params.writeFunction,
+        params.timeout,
+        params.verifyPeer,
+        params.verbose);
+
     curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
-    curl_easy_setopt(curl, CURLOPT_URL, remote);
-    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
-    curl_easy_setopt(curl, CURLOPT_POST, 1L);
-    curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeOut);
-    curl_easy_setopt(curl, CURLOPT_USERAGENT, "R5R HTTPS/1.0");
-    curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
-
-    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction);
-    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &outResponse);
-
-    curl_easy_setopt(curl, CURLOPT_VERBOSE, debug);
-    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
-
-    if (!verifyPeer)
+    if (request)
     {
-        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
+        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
+        curl_easy_setopt(curl, CURLOPT_POST, 1L);
     }
 
     return curl;