fetch.c 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  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. gettimeofday(&fetch_options.start, NULL);
  193. while (bytes_to_read > 0) {
  194. char buf[1024];
  195. size_t r = fread(buf, 1, bytes_to_read < 1024 ? bytes_to_read : 1024, f);
  196. fwrite(buf, 1, r, fetch_options.out);
  197. fetch_options.size += r;
  198. if (fetch_options.show_progress) {
  199. print_progress();
  200. }
  201. if (fetch_options.machine_readable && fetch_options.content_length) {
  202. fprintf(stdout,"%d %d\n",(int)fetch_options.size, (int)fetch_options.content_length);
  203. }
  204. bytes_to_read -= r;
  205. }
  206. return 0;
  207. }
  208. int main(int argc, char * argv[]) {
  209. int opt;
  210. while ((opt = getopt(argc, argv, "?c:hmo:Opu:vs:")) != -1) {
  211. switch (opt) {
  212. case '?':
  213. return usage(argv);
  214. case 'O':
  215. fetch_options.calculate_output = 1;
  216. break;
  217. case 'c':
  218. fetch_options.cookie = optarg;
  219. break;
  220. case 'h':
  221. fetch_options.show_headers = 1;
  222. break;
  223. case 'o':
  224. fetch_options.output_file = optarg;
  225. break;
  226. case 'u':
  227. fetch_options.upload_file = optarg;
  228. break;
  229. case 'v':
  230. fetch_options.show_progress = 1;
  231. break;
  232. case 'm':
  233. fetch_options.machine_readable = 1;
  234. break;
  235. case 'p':
  236. fetch_options.prompt_password = 1;
  237. break;
  238. case 's':
  239. fetch_options.slow_upload = atoi(optarg);
  240. break;
  241. }
  242. }
  243. if (optind >= argc) {
  244. return usage(argv);
  245. }
  246. struct http_req my_req;
  247. parse_url(argv[optind], &my_req);
  248. char file[100];
  249. sprintf(file, "/dev/net/%s", my_req.domain);
  250. if (fetch_options.calculate_output) {
  251. char * tmp = strdup(my_req.path);
  252. char * x = strrchr(tmp,'/');
  253. if (x) {
  254. tmp = x + 1;
  255. }
  256. fetch_options.output_file = tmp;
  257. }
  258. fetch_options.out = stdout;
  259. if (fetch_options.output_file) {
  260. fetch_options.out = fopen(fetch_options.output_file, "w+");
  261. }
  262. FILE * f = fopen(file,"r+");
  263. if (!f) {
  264. fprintf(stderr, "Nope.\n");
  265. return 1;
  266. }
  267. if (fetch_options.prompt_password) {
  268. fetch_options.password = malloc(100);
  269. collect_password(fetch_options.password);
  270. }
  271. if (fetch_options.upload_file) {
  272. FILE * in_file = fopen(fetch_options.upload_file, "r");
  273. srand(time(NULL));
  274. int boundary_fuzz = rand();
  275. char tmp[512];
  276. size_t out_size = 0;
  277. if (fetch_options.password) {
  278. out_size += sprintf(tmp,
  279. "--" BOUNDARY "%08x\r\n"
  280. "Content-Disposition: form-data; name=\"password\"\r\n"
  281. "\r\n"
  282. "%s\r\n",boundary_fuzz, fetch_options.password);
  283. }
  284. out_size += strlen("--" BOUNDARY "00000000\r\n"
  285. "Content-Disposition: form-data; name=\"file\"; filename=\"\"\r\n"
  286. "Content-Type: application/octet-stream\r\n"
  287. "\r\n"
  288. /* Data goes here */
  289. "\r\n"
  290. "--" BOUNDARY "00000000" "--\r\n");
  291. out_size += strlen(fetch_options.upload_file);
  292. fseek(in_file, 0, SEEK_END);
  293. out_size += ftell(in_file);
  294. fseek(in_file, 0, SEEK_SET);
  295. fprintf(f,
  296. "POST /%s HTTP/1.0\r\n"
  297. "User-Agent: curl/7.35.0\r\n"
  298. "Host: %s\r\n"
  299. "Accept: */*\r\n"
  300. "Content-Length: %d\r\n"
  301. "Content-Type: multipart/form-data; boundary=" BOUNDARY "%08x\r\n"
  302. "\r\n", my_req.path, my_req.domain, (int)out_size, boundary_fuzz);
  303. fprintf(f,"%s",tmp);
  304. fprintf(f,
  305. "--" BOUNDARY "%08x\r\n"
  306. "Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"
  307. "Content-Type: application/octet-stream\r\n"
  308. "\r\n", boundary_fuzz, fetch_options.upload_file);
  309. while (!feof(in_file)) {
  310. char buf[1024];
  311. size_t r = fread(buf, 1, 1024, in_file);
  312. fwrite(buf, 1, r, f);
  313. if (fetch_options.slow_upload) {
  314. usleep(1000 * fetch_options.slow_upload); /* TODO fix terrible network stack; hopefully this ensures we send stuff right. */
  315. }
  316. }
  317. fclose(in_file);
  318. fprintf(f,"\r\n--" BOUNDARY "%08x--\r\n", boundary_fuzz);
  319. fflush(f);
  320. } else if (fetch_options.cookie) {
  321. fprintf(f,
  322. "GET /%s HTTP/1.0\r\n"
  323. "User-Agent: curl/7.35.0\r\n"
  324. "Host: %s\r\n"
  325. "Accept: */*\r\n"
  326. "Cookie: %s\r\n"
  327. "\r\n", my_req.path, my_req.domain, fetch_options.cookie);
  328. } else {
  329. fprintf(f,
  330. "GET /%s HTTP/1.0\r\n"
  331. "User-Agent: curl/7.35.0\r\n"
  332. "Host: %s\r\n"
  333. "Accept: */*\r\n"
  334. "\r\n", my_req.path, my_req.domain);
  335. }
  336. http_fetch(f);
  337. fflush(fetch_options.out);
  338. if (fetch_options.show_progress) {
  339. fprintf(stderr,"\n");
  340. }
  341. if (fetch_options.machine_readable) {
  342. fprintf(stdout,"done\n");
  343. }
  344. return 0;
  345. }