1 /*
2    WebDAV property manipulation
3    Copyright (C) 2000-2008, Joe Orton <joe@manyfish.co.uk>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public
16    License along with this library; if not, write to the Free
17    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
18    MA 02111-1307, USA
19 
20 */
21 
22 #include "config.h"
23 
24 #ifdef HAVE_STDLIB_H
25 #include <stdlib.h>
26 #endif
27 #ifdef HAVE_STRING_H
28 #include <string.h>
29 #endif
30 
31 #include "ne_alloc.h"
32 #include "ne_xml.h"
33 #include "ne_props.h"
34 #include "ne_basic.h"
35 #include "ne_locks.h"
36 #include "ne_internal.h"
37 
38 /* don't store flat props with a value > 10K */
39 #define MAX_FLATPROP_LEN (102400)
40 
41 struct ne_propfind_handler_s {
42     ne_session *sess;
43     ne_request *request;
44 
45     int has_props; /* whether we've already written some
46 		    * props to the body. */
47     ne_buffer *body;
48 
49     ne_207_parser *parser207;
50     ne_xml_parser *parser;
51 
52     /* Creator/destructor callbacks. */
53     ne_props_create_complex creator;
54     ne_props_destroy_complex destructor;
55     void *cd_userdata;
56 
57     /* Current propset, or NULL if none being processed. */
58     ne_prop_result_set *current;
59 
60     ne_buffer *value; /* current flat property value */
61     int depth; /* nesting depth within a flat property */
62 
63     ne_props_result callback;
64     void *userdata;
65 };
66 
67 #define ELM_flatprop (NE_207_STATE_TOP - 1)
68 
69 /* We build up the results of one 'response' element in memory. */
70 struct prop {
71     char *name, *nspace, *value, *lang;
72     /* Store a ne_propname here too, for convienience.  pname.name =
73      * name, pname.nspace = nspace, but they are const'ed in pname. */
74     ne_propname pname;
75 };
76 
77 #define NSPACE(x) ((x) ? (x) : "")
78 
79 struct propstat {
80     struct prop *props;
81     int numprops;
82     ne_status status;
83 };
84 
85 /* Results set. */
86 struct ne_prop_result_set_s {
87     struct propstat *pstats;
88     int numpstats, counter;
89     void *private;
90     ne_uri uri;
91 };
92 
93 #define MAX_PROP_COUNTER (1024)
94 
95 static int
96 startelm(void *userdata, int state, const char *name, const char *nspace,
97 	 const char **atts);
98 static int
99 endelm(void *userdata, int state, const char *name, const char *nspace);
100 
101 /* Handle character data; flat property value. */
chardata(void * userdata,int state,const char * data,size_t len)102 static int chardata(void *userdata, int state, const char *data, size_t len)
103 {
104     ne_propfind_handler *hdl = userdata;
105 
106     if (state == ELM_flatprop && hdl->value->length < MAX_FLATPROP_LEN)
107         ne_buffer_append(hdl->value, data, len);
108 
109     return 0;
110 }
111 
ne_propfind_get_parser(ne_propfind_handler * handler)112 ne_xml_parser *ne_propfind_get_parser(ne_propfind_handler *handler)
113 {
114     return handler->parser;
115 }
116 
ne_propfind_get_request(ne_propfind_handler * handler)117 ne_request *ne_propfind_get_request(ne_propfind_handler *handler)
118 {
119     return handler->request;
120 }
121 
propfind(ne_propfind_handler * handler,ne_props_result results,void * userdata)122 static int propfind(ne_propfind_handler *handler,
123 		    ne_props_result results, void *userdata)
124 {
125     int ret;
126     ne_request *req = handler->request;
127 
128     /* Register the flat property handler to catch any properties
129      * which the user isn't handling as 'complex'. */
130     ne_xml_push_handler(handler->parser, startelm, chardata, endelm, handler);
131 
132     handler->callback = results;
133     handler->userdata = userdata;
134 
135     ne_set_request_body_buffer(req, handler->body->data,
136 			       ne_buffer_size(handler->body));
137 
138     ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
139 
140     ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v,
141 				  handler->parser);
142 
143     ret = ne_request_dispatch(req);
144 
145     if (ret == NE_OK && ne_get_status(req)->klass != 2) {
146 	ret = NE_ERROR;
147     } else if (ne_xml_failed(handler->parser)) {
148 	ne_set_error(handler->sess, "%s", ne_xml_get_error(handler->parser));
149 	ret = NE_ERROR;
150     }
151 
152     return ret;
153 }
154 
set_body(ne_propfind_handler * hdl,const ne_propname * names)155 static void set_body(ne_propfind_handler *hdl, const ne_propname *names)
156 {
157     ne_buffer *body = hdl->body;
158     int n;
159 
160     if (!hdl->has_props) {
161 	ne_buffer_czappend(body, "<prop>\n");
162 	hdl->has_props = 1;
163     }
164 
165     for (n = 0; names[n].name != NULL; n++) {
166 	ne_buffer_concat(body, "<", names[n].name, " xmlns=\"",
167 			 NSPACE(names[n].nspace), "\"/>\n", NULL);
168     }
169 
170 }
171 
ne_propfind_allprop(ne_propfind_handler * handler,ne_props_result results,void * userdata)172 int ne_propfind_allprop(ne_propfind_handler *handler,
173 			 ne_props_result results, void *userdata)
174 {
175     ne_buffer_czappend(handler->body, "<allprop/></propfind>\n");
176     return propfind(handler, results, userdata);
177 }
178 
ne_propfind_named(ne_propfind_handler * handler,const ne_propname * props,ne_props_result results,void * userdata)179 int ne_propfind_named(ne_propfind_handler *handler, const ne_propname *props,
180 		       ne_props_result results, void *userdata)
181 {
182     set_body(handler, props);
183     ne_buffer_czappend(handler->body, "</prop></propfind>\n");
184     return propfind(handler, results, userdata);
185 }
186 
187 
188 /* The easy one... PROPPATCH */
ne_proppatch(ne_session * sess,const char * uri,const ne_proppatch_operation * items)189 int ne_proppatch(ne_session *sess, const char *uri,
190 		 const ne_proppatch_operation *items)
191 {
192     ne_request *req = ne_request_create(sess, "PROPPATCH", uri);
193     ne_buffer *body = ne_buffer_create();
194     int n, ret;
195 
196     /* Create the request body */
197     ne_buffer_czappend(body, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
198                        "<D:propertyupdate xmlns:D=\"DAV:\">");
199 
200     for (n = 0; items[n].name != NULL; n++) {
201 	const char *elm = (items[n].type == ne_propset) ? "set" : "remove";
202 
203 	/* <set><prop><prop-name>value</prop-name></prop></set> */
204 	ne_buffer_concat(body, "<D:", elm, "><D:prop>"
205 			 "<", items[n].name->name, NULL);
206 
207 	if (items[n].name->nspace) {
208 	    ne_buffer_concat(body, " xmlns=\"", items[n].name->nspace, "\"", NULL);
209 	}
210 
211 	if (items[n].type == ne_propset) {
212 	    ne_buffer_concat(body, ">", items[n].value, NULL);
213 	} else {
214 	    ne_buffer_append(body, ">", 1);
215 	}
216 
217 	ne_buffer_concat(body, "</", items[n].name->name, "></D:prop></D:", elm,
218                          ">\n", NULL);
219     }
220 
221     ne_buffer_czappend(body, "</D:propertyupdate>\n");
222 
223     ne_set_request_body_buffer(req, body->data, ne_buffer_size(body));
224     ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
225 
226 #ifdef NE_HAVE_DAV
227     ne_lock_using_resource(req, uri, NE_DEPTH_ZERO);
228 #endif
229 
230     ret = ne_simple_request(sess, req);
231 
232     ne_buffer_destroy(body);
233 
234     return ret;
235 }
236 
237 /* Compare two property names. */
pnamecmp(const ne_propname * pn1,const ne_propname * pn2)238 static int pnamecmp(const ne_propname *pn1, const ne_propname *pn2)
239 {
240     if (pn1->nspace == NULL && pn2->nspace != NULL) {
241 	return 1;
242     } else if (pn1->nspace != NULL && pn2->nspace == NULL) {
243 	return -1;
244     } else if (pn1->nspace == NULL) {
245 	return strcmp(pn1->name, pn2->name);
246     } else {
247 	return (strcmp(pn1->nspace, pn2->nspace) ||
248 		strcmp(pn1->name, pn2->name));
249     }
250 }
251 
252 /* Find property in 'set' with name 'pname'.  If found, set pstat_ret
253  * to the containing propstat, likewise prop_ret, and returns zero.
254  * If not found, returns non-zero.  */
findprop(const ne_prop_result_set * set,const ne_propname * pname,struct propstat ** pstat_ret,struct prop ** prop_ret)255 static int findprop(const ne_prop_result_set *set, const ne_propname *pname,
256 		    struct propstat **pstat_ret, struct prop **prop_ret)
257 {
258 
259     int ps, p;
260 
261     for (ps = 0; ps < set->numpstats; ps++) {
262 	for (p = 0; p < set->pstats[ps].numprops; p++) {
263 	    struct prop *prop = &set->pstats[ps].props[p];
264 
265 	    if (pnamecmp(&prop->pname, pname) == 0) {
266 		if (pstat_ret != NULL)
267 		    *pstat_ret = &set->pstats[ps];
268 		if (prop_ret != NULL)
269 		    *prop_ret = prop;
270 		return 0;
271 	    }
272 	}
273     }
274 
275     return -1;
276 }
277 
ne_propset_value(const ne_prop_result_set * set,const ne_propname * pname)278 const char *ne_propset_value(const ne_prop_result_set *set,
279 			      const ne_propname *pname)
280 {
281     struct prop *prop;
282 
283     if (findprop(set, pname, NULL, &prop)) {
284 	return NULL;
285     } else {
286 	return prop->value;
287     }
288 }
289 
ne_propset_lang(const ne_prop_result_set * set,const ne_propname * pname)290 const char *ne_propset_lang(const ne_prop_result_set *set,
291 			     const ne_propname *pname)
292 {
293     struct prop *prop;
294 
295     if (findprop(set, pname, NULL, &prop)) {
296 	return NULL;
297     } else {
298 	return prop->lang;
299     }
300 }
301 
ne_propfind_current_private(ne_propfind_handler * handler)302 void *ne_propfind_current_private(ne_propfind_handler *handler)
303 {
304     return handler->current ? handler->current->private : NULL;
305 }
306 
ne_propset_private(const ne_prop_result_set * set)307 void *ne_propset_private(const ne_prop_result_set *set)
308 {
309     return set->private;
310 }
311 
ne_propset_iterate(const ne_prop_result_set * set,ne_propset_iterator iterator,void * userdata)312 int ne_propset_iterate(const ne_prop_result_set *set,
313 			ne_propset_iterator iterator, void *userdata)
314 {
315     int ps, p;
316 
317     for (ps = 0; ps < set->numpstats; ps++) {
318 	for (p = 0; p < set->pstats[ps].numprops; p++) {
319 	    struct prop *prop = &set->pstats[ps].props[p];
320 	    int ret = iterator(userdata, &prop->pname, prop->value,
321 			       &set->pstats[ps].status);
322 	    if (ret)
323 		return ret;
324 
325 	}
326     }
327 
328     return 0;
329 }
330 
ne_propset_status(const ne_prop_result_set * set,const ne_propname * pname)331 const ne_status *ne_propset_status(const ne_prop_result_set *set,
332 				      const ne_propname *pname)
333 {
334     struct propstat *pstat;
335 
336     if (findprop(set, pname, &pstat, NULL)) {
337 	/* TODO: it is tempting to return a dummy status object here
338 	 * rather than NULL, which says "Property result was not given
339 	 * by server."  but I'm not sure if this is best left to the
340 	 * client.  */
341 	return NULL;
342     } else {
343 	return &pstat->status;
344     }
345 }
346 
start_response(void * userdata,const ne_uri * uri)347 static void *start_response(void *userdata, const ne_uri *uri)
348 {
349     ne_prop_result_set *set = ne_calloc(sizeof(*set));
350     ne_propfind_handler *hdl = userdata;
351 
352     ne_uri_copy(&set->uri, uri);
353 
354     if (hdl->creator) {
355 	set->private = hdl->creator(hdl->cd_userdata, &set->uri);
356     }
357 
358     hdl->current = set;
359 
360     return set;
361 }
362 
start_propstat(void * userdata,void * response)363 static void *start_propstat(void *userdata, void *response)
364 {
365     ne_prop_result_set *set = response;
366     ne_propfind_handler *hdl = userdata;
367     struct propstat *pstat;
368     int n;
369 
370     if (++hdl->current->counter == MAX_PROP_COUNTER) {
371         ne_xml_set_error(hdl->parser, _("Response exceeds maximum property count"));
372         return NULL;
373     }
374 
375     n = set->numpstats;
376     set->pstats = ne_realloc(set->pstats, sizeof(struct propstat) * (n+1));
377     set->numpstats = n+1;
378 
379     pstat = &set->pstats[n];
380     memset(pstat, 0, sizeof(*pstat));
381 
382     /* And return this as the new pstat. */
383     return &set->pstats[n];
384 }
385 
startelm(void * userdata,int parent,const char * nspace,const char * name,const char ** atts)386 static int startelm(void *userdata, int parent,
387                     const char *nspace, const char *name, const char **atts)
388 {
389     ne_propfind_handler *hdl = userdata;
390     struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207);
391     struct prop *prop;
392     int n;
393     const char *lang;
394 
395     /* Just handle all children of propstat and their descendants. */
396     if ((parent != NE_207_STATE_PROP && parent != ELM_flatprop)
397         || pstat == NULL)
398         return NE_XML_DECLINE;
399 
400     if (parent == ELM_flatprop) {
401         /* collecting the flatprop value. */
402         hdl->depth++;
403         if (hdl->value->used < MAX_FLATPROP_LEN) {
404             const char **a = atts;
405 
406             ne_buffer_concat(hdl->value, "<", nspace, name, NULL);
407 
408             while (a[0] && hdl->value->used < MAX_FLATPROP_LEN) {
409                 const char *nsep = strchr(a[0], ':'), *pfx;
410 
411                 /* Resolve the attribute namespace prefix, if any.
412                  * Ignore a failure to resolve the namespace prefix. */
413                 pfx = nsep ? ne_xml_resolve_nspace(hdl->parser,
414                                                    a[0], nsep - a[0]) : NULL;
415 
416                 if (pfx) {
417                     ne_buffer_concat(hdl->value, " ", pfx, nsep + 1, "='",
418                                      a[1], "'", NULL);
419                 }
420                 else {
421                     ne_buffer_concat(hdl->value, " ", a[0], "='", a[1], "'", NULL);
422                 }
423                 a += 2;
424             }
425 
426             ne_buffer_czappend(hdl->value, ">");
427         }
428 
429         return ELM_flatprop;
430     }
431 
432     /* Enforce maximum number of properties per resource to prevent a
433      * memory exhaustion attack by a hostile server. */
434     if (++hdl->current->counter == MAX_PROP_COUNTER) {
435         ne_xml_set_error(hdl->parser, _("Response exceeds maximum property count"));
436         return NE_XML_ABORT;
437     }
438 
439     /* Add a property to this propstat */
440     n = pstat->numprops;
441 
442     pstat->props = ne_realloc(pstat->props, sizeof(struct prop) * (n + 1));
443     pstat->numprops = n+1;
444 
445     /* Fill in the new property. */
446     prop = &pstat->props[n];
447 
448     prop->pname.name = prop->name = ne_strdup(name);
449     if (nspace[0] == '\0') {
450 	prop->pname.nspace = prop->nspace = NULL;
451     } else {
452 	prop->pname.nspace = prop->nspace = ne_strdup(nspace);
453     }
454     prop->value = NULL;
455 
456     NE_DEBUG(NE_DBG_XML, "Got property #%d: {%s}%s.\n", n,
457 	     NSPACE(prop->nspace), prop->name);
458 
459     /* This is under discussion at time of writing (April '01), and it
460      * looks like we need to retrieve the xml:lang property from any
461      * element here or above.
462      *
463      * Also, I think we might need attribute namespace handling here.  */
464     lang = ne_xml_get_attr(hdl->parser, atts, NULL, "xml:lang");
465     if (lang != NULL) {
466 	prop->lang = ne_strdup(lang);
467 	NE_DEBUG(NE_DBG_XML, "Property language is %s\n", prop->lang);
468     } else {
469 	prop->lang = NULL;
470     }
471 
472     hdl->depth = 0;
473 
474     return ELM_flatprop;
475 }
476 
endelm(void * userdata,int state,const char * nspace,const char * name)477 static int endelm(void *userdata, int state,
478                   const char *nspace, const char *name)
479 {
480     ne_propfind_handler *hdl = userdata;
481     struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207);
482     int n;
483 
484     if (hdl->depth > 0) {
485         /* nested. */
486         if (hdl->value->used < MAX_FLATPROP_LEN)
487             ne_buffer_concat(hdl->value, "</", nspace, name, ">", NULL);
488         hdl->depth--;
489     } else {
490         /* end of the current property value */
491         n = pstat->numprops - 1;
492         pstat->props[n].value = ne_buffer_finish(hdl->value);
493         hdl->value = ne_buffer_create();
494     }
495     return 0;
496 }
497 
end_propstat(void * userdata,void * pstat_v,const ne_status * status,const char * description)498 static void end_propstat(void *userdata, void *pstat_v,
499 			 const ne_status *status,
500 			 const char *description)
501 {
502     struct propstat *pstat = pstat_v;
503 
504     /* Nothing to do if no status was given. */
505     if (!status) return;
506 
507     /* If we get a non-2xx response back here, we wipe the value for
508      * each of the properties in this propstat, so the caller knows to
509      * look at the status instead. It's annoying, since for each prop
510      * we will have done an unnecessary strdup("") above, but there is
511      * no easy way round that given the fact that we don't know
512      * whether we've got an error or not till after we get the
513      * property element.
514      *
515      * Interestingly IIS breaks the 2518 DTD and puts the status
516      * element first in the propstat. This is useful since then we
517      * *do* know whether each subsequent empty prop element means, but
518      * we can't rely on that here. */
519     if (status->klass != 2) {
520 	int n;
521 
522 	for (n = 0; n < pstat->numprops; n++) {
523 	    ne_free(pstat->props[n].value);
524 	    pstat->props[n].value = NULL;
525 	}
526     }
527 
528     /* copy the status structure, and dup the reason phrase. */
529     pstat->status = *status;
530     pstat->status.reason_phrase = ne_strdup(status->reason_phrase);
531 }
532 
533 /* Frees up a results set */
free_propset(ne_propfind_handler * handler,ne_prop_result_set * set)534 static void free_propset(ne_propfind_handler *handler,
535                          ne_prop_result_set *set)
536 {
537     int n;
538 
539     if (handler->destructor && set->private) {
540         handler->destructor(handler->cd_userdata, set->private);
541     }
542 
543     for (n = 0; n < set->numpstats; n++) {
544 	int m;
545 	struct propstat *p = &set->pstats[n];
546 
547 	for (m = 0; m < p->numprops; m++) {
548             if (p->props[m].nspace) ne_free(p->props[m].nspace);
549             ne_free(p->props[m].name);
550             if (p->props[m].lang) ne_free(p->props[m].lang);
551             if (p->props[m].value) ne_free(p->props[m].value);
552             p->props[m].nspace = p->props[m].lang =
553                 p->props[m].value = NULL;
554 	}
555 
556 	if (p->status.reason_phrase)
557 	    ne_free(p->status.reason_phrase);
558 	if (p->props)
559 	    ne_free(p->props);
560     }
561 
562     if (set->pstats)
563 	ne_free(set->pstats);
564     ne_uri_free(&set->uri);
565     ne_free(set);
566 }
567 
end_response(void * userdata,void * resource,const ne_status * status,const char * description)568 static void end_response(void *userdata, void *resource,
569 			 const ne_status *status,
570 			 const char *description)
571 {
572     ne_propfind_handler *handler = userdata;
573     ne_prop_result_set *set = resource;
574 
575     /* Pass back the results for this resource. */
576     if (handler->callback && set->numpstats > 0)
577 	handler->callback(handler->userdata, &set->uri, set);
578 
579     /* Clean up the propset tree we've just built. */
580     free_propset(handler, set);
581     handler->current = NULL;
582 }
583 
584 ne_propfind_handler *
ne_propfind_create(ne_session * sess,const char * uri,int depth)585 ne_propfind_create(ne_session *sess, const char *uri, int depth)
586 {
587     ne_propfind_handler *ret = ne_calloc(sizeof(ne_propfind_handler));
588     ne_uri base = {0};
589 
590     ne_fill_server_uri(sess, &base);
591     base.path = ne_strdup(uri);
592 
593     ret->parser = ne_xml_create();
594     ret->parser207 = ne_207_create(ret->parser, &base, ret);
595     ret->sess = sess;
596     ret->body = ne_buffer_create();
597     ret->request = ne_request_create(sess, "PROPFIND", uri);
598     ret->value = ne_buffer_create();
599 
600     ne_add_depth_header(ret->request, depth);
601 
602     ne_207_set_response_handlers(ret->parser207,
603 				  start_response, end_response);
604 
605     ne_207_set_propstat_handlers(ret->parser207, start_propstat,
606 				  end_propstat);
607 
608     /* The start of the request body is fixed: */
609     ne_buffer_czappend(ret->body,
610                        "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
611                        "<propfind xmlns=\"DAV:\">");
612 
613     ne_uri_free(&base);
614 
615     return ret;
616 }
617 
618 /* Destroy a propfind handler */
ne_propfind_destroy(ne_propfind_handler * handler)619 void ne_propfind_destroy(ne_propfind_handler *handler)
620 {
621     ne_buffer_destroy(handler->value);
622     if (handler->current)
623         free_propset(handler, handler->current);
624     ne_207_destroy(handler->parser207);
625     ne_xml_destroy(handler->parser);
626     ne_buffer_destroy(handler->body);
627     ne_request_destroy(handler->request);
628     ne_free(handler);
629 }
630 
ne_simple_propfind(ne_session * sess,const char * href,int depth,const ne_propname * props,ne_props_result results,void * userdata)631 int ne_simple_propfind(ne_session *sess, const char *href, int depth,
632 			const ne_propname *props,
633 			ne_props_result results, void *userdata)
634 {
635     ne_propfind_handler *hdl;
636     int ret;
637 
638     hdl = ne_propfind_create(sess, href, depth);
639     if (props != NULL) {
640 	ret = ne_propfind_named(hdl, props, results, userdata);
641     } else {
642 	ret = ne_propfind_allprop(hdl, results, userdata);
643     }
644 
645     ne_propfind_destroy(hdl);
646 
647     return ret;
648 }
649 
ne_propnames(ne_session * sess,const char * href,int depth,ne_props_result results,void * userdata)650 int ne_propnames(ne_session *sess, const char *href, int depth,
651 		  ne_props_result results, void *userdata)
652 {
653     ne_propfind_handler *hdl;
654     int ret;
655 
656     hdl = ne_propfind_create(sess, href, depth);
657 
658     ne_buffer_czappend(hdl->body, "<propname/></propfind>");
659 
660     ret = propfind(hdl, results, userdata);
661 
662     ne_propfind_destroy(hdl);
663 
664     return ret;
665 }
666 
ne_propfind_set_private(ne_propfind_handler * hdl,ne_props_create_complex creator,ne_props_destroy_complex destructor,void * userdata)667 void ne_propfind_set_private(ne_propfind_handler *hdl,
668                              ne_props_create_complex creator,
669                              ne_props_destroy_complex destructor,
670                              void *userdata)
671 {
672     hdl->creator = creator;
673     hdl->destructor = destructor;
674     hdl->cd_userdata = userdata;
675 }
676