/* 
   WebDAV property manipulation
   Copyright (C) 2000-2004, Joe Orton <joe@manyfish.co.uk>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
   
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA

*/

#include "config.h"

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include "ne_alloc.h"
#include "ne_xml.h"
#include "ne_props.h"
#include "ne_basic.h"
#include "ne_locks.h"

/* don't store flat props with a value > 10K */
#define MAX_FLATPROP_LEN (102400)

struct ne_propfind_handler_s {
    ne_session *sess;
    ne_request *request;

    int has_props; /* whether we've already written some
		    * props to the body. */
    ne_buffer *body;
    
    ne_207_parser *parser207;
    ne_xml_parser *parser;

    /* Callback to create the private structure. */
    ne_props_create_complex private_creator;
    void *private_userdata;
    
    /* Current propset, or NULL if none being processed. */
    ne_prop_result_set *current;

    ne_buffer *value; /* current flat property value */
    int depth; /* nesting depth within a flat property */

    ne_props_result callback;
    void *userdata;
};

#define ELM_flatprop (NE_207_STATE_TOP - 1)

/* We build up the results of one 'response' element in memory. */
struct prop {
    char *name, *nspace, *value, *lang;
    /* Store a ne_propname here too, for convienience.  pname.name =
     * name, pname.nspace = nspace, but they are const'ed in pname. */
    ne_propname pname;
};

#define NSPACE(x) ((x) ? (x) : "")

struct propstat {
    struct prop *props;
    int numprops;
    ne_status status;
};

/* Results set. */
struct ne_prop_result_set_s {
    struct propstat *pstats;
    int numpstats;
    void *private;
    char *href;
};


static int 
startelm(void *userdata, int state, const char *name, const char *nspace,
	 const char **atts);
static int 
endelm(void *userdata, int state, const char *name, const char *nspace);

/* Handle character data; flat property value. */
static int chardata(void *userdata, int state, const char *data, size_t len)
{
    ne_propfind_handler *hdl = userdata;

    if (state == ELM_flatprop && hdl->value->length < MAX_FLATPROP_LEN)
        ne_buffer_append(hdl->value, data, len);

    return 0;
}

ne_xml_parser *ne_propfind_get_parser(ne_propfind_handler *handler)
{
    return handler->parser;
}

ne_request *ne_propfind_get_request(ne_propfind_handler *handler)
{
    return handler->request;
}

static int propfind(ne_propfind_handler *handler, 
		    ne_props_result results, void *userdata)
{
    int ret;
    ne_request *req = handler->request;

    /* Register the flat property handler to catch any properties 
     * which the user isn't handling as 'complex'. */
    ne_xml_push_handler(handler->parser, startelm, chardata, endelm, handler);

    handler->callback = results;
    handler->userdata = userdata;

    ne_set_request_body_buffer(req, handler->body->data,
			       ne_buffer_size(handler->body));

    ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
    
    ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v, 
				  handler->parser);

    ret = ne_request_dispatch(req);

    if (ret == NE_OK && ne_get_status(req)->klass != 2) {
	ret = NE_ERROR;
    } else if (!ne_xml_valid(handler->parser)) {
	ne_set_error(handler->sess, "%s", ne_xml_get_error(handler->parser));
	ret = NE_ERROR;
    }

    return ret;
}

static void set_body(ne_propfind_handler *hdl, const ne_propname *names)
{
    ne_buffer *body = hdl->body;
    int n;
    
    if (!hdl->has_props) {
	ne_buffer_zappend(body, "<prop>" EOL);
	hdl->has_props = 1;
    }

    for (n = 0; names[n].name != NULL; n++) {
	ne_buffer_concat(body, "<", names[n].name, " xmlns=\"", 
			 NSPACE(names[n].nspace), "\"/>" EOL, NULL);
    }

}

int ne_propfind_allprop(ne_propfind_handler *handler, 
			 ne_props_result results, void *userdata)
{
    ne_buffer_zappend(handler->body, "<allprop/></propfind>" EOL);
    return propfind(handler, results, userdata);
}

int ne_propfind_named(ne_propfind_handler *handler, const ne_propname *props,
		       ne_props_result results, void *userdata)
{
    set_body(handler, props);
    ne_buffer_zappend(handler->body, "</prop></propfind>" EOL);
    return propfind(handler, results, userdata);
}


