Another copy of my dotfiles. Because I don't completely trust GitHub.
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.

231 lines
4.8 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 "data.h"
  10. #include "http.h"
  11. #include "util.h"
  12. enum status (* const data_fct[])(const struct response *,
  13. struct buffer *, size_t *) = {
  14. [RESTYPE_DIRLISTING] = data_prepare_dirlisting_buf,
  15. [RESTYPE_ERROR] = data_prepare_error_buf,
  16. [RESTYPE_FILE] = data_prepare_file_buf,
  17. };
  18. static int
  19. compareent(const struct dirent **d1, const struct dirent **d2)
  20. {
  21. int v;
  22. v = ((*d2)->d_type == DT_DIR ? 1 : -1) -
  23. ((*d1)->d_type == DT_DIR ? 1 : -1);
  24. if (v) {
  25. return v;
  26. }
  27. return strcmp((*d1)->d_name, (*d2)->d_name);
  28. }
  29. static char *
  30. suffix(int t)
  31. {
  32. switch (t) {
  33. case DT_FIFO: return "|";
  34. case DT_DIR: return "/";
  35. case DT_LNK: return "@";
  36. case DT_SOCK: return "=";
  37. }
  38. return "";
  39. }
  40. static void
  41. html_escape(const char *src, char *dst, size_t dst_siz)
  42. {
  43. const struct {
  44. char c;
  45. char *s;
  46. } escape[] = {
  47. { '&', "&amp;" },
  48. { '<', "&lt;" },
  49. { '>', "&gt;" },
  50. { '"', "&quot;" },
  51. { '\'', "&#x27;" },
  52. };
  53. size_t i, j, k, esclen;
  54. for (i = 0, j = 0; src[i] != '\0'; i++) {
  55. for (k = 0; k < LEN(escape); k++) {
  56. if (src[i] == escape[k].c) {
  57. break;
  58. }
  59. }
  60. if (k == LEN(escape)) {
  61. /* no escape char at src[i] */
  62. if (j == dst_siz - 1) {
  63. /* silent truncation */
  64. break;
  65. } else {
  66. dst[j++] = src[i];
  67. }
  68. } else {
  69. /* escape char at src[i] */
  70. esclen = strlen(escape[k].s);
  71. if (j >= dst_siz - esclen) {
  72. /* silent truncation */
  73. break;
  74. } else {
  75. memcpy(&dst[j], escape[k].s, esclen);
  76. j += esclen;
  77. }
  78. }
  79. }
  80. dst[j] = '\0';
  81. }
  82. enum status
  83. data_prepare_dirlisting_buf(const struct response *res,
  84. struct buffer *buf, size_t *progress)
  85. {
  86. enum status s = 0;
  87. struct dirent **e;
  88. size_t i;
  89. int dirlen;
  90. char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */
  91. /* reset buffer */
  92. memset(buf, 0, sizeof(*buf));
  93. /* read directory */
  94. if ((dirlen = scandir(res->internal_path, &e, NULL, compareent)) < 0) {
  95. return S_FORBIDDEN;
  96. }
  97. if (*progress == 0) {
  98. /* write listing header (sizeof(esc) >= PATH_MAX) */
  99. html_escape(res->path, esc, MIN(PATH_MAX, sizeof(esc)));
  100. if (buffer_appendf(buf,
  101. "<!DOCTYPE html>\n<html>\n\t<head>"
  102. "<title>Index of %s</title></head>\n"
  103. "\t<body>\n\t\t<a href=\"..\">..</a>",
  104. esc) < 0) {
  105. s = S_REQUEST_TIMEOUT;
  106. goto cleanup;
  107. }
  108. }
  109. /* listing entries */
  110. for (i = *progress; i < (size_t)dirlen; i++) {
  111. /* skip hidden files, "." and ".." */
  112. if (e[i]->d_name[0] == '.') {
  113. continue;
  114. }
  115. /* entry line */
  116. html_escape(e[i]->d_name, esc, sizeof(esc));
  117. if (buffer_appendf(buf,
  118. "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
  119. esc,
  120. (e[i]->d_type == DT_DIR) ? "/" : "",
  121. esc,
  122. suffix(e[i]->d_type))) {
  123. /* buffer full */
  124. break;
  125. }
  126. }
  127. *progress = i;
  128. if (*progress == (size_t)dirlen) {
  129. /* listing footer */
  130. if (buffer_appendf(buf, "\n\t</body>\n</html>\n") < 0) {
  131. s = S_REQUEST_TIMEOUT;
  132. goto cleanup;
  133. }
  134. (*progress)++;
  135. }
  136. cleanup:
  137. while (dirlen--) {
  138. free(e[dirlen]);
  139. }
  140. free(e);
  141. return s;
  142. }
  143. enum status
  144. data_prepare_error_buf(const struct response *res, struct buffer *buf,
  145. size_t *progress)
  146. {
  147. /* reset buffer */
  148. memset(buf, 0, sizeof(*buf));
  149. if (*progress == 0) {
  150. /* write error body */
  151. if (buffer_appendf(buf,
  152. "<!DOCTYPE html>\n<html>\n\t<head>\n"
  153. "\t\t<title>%d %s</title>\n\t</head>\n"
  154. "\t<body>\n\t\t<h1>%d %s</h1>\n"
  155. "\t</body>\n</html>\n",
  156. res->status, status_str[res->status],
  157. res->status, status_str[res->status])) {
  158. return S_INTERNAL_SERVER_ERROR;
  159. }
  160. (*progress)++;
  161. }
  162. return 0;
  163. }
  164. enum status
  165. data_prepare_file_buf(const struct response *res, struct buffer *buf,
  166. size_t *progress)
  167. {
  168. FILE *fp;
  169. enum status s = 0;
  170. ssize_t r;
  171. size_t remaining;
  172. /* reset buffer */
  173. memset(buf, 0, sizeof(*buf));
  174. /* open file */
  175. if (!(fp = fopen(res->internal_path, "r"))) {
  176. s = S_FORBIDDEN;
  177. goto cleanup;
  178. }
  179. /* seek to lower bound + progress */
  180. if (fseek(fp, res->file.lower + *progress, SEEK_SET)) {
  181. s = S_INTERNAL_SERVER_ERROR;
  182. goto cleanup;
  183. }
  184. /* read data into buf */
  185. remaining = res->file.upper - res->file.lower + 1 - *progress;
  186. while ((r = fread(buf->data + buf->len, 1,
  187. MIN(sizeof(buf->data) - buf->len,
  188. remaining), fp))) {
  189. if (r < 0) {
  190. s = S_INTERNAL_SERVER_ERROR;
  191. goto cleanup;
  192. }
  193. buf->len += r;
  194. *progress += r;
  195. remaining -= r;
  196. }
  197. cleanup:
  198. if (fp) {
  199. fclose(fp);
  200. }
  201. return s;
  202. }