src/server/plugins/postgresql/webdav.c

Mon, 25 Apr 2022 18:33:03 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 25 Apr 2022 18:33:03 +0200
branch
webdav
changeset 310
523fe96baeca
parent 309
fc021bd576d4
child 311
e676ed461b5b
permissions
-rw-r--r--

remove createtestdb from build

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2022 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 "webdav.h"
#include "vfs.h"

#include "../../util/util.h"

#include <libxml/tree.h>

static WebdavBackend pg_webdav_backend = {
    pg_dav_propfind_init,
    pg_dav_propfind_do,
    pg_dav_propfind_finish,
    pg_dav_proppatch_do,
    pg_dav_proppatch_finish,
    NULL, // opt_mkcol
    NULL, // opt_mkcol_finish
    NULL, // opt_delete
    NULL, // opt_delete_finish
    0,
    NULL,
    NULL
};


/*
 * SQL Queries
 */

// propfind with depth = 0
// params: $1: resource_id
static const char *sql_propfind_allprop_depth0 = "\
select\n\
    $2::text as ppath,\n\
    r.resource_id,\n\
    r.parent_id,\n\
    r.nodename,\n\
    r.iscollection,\n\
    r.lastmodified,\n\
    r.creationdate,\n\
    r.contentlength,\n\
    p.xmlns,\n\
    p.pname,\n\
    p.pvalue\n\
from Resource r\n\
left join Property p on r.resource_id = p.resource_id\n\
where r.resource_id = $1;";

// propfind with depth = 0 for specific properties
// params: $1: resource_id
static const char *sql_propfind_depth0 = "\
select\n\
    $2::text as ppath,\n\
    r.resource_id,\n\
    r.parent_id,\n\
    r.nodename,\n\
    r.iscollection,\n\
    r.lastmodified,\n\
    r.creationdate,\n\
    r.contentlength,\n\
    p.xmlns,\n\
    p.pname,\n\
    p.pvalue\n\
from Resource r\n\
left join Property p on r.resource_id = p.resource_id\n\
inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\
   on ( p.xmlns = n.xmlns and p.pname = n.pname ) or p.property_id is null\n\
where r.resource_id = $1;";

// propfind with depth = 1
// params: $1: resource_id
static const char *sql_propfind_allprop_depth1 = "\
select\n\
    case when r.resource_id = $1 then $2\n\
         else $2 || '/' || r.nodename\n\
    end as ppath,\n\
    r.resource_id,\n\
    r.parent_id,\n\
    r.nodename,\n\
    r.iscollection,\n\
    r.lastmodified,\n\
    r.creationdate,\n\
    r.contentlength,\n\
    p.xmlns,\n\
    p.pname,\n\
    p.pvalue\n\
from Resource r\n\
left join Property p on r.resource_id = p.resource_id\n\
where r.resource_id = $1 or r.parent_id = $1\n\
order by order by case when resource_id = $1 then 0 else 1 end, nodename, resource_id;";


// propfind with depth = 1 for specific properties
// params: $1: resource_id
static const char *sql_propfind_depth1 = "\
select\n\
    case when r.resource_id = $1 then $2\n\
         else $2 || '/' || r.nodename\n\
    end as ppath,\n\
    r.resource_id,\n\
    r.parent_id,\n\
    r.nodename,\n\
    r.iscollection,\n\
    r.lastmodified,\n\
    r.creationdate,\n\
    r.contentlength,\n\
    p.xmlns,\n\
    p.pname,\n\
    p.pvalue\n\
from Resource r\n\
left join Property p on r.resource_id = p.resource_id\n\
inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\
   on ( p.xmlns = n.xmlns and p.pname = n.pname ) or p.property_id is null\n\
where r.resource_id = $1 or r.parent_id = $1\n\
order by order by case when resource_id = $1 then 0 else 1 end, nodename, resource_id;";

// recursive propfind
// params: $1: resource_id
static const char *sql_propfind_allprop_recursive = "\
with recursive resolvepath as (\n\
    select\n\
        '' as ppath,\n\
        *\n\
    from Resource\n\
    where resource_id = $1 \n\
    union\n\
    select\n\
        p.ppath || '/' || r.nodename,\n\
        r.*\n\
    from Resource r\n\
    inner join resolvepath p on r.parent_id = p.resource_id\n\
    )\n\
select\n\
    case when r.resource_id = $1 then $2\n\
         else $2 || r.ppath\n\
    end as ppath,\n\
    r.resource_id,\n\
    r.parent_id,\n\
    r.nodename,\n\
    r.iscollection,\n\
    r.lastmodified,\n\
    r.creationdate,\n\
    r.contentlength,\n\
    p.xmlns,\n\
    p.pname,\n\
    p.pvalue\n\
    from resolvepath r\n\
left join Property p on r.resource_id = p.resource_id\n\
order by replace(ppath, '/', chr(1)), resource_id;";

