src/server/webdav/webdav.c

Sat, 12 Jan 2013 14:00:47 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 12 Jan 2013 14:00:47 +0100
changeset 46
636e05eb48f6
parent 44
3da1f7b6847f
child 48
37a512d7b8f6
permissions
-rw-r--r--

cleaning up resources after requests

/*
 * 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 <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 "../daemon/protocol.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, "PROPPATCH")) {
        return webdav_proppatch(pb, sn, rq);
    } else if(!strcmp(method, "PUT")) {
        return webdav_put(pb, sn, rq);
    } else if(!strcmp(method, "DELETE")) {
        return webdav_delete(pb, sn, rq);
    } else if(!strcmp(method, "MKCOL")) {
        return webdav_mkcol(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_delete(pblock *pb, Session *sn, Request *rq) {
    char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
    char *ppath = pblock_findkeyval(pb_key_ppath, rq->vars);
    
    int status = 204;
    
    struct stat st;
    if(stat(ppath, &st) != 0) {
        /* ERROR */
        status = 403; /* TODO: check errno */
    }
    
    if(!strcmp(uri, "/")) {
        status = 403;
    } else if((st.st_mode & S_IFDIR) == S_IFDIR) {
        if(rmdir(ppath) != 0) {
            /* ERROR */
            status = 403;
        }
    } else {
        if(unlink(ppath) != 0) {
            /* ERROR */
            status = 403; /* TODO: check errno */
        }
    }
    
    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_mkcol(pblock *pb, Session *sn, Request *rq) {
    char *ppath = pblock_findkeyval(pb_key_ppath, rq->vars);
    
    int status = 201;
    if(mkdir(ppath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) {
        status = 403;
    }
    
    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_ABORTED;
}

int webdav_copy(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_move(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

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");
        fprintf(stderr, "   file: %s\n", ppath);
        
        /* TODO: check errno only set status */
        protocol_status(sn, rq, 404, NULL);
        pblock_removekey(pb_key_content_type, rq->srvhdrs);
        pblock_nninsert("content-length", 0, rq->srvhdrs);
        http_start_response(sn, rq);
        
        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;
}

int webdav_proppatch(pblock *pb, Session *sn, Request *rq) {
    printf("webdav-proppatch\n");
    /* TODO: clean up if errors occurs */
    /* TODO: this is the same code as in propfind */
    char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
    if(uri == NULL) {
        /* TODO: error */
        return REQ_ABORTED;
    }

    /* 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;
    }
    
    /*
     * parse the xml request and create the proppatch object
     */
    ProppatchRequest *davrq = dav_parse_proppatch(sn, rq, xml_body, xml_len);
    davrq->sn = sn;
    davrq->rq = rq;
    davrq->out = sbuf_new(512);
    davrq->backend = create_property_backend();
    davrq->propstat = propstat_create(sn->pool);
    
    /* TODO: create prefixes for every namespace */
    XmlNs *ns = xmlnsmap_get(davrq->nsmap, "DAV:");
    ns->prefix = "D";
    ns->prelen = 1;
    
    /* 
     * begin multistatus response
     * 
     * The webdav backend does the most work. The backend->proppatch function
     * modifies the properties and adds status informations to the propstat
     * member of the ProppatchRequest. All we have to do here is to create
     * the xml response and send it to the client
     */ 
    
    /* write xml response header */
    /* TODO: add possible xml namespaces */
    sbuf_puts(davrq->out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
    sbuf_puts(davrq->out, "<D:multistatus xmlns:D=\"DAV:\">\n");
    
    sbuf_puts(davrq->out, "<D:response>\n<D:href>");
    sbuf_puts(davrq->out, uri);
    sbuf_puts(davrq->out, "</D:href>\n");
    
    /* do proppatch operation */
    davrq->backend->proppatch(davrq->backend, davrq);
    
    propstat_write(davrq->propstat, davrq->out, 0);
    
    sbuf_puts(davrq->out, "</D:response>\n");
    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", sstrdup(path).ptr, sstrdup(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;
    pb->proppatch = dav_rq_proppatch;
    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);
        }
    }
}

void dav_rq_proppatch(DAVPropertyBackend *b, ProppatchRequest *rq) {
    DAV_FOREACH(p, rq->setProps) {
        XmlElement *prop = (XmlElement*)p->data;
        propstat_add(rq->propstat, 403, prop);
    }
    
    DAV_FOREACH(p, rq->removeProps) {
        XmlElement *prop = (XmlElement*)p->data;
        propstat_add(rq->propstat, 403, prop);
    }
}



/*---------------------------------- utils ----------------------------------*/

/* XmlNsMap */

XmlNsMap* xmlnsmap_create() {
    XmlNsMap *map = malloc(sizeof(XmlNsMap));
    UcxMap *uxm = ucx_map_new(16);
    if(map == NULL || uxm == NULL) {
        return NULL;
    }
    map->map = uxm;
    return map;
}

