src/server/safs/service.c

changeset 104
a8acbb12f27c
parent 102
136a76e293b5
child 116
d7a186cf87f6
--- 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);

mercurial