// recursive propfind for specific properties
// params: $1: resource_id
//         $2: array of property xmlns
//         $3: array of property names
static const char *sql_propfind_recursive = "\
with recursive resolvepath as (\n\
    select\n\
        '' as ppath,\n\
        *\n\
    from Resource\n\
    where resource_id = $1 \n\
    union\n\
    select\n\
        p.ppath || '/' || r.nodename,\n\
        r.*\n\
    from Resource r\n\
    inner join resolvepath p on r.parent_id = p.resource_id\n\
    )\n\
select\n\
    case when r.resource_id = $1 then $2\n\
         else $2 || '/' || r.ppath\n\
    end as ppath,\n\
    r.resource_id,\n\
    r.parent_id,\n\
    r.nodename,\n\
    r.iscollection,\n\
    r.lastmodified,\n\
    r.creationdate,\n\
    r.contentlength,\n\
    p.xmlns,\n\
    p.pname,\n\
    p.pvalue\n\
from resolvepath r\n\
left join Property p on r.resource_id = p.resource_id\n\
inner join (select unnest($2::text[]) as xmlns, unnest($3::text[]) as pname) n\n\
   on ( p.xmlns = n.xmlns and p.pname = n.pname ) or p.property_id is null\n\
order by replace(ppath, '/', chr(1)), resource_id;";

WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb) {
    // resourcepool is required
    char *resource_pool = pblock_findval("resourcepool", pb);
    if(!resource_pool) {
        log_ereport(LOG_MISCONFIG, "pg_webdav_create: missing resourcepool parameter");
        return NULL;
    }
    
    // get the resource first (should only fail in case of misconfig)
    ResourceData *resdata = resourcepool_lookup(sn, rq, resource_pool, 0);
    if(!resdata) {
        log_ereport(LOG_MISCONFIG, "postgresql webdav: resource pool %s not found", resource_pool);
        return NULL;
    }
    
    return pg_webdav_create_from_resdata(sn, rq, resdata);
}

WebdavBackend* pg_webdav_create_from_resdata(Session *sn, Request *rq, ResourceData *resdata) {
    WebdavBackend *webdav = pool_malloc(sn->pool, sizeof(WebdavBackend));
    if(!webdav) {
        return NULL;
    }
    *webdav = pg_webdav_backend;
    
    PgWebdavBackend *instance = pool_malloc(sn->pool, sizeof(PgWebdavBackend));
    if(!instance) {
        pool_free(sn->pool, webdav);
        return NULL;
    }
    webdav->instance = instance;
    
    instance->pg_resource = resdata;
    instance->connection = resdata->data;
    
    return webdav;
}

WebdavBackend* pg_webdav_prop_create(Session *sn, Request *rq, pblock *pb) {
    return NULL;
}


int pg_dav_propfind_init(
        WebdavPropfindRequest *rq,
        const char *path,
        const char *href,
        WebdavPList **outplist)
{
    PgWebdavBackend *pgdav = rq->dav->instance;
    
    // check if the resource exists
    int64_t parent_id;
    int64_t resource_id;
    const char *resourcename;
    WSBool iscollection;
    int res_errno = 0;
    int err = pg_resolve_path(
            pgdav->connection,
            path,
            &parent_id,
            &resource_id,
            NULL, // OID
            &resourcename,
            &iscollection,
            NULL, // stat
            &res_errno);
    
    if(err) {
        if(res_errno == ENOENT) {
            protocol_status(rq->sn, rq->rq, PROTOCOL_NOT_FOUND, NULL);
        }
        return 1;
    }
    
    // choose sql query
    const char *query = NULL;
    if(!iscollection || rq->depth == 0) {
        query = rq->allprop ? sql_propfind_allprop_depth0 : sql_propfind_depth0;
    } else if(rq->depth == 1) {
        query = rq->allprop ? sql_propfind_allprop_depth1 : sql_propfind_depth1;
    } else if(rq->depth == -1) {
        query = rq->allprop ? sql_propfind_allprop_recursive : sql_propfind_recursive;
    } else {
        log_ereport(LOG_FAILURE, "%s", "pg_dav_propfind_init: invalid depth");
        return 1;
    }
    
    // get all resources and properties
    char resource_id_str[32];
    snprintf(resource_id_str, 32, "%" PRId64, resource_id);
    
    size_t href_len = strlen(href);
    char *href_param = pool_malloc(rq->sn->pool, href_len + 1);
    memcpy(href_param, href, href_len);
    if(href_param[href_len-1] == '/') {
        href_len--;
    }
    href_param[href_len] = '\0';
    
    
    const char* params[2] = { resource_id_str, href_param };
    PGresult *result = PQexecParams(
            pgdav->connection,
            sql_propfind_allprop_recursive,
            2,     // number of parameters
            NULL,
            params, // parameter value
            NULL,
            NULL,
            0);    // 0: result in text format
    int nrows = PQntuples(result);
    pool_free(rq->sn->pool, href_param);
    if(nrows < 1) {
        PQclear(result);
        return 1;
    }
    
    PgPropfind *pg = pool_malloc(rq->sn->pool, sizeof(PgPropfind));
    rq->userdata = pg;
    
    pg->path = path;
    pg->resource_id = resource_id;
    pg->vfsproperties = webdav_vfs_properties(outplist, TRUE, 0);
    pg->result = result;
    pg->nrows = nrows;
    
    return 0;
}

