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.

243 lines
6.1KB

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