Back to index

avfs  1.0.1
dav_locks.c
Go to the documentation of this file.
00001 /* 
00002    WebDAV Class 2 locking operations
00003    Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009    
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public
00016    License along with this library; if not, write to the Free
00017    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
00018    MA 02111-1307, USA
00019 
00020 */
00021 
00022 #include "config.h"
00023 
00024 #ifdef HAVE_STDLIB_H
00025 #include <stdlib.h>
00026 #endif
00027 
00028 #ifdef HAVE_STRING_H
00029 #include <string.h>
00030 #endif
00031 
00032 #ifdef HAVE_LIMITS_H
00033 #include <limits.h>
00034 #endif
00035 
00036 #include "http_request.h"
00037 #include "dav_locks.h"
00038 #include "dav_basic.h"
00039 #include "dav_props.h"
00040 #include "uri.h"
00041 #include "dav_207.h"
00042 #include "neon_i18n.h"
00043 #include "hip_xml.h"
00044 
00045 #include "ne_alloc.h"
00046 
00047 #define HOOK_ID "http://webdav.org/neon/hooks/webdav-locking"
00048 
00049 /* The list of locks to submit in an If header 
00050  * for the current requests */
00051 struct submit_locks {
00052     const struct dav_lock *lock;
00053     const char *uri;
00054     struct submit_locks *next;
00055 };
00056 
00057 struct dav_lock_session_s {
00058     struct dav_lock *locks;
00059 };
00060 
00061 /* Per-request lock structure */
00062 struct request_locks {
00063     struct submit_locks *locks; /* for the If header */
00064     dav_lock_session *session;
00065 };
00066 
00067 /* Context for PROPFIND/lockdiscovery callbacks */
00068 struct discover_ctx {
00069     dav_lock_result results;
00070     void *userdata;
00071 };
00072 
00073 /* Hook callbacks */
00074 static void *create(void *session, http_req *req, 
00075                   const char *method, const char *uri);
00076 static void pre_send(void *private, sbuffer req);
00077 static void destroy(void *private);
00078 
00079 /* The hooks are needed for construction of the If header */
00080 static http_request_hooks lock_hooks = {
00081     HOOK_ID, /* unique id for the locking hooks */
00082     create,
00083     NULL, /* use_body not needed */
00084     pre_send,
00085     NULL, /* post_send not needed */
00086     destroy
00087 };
00088 
00089 /* Element ID's start at HIP_ELM_UNUSED and work upwards */
00090 
00091 #define DAV_ELM_LOCK_FIRST (DAV_ELM_207_UNUSED)
00092 
00093 #define DAV_ELM_lockdiscovery (DAV_ELM_LOCK_FIRST)
00094 #define DAV_ELM_activelock (DAV_ELM_LOCK_FIRST + 1)
00095 #define DAV_ELM_lockscope (DAV_ELM_LOCK_FIRST + 2)
00096 #define DAV_ELM_locktype (DAV_ELM_LOCK_FIRST + 3)
00097 #define DAV_ELM_depth (DAV_ELM_LOCK_FIRST + 4)
00098 #define DAV_ELM_owner (DAV_ELM_LOCK_FIRST + 5)
00099 #define DAV_ELM_timeout (DAV_ELM_LOCK_FIRST + 6)
00100 #define DAV_ELM_locktoken (DAV_ELM_LOCK_FIRST + 7)
00101 #define DAV_ELM_lockinfo (DAV_ELM_LOCK_FIRST + 8)
00102 #define DAV_ELM_write (DAV_ELM_LOCK_FIRST + 9)
00103 #define DAV_ELM_exclusive (DAV_ELM_LOCK_FIRST + 10)
00104 #define DAV_ELM_shared (DAV_ELM_LOCK_FIRST + 11)
00105 
00106 static const struct hip_xml_elm lock_elms[] = {
00107 #define A(x) { "DAV:", #x, DAV_ELM_ ## x, HIP_XML_COLLECT } /* ANY */
00108 #define D(x) { "DAV:", #x, DAV_ELM_ ## x, 0 }               /* normal */
00109 #define C(x) { "DAV:", #x, DAV_ELM_ ## x, HIP_XML_CDATA }   /* (#PCDATA) */
00110 #define E(x) { "DAV:", #x, DAV_ELM_ ## x, 0 /* LEAF */ }    /* EMPTY */
00111     D(lockdiscovery), D(activelock),
00112     D(prop),
00113     D(lockscope), D(locktype), C(depth), A(owner), C(timeout), D(locktoken),
00114     /* no lockentry */
00115     D(lockinfo), D(lockscope), D(locktype),
00116     E(write), E(exclusive), E(shared),
00117     C(href),
00118 #undef A
00119 #undef D
00120 #undef C
00121 #undef E
00122     { NULL, 0, 0 }
00123 };
00124 
00125 static const dav_propname lock_props[] = {
00126     { "DAV:", "lockdiscovery" },
00127     { NULL }
00128 };
00129 
00130 static void *create(void *session, http_req *req, 
00131                   const char *method, const char *uri)
00132 {
00133     struct request_locks *rl = ne_calloc(sizeof *rl);
00134     rl->session = session;
00135     return rl;
00136 }
00137 
00138 static void pre_send(void *private, sbuffer req)
00139 {
00140     struct request_locks *rl = private;
00141     
00142     if (rl->locks != NULL) {
00143        struct submit_locks *item;
00144 
00145        /* Add in the If header */
00146        sbuffer_zappend(req, "If:");
00147        for (item = rl->locks; item != NULL; item = item->next) {
00148            sbuffer_concat(req, " <", item->lock->uri, "> (<",
00149                         item->lock->token, ">)", NULL);
00150        }
00151        sbuffer_zappend(req, EOL);
00152     }
00153 }
00154 
00155 static void destroy(void *priv)
00156 {
00157     struct request_locks *rl = priv;
00158     struct submit_locks *lock, *next;
00159     
00160     for (lock = rl->locks; lock != NULL; lock = next) {
00161        next = lock->next;
00162        free(lock);
00163     }
00164     
00165     free(rl);
00166 }
00167 
00168 dav_lock_session *dav_lock_register(http_session *sess)
00169 {
00170     dav_lock_session *locksess = ne_calloc(sizeof *locksess);
00171 
00172     /* Register the hooks */
00173     http_add_hooks(sess, &lock_hooks, locksess);
00174     
00175     return locksess;
00176 }
00177 
00178 void dav_lock_unregister(dav_lock_session *sess)
00179 {
00180     /* FIXME: free the lock list */
00181     free(sess);
00182 }
00183 
00184 /* Submit the given lock for the given URI */
00185 static void submit_lock(struct request_locks *rl, struct dav_lock *lock, 
00186                      const char *uri)
00187 {
00188     struct submit_locks *slock;
00189 
00190     /* Check for dups */
00191     for (slock = rl->locks; slock != NULL; slock = slock->next) {
00192        if (strcasecmp(slock->lock->token, lock->token) == 0)
00193            return;
00194     }
00195 
00196     slock = ne_calloc(sizeof *slock);
00197     slock->lock = lock;
00198     slock->uri = uri;
00199     slock->next = rl->locks;
00200     rl->locks = slock;
00201 }
00202 
00203 struct dav_lock *dav_lock_find(dav_lock_session *sess, const char *uri)
00204 {
00205     struct dav_lock *cur;
00206     for (cur = sess->locks; cur != NULL; cur = cur->next) {
00207        if (uri_compare(uri, cur->uri) == 0) 
00208            return cur;
00209     }
00210     return NULL;
00211 }
00212 
00213 void dav_lock_using_parent(http_req *req, const char *uri)
00214 {
00215     struct request_locks *rl = http_get_hook_private(req, HOOK_ID);
00216     char *parent;
00217 
00218     if (rl == NULL)
00219        return;       
00220 
00221     parent = uri_parent(uri);
00222 
00223     if (parent != NULL) {
00224        struct dav_lock *lock;
00225        /* Find any locks on the parent resource.
00226         * FIXME: should check for depth-infinity locks too. */
00227        lock = dav_lock_find(rl->session, parent);
00228        if (lock) {
00229            DEBUG(DEBUG_LOCKS, "Locked parent, %s on %s\n", lock->token, 
00230                 lock->uri);
00231            submit_lock(rl, lock, uri);
00232        }
00233        free(parent);
00234     }
00235 }
00236 
00237 int dav_lock_iterate(dav_lock_session *sess, 
00238                    dav_lock_walkfunc func, void *userdata)
00239 {
00240     struct dav_lock *lock;
00241     int count = 0;
00242 
00243     for (lock = sess->locks; lock != NULL; lock = lock->next) {
00244        if (func != NULL) {
00245            (*func)(lock, userdata);
00246        }
00247        count++;
00248     }
00249     
00250     return count;
00251 }
00252 
00253 void dav_lock_using_resource(http_req *req, const char *uri, int depth)
00254 {
00255     /* Grab the private cookie for this request */
00256     struct request_locks *rl = http_get_hook_private(req, HOOK_ID);
00257     struct dav_lock *lock; /* all the known locks */
00258     int match;
00259 
00260     if (rl == NULL)
00261        return;       
00262 
00263     /* Iterate over the list of session locks to see if any of
00264      * them apply to this resource */
00265     for (lock = rl->session->locks; lock != NULL; lock = lock->next) {
00266        
00267        match = 0;
00268        
00269        if (depth == DAV_DEPTH_INFINITE && uri_childof(uri, lock->uri)) {
00270            /* Case 1: this is a depth-infinity request which will 
00271             * modify a lock somewhere inside the collection. */
00272            DEBUG(DEBUG_LOCKS, "Has child: %s\n", lock->token);
00273            match = 1;
00274        } 
00275        else if (uri_compare(uri, lock->uri) == 0) {
00276            /* Case 2: this request is directly on a locked resource */
00277            DEBUG(DEBUG_LOCKS, "Has direct lock: %s\n", lock->token);
00278            match = 1;
00279        }
00280        else if (lock->depth == DAV_DEPTH_INFINITE && 
00281                uri_childof(lock->uri, uri)) {
00282            /* Case 3: there is a higher-up infinite-depth lock which
00283             * covers the resource that this request will modify. */
00284            DEBUG(DEBUG_LOCKS, "Is child of: %s\n", lock->token);
00285            match = 1;
00286        }
00287        
00288        if (match) {
00289            submit_lock(rl, lock, uri);
00290        }
00291     }
00292 
00293 }
00294 
00295 void dav_lock_add(dav_lock_session *sess, struct dav_lock *lock)
00296 {
00297     if (sess->locks != NULL) {
00298        sess->locks->prev = lock;
00299     }
00300     lock->prev = NULL;
00301     lock->next = sess->locks;
00302     sess->locks = lock;
00303 }
00304 
00305 void dav_lock_remove(dav_lock_session *sess, struct dav_lock *lock)
00306 {
00307     if (lock->prev != NULL) {
00308        lock->prev->next = lock->next;
00309     } else {
00310        sess->locks = lock->next;
00311     }
00312     if (lock->next != NULL) {
00313        lock->next->prev = lock->prev;
00314     }
00315 }
00316 
00317 struct dav_lock *dav_lock_copy(const struct dav_lock *lock)
00318 {
00319     struct dav_lock *ret = ne_calloc(sizeof *ret);
00320 
00321     ret->uri = ne_strdup(lock->uri);
00322     ret->depth = lock->depth;
00323     ret->type = lock->type;
00324     ret->scope = lock->scope;
00325     ret->token = ne_strdup(lock->token);
00326     ret->owner = ne_strdup(lock->owner);
00327     ret->timeout = lock->timeout;
00328 
00329     return ret;
00330 }
00331 
00332 void dav_lock_free(struct dav_lock *lock)
00333 {
00334     HTTP_FREE(lock->uri);
00335     HTTP_FREE(lock->owner);
00336     HTTP_FREE(lock->token);
00337     free(lock);
00338 }
00339 
00340 int dav_unlock(http_session *sess, struct dav_lock *lock)
00341 {
00342     http_req *req = http_request_create(sess, "UNLOCK", lock->uri);
00343     int ret;
00344     
00345     http_print_request_header(req, "Lock-Token", "<%s>", lock->token);
00346     
00347     /* TODO: need this or not?
00348      * it definitely goes away when lock-null resources go away */
00349     dav_lock_using_parent(req, lock->uri);
00350 
00351     ret = http_request_dispatch(req);
00352     
00353     if (ret == HTTP_OK && http_get_status(req)->klass == 2) {
00354        ret = HTTP_OK;    
00355     } else {
00356        ret = HTTP_ERROR;
00357     }
00358 
00359     http_request_destroy(req);
00360     
00361     return ret;
00362 }
00363 
00364 static int check_context(hip_xml_elmid parent, hip_xml_elmid child) {
00365     DEBUG(DEBUG_XML, "dav_locks: check_context %d in %d\n", child, parent);
00366     switch (parent) {
00367     case HIP_ELM_root:
00368        /* TODO: for LOCK requests only...
00369         * shouldn't allow this for PROPFIND really */
00370        if (child == DAV_ELM_prop)
00371            return HIP_XML_VALID;
00372        break;     
00373     case DAV_ELM_prop:
00374        if (child == DAV_ELM_lockdiscovery)
00375            return HIP_XML_VALID;
00376        break;
00377     case DAV_ELM_lockdiscovery:
00378        if (child == DAV_ELM_activelock)
00379            return HIP_XML_VALID;
00380        break;
00381     case DAV_ELM_activelock:
00382        switch (child) {
00383        case DAV_ELM_lockscope:
00384        case DAV_ELM_locktype:
00385        case DAV_ELM_depth:
00386        case DAV_ELM_owner:
00387        case DAV_ELM_timeout:
00388        case DAV_ELM_locktoken:
00389            return HIP_XML_VALID;
00390        default:
00391            break;
00392        }
00393        break;
00394     case DAV_ELM_lockscope:
00395        switch (child) {
00396        case DAV_ELM_exclusive:
00397        case DAV_ELM_shared:
00398            return HIP_XML_VALID;
00399        default:
00400            break;
00401        }
00402     case DAV_ELM_locktype:
00403        if (child == DAV_ELM_write)
00404            return HIP_XML_VALID;
00405        break;
00406        /* ... depth is PCDATA, owner is COLLECT, timeout is PCDATA */
00407     case DAV_ELM_locktoken:
00408        if (child == DAV_ELM_href)
00409            return HIP_XML_VALID;
00410        break;
00411     }
00412     return HIP_XML_DECLINE;
00413 }
00414 
00415 static int parse_depth(const char *depth) {
00416     if (strcasecmp(depth, "infinity") == 0) {
00417        return DAV_DEPTH_INFINITE;
00418     } else if (isdigit(depth[0])) {
00419        return atoi(depth);
00420     } else {
00421        return -1;
00422     }
00423 }
00424 
00425 static long parse_timeout(const char *timeout) {
00426     if (strcasecmp(timeout, "infinite") == 0) {
00427        return DAV_TIMEOUT_INFINITE;
00428     } else if (strncasecmp(timeout, "Second-", 7) == 0) {
00429        long to = strtol(timeout, NULL, 10);
00430        if (to == LONG_MIN || to == LONG_MAX)
00431            return DAV_TIMEOUT_INVALID;
00432        return to;
00433     } else {
00434        return DAV_TIMEOUT_INVALID;
00435     }
00436 }
00437 
00438 static void discover_results(void *userdata, const char *href,
00439                           const dav_prop_result_set *set)
00440 {
00441     struct discover_ctx *ctx = userdata;
00442     struct dav_lock *lock = dav_propset_private(set);
00443 
00444     if (lock == NULL)
00445        return;
00446 
00447     lock->uri = ne_strdup(href);
00448 
00449     ctx->results(ctx->userdata, lock, 
00450                href, dav_propset_status(set, &lock_props[0]));
00451 
00452     
00453     dav_lock_free(lock);
00454 
00455     DEBUG(DEBUG_LOCKS, "End of response for %s\n", href);
00456 }
00457 
00458 static int 
00459 end_element_common(struct dav_lock *l, const struct hip_xml_elm *elm,
00460                  const char *cdata)
00461 {
00462     switch (elm->id){ 
00463     case DAV_ELM_write:
00464        l->type = dav_locktype_write;
00465        break;
00466     case DAV_ELM_exclusive:
00467        l->scope = dav_lockscope_exclusive;
00468        break;
00469     case DAV_ELM_shared:
00470        l->scope = dav_lockscope_shared;
00471        break;
00472     case DAV_ELM_depth:
00473        DEBUG(DEBUG_LOCKS, "Got depth: %s\n", cdata);
00474        l->depth = parse_depth(cdata);
00475        if (l->depth == -1) {
00476            return -1;
00477        }
00478        break;
00479     case DAV_ELM_timeout:
00480        DEBUG(DEBUG_LOCKS, "Got timeout: %s\n", cdata);
00481        l->timeout = parse_timeout(cdata);
00482        if (l->timeout == DAV_TIMEOUT_INVALID) {
00483            return -1;
00484        }
00485        break;
00486     case DAV_ELM_owner:
00487        l->owner = strdup(cdata);
00488        break;
00489     case DAV_ELM_href:
00490        l->token = strdup(cdata);
00491        break;
00492     }
00493     return 0;
00494 }
00495 
00496 /* End-element handler for lock discovery PROPFIND response */
00497 static int 
00498 end_element_ldisc(void *userdata, const struct hip_xml_elm *elm, 
00499                 const char *cdata) 
00500 {
00501     struct dav_lock *lock = dav_propfind_current_private(userdata);
00502 
00503     return end_element_common(lock, elm, cdata);
00504 }
00505 
00506 /* End-element handler for LOCK response */
00507 static int
00508 end_element_lock(void *userdata, const struct hip_xml_elm *elm, 
00509                const char *cdata)
00510 {
00511     struct dav_lock *lock = userdata;
00512     return end_element_common(lock, elm, cdata);
00513 }
00514 
00515 static void *create_private(void *userdata, const char *uri)
00516 {
00517     return ne_calloc(sizeof(struct dav_lock));
00518 }
00519 
00520 /* Discover all locks on URI */
00521 int dav_lock_discover(http_session *sess, const char *uri, 
00522                     dav_lock_result callback, void *userdata)
00523 {
00524     dav_propfind_handler *handler;
00525     struct discover_ctx ctx = {0};
00526     int ret;
00527     
00528     ctx.results = callback;
00529     ctx.userdata = userdata;
00530 
00531     handler = dav_propfind_create(sess, uri, DAV_DEPTH_ZERO);
00532 
00533     dav_propfind_set_complex(handler, lock_props, create_private, NULL);
00534     
00535     hip_xml_push_handler(dav_propfind_get_parser(handler), lock_elms, 
00536                       check_context, NULL, end_element_ldisc, handler);
00537     
00538     ret = dav_propfind_named(handler, discover_results, &ctx);
00539     
00540     dav_propfind_destroy(handler);
00541 
00542     return ret;
00543 }
00544 
00545 int dav_lock(http_session *sess, struct dav_lock *lock) 
00546 {
00547     http_req *req = http_request_create(sess, "LOCK", lock->uri);
00548     sbuffer body = sbuffer_create();
00549     hip_xml_parser *parser = hip_xml_create();
00550     int ret, parse_failed;
00551 
00552     hip_xml_push_handler(parser, lock_elms, check_context, 
00553                       NULL, end_element_lock, lock);
00554     
00555     /* Create the body */
00556     sbuffer_concat(body, "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL
00557                   "<lockinfo xmlns='DAV:'>" EOL " <lockscope>",
00558                   lock->scope==dav_lockscope_exclusive?
00559                   "<exclusive/>":"<shared/>",
00560                   "</lockscope>" EOL
00561                   "<locktype><write/></locktype>", NULL);
00562 
00563     if (lock->owner) {
00564        sbuffer_concat(body, "<owner>", lock->owner, "</owner>" EOL, NULL);
00565     }
00566     sbuffer_zappend(body, "</lockinfo>" EOL);
00567 
00568     http_set_request_body_buffer(req, sbuffer_data(body));
00569     http_add_response_body_reader(req, http_accept_2xx, 
00570                               hip_xml_parse_v, parser);
00571     http_add_request_header(req, "Content-Type", "text/xml");
00572     dav_add_depth_header(req, lock->depth);
00573 
00574     /* TODO: 
00575      * By 2518, we need this only if we are creating a lock-null resource.
00576      * Since we don't KNOW whether the lock we're given is a lock-null
00577      * or not, we cover our bases.
00578      */
00579     dav_lock_using_parent(req, lock->uri);
00580     /* This one is clearer from 2518 sec 8.10.4. */
00581     dav_lock_using_resource(req, lock->uri, lock->depth);
00582 
00583     ret = http_request_dispatch(req);
00584 
00585     sbuffer_destroy(body);
00586     parse_failed = !hip_xml_valid(parser);
00587     
00588     if (ret == HTTP_OK && http_get_status(req)->klass == 2) {
00589        if (parse_failed) {
00590            ret = HTTP_ERROR;
00591            http_set_error(sess, hip_xml_get_error(parser));
00592        }
00593        else if (http_get_status(req)->code == 207) {
00594            ret = HTTP_ERROR;
00595            /* TODO: set the error string appropriately */
00596        }
00597     } else {
00598        ret = HTTP_ERROR;
00599     }
00600 
00601     http_request_destroy(req);
00602     hip_xml_destroy(parser);
00603 
00604     /* TODO: free the list */
00605     return ret;
00606 }