xref: /openbsd/usr.sbin/httpd/server_file.c (revision cecf84d4)
1 /*	$OpenBSD: server_file.c,v 1.54 2015/05/05 11:10:13 florian Exp $	*/
2 
3 /*
4  * Copyright (c) 2006 - 2015 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 
34 #include "httpd.h"
35 #include "http.h"
36 
37 #define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
38 #define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
39 #define MAX_RANGES	4
40 
41 struct range {
42 	off_t	start;
43 	off_t	end;
44 };
45 
46 int	 	 server_file_access(struct httpd *, struct client *,
47 		    char *, size_t);
48 int	 	 server_file_request(struct httpd *, struct client *,
49 		    char *, struct stat *);
50 int	 	 server_partial_file_request(struct httpd *, struct client *,
51 		    char *, struct stat *, char *);
52 int	 	 server_file_index(struct httpd *, struct client *,
53 		    struct stat *);
54 int		 server_file_modified_since(struct http_descriptor *,
55 		    struct stat *);
56 int	 	 server_file_method(struct client *);
57 int	 	 parse_range_spec(char *, size_t, struct range *);
58 struct range 	*parse_range(char *, size_t, int *);
59 int	 	 buffer_add_range(int, struct evbuffer *, struct range *);
60 
61 int
62 server_file_access(struct httpd *env, struct client *clt,
63     char *path, size_t len)
64 {
65 	struct http_descriptor	*desc = clt->clt_descreq;
66 	struct server_config	*srv_conf = clt->clt_srv_conf;
67 	struct stat		 st;
68 	struct kv		*r, key;
69 	char			*newpath;
70 	int			 ret;
71 
72 	errno = 0;
73 
74 	if (access(path, R_OK) == -1) {
75 		goto fail;
76 	} else if (stat(path, &st) == -1) {
77 		goto fail;
78 	} else if (S_ISDIR(st.st_mode)) {
79 		/* Deny access if directory indexing is disabled */
80 		if (srv_conf->flags & SRVFLAG_NO_INDEX) {
81 			errno = EACCES;
82 			goto fail;
83 		}
84 
85 		if (desc->http_path_alias != NULL) {
86 			/* Recursion - the index "file" is a directory? */
87 			errno = EINVAL;
88 			goto fail;
89 		}
90 
91 		/* Redirect to path with trailing "/" */
92 		if (path[strlen(path) - 1] != '/') {
93 			if (asprintf(&newpath, "http%s://%s%s/",
94 			    srv_conf->flags & SRVFLAG_TLS ? "s" : "",
95 			    desc->http_host, desc->http_path) == -1)
96 				return (500);
97 			/* Path alias will be used for the redirection */
98 			desc->http_path_alias = newpath;
99 
100 			/* Indicate that the file has been moved */
101 			return (301);
102 		}
103 
104 		/* Append the default index file to the location */
105 		if (asprintf(&newpath, "%s%s", desc->http_path,
106 		    srv_conf->index) == -1)
107 			return (500);
108 		desc->http_path_alias = newpath;
109 		if (server_getlocation(clt, newpath) != srv_conf) {
110 			/* The location has changed */
111 			return (server_file(env, clt));
112 		}
113 
114 		/* Otherwise append the default index file to the path */
115 		if (strlcat(path, srv_conf->index, len) >= len) {
116 			errno = EACCES;
117 			goto fail;
118 		}
119 
120 		ret = server_file_access(env, clt, path, len);
121 		if (ret == 404) {
122 			/*
123 			 * Index file not found; fail if auto-indexing is
124 			 * not enabled, otherwise return success but
125 			 * indicate directory with S_ISDIR of the previous
126 			 * stat.
127 			 */
128 			if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) {
129 				errno = EACCES;
130 				goto fail;
131 			}
132 
133 			return (server_file_index(env, clt, &st));
134 		}
135 		return (ret);
136 	} else if (!S_ISREG(st.st_mode)) {
137 		/* Don't follow symlinks and ignore special files */
138 		errno = EACCES;
139 		goto fail;
140 	}
141 
142 	key.kv_key = "Range";
143 	r = kv_find(&desc->http_headers, &key);
144 	if (r != NULL)
145 		return (server_partial_file_request(env, clt, path, &st,
146 		    r->kv_value));
147 	else
148 		return (server_file_request(env, clt, path, &st));
149 
150  fail:
151 	switch (errno) {
152 	case ENOENT:
153 	case ENOTDIR:
154 		return (404);
155 	case EACCES:
156 		return (403);
157 	default:
158 		return (500);
159 	}
160 
161 	/* NOTREACHED */
162 }
163 
164 int
165 server_file(struct httpd *env, struct client *clt)
166 {
167 	struct http_descriptor	*desc = clt->clt_descreq;
168 	struct server_config	*srv_conf = clt->clt_srv_conf;
169 	char			 path[PATH_MAX];
170 	const char		*stripped, *errstr = NULL;
171 	int			 ret = 500;
172 
173 	if (srv_conf->flags & SRVFLAG_FCGI)
174 		return (server_fcgi(env, clt));
175 
176 	/* Request path is already canonicalized */
177 	stripped = server_root_strip(
178 	    desc->http_path_alias != NULL ?
179 	    desc->http_path_alias : desc->http_path,
180 	    srv_conf->strip);
181 	if ((size_t)snprintf(path, sizeof(path), "%s%s",
182 	    srv_conf->root, stripped) >= sizeof(path)) {
183 		errstr = desc->http_path;
184 		goto abort;
185 	}
186 
187 	/* Returns HTTP status code on error */
188 	if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) {
189 		errstr = desc->http_path_alias != NULL ?
190 		    desc->http_path_alias : desc->http_path;
191 		goto abort;
192 	}
193 
194 	return (ret);
195 
196  abort:
197 	if (errstr == NULL)
198 		errstr = strerror(errno);
199 	server_abort_http(clt, ret, errstr);
200 	return (-1);
201 }
202 
203 int
204 server_file_method(struct client *clt)
205 {
206 	struct http_descriptor	*desc = clt->clt_descreq;
207 
208 	switch (desc->http_method) {
209 	case HTTP_METHOD_GET:
210 	case HTTP_METHOD_HEAD:
211 		return (0);
212 	default:
213 		/* Other methods are not allowed */
214 		errno = EACCES;
215 		return (405);
216 	}
217 	/* NOTREACHED */
218 }
219 
220 int
221 server_file_request(struct httpd *env, struct client *clt, char *path,
222     struct stat *st)
223 {
224 	struct server_config	*srv_conf = clt->clt_srv_conf;
225 	struct media_type	*media;
226 	const char		*errstr = NULL;
227 	int			 fd = -1, ret, code = 500;
228 
229 	if ((ret = server_file_method(clt)) != 0) {
230 		code = ret;
231 		goto abort;
232 	}
233 
234 	if ((ret = server_file_modified_since(clt->clt_descreq, st)) != -1)
235 		return ret;
236 
237 	/* Now open the file, should be readable or we have another problem */
238 	if ((fd = open(path, O_RDONLY)) == -1)
239 		goto abort;
240 
241 	media = media_find(env->sc_mediatypes, path);
242 	ret = server_response_http(clt, 200, media, st->st_size,
243 	    MINIMUM(time(NULL), st->st_mtim.tv_sec));
244 	switch (ret) {
245 	case -1:
246 		goto fail;
247 	case 0:
248 		/* Connection is already finished */
249 		close(fd);
250 		goto done;
251 	default:
252 		break;
253 	}
254 
255 	clt->clt_fd = fd;
256 	if (clt->clt_srvbev != NULL)
257 		bufferevent_free(clt->clt_srvbev);
258 
259 	clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read,
260 	    server_write, server_file_error, clt);
261 	if (clt->clt_srvbev == NULL) {
262 		errstr = "failed to allocate file buffer event";
263 		goto fail;
264 	}
265 
266 	/* Adjust read watermark to the socket output buffer size */
267 	bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0,
268 	    clt->clt_sndbufsiz);
269 
270 	bufferevent_settimeout(clt->clt_srvbev,
271 	    srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
272 	bufferevent_enable(clt->clt_srvbev, EV_READ);
273 	bufferevent_disable(clt->clt_bev, EV_READ);
274 
275  done:
276 	server_reset_http(clt);
277 	return (0);
278  fail:
279 	bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
280 	bufferevent_free(clt->clt_bev);
281 	clt->clt_bev = NULL;
282  abort:
283 	if (errstr == NULL)
284 		errstr = strerror(errno);
285 	server_abort_http(clt, code, errstr);
286 	return (-1);
287 }
288 
289 int
290 server_partial_file_request(struct httpd *env, struct client *clt, char *path,
291     struct stat *st, char *range_str)
292 {
293 	struct http_descriptor	*resp = clt->clt_descresp;
294 	struct http_descriptor	*desc = clt->clt_descreq;
295 	struct media_type	*media, multipart_media;
296 	struct range		*range;
297 	struct evbuffer		*evb = NULL;
298 	size_t			 content_length;
299 	int		 	 code = 500, fd = -1, i, nranges, ret;
300 	uint32_t		 boundary;
301 	char		 	 content_range[64];
302 	const char		*errstr = NULL;
303 
304 	/* Ignore range request for methods other than GET */
305 	if (desc->http_method != HTTP_METHOD_GET)
306 		return server_file_request(env, clt, path, st);
307 
308 	if ((range = parse_range(range_str, st->st_size, &nranges)) == NULL) {
309 		code = 416;
310 		(void)snprintf(content_range, sizeof(content_range),
311 		    "bytes */%lld", st->st_size);
312 		errstr = content_range;
313 		goto abort;
314 	}
315 
316 	/* Now open the file, should be readable or we have another problem */
317 	if ((fd = open(path, O_RDONLY)) == -1)
318 		goto abort;
319 
320 	media = media_find(env->sc_mediatypes, path);
321 	if ((evb = evbuffer_new()) == NULL) {
322 		errstr = "failed to allocate file buffer";
323 		goto abort;
324 	}
325 
326 	if (nranges == 1) {
327 		(void)snprintf(content_range, sizeof(content_range),
328 	    	    "bytes %lld-%lld/%lld", range->start, range->end,
329 		    st->st_size);
330 		if (kv_add(&resp->http_headers, "Content-Range",
331 		    content_range) == NULL)
332 			goto abort;
333 
334 		content_length = range->end - range->start + 1;
335 		if (buffer_add_range(fd, evb, range) == 0)
336 			goto abort;
337 
338 	} else {
339 		content_length = 0;
340 		boundary = arc4random();
341 		/* Generate a multipart payload of byteranges */
342 		while (nranges--) {
343 			if ((i = evbuffer_add_printf(evb, "\r\n--%ud\r\n",
344 			    boundary)) == -1)
345 				goto abort;
346 
347 			content_length += i;
348 			if ((i = evbuffer_add_printf(evb,
349 			    "Content-Type: %s/%s\r\n",
350 	    		    media == NULL ? "application" : media->media_type,
351 	    		    media == NULL ?
352 			    "octet-stream" : media->media_subtype)) == -1)
353 				goto abort;
354 
355 			content_length += i;
356 			if ((i = evbuffer_add_printf(evb,
357 			    "Content-Range: bytes %lld-%lld/%lld\r\n\r\n",
358 			    range->start, range->end, st->st_size)) == -1)
359 				goto abort;
360 
361 			content_length += i;
362 			if (buffer_add_range(fd, evb, range) == 0)
363 				goto abort;
364 
365 			content_length += range->end - range->start + 1;
366 			range++;
367 		}
368 
369 		if ((i = evbuffer_add_printf(evb, "\r\n--%ud--\r\n",
370 		    boundary)) == -1)
371 			goto abort;
372 
373 		content_length += i;
374 
375 		/* prepare multipart/byteranges media type */
376 		(void)strlcpy(multipart_media.media_type, "multipart",
377 	    	    sizeof(multipart_media.media_type));
378 		(void)snprintf(multipart_media.media_subtype,
379 	    	    sizeof(multipart_media.media_subtype),
380 	    	    "byteranges; boundary=%ud", boundary);
381 		media = &multipart_media;
382 	}
383 
384 	ret = server_response_http(clt, 206, media, content_length,
385 	    MINIMUM(time(NULL), st->st_mtim.tv_sec));
386 	switch (ret) {
387 	case -1:
388 		goto fail;
389 	case 0:
390 		/* Connection is already finished */
391 		close(fd);
392 		evbuffer_free(evb);
393 		evb = NULL;
394 		goto done;
395 	default:
396 		break;
397 	}
398 
399 	if (server_bufferevent_write_buffer(clt, evb) == -1)
400 		goto fail;
401 
402 	evbuffer_free(evb);
403 	evb = NULL;
404 
405 	bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
406 	if (clt->clt_persist)
407 		clt->clt_toread = TOREAD_HTTP_HEADER;
408 	else
409 		clt->clt_toread = TOREAD_HTTP_NONE;
410 	clt->clt_done = 0;
411 
412  done:
413 	server_reset_http(clt);
414 	return (0);
415  fail:
416 	bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
417 	bufferevent_free(clt->clt_bev);
418 	clt->clt_bev = NULL;
419  abort:
420 	if (errstr == NULL)
421 		errstr = strerror(errno);
422 	server_abort_http(clt, code, errstr);
423 	return (-1);
424 }
425 
426 int
427 server_file_index(struct httpd *env, struct client *clt, struct stat *st)
428 {
429 	char			  path[PATH_MAX];
430 	char			  tmstr[21];
431 	struct http_descriptor	 *desc = clt->clt_descreq;
432 	struct server_config	 *srv_conf = clt->clt_srv_conf;
433 	struct dirent		**namelist, *dp;
434 	int			  namesize, i, ret, fd = -1, namewidth, skip;
435 	int			  code = 500;
436 	struct evbuffer		 *evb = NULL;
437 	struct media_type	 *media;
438 	const char		 *stripped, *style;
439 	char			 *escapeduri, *escapedhtml, *escapedpath;
440 	struct tm		  tm;
441 	time_t			  t, dir_mtime;
442 
443 	if ((ret = server_file_method(clt)) != 0) {
444 		code = ret;
445 		goto abort;
446 	}
447 
448 	/* Request path is already canonicalized */
449 	stripped = server_root_strip(desc->http_path, srv_conf->strip);
450 	if ((size_t)snprintf(path, sizeof(path), "%s%s",
451 	    srv_conf->root, stripped) >= sizeof(path))
452 		goto abort;
453 
454 	/* Now open the file, should be readable or we have another problem */
455 	if ((fd = open(path, O_RDONLY)) == -1)
456 		goto abort;
457 
458 	/* Save last modification time */
459 	dir_mtime = MINIMUM(time(NULL), st->st_mtim.tv_sec);
460 
461 	if ((evb = evbuffer_new()) == NULL)
462 		goto abort;
463 
464 	if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1)
465 		goto abort;
466 
467 	/* Indicate failure but continue going through the list */
468 	skip = 0;
469 
470 	if ((escapedpath = escape_html(desc->http_path)) == NULL)
471 		goto fail;
472 
473 	/* A CSS stylesheet allows minimal customization by the user */
474 	style = "body { background-color: white; color: black; font-family: "
475 	    "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n";
476 	/* Generate simple HTML index document */
477 	if (evbuffer_add_printf(evb,
478 	    "<!DOCTYPE html>\n"
479 	    "<html>\n"
480 	    "<head>\n"
481 	    "<title>Index of %s</title>\n"
482 	    "<style type=\"text/css\"><!--\n%s\n--></style>\n"
483 	    "</head>\n"
484 	    "<body>\n"
485 	    "<h1>Index of %s</h1>\n"
486 	    "<hr>\n<pre>\n",
487 	    escapedpath, style, escapedpath) == -1)
488 		skip = 1;
489 
490 	free(escapedpath);
491 
492 	for (i = 0; i < namesize; i++) {
493 		dp = namelist[i];
494 
495 		if (skip ||
496 		    fstatat(fd, dp->d_name, st, 0) == -1) {
497 			free(dp);
498 			continue;
499 		}
500 
501 		t = st->st_mtime;
502 		localtime_r(&t, &tm);
503 		strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
504 		namewidth = 51 - strlen(dp->d_name);
505 
506 		if ((escapeduri = url_encode(dp->d_name)) == NULL)
507 			goto fail;
508 		if ((escapedhtml = escape_html(dp->d_name)) == NULL)
509 			goto fail;
510 
511 		if (dp->d_name[0] == '.' &&
512 		    !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) {
513 			/* ignore hidden files starting with a dot */
514 		} else if (S_ISDIR(st->st_mode)) {
515 			namewidth -= 1; /* trailing slash */
516 			if (evbuffer_add_printf(evb,
517 			    "<a href=\"%s%s/\">%s/</a>%*s%s%20s\n",
518 			    strchr(escapeduri, ':') != NULL ? "./" : "",
519 			    escapeduri, escapedhtml,
520 			    MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1)
521 				skip = 1;
522 		} else if (S_ISREG(st->st_mode)) {
523 			if (evbuffer_add_printf(evb,
524 			    "<a href=\"%s%s\">%s</a>%*s%s%20llu\n",
525 			    strchr(escapeduri, ':') != NULL ? "./" : "",
526 			    escapeduri, escapedhtml,
527 			    MAXIMUM(namewidth, 0), " ",
528 			    tmstr, st->st_size) == -1)
529 				skip = 1;
530 		}
531 		free(escapeduri);
532 		free(escapedhtml);
533 		free(dp);
534 	}
535 	free(namelist);
536 
537 	if (skip ||
538 	    evbuffer_add_printf(evb,
539 	    "</pre>\n<hr>\n</body>\n</html>\n") == -1)
540 		goto abort;
541 
542 	close(fd);
543 	fd = -1;
544 
545 	media = media_find(env->sc_mediatypes, "index.html");
546 	ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb),
547 	    dir_mtime);
548 	switch (ret) {
549 	case -1:
550 		goto fail;
551 	case 0:
552 		/* Connection is already finished */
553 		evbuffer_free(evb);
554 		goto done;
555 	default:
556 		break;
557 	}
558 
559 	if (server_bufferevent_write_buffer(clt, evb) == -1)
560 		goto fail;
561 	evbuffer_free(evb);
562 	evb = NULL;
563 
564 	bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
565 	if (clt->clt_persist)
566 		clt->clt_toread = TOREAD_HTTP_HEADER;
567 	else
568 		clt->clt_toread = TOREAD_HTTP_NONE;
569 	clt->clt_done = 0;
570 
571  done:
572 	server_reset_http(clt);
573 	return (0);
574  fail:
575 	bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
576 	bufferevent_free(clt->clt_bev);
577 	clt->clt_bev = NULL;
578  abort:
579 	if (fd != -1)
580 		close(fd);
581 	if (evb != NULL)
582 		evbuffer_free(evb);
583 	server_abort_http(clt, code, desc->http_path);
584 	return (-1);
585 }
586 
587 void
588 server_file_error(struct bufferevent *bev, short error, void *arg)
589 {
590 	struct client		*clt = arg;
591 	struct evbuffer		*dst;
592 
593 	if (error & EVBUFFER_TIMEOUT) {
594 		server_close(clt, "buffer event timeout");
595 		return;
596 	}
597 	if (error & EVBUFFER_ERROR) {
598 		if (errno == EFBIG) {
599 			bufferevent_enable(bev, EV_READ);
600 			return;
601 		}
602 		server_close(clt, "buffer event error");
603 		return;
604 	}
605 	if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
606 		bufferevent_disable(bev, EV_READ|EV_WRITE);
607 
608 		clt->clt_done = 1;
609 
610 		if (clt->clt_persist) {
611 			/* Close input file and wait for next HTTP request */
612 			if (clt->clt_fd != -1)
613 				close(clt->clt_fd);
614 			clt->clt_fd = -1;
615 			clt->clt_toread = TOREAD_HTTP_HEADER;
616 			server_reset_http(clt);
617 			bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
618 			return;
619 		}
620 
621 		dst = EVBUFFER_OUTPUT(clt->clt_bev);
622 		if (EVBUFFER_LENGTH(dst)) {
623 			/* Finish writing all data first */
624 			bufferevent_enable(clt->clt_bev, EV_WRITE);
625 			return;
626 		}
627 
628 		server_close(clt, "done");
629 		return;
630 	}
631 	server_close(clt, "unknown event error");
632 	return;
633 }
634 
635 int
636 server_file_modified_since(struct http_descriptor * desc, struct stat * st)
637 {
638 	struct kv		 key, *since;
639 	struct tm		 tm;
640 
641 	memset(&tm, 0, sizeof(struct tm));
642 
643 	key.kv_key = "If-Modified-Since";
644 	if ((since = kv_find(&desc->http_headers, &key)) != NULL &&
645 	    since->kv_value != NULL) {
646 		if (strptime(since->kv_value, "%a, %d %h %Y %T %Z", &tm) !=
647 		    NULL && timegm(&tm) >= st->st_mtim.tv_sec) {
648 			return (304);
649 		}
650 	}
651 
652 	return (-1);
653 }
654 
655 struct range *
656 parse_range(char *str, size_t file_sz, int *nranges)
657 {
658 	static struct range	 ranges[MAX_RANGES];
659 	int	 	 	 i = 0;
660 	char			*p, *q;
661 
662 	/* Extract range unit */
663 	if ((p = strchr(str, '=')) == NULL)
664 		return (NULL);
665 
666 	*p++ = '\0';
667 	/* Check if it's a bytes range spec */
668 	if (strcmp(str, "bytes") != 0)
669 		return (NULL);
670 
671 	while ((q = strchr(p, ',')) != NULL) {
672 		*q++ = '\0';
673 
674 		/* Extract start and end positions */
675 		if (parse_range_spec(p, file_sz, &ranges[i]) == 0)
676 			continue;
677 
678 		i++;
679 		if (i == MAX_RANGES)
680 			return (NULL);
681 
682 		p = q;
683 	}
684 
685 	if (parse_range_spec(p, file_sz, &ranges[i]) != 0)
686 		i++;
687 
688 	*nranges = i;
689 	return (i ? ranges : NULL);
690 }
691 
692 int
693 parse_range_spec(char *str, size_t size, struct range *r)
694 {
695 	size_t	 	 start_str_len, end_str_len;
696 	char		*p, *start_str, *end_str;
697 	const char	*errstr;
698 
699 	if ((p = strchr(str, '-')) == NULL)
700 		return (0);
701 
702 	*p++ = '\0';
703 	start_str = str;
704 	end_str = p;
705 	start_str_len = strlen(start_str);
706 	end_str_len = strlen(end_str);
707 
708 	/* Either 'start' or 'end' is optional but not both */
709 	if ((start_str_len == 0) && (end_str_len == 0))
710 		return (0);
711 
712 	if (end_str_len) {
713 		r->end = strtonum(end_str, 0, LLONG_MAX, &errstr);
714 		if (errstr)
715 			return (0);
716 
717 		if ((size_t)r->end >= size)
718 			r->end = size - 1;
719 	} else
720 		r->end = size - 1;
721 
722 	if (start_str_len) {
723 		r->start = strtonum(start_str, 0, LLONG_MAX, &errstr);
724 		if (errstr)
725 			return (0);
726 
727 		if ((size_t)r->start >= size)
728 			return (0);
729 	} else {
730 		r->start = size - r->end;
731 		r->end = size - 1;
732 	}
733 
734 	if (r->end < r->start)
735 		return (0);
736 
737 	return (1);
738 }
739 
740 int
741 buffer_add_range(int fd, struct evbuffer *evb, struct range *range)
742 {
743 	char	buf[BUFSIZ];
744 	size_t	n, range_sz;
745 	ssize_t	nread;
746 
747 	if (lseek(fd, range->start, SEEK_SET) == -1)
748 		return (0);
749 
750 	range_sz = range->end - range->start + 1;
751 	while (range_sz) {
752 		n = MINIMUM(range_sz, sizeof(buf));
753 		if ((nread = read(fd, buf, n)) == -1)
754 			return (0);
755 
756 		evbuffer_add(evb, buf, nread);
757 		range_sz -= nread;
758 	}
759 
760 	return (1);
761 }
762