From: florian Date: Sun, 3 May 2015 18:39:58 +0000 (+0000) Subject: Implement byte ranges. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=b0faf28ca8728778206d10bc1861222f488dd6d8;p=openbsd Implement byte ranges. From Sunil Nimmagadda OK benno@ --- diff --git a/usr.sbin/httpd/server_file.c b/usr.sbin/httpd/server_file.c index 3580bbbd323..2b75c574e01 100644 --- a/usr.sbin/httpd/server_file.c +++ b/usr.sbin/httpd/server_file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_file.c,v 1.52 2015/04/25 14:40:35 florian Exp $ */ +/* $OpenBSD: server_file.c,v 1.53 2015/05/03 18:39:58 florian Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -36,12 +36,25 @@ #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) - -int server_file_access(struct httpd *, struct client *, char *, size_t); -int server_file_request(struct httpd *, struct client *, char *, - struct stat *); -int server_file_index(struct httpd *, struct client *, struct stat *); -int server_file_method(struct client *); +#define MAX_RANGES 4 + +struct range { + off_t start; + off_t end; +}; + +int server_file_access(struct httpd *, struct client *, + char *, size_t); +int server_file_request(struct httpd *, struct client *, + char *, struct stat *); +int server_partial_file_request(struct httpd *, struct client *, + char *, struct stat *, char *); +int server_file_index(struct httpd *, struct client *, + struct stat *); +int server_file_method(struct client *); +int parse_range_spec(char *, size_t, struct range *); +struct range *parse_range(char *, size_t, int *); +int buffer_add_range(int, struct evbuffer *, struct range *); int server_file_access(struct httpd *env, struct client *clt, @@ -50,6 +63,7 @@ server_file_access(struct httpd *env, struct client *clt, struct http_descriptor *desc = clt->clt_descreq; struct server_config *srv_conf = clt->clt_srv_conf; struct stat st; + struct kv *r, key; char *newpath; int ret; @@ -123,7 +137,13 @@ server_file_access(struct httpd *env, struct client *clt, goto fail; } - return (server_file_request(env, clt, path, &st)); + key.kv_key = "Range"; + r = kv_find(&desc->http_headers, &key); + if (r != NULL) + return (server_partial_file_request(env, clt, path, &st, + r->kv_value)); + else + return (server_file_request(env, clt, path, &st)); fail: switch (errno) { @@ -261,6 +281,143 @@ server_file_request(struct httpd *env, struct client *clt, char *path, return (-1); } +int +server_partial_file_request(struct httpd *env, struct client *clt, char *path, + struct stat *st, char *range_str) +{ + struct http_descriptor *resp = clt->clt_descresp; + struct http_descriptor *desc = clt->clt_descreq; + struct media_type *media, multipart_media; + struct range *range; + struct evbuffer *evb = NULL; + size_t content_length; + int code = 500, fd = -1, i, nranges, ret; + uint32_t boundary; + char content_range[64]; + const char *errstr = NULL; + + /* Ignore range request for methods other than GET */ + if (desc->http_method != HTTP_METHOD_GET) + return server_file_request(env, clt, path, st); + + if ((range = parse_range(range_str, st->st_size, &nranges)) == NULL) { + code = 416; + (void)snprintf(content_range, sizeof(content_range), + "bytes */%lld", st->st_size); + errstr = content_range; + goto abort; + } + + /* Now open the file, should be readable or we have another problem */ + if ((fd = open(path, O_RDONLY)) == -1) + goto abort; + + media = media_find(env->sc_mediatypes, path); + if ((evb = evbuffer_new()) == NULL) { + errstr = "failed to allocate file buffer"; + goto abort; + } + + if (nranges == 1) { + (void)snprintf(content_range, sizeof(content_range), + "bytes %lld-%lld/%lld", range->start, range->end, + st->st_size); + if (kv_add(&resp->http_headers, "Content-Range", + content_range) == NULL) + goto abort; + + content_length = range->end - range->start + 1; + if (buffer_add_range(fd, evb, range) == 0) + goto abort; + + } else { + content_length = 0; + boundary = arc4random(); + /* Generate a multipart payload of byteranges */ + while (nranges--) { + if ((i = evbuffer_add_printf(evb, "\r\n--%ud\r\n", + boundary)) == -1) + goto abort; + + content_length += i; + if ((i = evbuffer_add_printf(evb, + "Content-Type: %s/%s\r\n", + media == NULL ? "application" : media->media_type, + media == NULL ? + "octet-stream" : media->media_subtype)) == -1) + goto abort; + + content_length += i; + if ((i = evbuffer_add_printf(evb, + "Content-Range: bytes %lld-%lld/%lld\r\n\r\n", + range->start, range->end, st->st_size)) == -1) + goto abort; + + content_length += i; + if (buffer_add_range(fd, evb, range) == 0) + goto abort; + + content_length += range->end - range->start + 1; + range++; + } + + if ((i = evbuffer_add_printf(evb, "\r\n--%ud--\r\n", + boundary)) == -1) + goto abort; + + content_length += i; + + /* prepare multipart/byteranges media type */ + (void)strlcpy(multipart_media.media_type, "multipart", + sizeof(multipart_media.media_type)); + (void)snprintf(multipart_media.media_subtype, + sizeof(multipart_media.media_subtype), + "byteranges; boundary=%ud", boundary); + media = &multipart_media; + } + + ret = server_response_http(clt, 206, media, content_length, + MINIMUM(time(NULL), st->st_mtim.tv_sec)); + switch (ret) { + case -1: + goto fail; + case 0: + /* Connection is already finished */ + close(fd); + evbuffer_free(evb); + evb = NULL; + goto done; + default: + break; + } + + if (server_bufferevent_write_buffer(clt, evb) == -1) + goto fail; + + evbuffer_free(evb); + evb = NULL; + + bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); + if (clt->clt_persist) + clt->clt_toread = TOREAD_HTTP_HEADER; + else + clt->clt_toread = TOREAD_HTTP_NONE; + clt->clt_done = 0; + + done: + server_reset_http(clt); + return (0); + fail: + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + bufferevent_free(clt->clt_bev); + clt->clt_bev = NULL; + abort: + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, code, errstr); + return (-1); +} + int server_file_index(struct httpd *env, struct client *clt, struct stat *st) { @@ -469,3 +626,112 @@ server_file_error(struct bufferevent *bev, short error, void *arg) server_close(clt, "unknown event error"); return; } + +struct range * +parse_range(char *str, size_t file_sz, int *nranges) +{ + static struct range ranges[MAX_RANGES]; + int i = 0; + char *p, *q; + + /* Extract range unit */ + if ((p = strchr(str, '=')) == NULL) + return (NULL); + + *p++ = '\0'; + /* Check if it's a bytes range spec */ + if (strcmp(str, "bytes") != 0) + return (NULL); + + while ((q = strchr(p, ',')) != NULL) { + *q++ = '\0'; + + /* Extract start and end positions */ + if (parse_range_spec(p, file_sz, &ranges[i]) == 0) + continue; + + i++; + if (i == MAX_RANGES) + return (NULL); + + p = q; + } + + if (parse_range_spec(p, file_sz, &ranges[i]) != 0) + i++; + + *nranges = i; + return (i ? ranges : NULL); +} + +int +parse_range_spec(char *str, size_t size, struct range *r) +{ + size_t start_str_len, end_str_len; + char *p, *start_str, *end_str; + const char *errstr; + + if ((p = strchr(str, '-')) == NULL) + return (0); + + *p++ = '\0'; + start_str = str; + end_str = p; + start_str_len = strlen(start_str); + end_str_len = strlen(end_str); + + /* Either 'start' or 'end' is optional but not both */ + if ((start_str_len == 0) && (end_str_len == 0)) + return (0); + + if (end_str_len) { + r->end = strtonum(end_str, 0, LLONG_MAX, &errstr); + if (errstr) + return (0); + + if ((size_t)r->end >= size) + r->end = size - 1; + } else + r->end = size - 1; + + if (start_str_len) { + r->start = strtonum(start_str, 0, LLONG_MAX, &errstr); + if (errstr) + return (0); + + if ((size_t)r->start >= size) + return (0); + } else { + r->start = size - r->end; + r->end = size - 1; + } + + if (r->end < r->start) + return (0); + + return (1); +} + +int +buffer_add_range(int fd, struct evbuffer *evb, struct range *range) +{ + char buf[BUFSIZ]; + size_t n, range_sz; + ssize_t nread; + + if (lseek(fd, range->start, SEEK_SET) == -1) + return (0); + + range_sz = range->end - range->start + 1; + while (range_sz) { + n = MINIMUM(range_sz, sizeof(buf)); + if ((nread = read(fd, buf, n)) == -1) + return (0); + + evbuffer_add(evb, buf, nread); + range_sz -= nread; + } + + return (1); +} + diff --git a/usr.sbin/httpd/server_http.c b/usr.sbin/httpd/server_http.c index 82fed5c146e..7b65a5e1777 100644 --- a/usr.sbin/httpd/server_http.c +++ b/usr.sbin/httpd/server_http.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_http.c,v 1.78 2015/04/18 09:27:54 jsg Exp $ */ +/* $OpenBSD: server_http.c,v 1.79 2015/05/03 18:39:58 florian Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -788,6 +788,13 @@ server_abort_http(struct client *clt, u_int code, const char *msg) extraheader = NULL; } break; + case 416: + if (asprintf(&extraheader, + "Content-Range: %s\r\n", msg) == -1) { + code = 500; + extraheader = NULL; + } + break; default: /* * Do not send details of the error. Traditionally,