GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/httpd/server_file.c Lines: 0 352 0.0 %
Date: 2017-11-07 Branches: 0 203 0.0 %

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