Florian Obser(flor...@openbsd.org) on 2015.05.03 12:39:02 +0000: > On Sun, May 03, 2015 at 01:46:56PM +0200, Sunil Nimmagadda wrote: > > On Sat, May 02, 2015 at 02:49:30PM +0000, Florian Obser wrote: > > > Sorry for the very late reply, I'm currently very busy :/ > > > > Thank you for taking time to review it. A new patch with style nits > > fixed and a gratuitous NULL check removed. > > > > [trimming some text] > > > > > this is missing the server_file_method() song and dance from > > > server_file_request() > > > > Fixed in a slightly different way than using server_file_method(). > > Since range request should be ignored for methods other than GET, > > fallback to server_file_request() for further processing/validation. > > yep, this is good. > > > > > > > + 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. > > > > I am confused about the RFC here, section 4.4 states that 416 is > > returned if "...the set of ranges requested has been rejected due > > to invalid ranges..." and a note follows immediately, "Because > > servers are free to ignore Range, many implementations will simply > > respond with the entire selected representation in a 200 response" > > > > As you noted apache returns 200 while nginx returns 416 for an > > incorrect range. What should httpd do ideally? > > Thanks for checking nginx, I don't have one around. > I noted this for 2 reasons: > 1) I guess apache is the defacto standard, so if it behaves > differently there might be a reason, maybe broken clients in the wild. > 2) The way the code is currently structured it will require a bit of > shuffling around if we ever want apache's behaviour. > > That being said, my understanding of the RFC is that your > implementation is correct. And with nginx behaving this way, too, it > shouldn't bite us in the future. :) > > > > > > > + > > > > + /* 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. > > > > Agree, since RFC says "server MAY send Accept-Range header" and > > "clients MAY generate range requests without having received this > > header field", dropping this header shouldn't make a difference. > > > > Comments? > > This looks good. > > OK florian@ if someone wants to commit it. Or give me an OK and I'll > commit. >
fwiw i dont know much about ranges, but this reads ok. two whitespace bits below. otherwise ok benno. one question though: whats the reasoning behind MAX_RANGES 4? nginx seems to have a default of "unlimited" (which i think questionable), but what is reasonably seen on the internet? > > > > Index: server_file.c > > =================================================================== > > RCS file: /cvs/src/usr.sbin/httpd/server_file.c,v > > retrieving revision 1.52 > > diff -u -p -r1.52 server_file.c > > --- server_file.c 25 Apr 2015 14:40:35 -0000 1.52 > > +++ server_file.c 3 May 2015 11:18:07 -0000 > > @@ -36,12 +36,25 @@ > > > > #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) > > #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) > > +#define MAX_RANGES 4 > > > > -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 *); > > +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, 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 +137,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 +282,143 @@ 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 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; > > + whitespace on blan line > > + 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]; > > @@ -469,3 +626,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]; > > + 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; > > + } > > + whitespace on blank line > > + return (1); > > +} > > + > > Index: server_http.c > > =================================================================== > > RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v > > retrieving revision 1.78 > > diff -u -p -r1.78 server_http.c > > --- server_http.c 18 Apr 2015 09:27:54 -0000 1.78 > > +++ server_http.c 3 May 2015 11:18:07 -0000 > > @@ -788,6 +788,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, > > -- > I'm not entirely sure you are real. >