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.

228 lines
5.4KB

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