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.

189 lines
4.4KB

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