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