You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
6.5KB

  1. #include <vector>
  2. #include <openssl/crypto.h>
  3. #define CURL_STATICLIB
  4. #include <curl/curl.h>
  5. #include <network.hpp>
  6. #include <system.hpp>
  7. #include <asset.hpp>
  8. #include <settings.hpp>
  9. namespace rack {
  10. namespace network {
  11. static const std::vector<std::string> methodNames = {
  12. "GET", "POST", "PUT", "DELETE",
  13. };
  14. static CURL* createCurl() {
  15. CURL* curl = curl_easy_init();
  16. assert(curl);
  17. // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
  18. std::string userAgent = APP_NAME + " " + APP_EDITION_NAME + "/" + APP_VERSION;
  19. curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
  20. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
  21. // Timeout to wait on initial HTTP connection.
  22. // This is lower than the typical HTTP timeout of 60 seconds to avoid DAWs from aborting plugin scans.
  23. curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30);
  24. // If curl can't resolve a DNS entry, it sends a signal to interrupt the process.
  25. // However, since we use curl on non-main thread, this crashes the application.
  26. // So tell curl not to signal.
  27. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
  28. // Load root certificates
  29. std::string caPath = asset::system("cacert.pem");
  30. curl_easy_setopt(curl, CURLOPT_CAINFO, caPath.c_str());
  31. // Don't verify HTTPS certificates if verifyHttpsCerts is false
  32. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, settings::verifyHttpsCerts);
  33. return curl;
  34. }
  35. static size_t writeStringCallback(char* ptr, size_t size, size_t nmemb, void* userdata) {
  36. std::string* str = (std::string*) userdata;
  37. size_t len = size * nmemb;
  38. str->append(ptr, len);
  39. return len;
  40. }
  41. static std::string getCookieString(const CookieMap& cookies) {
  42. std::string s;
  43. for (const auto& pair : cookies) {
  44. s += encodeUrl(pair.first);
  45. s += "=";
  46. s += encodeUrl(pair.second);
  47. s += ";";
  48. }
  49. return s;
  50. }
  51. void init() {
  52. // Because OpenSSL is compiled with no-pinshared, we need to initialize without defining atexit(), since we want to destroy it when libRack is unloaded.
  53. OPENSSL_init_crypto(OPENSSL_INIT_NO_ATEXIT, NULL);
  54. // 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.
  55. // https://curl.haxx.se/libcurl/c/curl_easy_init.html
  56. curl_global_init(CURL_GLOBAL_ALL);
  57. }
  58. void destroy() {
  59. curl_global_cleanup();
  60. // Don't destroy OpenSSL because it's not designed to be reinitialized.
  61. // OPENSSL_cleanup();
  62. }
  63. json_t* requestJson(Method method, const std::string& url, json_t* dataJ, const CookieMap& cookies) {
  64. std::string urlS = url;
  65. CURL* curl = createCurl();
  66. char* reqStr = NULL;
  67. // Process data
  68. if (dataJ) {
  69. if (method == METHOD_GET) {
  70. // Append ?key1=value1&key2=value2&... to url
  71. urlS += "?";
  72. bool isFirst = true;
  73. const char* key;
  74. json_t* value;
  75. json_object_foreach(dataJ, key, value) {
  76. if (json_is_string(value)) {
  77. if (!isFirst)
  78. urlS += "&";
  79. urlS += key;
  80. urlS += "=";
  81. const char* str = json_string_value(value);
  82. size_t len = json_string_length(value);
  83. char* escapedStr = curl_easy_escape(curl, str, len);
  84. urlS += escapedStr;
  85. curl_free(escapedStr);
  86. isFirst = false;
  87. }
  88. }
  89. }
  90. else {
  91. reqStr = json_dumps(dataJ, 0);
  92. }
  93. }
  94. curl_easy_setopt(curl, CURLOPT_URL, urlS.c_str());
  95. // Set HTTP method
  96. if (method == METHOD_GET) {
  97. // This is CURL's default
  98. }
  99. else if (method == METHOD_POST) {
  100. curl_easy_setopt(curl, CURLOPT_POST, true);
  101. }
  102. else if (method == METHOD_PUT) {
  103. curl_easy_setopt(curl, CURLOPT_PUT, true);
  104. }
  105. else if (method == METHOD_DELETE) {
  106. curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
  107. }
  108. // Set headers
  109. struct curl_slist* headers = NULL;
  110. headers = curl_slist_append(headers, "Accept: application/json");
  111. headers = curl_slist_append(headers, "Content-Type: application/json");
  112. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  113. // Cookies
  114. if (!cookies.empty()) {
  115. curl_easy_setopt(curl, CURLOPT_COOKIE, getCookieString(cookies).c_str());
  116. }
  117. // Body callbacks
  118. if (reqStr)
  119. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, reqStr);
  120. std::string resText;
  121. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCallback);
  122. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText);
  123. // Perform request
  124. INFO("Requesting JSON %s %s", methodNames[method].c_str(), urlS.c_str());
  125. CURLcode res = curl_easy_perform(curl);
  126. // Cleanup
  127. if (reqStr)
  128. std::free(reqStr);
  129. curl_easy_cleanup(curl);
  130. curl_slist_free_all(headers);
  131. if (res != CURLE_OK) {
  132. WARN("Could not request %s: %s", urlS.c_str(), curl_easy_strerror(res));
  133. return NULL;
  134. }
  135. // Parse JSON response
  136. json_error_t error;
  137. json_t* rootJ = json_loads(resText.c_str(), 0, &error);
  138. return rootJ;
  139. }
  140. static int xferInfoCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
  141. float* progress = (float*) clientp;
  142. if (progress) {
  143. if (dltotal <= 0)
  144. *progress = 0.f;
  145. else
  146. *progress = (float)dlnow / dltotal;
  147. }
  148. return 0;
  149. }
  150. bool requestDownload(const std::string& url, const std::string& filename, float* progress, const CookieMap& cookies) {
  151. CURL* curl = createCurl();
  152. FILE* file = std::fopen(filename.c_str(), "wb");
  153. if (!file)
  154. return false;
  155. curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
  156. curl_easy_setopt(curl, CURLOPT_NOPROGRESS, false);
  157. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
  158. curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);
  159. curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferInfoCallback);
  160. curl_easy_setopt(curl, CURLOPT_XFERINFODATA, progress);
  161. // Fail on 4xx and 5xx HTTP codes
  162. curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
  163. // Cookies
  164. if (!cookies.empty()) {
  165. curl_easy_setopt(curl, CURLOPT_COOKIE, getCookieString(cookies).c_str());
  166. }
  167. INFO("Requesting download %s", url.c_str());
  168. CURLcode res = curl_easy_perform(curl);
  169. curl_easy_cleanup(curl);
  170. std::fclose(file);
  171. if (res != CURLE_OK) {
  172. system::remove(filename);
  173. WARN("Could not download %s: %s", url.c_str(), curl_easy_strerror(res));
  174. return false;
  175. }
  176. return true;
  177. }
  178. std::string encodeUrl(const std::string& s) {
  179. CURL* curl = createCurl();
  180. DEFER({curl_easy_cleanup(curl);});
  181. assert(curl);
  182. char* escaped = curl_easy_escape(curl, s.c_str(), s.size());
  183. DEFER({curl_free(escaped);});
  184. return std::string(escaped);
  185. }
  186. std::string urlPath(const std::string& url) {
  187. CURLU* curl = curl_url();
  188. DEFER({curl_url_cleanup(curl);});
  189. if (curl_url_set(curl, CURLUPART_URL, url.c_str(), 0))
  190. return "";
  191. char* buf;
  192. if (curl_url_get(curl, CURLUPART_PATH, &buf, 0))
  193. return "";
  194. std::string ret = buf;
  195. curl_free(buf);
  196. return ret;
  197. }
  198. } // namespace network
  199. } // namespace rack