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.

220 lines
5.1KB

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