Verify bittorrent .torrent metainfo files.
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.

280 lines
7.3KB

  1. #ifdef HTTP_TORRENT
  2. #include "metainfo_http.h"
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <stdlib.h>
  6. #include <stdbool.h>
  7. #include <time.h>
  8. #include <sys/ioctl.h>
  9. #include <curl/curl.h>
  10. #include "metainfo.h"
  11. #include "opts.h"
  12. struct http_metainfo;
  13. typedef void (*progress_fn)(struct http_metainfo* mi);
  14. struct http_metainfo {
  15. int64_t c_size, max_size;
  16. char *data;
  17. int err;
  18. struct {
  19. struct winsize wsize;
  20. /* If -1, this is a chunked transfer */
  21. off_t cont_len;
  22. time_t last_upd_ms;
  23. int upd_count;
  24. progress_fn fn;
  25. bool show;
  26. struct timespec dlstart_time;
  27. } progress;
  28. };
  29. static void progress_known(struct http_metainfo* h_meta)
  30. {
  31. /* "[#####] 14%" */
  32. unsigned short cols;;
  33. struct timespec now;
  34. time_t nowms;
  35. int bars_count;
  36. float prcnt;
  37. if (h_meta == NULL) {
  38. /* Clear line at end */
  39. fprintf(stderr, "\033[1G\033[2K");
  40. fflush(stderr);
  41. return;
  42. }
  43. cols = h_meta->progress.wsize.ws_col;
  44. if (cols < 8)
  45. return;
  46. char line[cols + 1];
  47. /* 1 ms resoltion on my machine */
  48. clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
  49. nowms = now.tv_sec * 1000 + now.tv_nsec / 1000000;
  50. if (nowms - h_meta->progress.last_upd_ms < 500)
  51. return;
  52. h_meta->progress.last_upd_ms = nowms;
  53. prcnt = h_meta->c_size / (float)h_meta->progress.cont_len;
  54. bars_count = (cols - 8) * prcnt;
  55. line[0] = '[';
  56. memset(&line[1], '#', bars_count);
  57. sprintf(&line[bars_count + 1], "]% .0f%%", prcnt * 100);
  58. fprintf(stderr, "\033[2K\033[1G%s", line);
  59. fflush(stderr);
  60. }
  61. static void progress_unknown(struct http_metainfo* h_meta)
  62. {
  63. /* "[ / ] Downloading..." */
  64. const char pchar[] = "/-\\|";
  65. struct timespec now;
  66. time_t nowms;
  67. unsigned short cols;
  68. if (h_meta == NULL) {
  69. /* Clear line at end */
  70. fprintf(stderr, "\033[2K\033[1G");
  71. fflush(stderr);
  72. return;
  73. }
  74. cols = h_meta->progress.wsize.ws_col;
  75. if (cols < 20)
  76. return;
  77. /* 1 ms resoltion on my machine */
  78. clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
  79. nowms = now.tv_sec * 1000 + now.tv_nsec / 1000000;
  80. if (nowms - h_meta->progress.last_upd_ms < 500)
  81. return;
  82. h_meta->progress.last_upd_ms = nowms;
  83. fprintf(stderr, "\033[2K\033[1G[ %c ] Downloading...",
  84. pchar[h_meta->progress.upd_count++ % (sizeof(pchar) - 1)]);
  85. fflush(stderr);
  86. }
  87. static size_t metainfo_read_http_headercb(char *buf, size_t size, size_t n,
  88. void *data) {
  89. struct http_metainfo *h_meta = (struct http_metainfo*)data;
  90. const char *cl_header = "content-length";
  91. char *sep = memchr(buf, ':', size * n);
  92. if (!sep || (sep - buf) != strlen(cl_header))
  93. goto end;
  94. if (strncmp(cl_header, buf, strlen(cl_header)) == 0) {
  95. char *endp;
  96. int64_t len;
  97. sep += 2;
  98. errno = 0;
  99. len = strtoll(sep, &endp, 10);
  100. if (sep != endp && errno == 0) {
  101. if (len > MAX_TORRENT_SIZE) {
  102. h_meta->err = EFBIG;
  103. return 0;
  104. }
  105. h_meta->max_size = len;
  106. h_meta->progress.cont_len = len;
  107. if (!opt_silent)
  108. h_meta->progress.fn = progress_known;
  109. }
  110. }
  111. end:
  112. return size * n;
  113. }
  114. static int metainfo_http_progress(struct http_metainfo* h_meta) {
  115. struct timespec now;
  116. if (h_meta->progress.fn) {
  117. if (!h_meta->progress.show) {
  118. time_t diffms;
  119. clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
  120. diffms = (now.tv_sec - h_meta->progress.dlstart_time.tv_sec) * 1000 +
  121. (now.tv_nsec - h_meta->progress.dlstart_time.tv_nsec) / 1000000;
  122. if (diffms >= 1000) /* Show progress after 1 second of downloading */
  123. h_meta->progress.show = true;
  124. }
  125. if (h_meta->progress.show)
  126. h_meta->progress.fn(h_meta);
  127. }
  128. return CURL_PROGRESSFUNC_CONTINUE;
  129. }
  130. static int connect_cb(void* data, char* conn_ip, char* conn_local_ip,
  131. int conn_port, int conn_local_port) {
  132. struct http_metainfo* h_meta = data;
  133. if (!opt_silent)
  134. clock_gettime(CLOCK_MONOTONIC_COARSE, &h_meta->progress.dlstart_time);
  135. return CURL_PREREQFUNC_OK;
  136. }
  137. static size_t metainfo_read_http_writecb(char *ptr, size_t size, size_t n,
  138. void *data) {
  139. struct http_metainfo *h_meta = (struct http_metainfo*)data;
  140. size_t bytes = size * n;
  141. if (h_meta->max_size > MAX_TORRENT_SIZE) {
  142. h_meta->err = EFBIG;
  143. goto fail; /* Stop processing if too large */
  144. }
  145. if (h_meta->max_size == -1) {
  146. /* If no content-length, make a dinamic array */
  147. h_meta->max_size = 0;
  148. } else if (!h_meta->data) {
  149. /* We have content-size, and we haven't alloced yet */
  150. h_meta->data = malloc(h_meta->max_size);
  151. }
  152. size_t free_space = h_meta->max_size - h_meta->c_size;
  153. if (bytes > free_space) {
  154. while (bytes > free_space) {
  155. if (h_meta->max_size == 0)
  156. h_meta->max_size = 2048;
  157. h_meta->max_size *= 2;
  158. free_space = h_meta->max_size - h_meta->c_size;
  159. }
  160. void *n_data = realloc(h_meta->data, h_meta->max_size);
  161. if (!n_data) {
  162. h_meta->err = ENOMEM;
  163. goto fail;
  164. }
  165. h_meta->data = n_data;
  166. }
  167. if (h_meta->c_size + bytes > MAX_TORRENT_SIZE) {
  168. h_meta->err = EFBIG;
  169. goto fail;
  170. }
  171. memcpy(&h_meta->data[h_meta->c_size], ptr, bytes);
  172. h_meta->c_size += bytes;
  173. metainfo_http_progress(h_meta);
  174. return bytes;
  175. fail:
  176. if (h_meta->data)
  177. free(h_meta->data);
  178. return 0;
  179. }
  180. /*
  181. * Download the file in memory, and return the pointer to it (which needs to be
  182. * freed) in out_contents and the size in out_size. If the file is too big,
  183. * fail. Returns 0 on success and an errno on fail.
  184. */
  185. int metainfo_read_http(const char* url, char** out_contents, int* out_size) {
  186. char errbuf[CURL_ERROR_SIZE];
  187. CURL *curl = curl_easy_init();
  188. CURLcode res;
  189. struct http_metainfo h_meta = {
  190. .c_size = 0,
  191. .max_size = -1,
  192. .data = NULL,
  193. .progress.cont_len = -1,
  194. .progress.fn = opt_silent ? NULL : progress_unknown,
  195. };
  196. if (!curl) {
  197. return ENOMEM;
  198. }
  199. if (!opt_silent) {
  200. ioctl(0, TIOCGWINSZ, &h_meta.progress.wsize);
  201. }
  202. curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/8.1.2");
  203. curl_easy_setopt(curl, CURLOPT_HEADERDATA, &h_meta);
  204. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &h_meta);
  205. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, metainfo_read_http_headercb);
  206. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, metainfo_read_http_writecb);
  207. curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
  208. curl_easy_setopt(curl, CURLOPT_PREREQFUNCTION, connect_cb);
  209. curl_easy_setopt(curl, CURLOPT_PREREQDATA, &h_meta);
  210. curl_easy_setopt(curl, CURLOPT_URL, url);
  211. //curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, 1024);
  212. res = curl_easy_perform(curl);
  213. curl_easy_cleanup(curl);
  214. if (h_meta.progress.fn && h_meta.progress.show) {
  215. h_meta.progress.fn(NULL);
  216. }
  217. if (res) {
  218. fprintf(stderr, "libCurl error: %s\n", errbuf);
  219. return h_meta.err;
  220. }
  221. *out_contents = h_meta.data;
  222. *out_size = h_meta.c_size; /* caller will free */
  223. return 0;
  224. }
  225. #endif