int pg_dav_propfind_do(
	WebdavPropfindRequest *rq,
	WebdavResponse *response,
	VFS_DIR parent,
	WebdavResource *resource,
	struct stat *s)
{
    PgPropfind *pg = rq->userdata;
    pool_handle_t *pool = rq->sn->pool;
    PGresult *result = pg->result;
    WebdavVFSProperties vfsprops = pg->vfsproperties;
    
    WSBool vfsprops_set = 0;
    int64_t current_resource_id = pg->resource_id;
    for(int r=0;r<pg->nrows;r++) {
        // columns:
        //  0: path
        //  1: resource_id
        //  2: parent_id
        //  3: nodename
        //  4: iscollection
        //  5: lastmodified
        //  6: creationdate
        //  7: contentlength
        //  8: property xmlns
        //  9: property name
        // 10: property value
        
        char *path = PQgetvalue(result, r, 0);
        char *res_id = PQgetvalue(result, r, 1);
        char *iscollection_str = PQgetvalue(result, r, 4);
        WSBool iscollection = iscollection_str && iscollection_str[0] == 't';
        int64_t resource_id;
        if(!util_strtoint(res_id, &resource_id)) {
            log_ereport(LOG_FAILURE, "pg_dav_propfind_do: cannot convert resource_id '%s' to int", res_id);
            return 1;
        }
        
        char *nodename = PQgetvalue(result, r, 3);
        if(resource_id != current_resource_id) {
            // create a href string for the new resource
            // if the resource is a collection, it should have a trailing '/'
            size_t pathlen = strlen(path);
            if(pathlen == 0) {
                log_ereport(LOG_FAILURE, "pg_dav_propfind_do: query returned invalid path");
                return 1;
            }
            char *newres_href = pool_malloc(pool, pathlen+2);
            memcpy(newres_href, path, pathlen);
            if(iscollection && path[pathlen-1] != '/') {
                newres_href[pathlen++] = '/';
            }
            newres_href[pathlen] = '\0';
            
            // new resource
            resource = response->addresource(response, newres_href);
            vfsprops_set = FALSE;
            current_resource_id = resource_id;
        }
        
        // standard webdav properties
        if(!vfsprops_set) {
            if(vfsprops.getresourcetype) {
                if(iscollection) {
                    resource->addproperty(resource, webdav_resourcetype_collection(), 200);
                } else {
                    resource->addproperty(resource, webdav_resourcetype_empty(), 200);
                }
            }
            if(vfsprops.getlastmodified) {
                char *lastmodified = PQgetvalue(result, r, 5);
            }
            if(vfsprops.creationdate) {
                char *creationdate = PQgetvalue(result, r, 6);
            }
            if(vfsprops.getcontentlength) {
                char *contentlength = PQgetvalue(result, r, 7);
                webdav_resource_add_dav_stringproperty(resource, pool, "getcontentlength", contentlength, strlen(contentlength));
            }
            if(vfsprops.getetag) {
                
            }
            
            
            vfsprops_set = TRUE;
        }
        
        // dead properties
        if(!PQgetisnull(result, r, 9)) {
            char *xmlns = PQgetvalue(result, r, 8);
            char *pname = PQgetvalue(result, r, 9);
            char *pvalue = PQgetvalue(result, r, 10);
            
            WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
            property->lang = NULL;
            property->name = pool_strdup(pool, pname);
            
            xmlNs *namespace = pool_malloc(pool, sizeof(xmlNs));
            memset(namespace, 0, sizeof(struct _xmlNs));
            namespace->href = (xmlChar*)pool_strdup(pool, xmlns);
            namespace->prefix = (xmlChar*)"zx1"; // TODO
            property->namespace = namespace;
            
            property->vtype = WS_VALUE_TEXT;
            property->value.text.str = pool_strdup(pool, pvalue);
            property->value.text.length = strlen(pvalue);
            
            resource->addproperty(resource, property, 200);
        }
        
        
    }
    

    
    return 0;
}

int pg_dav_propfind_finish(WebdavPropfindRequest *rq) {
    return 0;
}

int pg_dav_proppatch_do(
	WebdavProppatchRequest *request,
	WebdavResource *response,
	VFSFile *file,
	WebdavPList **out_set,
	WebdavPList **out_remove)
{
    return 1;
}

int pg_dav_proppatch_finish(
	WebdavProppatchRequest *request,
	WebdavResource *response,
	VFSFile *file,
	WSBool commit)
{
    return 1;
}

mercurial