1*d842f11eSflorian /* $OpenBSD: server_file.c,v 1.80 2024/04/29 16:17:46 florian Exp $ */
2b7b6a941Sreyk
3b7b6a941Sreyk /*
4142cfc82Sreyk * Copyright (c) 2006 - 2017 Reyk Floeter <reyk@openbsd.org>
5b7b6a941Sreyk *
6b7b6a941Sreyk * Permission to use, copy, modify, and distribute this software for any
7b7b6a941Sreyk * purpose with or without fee is hereby granted, provided that the above
8b7b6a941Sreyk * copyright notice and this permission notice appear in all copies.
9b7b6a941Sreyk *
10b7b6a941Sreyk * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11b7b6a941Sreyk * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12b7b6a941Sreyk * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13b7b6a941Sreyk * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14b7b6a941Sreyk * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15b7b6a941Sreyk * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16b7b6a941Sreyk * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17b7b6a941Sreyk */
18b7b6a941Sreyk
19b7b6a941Sreyk #include <sys/types.h>
20b7b6a941Sreyk #include <sys/time.h>
21b7b6a941Sreyk #include <sys/stat.h>
22b7b6a941Sreyk
2386f952e4Sreyk #include <limits.h>
24b7b6a941Sreyk #include <errno.h>
25b7b6a941Sreyk #include <fcntl.h>
26b7b6a941Sreyk #include <stdlib.h>
27b7b6a941Sreyk #include <string.h>
28b7b6a941Sreyk #include <unistd.h>
29b7b6a941Sreyk #include <stdio.h>
3043d7585dSreyk #include <dirent.h>
3143d7585dSreyk #include <time.h>
32b7b6a941Sreyk #include <event.h>
338dc592d3Sespie #include <util.h>
34b7b6a941Sreyk
35b7b6a941Sreyk #include "httpd.h"
36b7b6a941Sreyk #include "http.h"
378dc592d3Sespie #include "css.h"
388dc592d3Sespie #include "js.h"
39b7b6a941Sreyk
40b9fc9a72Sderaadt #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
41b9fc9a72Sderaadt #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
42b0faf28cSflorian
43b0faf28cSflorian int server_file_access(struct httpd *, struct client *,
44b0faf28cSflorian char *, size_t);
45b0faf28cSflorian int server_file_request(struct httpd *, struct client *,
46db87a26fSflorian struct media_type *, int, const struct stat *);
47b0faf28cSflorian int server_partial_file_request(struct httpd *, struct client *,
48db87a26fSflorian struct media_type *, int, const struct stat *,
49db87a26fSflorian char *);
50db87a26fSflorian int server_file_index(struct httpd *, struct client *, int,
51db87a26fSflorian struct stat *);
525c205e9bSderaadt int server_file_modified_since(struct http_descriptor *,
53db87a26fSflorian const struct timespec *);
54de6550b1Sreyk int server_file_method(struct client *);
55b0faf28cSflorian int parse_range_spec(char *, size_t, struct range *);
56142cfc82Sreyk int parse_ranges(struct client *, char *, size_t);
5757fd1053Sespie static int select_visible(const struct dirent *);
5812312c27Sreyk
59b7b6a941Sreyk int
server_file_access(struct httpd * env,struct client * clt,char * path,size_t len)60de6550b1Sreyk server_file_access(struct httpd *env, struct client *clt,
61f6c6e883Schrisz char *path, size_t len)
625d9e55e4Sreyk {
63d08e4976Sreyk struct http_descriptor *desc = clt->clt_descreq;
6443d7585dSreyk struct server_config *srv_conf = clt->clt_srv_conf;
65f6c6e883Schrisz struct stat st;
66b0faf28cSflorian struct kv *r, key;
67db87a26fSflorian struct media_type *media;
68c81abcf1Ssemarie char *newpath, *encodedpath;
69db87a26fSflorian int ret, fd;
7043d7585dSreyk
71db87a26fSflorian if ((fd = open(path, O_RDONLY)) == -1) {
72db87a26fSflorian switch (errno) {
73db87a26fSflorian case ENOENT:
74db87a26fSflorian case ENOTDIR:
75db87a26fSflorian return (404);
76db87a26fSflorian case EACCES:
77db87a26fSflorian return (403);
78db87a26fSflorian default:
79db87a26fSflorian return (500);
80db87a26fSflorian }
81db87a26fSflorian }
82db87a26fSflorian if (fstat(fd, &st) == -1) {
83db87a26fSflorian close(fd);
84db87a26fSflorian return (500);
85db87a26fSflorian }
86c9351fd6Sreyk
87db87a26fSflorian if (S_ISDIR(st.st_mode)) {
8843d7585dSreyk /* Deny access if directory indexing is disabled */
8943d7585dSreyk if (srv_conf->flags & SRVFLAG_NO_INDEX) {
90db87a26fSflorian close(fd);
91db87a26fSflorian return (403);
9243d7585dSreyk }
935d9e55e4Sreyk
94de6550b1Sreyk if (desc->http_path_alias != NULL) {
955d9e55e4Sreyk /* Recursion - the index "file" is a directory? */
96db87a26fSflorian close(fd);
97db87a26fSflorian return (500);
985d9e55e4Sreyk }
995d9e55e4Sreyk
1005d9e55e4Sreyk /* Redirect to path with trailing "/" */
1015d9e55e4Sreyk if (path[strlen(path) - 1] != '/') {
102db87a26fSflorian close(fd);
103c81abcf1Ssemarie if ((encodedpath = url_encode(desc->http_path)) == NULL)
104c81abcf1Ssemarie return (500);
105cb8c4e3eSdv if (asprintf(&newpath, "%s/", encodedpath) == -1) {
106c81abcf1Ssemarie free(encodedpath);
107c9351fd6Sreyk return (500);
108c81abcf1Ssemarie }
109c81abcf1Ssemarie free(encodedpath);
110c81abcf1Ssemarie
111de6550b1Sreyk /* Path alias will be used for the redirection */
112de6550b1Sreyk desc->http_path_alias = newpath;
1135d9e55e4Sreyk
1145d9e55e4Sreyk /* Indicate that the file has been moved */
1155d9e55e4Sreyk return (301);
1165d9e55e4Sreyk }
1175d9e55e4Sreyk
118de6550b1Sreyk /* Append the default index file to the location */
119de6550b1Sreyk if (asprintf(&newpath, "%s%s", desc->http_path,
120db87a26fSflorian srv_conf->index) == -1) {
121db87a26fSflorian close(fd);
122de6550b1Sreyk return (500);
123db87a26fSflorian }
124de6550b1Sreyk desc->http_path_alias = newpath;
125de6550b1Sreyk if (server_getlocation(clt, newpath) != srv_conf) {
126de6550b1Sreyk /* The location has changed */
127db87a26fSflorian close(fd);
128de6550b1Sreyk return (server_file(env, clt));
129de6550b1Sreyk }
130de6550b1Sreyk
131de6550b1Sreyk /* Otherwise append the default index file to the path */
13243d7585dSreyk if (strlcat(path, srv_conf->index, len) >= len) {
133db87a26fSflorian close(fd);
134db87a26fSflorian return (403);
1355d9e55e4Sreyk }
1365d9e55e4Sreyk
137f6c6e883Schrisz ret = server_file_access(env, clt, path, len);
138de6550b1Sreyk if (ret == 404) {
13943d7585dSreyk /*
14043d7585dSreyk * Index file not found; fail if auto-indexing is
14143d7585dSreyk * not enabled, otherwise return success but
14243d7585dSreyk * indicate directory with S_ISDIR of the previous
14343d7585dSreyk * stat.
14443d7585dSreyk */
14543d7585dSreyk if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) {
146db87a26fSflorian close(fd);
147db87a26fSflorian return (403);
14843d7585dSreyk }
149de6550b1Sreyk
150db87a26fSflorian return (server_file_index(env, clt, fd, &st));
15143d7585dSreyk }
152db87a26fSflorian close(fd);
153de6550b1Sreyk return (ret);
154f6c6e883Schrisz } else if (!S_ISREG(st.st_mode)) {
1555d9e55e4Sreyk /* Don't follow symlinks and ignore special files */
156db87a26fSflorian close(fd);
157db87a26fSflorian return (403);
1585d9e55e4Sreyk }
1595d9e55e4Sreyk
160db87a26fSflorian media = media_find_config(env, srv_conf, path);
161db87a26fSflorian
162db87a26fSflorian /* Only consider range requests for GET */
163db87a26fSflorian if (desc->http_method == HTTP_METHOD_GET) {
164b0faf28cSflorian key.kv_key = "Range";
165b0faf28cSflorian r = kv_find(&desc->http_headers, &key);
166b0faf28cSflorian if (r != NULL)
167db87a26fSflorian return (server_partial_file_request(env, clt, media,
168db87a26fSflorian fd, &st, r->kv_value));
169db87a26fSflorian }
1705d9e55e4Sreyk
171db87a26fSflorian /* change path to path.gz if necessary. */
172db87a26fSflorian if (srv_conf->flags & SRVFLAG_GZIP_STATIC) {
173db87a26fSflorian struct http_descriptor *req = clt->clt_descreq;
174db87a26fSflorian struct http_descriptor *resp = clt->clt_descresp;
175db87a26fSflorian struct stat gzst;
176db87a26fSflorian int gzfd;
177db87a26fSflorian char gzpath[PATH_MAX];
178db87a26fSflorian
179db87a26fSflorian /* check Accept-Encoding header */
180db87a26fSflorian key.kv_key = "Accept-Encoding";
181db87a26fSflorian r = kv_find(&req->http_headers, &key);
182db87a26fSflorian
183db87a26fSflorian if (r != NULL && strstr(r->kv_value, "gzip") != NULL) {
184db87a26fSflorian /* append ".gz" to path and check existence */
185db87a26fSflorian ret = snprintf(gzpath, sizeof(gzpath), "%s.gz", path);
186db87a26fSflorian if (ret < 0 || (size_t)ret >= sizeof(gzpath)) {
187db87a26fSflorian close(fd);
1885d9e55e4Sreyk return (500);
1895d9e55e4Sreyk }
1905d9e55e4Sreyk
191db87a26fSflorian if ((gzfd = open(gzpath, O_RDONLY)) != -1) {
192db87a26fSflorian /* .gz must be a file, and not older */
193db87a26fSflorian if (fstat(gzfd, &gzst) != -1 &&
194db87a26fSflorian S_ISREG(gzst.st_mode) &&
195db87a26fSflorian timespeccmp(&gzst.st_mtim, &st.st_mtim,
196db87a26fSflorian >=)) {
197db87a26fSflorian kv_add(&resp->http_headers,
198db87a26fSflorian "Content-Encoding", "gzip");
199db87a26fSflorian /* Use original file timestamp */
200db87a26fSflorian gzst.st_mtim = st.st_mtim;
201db87a26fSflorian st = gzst;
202db87a26fSflorian close(fd);
203db87a26fSflorian fd = gzfd;
204db87a26fSflorian } else {
205db87a26fSflorian close(gzfd);
206db87a26fSflorian }
207db87a26fSflorian }
208db87a26fSflorian }
209db87a26fSflorian }
210db87a26fSflorian
211db87a26fSflorian return (server_file_request(env, clt, media, fd, &st));
2125d9e55e4Sreyk }
2135d9e55e4Sreyk
2145d9e55e4Sreyk int
server_file(struct httpd * env,struct client * clt)2155fa30660Sreyk server_file(struct httpd *env, struct client *clt)
216b7b6a941Sreyk {
217d08e4976Sreyk struct http_descriptor *desc = clt->clt_descreq;
2189fb8351aSreyk struct server_config *srv_conf = clt->clt_srv_conf;
219b9fc9a72Sderaadt char path[PATH_MAX];
2204624b10aSchrisz const char *stripped, *errstr = NULL;
221de6550b1Sreyk int ret = 500;
222b7b6a941Sreyk
223de6550b1Sreyk if (srv_conf->flags & SRVFLAG_FCGI)
224de6550b1Sreyk return (server_fcgi(env, clt));
225de6550b1Sreyk
226c9351fd6Sreyk /* Request path is already canonicalized */
2274624b10aSchrisz stripped = server_root_strip(
228de6550b1Sreyk desc->http_path_alias != NULL ?
2294624b10aSchrisz desc->http_path_alias : desc->http_path,
2304624b10aSchrisz srv_conf->strip);
2314624b10aSchrisz if ((size_t)snprintf(path, sizeof(path), "%s%s",
2324624b10aSchrisz srv_conf->root, stripped) >= sizeof(path)) {
233e9b02f4aSreyk errstr = desc->http_path;
234e9b02f4aSreyk goto abort;
23533cf7fc3Sreyk }
23633cf7fc3Sreyk
2375d9e55e4Sreyk /* Returns HTTP status code on error */
238f6c6e883Schrisz if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) {
239de6550b1Sreyk errstr = desc->http_path_alias != NULL ?
240de6550b1Sreyk desc->http_path_alias : desc->http_path;
241e9b02f4aSreyk goto abort;
24233cf7fc3Sreyk }
243b7b6a941Sreyk
244de6550b1Sreyk return (ret);
245de6550b1Sreyk
246de6550b1Sreyk abort:
247de6550b1Sreyk if (errstr == NULL)
248de6550b1Sreyk errstr = strerror(errno);
249de6550b1Sreyk server_abort_http(clt, ret, errstr);
250de6550b1Sreyk return (-1);
251de6550b1Sreyk }
252de6550b1Sreyk
253de6550b1Sreyk int
server_file_method(struct client * clt)254de6550b1Sreyk server_file_method(struct client *clt)
255de6550b1Sreyk {
256d08e4976Sreyk struct http_descriptor *desc = clt->clt_descreq;
257de6550b1Sreyk
258de6550b1Sreyk switch (desc->http_method) {
259de6550b1Sreyk case HTTP_METHOD_GET:
260de6550b1Sreyk case HTTP_METHOD_HEAD:
261de6550b1Sreyk return (0);
262de6550b1Sreyk default:
263de6550b1Sreyk /* Other methods are not allowed */
264de6550b1Sreyk errno = EACCES;
265de6550b1Sreyk return (405);
266de6550b1Sreyk }
267de6550b1Sreyk /* NOTREACHED */
268de6550b1Sreyk }
269de6550b1Sreyk
270de6550b1Sreyk int
server_file_request(struct httpd * env,struct client * clt,struct media_type * media,int fd,const struct stat * st)271db87a26fSflorian server_file_request(struct httpd *env, struct client *clt, struct media_type
272db87a26fSflorian *media, int fd, const struct stat *st)
273de6550b1Sreyk {
274de6550b1Sreyk struct server_config *srv_conf = clt->clt_srv_conf;
275de6550b1Sreyk const char *errstr = NULL;
276db87a26fSflorian int ret, code = 500;
277a236030eSotto size_t bufsiz;
278de6550b1Sreyk
279de6550b1Sreyk if ((ret = server_file_method(clt)) != 0) {
280de6550b1Sreyk code = ret;
281de6550b1Sreyk goto abort;
28243d7585dSreyk }
28343d7585dSreyk
284db87a26fSflorian if ((ret = server_file_modified_since(clt->clt_descreq, &st->st_mtim))
285db87a26fSflorian != -1) {
286cc526105Sreyk /* send the header without a body */
287cc526105Sreyk if ((ret = server_response_http(clt, ret, media, -1,
288db87a26fSflorian MINIMUM(time(NULL), st->st_mtim.tv_sec))) == -1)
289cc526105Sreyk goto fail;
290*d842f11eSflorian close(fd);
291cc526105Sreyk goto done;
292cc526105Sreyk }
2937f03222aSflorian
294db87a26fSflorian ret = server_response_http(clt, 200, media, st->st_size,
295db87a26fSflorian MINIMUM(time(NULL), st->st_mtim.tv_sec));
2965fa30660Sreyk switch (ret) {
2975fa30660Sreyk case -1:
2989b9ff8ecSreyk goto fail;
2995fa30660Sreyk case 0:
3005fa30660Sreyk /* Connection is already finished */
3015fa30660Sreyk close(fd);
302cebbf23cSreyk goto done;
3035fa30660Sreyk default:
3045fa30660Sreyk break;
3055fa30660Sreyk }
306b7b6a941Sreyk
307b7b6a941Sreyk clt->clt_fd = fd;
308151a32cfSreyk if (clt->clt_srvbev != NULL)
309151a32cfSreyk bufferevent_free(clt->clt_srvbev);
31012312c27Sreyk
311f3e6e694Sflorian clt->clt_srvbev_throttled = 0;
312151a32cfSreyk clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read,
31312312c27Sreyk server_write, server_file_error, clt);
314151a32cfSreyk if (clt->clt_srvbev == NULL) {
315b7b6a941Sreyk errstr = "failed to allocate file buffer event";
316b7b6a941Sreyk goto fail;
317b7b6a941Sreyk }
318b7b6a941Sreyk
319a236030eSotto /* Adjust read watermark to the optimal file io size */
320db87a26fSflorian bufsiz = MAXIMUM(st->st_blksize, 64 * 1024);
321720c14e5Sreyk bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0,
322a236030eSotto bufsiz);
323720c14e5Sreyk
324151a32cfSreyk bufferevent_settimeout(clt->clt_srvbev,
3259fb8351aSreyk srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
326151a32cfSreyk bufferevent_enable(clt->clt_srvbev, EV_READ);
32712312c27Sreyk bufferevent_disable(clt->clt_bev, EV_READ);
328b7b6a941Sreyk
329cebbf23cSreyk done:
330cebbf23cSreyk server_reset_http(clt);
331b7b6a941Sreyk return (0);
332b7b6a941Sreyk fail:
333e9b02f4aSreyk bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
334e9b02f4aSreyk bufferevent_free(clt->clt_bev);
335e9b02f4aSreyk clt->clt_bev = NULL;
336e9b02f4aSreyk abort:
337f035d8b1Sderaadt if (fd != -1)
338f035d8b1Sderaadt close(fd);
339b7b6a941Sreyk if (errstr == NULL)
340b7b6a941Sreyk errstr = strerror(errno);
341e9b02f4aSreyk server_abort_http(clt, code, errstr);
342b7b6a941Sreyk return (-1);
343b7b6a941Sreyk }
34412312c27Sreyk
34543d7585dSreyk int
server_partial_file_request(struct httpd * env,struct client * clt,struct media_type * media,int fd,const struct stat * st,char * range_str)346db87a26fSflorian server_partial_file_request(struct httpd *env, struct client *clt,
347db87a26fSflorian struct media_type *media, int fd, const struct stat *st, char *range_str)
348b0faf28cSflorian {
349d24f6b1eSreyk struct server_config *srv_conf = clt->clt_srv_conf;
350b0faf28cSflorian struct http_descriptor *resp = clt->clt_descresp;
351db87a26fSflorian struct media_type multipart_media;
352142cfc82Sreyk struct range_data *r = &clt->clt_ranges;
353b0faf28cSflorian struct range *range;
354a236030eSotto size_t content_length = 0, bufsiz;
355db87a26fSflorian int code = 500, i, nranges, ret;
356b0faf28cSflorian char content_range[64];
357b0faf28cSflorian const char *errstr = NULL;
358b0faf28cSflorian
359db87a26fSflorian if ((nranges = parse_ranges(clt, range_str, st->st_size)) < 1) {
360a10cbbf9Sderaadt code = 416;
361a10cbbf9Sderaadt (void)snprintf(content_range, sizeof(content_range),
362db87a26fSflorian "bytes */%lld", st->st_size);
363a10cbbf9Sderaadt errstr = content_range;
364a10cbbf9Sderaadt goto abort;
365a10cbbf9Sderaadt }
366b0faf28cSflorian
367142cfc82Sreyk r->range_media = media;
368b0faf28cSflorian
369b0faf28cSflorian if (nranges == 1) {
370142cfc82Sreyk range = &r->range[0];
371b0faf28cSflorian (void)snprintf(content_range, sizeof(content_range),
372b0faf28cSflorian "bytes %lld-%lld/%lld", range->start, range->end,
373db87a26fSflorian st->st_size);
374b0faf28cSflorian if (kv_add(&resp->http_headers, "Content-Range",
375b0faf28cSflorian content_range) == NULL)
376b0faf28cSflorian goto abort;
377b0faf28cSflorian
378142cfc82Sreyk range = &r->range[0];
379b0faf28cSflorian content_length += range->end - range->start + 1;
380142cfc82Sreyk } else {
381142cfc82Sreyk /* Add boundary, all parts will be handled by the callback */
382142cfc82Sreyk arc4random_buf(&clt->clt_boundary, sizeof(clt->clt_boundary));
383b0faf28cSflorian
384142cfc82Sreyk /* Calculate Content-Length of the complete multipart body */
385142cfc82Sreyk for (i = 0; i < nranges; i++) {
386142cfc82Sreyk range = &r->range[i];
387142cfc82Sreyk
388142cfc82Sreyk /* calculate Content-Length of the complete body */
389142cfc82Sreyk if ((ret = snprintf(NULL, 0,
390142cfc82Sreyk "\r\n--%llu\r\n"
391142cfc82Sreyk "Content-Type: %s/%s\r\n"
392142cfc82Sreyk "Content-Range: bytes %lld-%lld/%lld\r\n\r\n",
393142cfc82Sreyk clt->clt_boundary,
394142cfc82Sreyk media->media_type, media->media_subtype,
395db87a26fSflorian range->start, range->end, st->st_size)) < 0)
396b0faf28cSflorian goto abort;
397b0faf28cSflorian
398142cfc82Sreyk /* Add data length */
399142cfc82Sreyk content_length += ret + range->end - range->start + 1;
400142cfc82Sreyk
401142cfc82Sreyk }
402142cfc82Sreyk if ((ret = snprintf(NULL, 0, "\r\n--%llu--\r\n",
403142cfc82Sreyk clt->clt_boundary)) < 0)
404142cfc82Sreyk goto abort;
405142cfc82Sreyk content_length += ret;
406b0faf28cSflorian
407b0faf28cSflorian /* prepare multipart/byteranges media type */
408b0faf28cSflorian (void)strlcpy(multipart_media.media_type, "multipart",
409b0faf28cSflorian sizeof(multipart_media.media_type));
410b0faf28cSflorian (void)snprintf(multipart_media.media_subtype,
411b0faf28cSflorian sizeof(multipart_media.media_subtype),
412142cfc82Sreyk "byteranges; boundary=%llu", clt->clt_boundary);
413b0faf28cSflorian media = &multipart_media;
414b0faf28cSflorian }
415b0faf28cSflorian
416142cfc82Sreyk /* Start with first range */
417142cfc82Sreyk r->range_toread = TOREAD_HTTP_RANGE;
418f035d8b1Sderaadt
419b0faf28cSflorian ret = server_response_http(clt, 206, media, content_length,
420db87a26fSflorian MINIMUM(time(NULL), st->st_mtim.tv_sec));
421b0faf28cSflorian switch (ret) {
422b0faf28cSflorian case -1:
423b0faf28cSflorian goto fail;
424b0faf28cSflorian case 0:
425b0faf28cSflorian /* Connection is already finished */
426142cfc82Sreyk close(fd);
427b0faf28cSflorian goto done;
428b0faf28cSflorian default:
429b0faf28cSflorian break;
430b0faf28cSflorian }
431b0faf28cSflorian
432142cfc82Sreyk clt->clt_fd = fd;
433142cfc82Sreyk if (clt->clt_srvbev != NULL)
434142cfc82Sreyk bufferevent_free(clt->clt_srvbev);
435b0faf28cSflorian
436142cfc82Sreyk clt->clt_srvbev_throttled = 0;
437142cfc82Sreyk clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read_httprange,
438142cfc82Sreyk server_write, server_file_error, clt);
439142cfc82Sreyk if (clt->clt_srvbev == NULL) {
440142cfc82Sreyk errstr = "failed to allocate file buffer event";
441142cfc82Sreyk goto fail;
442142cfc82Sreyk }
443142cfc82Sreyk
444a236030eSotto /* Adjust read watermark to the optimal file io size */
445db87a26fSflorian bufsiz = MAXIMUM(st->st_blksize, 64 * 1024);
446142cfc82Sreyk bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0,
447a236030eSotto bufsiz);
448142cfc82Sreyk
449142cfc82Sreyk bufferevent_settimeout(clt->clt_srvbev,
450142cfc82Sreyk srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
451142cfc82Sreyk bufferevent_enable(clt->clt_srvbev, EV_READ);
452142cfc82Sreyk bufferevent_disable(clt->clt_bev, EV_READ);
453b0faf28cSflorian
454b0faf28cSflorian done:
455b0faf28cSflorian server_reset_http(clt);
456b0faf28cSflorian return (0);
457b0faf28cSflorian fail:
458b0faf28cSflorian bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
459b0faf28cSflorian bufferevent_free(clt->clt_bev);
460b0faf28cSflorian clt->clt_bev = NULL;
461b0faf28cSflorian abort:
462f035d8b1Sderaadt if (fd != -1)
463f035d8b1Sderaadt close(fd);
464b0faf28cSflorian if (errstr == NULL)
465b0faf28cSflorian errstr = strerror(errno);
466b0faf28cSflorian server_abort_http(clt, code, errstr);
467b0faf28cSflorian return (-1);
468b0faf28cSflorian }
469b0faf28cSflorian
47057fd1053Sespie /* ignore hidden files starting with a dot */
47157fd1053Sespie static int
select_visible(const struct dirent * dp)47257fd1053Sespie select_visible(const struct dirent *dp)
47357fd1053Sespie {
47457fd1053Sespie if (dp->d_name[0] == '.' &&
47557fd1053Sespie !(dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
47657fd1053Sespie return 0;
47757fd1053Sespie else
47857fd1053Sespie return 1;
47957fd1053Sespie }
48057fd1053Sespie
481b0faf28cSflorian int
server_file_index(struct httpd * env,struct client * clt,int fd,struct stat * st)482db87a26fSflorian server_file_index(struct httpd *env, struct client *clt, int fd,
483db87a26fSflorian struct stat *st)
48443d7585dSreyk {
485b9fc9a72Sderaadt char path[PATH_MAX];
48643d7585dSreyk char tmstr[21];
487d08e4976Sreyk struct http_descriptor *desc = clt->clt_descreq;
48843d7585dSreyk struct server_config *srv_conf = clt->clt_srv_conf;
48943d7585dSreyk struct dirent **namelist, *dp;
490db87a26fSflorian int namesize, i, ret, skip;
491de6550b1Sreyk int code = 500;
49243d7585dSreyk struct evbuffer *evb = NULL;
49343d7585dSreyk struct media_type *media;
4948dc592d3Sespie const char *stripped;
4957fa6cf2cSflorian char *escapeduri, *escapedhtml, *escapedpath;
49643d7585dSreyk struct tm tm;
497be5ab2e6Schrisz time_t t, dir_mtime;
4988dc592d3Sespie char human_size[FMT_SCALED_STRSIZE];
49943d7585dSreyk
500de6550b1Sreyk if ((ret = server_file_method(clt)) != 0) {
501de6550b1Sreyk code = ret;
502de6550b1Sreyk goto abort;
503de6550b1Sreyk }
504de6550b1Sreyk
50543d7585dSreyk /* Request path is already canonicalized */
5064624b10aSchrisz stripped = server_root_strip(desc->http_path, srv_conf->strip);
50743d7585dSreyk if ((size_t)snprintf(path, sizeof(path), "%s%s",
5084624b10aSchrisz srv_conf->root, stripped) >= sizeof(path))
509e9b02f4aSreyk goto abort;
51043d7585dSreyk
511be5ab2e6Schrisz /* Save last modification time */
512db87a26fSflorian dir_mtime = MINIMUM(time(NULL), st->st_mtim.tv_sec);
513be5ab2e6Schrisz
51443d7585dSreyk if ((evb = evbuffer_new()) == NULL)
515e9b02f4aSreyk goto abort;
51643d7585dSreyk
5177fa6cf2cSflorian if ((escapedpath = escape_html(desc->http_path)) == NULL)
518896eaea5Sop goto abort;
5197fa6cf2cSflorian
52043d7585dSreyk /* Generate simple HTML index document */
52143d7585dSreyk if (evbuffer_add_printf(evb,
522d89214cdSreyk "<!DOCTYPE html>\n"
5238dc592d3Sespie "<html lang=\"en\">\n"
52443d7585dSreyk "<head>\n"
5258643c0edSbentley "<meta charset=\"utf-8\">\n"
52643d7585dSreyk "<title>Index of %s</title>\n"
5278dc592d3Sespie "<style><!--\n%s--></style>\n"
52843d7585dSreyk "</head>\n"
52943d7585dSreyk "<body>\n"
53043d7585dSreyk "<h1>Index of %s</h1>\n"
5318dc592d3Sespie "<table><thead>\n"
5328dc592d3Sespie "<tr class=\"sort\"><th class=\"sorted\">Name</th>\n"
5338dc592d3Sespie " <th>Date</th><th>Size</th></tr>\n"
5348dc592d3Sespie "</thead><tbody>\n",
5358dc592d3Sespie escapedpath, css, escapedpath) == -1) {
536896eaea5Sop free(escapedpath);
537896eaea5Sop goto abort;
538896eaea5Sop }
53943d7585dSreyk
5407fa6cf2cSflorian free(escapedpath);
5417fa6cf2cSflorian
542db87a26fSflorian if ((namesize = scandirat(fd, ".", &namelist, select_visible,
54357fd1053Sespie alphasort)) == -1)
544896eaea5Sop goto abort;
545896eaea5Sop
546896eaea5Sop /* Indicate failure but continue going through the list */
547896eaea5Sop skip = 0;
548896eaea5Sop
54943d7585dSreyk for (i = 0; i < namesize; i++) {
550a10cbbf9Sderaadt struct stat subst;
551a10cbbf9Sderaadt
55243d7585dSreyk dp = namelist[i];
55343d7585dSreyk
55443d7585dSreyk if (skip ||
555a10cbbf9Sderaadt fstatat(fd, dp->d_name, &subst, 0) == -1) {
55643d7585dSreyk free(dp);
55743d7585dSreyk continue;
55843d7585dSreyk }
55943d7585dSreyk
560a10cbbf9Sderaadt t = subst.st_mtime;
56143d7585dSreyk localtime_r(&t, &tm);
56243d7585dSreyk strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
56343d7585dSreyk
564896eaea5Sop if ((escapeduri = url_encode(dp->d_name)) == NULL) {
565896eaea5Sop skip = 1;
566896eaea5Sop free(dp);
567896eaea5Sop continue;
568896eaea5Sop }
569896eaea5Sop if ((escapedhtml = escape_html(dp->d_name)) == NULL) {
570896eaea5Sop skip = 1;
571896eaea5Sop free(escapeduri);
572896eaea5Sop free(dp);
573896eaea5Sop continue;
574896eaea5Sop }
5757fa6cf2cSflorian
57657fd1053Sespie if (S_ISDIR(subst.st_mode)) {
57743d7585dSreyk if (evbuffer_add_printf(evb,
5788dc592d3Sespie "<tr class=\"dir\">"
5798dc592d3Sespie "<td><a href=\"%s%s/\">%s/</a></td>\n"
5808dc592d3Sespie " <td data-o=\"%lld\">%s</td><td>%s</td></tr>\n",
5813bbf9882Sflorian strchr(escapeduri, ':') != NULL ? "./" : "",
5827fa6cf2cSflorian escapeduri, escapedhtml,
5838dc592d3Sespie (long long)t, tmstr, "-") == -1)
58443d7585dSreyk skip = 1;
585a10cbbf9Sderaadt } else if (S_ISREG(subst.st_mode)) {
5868dc592d3Sespie if ((fmt_scaled(subst.st_size, human_size) != 0) ||
5878dc592d3Sespie (evbuffer_add_printf(evb,
5888dc592d3Sespie "<tr><td><a href=\"%s%s\">%s</a></td>\n"
5898dc592d3Sespie " <td data-o=\"%lld\">%s</td>"
590ef0dceb5Sespie "<td title=\"%llu\">%s</td></tr>\n",
5913bbf9882Sflorian strchr(escapeduri, ':') != NULL ? "./" : "",
5927fa6cf2cSflorian escapeduri, escapedhtml,
5938dc592d3Sespie (long long)t, tmstr,
5948dc592d3Sespie subst.st_size, human_size) == -1))
59543d7585dSreyk skip = 1;
59643d7585dSreyk }
5977fa6cf2cSflorian free(escapeduri);
5987fa6cf2cSflorian free(escapedhtml);
59943d7585dSreyk free(dp);
60043d7585dSreyk }
60143d7585dSreyk free(namelist);
60243d7585dSreyk
60343d7585dSreyk if (skip ||
60443d7585dSreyk evbuffer_add_printf(evb,
6058dc592d3Sespie "</tbody></table>\n<script>\n"
6068dc592d3Sespie "%s\n"
6078dc592d3Sespie "</script>\n</body>\n</html>\n", js) == -1)
608e9b02f4aSreyk goto abort;
60943d7585dSreyk
61043d7585dSreyk close(fd);
611e9b02f4aSreyk fd = -1;
61243d7585dSreyk
613d24f6b1eSreyk media = media_find_config(env, srv_conf, "index.html");
614be5ab2e6Schrisz ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb),
615be5ab2e6Schrisz dir_mtime);
61643d7585dSreyk switch (ret) {
61743d7585dSreyk case -1:
61843d7585dSreyk goto fail;
61943d7585dSreyk case 0:
62043d7585dSreyk /* Connection is already finished */
62143d7585dSreyk evbuffer_free(evb);
622cebbf23cSreyk goto done;
62343d7585dSreyk default:
62443d7585dSreyk break;
62543d7585dSreyk }
62643d7585dSreyk
62743d7585dSreyk if (server_bufferevent_write_buffer(clt, evb) == -1)
62843d7585dSreyk goto fail;
62943d7585dSreyk evbuffer_free(evb);
630e9b02f4aSreyk evb = NULL;
63143d7585dSreyk
63243d7585dSreyk bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
63343d7585dSreyk if (clt->clt_persist)
63443d7585dSreyk clt->clt_toread = TOREAD_HTTP_HEADER;
63543d7585dSreyk else
63643d7585dSreyk clt->clt_toread = TOREAD_HTTP_NONE;
63743d7585dSreyk clt->clt_done = 0;
63843d7585dSreyk
639cebbf23cSreyk done:
640cebbf23cSreyk server_reset_http(clt);
64143d7585dSreyk return (0);
64243d7585dSreyk fail:
643e9b02f4aSreyk bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
644e9b02f4aSreyk bufferevent_free(clt->clt_bev);
645e9b02f4aSreyk clt->clt_bev = NULL;
646e9b02f4aSreyk abort:
64743d7585dSreyk if (fd != -1)
64843d7585dSreyk close(fd);
64943d7585dSreyk if (evb != NULL)
65043d7585dSreyk evbuffer_free(evb);
651de6550b1Sreyk server_abort_http(clt, code, desc->http_path);
65243d7585dSreyk return (-1);
65343d7585dSreyk }
65443d7585dSreyk
65512312c27Sreyk void
server_file_error(struct bufferevent * bev,short error,void * arg)65612312c27Sreyk server_file_error(struct bufferevent *bev, short error, void *arg)
65712312c27Sreyk {
65812312c27Sreyk struct client *clt = arg;
65971b1c91aSreyk struct evbuffer *src, *dst;
66012312c27Sreyk
66112312c27Sreyk if (error & EVBUFFER_TIMEOUT) {
66212312c27Sreyk server_close(clt, "buffer event timeout");
66312312c27Sreyk return;
66412312c27Sreyk }
66518b6a227Sreyk if (error & EVBUFFER_ERROR) {
66618b6a227Sreyk if (errno == EFBIG) {
66718b6a227Sreyk bufferevent_enable(bev, EV_READ);
66818b6a227Sreyk return;
66918b6a227Sreyk }
67018b6a227Sreyk server_close(clt, "buffer event error");
67118b6a227Sreyk return;
67218b6a227Sreyk }
67312312c27Sreyk if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
67494176587Sreyk bufferevent_disable(bev, EV_READ|EV_WRITE);
67512312c27Sreyk
67612312c27Sreyk clt->clt_done = 1;
67712312c27Sreyk
67871b1c91aSreyk src = EVBUFFER_INPUT(clt->clt_bev);
67971b1c91aSreyk
68071b1c91aSreyk /* Close the connection if a previous pipeline is empty */
68171b1c91aSreyk if (clt->clt_pipelining && EVBUFFER_LENGTH(src) == 0)
68271b1c91aSreyk clt->clt_persist = 0;
68371b1c91aSreyk
68412312c27Sreyk if (clt->clt_persist) {
68512312c27Sreyk /* Close input file and wait for next HTTP request */
68612312c27Sreyk if (clt->clt_fd != -1)
68712312c27Sreyk close(clt->clt_fd);
68812312c27Sreyk clt->clt_fd = -1;
68912312c27Sreyk clt->clt_toread = TOREAD_HTTP_HEADER;
69012312c27Sreyk server_reset_http(clt);
69112312c27Sreyk bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
69271b1c91aSreyk
69371b1c91aSreyk /* Start pipelining if the buffer is not empty */
69471b1c91aSreyk if (EVBUFFER_LENGTH(src)) {
69571b1c91aSreyk clt->clt_pipelining++;
69671b1c91aSreyk server_read_http(clt->clt_bev, arg);
69771b1c91aSreyk }
69812312c27Sreyk return;
69912312c27Sreyk }
7002cadac13Sreyk
7012cadac13Sreyk dst = EVBUFFER_OUTPUT(clt->clt_bev);
7022cadac13Sreyk if (EVBUFFER_LENGTH(dst)) {
7032cadac13Sreyk /* Finish writing all data first */
7042cadac13Sreyk bufferevent_enable(clt->clt_bev, EV_WRITE);
7052cadac13Sreyk return;
7062cadac13Sreyk }
7072cadac13Sreyk
70812312c27Sreyk server_close(clt, "done");
70912312c27Sreyk return;
71012312c27Sreyk }
71118b6a227Sreyk server_close(clt, "unknown event error");
71212312c27Sreyk return;
71312312c27Sreyk }
714b0faf28cSflorian
7157f03222aSflorian int
server_file_modified_since(struct http_descriptor * desc,const struct timespec * mtim)716db87a26fSflorian server_file_modified_since(struct http_descriptor *desc, const struct timespec
717db87a26fSflorian *mtim)
7187f03222aSflorian {
7197f03222aSflorian struct kv key, *since;
7207f03222aSflorian struct tm tm;
7217f03222aSflorian
7227f03222aSflorian key.kv_key = "If-Modified-Since";
7237f03222aSflorian if ((since = kv_find(&desc->http_headers, &key)) != NULL &&
72478fc662fSkili since->kv_value != NULL) {
7250b855fd2Sreyk memset(&tm, 0, sizeof(struct tm));
7260b855fd2Sreyk
7270b855fd2Sreyk /*
7280b855fd2Sreyk * Return "Not modified" if the file hasn't changed since
7290b855fd2Sreyk * the requested time.
7300b855fd2Sreyk */
7310b855fd2Sreyk if (strptime(since->kv_value,
7320b855fd2Sreyk "%a, %d %h %Y %T %Z", &tm) != NULL &&
7335c205e9bSderaadt timegm(&tm) >= mtim->tv_sec)
7347f03222aSflorian return (304);
7357f03222aSflorian }
7367f03222aSflorian
7377f03222aSflorian return (-1);
7387f03222aSflorian }
7397f03222aSflorian
740142cfc82Sreyk int
parse_ranges(struct client * clt,char * str,size_t file_sz)741142cfc82Sreyk parse_ranges(struct client *clt, char *str, size_t file_sz)
742b0faf28cSflorian {
743b0faf28cSflorian int i = 0;
744b0faf28cSflorian char *p, *q;
745142cfc82Sreyk struct range_data *r = &clt->clt_ranges;
746142cfc82Sreyk
747142cfc82Sreyk memset(r, 0, sizeof(*r));
748b0faf28cSflorian
749b0faf28cSflorian /* Extract range unit */
750b0faf28cSflorian if ((p = strchr(str, '=')) == NULL)
751142cfc82Sreyk return (-1);
752b0faf28cSflorian
753b0faf28cSflorian *p++ = '\0';
754b0faf28cSflorian /* Check if it's a bytes range spec */
755b0faf28cSflorian if (strcmp(str, "bytes") != 0)
756142cfc82Sreyk return (-1);
757b0faf28cSflorian
758b0faf28cSflorian while ((q = strchr(p, ',')) != NULL) {
759b0faf28cSflorian *q++ = '\0';
760b0faf28cSflorian
761b0faf28cSflorian /* Extract start and end positions */
762142cfc82Sreyk if (parse_range_spec(p, file_sz, &r->range[i]) == 0)
763b0faf28cSflorian continue;
764b0faf28cSflorian
765b0faf28cSflorian i++;
766142cfc82Sreyk if (i == SERVER_MAX_RANGES)
767142cfc82Sreyk return (-1);
768b0faf28cSflorian
769b0faf28cSflorian p = q;
770b0faf28cSflorian }
771b0faf28cSflorian
772142cfc82Sreyk if (parse_range_spec(p, file_sz, &r->range[i]) != 0)
773b0faf28cSflorian i++;
774b0faf28cSflorian
775142cfc82Sreyk r->range_total = file_sz;
776142cfc82Sreyk r->range_count = i;
777142cfc82Sreyk return (i);
778b0faf28cSflorian }
779b0faf28cSflorian
780b0faf28cSflorian int
parse_range_spec(char * str,size_t size,struct range * r)781b0faf28cSflorian parse_range_spec(char *str, size_t size, struct range *r)
782b0faf28cSflorian {
783b0faf28cSflorian size_t start_str_len, end_str_len;
784b0faf28cSflorian char *p, *start_str, *end_str;
785b0faf28cSflorian const char *errstr;
786b0faf28cSflorian
787b0faf28cSflorian if ((p = strchr(str, '-')) == NULL)
788b0faf28cSflorian return (0);
789b0faf28cSflorian
790b0faf28cSflorian *p++ = '\0';
791b0faf28cSflorian start_str = str;
792b0faf28cSflorian end_str = p;
793b0faf28cSflorian start_str_len = strlen(start_str);
794b0faf28cSflorian end_str_len = strlen(end_str);
795b0faf28cSflorian
796b0faf28cSflorian /* Either 'start' or 'end' is optional but not both */
797b0faf28cSflorian if ((start_str_len == 0) && (end_str_len == 0))
798b0faf28cSflorian return (0);
799b0faf28cSflorian
800b0faf28cSflorian if (end_str_len) {
801b0faf28cSflorian r->end = strtonum(end_str, 0, LLONG_MAX, &errstr);
802b0faf28cSflorian if (errstr)
803b0faf28cSflorian return (0);
804b0faf28cSflorian
805b0faf28cSflorian if ((size_t)r->end >= size)
806b0faf28cSflorian r->end = size - 1;
807b0faf28cSflorian } else
808b0faf28cSflorian r->end = size - 1;
809b0faf28cSflorian
810b0faf28cSflorian if (start_str_len) {
811b0faf28cSflorian r->start = strtonum(start_str, 0, LLONG_MAX, &errstr);
812b0faf28cSflorian if (errstr)
813b0faf28cSflorian return (0);
814b0faf28cSflorian
815b0faf28cSflorian if ((size_t)r->start >= size)
816b0faf28cSflorian return (0);
817b0faf28cSflorian } else {
818b0faf28cSflorian r->start = size - r->end;
819b0faf28cSflorian r->end = size - 1;
820b0faf28cSflorian }
821b0faf28cSflorian
822b0faf28cSflorian if (r->end < r->start)
823b0faf28cSflorian return (0);
824b0faf28cSflorian
825b0faf28cSflorian return (1);
826b0faf28cSflorian }
827