Mon, 06 Mar 2017 17:32:26 +0100
merge srvctrl into default branch
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2013 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <stdio.h> #include <sys/file.h> #include <sys/stat.h> #include "service.h" #include "../util/io.h" #include "../util/pblock.h" #include "../util/util.h" #include "../daemon/protocol.h" #include "../daemon/vfs.h" #include "../util/strbuf.h" #include <ucx/string.h> #include <ucx/utils.h> #include <errno.h> /* * prepares servicing a file * * adds content-length header * * return the opened file */ SYS_FILE prepare_service_file(Session *sn, Request *rq, VFSContext *vfs, struct stat *s) { char *path = pblock_findkeyval(pb_key_path, rq->vars); // open the file SYS_FILE fd = vfs_open(vfs, path, O_RDONLY); if(!fd) { // vfs_open sets http status code return NULL; } // get stat if(vfs_fstat(vfs, fd, s) != 0) { //perror("prepare_service_file: stat"); protocol_status(sn, rq, 500, NULL); return NULL; } // check if the file is a directory if(S_ISDIR(s->st_mode)) { pblock_nvinsert("content-length", "0", rq->srvhdrs); pblock_removekey(pb_key_content_type, rq->srvhdrs); char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); size_t urilen = strlen(uri); char *location = pool_malloc(sn->pool, urilen + 2); memcpy(location, uri, urilen); location[urilen] = '/'; location[urilen+1] = '\0'; pblock_kvinsert(pb_key_location, location, urilen + 1, rq->srvhdrs); protocol_status(sn, rq, 302, NULL); http_start_response(sn, rq); vfs_close(fd); return NULL; } // sets last-modified, content-length and checks conditions if(http_set_finfo(sn, rq, s) != REQ_PROCEED) { 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); 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); free(r); return 0; } int send_file(pblock *pb, Session *sn, Request *rq) { struct stat 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; } // 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); pblock_nninsert("content-length", 13, rq->srvhdrs); protocol_status(sn, rq, 200, NULL); http_start_response(sn, rq); net_write(sn->csd, "Hello World!\n", 13); return REQ_PROCEED; } static int ws_msghandler(WebSocket *ws, WSMessage *msg) { if(msg->type == 1) { printf("Message(text): %.*s\n", (int)msg->length, msg->data); websocket_send_text(ws->userdata, "hello", 5); } else { printf("Message: opcode: %d | length: %d\n", msg->type, (int)msg->length); } return 0; } int service_ws_hello(pblock *pb, Session *sn, Request *rq) { WebSocket ws; ZERO(&ws, sizeof(WebSocket)); ws.userdata = sn->csd; ws.on_message = ws_msghandler; return http_handle_websocket(sn, rq, &ws); } int service_index(pblock *pb, Session *sn, Request *rq) { //printf("service_index\n"); char *path = pblock_findkeyval(pb_key_path, rq->vars); char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); sstr_t r_uri = sstr(uri); // open the file VFSContext *vfs = vfs_request_context(sn, rq); VFS_DIR dir = vfs_opendir(vfs, path); if(!dir) { return REQ_ABORTED; } sbuf_t *out = sbuf_new(1024); // output buffer // write html header sbuf_puts(out, "<html>\n<head>\n<title>Index of "); sbuf_puts(out, uri); sbuf_puts(out, "</title>\n</head><body>\n<h1>Index of "); sbuf_puts(out, uri); sbuf_puts(out, "</h1><hr>\n\n"); // list directory VFS_ENTRY f; while(vfs_readdir(dir, &f)) { sstr_t filename = sstr(f.name); sbuf_puts(out, "<a href=\""); sbuf_append(out, r_uri); sbuf_append(out, filename); sbuf_puts(out, "\">"); sbuf_append(out, filename); sbuf_puts(out, "</a><br>\n"); } sbuf_puts(out, "\n</body>\n</html>\n"); // send stuff to client pblock_removekey(pb_key_content_type, rq->srvhdrs); pblock_kvinsert(pb_key_content_type, "text/html", 9, rq->srvhdrs); pblock_nninsert("content-length", out->length, rq->srvhdrs); protocol_status(sn, rq, 200, NULL); http_start_response(sn, rq); net_write(sn->csd, out->ptr, out->length); // close vfs_closedir(dir); sbuf_free(out); return REQ_PROCEED; } int send_options(pblock *pb, Session *sn, Request *rq) { char *allow = "HEAD, GET, PUT, DELETE, TRACE, OPTIONS, MOVE, COPY, " "PROPFIND, PROPPATCH, MKCOL, LOCK, UNLOCK, ACL, REPORT"; char *dav = "1,2,access-control"; pblock_removekey(pb_key_content_type, rq->srvhdrs); pblock_nvinsert("allow", allow, rq->srvhdrs); pblock_nvinsert("dav", dav, rq->srvhdrs); pblock_nninsert("content-length", 0, rq->srvhdrs); protocol_status(sn, rq, 204, NULL); http_start_response(sn, rq); return REQ_PROCEED; }