/* The easy one... PROPPATCH */
int ne_proppatch(ne_session *sess, const char *uri, 
		 const ne_proppatch_operation *items)
{
    ne_request *req = ne_request_create(sess, "PROPPATCH", uri);
    ne_buffer *body = ne_buffer_create();
    int n, ret;
    
    /* Create the request body */
    ne_buffer_zappend(body, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" EOL
		     "<D:propertyupdate xmlns:D=\"DAV:\">");

    for (n = 0; items[n].name != NULL; n++) {
	const char *elm = (items[n].type == ne_propset) ? "set" : "remove";

	/* <set><prop><prop-name>value</prop-name></prop></set> */
	ne_buffer_concat(body, "<D:", elm, "><D:prop>"
			 "<", items[n].name->name, NULL);
	
	if (items[n].name->nspace) {
	    ne_buffer_concat(body, " xmlns=\"", items[n].name->nspace, "\"", NULL);
	}

	if (items[n].type == ne_propset) {
	    ne_buffer_concat(body, ">", items[n].value, NULL);
	} else {
	    ne_buffer_append(body, ">", 1);
	}

	ne_buffer_concat(body, "</", items[n].name->name, "></D:prop></D:", elm, ">"
			 EOL, NULL);
    }	

    ne_buffer_zappend(body, "</D:propertyupdate>" EOL);

    ne_set_request_body_buffer(req, body->data, ne_buffer_size(body));
    ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
    
#ifdef USE_DAV_LOCKS
    ne_lock_using_resource(req, uri, NE_DEPTH_ZERO);
#endif

    ret = ne_simple_request(sess, req);
    
    ne_buffer_destroy(body);

    return ret;
}

/* Compare two property names. */
static int pnamecmp(const ne_propname *pn1, const ne_propname *pn2)
{
    if (pn1->nspace == NULL && pn2->nspace != NULL) {
	return 1;
    } else if (pn1->nspace != NULL && pn2->nspace == NULL) {
	return -1;
    } else if (pn1->nspace == NULL) {
	return strcmp(pn1->name, pn2->name);
    } else {
	return (strcmp(pn1->nspace, pn2->nspace) ||
		strcmp(pn1->name, pn2->name));
    }
}

/* Find property in 'set' with name 'pname'.  If found, set pstat_ret
 * to the containing propstat, likewise prop_ret, and returns zero.
 * If not found, returns non-zero.  */
static int findprop(const ne_prop_result_set *set, const ne_propname *pname,
		    struct propstat **pstat_ret, struct prop **prop_ret)
{
    
    int ps, p;

    for (ps = 0; ps < set->numpstats; ps++) {
	for (p = 0; p < set->pstats[ps].numprops; p++) {
	    struct prop *prop = &set->pstats[ps].props[p];

	    if (pnamecmp(&prop->pname, pname) == 0) {
		if (pstat_ret != NULL)
		    *pstat_ret = &set->pstats[ps];
		if (prop_ret != NULL)
		    *prop_ret = prop;
		return 0;
	    }
	}
    }

    return -1;
}

const char *ne_propset_value(const ne_prop_result_set *set,
			      const ne_propname *pname)
{
    struct prop *prop;
    
    if (findprop(set, pname, NULL, &prop)) {
	return NULL;
    } else {
	return prop->value;
    }
}

const char *ne_propset_lang(const ne_prop_result_set *set,
			     const ne_propname *pname)
{
    struct prop *prop;

    if (findprop(set, pname, NULL, &prop)) {
	return NULL;
    } else {
	return prop->lang;
    }
}

void *ne_propfind_current_private(ne_propfind_handler *handler)
{
    return handler->current ? handler->current->private : NULL;
}

void *ne_propset_private(const ne_prop_result_set *set)
{
    return set->private;
}

int ne_propset_iterate(const ne_prop_result_set *set,
			ne_propset_iterator iterator, void *userdata)
{
    int ps, p;

    for (ps = 0; ps < set->numpstats; ps++) {
	for (p = 0; p < set->pstats[ps].numprops; p++) {
	    struct prop *prop = &set->pstats[ps].props[p];
	    int ret = iterator(userdata, &prop->pname, prop->value, 
			       &set->pstats[ps].status);
	    if (ret)
		return ret;

	}
    }

    return 0;
}

