extremely small and simple HTTP GET/HEAD-only web server for static content
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.
 
 
 

191 lines
3.9 KiB

  1. /* See LICENSE file for copyright and license details. */
  2. #include <dirent.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <sys/stat.h>
  7. #include <time.h>
  8. #include <unistd.h>
  9. #include "http.h"
  10. #include "resp.h"
  11. #include "util.h"
  12. static int
  13. compareent(const struct dirent **d1, const struct dirent **d2)
  14. {
  15. int v;
  16. v = ((*d2)->d_type == DT_DIR ? 1 : -1) -
  17. ((*d1)->d_type == DT_DIR ? 1 : -1);
  18. if (v) {
  19. return v;
  20. }
  21. return strcmp((*d1)->d_name, (*d2)->d_name);
  22. }
  23. static char *
  24. suffix(int t)
  25. {
  26. switch (t) {
  27. case DT_FIFO: return "|";
  28. case DT_DIR: return "/";
  29. case DT_LNK: return "@";
  30. case DT_SOCK: return "=";
  31. }
  32. return "";
  33. }
  34. enum status
  35. resp_dir(int fd, char *name, struct request *r)
  36. {
  37. struct dirent **e;
  38. size_t i;
  39. int dirlen, s;
  40. static char t[TIMESTAMP_LEN];
  41. /* read directory */
  42. if ((dirlen = scandir(name, &e, NULL, compareent)) < 0) {
  43. return http_send_status(fd, S_FORBIDDEN);
  44. }
  45. /* send header as late as possible */
  46. if (dprintf(fd,
  47. "HTTP/1.1 %d %s\r\n"
  48. "Date: %s\r\n"
  49. "Connection: close\r\n"
  50. "Content-Type: text/html\r\n"
  51. "\r\n",
  52. S_OK, status_str[S_OK], timestamp(time(NULL), t)) < 0) {
  53. s = S_REQUEST_TIMEOUT;
  54. goto cleanup;
  55. }
  56. if (r->method == M_GET) {
  57. /* listing header */
  58. if (dprintf(fd,
  59. "<!DOCTYPE html>\n<html>\n\t<head>"
  60. "<title>Index of %s</title></head>\n"
  61. "\t<body>\n\t\t<a href=\"..\">..</a>",
  62. name) < 0) {
  63. s = S_REQUEST_TIMEOUT;
  64. goto cleanup;
  65. }
  66. /* listing */
  67. for (i = 0; i < dirlen; i++) {
  68. /* skip hidden files, "." and ".." */
  69. if (e[i]->d_name[0] == '.') {
  70. continue;
  71. }
  72. /* entry line */
  73. if (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
  74. e[i]->d_name,
  75. (e[i]->d_type == DT_DIR) ? "/" : "",
  76. e[i]->d_name,
  77. suffix(e[i]->d_type)) < 0) {
  78. s = S_REQUEST_TIMEOUT;
  79. goto cleanup;
  80. }
  81. }
  82. /* listing footer */
  83. if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) {
  84. s = S_REQUEST_TIMEOUT;
  85. goto cleanup;
  86. }
  87. }
  88. s = S_OK;
  89. cleanup:
  90. while (dirlen--) {
  91. free(e[dirlen]);
  92. }
  93. free(e);
  94. return s;
  95. }
  96. enum status
  97. resp_file(int fd, char *name, struct request *r, struct stat *st, char *mime,
  98. off_t lower, off_t upper)
  99. {
  100. FILE *fp;
  101. enum status s;
  102. ssize_t bread, bwritten;
  103. off_t remaining;
  104. int range;
  105. static char buf[BUFSIZ], *p, t1[TIMESTAMP_LEN], t2[TIMESTAMP_LEN];
  106. /* open file */
  107. if (!(fp = fopen(name, "r"))) {
  108. s = http_send_status(fd, S_FORBIDDEN);
  109. goto cleanup;
  110. }
  111. /* seek to lower bound */
  112. if (fseek(fp, lower, SEEK_SET)) {
  113. s = http_send_status(fd, S_INTERNAL_SERVER_ERROR);
  114. goto cleanup;
  115. }
  116. /* send header as late as possible */
  117. range = r->field[REQ_RANGE][0];
  118. s = range ? S_PARTIAL_CONTENT : S_OK;
  119. if (dprintf(fd,
  120. "HTTP/1.1 %d %s\r\n"
  121. "Date: %s\r\n"
  122. "Connection: close\r\n"
  123. "Last-Modified: %s\r\n"
  124. "Content-Type: %s\r\n"
  125. "Content-Length: %zu\r\n",
  126. s, status_str[s], timestamp(time(NULL), t1),
  127. timestamp(st->st_mtim.tv_sec, t2), mime,
  128. upper - lower + 1) < 0) {
  129. s = S_REQUEST_TIMEOUT;
  130. goto cleanup;
  131. }
  132. if (range) {
  133. if (dprintf(fd, "Content-Range: bytes %zd-%zd/%zu\r\n",
  134. lower, upper + (upper < 0), st->st_size) < 0) {
  135. s = S_REQUEST_TIMEOUT;
  136. goto cleanup;
  137. }
  138. }
  139. if (dprintf(fd, "\r\n") < 0) {
  140. s = S_REQUEST_TIMEOUT;
  141. goto cleanup;
  142. }
  143. if (r->method == M_GET) {
  144. /* write data until upper bound is hit */
  145. remaining = upper - lower + 1;
  146. while ((bread = fread(buf, 1, MIN(sizeof(buf), remaining), fp))) {
  147. if (bread < 0) {
  148. return S_INTERNAL_SERVER_ERROR;
  149. }
  150. remaining -= bread;
  151. p = buf;
  152. while (bread > 0) {
  153. bwritten = write(fd, p, bread);
  154. if (bwritten <= 0) {
  155. return S_REQUEST_TIMEOUT;
  156. }
  157. bread -= bwritten;
  158. p += bwritten;
  159. }
  160. }
  161. }
  162. cleanup:
  163. if (fp) {
  164. fclose(fp);
  165. }
  166. return s;
  167. }