property.c revision 251886
1/* 2 * property.c : property routines 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 <serf.h> 27 28#include "svn_hash.h" 29#include "svn_path.h" 30#include "svn_base64.h" 31#include "svn_xml.h" 32#include "svn_props.h" 33#include "svn_dirent_uri.h" 34 35#include "private/svn_dav_protocol.h" 36#include "private/svn_fspath.h" 37#include "private/svn_string_private.h" 38#include "svn_private_config.h" 39 40#include "ra_serf.h" 41 42 43/* Our current parsing state we're in for the PROPFIND response. */ 44typedef enum prop_state_e { 45 INITIAL = 0, 46 MULTISTATUS, 47 RESPONSE, 48 HREF, 49 PROPSTAT, 50 STATUS, 51 PROP, 52 PROPVAL, 53 COLLECTION, 54 HREF_VALUE 55} prop_state_e; 56 57 58/* 59 * This structure represents a pending PROPFIND response. 60 */ 61typedef struct propfind_context_t { 62 /* pool to issue allocations from */ 63 apr_pool_t *pool; 64 65 svn_ra_serf__handler_t *handler; 66 67 /* associated serf session */ 68 svn_ra_serf__session_t *sess; 69 svn_ra_serf__connection_t *conn; 70 71 /* the requested path */ 72 const char *path; 73 74 /* the requested version (number and string form) */ 75 svn_revnum_t rev; 76 const char *label; 77 78 /* the request depth */ 79 const char *depth; 80 81 /* the list of requested properties */ 82 const svn_ra_serf__dav_props_t *find_props; 83 84 /* hash table that will be updated with the properties 85 * 86 * This can be shared between multiple propfind_context_t 87 * structures 88 */ 89 apr_hash_t *ret_props; 90 91 /* hash table containing all the properties associated with the 92 * "current" <propstat> tag. These will get copied into RET_PROPS 93 * if the status code similarly associated indicates that they are 94 * "good"; otherwise, they'll get discarded. 95 */ 96 apr_hash_t *ps_props; 97 98 /* If not-NULL, add us to this list when we're done. */ 99 svn_ra_serf__list_t **done_list; 100 101 svn_ra_serf__list_t done_item; 102 103} propfind_context_t; 104 105 106#define D_ "DAV:" 107#define S_ SVN_XML_NAMESPACE 108static const svn_ra_serf__xml_transition_t propfind_ttable[] = { 109 { INITIAL, D_, "multistatus", MULTISTATUS, 110 FALSE, { NULL }, TRUE }, 111 112 { MULTISTATUS, D_, "response", RESPONSE, 113 FALSE, { NULL }, FALSE }, 114 115 { RESPONSE, D_, "href", HREF, 116 TRUE, { NULL }, TRUE }, 117 118 { RESPONSE, D_, "propstat", PROPSTAT, 119 FALSE, { NULL }, TRUE }, 120 121 { PROPSTAT, D_, "status", STATUS, 122 TRUE, { NULL }, TRUE }, 123 124 { PROPSTAT, D_, "prop", PROP, 125 FALSE, { NULL }, FALSE }, 126 127 { PROP, "*", "*", PROPVAL, 128 TRUE, { "?V:encoding", NULL }, TRUE }, 129 130 { PROPVAL, D_, "collection", COLLECTION, 131 FALSE, { NULL }, TRUE }, 132 133 { PROPVAL, D_, "href", HREF_VALUE, 134 TRUE, { NULL }, TRUE }, 135 136 { 0 } 137}; 138 139 140/* Return the HTTP status code contained in STATUS_LINE, or 0 if 141 there's a problem parsing it. */ 142static int parse_status_code(const char *status_line) 143{ 144 /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */ 145 if (status_line[0] == 'H' && 146 status_line[1] == 'T' && 147 status_line[2] == 'T' && 148 status_line[3] == 'P' && 149 status_line[4] == '/' && 150 (status_line[5] >= '0' && status_line[5] <= '9') && 151 status_line[6] == '.' && 152 (status_line[7] >= '0' && status_line[7] <= '9') && 153 status_line[8] == ' ') 154 { 155 char *reason; 156 157 return apr_strtoi64(status_line + 8, &reason, 10); 158 } 159 return 0; 160} 161 162 163/* Conforms to svn_ra_serf__path_rev_walker_t */ 164static svn_error_t * 165copy_into_ret_props(void *baton, 166 const char *path, apr_ssize_t path_len, 167 const char *ns, apr_ssize_t ns_len, 168 const char *name, apr_ssize_t name_len, 169 const svn_string_t *val, 170 apr_pool_t *pool) 171{ 172 propfind_context_t *ctx = baton; 173 174 svn_ra_serf__set_ver_prop(ctx->ret_props, path, ctx->rev, ns, name, 175 val, ctx->pool); 176 return SVN_NO_ERROR; 177} 178 179 180/* Conforms to svn_ra_serf__xml_opened_t */ 181static svn_error_t * 182propfind_opened(svn_ra_serf__xml_estate_t *xes, 183 void *baton, 184 int entered_state, 185 const svn_ra_serf__dav_props_t *tag, 186 apr_pool_t *scratch_pool) 187{ 188 propfind_context_t *ctx = baton; 189 190 if (entered_state == PROPVAL) 191 { 192 svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->namespace); 193 svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name); 194 } 195 else if (entered_state == PROPSTAT) 196 { 197 ctx->ps_props = apr_hash_make(ctx->pool); 198 } 199 200 return SVN_NO_ERROR; 201} 202 203 204/* Conforms to svn_ra_serf__xml_closed_t */ 205static svn_error_t * 206propfind_closed(svn_ra_serf__xml_estate_t *xes, 207 void *baton, 208 int leaving_state, 209 const svn_string_t *cdata, 210 apr_hash_t *attrs, 211 apr_pool_t *scratch_pool) 212{ 213 propfind_context_t *ctx = baton; 214 215 if (leaving_state == MULTISTATUS) 216 { 217 /* We've gathered all the data from the reponse. Add this item 218 onto the "done list". External callers will then know this 219 request has been completed (tho stray response bytes may still 220 arrive). */ 221 if (ctx->done_list) 222 { 223 ctx->done_item.data = ctx->handler; 224 ctx->done_item.next = *ctx->done_list; 225 *ctx->done_list = &ctx->done_item; 226 } 227 } 228 else if (leaving_state == HREF) 229 { 230 const char *path; 231 const svn_string_t *val_str; 232 233 if (strcmp(ctx->depth, "1") == 0) 234 path = svn_urlpath__canonicalize(cdata->data, scratch_pool); 235 else 236 path = ctx->path; 237 238 svn_ra_serf__xml_note(xes, RESPONSE, "path", path); 239 240 /* Copy the value into the right pool, then save the HREF. */ 241 val_str = svn_string_dup(cdata, ctx->pool); 242 svn_ra_serf__set_ver_prop(ctx->ret_props, 243 path, ctx->rev, D_, "href", val_str, 244 ctx->pool); 245 } 246 else if (leaving_state == COLLECTION) 247 { 248 svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection"); 249 } 250 else if (leaving_state == HREF_VALUE) 251 { 252 svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data); 253 } 254 else if (leaving_state == STATUS) 255 { 256 /* Parse the status field, and remember if this is a property 257 that we wish to ignore. (Typically, if it's not a 200, the 258 status will be 404 to indicate that a property we 259 specifically requested from the server doesn't exist.) */ 260 int status = parse_status_code(cdata->data); 261 if (status != 200) 262 svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*"); 263 } 264 else if (leaving_state == PROPVAL) 265 { 266 const char *encoding = svn_hash_gets(attrs, "V:encoding"); 267 const svn_string_t *val_str; 268 apr_hash_t *gathered; 269 const char *path; 270 const char *ns; 271 const char *name; 272 const char *altvalue; 273 274 if (encoding) 275 { 276 if (strcmp(encoding, "base64") != 0) 277 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, 278 NULL, 279 _("Got unrecognized encoding '%s'"), 280 encoding); 281 282 /* Decode into the right pool. */ 283 val_str = svn_base64_decode_string(cdata, ctx->pool); 284 } 285 else 286 { 287 /* Copy into the right pool. */ 288 val_str = svn_string_dup(cdata, ctx->pool); 289 } 290 291 /* The current path sits on the RESPONSE state. Gather up all the 292 state from this PROPVAL to the (grandparent) RESPONSE state, 293 and grab the path from there. 294 295 Now, it would be nice if we could, at this point, know that 296 the status code for this property indicated a problem -- then 297 we could simply bail out here and ignore the property. 298 Sadly, though, we might get the status code *after* we get 299 the property value. So we'll carry on with our processing 300 here, setting the property and value as expected. Once we 301 know for sure the status code associate with the property, 302 we'll decide its fate. */ 303 gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE); 304 305 /* These will be dup'd into CTX->POOL, as necessary. */ 306 path = svn_hash_gets(gathered, "path"); 307 if (path == NULL) 308 path = ctx->path; 309 310 ns = svn_hash_gets(attrs, "ns"); 311 name = apr_pstrdup(ctx->pool, 312 svn_hash_gets(attrs, "name")); 313 314 altvalue = svn_hash_gets(attrs, "altvalue"); 315 if (altvalue != NULL) 316 val_str = svn_string_create(altvalue, ctx->pool); 317 318 svn_ra_serf__set_ver_prop(ctx->ps_props, 319 path, ctx->rev, ns, name, val_str, 320 ctx->pool); 321 } 322 else 323 { 324 apr_hash_t *gathered; 325 326 SVN_ERR_ASSERT(leaving_state == PROPSTAT); 327 328 gathered = svn_ra_serf__xml_gather_since(xes, PROPSTAT); 329 330 /* If we've squirreled away a note that says we want to ignore 331 these properties, we'll do so. Otherwise, we need to copy 332 them from the temporary hash into the ctx->ret_props hash. */ 333 if (! svn_hash_gets(gathered, "ignore-prop")) 334 { 335 SVN_ERR(svn_ra_serf__walk_all_paths(ctx->ps_props, ctx->rev, 336 copy_into_ret_props, ctx, 337 scratch_pool)); 338 } 339 340 ctx->ps_props = NULL; 341 } 342 343 return SVN_NO_ERROR; 344} 345 346 347const svn_string_t * 348svn_ra_serf__get_ver_prop_string(apr_hash_t *props, 349 const char *path, 350 svn_revnum_t rev, 351 const char *ns, 352 const char *name) 353{ 354 apr_hash_t *ver_props, *path_props, *ns_props; 355 void *val = NULL; 356 357 ver_props = apr_hash_get(props, &rev, sizeof(rev)); 358 if (ver_props) 359 { 360 path_props = svn_hash_gets(ver_props, path); 361 362 if (path_props) 363 { 364 ns_props = svn_hash_gets(path_props, ns); 365 if (ns_props) 366 { 367 val = svn_hash_gets(ns_props, name); 368 } 369 } 370 } 371 372 return val; 373} 374 375const char * 376svn_ra_serf__get_ver_prop(apr_hash_t *props, 377 const char *path, 378 svn_revnum_t rev, 379 const char *ns, 380 const char *name) 381{ 382 const svn_string_t *val; 383 384 val = svn_ra_serf__get_ver_prop_string(props, path, rev, ns, name); 385 386 if (val) 387 { 388 return val->data; 389 } 390 391 return NULL; 392} 393 394const svn_string_t * 395svn_ra_serf__get_prop_string(apr_hash_t *props, 396 const char *path, 397 const char *ns, 398 const char *name) 399{ 400 return svn_ra_serf__get_ver_prop_string(props, path, SVN_INVALID_REVNUM, 401 ns, name); 402} 403 404const char * 405svn_ra_serf__get_prop(apr_hash_t *props, 406 const char *path, 407 const char *ns, 408 const char *name) 409{ 410 return svn_ra_serf__get_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name); 411} 412 413void 414svn_ra_serf__set_ver_prop(apr_hash_t *props, 415 const char *path, svn_revnum_t rev, 416 const char *ns, const char *name, 417 const svn_string_t *val, apr_pool_t *pool) 418{ 419 apr_hash_t *ver_props, *path_props, *ns_props; 420 421 ver_props = apr_hash_get(props, &rev, sizeof(rev)); 422 if (!ver_props) 423 { 424 ver_props = apr_hash_make(pool); 425 apr_hash_set(props, apr_pmemdup(pool, &rev, sizeof(rev)), sizeof(rev), 426 ver_props); 427 } 428 429 path_props = svn_hash_gets(ver_props, path); 430 431 if (!path_props) 432 { 433 path_props = apr_hash_make(pool); 434 path = apr_pstrdup(pool, path); 435 svn_hash_sets(ver_props, path, path_props); 436 437 /* todo: we know that we'll fail the next check, but fall through 438 * for now for simplicity's sake. 439 */ 440 } 441 442 ns_props = svn_hash_gets(path_props, ns); 443 if (!ns_props) 444 { 445 ns_props = apr_hash_make(pool); 446 ns = apr_pstrdup(pool, ns); 447 svn_hash_sets(path_props, ns, ns_props); 448 } 449 450 svn_hash_sets(ns_props, name, val); 451} 452 453void 454svn_ra_serf__set_prop(apr_hash_t *props, 455 const char *path, 456 const char *ns, const char *name, 457 const svn_string_t *val, apr_pool_t *pool) 458{ 459 svn_ra_serf__set_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name, 460 val, pool); 461} 462 463 464static svn_error_t * 465setup_propfind_headers(serf_bucket_t *headers, 466 void *setup_baton, 467 apr_pool_t *pool) 468{ 469 propfind_context_t *ctx = setup_baton; 470 471 serf_bucket_headers_setn(headers, "Depth", ctx->depth); 472 if (ctx->label) 473 { 474 serf_bucket_headers_setn(headers, "Label", ctx->label); 475 } 476 477 return SVN_NO_ERROR; 478} 479 480#define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">" 481#define PROPFIND_TRAILER "</propfind>" 482 483static svn_error_t * 484create_propfind_body(serf_bucket_t **bkt, 485 void *setup_baton, 486 serf_bucket_alloc_t *alloc, 487 apr_pool_t *pool) 488{ 489 propfind_context_t *ctx = setup_baton; 490 491 serf_bucket_t *body_bkt, *tmp; 492 const svn_ra_serf__dav_props_t *prop; 493 svn_boolean_t requested_allprop = FALSE; 494 495 body_bkt = serf_bucket_aggregate_create(alloc); 496 497 prop = ctx->find_props; 498 while (prop && prop->namespace) 499 { 500 /* special case the allprop case. */ 501 if (strcmp(prop->name, "allprop") == 0) 502 { 503 requested_allprop = TRUE; 504 } 505 506 /* <*propname* xmlns="*propns*" /> */ 507 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc); 508 serf_bucket_aggregate_append(body_bkt, tmp); 509 510 tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc); 511 serf_bucket_aggregate_append(body_bkt, tmp); 512 513 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"", 514 sizeof(" xmlns=\"")-1, 515 alloc); 516 serf_bucket_aggregate_append(body_bkt, tmp); 517 518 tmp = SERF_BUCKET_SIMPLE_STRING(prop->namespace, alloc); 519 serf_bucket_aggregate_append(body_bkt, tmp); 520 521 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1, 522 alloc); 523 serf_bucket_aggregate_append(body_bkt, tmp); 524 525 prop++; 526 } 527 528 /* If we're not doing an allprop, add <prop> tags. */ 529 if (!requested_allprop) 530 { 531 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>", 532 sizeof("<prop>")-1, 533 alloc); 534 serf_bucket_aggregate_prepend(body_bkt, tmp); 535 } 536 537 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER, 538 sizeof(PROPFIND_HEADER)-1, 539 alloc); 540 541 serf_bucket_aggregate_prepend(body_bkt, tmp); 542 543 if (!requested_allprop) 544 { 545 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>", 546 sizeof("</prop>")-1, 547 alloc); 548 serf_bucket_aggregate_append(body_bkt, tmp); 549 } 550 551 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER, 552 sizeof(PROPFIND_TRAILER)-1, 553 alloc); 554 serf_bucket_aggregate_append(body_bkt, tmp); 555 556 *bkt = body_bkt; 557 return SVN_NO_ERROR; 558} 559 560 561svn_error_t * 562svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler, 563 apr_hash_t *ret_props, 564 svn_ra_serf__session_t *sess, 565 svn_ra_serf__connection_t *conn, 566 const char *path, 567 svn_revnum_t rev, 568 const char *depth, 569 const svn_ra_serf__dav_props_t *find_props, 570 svn_ra_serf__list_t **done_list, 571 apr_pool_t *pool) 572{ 573 propfind_context_t *new_prop_ctx; 574 svn_ra_serf__handler_t *handler; 575 svn_ra_serf__xml_context_t *xmlctx; 576 577 new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx)); 578 579 new_prop_ctx->pool = apr_hash_pool_get(ret_props); 580 new_prop_ctx->path = path; 581 new_prop_ctx->find_props = find_props; 582 new_prop_ctx->ret_props = ret_props; 583 new_prop_ctx->depth = depth; 584 new_prop_ctx->sess = sess; 585 new_prop_ctx->conn = conn; 586 new_prop_ctx->rev = rev; 587 new_prop_ctx->done_list = done_list; 588 589 if (SVN_IS_VALID_REVNUM(rev)) 590 { 591 new_prop_ctx->label = apr_ltoa(pool, rev); 592 } 593 else 594 { 595 new_prop_ctx->label = NULL; 596 } 597 598 xmlctx = svn_ra_serf__xml_context_create(propfind_ttable, 599 propfind_opened, 600 propfind_closed, 601 NULL, 602 new_prop_ctx, 603 pool); 604 handler = svn_ra_serf__create_expat_handler(xmlctx, pool); 605 606 handler->method = "PROPFIND"; 607 handler->path = path; 608 handler->body_delegate = create_propfind_body; 609 handler->body_type = "text/xml"; 610 handler->body_delegate_baton = new_prop_ctx; 611 handler->header_delegate = setup_propfind_headers; 612 handler->header_delegate_baton = new_prop_ctx; 613 614 handler->session = new_prop_ctx->sess; 615 handler->conn = new_prop_ctx->conn; 616 617 new_prop_ctx->handler = handler; 618 619 *propfind_handler = handler; 620 621 return SVN_NO_ERROR; 622} 623 624 625/* 626 * This helper function will block until the PROP_CTX indicates that is done 627 * or another error is returned. 628 */ 629svn_error_t * 630svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler, 631 apr_pool_t *scratch_pool) 632{ 633 svn_error_t *err; 634 svn_error_t *err2; 635 636 err = svn_ra_serf__context_run_one(handler, scratch_pool); 637 638 err2 = svn_ra_serf__error_on_status(handler->sline.code, 639 handler->path, 640 handler->location); 641 642 return svn_error_compose_create(err2, err); 643} 644 645/* 646 * This is a blocking version of deliver_props. 647 */ 648svn_error_t * 649svn_ra_serf__retrieve_props(apr_hash_t **results, 650 svn_ra_serf__session_t *sess, 651 svn_ra_serf__connection_t *conn, 652 const char *url, 653 svn_revnum_t rev, 654 const char *depth, 655 const svn_ra_serf__dav_props_t *props, 656 apr_pool_t *result_pool, 657 apr_pool_t *scratch_pool) 658{ 659 svn_ra_serf__handler_t *handler; 660 661 *results = apr_hash_make(result_pool); 662 663 SVN_ERR(svn_ra_serf__deliver_props(&handler, *results, sess, conn, url, 664 rev, depth, props, NULL, result_pool)); 665 SVN_ERR(svn_ra_serf__wait_for_props(handler, scratch_pool)); 666 667 return SVN_NO_ERROR; 668} 669 670 671svn_error_t * 672svn_ra_serf__fetch_node_props(apr_hash_t **results, 673 svn_ra_serf__connection_t *conn, 674 const char *url, 675 svn_revnum_t revision, 676 const svn_ra_serf__dav_props_t *which_props, 677 apr_pool_t *result_pool, 678 apr_pool_t *scratch_pool) 679{ 680 apr_hash_t *multiprops; 681 apr_hash_t *ver_props; 682 683 /* Note: a couple extra hash tables and whatnot get into RESULT_POOL. 684 Not a big deal at this point. Theoretically, we could fetch all 685 props into SCRATCH_POOL, then copy just the REVISION/URL props 686 into RESULT_POOL. Too much work for too little gain... */ 687 SVN_ERR(svn_ra_serf__retrieve_props(&multiprops, conn->session, conn, 688 url, revision, "0", which_props, 689 result_pool, scratch_pool)); 690 691 ver_props = apr_hash_get(multiprops, &revision, sizeof(revision)); 692 if (ver_props != NULL) 693 { 694 *results = svn_hash_gets(ver_props, url); 695 if (*results != NULL) 696 return SVN_NO_ERROR; 697 } 698 699 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 700 _("The PROPFIND response did not include " 701 "the requested properties")); 702} 703 704 705svn_error_t * 706svn_ra_serf__walk_node_props(apr_hash_t *props, 707 svn_ra_serf__walker_visitor_t walker, 708 void *baton, 709 apr_pool_t *scratch_pool) 710{ 711 apr_pool_t *iterpool; 712 apr_hash_index_t *ns_hi; 713 714 iterpool = svn_pool_create(scratch_pool); 715 for (ns_hi = apr_hash_first(scratch_pool, props); ns_hi; 716 ns_hi = apr_hash_next(ns_hi)) 717 { 718 void *ns_val; 719 const void *ns_name; 720 apr_hash_index_t *name_hi; 721 722 /* NOTE: We do not clear ITERPOOL in this loop. Generally, there are 723 very few namespaces, so this loop will not have many iterations. 724 Instead, ITERPOOL is used for the inner loop. */ 725 726 apr_hash_this(ns_hi, &ns_name, NULL, &ns_val); 727 728 for (name_hi = apr_hash_first(scratch_pool, ns_val); name_hi; 729 name_hi = apr_hash_next(name_hi)) 730 { 731 void *prop_val; 732 const void *prop_name; 733 734 /* See note above, regarding clearing of this pool. */ 735 svn_pool_clear(iterpool); 736 737 apr_hash_this(name_hi, &prop_name, NULL, &prop_val); 738 739 SVN_ERR(walker(baton, ns_name, prop_name, prop_val, iterpool)); 740 } 741 } 742 svn_pool_destroy(iterpool); 743 744 return SVN_NO_ERROR; 745} 746 747 748svn_error_t * 749svn_ra_serf__walk_all_props(apr_hash_t *props, 750 const char *name, 751 svn_revnum_t rev, 752 svn_ra_serf__walker_visitor_t walker, 753 void *baton, 754 apr_pool_t *scratch_pool) 755{ 756 apr_hash_t *ver_props; 757 apr_hash_t *path_props; 758 759 ver_props = apr_hash_get(props, &rev, sizeof(rev)); 760 if (!ver_props) 761 return SVN_NO_ERROR; 762 763 path_props = svn_hash_gets(ver_props, name); 764 if (!path_props) 765 return SVN_NO_ERROR; 766 767 return svn_error_trace(svn_ra_serf__walk_node_props(path_props, 768 walker, baton, 769 scratch_pool)); 770} 771 772 773svn_error_t * 774svn_ra_serf__walk_all_paths(apr_hash_t *props, 775 svn_revnum_t rev, 776 svn_ra_serf__path_rev_walker_t walker, 777 void *baton, 778 apr_pool_t *pool) 779{ 780 apr_hash_index_t *path_hi; 781 apr_hash_t *ver_props; 782 783 ver_props = apr_hash_get(props, &rev, sizeof(rev)); 784 785 if (!ver_props) 786 { 787 return SVN_NO_ERROR; 788 } 789 790 for (path_hi = apr_hash_first(pool, ver_props); path_hi; 791 path_hi = apr_hash_next(path_hi)) 792 { 793 void *path_props; 794 const void *path_name; 795 apr_ssize_t path_len; 796 apr_hash_index_t *ns_hi; 797 798 apr_hash_this(path_hi, &path_name, &path_len, &path_props); 799 for (ns_hi = apr_hash_first(pool, path_props); ns_hi; 800 ns_hi = apr_hash_next(ns_hi)) 801 { 802 void *ns_val; 803 const void *ns_name; 804 apr_ssize_t ns_len; 805 apr_hash_index_t *name_hi; 806 apr_hash_this(ns_hi, &ns_name, &ns_len, &ns_val); 807 for (name_hi = apr_hash_first(pool, ns_val); name_hi; 808 name_hi = apr_hash_next(name_hi)) 809 { 810 void *prop_val; 811 const void *prop_name; 812 apr_ssize_t prop_len; 813 814 apr_hash_this(name_hi, &prop_name, &prop_len, &prop_val); 815 /* use a subpool? */ 816 SVN_ERR(walker(baton, path_name, path_len, ns_name, ns_len, 817 prop_name, prop_len, prop_val, pool)); 818 } 819 } 820 } 821 822 return SVN_NO_ERROR; 823} 824 825 826const char * 827svn_ra_serf__svnname_from_wirename(const char *ns, 828 const char *name, 829 apr_pool_t *result_pool) 830{ 831 if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) 832 return apr_pstrdup(result_pool, name); 833 834 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) 835 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL); 836 837 if (strcmp(ns, SVN_PROP_PREFIX) == 0) 838 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL); 839 840 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) 841 return SVN_PROP_ENTRY_COMMITTED_REV; 842 843 if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) 844 return SVN_PROP_ENTRY_COMMITTED_DATE; 845 846 if (strcmp(name, "creator-displayname") == 0) 847 return SVN_PROP_ENTRY_LAST_AUTHOR; 848 849 if (strcmp(name, "repository-uuid") == 0) 850 return SVN_PROP_ENTRY_UUID; 851 852 if (strcmp(name, "lock-token") == 0) 853 return SVN_PROP_ENTRY_LOCK_TOKEN; 854 855 if (strcmp(name, "checked-in") == 0) 856 return SVN_RA_SERF__WC_CHECKED_IN_URL; 857 858 if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) 859 { 860 /* Here DAV: properties not yet converted to svn: properties should be 861 ignored. */ 862 return NULL; 863 } 864 865 /* An unknown namespace, must be a custom property. */ 866 return apr_pstrcat(result_pool, ns, name, (char *)NULL); 867} 868 869 870/* Conforms to svn_ra_serf__walker_visitor_t */ 871static svn_error_t * 872set_flat_props(void *baton, 873 const char *ns, 874 const char *name, 875 const svn_string_t *value, 876 apr_pool_t *pool) 877{ 878 apr_hash_t *props = baton; 879 apr_pool_t *result_pool = apr_hash_pool_get(props); 880 const char *prop_name; 881 882 /* ### is VAL in the proper pool? */ 883 884 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool); 885 if (prop_name != NULL) 886 svn_hash_sets(props, prop_name, value); 887 888 return SVN_NO_ERROR; 889} 890 891 892svn_error_t * 893svn_ra_serf__flatten_props(apr_hash_t **flat_props, 894 apr_hash_t *props, 895 apr_pool_t *result_pool, 896 apr_pool_t *scratch_pool) 897{ 898 *flat_props = apr_hash_make(result_pool); 899 900 return svn_error_trace(svn_ra_serf__walk_node_props( 901 props, 902 set_flat_props, 903 *flat_props /* baton */, 904 scratch_pool)); 905} 906 907 908static svn_error_t * 909select_revprops(void *baton, 910 const char *ns, 911 const char *name, 912 const svn_string_t *val, 913 apr_pool_t *scratch_pool) 914{ 915 apr_hash_t *revprops = baton; 916 apr_pool_t *result_pool = apr_hash_pool_get(revprops); 917 const char *prop_name; 918 919 /* ### copy NAME into the RESULT_POOL? */ 920 /* ### copy VAL into the RESULT_POOL? */ 921 922 if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) 923 prop_name = name; 924 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) 925 prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL); 926 else if (strcmp(ns, SVN_PROP_PREFIX) == 0) 927 prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL); 928 else if (strcmp(ns, "") == 0) 929 prop_name = name; 930 else 931 { 932 /* do nothing for now? */ 933 return SVN_NO_ERROR; 934 } 935 936 svn_hash_sets(revprops, prop_name, val); 937 938 return SVN_NO_ERROR; 939} 940 941 942svn_error_t * 943svn_ra_serf__select_revprops(apr_hash_t **revprops, 944 const char *name, 945 svn_revnum_t rev, 946 apr_hash_t *all_revprops, 947 apr_pool_t *result_pool, 948 apr_pool_t *scratch_pool) 949{ 950 *revprops = apr_hash_make(result_pool); 951 952 return svn_error_trace(svn_ra_serf__walk_all_props( 953 all_revprops, name, rev, 954 select_revprops, *revprops, 955 scratch_pool)); 956} 957 958 959/* 960 * Contact the server (using CONN) to calculate baseline 961 * information for BASELINE_URL at REVISION (which may be 962 * SVN_INVALID_REVNUM to query the HEAD revision). 963 * 964 * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision 965 * retrieved from the server as part of this process (which should 966 * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the 967 * baseline collection URL. 968 */ 969static svn_error_t * 970retrieve_baseline_info(svn_revnum_t *actual_revision, 971 const char **basecoll_url_p, 972 svn_ra_serf__connection_t *conn, 973 const char *baseline_url, 974 svn_revnum_t revision, 975 apr_pool_t *result_pool, 976 apr_pool_t *scratch_pool) 977{ 978 apr_hash_t *props; 979 apr_hash_t *dav_props; 980 const char *basecoll_url; 981 982 SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn, 983 baseline_url, revision, 984 baseline_props, 985 scratch_pool, scratch_pool)); 986 dav_props = apr_hash_get(props, "DAV:", 4); 987 /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */ 988 989 basecoll_url = svn_prop_get_value(dav_props, "baseline-collection"); 990 if (!basecoll_url) 991 { 992 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 993 _("The PROPFIND response did not include " 994 "the requested baseline-collection value")); 995 } 996 *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool); 997 998 if (actual_revision) 999 { 1000 const char *version_name; 1001 1002 version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME); 1003 if (!version_name) 1004 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 1005 _("The PROPFIND response did not include " 1006 "the requested version-name value")); 1007 1008 *actual_revision = SVN_STR_TO_REV(version_name); 1009 } 1010 1011 return SVN_NO_ERROR; 1012} 1013 1014 1015/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest 1016 revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline 1017 collection URL is also returned. 1018 1019 Do the work over CONN. 1020 1021 *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All 1022 temporary allocations will be made in SCRATCH_POOL. */ 1023static svn_error_t * 1024v1_get_youngest_revnum(svn_revnum_t *youngest, 1025 const char **basecoll_url, 1026 svn_ra_serf__connection_t *conn, 1027 const char *vcc_url, 1028 apr_pool_t *result_pool, 1029 apr_pool_t *scratch_pool) 1030{ 1031 const char *baseline_url; 1032 const char *bc_url; 1033 1034 /* Fetching DAV:checked-in from the VCC (with no Label: to specify a 1035 revision) will return the latest Baseline resource's URL. */ 1036 SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, conn, vcc_url, 1037 SVN_INVALID_REVNUM, 1038 "checked-in", 1039 scratch_pool, scratch_pool)); 1040 if (!baseline_url) 1041 { 1042 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 1043 _("The OPTIONS response did not include " 1044 "the requested checked-in value")); 1045 } 1046 baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool); 1047 1048 /* From the Baseline resource, we can fetch the DAV:baseline-collection 1049 and DAV:version-name properties. The latter is the revision number, 1050 which is formally the name used in Label: headers. */ 1051 1052 /* First check baseline information cache. */ 1053 SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url, 1054 youngest, 1055 conn->session->blncache, 1056 baseline_url, 1057 scratch_pool)); 1058 if (!bc_url) 1059 { 1060 SVN_ERR(retrieve_baseline_info(youngest, &bc_url, conn, 1061 baseline_url, SVN_INVALID_REVNUM, 1062 scratch_pool, scratch_pool)); 1063 SVN_ERR(svn_ra_serf__blncache_set(conn->session->blncache, 1064 baseline_url, *youngest, 1065 bc_url, scratch_pool)); 1066 } 1067 1068 if (basecoll_url != NULL) 1069 *basecoll_url = apr_pstrdup(result_pool, bc_url); 1070 1071 return SVN_NO_ERROR; 1072} 1073 1074 1075svn_error_t * 1076svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest, 1077 svn_ra_serf__session_t *session, 1078 apr_pool_t *scratch_pool) 1079{ 1080 const char *vcc_url; 1081 1082 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 1083 return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum( 1084 youngest, session->conns[0], scratch_pool)); 1085 1086 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, scratch_pool)); 1087 1088 return svn_error_trace(v1_get_youngest_revnum(youngest, NULL, 1089 session->conns[0], vcc_url, 1090 scratch_pool, scratch_pool)); 1091} 1092 1093 1094/* Set *BC_URL to the baseline collection url for REVISION. If REVISION 1095 is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used. 1096 1097 *REVNUM_USED will be set to the revision used. 1098 1099 Uses the specified CONN, which is part of SESSION. 1100 1101 All allocations (results and temporary) are performed in POOL. */ 1102static svn_error_t * 1103get_baseline_info(const char **bc_url, 1104 svn_revnum_t *revnum_used, 1105 svn_ra_serf__session_t *session, 1106 svn_ra_serf__connection_t *conn, 1107 svn_revnum_t revision, 1108 apr_pool_t *pool) 1109{ 1110 /* If we detected HTTP v2 support on the server, we can construct 1111 the baseline collection URL ourselves, and fetch the latest 1112 revision (if needed) with an OPTIONS request. */ 1113 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 1114 { 1115 if (SVN_IS_VALID_REVNUM(revision)) 1116 { 1117 *revnum_used = revision; 1118 } 1119 else 1120 { 1121 SVN_ERR(svn_ra_serf__v2_get_youngest_revnum( 1122 revnum_used, conn, pool)); 1123 if (! SVN_IS_VALID_REVNUM(*revnum_used)) 1124 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 1125 _("The OPTIONS response did not include " 1126 "the youngest revision")); 1127 } 1128 1129 *bc_url = apr_psprintf(pool, "%s/%ld", 1130 session->rev_root_stub, *revnum_used); 1131 } 1132 1133 /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */ 1134 else 1135 { 1136 const char *vcc_url; 1137 1138 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, conn, pool)); 1139 1140 if (SVN_IS_VALID_REVNUM(revision)) 1141 { 1142 /* First check baseline information cache. */ 1143 SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url, 1144 session->blncache, 1145 revision, pool)); 1146 if (!*bc_url) 1147 { 1148 SVN_ERR(retrieve_baseline_info(NULL, bc_url, conn, 1149 vcc_url, revision, pool, pool)); 1150 SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL, 1151 revision, *bc_url, pool)); 1152 } 1153 1154 *revnum_used = revision; 1155 } 1156 else 1157 { 1158 SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url, 1159 conn, vcc_url, 1160 pool, pool)); 1161 } 1162 } 1163 1164 return SVN_NO_ERROR; 1165} 1166 1167 1168svn_error_t * 1169svn_ra_serf__get_stable_url(const char **stable_url, 1170 svn_revnum_t *latest_revnum, 1171 svn_ra_serf__session_t *session, 1172 svn_ra_serf__connection_t *conn, 1173 const char *url, 1174 svn_revnum_t revision, 1175 apr_pool_t *result_pool, 1176 apr_pool_t *scratch_pool) 1177{ 1178 const char *basecoll_url; 1179 const char *repos_relpath; 1180 svn_revnum_t revnum_used; 1181 1182 /* No URL? No sweat. We'll use the session URL. */ 1183 if (! url) 1184 url = session->session_url.path; 1185 1186 /* If the caller didn't provide a specific connection for us to use, 1187 we'll use the default connection. */ 1188 if (! conn) 1189 conn = session->conns[0]; 1190 1191 SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used, 1192 session, conn, revision, scratch_pool)); 1193 SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url, 1194 session, conn, scratch_pool)); 1195 1196 *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath, 1197 result_pool); 1198 if (latest_revnum) 1199 *latest_revnum = revnum_used; 1200 1201 return SVN_NO_ERROR; 1202} 1203 1204 1205svn_error_t * 1206svn_ra_serf__get_resource_type(svn_node_kind_t *kind, 1207 apr_hash_t *props) 1208{ 1209 apr_hash_t *dav_props; 1210 const char *res_type; 1211 1212 dav_props = apr_hash_get(props, "DAV:", 4); 1213 res_type = svn_prop_get_value(dav_props, "resourcetype"); 1214 if (!res_type) 1215 { 1216 /* How did this happen? */ 1217 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 1218 _("The PROPFIND response did not include the " 1219 "requested resourcetype value")); 1220 } 1221 1222 if (strcmp(res_type, "collection") == 0) 1223 { 1224 *kind = svn_node_dir; 1225 } 1226 else 1227 { 1228 *kind = svn_node_file; 1229 } 1230 1231 return SVN_NO_ERROR; 1232} 1233 1234 1235svn_error_t * 1236svn_ra_serf__fetch_dav_prop(const char **value, 1237 svn_ra_serf__connection_t *conn, 1238 const char *url, 1239 svn_revnum_t revision, 1240 const char *propname, 1241 apr_pool_t *result_pool, 1242 apr_pool_t *scratch_pool) 1243{ 1244 apr_hash_t *props; 1245 apr_hash_t *dav_props; 1246 1247 SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn, url, revision, 1248 checked_in_props, 1249 scratch_pool, scratch_pool)); 1250 dav_props = apr_hash_get(props, "DAV:", 4); 1251 if (dav_props == NULL) 1252 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 1253 _("The PROPFIND response did not include " 1254 "the requested 'DAV:' properties")); 1255 1256 /* We wouldn't get here if the resource was not found (404), so the 1257 property should be present. 1258 1259 Note: it is okay to call apr_pstrdup() with NULL. */ 1260 *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname)); 1261 1262 return SVN_NO_ERROR; 1263} 1264