const ne_status *ne_propset_status(const ne_prop_result_set *set,
				      const ne_propname *pname)
{
    struct propstat *pstat;
    
    if (findprop(set, pname, &pstat, NULL)) {
	/* TODO: it is tempting to return a dummy status object here
	 * rather than NULL, which says "Property result was not given
	 * by server."  but I'm not sure if this is best left to the
	 * client.  */
	return NULL;
    } else {
	return &pstat->status;
    }
}

static void *start_response(void *userdata, const char *href)
{
    ne_prop_result_set *set = ne_calloc(sizeof(*set));
    ne_propfind_handler *hdl = userdata;

    set->href = ne_strdup(href);

    if (hdl->private_creator != NULL) {
	set->private = hdl->private_creator(hdl->private_userdata, href);
    }

    hdl->current = set;

    return set;
}

static void *start_propstat(void *userdata, void *response)
{
    ne_prop_result_set *set = response;
    int n;
    struct propstat *pstat;

    n = set->numpstats;
    set->pstats = ne_realloc(set->pstats, sizeof(struct propstat) * (n+1));
    set->numpstats = n+1;

    pstat = &set->pstats[n];
    memset(pstat, 0, sizeof(*pstat));
    
    /* And return this as the new pstat. */
    return &set->pstats[n];
}

static int startelm(void *userdata, int parent,
                    const char *nspace, const char *name, const char **atts)
{
    ne_propfind_handler *hdl = userdata;
    struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207);
    struct prop *prop;
    int n;
    const char *lang;

    /* Just handle all children of propstat and their descendants. */
    if ((parent != NE_207_STATE_PROP && parent != ELM_flatprop) 
        || pstat == NULL)
        return NE_XML_DECLINE;

    if (parent == ELM_flatprop) {
        /* collecting the flatprop value. */
        hdl->depth++;
        if (hdl->value->used < MAX_FLATPROP_LEN)
            ne_buffer_concat(hdl->value, "<", name, ">", NULL);
        return ELM_flatprop;
    }        

    /* Add a property to this propstat */
    n = pstat->numprops;

    pstat->props = ne_realloc(pstat->props, sizeof(struct prop) * (n + 1));
    pstat->numprops = n+1;

    /* Fill in the new property. */
    prop = &pstat->props[n];

    prop->pname.name = prop->name = ne_strdup(name);
    if (nspace[0] == '\0') {
	prop->pname.nspace = prop->nspace = NULL;
    } else {
	prop->pname.nspace = prop->nspace = ne_strdup(nspace);
    }
    prop->value = NULL;

    NE_DEBUG(NE_DBG_XML, "Got property #%d: {%s}%s.\n", n, 
	     NSPACE(prop->nspace), prop->name);

    /* This is under discussion at time of writing (April '01), and it
     * looks like we need to retrieve the xml:lang property from any
     * element here or above.
     *
     * Also, I think we might need attribute namespace handling here.  */
    lang = ne_xml_get_attr(hdl->parser, atts, NULL, "xml:lang");
    if (lang != NULL) {
	prop->lang = ne_strdup(lang);
	NE_DEBUG(NE_DBG_XML, "Property language is %s\n", prop->lang);
    } else {
	prop->lang = NULL;
    }

    hdl->depth = 0;

    return ELM_flatprop;
}

static int endelm(void *userdata, int state,
                  const char *nspace, const char *name)
{
    ne_propfind_handler *hdl = userdata;
    struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207);
    int n;

    if (hdl->depth > 0) {
        /* nested. */
        if (hdl->value->used < MAX_FLATPROP_LEN)
            ne_buffer_concat(hdl->value, "</", name, ">", NULL);
        hdl->depth--;
    } else {
        /* end of the current property value */
        n = pstat->numprops - 1;
        pstat->props[n].value = ne_buffer_finish(hdl->value);
        hdl->value = ne_buffer_create();
    }
    return 0;
}

