/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include "data.h" #include "http.h" #include "util.h" enum status (* const data_fct[])(const struct response *, struct buffer *, size_t *) = { [RESTYPE_DIRLISTING] = data_prepare_dirlisting_buf, [RESTYPE_ERROR] = data_prepare_error_buf, [RESTYPE_FILE] = data_prepare_file_buf, }; static int compareent(const struct dirent **d1, const struct dirent **d2) { int v; v = ((*d2)->d_type == DT_DIR ? 1 : -1) - ((*d1)->d_type == DT_DIR ? 1 : -1); if (v) { return v; } return strcmp((*d1)->d_name, (*d2)->d_name); } static char * suffix(int t) { switch (t) { case DT_FIFO: return "|"; case DT_DIR: return "/"; case DT_LNK: return "@"; case DT_SOCK: return "="; } return ""; } static void html_escape(const char *src, char *dst, size_t dst_siz) { const struct { char c; char *s; } escape[] = { { '&', "&" }, { '<', "<" }, { '>', ">" }, { '"', """ }, { '\'', "'" }, }; size_t i, j, k, esclen; for (i = 0, j = 0; src[i] != '\0'; i++) { for (k = 0; k < LEN(escape); k++) { if (src[i] == escape[k].c) { break; } } if (k == LEN(escape)) { /* no escape char at src[i] */ if (j == dst_siz - 1) { /* silent truncation */ break; } else { dst[j++] = src[i]; } } else { /* escape char at src[i] */ esclen = strlen(escape[k].s); if (j >= dst_siz - esclen) { /* silent truncation */ break; } else { memcpy(&dst[j], escape[k].s, esclen); j += esclen; } } } dst[j] = '\0'; } enum status data_prepare_dirlisting_buf(const struct response *res, struct buffer *buf, size_t *progress) { enum status s = 0; struct dirent **e; size_t i; int dirlen; char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */ /* reset buffer */ memset(buf, 0, sizeof(*buf)); /* read directory */ if ((dirlen = scandir(res->internal_path, &e, NULL, compareent)) < 0) { return S_FORBIDDEN; } if (*progress == 0) { /* write listing header (sizeof(esc) >= PATH_MAX) */ html_escape(res->path, esc, MIN(PATH_MAX, sizeof(esc))); if (buffer_appendf(buf, "\n\n\t" "Index of %s\n" "\t\n\t\t..", esc) < 0) { s = S_REQUEST_TIMEOUT; goto cleanup; } } /* listing entries */ for (i = *progress; i < (size_t)dirlen; i++) { /* skip hidden files, "." and ".." */ if (e[i]->d_name[0] == '.') { continue; } /* entry line */ html_escape(e[i]->d_name, esc, sizeof(esc)); if (buffer_appendf(buf, "
\n\t\t%s%s", esc, (e[i]->d_type == DT_DIR) ? "/" : "", esc, suffix(e[i]->d_type))) { /* buffer full */ break; } } *progress = i; if (*progress == (size_t)dirlen) { /* listing footer */ if (buffer_appendf(buf, "\n\t\n\n") < 0) { s = S_REQUEST_TIMEOUT; goto cleanup; } (*progress)++; } cleanup: while (dirlen--) { free(e[dirlen]); } free(e); return s; } enum status data_prepare_error_buf(const struct response *res, struct buffer *buf, size_t *progress) { /* reset buffer */ memset(buf, 0, sizeof(*buf)); if (*progress == 0) { /* write error body */ if (buffer_appendf(buf, "\n\n\t\n" "\t\t%d %s\n\t\n" "\t\n\t\t

%d %s

\n" "\t\n\n", res->status, status_str[res->status], res->status, status_str[res->status])) { return S_INTERNAL_SERVER_ERROR; } (*progress)++; } return 0; } enum status data_prepare_file_buf(const struct response *res, struct buffer *buf, size_t *progress) { FILE *fp; enum status s = 0; ssize_t r; size_t remaining; /* reset buffer */ memset(buf, 0, sizeof(*buf)); /* open file */ if (!(fp = fopen(res->internal_path, "r"))) { s = S_FORBIDDEN; goto cleanup; } /* seek to lower bound + progress */ if (fseek(fp, res->file.lower + *progress, SEEK_SET)) { s = S_INTERNAL_SERVER_ERROR; goto cleanup; } /* read data into buf */ remaining = res->file.upper - res->file.lower + 1 - *progress; while ((r = fread(buf->data + buf->len, 1, MIN(sizeof(buf->data) - buf->len, remaining), fp))) { if (r < 0) { s = S_INTERNAL_SERVER_ERROR; goto cleanup; } buf->len += r; *progress += r; remaining -= r; } cleanup: if (fp) { fclose(fp); } return s; }