xref: /openbsd/usr.sbin/httpd/server_file.c (revision d842f11e)
1 /*	$OpenBSD: server_file.c,v 1.80 2024/04/29 16:17:46 florian Exp $	*/
2 
3 /*
4  * Copyright (c) 2006 - 2017 Reyk Floeter <reyk@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/time.h>
21 #include <sys/stat.h>
22 
23 #include <limits.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <stdio.h>
30 #include <dirent.h>
31 #include <time.h>
32 #include <event.h>
33 #include <util.h>
34 
35 #include "httpd.h"
36 #include "http.h"
37 #include "css.h"
38 #include "js.h"
39 
40 #define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
41 #define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
42 
43 int		 server_file_access(struct httpd *, struct client *,
44 		    char *, size_t);
45 int		 server_file_request(struct httpd *, struct client *,
46 		    struct media_type *, int, const struct stat *);
47 int		 server_partial_file_request(struct httpd *, struct client *,
48 		    struct media_type *, int, const struct stat *,
49 		    char *);
50 int		 server_file_index(struct httpd *, struct client *, int,
51 		    struct stat *);
52 int		 server_file_modified_since(struct http_descriptor *,
53 		    const struct timespec *);
54 int		 server_file_method(struct client *);
55 int		 parse_range_spec(char *, size_t, struct range *);
56 int		 parse_ranges(struct client *, char *, size_t);
57 static int	 select_visible(const struct dirent *);
58 
59 int
server_file_access(struct httpd * env,struct client * clt,char * path,size_t len)60 server_file_access(struct httpd *env, struct client *clt,
61     char *path, size_t len)
62 {
63 	struct http_descriptor	*desc = clt->clt_descreq;
64 	struct server_config	*srv_conf = clt->clt_srv_conf;
65 	struct stat		 st;
66 	struct kv		*r, key;
67 	struct media_type	*media;
68 	char			*newpath, *encodedpath;
69 	int			 ret, fd;
70 
71 	if ((fd = open(path, O_RDONLY)) == -1) {
72 		switch (errno) {
73 		case ENOENT:
74 		case ENOTDIR:
75 			return (404);
76 		case EACCES:
77 			return (403);
78 		default:
79 			return (500);
80 		}
81 	}
82 	if (fstat(fd, &st) == -1) {
83 		close(fd);
84 		return (500);
85 	}
86 
87 	if (S_ISDIR(st.st_mode)) {
88 		/* Deny access if directory indexing is disabled */
89 		if (srv_conf->flags & SRVFLAG_NO_INDEX) {
90 			close(fd);
91 			return (403);
92 		}
93 
94 		if (desc->http_path_alias != NULL) {
95 			/* Recursion - the index "file" is a directory? */
96 			close(fd);
97 			return (500);
98 		}
99 
100 		/* Redirect to path with trailing "/" */
101 		if (path[strlen(path) - 1] != '/') {
102 			close(fd);
103 			if ((encodedpath = url_encode(desc->http_path)) == NULL)
104 				return (500);
105 			if (asprintf(&newpath, "%s/", encodedpath) == -1) {
106 				free(encodedpath);
107 				return (500);
108 			}
109 			free(encodedpath);
110 
111 			/* Path alias will be used for the redirection */
112 			desc->http_path_alias = newpath;
113 
114 			/* Indicate that the file has been moved */
115 			return (301);
116 		}
117 
118 		/* Append the default index file to the location */
119 		if (asprintf(&newpath, "%s%s", desc->http_path,
120 		    srv_conf->index) == -1) {
121 			close(fd);
122 			return (500);
123 		}
124 		desc->http_path_alias = newpath;
125 		if (server_getlocation(clt, newpath) != srv_conf) {
126 			/* The location has changed */
127 			close(fd);
128 			return (server_file(env, clt));
129 		}
130 
131 		/* Otherwise append the default index file to the path */
132 		if (strlcat(path, srv_conf->index, len) >= len) {
133 			close(fd);
134 			return (403);
135 		}
136 
137 		ret = server_file_access(env, clt, path, len);
138 		if (ret == 404) {
139 			/*
140 			 * Index file not found; fail if auto-indexing is
141 			 * not enabled, otherwise return success but
142 			 * indicate directory with S_ISDIR of the previous
143 			 * stat.
144 			 */
145 			if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) {
146 				close(fd);
147 				return (403);
148 			}
149 
150 			return (server_file_index(env, clt, fd, &st));
151 		}
152 		close(fd);
153 		return (ret);
154 	} else if (!S_ISREG(st.st_mode)) {
155 		/* Don't follow symlinks and ignore special files */
156 		close(fd);
157 		return (403);
158 	}
159 
160 	media = media_find_config(env, srv_conf, path);
161 
162 	/* Only consider range requests for GET */
163 	if (desc->http_method == HTTP_METHOD_GET) {
164 		key.kv_key = "Range";
165 		r = kv_find(&desc->http_headers, &key);
166 		if (r != NULL)
167 			return (server_partial_file_request(env, clt, media,
168 			    fd, &st, r->kv_value));
169 	}
170 
171 	/* change path to path.gz if necessary. */
172 	if (srv_conf->flags & SRVFLAG_GZIP_STATIC) {
173 		struct http_descriptor	*req = clt->clt_descreq;
174 		struct http_descriptor	*resp = clt->clt_descresp;
175 		struct stat		 gzst;
176 		int			 gzfd;
177 		char			 gzpath[PATH_MAX];
178 
179 		/* check Accept-Encoding header */
180 		key.kv_key = "Accept-Encoding";
181 		r = kv_find(&req->http_headers, &key);
182 
183 		if (r != NULL && strstr(r->kv_value, "gzip") != NULL) {
184 			/* append ".gz" to path and check existence */
185 			ret = snprintf(gzpath, sizeof(gzpath), "%s.gz", path);
186 			if (ret < 0 || (size_t)ret >= sizeof(gzpath)) {
187 				close(fd);
188 				return (500);
189 			}
190 
191 			if ((gzfd = open(gzpath, O_RDONLY)) != -1) {
192 				/* .gz must be a file, and not older */
193 				if (fstat(gzfd, &gzst) != -1 &&
194 				    S_ISREG(gzst.st_mode) &&
195 				    timespeccmp(&gzst.st_mtim, &st.st_mtim,
196 				    >=)) {
197 					kv_add(&resp->http_headers,
198 					    "Content-Encoding", "gzip");
199 					/* Use original file timestamp */
200 					gzst.st_mtim = st.st_mtim;
201 					st = gzst;
202 					close(fd);
203 					fd = gzfd;
204 				} else {
205 					close(gzfd);
206 				}
207 			}
208 		}
209 	}
210 
211 	return (server_file_request(env, clt, media, fd, &st));
212 }
213 
214 int
server_file(struct httpd * env,struct client * clt)215 server_file(struct httpd *env, struct client *clt)
216 {
217 	struct http_descriptor	*desc = clt->clt_descreq;
218 	struct server_config	*srv_conf = clt->clt_srv_conf;
219 	char			 path[PATH_MAX];
220 	const char		*stripped, *errstr = NULL;
221 	int			 ret = 500;
222 
223 	if (srv_conf->flags & SRVFLAG_FCGI)
224 		return (server_fcgi(env, clt));
225 
226 	/* Request path is already canonicalized */
227 	stripped = server_root_strip(
228 	    desc->http_path_alias != NULL ?
229 	    desc->http_path_alias : desc->http_path,
230 	    srv_conf->strip);
231 	if ((size_t)snprintf(path, sizeof(path), "%s%s",
232 	    srv_conf->root, stripped) >= sizeof(path)) {
233 		errstr = desc->http_path;
234 		goto abort;
235 	}
236 
237 	/* Returns HTTP status code on error */
238 	if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) {
239 		errstr = desc->http_path_alias != NULL ?
240 		    desc->http_path_alias : desc->http_path;
241 		goto abort;
242 	}
243 
244 	return (ret);
245 
246  abort:
247 	if (errstr == NULL)
248 		errstr = strerror(errno);
249 	server_abort_http(clt, ret, errstr);
250 	return (-1);
251 }
252 
253 int
server_file_method(struct client * clt)254 server_file_method(struct client *clt)
255 {
256 	struct http_descriptor	*desc = clt->clt_descreq;
257 
258 	switch (desc->http_method) {
259 	case HTTP_METHOD_GET:
260 	case HTTP_METHOD_HEAD:
261 		return (0);
262 	default:
263 		/* Other methods are not allowed */
264 		errno = EACCES;
265 		return (405);
266 	}
267 	/* NOTREACHED */
268 }
269 
270 int
server_file_request(struct httpd * env,struct client * clt,struct media_type * media,int fd,const struct stat * st)271 server_file_request(struct httpd *env, struct client *clt, struct media_type
272     *media, int fd, const struct stat *st)
273 {
274 	struct server_config	*srv_conf = clt->clt_srv_conf;
275 	const char		*errstr = NULL;
276 	int			 ret, code = 500;
277 	size_t			 bufsiz;
278 
279 	if ((ret = server_file_method(clt)) != 0) {
280 		code = ret;
281 		goto abort;
282 	}
283 
284 	if ((ret = server_file_modified_since(clt->clt_descreq, &st->st_mtim))
285 	    != -1) {
286 		/* send the header without a body */
287 		if ((ret = server_response_http(clt, ret, media, -1,
288 		    MINIMUM(time(NULL), st->st_mtim.tv_sec))) == -1)
289 			goto fail;
290 		close(fd);
291 		goto done;
292 	}
293 
294 	ret = server_response_http(clt, 200, media, st->st_size,
295 	    MINIMUM(time(NULL), st->st_mtim.tv_sec));
296 	switch (ret) {
297 	case -1:
298 		goto fail;
299 	case 0:
300 		/* Connection is already finished */
301 		close(fd);
302 		goto done;
303 	default:
304 		break;
305 	}
306 
307 	clt->clt_fd = fd;
308 	if (clt->clt_srvbev != NULL)
309 		bufferevent_free(clt->clt_srvbev);
310 
311 	clt->clt_srvbev_throttled = 0;
312 	clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read,
313 	    server_write, server_file_error, clt);
314 	if (clt->clt_srvbev == NULL) {
315 		errstr = "failed to allocate file buffer event";
316 		goto fail;
317 	}
318 
319 	/* Adjust read watermark to the optimal file io size */
320 	bufsiz = MAXIMUM(st->st_blksize, 64 * 1024);
321 	bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0,
322 	    bufsiz);
323 
324 	bufferevent_settimeout(clt->clt_srvbev,
325 	    srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
326 	bufferevent_enable(clt->clt_srvbev, EV_READ);
327 	bufferevent_disable(clt->clt_bev, EV_READ);
328 
329  done:
330 	server_reset_http(clt);
331 	return (0);
332  fail:
333 	bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
334 	bufferevent_free(clt->clt_bev);
335 	clt->clt_bev = NULL;
336  abort:
337 	if (fd != -1)
338 		close(fd);
339 	if (errstr == NULL)
340 		errstr = strerror(errno);
341 	server_abort_http(clt, code, errstr);
342 	return (-1);
343 }
344 
345 int
server_partial_file_request(struct httpd * env,struct client * clt,struct media_type * media,int fd,const struct stat * st,char * range_str)346 server_partial_file_request(struct httpd *env, struct client *clt,
347     struct media_type *media, int fd, const struct stat *st, char *range_str)
348 {
349 	struct server_config	*srv_conf = clt->clt_srv_conf;
350 	struct http_descriptor	*resp = clt->clt_descresp;
351 	struct media_type	 multipart_media;
352 	struct range_data	*r = &clt->clt_ranges;
353 	struct range		*range;
354 	size_t			 content_length = 0, bufsiz;
355 	int			 code = 500, i, nranges, ret;
356 	char			 content_range[64];
357 	const char		*errstr = NULL;
358 
359 	if ((nranges = parse_ranges(clt, range_str, st->st_size)) < 1) {
360 		code = 416;
361 		(void)snprintf(content_range, sizeof(content_range),
362 		    "bytes */%lld", st->st_size);
363 		errstr = content_range;
364 		goto abort;
365 	}
366 
367 	r->range_media = media;
368 
369 	if (nranges == 1) {
370 		range = &r->range[0];
371 		(void)snprintf(content_range, sizeof(content_range),
372 		    "bytes %lld-%lld/%lld", range->start, range->end,
373 		    st->st_size);
374 		if (kv_add(&resp->http_headers, "Content-Range",
375 		    content_range) == NULL)
376 			goto abort;
377 
378 		range = &r->range[0];
379 		content_length += range->end - range->start + 1;
380 	} else {
381 		/* Add boundary, all parts will be handled by the callback */
382 		arc4random_buf(&clt->clt_boundary, sizeof(clt->clt_boundary));
383 
384 		/* Calculate Content-Length of the complete multipart body */
385 		for (i = 0; i < nranges; i++) {
386 			range = &r->range[i];
387 
388 			/* calculate Content-Length of the complete body */
389 			if ((ret = snprintf(NULL, 0,
390 			    "\r\n--%llu\r\n"
391 			    "Content-Type: %s/%s\r\n"
392 			    "Content-Range: bytes %lld-%lld/%lld\r\n\r\n",
393 			    clt->clt_boundary,
394 			    media->media_type, media->media_subtype,
395 			    range->start, range->end, st->st_size)) < 0)
396 				goto abort;
397 
398 			/* Add data length */
399 			content_length += ret + range->end - range->start + 1;
400 
401 		}
402 		if ((ret = snprintf(NULL, 0, "\r\n--%llu--\r\n",
403 		    clt->clt_boundary)) < 0)
404 			goto abort;
405 		content_length += ret;
406 
407 		/* prepare multipart/byteranges media type */
408 		(void)strlcpy(multipart_media.media_type, "multipart",
409 		    sizeof(multipart_media.media_type));
410 		(void)snprintf(multipart_media.media_subtype,
411 		    sizeof(multipart_media.media_subtype),
412 		    "byteranges; boundary=%llu", clt->clt_boundary);
413 		media = &multipart_media;
414 	}
415 
416 	/* Start with first range */
417 	r->range_toread = TOREAD_HTTP_RANGE;
418 
419 	ret = server_response_http(clt, 206, media, content_length,
420 	    MINIMUM(time(NULL), st->st_mtim.tv_sec));
421 	switch (ret) {
422 	case -1:
423 		goto fail;
424 	case 0:
425 		/* Connection is already finished */
426 		close(fd);
427 		goto done;
428 	default:
429 		break;
430 	}
431 
432 	clt->clt_fd = fd;
433 	if (clt->clt_srvbev != NULL)
434 		bufferevent_free(clt->clt_srvbev);
435 
436 	clt->clt_srvbev_throttled = 0;
437 	clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read_httprange,
438 	    server_write, server_file_error, clt);
439 	if (clt->clt_srvbev == NULL) {
440 		errstr = "failed to allocate file buffer event";
441 		goto fail;
442 	}
443 
444 	/* Adjust read watermark to the optimal file io size */
445 	bufsiz = MAXIMUM(st->st_blksize, 64 * 1024);
446 	bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0,
447 	    bufsiz);
448 
449 	bufferevent_settimeout(clt->clt_srvbev,
450 	    srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
451 	bufferevent_enable(clt->clt_srvbev, EV_READ);
452 	bufferevent_disable(clt->clt_bev, EV_READ);
453 
454  done:
455 	server_reset_http(clt);
456 	return (0);
457  fail:
458 	bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
459 	bufferevent_free(clt->clt_bev);
460 	clt->clt_bev = NULL;
461  abort:
462 	if (fd != -1)
463 		close(fd);
464 	if (errstr == NULL)
465 		errstr = strerror(errno);
466 	server_abort_http(clt, code, errstr);
467 	return (-1);
468 }
469 
470 /* ignore hidden files starting with a dot */
471 static int
select_visible(const struct dirent * dp)472 select_visible(const struct dirent *dp)
473 {
474     if (dp->d_name[0] == '.' &&
475 	!(dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
476 	    return 0;
477     else
478 	    return 1;
479 }
480 
481 int
server_file_index(struct httpd * env,struct client * clt,int fd,struct stat * st)482 server_file_index(struct httpd *env, struct client *clt, int fd,
483     struct stat *st)
484 {
485 	char			  path[PATH_MAX];
486 	char			  tmstr[21];
487 	struct http_descriptor	 *desc = clt->clt_descreq;
488 	struct server_config	 *srv_conf = clt->clt_srv_conf;
489 	struct dirent		**namelist, *dp;
490 	int			  namesize, i, ret, skip;
491 	int			  code = 500;
492 	struct evbuffer		 *evb = NULL;
493 	struct media_type	 *media;
494 	const char		 *stripped;
495 	char			 *escapeduri, *escapedhtml, *escapedpath;
496 	struct tm		  tm;
497 	time_t			  t, dir_mtime;
498 	char 			  human_size[FMT_SCALED_STRSIZE];
499 
500 	if ((ret = server_file_method(clt)) != 0) {
501 		code = ret;
502 		goto abort;
503 	}
504 
505 	/* Request path is already canonicalized */
506 	stripped = server_root_strip(desc->http_path, srv_conf->strip);
507 	if ((size_t)snprintf(path, sizeof(path), "%s%s",
508 	    srv_conf->root, stripped) >= sizeof(path))
509 		goto abort;
510 
511 	/* Save last modification time */
512 	dir_mtime = MINIMUM(time(NULL), st->st_mtim.tv_sec);
513 
514 	if ((evb = evbuffer_new()) == NULL)
515 		goto abort;
516 
517 	if ((escapedpath = escape_html(desc->http_path)) == NULL)
518 		goto abort;
519 
520 	/* Generate simple HTML index document */
521 	if (evbuffer_add_printf(evb,
522 	    "<!DOCTYPE html>\n"
523 	    "<html lang=\"en\">\n"
524 	    "<head>\n"
525 	    "<meta charset=\"utf-8\">\n"
526 	    "<title>Index of %s</title>\n"
527 	    "<style><!--\n%s--></style>\n"
528 	    "</head>\n"
529 	    "<body>\n"
530 	    "<h1>Index of %s</h1>\n"
531 	    "<table><thead>\n"
532 	    "<tr class=\"sort\"><th class=\"sorted\">Name</th>\n"
533 	    "    <th>Date</th><th>Size</th></tr>\n"
534 	    "</thead><tbody>\n",
535 	    escapedpath, css, escapedpath) == -1) {
536 		free(escapedpath);
537 		goto abort;
538 	}
539 
540 	free(escapedpath);
541 
542 	if ((namesize = scandirat(fd, ".", &namelist, select_visible,
543 	    alphasort)) == -1)
544 		goto abort;
545 
546 	/* Indicate failure but continue going through the list */
547 	skip = 0;
548 
549 	for (i = 0; i < namesize; i++) {
550 		struct stat subst;
551 
552 		dp = namelist[i];
553 
554 		if (skip ||
555 		    fstatat(fd, dp->d_name, &subst, 0) == -1) {
556 			free(dp);
557 			continue;
558 		}
559 
560 		t = subst.st_mtime;
561 		localtime_r(&t, &tm);
562 		strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
563 
564 		if ((escapeduri = url_encode(dp->d_name)) == NULL) {
565 			skip = 1;
566 			free(dp);
567 			continue;
568 		}
569 		if ((escapedhtml = escape_html(dp->d_name)) == NULL) {
570 			skip = 1;
571 			free(escapeduri);
572 			free(dp);
573 			continue;
574 		}
575 
576 		if (S_ISDIR(subst.st_mode)) {
577 			if (evbuffer_add_printf(evb,
578 			    "<tr class=\"dir\">"
579 			    "<td><a href=\"%s%s/\">%s/</a></td>\n"
580 			    "    <td data-o=\"%lld\">%s</td><td>%s</td></tr>\n",
581 			    strchr(escapeduri, ':') != NULL ? "./" : "",
582 			    escapeduri, escapedhtml,
583 			    (long long)t, tmstr, "-") == -1)
584 				skip = 1;
585 		} else if (S_ISREG(subst.st_mode)) {
586 			if ((fmt_scaled(subst.st_size, human_size) != 0) ||
587 			   (evbuffer_add_printf(evb,
588 			    "<tr><td><a href=\"%s%s\">%s</a></td>\n"
589 			    "    <td data-o=\"%lld\">%s</td>"
590 			    "<td title=\"%llu\">%s</td></tr>\n",
591 			    strchr(escapeduri, ':') != NULL ? "./" : "",
592 			    escapeduri, escapedhtml,
593 			    (long long)t, tmstr,
594 			    subst.st_size, human_size) == -1))
595 				skip = 1;
596 		}
597 		free(escapeduri);
598 		free(escapedhtml);
599 		free(dp);
600 	}
601 	free(namelist);
602 
603 	if (skip ||
604 	    evbuffer_add_printf(evb,
605 	    "</tbody></table>\n<script>\n"
606 	    "%s\n"
607 	    "</script>\n</body>\n</html>\n", js) == -1)
608 		goto abort;
609 
610 	close(fd);
611 	fd = -1;
612 
613 	media = media_find_config(env, srv_conf, "index.html");
614 	ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb),
615 	    dir_mtime);
616 	switch (ret) {
617 	case -1:
618 		goto fail;
619 	case 0:
620 		/* Connection is already finished */
621 		evbuffer_free(evb);
622 		goto done;
623 	default:
624 		break;
625 	}
626 
627 	if (server_bufferevent_write_buffer(clt, evb) == -1)
628 		goto fail;
629 	evbuffer_free(evb);
630 	evb = NULL;
631 
632 	bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
633 	if (clt->clt_persist)
634 		clt->clt_toread = TOREAD_HTTP_HEADER;
635 	else
636 		clt->clt_toread = TOREAD_HTTP_NONE;
637 	clt->clt_done = 0;
638 
639  done:
640 	server_reset_http(clt);
641 	return (0);
642  fail:
643 	bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
644 	bufferevent_free(clt->clt_bev);
645 	clt->clt_bev = NULL;
646  abort:
647 	if (fd != -1)
648 		close(fd);
649 	if (evb != NULL)
650 		evbuffer_free(evb);
651 	server_abort_http(clt, code, desc->http_path);
652 	return (-1);
653 }
654 
655 void
server_file_error(struct bufferevent * bev,short error,void * arg)656 server_file_error(struct bufferevent *bev, short error, void *arg)
657 {
658 	struct client		*clt = arg;
659 	struct evbuffer		*src, *dst;
660 
661 	if (error & EVBUFFER_TIMEOUT) {
662 		server_close(clt, "buffer event timeout");
663 		return;
664 	}
665 	if (error & EVBUFFER_ERROR) {
666 		if (errno == EFBIG) {
667 			bufferevent_enable(bev, EV_READ);
668 			return;
669 		}
670 		server_close(clt, "buffer event error");
671 		return;
672 	}
673 	if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
674 		bufferevent_disable(bev, EV_READ|EV_WRITE);
675 
676 		clt->clt_done = 1;
677 
678 		src = EVBUFFER_INPUT(clt->clt_bev);
679 
680 		/* Close the connection if a previous pipeline is empty */
681 		if (clt->clt_pipelining && EVBUFFER_LENGTH(src) == 0)
682 			clt->clt_persist = 0;
683 
684 		if (clt->clt_persist) {
685 			/* Close input file and wait for next HTTP request */
686 			if (clt->clt_fd != -1)
687 				close(clt->clt_fd);
688 			clt->clt_fd = -1;
689 			clt->clt_toread = TOREAD_HTTP_HEADER;
690 			server_reset_http(clt);
691 			bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
692 
693 			/* Start pipelining if the buffer is not empty */
694 			if (EVBUFFER_LENGTH(src)) {
695 				clt->clt_pipelining++;
696 				server_read_http(clt->clt_bev, arg);
697 			}
698 			return;
699 		}
700 
701 		dst = EVBUFFER_OUTPUT(clt->clt_bev);
702 		if (EVBUFFER_LENGTH(dst)) {
703 			/* Finish writing all data first */
704 			bufferevent_enable(clt->clt_bev, EV_WRITE);
705 			return;
706 		}
707 
708 		server_close(clt, "done");
709 		return;
710 	}
711 	server_close(clt, "unknown event error");
712 	return;
713 }
714 
715 int
server_file_modified_since(struct http_descriptor * desc,const struct timespec * mtim)716 server_file_modified_since(struct http_descriptor *desc, const struct timespec
717     *mtim)
718 {
719 	struct kv	 key, *since;
720 	struct tm	 tm;
721 
722 	key.kv_key = "If-Modified-Since";
723 	if ((since = kv_find(&desc->http_headers, &key)) != NULL &&
724 	    since->kv_value != NULL) {
725 		memset(&tm, 0, sizeof(struct tm));
726 
727 		/*
728 		 * Return "Not modified" if the file hasn't changed since
729 		 * the requested time.
730 		 */
731 		if (strptime(since->kv_value,
732 		    "%a, %d %h %Y %T %Z", &tm) != NULL &&
733 		    timegm(&tm) >= mtim->tv_sec)
734 			return (304);
735 	}
736 
737 	return (-1);
738 }
739 
740 int
parse_ranges(struct client * clt,char * str,size_t file_sz)741 parse_ranges(struct client *clt, char *str, size_t file_sz)
742 {
743 	int			 i = 0;
744 	char			*p, *q;
745 	struct range_data	*r = &clt->clt_ranges;
746 
747 	memset(r, 0, sizeof(*r));
748 
749 	/* Extract range unit */
750 	if ((p = strchr(str, '=')) == NULL)
751 		return (-1);
752 
753 	*p++ = '\0';
754 	/* Check if it's a bytes range spec */
755 	if (strcmp(str, "bytes") != 0)
756 		return (-1);
757 
758 	while ((q = strchr(p, ',')) != NULL) {
759 		*q++ = '\0';
760 
761 		/* Extract start and end positions */
762 		if (parse_range_spec(p, file_sz, &r->range[i]) == 0)
763 			continue;
764 
765 		i++;
766 		if (i == SERVER_MAX_RANGES)
767 			return (-1);
768 
769 		p = q;
770 	}
771 
772 	if (parse_range_spec(p, file_sz, &r->range[i]) != 0)
773 		i++;
774 
775 	r->range_total = file_sz;
776 	r->range_count = i;
777 	return (i);
778 }
779 
780 int
parse_range_spec(char * str,size_t size,struct range * r)781 parse_range_spec(char *str, size_t size, struct range *r)
782 {
783 	size_t		 start_str_len, end_str_len;
784 	char		*p, *start_str, *end_str;
785 	const char	*errstr;
786 
787 	if ((p = strchr(str, '-')) == NULL)
788 		return (0);
789 
790 	*p++ = '\0';
791 	start_str = str;
792 	end_str = p;
793 	start_str_len = strlen(start_str);
794 	end_str_len = strlen(end_str);
795 
796 	/* Either 'start' or 'end' is optional but not both */
797 	if ((start_str_len == 0) && (end_str_len == 0))
798 		return (0);
799 
800 	if (end_str_len) {
801 		r->end = strtonum(end_str, 0, LLONG_MAX, &errstr);
802 		if (errstr)
803 			return (0);
804 
805 		if ((size_t)r->end >= size)
806 			r->end = size - 1;
807 	} else
808 		r->end = size - 1;
809 
810 	if (start_str_len) {
811 		r->start = strtonum(start_str, 0, LLONG_MAX, &errstr);
812 		if (errstr)
813 			return (0);
814 
815 		if ((size_t)r->start >= size)
816 			return (0);
817 	} else {
818 		r->start = size - r->end;
819 		r->end = size - 1;
820 	}
821 
822 	if (r->end < r->start)
823 		return (0);
824 
825 	return (1);
826 }
827