xref: /openbsd-src/usr.sbin/httpd/server_file.c (revision d842f11e1d9610068997e5d5fef3d53c151a38a3)
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