fetch.c 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. /* vim: tabstop=4 shiftwidth=4 noexpandtab
  2. * This file is part of ToaruOS and is released under the terms
  3. * of the NCSA / University of Illinois License - see LICENSE.md
  4. * Copyright (C) 2015-2018 K. Lange
  5. *
  6. * fetch - Retreive documents from HTTP servers.
  7. *
  8. */
  9. #include <stdio.h>
  10. #include <string.h>
  11. #include <stdlib.h>
  12. #include <getopt.h>
  13. #include <time.h>
  14. #include <sys/time.h>
  15. #include <termios.h>
  16. #include <unistd.h>
  17. #include <toaru/hashmap.h>
  18. #define SIZE 512
  19. #define BOUNDARY "------ToaruOSFetchUploadBoundary"
  20. struct http_req {
  21. char domain[SIZE];
  22. char path[SIZE];
  23. };
  24. struct {
  25. int show_headers;
  26. const char * output_file;
  27. const char * cookie;
  28. FILE * out;
  29. int prompt_password;
  30. const char * upload_file;
  31. char * password;
  32. int show_progress;
  33. size_t content_length;
  34. size_t size;
  35. struct timeval start;
  36. int calculate_output;
  37. int slow_upload;
  38. int machine_readable;
  39. } fetch_options = {0};
  40. void parse_url(char * d, struct http_req * r) {
  41. if (strstr(d, "http://") == d) {
  42. d += strlen("http://");
  43. char * s = strstr(d, "/");
  44. if (!s) {
  45. strcpy(r->domain, d);
  46. strcpy(r->path, "");
  47. } else {
  48. *s = 0;
  49. s++;
  50. strcpy(r->domain, d);
  51. strcpy(r->path, s);
  52. }
  53. } else {
  54. fprintf(stderr, "sorry, can't parse %s\n", d);
  55. exit(1);
  56. }
  57. }
  58. #define BAR_WIDTH 20
  59. #define bar_perc "||||||||||||||||||||"
  60. #define bar_spac " "
  61. void print_progress(void) {
  62. struct timeval now;
  63. gettimeofday(&now, NULL);
  64. fprintf(stderr,"\033[G%6dkB",(int)fetch_options.size/1024);
  65. if (fetch_options.content_length) {
  66. int percent = (fetch_options.size * BAR_WIDTH) / (fetch_options.content_length);
  67. fprintf(stderr," / %6dkB [%.*s%.*s]", (int)fetch_options.content_length/1024, percent,bar_perc,BAR_WIDTH-percent,bar_spac);
  68. }
  69. double timediff = (double)(now.tv_sec - fetch_options.start.tv_sec) + (double)(now.tv_usec - fetch_options.start.tv_usec)/1000000.0;
  70. if (timediff > 0.0) {
  71. double rate = (double)(fetch_options.size) / timediff;
  72. double s = rate/(1024.0) * 8.0;
  73. if (s > 1024.0) {
  74. fprintf(stderr," %.2f mbps", s/1024.0);
  75. } else {
  76. fprintf(stderr," %.2f kbps", s);
  77. }
  78. if (fetch_options.content_length) {
  79. if (rate > 0.0) {
  80. double remaining = (double)(fetch_options.content_length - fetch_options.size) / rate;
  81. fprintf(stderr," (%.2f sec remaining)", remaining);
  82. }
  83. }
  84. }
  85. fprintf(stderr,"\033[K");
  86. fflush(stderr);
  87. }
  88. int usage(char * argv[]) {
  89. fprintf(stderr,
  90. "fetch - download files over HTTP\n"
  91. "\n"
  92. "usage: %s [-hOvmp?] [-c cookie] [-o file] [-u file] [-s speed] URL\n"
  93. "\n"
  94. " -h \033[3mshow headers\033[0m\n"
  95. " -O \033[3msave the file based on the filename in the URL\033[0m\n"
  96. " -v \033[3mshow progress\033[0m\n"
  97. " -m \033[3mmachine readable output\033[0m\n"
  98. " -p \033[3mprompt for password\033[0m\n"
  99. " -c ... \033[3mset cookies\033[0m\n"
  100. " -o ... \033[3msave to the specified file\033[0m\n"
  101. " -u ... \033[3mupload the specified file\033[0m\n"
  102. " -s ... \033[3mspecify the speed for uploading slowly\033[0m\n"
  103. " -? \033[3mshow this help text\033[0m\n"
  104. "\n", argv[0]);
  105. return 1;
  106. }
  107. int collect_password(char * password) {
  108. fprintf(stdout, "Password for upload: ");
  109. fflush(stdout);
  110. /* Disable echo */
  111. struct termios old, new;
  112. tcgetattr(fileno(stdin), &old);
  113. new = old;
  114. new.c_lflag &= (~ECHO);
  115. tcsetattr(fileno(stdin), TCSAFLUSH, &new);
  116. fgets(password, 1024, stdin);
  117. password[strlen(password)-1] = '\0';
  118. tcsetattr(fileno(stdin), TCSAFLUSH, &old);
  119. fprintf(stdout, "\n");
  120. return 0;
  121. }
  122. void read_http_line(char * buf, FILE * f) {
  123. memset(buf, 0x00, 256);
  124. fgets(buf, 255, f);
  125. char * _r = strchr(buf, '\r');
  126. if (_r) {
  127. *_r = '\0';
  128. }
  129. if (!_r) {
  130. _r = strchr(buf, '\n'); /* that's not right, but, whatever */
  131. if (_r) {
  132. *_r = '\0';
  133. }
  134. }
  135. }
  136. void bad_response(void) {
  137. fprintf(stderr, "Bad response.\n");
  138. exit(1);
  139. }
  140. int http_fetch(FILE * f) {
  141. hashmap_t * headers = hashmap_create(10);
  142. /* Parse response */
  143. {
  144. char buf[256];
  145. read_http_line(buf, f);
  146. char * elements[3];
  147. elements[0] = buf;
  148. elements[1] = strchr(elements[0], ' ');
  149. if (!elements[1]) bad_response();
  150. *elements[1] = '\0';
  151. elements[1]++;
  152. elements[2] = strchr(elements[1], ' ');
  153. if (!elements[2]) bad_response();
  154. *elements[2] = '\0';
  155. elements[2]++;
  156. if (strcmp(elements[1], "200")) {
  157. fprintf(stderr, "Bad response code: %s\n", elements[1]);
  158. return 1;
  159. }
  160. }
  161. /* Parse headers */
  162. while (1) {
  163. char buf[256];
  164. read_http_line(buf, f);
  165. if (!*buf) {
  166. break;
  167. }
  168. /* Split */
  169. char * name = buf;
  170. char * value = strstr(buf, ": ");
  171. if (!value) bad_response();
  172. *value = '\0';
  173. value += 2;
  174. hashmap_set(headers, name, strdup(value));
  175. }
  176. if (fetch_options.show_headers) {
  177. list_t * hash_keys = hashmap_keys(headers);
  178. foreach(_key, hash_keys) {
  179. char * key = (char *)_key->value;
  180. fprintf(stderr, "[%s] = %s\n", key, (char*)hashmap_get(headers, key));
  181. }
  182. list_free(hash_keys);
  183. free(hash_keys);
  184. }
  185. /* determine how many bytes we should read now */
  186. if (!hashmap_has(headers, "Content-Length")) {
  187. fprintf(stderr, "Don't know how much to read.\n");
  188. return 1;
  189. }
  190. int bytes_to_read = atoi(hashmap_get(headers, "Content-Length"));
  191. fetch_options.content_length = bytes_to_read;
  192. while (bytes_to_read > 0) {
  193. char buf[1024];
  194. size_t r = fread(buf, 1, bytes_to_read < 1024 ? bytes_to_read : 1024, f);
  195. fwrite(buf, 1, r, fetch_options.out);
  196. fetch_options.size += r;
  197. if (fetch_options.show_progress) {
  198. print_progress();
  199. }
  200. if (fetch_options.machine_readable && fetch_options.content_length) {
  201. fprintf(stdout,"%d %d\n",(int)fetch_options.size, (int)fetch_options.content_length);
  202. }
  203. bytes_to_read -= r;
  204. }
  205. return 0;
  206. }
  207. int main(int argc, char * argv[]) {
  208. int opt;
  209. while ((opt = getopt(argc, argv, "?c:hmo:Opu:vs:")) != -1) {
  210. switch (opt) {
  211. case '?':
  212. return usage(argv);
  213. case 'O':
  214. fetch_options.calculate_output = 1;
  215. break;
  216. case 'c':
  217. fetch_options.cookie = optarg;
  218. break;
  219. case 'h':
  220. fetch_options.show_headers = 1;
  221. break;
  222. case 'o':
  223. fetch_options.output_file = optarg;
  224. break;
  225. case 'u':
  226. fetch_options.upload_file = optarg;
  227. break;
  228. case 'v':
  229. fetch_options.show_progress = 1;
  230. break;
  231. case 'm':
  232. fetch_options.machine_readable = 1;
  233. break;
  234. case 'p':
  235. fetch_options.prompt_password = 1;
  236. break;
  237. case 's':
  238. fetch_options.slow_upload = atoi(optarg);
  239. break;
  240. }
  241. }
  242. if (optind >= argc) {
  243. return usage(argv);
  244. }
  245. struct http_req my_req;
  246. parse_url(argv[optind], &my_req);
  247. char file[100];
  248. sprintf(file, "/dev/net/%s", my_req.domain);
  249. if (fetch_options.calculate_output) {
  250. char * tmp = strdup(my_req.path);
  251. char * x = strrchr(tmp,'/');
  252. if (x) {
  253. tmp = x + 1;
  254. }
  255. fetch_options.output_file = tmp;
  256. }
  257. fetch_options.out = stdout;
  258. if (fetch_options.output_file) {
  259. fetch_options.out = fopen(fetch_options.output_file, "w+");
  260. }
  261. FILE * f = fopen(file,"r+");
  262. if (!f) {
  263. fprintf(stderr, "Nope.\n");
  264. return 1;
  265. }
  266. if (fetch_options.prompt_password) {
  267. fetch_options.password = malloc(100);
  268. collect_password(fetch_options.password);
  269. }
  270. if (fetch_options.upload_file) {
  271. FILE * in_file = fopen(fetch_options.upload_file, "r");
  272. srand(time(NULL));
  273. int boundary_fuzz = rand();
  274. char tmp[512];
  275. size_t out_size = 0;
  276. if (fetch_options.password) {
  277. out_size += sprintf(tmp,
  278. "--" BOUNDARY "%08x\r\n"
  279. "Content-Disposition: form-data; name=\"password\"\r\n"
  280. "\r\n"
  281. "%s\r\n",boundary_fuzz, fetch_options.password);
  282. }
  283. out_size += strlen("--" BOUNDARY "00000000\r\n"
  284. "Content-Disposition: form-data; name=\"file\"; filename=\"\"\r\n"
  285. "Content-Type: application/octet-stream\r\n"
  286. "\r\n"
  287. /* Data goes here */
  288. "\r\n"
  289. "--" BOUNDARY "00000000" "--\r\n");
  290. out_size += strlen(fetch_options.upload_file);
  291. fseek(in_file, 0, SEEK_END);
  292. out_size += ftell(in_file);
  293. fseek(in_file, 0, SEEK_SET);
  294. fprintf(f,
  295. "POST /%s HTTP/1.0\r\n"
  296. "User-Agent: curl/7.35.0\r\n"
  297. "Host: %s\r\n"
  298. "Accept: */*\r\n"
  299. "Content-Length: %d\r\n"
  300. "Content-Type: multipart/form-data; boundary=" BOUNDARY "%08x\r\n"
  301. "\r\n", my_req.path, my_req.domain, (int)out_size, boundary_fuzz);
  302. fprintf(f,"%s",tmp);
  303. fprintf(f,
  304. "--" BOUNDARY "%08x\r\n"
  305. "Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"
  306. "Content-Type: application/octet-stream\r\n"
  307. "\r\n", boundary_fuzz, fetch_options.upload_file);
  308. while (!feof(in_file)) {
  309. char buf[1024];
  310. size_t r = fread(buf, 1, 1024, in_file);
  311. fwrite(buf, 1, r, f);
  312. if (fetch_options.slow_upload) {
  313. usleep(1000 * fetch_options.slow_upload); /* TODO fix terrible network stack; hopefully this ensures we send stuff right. */
  314. }
  315. }
  316. fclose(in_file);
  317. fprintf(f,"\r\n--" BOUNDARY "%08x--\r\n", boundary_fuzz);
  318. fflush(f);
  319. } else if (fetch_options.cookie) {
  320. fprintf(f,
  321. "GET /%s HTTP/1.0\r\n"
  322. "User-Agent: curl/7.35.0\r\n"
  323. "Host: %s\r\n"
  324. "Accept: */*\r\n"
  325. "Cookie: %s\r\n"
  326. "\r\n", my_req.path, my_req.domain, fetch_options.cookie);
  327. } else {
  328. fprintf(f,
  329. "GET /%s HTTP/1.0\r\n"
  330. "User-Agent: curl/7.35.0\r\n"
  331. "Host: %s\r\n"
  332. "Accept: */*\r\n"
  333. "\r\n", my_req.path, my_req.domain);
  334. }
  335. http_fetch(f);
  336. fflush(fetch_options.out);
  337. if (fetch_options.show_progress) {
  338. fprintf(stderr,"\n");
  339. }
  340. if (fetch_options.machine_readable) {
  341. fprintf(stdout,"done\n");
  342. }
  343. return 0;
  344. }