1/* 2 * locks.c : entry point for locking RA functions for ra_serf 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#include <apr_uri.h> 27#include <serf.h> 28 29#include "svn_dav.h" 30#include "svn_pools.h" 31#include "svn_ra.h" 32 33#include "../libsvn_ra/ra_loader.h" 34#include "svn_config.h" 35#include "svn_path.h" 36#include "svn_time.h" 37#include "svn_private_config.h" 38 39#include "ra_serf.h" 40 41 42/* 43 * This enum represents the current state of our XML parsing for a REPORT. 44 */ 45enum { 46 INITIAL = 0, 47 MULTISTATUS, 48 RESPONSE, 49 PROPSTAT, 50 PROP, 51 LOCK_DISCOVERY, 52 ACTIVE_LOCK, 53 LOCK_TYPE, 54 LOCK_SCOPE, 55 DEPTH, 56 TIMEOUT, 57 LOCK_TOKEN, 58 OWNER, 59 HREF 60}; 61 62typedef struct lock_info_t { 63 apr_pool_t *pool; 64 65 const char *path; 66 67 svn_lock_t *lock; 68 69 svn_boolean_t force; 70 svn_revnum_t revision; 71 72 svn_boolean_t read_headers; 73 74 svn_ra_serf__handler_t *handler; 75 76 /* The expat handler. We wrap this to do a bit more work. */ 77 svn_ra_serf__response_handler_t inner_handler; 78 void *inner_baton; 79 80} lock_info_t; 81 82#define D_ "DAV:" 83#define S_ SVN_XML_NAMESPACE 84static const svn_ra_serf__xml_transition_t locks_ttable[] = { 85 /* The INITIAL state can transition into D:prop (LOCK) or 86 to D:multistatus (PROPFIND) */ 87 { INITIAL, D_, "prop", PROP, 88 FALSE, { NULL }, FALSE }, 89 { INITIAL, D_, "multistatus", MULTISTATUS, 90 FALSE, { NULL }, FALSE }, 91 92 { MULTISTATUS, D_, "response", RESPONSE, 93 FALSE, { NULL }, FALSE }, 94 95 { RESPONSE, D_, "propstat", PROPSTAT, 96 FALSE, { NULL }, FALSE }, 97 98 { PROPSTAT, D_, "prop", PROP, 99 FALSE, { NULL }, FALSE }, 100 101 { PROP, D_, "lockdiscovery", LOCK_DISCOVERY, 102 FALSE, { NULL }, FALSE }, 103 104 { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK, 105 FALSE, { NULL }, FALSE }, 106 107#if 0 108 /* ### we don't really need to parse locktype/lockscope. we know what 109 ### the values are going to be. we *could* validate that the only 110 ### possible children are D:write and D:exclusive. we'd need to 111 ### modify the state transition to tell us about all children 112 ### (ie. maybe support "*" for the name) and then validate. but it 113 ### just isn't important to validate, so disable this for now... */ 114 115 { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE, 116 FALSE, { NULL }, FALSE }, 117 118 { LOCK_TYPE, D_, "write", WRITE, 119 FALSE, { NULL }, TRUE }, 120 121 { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE, 122 FALSE, { NULL }, FALSE }, 123 124 { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE, 125 FALSE, { NULL }, TRUE }, 126#endif /* 0 */ 127 128 { ACTIVE_LOCK, D_, "timeout", TIMEOUT, 129 TRUE, { NULL }, TRUE }, 130 131 { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN, 132 FALSE, { NULL }, FALSE }, 133 134 { LOCK_TOKEN, D_, "href", HREF, 135 TRUE, { NULL }, TRUE }, 136 137 { ACTIVE_LOCK, D_, "owner", OWNER, 138 TRUE, { NULL }, TRUE }, 139 140 /* ACTIVE_LOCK has a D:depth child, but we can ignore that. */ 141 142 { 0 } 143}; 144 145 146/* Conforms to svn_ra_serf__xml_closed_t */ 147static svn_error_t * 148locks_closed(svn_ra_serf__xml_estate_t *xes, 149 void *baton, 150 int leaving_state, 151 const svn_string_t *cdata, 152 apr_hash_t *attrs, 153 apr_pool_t *scratch_pool) 154{ 155 lock_info_t *lock_ctx = baton; 156 157 if (leaving_state == TIMEOUT) 158 { 159 if (strcasecmp(cdata->data, "Infinite") == 0) 160 lock_ctx->lock->expiration_date = 0; 161 else if (strncasecmp(cdata->data, "Second-", 7) == 0) 162 { 163 unsigned n; 164 SVN_ERR(svn_cstring_atoui(&n, cdata->data+7)); 165 166 lock_ctx->lock->expiration_date = apr_time_now() + 167 apr_time_from_sec(n); 168 } 169 else 170 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 171 _("Invalid LOCK timeout value '%s'"), 172 cdata->data); 173 } 174 else if (leaving_state == HREF) 175 { 176 if (cdata->len) 177 { 178 char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len); 179 180 apr_collapse_spaces(buf, buf); 181 lock_ctx->lock->token = buf; 182 } 183 } 184 else if (leaving_state == OWNER) 185 { 186 if (cdata->len) 187 { 188 lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool, 189 cdata->data, cdata->len); 190 } 191 } 192 193 return SVN_NO_ERROR; 194} 195 196 197static svn_error_t * 198set_lock_headers(serf_bucket_t *headers, 199 void *baton, 200 apr_pool_t *pool) 201{ 202 lock_info_t *lock_ctx = baton; 203 204 if (lock_ctx->force) 205 { 206 serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, 207 SVN_DAV_OPTION_LOCK_STEAL); 208 } 209 210 if (SVN_IS_VALID_REVNUM(lock_ctx->revision)) 211 { 212 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER, 213 apr_ltoa(pool, lock_ctx->revision)); 214 } 215 216 return APR_SUCCESS; 217} 218 219 220/* Register an error within the session. If something is already there, 221 then it will take precedence. */ 222static svn_error_t * 223determine_error(svn_ra_serf__handler_t *handler, 224 svn_error_t *err) 225{ 226 { 227 apr_status_t errcode; 228 229 if (handler->sline.code == 423) 230 errcode = SVN_ERR_FS_PATH_ALREADY_LOCKED; 231 else if (handler->sline.code == 403) 232 errcode = SVN_ERR_RA_DAV_FORBIDDEN; 233 else 234 return err; 235 236 /* Client-side or server-side error already. Return it. */ 237 if (err != NULL) 238 return err; 239 240 /* The server did not send us a detailed human-readable error. 241 Provide a generic error. */ 242 err = svn_error_createf(errcode, NULL, 243 _("Lock request failed: %d %s"), 244 handler->sline.code, 245 handler->sline.reason); 246 } 247 248 return err; 249} 250 251 252/* Implements svn_ra_serf__response_handler_t */ 253static svn_error_t * 254handle_lock(serf_request_t *request, 255 serf_bucket_t *response, 256 void *handler_baton, 257 apr_pool_t *pool) 258{ 259 lock_info_t *ctx = handler_baton; 260 261 /* 403 (Forbidden) when a lock doesn't exist. 262 423 (Locked) when a lock already exists. */ 263 if (ctx->handler->sline.code == 403 264 || ctx->handler->sline.code == 423) 265 { 266 /* Go look in the body for a server-provided error. This will 267 reset flags for the core handler to Do The Right Thing. We 268 won't be back to this handler again. */ 269 return svn_error_trace(svn_ra_serf__expect_empty_body( 270 request, response, ctx->handler, pool)); 271 } 272 273 if (!ctx->read_headers) 274 { 275 serf_bucket_t *headers; 276 const char *val; 277 278 headers = serf_bucket_response_get_headers(response); 279 280 val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER); 281 if (val) 282 { 283 ctx->lock->owner = apr_pstrdup(ctx->pool, val); 284 } 285 286 val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER); 287 if (val) 288 { 289 SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val, 290 ctx->pool)); 291 } 292 293 ctx->read_headers = TRUE; 294 } 295 296 return ctx->inner_handler(request, response, ctx->inner_baton, pool); 297} 298 299/* Implements svn_ra_serf__request_body_delegate_t */ 300static svn_error_t * 301create_getlock_body(serf_bucket_t **body_bkt, 302 void *baton, 303 serf_bucket_alloc_t *alloc, 304 apr_pool_t *pool) 305{ 306 serf_bucket_t *buckets; 307 308 buckets = serf_bucket_aggregate_create(alloc); 309 310 svn_ra_serf__add_xml_header_buckets(buckets, alloc); 311 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "propfind", 312 "xmlns", "DAV:", 313 NULL); 314 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "prop", NULL); 315 svn_ra_serf__add_tag_buckets(buckets, "lockdiscovery", NULL, alloc); 316 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "prop"); 317 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "propfind"); 318 319 *body_bkt = buckets; 320 return SVN_NO_ERROR; 321} 322 323static svn_error_t* 324setup_getlock_headers(serf_bucket_t *headers, 325 void *baton, 326 apr_pool_t *pool) 327{ 328 serf_bucket_headers_setn(headers, "Depth", "0"); 329 330 return SVN_NO_ERROR; 331} 332 333/* Implements svn_ra_serf__request_body_delegate_t */ 334static svn_error_t * 335create_lock_body(serf_bucket_t **body_bkt, 336 void *baton, 337 serf_bucket_alloc_t *alloc, 338 apr_pool_t *pool) 339{ 340 lock_info_t *ctx = baton; 341 serf_bucket_t *buckets; 342 343 buckets = serf_bucket_aggregate_create(alloc); 344 345 svn_ra_serf__add_xml_header_buckets(buckets, alloc); 346 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo", 347 "xmlns", "DAV:", 348 NULL); 349 350 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockscope", NULL); 351 svn_ra_serf__add_tag_buckets(buckets, "exclusive", NULL, alloc); 352 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockscope"); 353 354 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "locktype", NULL); 355 svn_ra_serf__add_tag_buckets(buckets, "write", NULL, alloc); 356 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "locktype"); 357 358 if (ctx->lock->comment) 359 { 360 svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment, 361 alloc); 362 } 363 364 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo"); 365 366 *body_bkt = buckets; 367 return SVN_NO_ERROR; 368} 369 370svn_error_t * 371svn_ra_serf__get_lock(svn_ra_session_t *ra_session, 372 svn_lock_t **lock, 373 const char *path, 374 apr_pool_t *result_pool) 375{ 376 svn_ra_serf__session_t *session = ra_session->priv; 377 svn_ra_serf__handler_t *handler; 378 svn_ra_serf__xml_context_t *xmlctx; 379 apr_pool_t *scratch_pool = svn_pool_create(result_pool); 380 lock_info_t *lock_ctx; 381 const char *req_url; 382 svn_error_t *err; 383 384 req_url = svn_path_url_add_component2(session->session_url.path, path, 385 scratch_pool); 386 387 lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx)); 388 lock_ctx->pool = result_pool; 389 lock_ctx->path = req_url; 390 lock_ctx->lock = svn_lock_create(result_pool); 391 lock_ctx->lock->path = apr_pstrdup(result_pool, path); 392 393 xmlctx = svn_ra_serf__xml_context_create(locks_ttable, 394 NULL, locks_closed, NULL, 395 lock_ctx, 396 scratch_pool); 397 handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool); 398 399 handler->method = "PROPFIND"; 400 handler->path = req_url; 401 handler->body_type = "text/xml"; 402 handler->conn = session->conns[0]; 403 handler->session = session; 404 405 handler->body_delegate = create_getlock_body; 406 handler->body_delegate_baton = lock_ctx; 407 408 handler->header_delegate = setup_getlock_headers; 409 handler->header_delegate_baton = lock_ctx; 410 411 lock_ctx->inner_handler = handler->response_handler; 412 lock_ctx->inner_baton = handler->response_baton; 413 handler->response_handler = handle_lock; 414 handler->response_baton = lock_ctx; 415 416 lock_ctx->handler = handler; 417 418 err = svn_ra_serf__context_run_one(handler, scratch_pool); 419 err = determine_error(handler, err); 420 421 if (handler->sline.code == 404) 422 { 423 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, err, 424 _("Malformed URL for repository")); 425 } 426 if (err) 427 { 428 /* TODO Shh. We're telling a white lie for now. */ 429 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, 430 _("Server does not support locking features")); 431 } 432 433 if (lock_ctx->lock && lock_ctx->lock->token) 434 *lock = lock_ctx->lock; 435 else 436 *lock = NULL; 437 438 svn_pool_destroy(scratch_pool); 439 440 return SVN_NO_ERROR; 441} 442 443svn_error_t * 444svn_ra_serf__lock(svn_ra_session_t *ra_session, 445 apr_hash_t *path_revs, 446 const char *comment, 447 svn_boolean_t force, 448 svn_ra_lock_callback_t lock_func, 449 void *lock_baton, 450 apr_pool_t *scratch_pool) 451{ 452 svn_ra_serf__session_t *session = ra_session->priv; 453 apr_hash_index_t *hi; 454 apr_pool_t *iterpool; 455 456 iterpool = svn_pool_create(scratch_pool); 457 458 /* ### TODO for issue 2263: Send all the locks over the wire at once. This 459 ### loop is just a temporary shim. 460 ### an alternative, which is backwards-compat with all servers is to 461 ### pipeline these requests. ie. stop using run_wait/run_one. */ 462 463 for (hi = apr_hash_first(scratch_pool, path_revs); 464 hi; 465 hi = apr_hash_next(hi)) 466 { 467 svn_ra_serf__handler_t *handler; 468 svn_ra_serf__xml_context_t *xmlctx; 469 const char *req_url; 470 lock_info_t *lock_ctx; 471 svn_error_t *err; 472 svn_error_t *new_err = NULL; 473 474 svn_pool_clear(iterpool); 475 476 lock_ctx = apr_pcalloc(iterpool, sizeof(*lock_ctx)); 477 478 lock_ctx->pool = iterpool; 479 lock_ctx->path = svn__apr_hash_index_key(hi); 480 lock_ctx->revision = *((svn_revnum_t*)svn__apr_hash_index_val(hi)); 481 lock_ctx->lock = svn_lock_create(iterpool); 482 lock_ctx->lock->path = lock_ctx->path; 483 lock_ctx->lock->comment = comment; 484 485 lock_ctx->force = force; 486 req_url = svn_path_url_add_component2(session->session_url.path, 487 lock_ctx->path, iterpool); 488 489 xmlctx = svn_ra_serf__xml_context_create(locks_ttable, 490 NULL, locks_closed, NULL, 491 lock_ctx, 492 iterpool); 493 handler = svn_ra_serf__create_expat_handler(xmlctx, iterpool); 494 495 handler->method = "LOCK"; 496 handler->path = req_url; 497 handler->body_type = "text/xml"; 498 handler->conn = session->conns[0]; 499 handler->session = session; 500 501 handler->header_delegate = set_lock_headers; 502 handler->header_delegate_baton = lock_ctx; 503 504 handler->body_delegate = create_lock_body; 505 handler->body_delegate_baton = lock_ctx; 506 507 lock_ctx->inner_handler = handler->response_handler; 508 lock_ctx->inner_baton = handler->response_baton; 509 handler->response_handler = handle_lock; 510 handler->response_baton = lock_ctx; 511 512 lock_ctx->handler = handler; 513 514 err = svn_ra_serf__context_run_one(handler, iterpool); 515 err = determine_error(handler, err); 516 517 if (lock_func) 518 new_err = lock_func(lock_baton, lock_ctx->path, TRUE, lock_ctx->lock, 519 err, iterpool); 520 svn_error_clear(err); 521 522 SVN_ERR(new_err); 523 } 524 525 svn_pool_destroy(iterpool); 526 527 return SVN_NO_ERROR; 528} 529 530struct unlock_context_t { 531 const char *token; 532 svn_boolean_t force; 533}; 534 535static svn_error_t * 536set_unlock_headers(serf_bucket_t *headers, 537 void *baton, 538 apr_pool_t *pool) 539{ 540 struct unlock_context_t *ctx = baton; 541 542 serf_bucket_headers_set(headers, "Lock-Token", ctx->token); 543 if (ctx->force) 544 { 545 serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, 546 SVN_DAV_OPTION_LOCK_BREAK); 547 } 548 549 return SVN_NO_ERROR; 550} 551 552svn_error_t * 553svn_ra_serf__unlock(svn_ra_session_t *ra_session, 554 apr_hash_t *path_tokens, 555 svn_boolean_t force, 556 svn_ra_lock_callback_t lock_func, 557 void *lock_baton, 558 apr_pool_t *scratch_pool) 559{ 560 svn_ra_serf__session_t *session = ra_session->priv; 561 apr_hash_index_t *hi; 562 apr_pool_t *iterpool; 563 564 iterpool = svn_pool_create(scratch_pool); 565 566 /* ### TODO for issue 2263: Send all the locks over the wire at once. This 567 ### loop is just a temporary shim. 568 ### an alternative, which is backwards-compat with all servers is to 569 ### pipeline these requests. ie. stop using run_wait/run_one. */ 570 571 for (hi = apr_hash_first(scratch_pool, path_tokens); 572 hi; 573 hi = apr_hash_next(hi)) 574 { 575 svn_ra_serf__handler_t *handler; 576 const char *req_url, *path, *token; 577 svn_lock_t *existing_lock = NULL; 578 struct unlock_context_t unlock_ctx; 579 svn_error_t *err = NULL; 580 svn_error_t *new_err = NULL; 581 582 583 svn_pool_clear(iterpool); 584 585 path = svn__apr_hash_index_key(hi); 586 token = svn__apr_hash_index_val(hi); 587 588 if (force && (!token || token[0] == '\0')) 589 { 590 SVN_ERR(svn_ra_serf__get_lock(ra_session, &existing_lock, path, 591 iterpool)); 592 token = existing_lock ? existing_lock->token : NULL; 593 if (!token) 594 { 595 err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, NULL, 596 _("'%s' is not locked in the repository"), 597 path); 598 599 if (lock_func) 600 { 601 svn_error_t *err2; 602 err2 = lock_func(lock_baton, path, FALSE, NULL, err, 603 iterpool); 604 svn_error_clear(err); 605 err = NULL; 606 if (err2) 607 return svn_error_trace(err2); 608 } 609 else 610 { 611 svn_error_clear(err); 612 err = NULL; 613 } 614 continue; 615 } 616 } 617 618 unlock_ctx.force = force; 619 unlock_ctx.token = apr_pstrcat(iterpool, "<", token, ">", (char *)NULL); 620 621 req_url = svn_path_url_add_component2(session->session_url.path, path, 622 iterpool); 623 624 handler = apr_pcalloc(iterpool, sizeof(*handler)); 625 626 handler->handler_pool = iterpool; 627 handler->method = "UNLOCK"; 628 handler->path = req_url; 629 handler->conn = session->conns[0]; 630 handler->session = session; 631 632 handler->header_delegate = set_unlock_headers; 633 handler->header_delegate_baton = &unlock_ctx; 634 635 handler->response_handler = svn_ra_serf__expect_empty_body; 636 handler->response_baton = handler; 637 638 SVN_ERR(svn_ra_serf__context_run_one(handler, iterpool)); 639 640 switch (handler->sline.code) 641 { 642 case 204: 643 break; /* OK */ 644 case 403: 645 /* Api users expect this specific error code to detect failures */ 646 err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL, 647 _("Unlock request failed: %d %s"), 648 handler->sline.code, 649 handler->sline.reason); 650 break; 651 default: 652 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 653 _("Unlock request failed: %d %s"), 654 handler->sline.code, 655 handler->sline.reason); 656 } 657 658 if (lock_func) 659 new_err = lock_func(lock_baton, path, FALSE, existing_lock, err, 660 iterpool); 661 662 svn_error_clear(err); 663 SVN_ERR(new_err); 664 } 665 666 svn_pool_destroy(iterpool); 667 668 return SVN_NO_ERROR; 669} 670