1 /*
2    WebDAV Class 2 locking operations
3    Copyright (C) 1999-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 
28 #ifdef HAVE_STRING_H
29 #include <string.h>
30 #endif
31 
32 #ifdef HAVE_LIMITS_H
33 #include <limits.h>
34 #endif
35 
36 #include <ctype.h> /* for isdigit() */
37 
38 #include "ne_alloc.h"
39 
40 #include "ne_request.h"
41 #include "ne_xml.h"
42 #include "ne_locks.h"
43 #include "ne_uri.h"
44 #include "ne_basic.h"
45 #include "ne_props.h"
46 #include "ne_207.h"
47 #include "ne_i18n.h"
48 
49 #define HOOK_ID "http://webdav.org/neon/hooks/webdav-locking"
50 
51 /* A list of lock objects. */
52 struct lock_list {
53     struct ne_lock *lock;
54     struct lock_list *next, *prev;
55 };
56 
57 struct ne_lock_store_s {
58     struct lock_list *locks;
59     struct lock_list *cursor; /* current position in 'locks' */
60 };
61 
62 struct lh_req_cookie {
63     const ne_lock_store *store;
64     struct lock_list *submit;
65 };
66 
67 /* Context for PROPFIND/lockdiscovery callbacks */
68 struct discover_ctx {
69     ne_session *session;
70     ne_lock_result results;
71     void *userdata;
72     ne_buffer *cdata;
73 };
74 
75 /* Context for handling LOCK response */
76 struct lock_ctx {
77     struct ne_lock active; /* activelock */
78     char *token; /* the token we're after. */
79     int found;
80     ne_buffer *cdata;
81 };
82 
83 /* use the "application" state space. */
84 #define ELM_LOCK_FIRST (NE_PROPS_STATE_TOP + 66)
85 
86 #define ELM_lockdiscovery (ELM_LOCK_FIRST)
87 #define ELM_activelock (ELM_LOCK_FIRST + 1)
88 #define ELM_lockscope (ELM_LOCK_FIRST + 2)
89 #define ELM_locktype (ELM_LOCK_FIRST + 3)
90 #define ELM_depth (ELM_LOCK_FIRST + 4)
91 #define ELM_owner (ELM_LOCK_FIRST + 5)
92 #define ELM_timeout (ELM_LOCK_FIRST + 6)
93 #define ELM_locktoken (ELM_LOCK_FIRST + 7)
94 #define ELM_lockinfo (ELM_LOCK_FIRST + 8)
95 #define ELM_write (ELM_LOCK_FIRST + 9)
96 #define ELM_exclusive (ELM_LOCK_FIRST + 10)
97 #define ELM_shared (ELM_LOCK_FIRST + 11)
98 #define ELM_href (ELM_LOCK_FIRST + 12)
99 #define ELM_prop (NE_207_STATE_PROP)
100 
101 static const struct ne_xml_idmap element_map[] = {
102 #define ELM(x) { "DAV:", #x, ELM_ ## x }
103     ELM(lockdiscovery), ELM(activelock), ELM(prop), ELM(lockscope),
104     ELM(locktype), ELM(depth), ELM(owner), ELM(timeout), ELM(locktoken),
105     ELM(lockinfo), ELM(lockscope), ELM(locktype), ELM(write), ELM(exclusive),
106     ELM(shared), ELM(href)
107     /* no "lockentry" */
108 #undef ELM
109 };
110 
111 static const ne_propname lock_props[] = {
112     { "DAV:", "lockdiscovery" },
113     { NULL }
114 };
115 
116 /* this simply registers the accessor for the function. */
lk_create(ne_request * req,void * session,const char * method,const char * uri)117 static void lk_create(ne_request *req, void *session,
118 		       const char *method, const char *uri)
119 {
120     struct lh_req_cookie *lrc = ne_malloc(sizeof *lrc);
121     lrc->store = session;
122     lrc->submit = NULL;
123     ne_set_request_private(req, HOOK_ID, lrc);
124 }
125 
lk_pre_send(ne_request * r,void * userdata,ne_buffer * req)126 static void lk_pre_send(ne_request *r, void *userdata, ne_buffer *req)
127 {
128     struct lh_req_cookie *lrc = ne_get_request_private(r, HOOK_ID);
129 
130     if (lrc->submit != NULL) {
131 	struct lock_list *item;
132 
133 	/* Add in the If header */
134 	ne_buffer_zappend(req, "If:");
135 	for (item = lrc->submit; item != NULL; item = item->next) {
136 	    char *uri = ne_uri_unparse(&item->lock->uri);
137 	    ne_buffer_concat(req, " <", uri, "> (<",
138 			     item->lock->token, ">)", NULL);
139 	    ne_free(uri);
140 	}
141 	ne_buffer_zappend(req, EOL);
142     }
143 }
144 
145 /* Insert 'lock' into lock list *list. */
insert_lock(struct lock_list ** list,struct ne_lock * lock)146 static void insert_lock(struct lock_list **list, struct ne_lock *lock)
147 {
148     struct lock_list *item = ne_malloc(sizeof *item);
149     if (*list != NULL) {
150 	(*list)->prev = item;
151     }
152     item->prev = NULL;
153     item->next = *list;
154     item->lock = lock;
155     *list = item;
156 }
157 
free_list(struct lock_list * list,int destroy)158 static void free_list(struct lock_list *list, int destroy)
159 {
160     struct lock_list *next;
161 
162     while (list != NULL) {
163 	next = list->next;
164 	if (destroy)
165 	    ne_lock_destroy(list->lock);
166 	ne_free(list);
167 	list = next;
168     }
169 }
170 
lk_destroy(ne_request * req,void * userdata)171 static void lk_destroy(ne_request *req, void *userdata)
172 {
173     struct lh_req_cookie *lrc = ne_get_request_private(req, HOOK_ID);
174     free_list(lrc->submit, 0);
175     ne_free(lrc);
176 }
177 
ne_lockstore_destroy(ne_lock_store * store)178 void ne_lockstore_destroy(ne_lock_store *store)
179 {
180     free_list(store->locks, 1);
181     ne_free(store);
182 }
183 
ne_lockstore_create(void)184 ne_lock_store *ne_lockstore_create(void)
185 {
186     return ne_calloc(sizeof(ne_lock_store));
187 }
188 
189 #define CURSOR_RET(s) ((s)->cursor?(s)->cursor->lock:NULL)
190 
ne_lockstore_first(ne_lock_store * store)191 struct ne_lock *ne_lockstore_first(ne_lock_store *store)
192 {
193     store->cursor = store->locks;
194     return CURSOR_RET(store);
195 }
196 
ne_lockstore_next(ne_lock_store * store)197 struct ne_lock *ne_lockstore_next(ne_lock_store *store)
198 {
199     store->cursor = store->cursor->next;
200     return CURSOR_RET(store);
201 }
202 
ne_lockstore_register(ne_lock_store * store,ne_session * sess)203 void ne_lockstore_register(ne_lock_store *store, ne_session *sess)
204 {
205     /* Register the hooks */
206     ne_hook_create_request(sess, lk_create, store);
207     ne_hook_pre_send(sess, lk_pre_send, store);
208     ne_hook_destroy_request(sess, lk_destroy, store);
209 }
210 
211 /* Submit the given lock for the given URI */
submit_lock(struct lh_req_cookie * lrc,struct ne_lock * lock)212 static void submit_lock(struct lh_req_cookie *lrc, struct ne_lock *lock)
213 {
214     struct lock_list *item;
215 
216     /* Check for dups */
217     for (item = lrc->submit; item != NULL; item = item->next) {
218 	if (strcasecmp(item->lock->token, lock->token) == 0)
219 	    return;
220     }
221 
222     insert_lock(&lrc->submit, lock);
223 }
224 
ne_lockstore_findbyuri(ne_lock_store * store,const ne_uri * uri)225 struct ne_lock *ne_lockstore_findbyuri(ne_lock_store *store,
226 				       const ne_uri *uri)
227 {
228     struct lock_list *cur;
229 
230     for (cur = store->locks; cur != NULL; cur = cur->next) {
231 	if (ne_uri_cmp(&cur->lock->uri, uri) == 0) {
232 	    return cur->lock;
233 	}
234     }
235 
236     return NULL;
237 }
238 
ne_lock_using_parent(ne_request * req,const char * path)239 void ne_lock_using_parent(ne_request *req, const char *path)
240 {
241     struct lh_req_cookie *lrc = ne_get_request_private(req, HOOK_ID);
242     ne_uri u;
243     struct lock_list *item;
244     char *parent;
245 
246     if (lrc == NULL)
247 	return;
248 
249     parent = ne_path_parent(path);
250     if (parent == NULL)
251 	return;
252 
253     u.authinfo = NULL;
254     ne_fill_server_uri(ne_get_session(req), &u);
255 
256     for (item = lrc->store->locks; item != NULL; item = item->next) {
257 
258 	/* Only care about locks which are on this server. */
259 	u.path = item->lock->uri.path;
260 	if (ne_uri_cmp(&u, &item->lock->uri))
261 	    continue;
262 
263 	/* This lock is needed if it is an infinite depth lock which
264 	 * covers the parent, or a lock on the parent itself. */
265 	if ((item->lock->depth == NE_DEPTH_INFINITE &&
266 	     ne_path_childof(item->lock->uri.path, parent)) ||
267 	    ne_path_compare(item->lock->uri.path, parent) == 0) {
268 	    NE_DEBUG(NE_DBG_LOCKS, "Locked parent, %s on %s\n",
269 		     item->lock->token, item->lock->uri.path);
270 	    submit_lock(lrc, item->lock);
271 	}
272     }
273 
274     u.path = parent; /* handy: makes u.path valid and ne_free(parent). */
275     ne_uri_free(&u);
276 }
277 
ne_lock_using_resource(ne_request * req,const char * uri,int depth)278 void ne_lock_using_resource(ne_request *req, const char *uri, int depth)
279 {
280     struct lh_req_cookie *lrc = ne_get_request_private(req, HOOK_ID);
281     struct lock_list *item;
282     int match;
283 
284     if (lrc == NULL)
285 	return;
286 
287     /* Iterate over the list of stored locks to see if any of them
288      * apply to this resource */
289     for (item = lrc->store->locks; item != NULL; item = item->next) {
290 
291 	match = 0;
292 
293 	if (depth == NE_DEPTH_INFINITE &&
294 	    ne_path_childof(uri, item->lock->uri.path)) {
295 	    /* Case 1: this is a depth-infinity request which will
296 	     * modify a lock somewhere inside the collection. */
297 	    NE_DEBUG(NE_DBG_LOCKS, "Has child: %s\n", item->lock->token);
298 	    match = 1;
299 	}
300 	else if (ne_path_compare(uri, item->lock->uri.path) == 0) {
301 	    /* Case 2: this request is directly on a locked resource */
302 	    NE_DEBUG(NE_DBG_LOCKS, "Has direct lock: %s\n", item->lock->token);
303 	    match = 1;
304 	}
305 	else if (item->lock->depth == NE_DEPTH_INFINITE &&
306 		 ne_path_childof(item->lock->uri.path, uri)) {
307 	    /* Case 3: there is a higher-up infinite-depth lock which
308 	     * covers the resource that this request will modify. */
309 	    NE_DEBUG(NE_DBG_LOCKS, "Is child of: %s\n", item->lock->token);
310 	    match = 1;
311 	}
312 
313 	if (match) {
314 	    submit_lock(lrc, item->lock);
315 	}
316     }
317 
318 }
319 
ne_lockstore_add(ne_lock_store * store,struct ne_lock * lock)320 void ne_lockstore_add(ne_lock_store *store, struct ne_lock *lock)
321 {
322     insert_lock(&store->locks, lock);
323 }
324 
ne_lockstore_remove(ne_lock_store * store,struct ne_lock * lock)325 void ne_lockstore_remove(ne_lock_store *store, struct ne_lock *lock)
326 {
327     struct lock_list *item;
328 
329     /* Find the lock */
330     for (item = store->locks; item != NULL; item = item->next)
331 	if (item->lock == lock)
332 	    break;
333 
334     if (item->prev != NULL) {
335 	item->prev->next = item->next;
336     } else {
337 	store->locks = item->next;
338     }
339     if (item->next != NULL) {
340 	item->next->prev = item->prev;
341     }
342     ne_free(item);
343 }
344 
ne_lock_copy(const struct ne_lock * lock)345 struct ne_lock *ne_lock_copy(const struct ne_lock *lock)
346 {
347     struct ne_lock *ret = ne_calloc(sizeof *ret);
348 
349     ret->uri.path = ne_strdup(lock->uri.path);
350     ret->uri.host = ne_strdup(lock->uri.host);
351     ret->uri.scheme = ne_strdup(lock->uri.scheme);
352     ret->uri.port = lock->uri.port;
353     ret->token = ne_strdup(lock->token);
354     ret->depth = lock->depth;
355     ret->type = lock->type;
356     ret->scope = lock->scope;
357     if (lock->owner) ret->owner = ne_strdup(lock->owner);
358     ret->timeout = lock->timeout;
359 
360     return ret;
361 }
362 
ne_lock_create(void)363 struct ne_lock *ne_lock_create(void)
364 {
365     struct ne_lock *lock = ne_calloc(sizeof *lock);
366     lock->depth = NE_DEPTH_ZERO;
367     lock->type = ne_locktype_write;
368     lock->scope = ne_lockscope_exclusive;
369     lock->timeout = NE_TIMEOUT_INVALID;
370     return lock;
371 }
372 
ne_lock_free(struct ne_lock * lock)373 void ne_lock_free(struct ne_lock *lock)
374 {
375     ne_uri_free(&lock->uri);
376     NE_FREE(lock->owner);
377     NE_FREE(lock->token);
378 }
379 
ne_lock_destroy(struct ne_lock * lock)380 void ne_lock_destroy(struct ne_lock *lock)
381 {
382     ne_lock_free(lock);
383     ne_free(lock);
384 }
385 
ne_unlock(ne_session * sess,const struct ne_lock * lock)386 int ne_unlock(ne_session *sess, const struct ne_lock *lock)
387 {
388     ne_request *req = ne_request_create(sess, "UNLOCK", lock->uri.path);
389     int ret;
390 
391     ne_print_request_header(req, "Lock-Token", "<%s>", lock->token);
392 
393     /* UNLOCK of a lock-null resource removes the resource from the
394      * parent collection; so an UNLOCK may modify the parent
395      * collection. (somewhat counter-intuitive, and not easily derived
396      * from 2518.) */
397     ne_lock_using_parent(req, lock->uri.path);
398 
399     ret = ne_request_dispatch(req);
400 
401     if (ret == NE_OK && ne_get_status(req)->klass == 2) {
402 	ret = NE_OK;
403     } else {
404 	ret = NE_ERROR;
405     }
406 
407     ne_request_destroy(req);
408 
409     return ret;
410 }
411 
parse_depth(const char * depth)412 static int parse_depth(const char *depth)
413 {
414     if (strcasecmp(depth, "infinity") == 0) {
415 	return NE_DEPTH_INFINITE;
416     } else if (isdigit(depth[0])) {
417 	return atoi(depth);
418     } else {
419 	return -1;
420     }
421 }
422 
parse_timeout(const char * timeout)423 static long parse_timeout(const char *timeout)
424 {
425     if (strcasecmp(timeout, "infinite") == 0) {
426 	return NE_TIMEOUT_INFINITE;
427     } else if (strncasecmp(timeout, "Second-", 7) == 0) {
428 	long to = strtol(timeout+7, NULL, 10);
429 	if (to == LONG_MIN || to == LONG_MAX)
430 	    return NE_TIMEOUT_INVALID;
431 	return to;
432     } else {
433 	return NE_TIMEOUT_INVALID;
434     }
435 }
436 
discover_results(void * userdata,const char * href,const ne_prop_result_set * set)437 static void discover_results(void *userdata, const char *href,
438 			     const ne_prop_result_set *set)
439 {
440     struct discover_ctx *ctx = userdata;
441     struct ne_lock *lock = ne_propset_private(set);
442     const ne_status *status = ne_propset_status(set, &lock_props[0]);
443 
444     /* Require at least that the lock has a token. */
445     if (lock->token) {
446 	if (status && status->klass != 2) {
447 	    ctx->results(ctx->userdata, NULL, lock->uri.path, status);
448 	} else {
449 	    ctx->results(ctx->userdata, lock, lock->uri.path, NULL);
450 	}
451     }
452     else if (status) {
453 	ctx->results(ctx->userdata, NULL, href, status);
454     }
455 
456     ne_lock_destroy(lock);
457 
458     NE_DEBUG(NE_DBG_LOCKS, "End of response for %s\n", href);
459 }
460 
461 static int
end_element_common(struct ne_lock * l,int state,const char * cdata)462 end_element_common(struct ne_lock *l, int state, const char *cdata)
463 {
464     switch (state) {
465     case ELM_write:
466 	l->type = ne_locktype_write;
467 	break;
468     case ELM_exclusive:
469 	l->scope = ne_lockscope_exclusive;
470 	break;
471     case ELM_shared:
472 	l->scope = ne_lockscope_shared;
473 	break;
474     case ELM_depth:
475 	NE_DEBUG(NE_DBG_LOCKS, "Got depth: %s\n", cdata);
476 	l->depth = parse_depth(cdata);
477 	if (l->depth == -1) {
478 	    return -1;
479 	}
480 	break;
481     case ELM_timeout:
482 	NE_DEBUG(NE_DBG_LOCKS, "Got timeout: %s\n", cdata);
483 	l->timeout = parse_timeout(cdata);
484 	if (l->timeout == NE_TIMEOUT_INVALID) {
485 	    return -1;
486 	}
487 	break;
488     case ELM_owner:
489 	l->owner = strdup(cdata);
490 	break;
491     case ELM_href:
492 	l->token = strdup(cdata);
493 	break;
494     }
495     return 0;
496 }
497 
498 /* End-element handler for lock discovery PROPFIND response */
end_element_ldisc(void * userdata,int state,const char * nspace,const char * name)499 static int end_element_ldisc(void *userdata, int state,
500                              const char *nspace, const char *name)
501 {
502     struct ne_lock *lock = ne_propfind_current_private(userdata);
503     struct discover_ctx *ctx = userdata;
504 
505     return end_element_common(lock, state, ctx->cdata->data);
506 }
507 
can_accept(int parent,int id)508 static inline int can_accept(int parent, int id)
509 {
510     return (parent == NE_XML_STATEROOT && id == ELM_prop) ||
511         (parent == ELM_prop && id == ELM_lockdiscovery) ||
512         (parent == ELM_lockdiscovery && id == ELM_activelock) ||
513         (parent == ELM_activelock &&
514          (id == ELM_lockscope || id == ELM_locktype ||
515           id == ELM_depth || id == ELM_owner ||
516           id == ELM_timeout || id == ELM_locktoken)) ||
517         (parent == ELM_lockscope &&
518          (id == ELM_exclusive || id == ELM_shared)) ||
519         (parent == ELM_locktype && id == ELM_write) ||
520         (parent == ELM_locktoken && id == ELM_href);
521 }
522 
ld_startelm(void * userdata,int parent,const char * nspace,const char * name,const char ** atts)523 static int ld_startelm(void *userdata, int parent,
524                        const char *nspace, const char *name,
525 		       const char **atts)
526 {
527     struct discover_ctx *ctx = userdata;
528     int id = ne_xml_mapid(element_map, NE_XML_MAPLEN(element_map),
529                           nspace, name);
530 
531     ne_buffer_clear(ctx->cdata);
532 
533     if (can_accept(parent, id))
534         return id;
535     else
536         return NE_XML_DECLINE;
537 }
538 
539 #define MAX_CDATA (256)
540 
lk_cdata(void * userdata,int state,const char * cdata,size_t len)541 static int lk_cdata(void *userdata, int state,
542                     const char *cdata, size_t len)
543 {
544     struct lock_ctx *ctx = userdata;
545 
546     if (ctx->cdata->used + len < MAX_CDATA)
547         ne_buffer_append(ctx->cdata, cdata, len);
548 
549     return 0;
550 }
551 
ld_cdata(void * userdata,int state,const char * cdata,size_t len)552 static int ld_cdata(void *userdata, int state,
553                     const char *cdata, size_t len)
554 {
555     struct discover_ctx *ctx = userdata;
556 
557     if (ctx->cdata->used + len < MAX_CDATA)
558         ne_buffer_append(ctx->cdata, cdata, len);
559 
560     return 0;
561 }
562 
lk_startelm(void * userdata,int parent,const char * nspace,const char * name,const char ** atts)563 static int lk_startelm(void *userdata, int parent,
564                        const char *nspace, const char *name,
565 		       const char **atts)
566 {
567     struct lock_ctx *ctx = userdata;
568     int id;
569 
570     id = ne_xml_mapid(element_map, NE_XML_MAPLEN(element_map), nspace, name);
571 
572     NE_DEBUG(NE_DBG_LOCKS, "lk_startelm: %s => %d\n", name, id);
573 
574     /* Lock-Token header is a MUST requirement: if the token
575      * is not known, bail out. */
576     if (id == 0 || ctx->token == NULL)
577 	return NE_XML_DECLINE;
578 
579     /* TODO: only accept 'prop' as root for LOCK response */
580     if (!can_accept(parent, id))
581         return NE_XML_DECLINE;
582 
583     if (id == ELM_activelock && !ctx->found) {
584 	/* a new activelock */
585 	ne_lock_free(&ctx->active);
586 	memset(&ctx->active, 0, sizeof ctx->active);
587     }
588 
589     ne_buffer_clear(ctx->cdata);
590 
591     return id;
592 }
593 
594 /* End-element handler for LOCK response */
lk_endelm(void * userdata,int state,const char * nspace,const char * name)595 static int lk_endelm(void *userdata, int state,
596                      const char *nspace, const char *name)
597 {
598     struct lock_ctx *ctx = userdata;
599 
600     if (ctx->found)
601 	return 0;
602 
603     if (end_element_common(&ctx->active, state, ctx->cdata->data))
604 	return -1;
605 
606     if (state == ELM_activelock) {
607 	if (ctx->active.token && strcmp(ctx->active.token, ctx->token) == 0) {
608 	    ctx->found = 1;
609 	}
610     }
611 
612     return 0;
613 }
614 
ld_create(void * userdata,const char * href)615 static void *ld_create(void *userdata, const char *href)
616 {
617     struct discover_ctx *ctx = userdata;
618     struct ne_lock *lk = ne_lock_create();
619 
620     if (ne_uri_parse(href, &lk->uri) != 0) {
621 	ne_lock_destroy(lk);
622 	return NULL;
623     }
624 
625     if (!lk->uri.host)
626 	ne_fill_server_uri(ctx->session, &lk->uri);
627 
628     return lk;
629 }
630 
631 /* Discover all locks on URI */
ne_lock_discover(ne_session * sess,const char * uri,ne_lock_result callback,void * userdata)632 int ne_lock_discover(ne_session *sess, const char *uri,
633 		     ne_lock_result callback, void *userdata)
634 {
635     ne_propfind_handler *handler;
636     struct discover_ctx ctx = {0};
637     int ret;
638 
639     ctx.results = callback;
640     ctx.userdata = userdata;
641     ctx.session = sess;
642     ctx.cdata = ne_buffer_create();
643 
644     handler = ne_propfind_create(sess, uri, NE_DEPTH_ZERO);
645 
646     ne_propfind_set_private(handler, ld_create, &ctx);
647 
648     ne_xml_push_handler(ne_propfind_get_parser(handler),
649                         ld_startelm, ld_cdata, end_element_ldisc, handler);
650 
651     ret = ne_propfind_named(handler, lock_props, discover_results, &ctx);
652 
653     ne_buffer_destroy(ctx.cdata);
654     ne_propfind_destroy(handler);
655 
656     return ret;
657 }
658 
add_timeout_header(ne_request * req,long timeout)659 static void add_timeout_header(ne_request *req, long timeout)
660 {
661     if (timeout == NE_TIMEOUT_INFINITE) {
662 	ne_add_request_header(req, "Timeout", "Infinite");
663     }
664     else if (timeout != NE_TIMEOUT_INVALID && timeout > 0) {
665 	ne_print_request_header(req, "Timeout", "Second-%ld", timeout);
666     }
667     /* just ignore it if timeout == 0 or invalid. */
668 }
669 
670 /* Parse a Lock-Token response header. */
get_ltoken_hdr(void * ud,const char * value)671 static void get_ltoken_hdr(void *ud, const char *value)
672 {
673     struct lock_ctx *ctx = ud;
674 
675     if (value[0] == '<') value++;
676     ctx->token = ne_strdup(value);
677     ne_shave(ctx->token, ">");
678 }
679 
ne_lock(ne_session * sess,struct ne_lock * lock)680 int ne_lock(ne_session *sess, struct ne_lock *lock)
681 {
682     ne_request *req = ne_request_create(sess, "LOCK", lock->uri.path);
683     ne_buffer *body = ne_buffer_create();
684     ne_xml_parser *parser = ne_xml_create();
685     int ret, parse_failed;
686     struct lock_ctx ctx;
687 
688     memset(&ctx, 0, sizeof ctx);
689     ctx.cdata = ne_buffer_create();
690 
691     ne_xml_push_handler(parser, lk_startelm, lk_cdata, lk_endelm, &ctx);
692 
693     /* Create the body */
694     ne_buffer_concat(body, "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL
695 		    "<lockinfo xmlns='DAV:'>" EOL " <lockscope>",
696 		    lock->scope==ne_lockscope_exclusive?
697 		    "<exclusive/>":"<shared/>",
698 		    "</lockscope>" EOL
699 		    "<locktype><write/></locktype>", NULL);
700 
701     if (lock->owner) {
702 	ne_buffer_concat(body, "<owner>", lock->owner, "</owner>" EOL, NULL);
703     }
704     ne_buffer_zappend(body, "</lockinfo>" EOL);
705 
706     ne_set_request_body_buffer(req, body->data, ne_buffer_size(body));
707     ne_add_response_body_reader(req, ne_accept_2xx,
708 				ne_xml_parse_v, parser);
709     ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
710     ne_add_depth_header(req, lock->depth);
711     add_timeout_header(req, lock->timeout);
712 
713     ne_add_response_header_handler(req, "Lock-Token", get_ltoken_hdr, &ctx);
714 
715     /* TODO:
716      * By 2518, we need this only if we are creating a lock-null resource.
717      * Since we don't KNOW whether the lock we're given is a lock-null
718      * or not, we cover our bases.
719      */
720     ne_lock_using_parent(req, lock->uri.path);
721     /* This one is clearer from 2518 sec 8.10.4. */
722     ne_lock_using_resource(req, lock->uri.path, lock->depth);
723 
724     ret = ne_request_dispatch(req);
725 
726     ne_buffer_destroy(body);
727     ne_buffer_destroy(ctx.cdata);
728     parse_failed = !ne_xml_valid(parser);
729 
730     if (ret == NE_OK && ne_get_status(req)->klass == 2) {
731 	if (ctx.token == NULL) {
732 	    ret = NE_ERROR;
733 	    ne_set_error(sess, _("No Lock-Token header given"));
734 	}
735 	else if (parse_failed) {
736 	    ret = NE_ERROR;
737 	    ne_set_error(sess, "%s", ne_xml_get_error(parser));
738 	}
739 	else if (ne_get_status(req)->code == 207) {
740 	    ret = NE_ERROR;
741 	    /* TODO: set the error string appropriately */
742 	}
743 	else if (ctx.found) {
744 	    /* it worked: copy over real lock details if given. */
745 	    NE_FREE(lock->token);
746 	    lock->token = ctx.token;
747 	    ctx.token = NULL;
748 	    if (ctx.active.timeout != NE_TIMEOUT_INVALID)
749 		lock->timeout = ctx.active.timeout;
750 	    lock->scope = ctx.active.scope;
751 	    lock->type = ctx.active.type;
752 	    if (ctx.active.depth >= 0)
753 		lock->depth = ctx.active.depth;
754 	    if (ctx.active.owner) {
755 		NE_FREE(lock->owner);
756 		lock->owner = ctx.active.owner;
757 		ctx.active.owner = NULL;
758 	    }
759 	} else {
760 	    ret = NE_ERROR;
761 	    ne_set_error(sess, _("Response missing activelock for %s"),
762 			 ctx.token);
763 	}
764     } else {
765 	ret = NE_ERROR;
766     }
767 
768     if (ctx.token)
769 	ne_free(ctx.token);
770     ne_lock_free(&ctx.active);
771 
772     ne_request_destroy(req);
773     ne_xml_destroy(parser);
774 
775     return ret;
776 }
777 
ne_lock_refresh(ne_session * sess,struct ne_lock * lock)778 int ne_lock_refresh(ne_session *sess, struct ne_lock *lock)
779 {
780     ne_request *req = ne_request_create(sess, "LOCK", lock->uri.path);
781     ne_xml_parser *parser = ne_xml_create();
782     int ret, parse_failed;
783 
784     /* Handle the response and update *lock appropriately. */
785     ne_xml_push_handler(parser, lk_startelm, NULL, lk_endelm, lock);
786 
787     ne_add_response_body_reader(req, ne_accept_2xx,
788 				ne_xml_parse_v, parser);
789 
790     /* Probably don't need to submit any other lock-tokens for this
791      * resource? If it's an exclusive lock, then there can be no other
792      * locks covering the resource. If it's a shared lock, then this
793      * lock (which is being refreshed) is sufficient to modify the
794      * resource state? */
795     ne_print_request_header(req, "If", "(<%s>)", lock->token);
796     add_timeout_header(req, lock->timeout);
797 
798     ret = ne_request_dispatch(req);
799 
800     parse_failed = !ne_xml_valid(parser);
801 
802     if (ret == NE_OK && ne_get_status(req)->klass == 2) {
803 	if (parse_failed) {
804 	    ret = NE_ERROR;
805 	    ne_set_error(sess, "%s", ne_xml_get_error(parser));
806 	}
807 	else if (ne_get_status(req)->code == 207) {
808 	    ret = NE_ERROR;
809 	    /* TODO: set the error string appropriately */
810 	}
811     } else {
812 	ret = NE_ERROR;
813     }
814 
815     ne_request_destroy(req);
816     ne_xml_destroy(parser);
817 
818     return ret;
819 }
820