void xmlnsmap_free(XmlNsMap *map) {
    /* TODO: implement */
}

XmlNs* xmlnsmap_put(XmlNsMap *map, char *ns) {
    XmlNs *xmlns = xmlnsmap_get(map, ns);
    if(xmlns != NULL) {
        return xmlns;
    }
    
    xmlns = malloc(sizeof(XmlNs));
    if(xmlns == NULL) {
        return NULL;
    }
    
    xmlns->xmlns = ns;
    xmlns->nslen = strlen(ns);
    
    xmlns->prefix = NULL;
    xmlns->prelen = 0;
    
    ucx_map_cstr_put(map->map, ns, xmlns); /* TODO: check return value */
    return xmlns;
}

XmlNs* xmlnsmap_get(XmlNsMap *map, char *ns) {
    return ucx_map_cstr_get(map->map, ns);
}


/* XmlElement */

void xmlelm_add_child(XmlElement *parent, XmlElement *child) {
    if(parent->ctlen == 0) {
        parent->content = ucx_dlist_append(parent->content, child);
    }
}

void xmlelm_write(XmlElement *elm, sbuf_t *out, int wv) {
    sbuf_append(out, sstrn("<", 1));
    sbuf_append(out, sstrn(elm->xmlns->prefix, elm->xmlns->prelen));
    sbuf_append(out, sstrn(":", 1));
    sbuf_append(out, elm->name);
    
    if(wv) {
        if(elm->ctlen == 0) {
            if(elm->content == NULL) {
                sbuf_append(out, sstrn(" />", 3));
            } else {
                sbuf_append(out, sstrn(">", 1));
                DAV_FOREACH(pr, (UcxDlist*)elm->content) {
                    xmlelm_write((XmlElement*)pr->data, out, 1);
                }
                sbuf_append(out, sstrn("</", 2));
                sbuf_append(out, sstrn(elm->xmlns->prefix, elm->xmlns->prelen));
                sbuf_append(out, sstrn(":", 1));
                sbuf_append(out, elm->name);
                sbuf_append(out, sstrn(">", 1));
            }
        } else {
            sbuf_append(out, sstrn(" />", 3));
            sbuf_append(out, sstrn((char*)elm->content, elm->ctlen));
            sbuf_append(out, sstrn("</", 2));
            sbuf_append(out, sstrn(elm->xmlns->prefix, elm->xmlns->prelen));
            sbuf_append(out, sstrn(":", 1));
            sbuf_append(out, elm->name);
            sbuf_append(out, sstrn(">", 1));
        }
    } else {
        sbuf_append(out, sstrn(" />", 3));
    }
}


/* PropstatMap */

Propstat* propstat_create(pool_handle_t *pool) {
    Propstat *propstat = (Propstat*)pool_malloc(pool, sizeof(Propstat));
    propstat->map = ucx_map_new(8);
    propstat->okprop = NULL;
    propstat->pool = pool;
    return propstat;
}

void propstat_add(Propstat *propstat, int status, XmlElement *prop) {
    if(status == 200) {
        propstat->okprop = ucx_dlist_append(propstat->okprop, prop);
    } else {
        UcxKey key;
        key.data = &status;
        key.len = sizeof(int);

        UcxDlist *list = ucx_map_get(propstat->map, key);
        list = ucx_dlist_append(list, prop);

        ucx_map_put(propstat->map, key, list);
    }
}

void propstat_write(Propstat *propstat, sbuf_t *out, int wv) {
    if(propstat->okprop) {
        sbuf_puts(out, "<D:propstat>\n<D:prop>\n");
        
        DAV_FOREACH(prop, propstat->okprop) {    
            xmlelm_write((XmlElement*)prop->data, out, wv);
        }
        
        sbuf_puts(out, "\n</D:prop>\n<D:status>HTTP/1.1 200 OK</D:status>\n");
        sbuf_puts(out, "</D:propstat>\n");
    }
    
    UcxMapIterator iter = ucx_map_iterator(propstat->map);
    UcxDlist *proplist;
    UCX_MAP_FOREACH(proplist, iter) { 
        if(proplist) {
            sbuf_puts(out, "<D:propstat>\n<D:prop>\n");
            
            DAV_FOREACH(prop, proplist) {
                xmlelm_write((XmlElement*)prop->data, out, wv);
            }
                
            sbuf_puts(out, "\n</D:prop>\n<D:status>");
                
            int status = *(int*)iter.cur->key.data;
            if(status < 1000 && status > 0) {
                char buf[5];
                buf[4] = 0;
                sprintf(buf, "%d ", status);
                sbuf_puts(out, "HTTP/1.1 ");
                sbuf_puts(out, buf);
                sbuf_puts(out, (char*)protocol_status_message(status));
            }
            
            sbuf_puts(out, "</D:status>\n</D:propstat>\n");
        }
    }
}

mercurial