UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2018 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 33 #include <cx/buffer.h> 34 #include <cx/utils.h> 35 #include <cx/basic_mempool.h> 36 #include <cx/hash_map.h> 37 38 #include "utils.h" 39 #include "session.h" 40 #include "resource.h" 41 #include "methods.h" 42 43 DavSession* dav_session_new(DavContext *context, char *base_url) { 44 if(!base_url) { 45 return NULL; 46 } 47 cxstring url = cx_str(base_url); 48 if(url.length == 0) { 49 return NULL; 50 } 51 DavSession *sn = malloc(sizeof(DavSession)); 52 memset(sn, 0, sizeof(DavSession)); 53 sn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE); 54 sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE); 55 sn->key = NULL; 56 sn->errorstr = NULL; 57 sn->error = DAV_OK; 58 sn->flags = 0; 59 60 dav_session_set_baseurl(sn, base_url); 61 62 sn->handle = curl_easy_init(); 63 curl_easy_setopt(sn->handle, CURLOPT_FOLLOWLOCATION, 1L); 64 65 // lock manager is created on-demand 66 sn->locks = NULL; 67 68 // set proxy 69 DavProxy *proxy = cx_strprefix(url, CX_STR("https")) ? context->https_proxy 70 : context->http_proxy; 71 72 if (proxy->url) { 73 curl_easy_setopt(sn->handle, CURLOPT_PROXY, proxy->url); 74 if (proxy->username) { 75 curl_easy_setopt(sn->handle, CURLOPT_PROXYUSERNAME, 76 proxy->username); 77 if (proxy->password) { 78 curl_easy_setopt(sn->handle, CURLOPT_PROXYPASSWORD, 79 proxy->password); 80 } else { 81 // TODO: prompt 82 } 83 } 84 if(proxy->no_proxy) { 85 curl_easy_setopt(sn->handle, CURLOPT_NOPROXY, 86 proxy->no_proxy); 87 } 88 } 89 90 // set url 91 #if LIBCURL_VERSION_NUM >= 0x072D00 92 curl_easy_setopt(sn->handle, CURLOPT_DEFAULT_PROTOCOL, "http"); 93 #endif 94 curl_easy_setopt(sn->handle, CURLOPT_URL, base_url); 95 96 // add to context 97 dav_context_add_session(context, sn); 98 sn->context = context; 99 100 return sn; 101 } 102 103 DavSession* dav_session_new_auth( 104 DavContext *context, 105 char *base_url, 106 char *user, 107 char *password) 108 { 109 DavSession *sn = dav_session_new(context, base_url); 110 if(!sn) { 111 return NULL; 112 } 113 dav_session_set_auth(sn, user, password); 114 return sn; 115 } 116 117 DavSession* dav_session_clone(DavSession *sn) { 118 CURL *newhandle = curl_easy_duphandle(sn->handle); 119 120 DavSession *newsn = malloc(sizeof(DavSession)); 121 memset(newsn, 0, sizeof(DavSession)); 122 newsn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE); 123 newsn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE); 124 newsn->key = sn->key; 125 newsn->errorstr = NULL; 126 newsn->error = DAV_OK; 127 newsn->flags = 0; 128 129 newsn->handle = newhandle; 130 131 newsn->base_url = cx_strdup_a(newsn->mp->allocator, cx_str(sn->base_url)).ptr; 132 newsn->auth_prompt = sn->auth_prompt; 133 newsn->authprompt_userdata = sn->authprompt_userdata; 134 newsn->logfunc = sn->logfunc; 135 newsn->get_progress = sn->get_progress; 136 newsn->put_progress = sn->put_progress; 137 newsn->progress_userdata = sn->progress_userdata; 138 139 // add to context 140 dav_context_add_session(sn->context, newsn); 141 newsn->context = sn->context; 142 143 return newsn; 144 } 145 146 void dav_session_set_auth(DavSession *sn, const char *user, const char *password) { 147 if(user && password) { 148 dav_session_set_auth_s(sn, cx_str(user), cx_str(password)); 149 } 150 } 151 152 void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password) { 153 if(user.length > 0 && password.length > 0) { 154 size_t upwdlen = user.length + password.length + 2; 155 char *upwdbuf = malloc(upwdlen); 156 snprintf(upwdbuf, upwdlen, "%.*s:%.*s", (int)user.length, user.ptr, (int)password.length, password.ptr); 157 curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf); 158 free(upwdbuf); 159 } 160 } 161 162 void dav_session_set_baseurl(DavSession *sn, char *base_url) { 163 const CxAllocator *a = sn->mp->allocator; 164 if(sn->base_url) { 165 cxFree(a, sn->base_url); 166 } 167 168 cxstring url = cx_str(base_url); 169 if(url.ptr[url.length - 1] == '/') { 170 cxmutstr url_m = cx_strdup_a(a, cx_str(base_url)); 171 sn->base_url = url_m.ptr; 172 } else { 173 char *url_str = cxMalloc(a, url.length + 2); 174 memcpy(url_str, base_url, url.length); 175 url_str[url.length] = '/'; 176 url_str[url.length + 1] = '\0'; 177 sn->base_url = url_str; 178 } 179 } 180 181 void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags) { 182 sn->key = key; 183 // TODO: review sanity 184 if(flags != 0) { 185 sn->flags |= flags; 186 } else { 187 sn->flags |= DAV_SESSION_ENCRYPT_CONTENT; 188 } 189 } 190 191 void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata) { 192 sn->auth_prompt = func; 193 sn->authprompt_userdata = userdata; 194 } 195 196 void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata) { 197 sn->get_progress = get; 198 sn->put_progress = put; 199 sn->progress_userdata = userdata; 200 } 201 202 CURLcode dav_session_curl_perform(DavSession *sn, long *status) { 203 return dav_session_curl_perform_buf(sn, NULL, NULL, status); 204 } 205 206 CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status) { 207 CURLcode ret = curl_easy_perform(sn->handle); 208 long http_status; 209 curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status); 210 if(ret == CURLE_OK) { 211 if(sn->logfunc) { 212 char *log_method; 213 char *log_url; 214 curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &log_url); 215 curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_METHOD , &log_method); 216 char *log_reqbody = NULL; 217 size_t log_reqbodylen = 0; 218 char *log_rpbody = NULL; 219 size_t log_rpbodylen = 0; 220 if(request) { 221 log_reqbody = request->space; 222 log_reqbodylen = request->size; 223 } 224 if(response) { 225 log_rpbody = response->space; 226 log_rpbodylen = response->size; 227 } 228 sn->logfunc(sn, log_method, log_url, log_reqbody, log_reqbodylen, http_status, log_rpbody, log_rpbodylen); 229 } 230 231 if(http_status == 401 && sn->auth_prompt) { 232 if(!sn->auth_prompt(sn, sn->authprompt_userdata)) { 233 if(request) { 234 cxBufferSeek(request, 0, SEEK_SET); 235 } 236 if(response) { 237 cxBufferSeek(response, 0, SEEK_SET); 238 } 239 ret = curl_easy_perform(sn->handle); 240 curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status); 241 } 242 } 243 244 } 245 246 if(status) { 247 *status = http_status; 248 } 249 return ret; 250 } 251 252 int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { 253 DavResource *res = clientp; 254 DavSession *sn = res->session; 255 if(sn->get_progress) { 256 sn->get_progress(res, (int64_t)dltotal, (int64_t)dlnow, sn->progress_userdata); 257 } 258 return 0; 259 } 260 261 int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { 262 DavResource *res = clientp; 263 DavSession *sn = res->session; 264 if(sn->put_progress) { 265 sn->put_progress(res, (int64_t)ultotal, (int64_t)ulnow, sn->progress_userdata); 266 } 267 return 0; 268 } 269 270 void dav_session_set_error(DavSession *sn, C Lcode c, int status) { 271 if(status > 0) { 272 switch(status) { 273 default: { 274 switch(c) { 275 default: sn->error = DAV_ERROR; 276 } 277 break; 278 } 279 case 401: sn->error = DAV_UNAUTHORIZED; break; 280 case 403: sn->error = DAV_FORBIDDEN; break; 281 case 404: sn->error = DAV_NOT_FOUND; break; 282 case 405: sn->error = DAV_METHOD_NOT_ALLOWED; break; 283 case 407: sn->error = DAV_PROXY_AUTH_REQUIRED; break; 284 case 409: sn->error = DAV_CONFLICT; break; 285 case 412: sn->error = DAV_PRECONDITION_FAILED; break; 286 case 413: sn->error = DAV_REQUEST_ENTITY_TOO_LARGE; break; 287 case 414: sn->error = DAV_REQUEST_URL_TOO_LONG; break; 288 case 423: sn->error = DAV_LOCKED; break; 289 case 511: sn->error = DAV_NET_AUTH_REQUIRED; break; 290 } 291 } else { 292 switch(c) { 293 case CURLE_UNSUPPORTED_PROTOCOL: sn->error = DAV_UNSUPPORTED_PROTOCOL; break; 294 case CURLE_COULDNT_RESOLVE_PROXY: sn->error = DAV_COULDNT_RESOLVE_PROXY; break; 295 case CURLE_COULDNT_RESOLVE_HOST: sn->error = DAV_COULDNT_RESOLVE_HOST; break; 296 case CURLE_COULDNT_CONNECT: sn->error = DAV_COULDNT_CONNECT; break; 297 case CURLE_OPERATION_TIMEDOUT: sn->error = DAV_TIMEOUT; break; 298 case CURLE_SSL_CONNECT_ERROR: 299 case CURLE_PEER_FAILED_VERIFICATION: 300 case CURLE_SSL_ENGINE_NOTFOUND: 301 case CURLE_SSL_ENGINE_SETFAILED: 302 case CURLE_SSL_CERTPROBLEM: 303 case CURLE_SSL_CIPHER: 304 //#ifndef CURLE_SSL_CACERT 305 // case CURLE_SSL_CACERT: 306 //#endif 307 case CURLE_SSL_CACERT_BADFILE: 308 case CURLE_SSL_SHUTDOWN_FAILED: 309 case CURLE_SSL_CRL_BADFILE: 310 case CURLE_SSL_ISSUER_ERROR: sn->error = DAV_SSL_ERROR; break; 311 default: sn->error = DAV_ERROR; break; 312 } 313 } 314 if(c != CURLE_OK) { 315 dav_session_set_errstr(sn, curl_easy_strerror(c)); 316 } else { 317 dav_session_set_errstr(sn, NULL); 318 } 319 } 320 321 void dav_session_set_errstr(DavSession *sn, const char *str) { 322 if(sn->errorstr) { 323 dav_session_free(sn, sn->errorstr); 324 } 325 char *errstr = NULL; 326 if(str) { 327 errstr = dav_session_strdup(sn, str); 328 } 329 sn->errorstr = errstr; 330 } 331 332 void dav_session_destroy(DavSession *sn) { 333 // remove session from context 334 if (dav_context_remove_session(sn->context, sn)) { 335 fprintf(stderr, "Error: session not found in ctx->sessions\n"); 336 dav_session_destructor(sn); 337 } 338 } 339 340 void dav_session_destructor(DavSession *sn) { 341 cxMempoolDestroy(sn->mp); 342 curl_easy_cleanup(sn->handle); 343 free(sn); 344 } 345 346 347 void* dav_session_malloc(DavSession *sn, size_t size) { 348 return cxMalloc(sn->mp->allocator, size); 349 } 350 351 void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) { 352 return cxCalloc(sn->mp->allocator, nelm, size); 353 } 354 355 void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) { 356 return cxRealloc(sn->mp->allocator, ptr, size); 357 } 358 359 void dav_session_free(DavSession *sn, void *ptr) { 360 cxFree(sn->mp->allocator, ptr); 361 } 362 363 char* dav_session_strdup(DavSession *sn, const char *str) { 364 return cx_strdup_a(sn->mp->allocator, cx_str((char*)str)).ptr; 365 } 366 367 368 char* dav_session_create_plain_href(DavSession *sn, const char *path) { 369 if(!DAV_ENCRYPT_NAME(sn) && !DAV_DECRYPT_NAME(sn)) { 370 // non encrypted file names 371 char *url = util_path_to_url(sn, path); 372 char *href = dav_session_strdup(sn, util_url_path(url)); 373 free(url); 374 return href; 375 } else { 376 return NULL; 377 } 378 } 379 380 char* dav_session_get_href(DavSession *sn, const char *path) { 381 if(DAV_DECRYPT_NAME(sn) || DAV_ENCRYPT_NAME(sn)) { 382 cxstring p = cx_str(path); 383 CxBuffer href; 384 CxBuffer pbuf; 385 cxBufferInit(&href, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); 386 cxBufferInit(&pbuf, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); 387 388 int start = 0; 389 int begin = 0; 390 391 // check path cache 392 char *cp = strdup(path); 393 //printf("cp: %s\n", cp); 394 while(strlen(cp) > 1) { 395 char *cached = cxMapGet(sn->pathcache, cx_hash_key_str(cp)); 396 if(cached) { 397 start = strlen(cp); 398 begin = start; 399 cxBufferPutString(&href, cached); 400 break; 401 } else { 402 // check, if the parent path is cached 403 char *f = cp; 404 cp = util_parent_path(cp); 405 free(f); 406 } 407 } 408 free(cp); 409 if(href.pos == 0) { 410 // if there are no cached elements we have to add the base url path 411 // to the href buffer 412 cxBufferPutString(&href, util_url_path(sn->base_url)); 413 } 414 415 // create resource for name lookup 416 cxmutstr rp = cx_strdup(cx_strn(path, start)); 417 DavResource *root = dav_resource_new(sn, rp.ptr); 418 free(rp.ptr); 419 resource_set_href(root, cx_strn(href.space, href.pos)); 420 421 // create request buffer for propfind requests 422 CxBuffer *rqbuf = create_basic_propfind_request(); 423 424 cxstring remaining = cx_strsubs(p, start); 425 CxStrtokCtx elms = cx_strtok(remaining, CX_STR("/"), INT_MAX); 426 DavResource *res = root; 427 cxBufferPutString(&pbuf, res->path); 428 // iterate over all remaining path elements 429 cxstring elm; 430 while(cx_strtok_next(&elms, &elm)) { 431 if(elm.length > 0) { 432 //printf("elm: %.*s\n", elm.length, elm.ptr); 433 DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr); 434 435 // if necessary add a path separator 436 if(pbuf.space[pbuf.pos-1] != '/') { 437 if(href.space[href.pos-1] != '/') { 438 cxBufferPut(&href, '/'); 439 } 440 cxBufferPut(&pbuf, '/'); 441 } 442 // add last path/href to the cache 443 cxstring pp = cx_strn(pbuf.space, pbuf.size); 444 cxstring hh = cx_strn(href.space, href.size); 445 dav_session_cache_path(sn, pp, hh); 446 447 cxBufferWrite(elm.ptr, 1, elm.length, &pbuf); 448 if(child) { 449 // href is already URL encoded, so don't encode again 450 cxBufferPutString(&href, util_resource_name(child->href)); 451 res = child; 452 } else if(DAV_ENCRYPT_NAME(sn)) { 453 char *random_name = util_random_str(); 454 cxBufferPutString(&href, random_name); 455 free(random_name); 456 } else { 457 // path is not URL encoded, so we have to do this here 458 cxstring resname = cx_str(util_resource_name((const char*)path)); 459 // the name of collections ends with 460 // a trailing slash, which MUST NOT be encoded 461 if(resname.ptr[resname.length-1] == '/') { 462 char *esc = curl_easy_escape(sn->handle, 463 resname.ptr, resname.length-1); 464 cxBufferWrite(esc, 1, strlen(esc), &href); 465 cxBufferPut(&href, '/'); 466 curl_free(esc); 467 } else { 468 char *esc = curl_easy_escape(sn->handle, 469 resname.ptr, resname.length); 470 cxBufferWrite(esc, 1, strlen(esc), &href); 471 curl_free(esc); 472 } 473 } 474 } 475 } 476 477 // if necessary add a path separator 478 if(p.ptr[p.length-1] == '/') { 479 if(href.space[href.pos-1] != '/') { 480 cxBufferPut(&href, '/'); 481 } 482 cxBufferPut(&pbuf, '/'); 483 } 484 // add the final path to the cache 485 cxstring pp = cx_strn(pbuf.space, pbuf.size); 486 cxstring hh = cx_strn(href.space, href.size); 487 dav_session_cache_path(sn, pp, hh); 488 489 cxmutstr href_str = cx_strdup_a( 490 sn->mp->allocator, 491 cx_strn(href.space, href.size)); 492 493 // cleanup 494 dav_resource_free_all(root); 495 cxBufferFree(rqbuf); 496 497 cxBufferDestroy(&pbuf); 498 cxBufferDestroy(&href); 499 500 return href_str.ptr; 501 } else { 502 return dav_session_create_plain_href(sn, path); 503 } 504 } 505 506 DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name) { 507 if(res && !dav_propfind(sn, res, rqbuf)) { 508 DavResource *child = res->children; 509 while(child) { 510 if(!strcmp(child->name, name)) { 511 return child; 512 } 513 child = child->next; 514 } 515 } 516 return NULL; 517 } 518 519 void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href) { 520 CxHashKey path_key = cx_hash_key(path.ptr, path.length); 521 char *elm = cxMapGet(sn->pathcache, path_key); 522 if(!elm) { 523 cxmutstr href_s = cx_strdup_a(sn->mp->allocator, href); 524 cxMapPut(sn->pathcache, path_key, href_s.ptr); 525 } 526 } 527 528 529 DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout) { 530 DavLock *lock = dav_session_malloc(sn, sizeof(DavLock)); 531 lock->path = NULL; 532 lock->token = dav_session_strdup(sn, token); 533 534 // TODO: timeout 535 536 return lock; 537 } 538 539 void dav_destroy_lock(DavSession *sn, DavLock *lock) { 540 dav_session_free(sn, lock->token); 541 if(lock->path) { 542 dav_session_free(sn, lock->path); 543 } 544 dav_session_free(sn, lock); 545 } 546 547 548 static int dav_lock_cmp(void const *left, void const *right) { 549 const DavLock *l = left; 550 const DavLock *r = right; 551 return strcmp(l->path, r->path); 552 } 553 554 static int create_lock_manager(DavSession *sn) { 555 // create lock manager 556 DavLockManager *locks = cxMalloc(sn->mp->allocator, sizeof(DavLockManager)); 557 locks->resource_locks = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 16); 558 locks->collection_locks = cxLinkedListCreate(sn->mp->allocator, dav_lock_cmp, CX_STORE_POINTERS); 559 sn->locks = locks; 560 return 0; 561 } 562 563 static DavLockManager* get_lock_manager(DavSession *sn) { 564 DavLockManager *locks = sn->locks; 565 if(!locks) { 566 if(create_lock_manager(sn)) { 567 return NULL; 568 } 569 locks = sn->locks; 570 } 571 return locks; 572 } 573 574 int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock) { 575 DavLockManager *locks = get_lock_manager(sn); 576 if(!locks) { 577 return -1; 578 } 579 580 CxHashKey path_key = cx_hash_key_str(path); 581 if(cxMapGet(locks->resource_locks, path_key)) { 582 return -1; 583 } 584 585 cxMapPut(locks->resource_locks, path_key, lock); 586 return 0; 587 } 588 589 int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock) { 590 DavLockManager *locks = get_lock_manager(sn); 591 if(!locks) { 592 return -1; 593 } 594 595 lock->path = dav_session_strdup(sn, path); 596 cxListAdd(locks->collection_locks, lock); 597 cxListSort(locks->collection_locks); 598 599 return 0; 600 } 601 602 DavLock* dav_get_lock(DavSession *sn, const char *path) { 603 DavLockManager *locks = get_lock_manager(sn); 604 if(!locks) { 605 return NULL; 606 } 607 608 cxstring p = cx_str(path); 609 610 DavLock *lock = cxMapGet(locks->resource_locks, cx_hash_key(p.ptr, p.length)); 611 if(lock) { 612 return lock; 613 } 614 615 CxIterator i = cxListIterator(locks->collection_locks); 616 cx_foreach(DavLock*, col_lock, i) { 617 int cmd = strcmp(path, col_lock->path); 618 if(cmd == 0) { 619 return col_lock; 620 } else if(cx_strprefix(p, cx_str(col_lock->path))) { 621 return col_lock; 622 } else if(cmd > 0) { 623 break; 624 } 625 } 626 627 return NULL; 628 } 629 630 void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock) { 631 DavLockManager *locks = get_lock_manager(sn); 632 if(!locks) { 633 return; 634 } 635 636 if(cxMapRemoveAndGet(locks->resource_locks, cx_hash_key_str(path))) { 637 return; 638 } 639 640 CxMutIterator i = cxListMutIterator(locks->collection_locks); 641 int rm = 0; 642 cx_foreach(DavLock* , cl, i) { 643 if(rm) { 644 break; 645 } 646 if(cl == lock) { 647 cxIteratorFlagRemoval(i); 648 rm = 1; 649 } 650 } 651 } 652