2018-02-04 20:27:33 +00:00
|
|
|
/* See LICENSE file for copyright and license details. */
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "http.h"
|
|
|
|
#include "resp.h"
|
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
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 "";
|
|
|
|
}
|
|
|
|
|
2020-03-25 13:07:17 +00:00
|
|
|
static void
|
2020-08-05 16:28:21 +00:00
|
|
|
html_escape(const char *src, char *dst, size_t dst_siz)
|
2020-03-25 13:07:17 +00:00
|
|
|
{
|
|
|
|
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';
|
|
|
|
}
|
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
enum status
|
2020-08-05 16:28:21 +00:00
|
|
|
resp_dir(int fd, const char *name, const struct request *req)
|
2018-02-04 20:27:33 +00:00
|
|
|
{
|
2020-08-05 11:41:44 +00:00
|
|
|
enum status sendstatus;
|
2018-02-04 20:27:33 +00:00
|
|
|
struct dirent **e;
|
2020-08-05 11:41:44 +00:00
|
|
|
struct response res = {
|
|
|
|
.status = S_OK,
|
|
|
|
.field[RES_CONTENT_TYPE] = "text/html; charset=utf-8",
|
|
|
|
};
|
2018-02-04 20:27:33 +00:00
|
|
|
size_t i;
|
2020-08-05 11:41:44 +00:00
|
|
|
int dirlen;
|
2020-03-25 13:07:17 +00:00
|
|
|
char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */
|
2018-02-04 20:27:33 +00:00
|
|
|
|
|
|
|
/* read directory */
|
|
|
|
if ((dirlen = scandir(name, &e, NULL, compareent)) < 0) {
|
|
|
|
return http_send_status(fd, S_FORBIDDEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* send header as late as possible */
|
2020-08-05 11:41:44 +00:00
|
|
|
if ((sendstatus = http_send_header(fd, &res)) != res.status) {
|
|
|
|
res.status = sendstatus;
|
2018-02-04 20:27:33 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2020-08-05 13:43:29 +00:00
|
|
|
if (req->method == M_GET) {
|
2018-02-04 20:27:33 +00:00
|
|
|
/* listing header */
|
2020-03-25 13:07:17 +00:00
|
|
|
html_escape(name, esc, sizeof(esc));
|
2018-02-04 20:27:33 +00:00
|
|
|
if (dprintf(fd,
|
|
|
|
"<!DOCTYPE html>\n<html>\n\t<head>"
|
|
|
|
"<title>Index of %s</title></head>\n"
|
|
|
|
"\t<body>\n\t\t<a href=\"..\">..</a>",
|
2020-03-25 13:07:17 +00:00
|
|
|
esc) < 0) {
|
2020-08-05 11:41:44 +00:00
|
|
|
res.status = S_REQUEST_TIMEOUT;
|
2018-02-04 20:27:33 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* listing */
|
2018-03-04 23:30:53 +00:00
|
|
|
for (i = 0; i < (size_t)dirlen; i++) {
|
2018-02-04 20:27:33 +00:00
|
|
|
/* skip hidden files, "." and ".." */
|
|
|
|
if (e[i]->d_name[0] == '.') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* entry line */
|
2020-03-25 13:07:17 +00:00
|
|
|
html_escape(e[i]->d_name, esc, sizeof(esc));
|
2018-02-04 20:27:33 +00:00
|
|
|
if (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
|
2020-03-25 13:07:17 +00:00
|
|
|
esc,
|
2018-02-04 20:27:33 +00:00
|
|
|
(e[i]->d_type == DT_DIR) ? "/" : "",
|
2020-03-25 13:07:17 +00:00
|
|
|
esc,
|
2018-02-04 20:27:33 +00:00
|
|
|
suffix(e[i]->d_type)) < 0) {
|
2020-08-05 11:41:44 +00:00
|
|
|
res.status = S_REQUEST_TIMEOUT;
|
2018-02-04 20:27:33 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* listing footer */
|
|
|
|
if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) {
|
2020-08-05 11:41:44 +00:00
|
|
|
res.status = S_REQUEST_TIMEOUT;
|
2018-02-04 20:27:33 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
while (dirlen--) {
|
|
|
|
free(e[dirlen]);
|
|
|
|
}
|
|
|
|
free(e);
|
|
|
|
|
2020-08-05 11:41:44 +00:00
|
|
|
return res.status;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
enum status
|
2020-08-05 16:28:21 +00:00
|
|
|
resp_file(int fd, const char *name, const struct request *req,
|
2020-08-05 16:59:55 +00:00
|
|
|
const struct stat *st, const char *mime, size_t lower,
|
|
|
|
size_t upper)
|
2018-02-04 20:27:33 +00:00
|
|
|
{
|
|
|
|
FILE *fp;
|
2020-08-05 11:41:44 +00:00
|
|
|
enum status sendstatus;
|
|
|
|
struct response res = {
|
2020-08-05 13:43:29 +00:00
|
|
|
.status = (req->field[REQ_RANGE][0] != '\0') ?
|
2020-08-05 11:41:44 +00:00
|
|
|
S_PARTIAL_CONTENT : S_OK,
|
|
|
|
.field[RES_ACCEPT_RANGES] = "bytes",
|
|
|
|
};
|
2018-02-04 20:27:33 +00:00
|
|
|
ssize_t bread, bwritten;
|
2020-08-05 16:59:55 +00:00
|
|
|
size_t remaining;
|
2020-08-05 11:41:44 +00:00
|
|
|
static char buf[BUFSIZ], *p;
|
2018-02-04 20:27:33 +00:00
|
|
|
|
|
|
|
/* open file */
|
|
|
|
if (!(fp = fopen(name, "r"))) {
|
2020-08-05 11:41:44 +00:00
|
|
|
res.status = http_send_status(fd, S_FORBIDDEN);
|
2018-02-04 20:27:33 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* seek to lower bound */
|
|
|
|
if (fseek(fp, lower, SEEK_SET)) {
|
2020-08-05 11:41:44 +00:00
|
|
|
res.status = http_send_status(fd, S_INTERNAL_SERVER_ERROR);
|
2018-02-04 20:27:33 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2020-08-05 11:41:44 +00:00
|
|
|
/* build header */
|
|
|
|
if (esnprintf(res.field[RES_CONTENT_LENGTH],
|
|
|
|
sizeof(res.field[RES_CONTENT_LENGTH]),
|
|
|
|
"%zu", upper - lower + 1)) {
|
|
|
|
return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
2020-08-05 13:43:29 +00:00
|
|
|
if (req->field[REQ_RANGE][0] != '\0') {
|
2020-08-05 11:41:44 +00:00
|
|
|
if (esnprintf(res.field[RES_CONTENT_RANGE],
|
|
|
|
sizeof(res.field[RES_CONTENT_RANGE]),
|
|
|
|
"bytes %zd-%zd/%zu", lower, upper,
|
|
|
|
st->st_size)) {
|
|
|
|
return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-05 11:41:44 +00:00
|
|
|
if (esnprintf(res.field[RES_CONTENT_TYPE],
|
|
|
|
sizeof(res.field[RES_CONTENT_TYPE]),
|
|
|
|
"%s", mime)) {
|
|
|
|
return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
|
|
|
|
}
|
|
|
|
if (timestamp(res.field[RES_LAST_MODIFIED],
|
|
|
|
sizeof(res.field[RES_LAST_MODIFIED]),
|
|
|
|
st->st_mtim.tv_sec)) {
|
|
|
|
return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* send header as late as possible */
|
|
|
|
if ((sendstatus = http_send_header(fd, &res)) != res.status) {
|
|
|
|
res.status = sendstatus;
|
2018-02-04 20:27:33 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2020-08-05 13:43:29 +00:00
|
|
|
if (req->method == M_GET) {
|
2018-02-04 20:27:33 +00:00
|
|
|
/* write data until upper bound is hit */
|
|
|
|
remaining = upper - lower + 1;
|
|
|
|
|
2018-03-04 23:30:53 +00:00
|
|
|
while ((bread = fread(buf, 1, MIN(sizeof(buf),
|
2020-08-05 16:59:55 +00:00
|
|
|
remaining), fp))) {
|
2018-02-04 20:27:33 +00:00
|
|
|
if (bread < 0) {
|
2020-08-05 11:41:44 +00:00
|
|
|
res.status = S_INTERNAL_SERVER_ERROR;
|
2020-04-22 18:46:30 +00:00
|
|
|
goto cleanup;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
remaining -= bread;
|
|
|
|
p = buf;
|
|
|
|
while (bread > 0) {
|
|
|
|
bwritten = write(fd, p, bread);
|
|
|
|
if (bwritten <= 0) {
|
2020-08-05 11:41:44 +00:00
|
|
|
res.status = S_REQUEST_TIMEOUT;
|
2020-04-22 18:46:30 +00:00
|
|
|
goto cleanup;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
bread -= bwritten;
|
|
|
|
p += bwritten;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cleanup:
|
|
|
|
if (fp) {
|
|
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
|
2020-08-05 11:41:44 +00:00
|
|
|
return res.status;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|