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