diff --git a/include/network.hpp b/include/network.hpp index 4014c8f9..9acb1271 100644 --- a/include/network.hpp +++ b/include/network.hpp @@ -1,4 +1,6 @@ #pragma once +#include + #include #include @@ -12,6 +14,8 @@ namespace rack { namespace network { +typedef std::map CookieMap; + enum Method { METHOD_GET, METHOD_POST, @@ -23,9 +27,9 @@ void init(); /** Requests a JSON API URL over HTTP(S), using the data as the query (GET) or the body (POST, etc) Caller must json_decref(). */ -json_t* requestJson(Method method, std::string url, json_t* dataJ); +json_t* requestJson(Method method, const std::string& url, json_t* dataJ, const CookieMap& cookies = {}); /** Returns true if downloaded successfully */ -bool requestDownload(std::string url, const std::string& filename, float* progress); +bool requestDownload(const std::string& url, const std::string& filename, float* progress, const CookieMap& cookies = {}); /** URL-encodes `s` */ std::string encodeUrl(const std::string& s); /** Gets the path portion of the URL. */ diff --git a/src/network.cpp b/src/network.cpp index 1ffa4d97..2896430f 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -1,3 +1,5 @@ +#include + #define CURL_STATICLIB #include @@ -9,6 +11,11 @@ namespace rack { namespace network { +static const std::vector methodNames = { + "GET", "POST", "PUT", "DELETE", +}; + + static CURL* createCurl() { CURL* curl = curl_easy_init(); assert(curl); @@ -31,6 +38,18 @@ static size_t writeStringCallback(char* ptr, size_t size, size_t nmemb, void* us } +static std::string getCookieString(const CookieMap& cookies) { + std::string s; + for (const auto& pair : cookies) { + s += encodeUrl(pair.first); + s += "="; + s += encodeUrl(pair.second); + s += ";"; + } + return s; +} + + void init() { // curl_easy_init() calls this automatically, but it's good to make sure this is done on the main thread before other threads are spawned. // https://curl.haxx.se/libcurl/c/curl_easy_init.html @@ -38,7 +57,8 @@ void init() { } -json_t* requestJson(Method method, std::string url, json_t* dataJ) { +json_t* requestJson(Method method, const std::string& url, json_t* dataJ, const CookieMap& cookies) { + std::string urlS = url; CURL* curl = createCurl(); char* reqStr = NULL; @@ -46,20 +66,20 @@ json_t* requestJson(Method method, std::string url, json_t* dataJ) { if (dataJ) { if (method == METHOD_GET) { // Append ?key=value&... to url - url += "?"; + urlS += "?"; bool isFirst = true; const char* key; json_t* value; json_object_foreach(dataJ, key, value) { if (json_is_string(value)) { if (!isFirst) - url += "&"; - url += key; - url += "="; + urlS += "&"; + urlS += key; + urlS += "="; const char* str = json_string_value(value); size_t len = json_string_length(value); char* escapedStr = curl_easy_escape(curl, str, len); - url += escapedStr; + urlS += escapedStr; curl_free(escapedStr); isFirst = false; } @@ -70,22 +90,20 @@ json_t* requestJson(Method method, std::string url, json_t* dataJ) { } } - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, urlS.c_str()); // Set HTTP method - switch (method) { - case METHOD_GET: - // This is CURL's default - break; - case METHOD_POST: - curl_easy_setopt(curl, CURLOPT_POST, true); - break; - case METHOD_PUT: - curl_easy_setopt(curl, CURLOPT_PUT, true); - break; - case METHOD_DELETE: - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - break; + if (method == METHOD_GET) { + // This is CURL's default + } + else if (method == METHOD_POST) { + curl_easy_setopt(curl, CURLOPT_POST, true); + } + else if (method == METHOD_PUT) { + curl_easy_setopt(curl, CURLOPT_PUT, true); + } + else if (method == METHOD_DELETE) { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); } // Set headers @@ -94,6 +112,11 @@ json_t* requestJson(Method method, std::string url, json_t* dataJ) { headers = curl_slist_append(headers, "Content-Type: application/json"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + // Cookies + if (!cookies.empty()) { + curl_easy_setopt(curl, CURLOPT_COOKIE, getCookieString(cookies).c_str()); + } + // Body callbacks if (reqStr) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, reqStr); @@ -103,6 +126,7 @@ json_t* requestJson(Method method, std::string url, json_t* dataJ) { curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText); // Perform request + INFO("Requesting %s %s", methodNames[method].c_str(), urlS.c_str()); CURLcode res = curl_easy_perform(curl); // Cleanup @@ -132,7 +156,7 @@ static int xferInfoCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, return 0; } -bool requestDownload(std::string url, const std::string& filename, float* progress) { +bool requestDownload(const std::string& url, const std::string& filename, float* progress, const CookieMap& cookies) { CURL* curl = createCurl(); FILE* file = fopen(filename.c_str(), "wb"); @@ -148,6 +172,12 @@ bool requestDownload(std::string url, const std::string& filename, float* progre // Fail on 4xx and 5xx HTTP codes curl_easy_setopt(curl, CURLOPT_FAILONERROR, true); + // Cookies + if (!cookies.empty()) { + curl_easy_setopt(curl, CURLOPT_COOKIE, getCookieString(cookies).c_str()); + } + + INFO("Downloading %s", url.c_str()); CURLcode res = curl_easy_perform(curl); curl_easy_cleanup(curl); diff --git a/src/plugin.cpp b/src/plugin.cpp index 7f11056e..e73a8c4d 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -331,10 +331,9 @@ void queryUpdates() { // Get user's plugins list std::string pluginsUrl = API_URL + "/plugins"; - json_t* pluginsReqJ = json_object(); - json_object_set(pluginsReqJ, "token", json_string(settings::token.c_str())); - json_t* pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, pluginsReqJ); - json_decref(pluginsReqJ); + network::CookieMap cookies; + cookies["token"] = settings::token; + json_t* pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, NULL, cookies); if (!pluginsResJ) { WARN("Request for user's plugins failed"); updateStatus = "Could not query updates"; @@ -440,16 +439,18 @@ void syncUpdate(Update* update) { }); std::string downloadUrl = API_URL + "/download"; - downloadUrl += "?token=" + network::encodeUrl(settings::token); - downloadUrl += "&slug=" + network::encodeUrl(update->pluginSlug); + downloadUrl += "?slug=" + network::encodeUrl(update->pluginSlug); downloadUrl += "&version=" + network::encodeUrl(update->version); downloadUrl += "&arch=" + network::encodeUrl(APP_ARCH); + network::CookieMap cookies; + cookies["token"] = settings::token; + INFO("Downloading plugin %s %s %s", update->pluginSlug.c_str(), update->version.c_str(), APP_ARCH.c_str()); // Download zip std::string pluginDest = asset::pluginsPath + "/" + update->pluginSlug + ".zip"; - if (!network::requestDownload(downloadUrl, pluginDest, &update->progress)) { + if (!network::requestDownload(downloadUrl, pluginDest, &update->progress, cookies)) { WARN("Plugin %s download was unsuccessful", update->pluginSlug.c_str()); return; }