static void end_propstat(void *userdata, void *pstat_v, 
			 const ne_status *status,
			 const char *description)
{
    struct propstat *pstat = pstat_v;

    /* Nothing to do if no status was given. */
    if (!status) return;

    /* If we get a non-2xx response back here, we wipe the value for
     * each of the properties in this propstat, so the caller knows to
     * look at the status instead. It's annoying, since for each prop
     * we will have done an unnecessary strdup("") above, but there is
     * no easy way round that given the fact that we don't know
     * whether we've got an error or not till after we get the
     * property element.
     *
     * Interestingly IIS breaks the 2518 DTD and puts the status
     * element first in the propstat. This is useful since then we
     * *do* know whether each subsequent empty prop element means, but
     * we can't rely on that here. */
    if (status->klass != 2) {
	int n;
	
	for (n = 0; n < pstat->numprops; n++) {
	    ne_free(pstat->props[n].value);
	    pstat->props[n].value = NULL;
	}
    }

    /* copy the status structure, and dup the reason phrase. */
    pstat->status = *status;
    pstat->status.reason_phrase = ne_strdup(status->reason_phrase);
}

/* Frees up a results set */
static void free_propset(ne_prop_result_set *set)
{
    int n;
    
    for (n = 0; n < set->numpstats; n++) {
	int m;
	struct propstat *p = &set->pstats[n];

	for (m = 0; m < p->numprops; m++) {
	    NE_FREE(p->props[m].nspace);
	    ne_free(p->props[m].name);
	    NE_FREE(p->props[m].lang);
	    NE_FREE(p->props[m].value);
	}

	if (p->status.reason_phrase)
	    ne_free(p->status.reason_phrase);
	if (p->props)
	    ne_free(p->props);
    }

    if (set->pstats)
	ne_free(set->pstats);
    ne_free(set->href);
    ne_free(set);
}

static void end_response(void *userdata, void *resource,
			 const ne_status *status,
			 const char *description)
{
    ne_propfind_handler *handler = userdata;
    ne_prop_result_set *set = resource;

    /* Pass back the results for this resource. */
    if (handler->callback && set->numpstats > 0)
	handler->callback(handler->userdata, set->href, set);

    /* Clean up the propset tree we've just built. */
    free_propset(set);
    handler->current = NULL;
}

ne_propfind_handler *
ne_propfind_create(ne_session *sess, const char *uri, int depth)
{
    ne_propfind_handler *ret = ne_calloc(sizeof(ne_propfind_handler));

    ret->parser = ne_xml_create();
    ret->parser207 = ne_207_create(ret->parser, ret);
    ret->sess = sess;
    ret->body = ne_buffer_create();
    ret->request = ne_request_create(sess, "PROPFIND", uri);
    ret->value = ne_buffer_create();

    ne_add_depth_header(ret->request, depth);

    ne_207_set_response_handlers(ret->parser207, 
				  start_response, end_response);

    ne_207_set_propstat_handlers(ret->parser207, start_propstat,
				  end_propstat);

    /* The start of the request body is fixed: */
    ne_buffer_concat(ret->body, 
		    "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL 
		    "<propfind xmlns=\"DAV:\">", NULL);

    return ret;
}

/* Destroy a propfind handler */
void ne_propfind_destroy(ne_propfind_handler *handler)
{
    ne_buffer_destroy(handler->value);
    if (handler->current)
        free_propset(handler->current);
    ne_207_destroy(handler->parser207);
    ne_xml_destroy(handler->parser);
    ne_buffer_destroy(handler->body);
    ne_request_destroy(handler->request);
    ne_free(handler);    
}

int ne_simple_propfind(ne_session *sess, const char *href, int depth,
			const ne_propname *props,
			ne_props_result results, void *userdata)
{
    ne_propfind_handler *hdl;
    int ret;

    hdl = ne_propfind_create(sess, href, depth);
    if (props != NULL) {
	ret = ne_propfind_named(hdl, props, results, userdata);
    } else {
	ret = ne_propfind_allprop(hdl, results, userdata);
    }
	
    ne_propfind_destroy(hdl);
    
    return ret;
}

int ne_propnames(ne_session *sess, const char *href, int depth,
		  ne_props_result results, void *userdata)
{
    ne_propfind_handler *hdl;
    int ret;

    hdl = ne_propfind_create(sess, href, depth);

    ne_buffer_zappend(hdl->body, "<propname/></propfind>");

    ret = propfind(hdl, results, userdata);

    ne_propfind_destroy(hdl);

    return ret;
}

void ne_propfind_set_private(ne_propfind_handler *hdl,
			      ne_props_create_complex creator,
			      void *userdata)
{
    hdl->private_creator = creator;
    hdl->private_userdata = userdata;
}
