logo

httpc

barebones HTTP client, intended for simple usage like downloading filesgit clone https://hacktivis.me/git/httpc.git

httpc.c (3868B)


  1. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+httpc@hacktivis.me>
  2. // SPDX-License-Identifier: MPL-2.0
  3. #define _POSIX_C_SOURCE 200809L
  4. #include <limits.h> // PAGESIZE
  5. #include <tls.h>
  6. #include <errno.h>
  7. #include <stdio.h> // fprintf
  8. #include <unistd.h> // write, fsync
  9. #include <string.h> // strlen, strerror
  10. #include <stdlib.h> // abort
  11. #include <stdbool.h>
  12. #include <fcntl.h> // open, O_*
  13. #include "httpc.h"
  14. int body = 0;
  15. bool verbose = false;
  16. static size_t
  17. read_header(const char *res, ssize_t len)
  18. {
  19. size_t header_len = 0;
  20. for(ssize_t i = 0; i<len; i++)
  21. {
  22. if(res[i] == '\r' && res[i+1] == '\n') i++;
  23. if(res[i] == '\n')
  24. {
  25. if(header_len == 0)
  26. {
  27. body = 1;
  28. return i+1;
  29. }
  30. if(verbose)
  31. {
  32. fputs("> ", stderr);
  33. fwrite(res, 1, header_len, stderr);
  34. fputs("\n", stderr);
  35. }
  36. return i+1;
  37. }
  38. header_len++;
  39. }
  40. // Maybe case where first packet didn't contain all headers should be handled
  41. // But who the heck even sends >PAGESIZE of headers
  42. abort();
  43. }
  44. int
  45. main(int argc, char *argv[])
  46. {
  47. int out_flags = O_WRONLY | O_CLOEXEC | O_CREAT | O_NOCTTY | O_TRUNC;
  48. char *out_name = NULL;
  49. int out = -1;
  50. int c = -1;
  51. while((c = getopt(argc, argv, ":o:v")) != -1)
  52. {
  53. switch(c)
  54. {
  55. case 'o':
  56. out_name = optarg;
  57. break;
  58. case 'v':
  59. verbose = true;
  60. break;
  61. }
  62. }
  63. argv += optind;
  64. argc -= optind;
  65. // FIXME: URL basename
  66. if(out_name == NULL) out_name = "body";
  67. out = open(out_name, out_flags, 0644);
  68. if(out < 0)
  69. {
  70. fprintf(stderr, "httpc: Failed opening destination file '%s': %s\n", out_name, strerror(errno));
  71. return 1;
  72. }
  73. if(argc != 1)
  74. {
  75. fprintf(stderr, "httpc: Expected 1 argument, got %d\n", argc);
  76. return 1;
  77. }
  78. char *errstr = NULL;
  79. struct URI *uri = decode_uri(argv[0], &errstr);
  80. if(uri == NULL)
  81. {
  82. fprintf(stderr, "httpc: Error while decoding <%s>: %s\n", argv[0], errstr);
  83. return 1;
  84. }
  85. struct tls *tls_ctx = tls_client();
  86. struct tls_config *tls_cfg = tls_config_new();
  87. if(tls_configure(tls_ctx, tls_cfg) != 0)
  88. {
  89. fprintf(stderr, "httpc: TLS Configuration Error: %s\n", tls_error(tls_ctx));
  90. decode_uri_finish(uri);
  91. tls_config_free(tls_cfg);
  92. return 1;
  93. }
  94. if(tls_connect(tls_ctx, uri->host, uri->port ? uri->port : "443") != 0)
  95. {
  96. fprintf(stderr, "httpc: TLS Connection Error: %s\n", tls_error(tls_ctx));
  97. decode_uri_finish(uri);
  98. tls_config_free(tls_cfg);
  99. return 1;
  100. }
  101. int err = 0;
  102. if(tls_handshake(tls_ctx) != 0)
  103. {
  104. fprintf(stderr, "httpc: TLS Handshake Error: %s\n", tls_error(tls_ctx));
  105. err = 1;
  106. goto cleanup;
  107. }
  108. static const char *req_fmt = "\
  109. GET %s HTTP/1.0\r\n\
  110. Host: %s\r\n\
  111. User-Agent: httpc/0.0\r\n\
  112. Connection: close\r\n\
  113. \r\n";
  114. char req[PAGESIZE];
  115. snprintf(req, PAGESIZE, req_fmt, uri->path ? uri->path : "/", uri->host);
  116. if(verbose) write(STDERR_FILENO, req, strlen(req));
  117. ssize_t nwrite = tls_write(tls_ctx, req, strlen(req));
  118. if(nwrite < 0)
  119. {
  120. fprintf(stderr, "httpc: Write Error: %s\n", tls_error(tls_ctx));
  121. err = 1;
  122. goto cleanup;
  123. }
  124. char res[PAGESIZE] = {};
  125. while(1)
  126. {
  127. ssize_t nread = tls_read(tls_ctx, res, PAGESIZE);
  128. if(nread == TLS_WANT_POLLIN || nread == TLS_WANT_POLLOUT)
  129. continue;
  130. if(nread < 0)
  131. {
  132. fprintf(stderr, "httpc: Write Error: %s\n", tls_error(tls_ctx));
  133. break;
  134. }
  135. if(nread == 0) break;
  136. ssize_t off = 0;
  137. // TODO: Read status
  138. while(body == 0 && off < nread) off += read_header(res+off, nread);
  139. if(body == 1) write(out, res+off, nread-off);
  140. }
  141. if(fsync(out) < 0)
  142. {
  143. fprintf(stderr, "httpc: Failed syncing destination file '%s' to disk: %s\n", out_name, strerror(errno));
  144. err = 1;
  145. }
  146. if(close(out) < 0)
  147. {
  148. fprintf(stderr, "httpc: Failed closing destination file '%s': %s\n", out_name, strerror(errno));
  149. err = 1;
  150. }
  151. cleanup:
  152. decode_uri_finish(uri);
  153. tls_close(tls_ctx);
  154. tls_free(tls_ctx);
  155. tls_config_free(tls_cfg);
  156. return err;
  157. }