Fri, 23 Oct 2015 17:28:09 +0200
implemented range requests
--- a/src/server/daemon/http.c Sat Oct 17 23:05:23 2015 +0200 +++ b/src/server/daemon/http.c Fri Oct 23 17:28:09 2015 +0200 @@ -138,12 +138,10 @@ header = pblock_findkeyval(pb_key_if_unmodified_since, rq->headers); if (header) { if (mtm && !util_later_than(mtm, header)) { - //PRTime temptime; - //PRStatus status = PR_ParseTimeString(header, PR_TRUE, &temptime); - //if (status == PR_SUCCESS) { - // http_status(sn, rq, PROTOCOL_PRECONDITION_FAIL, NULL); - // return REQ_ABORTED; - //} + if(util_isdate(header)) { + protocol_status(sn, rq, PROTOCOL_PRECONDITION_FAIL, NULL); + return REQ_ABORTED; + } } } @@ -173,6 +171,26 @@ return REQ_ABORTED; } } + + /* If-range */ + header = pblock_findkeyval(pb_key_if_range, rq->headers); + char *range = pblock_findkeyval(pb_key_range, rq->headers); + if (range && header) { + int rmir = PR_FALSE; + if (util_isdate(header)) { + if (mtm && !util_later_than(mtm, header)) { + rmir = PR_TRUE; + } + } else { + if (!http_match_etag(header, etag, PR_TRUE)) { + rmir = PR_TRUE; + } + } + + if(rmir) { + pblock_removekey(pb_key_range, rq->headers); + } + } return REQ_PROCEED; }
--- a/src/server/daemon/protocol.c Sat Oct 17 23:05:23 2015 +0200 +++ b/src/server/daemon/protocol.c Fri Oct 23 17:28:09 2015 +0200 @@ -305,9 +305,6 @@ // add the http status line to the output buffer add_http_status_line(out, sn->pool, rq); - - // add server header - sbuf_write(out, "Server: webserver\r\n", 19); // add date header struct tm mtms; @@ -318,6 +315,9 @@ sbuf_write(out, date, strlen(date)); sbuf_write(out, "\r\n", 2); + // add server header + sbuf_write(out, "Server: webserver\r\n", 19); + // check content length ans transfer encoding char *ctlen = pblock_findkeyval(pb_key_content_length, rq->srvhdrs); char *enc = pblock_findkeyval(pb_key_transfer_encoding, rq->srvhdrs);
--- a/src/server/public/nsapi.h Sat Oct 17 23:05:23 2015 +0200 +++ b/src/server/public/nsapi.h Fri Oct 23 17:28:09 2015 +0200 @@ -530,7 +530,7 @@ typedef struct sendfiledata sendfiledata; struct sendfiledata { SYS_FILE fd; /* file to send */ - size_t offset; /* offset in file to start sending from */ + off_t offset; /* offset in file to start sending from */ size_t len; /* number of bytes to send from file */ const void *header; /* data to send before file */ int hlen; /* number of bytes to send before file */
--- a/src/server/safs/service.c Sat Oct 17 23:05:23 2015 +0200 +++ b/src/server/safs/service.c Fri Oct 23 17:28:09 2015 +0200 @@ -33,27 +33,29 @@ #include "service.h" #include "../util/io.h" #include "../util/pblock.h" +#include "../util/util.h" #include "../daemon/protocol.h" #include "../daemon/vfs.h" //include <sys/sendfile.h> #include "../util/strbuf.h" +#include <ucx/string.h> +#include <ucx/utils.h> #include <errno.h> /* - * prepares for servicing a file + * prepares servicing a file * - * adds content-length header and starts the response + * adds content-length header * * return the opened file */ -SYS_FILE prepare_service_file(Session *sn, Request *rq, struct stat *s) { +SYS_FILE prepare_service_file(Session *sn, Request *rq, VFSContext *vfs, struct stat *s) { char *ppath = pblock_findkeyval(pb_key_ppath, rq->vars); // open the file - VFSContext *vfs = vfs_request_context(sn, rq); SYS_FILE fd = vfs_open(vfs, ppath, O_RDONLY); if(!fd) { // vfs_open sets http status code @@ -89,39 +91,353 @@ vfs_close(fd); return NULL; } + + // TODO: check if vfs can seek + pblock_kvinsert(pb_key_accept_ranges, "bytes", 5, rq->srvhdrs); // start response protocol_status(sn, rq, 200, NULL); - http_start_response(sn, rq); return fd; } +static void free_range(Session *sn, HttpRange *range) { + HttpRange *elm = range; + while(elm) { + HttpRange *next = elm->next; + pool_free(sn->pool, elm); + elm = next; + } +} + +static HttpRange* parse_range(Session *sn, char *header, int *status) { + *status = PROTOCOL_OK; + + sstr_t range = sstrtrim(sstr(header)); + if(!sstrprefix(range, S("bytes="))) { + // unknown range unit - ignore range header + return NULL; + } + + // get byte-range-set + range = sstrsubs(range, 6); + if(range.length < 1) { + return NULL; + } + + HttpRange *range_list = NULL; + HttpRange *last = NULL; + off_t begin = -1; + int start = 0; + int hasbegin = 0; + for(int i=0;i<=range.length;i++) { + char c = range.ptr[i]; + if(c == '-') { + sstr_t num = sstrsubsl(range, start, i-start); + if(num.length == 0) { + // empty string before '-' is legal + hasbegin = 1; + begin = -1; + start = i+1; + continue; + } + char *end; + long long n = strtoll(num.ptr, &end, 10); + if(errno == 0 && end == range.ptr + i && n >= 0) { + begin = n; + hasbegin = 1; + start = i+1; + } else { + // syntax error + free_range(sn, range_list); + return NULL; + } + } else if(c == ',' || c == '\0') { + sstr_t num = sstrsubsl(range, start, i-start); + if(hasbegin) { + long long n; + if(num.length == 0) { + // empty string after '-' is legal + n = -1; + } else { + char *end; + n = strtoll(num.ptr, &end, 10); + if(errno != 0 || end != range.ptr + i || n < 0) { + // syntax error + free_range(sn, range_list); + return NULL; + } + } + + if(!(begin < 0 && n < 0)) { + // range: begin - n + HttpRange *rangeelm = pool_malloc(sn->pool, sizeof(HttpRange)); + if(!rangeelm) { + free_range(sn, range_list); + *status = PROTOCOL_SERVER_ERROR; + return NULL; + } + rangeelm->begin = begin; + rangeelm->end = n; + rangeelm->next = NULL; + if(!last) { + range_list = rangeelm; + last = rangeelm; + } else { + last->next = rangeelm; + last = rangeelm; + } + + hasbegin = 0; + start = i+1; + continue; + } + } + + // syntax error + free_range(sn, range_list); + return NULL; + } + } + + return range_list; +} + +static int validate_range(HttpRange *range, struct stat *finfo, int *status) { + off_t max_len = finfo->st_size; + while(range) { + if(range->begin > 0 && range->end > 0) { + if(range->end < range->begin) { + *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; + return 0; + } + } + if(range->begin >= max_len) { + *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; + return 0; + } + if(range->end >= max_len) { + *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; + return 0; + } + + range = range->next; + } + + // TODO: check for Denial-of-Service Attacks + + return 1; +} + +/* + * translates a HttpRange element to a begin offset and a length + * the HttpRange must be validated + */ +static void range2off(HttpRange *range, off_t filelen, off_t *begin, off_t *length) { + if(range->begin < 0) { + // bytes=-a + *begin = filelen - range->end; + *length = range->end; + } else if(range->end < 0) { + // bytes=a- + *begin = range->begin; + *length = filelen - range->begin; + } else { + // bytes=a-b + *begin = range->begin; + *length = range->end + 1 - range->begin; + } +} + +#define SF_MAX_LEN 0x8000000 + +static int send_range(Session *sn, SYS_FILE fd, off_t offset, off_t length, char *header, int headerlen) { + off_t remaining = length; + + sendfiledata sfd; + sfd.fd = fd; + sfd.header = header; + sfd.hlen = headerlen; + sfd.trailer = NULL; + + while(remaining > 0) { + size_t sflen = remaining < SF_MAX_LEN ? remaining : SF_MAX_LEN; + sfd.offset = offset; + sfd.len = sflen; + + ssize_t r = net_sendfile(sn->csd, &sfd); + if(r < 0) { + return -1; + } + + sfd.header = NULL; // make sure the header is only sent once + offset += r; + remaining -= r; + } + + return 0; +} + +struct multi_range_elm { + sstr_t header; + off_t offset; + off_t length; +}; + +static int send_multi_range(Session *sn, Request *rq, SYS_FILE fd, off_t filelen, HttpRange *range) { + pb_param *content_type = pblock_remove("content-type", rq->srvhdrs); + + char sep[64]; + int seplen = util_mime_separator(sep); + + sstr_t newct = ucx_sprintf("multipart/byteranges; boundary=%s", sep+4); + pblock_kvinsert( + pb_key_content_type, + newct.ptr, + newct.length, + rq->srvhdrs); + free(newct.ptr); + + // calculate content-length + off_t response_len = 0; + + int nrange = 0; + HttpRange *rangeelm = range; + while(rangeelm) { + nrange++; + rangeelm = rangeelm->next; + } + + struct multi_range_elm *r = calloc(nrange, sizeof(struct multi_range_elm)); + rangeelm = range; + int i=0; + while(rangeelm) { + range2off(rangeelm, filelen, &(r[i].offset), &(r[i].length)); + r[i].header = ucx_sprintf( + "%s\r\nContent-Type: %s\r\nContent-Range: bytes %lld-%lld/%lld\r\n\r\n", + sep, + content_type->value, + (long long)r[i].offset, + (long long)r[i].offset+r[i].length - 1, + filelen); + + response_len += r[i].header.length + r[i].length; + + rangeelm = rangeelm->next; + i++; + } + + response_len += seplen + 4; // trailer: sep + '--' + CRLF + + // finally, set the content-length header + pblock_kllinsert( + pb_key_content_length, + (long long)response_len, + rq->srvhdrs); + + // and start the response + http_start_response(sn, rq); + + rangeelm = range; + i = 0; + while(rangeelm) { + if(send_range(sn, fd, r[i].offset, r[i].length, r[i].header.ptr, r[i].header.length)) { + // TODO: error + } + rangeelm = rangeelm->next; + i++; + } + net_printf(sn->csd, "%s--\r\n", sep); + + return 0; +} + int send_file(pblock *pb, Session *sn, Request *rq) { struct stat s; - SYS_FILE fd = prepare_service_file(sn, rq, &s); + VFSContext *vfs = vfs_request_context(sn, rq); + SYS_FILE fd = prepare_service_file(sn, rq, vfs, &s); if(!fd) { // if an error occurs, prepare_service_file sets the http status code // we can just return REQ_ABORTED return REQ_ABORTED; } - if(!S_ISDIR(s.st_mode)) { - // send file - sendfiledata sfd; - sfd.fd = fd; - sfd.len = s.st_size; - sfd.offset = 0; - sfd.header = NULL; - sfd.trailer = NULL; - net_sendfile(sn->csd, &sfd); - } // else: status 302 set by prepare_service_file + // get and validate range header + char *range_header = pblock_findkeyval(pb_key_range, rq->headers); + HttpRange *range = NULL; + if(range_header) { + int status; + range = parse_range(sn, range_header, &status); + if(status != PROTOCOL_OK) { + protocol_status(sn, rq, status, NULL); + vfs_close(fd); + return REQ_ABORTED; + } + + if(!validate_range(range, &s, &status)) { + protocol_status(sn, rq, status, NULL); + free_range(sn, range); + vfs_close(fd); + return REQ_ABORTED; + } + } + int single_range = 1; + off_t offset; + off_t length; + if(range) { + protocol_status(sn, rq, 206, NULL); + pblock_removekey(pb_key_content_length, rq->srvhdrs); + + if(range->next) { + single_range = 0; + } else { + range2off(range, s.st_size, &offset, &length); + + pblock_kllinsert( + pb_key_content_length, + (long long)length, + rq->srvhdrs); + + sstr_t content_range = ucx_sprintf( + "%lld-%lld/%lld", + (long long)offset, + (long long)offset+length - 1, + s.st_size); + pblock_kvinsert( + pb_key_content_range, + content_range.ptr, + content_range.length, + rq->srvhdrs); + free(content_range.ptr); + } + } else { + offset = 0; + length = s.st_size; + } + + if(single_range) { + // send response header + http_start_response(sn, rq); + // send content + if(send_range(sn, fd, offset, length, NULL, 0)) { + // TODO: error + } + } else { + if(send_multi_range(sn, rq, fd, s.st_size, range)) { + // TODO: error + } + } + + // cleanup vfs_close(fd); + free_range(sn, range); return REQ_PROCEED; } + + int service_hello(pblock *pb, Session *sn, Request *rq) { pblock_removekey(pb_key_content_type, rq->srvhdrs); pblock_nvinsert("content-type", "text/plain", rq->srvhdrs);
--- a/src/server/safs/service.h Sat Oct 17 23:05:23 2015 +0200 +++ b/src/server/safs/service.h Fri Oct 23 17:28:09 2015 +0200 @@ -35,6 +35,15 @@ extern "C" { #endif +typedef struct HttpRange HttpRange; + +struct HttpRange { + off_t begin; + off_t end; + HttpRange *next; +}; + + int send_file(pblock *pb, Session *sn, Request *rq); int service_hello(pblock *pb, Session *sn, Request *rq);
--- a/src/server/util/io.c Sat Oct 17 23:05:23 2015 +0200 +++ b/src/server/util/io.c Fri Oct 23 17:28:09 2015 +0200 @@ -136,8 +136,7 @@ return r; } -ssize_t net_stream_sendfile(NetIOStream *st, sendfiledata *sfd) { - // TODO: header and trailer +ssize_t net_stream_sendfile(NetIOStream *st, sendfiledata *sfd) { ssize_t ret = 0; off_t fileoffset = sfd->offset; if(sfd->fd->fd != -1) { @@ -168,7 +167,13 @@ 0); #endif #else // Solaris/Linux - ret = sendfile(st->fd, sfd->fd->fd, &fileoffset, sfd->len); + if(sfd->header) { + ret += write(st->fd, sfd->header, sfd->hlen); + } + ret += sendfile(st->fd, sfd->fd->fd, &fileoffset, sfd->len); + if(sfd->trailer) { + ret += write(st->fd, sfd->trailer, sfd->tlen); + } #endif } else { // TODO: regular copy
--- a/src/server/util/util.c Sat Oct 17 23:05:23 2015 +0200 +++ b/src/server/util/util.c Fri Oct 23 17:28:09 2015 +0200 @@ -906,3 +906,70 @@ return gmtime_r(clock, res); } +int util_isdate(char *str) { + sstr_t datestr = sstr(str); + sstr_t example = S("Sun, 06 Nov 1994 08:49:37 GMT"); + + if(datestr.length != example.length) { + return 0; + } + + for(int i=0;i<datestr.length;i++) { + char e = example.ptr[i]; + if(isdigit(e)) { + if(!isdigit(datestr.ptr[i])) { + return 0; + } + } else if(e == ' ') { + if(datestr.ptr[i] != ' ') { + return 0; + } + } else if(e == ',') { + if(datestr.ptr[i] != ',') { + return 0; + } + } else if(e == ':') { + if(datestr.ptr[i] != ':') { + return 0; + } + } + } + + if(!sstrsuffix(datestr, S("GMT"))) { + return 0; + } + + return 1; +} + + +/* ------------------------- util_mime_separator -------------------------- */ + + +NSAPI_PUBLIC int util_mime_separator(char *sep) +{ + int size = 35; // documented in nsapi.h + int pos = 0; + + sep[pos++] = CR; + sep[pos++] = LF; + sep[pos++] = '-'; + sep[pos++] = '-'; + + int r[6]; + for(int i=0;i<6;i++) { + r[i] = rand() % 10000; + } + pos += snprintf( + sep+4, + size-4, + "X%04x%04x%04x%04x%04x%04xE", + r[0], + r[1], + r[2], + r[3], + r[4], + r[5]); + + return pos; +}
--- a/src/server/util/util.h Sat Oct 17 23:05:23 2015 +0200 +++ b/src/server/util/util.h Fri Oct 23 17:28:09 2015 +0200 @@ -236,6 +236,8 @@ /* ucx utils */ UcxAllocator util_pool_allocator(pool_handle_t *pool); +int util_isdate(char *str); + /* --- End common function prototypes --- */ /* --- Begin Unix-only function prototypes --- */