src/server/webdav/webdav.c

Sun, 06 May 2012 10:09:27 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 06 May 2012 10:09:27 +0200
changeset 28
f387669912e8
parent 27
05b7576dca2b
child 29
e8619defde14
permissions
-rw-r--r--

added logging

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2011 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 <stdlib.h>
#include <string.h>

#include "webdav.h"
#include "../ucx/string.h"
#include "../util/pool.h"
#include "../util/pblock.h"
#include "../util/date.h"

#include "davparser.h"

int webdav_service(pblock *pb, Session *sn, Request *rq) {
    char *method = pblock_findkeyval(pb_key_method, rq->reqpb);
    if(method == NULL) {
        return REQ_ABORTED;
    }
    
    if(!strcmp(method, "PROPFIND")) {
        return webdav_propfind(pb, sn, rq);
    } else if(!strcmp(method, "PUT")) {
        return webdav_put(pb, sn, rq);
    }
    
    return REQ_NOACTION;
}

int webdav_put(pblock *pb, Session *sn, Request *rq) {
    int length = 0;
    
    char *ctlen = pblock_findkeyval(pb_key_content_length, rq->headers);
    if(ctlen) {
        length = atoi(ctlen);
    } else {
        /* invalid request */
        printf("invalid request\n");
        return REQ_ABORTED;
    }
    
    printf("PUT length: %d\n", length);
    
    int status = 204;
    if(length >= 0) {
        char *ppath = pblock_findkeyval(pb_key_ppath, rq->vars);
        
        FILE *out = fopen(ppath, "w");
        if(out == NULL) {
            fprintf(stderr, "fopen(%s, \"w\") failed\n", ppath);
            return REQ_ABORTED;
        }
        setvbuf(out, NULL, _IONBF, 0);
        
        size_t l = (length > 4096) ? (4096) : (length);
        char *buffer = malloc(l);
        
        int r;
        int r2 = 0;
        while(r2 < length) {
            r = netbuf_getbytes(sn->inbuf, buffer, l);
            if(r == NETBUF_EOF) {
                break;
            }
            fwrite(buffer, 1, r, out);
            
            r2 += r;
        }
        
        fclose(out);
    }
    
    protocol_status(sn, rq, status, NULL);
    pblock_removekey(pb_key_content_type, rq->srvhdrs);
    pblock_nninsert("content-length", 0, rq->srvhdrs);
    http_start_response(sn, rq);
    
    return REQ_PROCEED;
}

