Sorry for the very late reply, I'm currently very busy :/ On Fri, Apr 17, 2015 at 05:04:01AM +0200, Sunil Nimmagadda wrote: > Range requests as defined in RFC7233 is required for resuming > interrupted http(s) downloads for example: > ftp -C http://foo.bar/install57.iso > > With this diff, httpd parses "Range" header in the requests and > provide either 206(Partial Content) or 416(Range not Satisfiable) > responses with "Content-Range" header set appropriately. Further, > it understands multi range request and generate satisfiable payloads > with "multipart/byteranges" media type. > > Suggestions/comments to improve the diff are welcome. > > Note, "If-Range" isn't implemented yet. > > Index: server_file.c > =================================================================== > RCS file: /cvs/src/usr.sbin/httpd/server_file.c,v > retrieving revision 1.51 > diff -u -p -r1.51 server_file.c > --- server_file.c 12 Feb 2015 10:05:29 -0000 1.51 > +++ server_file.c 17 Apr 2015 02:22:12 -0000 > @@ -36,12 +36,23 @@ > > #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) > #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) > +#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 *);
This whole block now needs another tab indentation after the type (except for parse_range of course) > > int > server_file_access(struct httpd *env, struct client *clt, > @@ -50,6 +61,7 @@ server_file_access(struct httpd *env, st > 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 +135,13 @@ server_file_access(struct httpd *env, st > 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) { > @@ -262,6 +280,138 @@ server_file_request(struct httpd *env, s > } > > 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 media_type *media, multipart_media; > + struct range *range; > + struct evbuffer *evb = NULL; > + size_t content_length; > + int code = 500, fd = -1, i, nranges, ret; > + char content_range[64]; > + const char *errstr = NULL; > + uint32_t boundary; Nit: uint32_t should be below int > + this is missing the server_file_method() song and dance from server_file_request() > + 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; > + } hm, apache answers with the full file and 200 if the range header is syntactically incorrect or if end < start. As far as I can tell it only answers 416 if the range actually lies outside of the file. > + > + /* 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) > { > char path[PATH_MAX]; > @@ -467,3 +617,112 @@ server_file_error(struct bufferevent *be > 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]; > + char *p, *q; > + int i = 0; Nit: flip those two lines, int before char. > + > + /* 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 += 1; i++; > + if (i == MAX_RANGES) > + return (NULL); > + > + p = q; > + } > + > + if (p != NULL && parse_range_spec(p, file_sz, &ranges[i]) != 0) why p != NULL? that can never be false here? > + i += 1; 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); > +} > + > Index: server_http.c > =================================================================== > RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v > retrieving revision 1.77 > diff -u -p -r1.77 server_http.c > --- server_http.c 9 Apr 2015 16:48:29 -0000 1.77 > +++ server_http.c 17 Apr 2015 02:22:12 -0000 > @@ -787,6 +787,13 @@ server_abort_http(struct client *clt, u_ > 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, > @@ -1177,6 +1184,11 @@ server_response_http(struct client *clt, > /* Date header is mandatory and should be added as late as possible */ > if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 || > kv_add(&resp->http_headers, "Date", tmbuf) == NULL) > + return (-1); > + > + /* Accept byte ranges */ > + if (code == 200 && > + kv_add(&resp->http_headers, "Accept-Ranges", "bytes") == NULL) > return (-1); I don't think we should advertise ranges for all 200 results, for example we don't support it for directory indexes. > > /* Write completed header */ > -- I'm not entirely sure you are real.