int webdav_propfind(pblock *pb, Session *sn, Request *rq) {
    /* TODO: clean up if errors occurs */

    /* Get request body which contains the webdav XML request */
    char   *xml_body;
    size_t xml_len = 0;

    char *ctlen = pblock_findkeyval(pb_key_content_length, rq->headers);
    if(ctlen) {
        xml_len = atoi(ctlen);
    } else {
        /* invalid request */
        printf("invalid request\n");
        return REQ_ABORTED;
    }

    xml_body = pool_malloc(sn->pool, xml_len + 1);
    if(xml_body == NULL) {
        return REQ_ABORTED;
    }
    xml_body[xml_len] = 0;
    if(!xml_body) {
        /* server error */
        printf("server error\n");
        return REQ_ABORTED;
    }

    /* get request body */
    int r = 0;
    char *xb = xml_body;
    size_t xl = xml_len;
    while((r = netbuf_getbytes(sn->inbuf, xb, xl)) != NETBUF_EOF) {
        xb += r;
        xl -= r;
    }

    /*
     * get requested properties and initialize some stuff
     */
    PropfindRequest *davrq = dav_parse_propfind(sn, rq, xml_body, xml_len);
    davrq->sn = sn;
    davrq->rq = rq;
    davrq->out = sbuf_new(512);
    davrq->propertyBackend = create_property_backend();
    
    /* write xml response header */
    sbuf_puts(davrq->out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
    sbuf_puts(davrq->out, "<D:multistatus xmlns:D=\"DAV:\">\n");
    
    /* begin multistatus response */
    char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
    char *ppath = pblock_findkeyval(pb_key_ppath, rq->vars);
    
    struct stat st;
    if(stat(ppath, &st) != 0) {
        perror("webdav_propfind: stat");
        return REQ_ABORTED;
    } 
    
    /*
     * if the requested webdav resource(file) is a directory, we create
     * a response for every child
     */
    if(S_ISDIR(st.st_mode)) {
        DIR *dir = opendir(ppath);
        if(dir == NULL) {
            protocol_status(sn, rq, 500, NULL);
            printf("webdav_propfind: DIR is null\n");
            return REQ_ABORTED;
        }
        
        struct dirent *f;
        while((f = readdir(dir)) != NULL) {
            if(strcmp(f->d_name, ".") == 0 || strcmp(f->d_name, "..") == 0) {
                continue;
            }
            
            sstr_t filename = sstr(f->d_name);
            sstr_t _path = sstr(ppath); 
            sstr_t _uri = sstr(uri);
            sstr_t ps;
            sstr_t us;
            ps.length = 0;
            ps.ptr = NULL;
            us.length = 0;
            us.ptr = NULL;
            if(_path.ptr[_path.length - 1] != '/') {
                ps = sstrn("/", 1);
            }
            if(_uri.ptr[_uri.length - 1] != '/') {
                us = sstrn("/", 1);
            }
            
            sstr_t newuri;
            newuri.length = filename.length + _uri.length + us.length;
            newuri.ptr = alloca(newuri.length + 1);
            if(us.length == 1) {
                newuri = sstrncat(3, newuri, _uri, us, filename);
            } else {
                newuri = sstrncat(2, newuri, _uri, filename);
            }
            
            sstr_t newpath;
            newpath.length = _path.length + filename.length + ps.length;
            newpath.ptr = alloca(newpath.length + 1);
            if(ps.length == 1) {
                newpath = sstrncat(3, newpath, _path, ps, filename);
            } else {
                newpath = sstrncat(2, newpath, _path, filename);
            }
            
            /* child response */
            dav_resource_response(davrq, newpath, newuri);
        }
    }
    
    /* create the response for the requested resource */
    dav_resource_response(davrq, sstr(ppath), sstr(uri));
    
    /* end xml */
    sbuf_puts(davrq->out, "</D:multistatus>\n");
    
    /* send the xml response to the client */
    protocol_status(sn, rq, 207, "Multi Status");
    pblock_removekey(pb_key_content_type, rq->srvhdrs);
    pblock_nvinsert("content-type", "text/xml", rq->srvhdrs);
    pblock_nninsert("content-length", davrq->out->length, rq->srvhdrs);
    
    pblock_nvinsert("connection", "close", rq->srvhdrs);
    http_start_response(sn, rq);
    
    net_write(sn->csd, davrq->out->ptr, davrq->out->length);
    
    

    
    return REQ_PROCEED;
}

void dav_resource_response(PropfindRequest *davrq, sstr_t path, sstr_t uri) {
    printf("dav_resource_response %s %s\n", sstrdub(path).ptr, sstrdub(uri).ptr);
    
    sbuf_puts(davrq->out, "<D:response>\n");
    sbuf_puts(davrq->out, "<D:href>");
    sbuf_append(davrq->out, uri);
    sbuf_puts(davrq->out, "</D:href>\n");
    
    davrq->propertyBackend->propfind(davrq->propertyBackend, davrq, path.ptr);
    
    if(davrq->prop) {
        /* 
         * there are some properties written, so we close the
         * prop and propstat tag
         */
        sbuf_puts(davrq->out, "</D:prop>\n");
        sbuf_puts(davrq->out, "<D:status>HTTP/1.1 200 OK</D:status>\n");
        sbuf_puts(davrq->out, "</D:propstat>\n");
    }
    
    if(davrq->notFoundProps != NULL) {
        sbuf_puts(davrq->out, "<D:propstat>\n<D:prop>\n");
        DAV_FOREACH(elm, davrq->notFoundProps) {
            DavProperty *prop = (DavProperty*)elm->data;
            sbuf_puts(davrq->out, "<D:");
            sbuf_puts(davrq->out, prop->name);
            sbuf_puts(davrq->out, " />\n");
        }
        sbuf_puts(davrq->out, "</D:prop>\n");
        sbuf_puts(davrq->out, "<D:status>HTTP/1.1 404 Not Found</D:status>\n");
        sbuf_puts(davrq->out, "</D:propstat>\n");
    }
    
    sbuf_puts(davrq->out, "</D:response>\n");
    
    /* reset */
    davrq->prop = 0;
    davrq->notFoundProps = NULL;
    davrq->forbiddenProps = NULL;
    
}

void dav_propfind_add_str_prop(
        PropfindRequest *davrq,
        DavProperty* prop,
        char *str,
        size_t len)
{
    if(!davrq->prop) {
        sbuf_puts(davrq->out, "<D:propstat>\n<D:prop>\n");
        davrq->prop = 1;
    }
    
    sbuf_puts(davrq->out, "<D:");
    sbuf_puts(davrq->out, prop->name);
    sbuf_puts(davrq->out, ">");
    
    sbuf_append(davrq->out, sstrn(str, len));
    
    sbuf_puts(davrq->out, "</D:");
    sbuf_puts(davrq->out, prop->name);
    sbuf_puts(davrq->out, ">\n");
}

void dav_propfind_add_prop_error(
        PropfindRequest *davrq,
        DavProperty *prop,
        int error)
{
    davrq->notFoundProps = ucx_dlist_append(davrq->notFoundProps, prop);
}




/* WebDAV Default Backend */
DAVPropertyBackend* create_property_backend() {
    DAVPropertyBackend *pb = malloc(sizeof(DAVPropertyBackend));
    if(pb == NULL) {
        //
    }
    pb->propfind = dav_rq_propfind;
    return pb;
}

void dav_rq_propfind(DAVPropertyBackend *b, PropfindRequest *rq ,char *path) {
    struct stat st;
    if(stat(path, &st) != 0) {
        perror("dav_be_propfind");
        fprintf(stderr, "Cannot get stat of file: %s\n", path);
    }
    
    DAV_FOREACH(elm, rq->properties) {
        DavProperty *prop = (DavProperty*)elm->data;
        
        char *s = prop->name;
        if(!strcmp(s, "resourcetype")) {
            if(S_ISDIR(st.st_mode)) {
                dav_propfind_add_str_prop(rq, prop, "<D:collection/>", 15);
            } else {
                dav_propfind_add_str_prop(rq, prop, NULL, 0);
            }
        } else if(!strcmp(s, "getcontentlength") && !S_ISDIR(st.st_mode)) {
            char buf[32];
            size_t n = snprintf(buf, 32, "%d", st.st_size);
            dav_propfind_add_str_prop(rq, prop, buf, n);
        } else if(!strcmp(s, "getlastmodified")) {
            sstr_t s = date_format_http(st.st_mtim.tv_sec, rq->sn->pool);
            dav_propfind_add_str_prop(rq, prop, s.ptr, s.length);
        } else if(!strcmp(s, "creationdate")) {
            sstr_t s = date_format_iso8601(st.st_ctim.tv_sec, rq->sn->pool);
            dav_propfind_add_str_prop(rq, prop, s.ptr, s.length);
        } else {
            dav_propfind_add_prop_error(rq, prop, 404);
        }
    }
}

mercurial