property.c revision 299742
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 = XML_STATE_INITIAL, 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 svn_ra_serf__handler_t *handler; 63 64 /* the requested path */ 65 const char *path; 66 67 /* the requested version (in string form) */ 68 const char *label; 69 70 /* the request depth */ 71 const char *depth; 72 73 /* the list of requested properties */ 74 const svn_ra_serf__dav_props_t *find_props; 75 76 svn_ra_serf__prop_func_t prop_func; 77 void *prop_func_baton; 78 79 /* hash table containing all the properties associated with the 80 * "current" <propstat> tag. These will get copied into RET_PROPS 81 * if the status code similarly associated indicates that they are 82 * "good"; otherwise, they'll get discarded. 83 */ 84 apr_hash_t *ps_props; 85} propfind_context_t; 86 87 88#define D_ "DAV:" 89#define S_ SVN_XML_NAMESPACE 90static const svn_ra_serf__xml_transition_t propfind_ttable[] = { 91 { INITIAL, D_, "multistatus", MULTISTATUS, 92 FALSE, { NULL }, TRUE }, 93 94 { MULTISTATUS, D_, "response", RESPONSE, 95 FALSE, { NULL }, FALSE }, 96 97 { RESPONSE, D_, "href", HREF, 98 TRUE, { NULL }, TRUE }, 99 100 { RESPONSE, D_, "propstat", PROPSTAT, 101 FALSE, { NULL }, TRUE }, 102 103 { PROPSTAT, D_, "status", STATUS, 104 TRUE, { NULL }, TRUE }, 105 106 { PROPSTAT, D_, "prop", PROP, 107 FALSE, { NULL }, FALSE }, 108 109 { PROP, "*", "*", PROPVAL, 110 TRUE, { "?V:encoding", NULL }, TRUE }, 111 112 { PROPVAL, D_, "collection", COLLECTION, 113 FALSE, { NULL }, TRUE }, 114 115 { PROPVAL, D_, "href", HREF_VALUE, 116 TRUE, { NULL }, TRUE }, 117 118 { 0 } 119}; 120 121static const int propfind_expected_status[] = { 122 207, 123 0 124}; 125 126/* Return the HTTP status code contained in STATUS_LINE, or 0 if 127 there's a problem parsing it. */ 128static apr_int64_t parse_status_code(const char *status_line) 129{ 130 /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */ 131 if (status_line[0] == 'H' && 132 status_line[1] == 'T' && 133 status_line[2] == 'T' && 134 status_line[3] == 'P' && 135 status_line[4] == '/' && 136 (status_line[5] >= '0' && status_line[5] <= '9') && 137 status_line[6] == '.' && 138 (status_line[7] >= '0' && status_line[7] <= '9') && 139 status_line[8] == ' ') 140 { 141 char *reason; 142 143 return apr_strtoi64(status_line + 8, &reason, 10); 144 } 145 return 0; 146} 147 148/* Conforms to svn_ra_serf__xml_opened_t */ 149static svn_error_t * 150propfind_opened(svn_ra_serf__xml_estate_t *xes, 151 void *baton, 152 int entered_state, 153 const svn_ra_serf__dav_props_t *tag, 154 apr_pool_t *scratch_pool) 155{ 156 propfind_context_t *ctx = baton; 157 158 if (entered_state == PROPVAL) 159 { 160 svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->xmlns); 161 svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name); 162 } 163 else if (entered_state == PROPSTAT) 164 { 165 ctx->ps_props = apr_hash_make(svn_ra_serf__xml_state_pool(xes)); 166 } 167 168 return SVN_NO_ERROR; 169} 170 171/* Set PROPS for NS:NAME VAL. Helper for propfind_closed */ 172static void 173set_ns_prop(apr_hash_t *ns_props, 174 const char *ns, const char *name, 175 const svn_string_t *val, apr_pool_t *result_pool) 176{ 177 apr_hash_t *props = svn_hash_gets(ns_props, ns); 178 179 if (!props) 180 { 181 props = apr_hash_make(result_pool); 182 ns = apr_pstrdup(result_pool, ns); 183 svn_hash_sets(ns_props, ns, props); 184 } 185 186 if (val) 187 { 188 name = apr_pstrdup(result_pool, name); 189 val = svn_string_dup(val, result_pool); 190 } 191 192 svn_hash_sets(props, name, val); 193} 194 195/* Conforms to svn_ra_serf__xml_closed_t */ 196static svn_error_t * 197propfind_closed(svn_ra_serf__xml_estate_t *xes, 198 void *baton, 199 int leaving_state, 200 const svn_string_t *cdata, 201 apr_hash_t *attrs, 202 apr_pool_t *scratch_pool) 203{ 204 propfind_context_t *ctx = baton; 205 206 if (leaving_state == MULTISTATUS) 207 { 208 /* We've gathered all the data from the reponse. Add this item 209 onto the "done list". External callers will then know this 210 request has been completed (tho stray response bytes may still 211 arrive). */ 212 } 213 else if (leaving_state == HREF) 214 { 215 const char *path; 216 217 if (strcmp(ctx->depth, "1") == 0) 218 path = svn_urlpath__canonicalize(cdata->data, scratch_pool); 219 else 220 path = ctx->path; 221 222 svn_ra_serf__xml_note(xes, RESPONSE, "path", path); 223 224 SVN_ERR(ctx->prop_func(ctx->prop_func_baton, 225 path, 226 D_, "href", 227 cdata, scratch_pool)); 228 } 229 else if (leaving_state == COLLECTION) 230 { 231 svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection"); 232 } 233 else if (leaving_state == HREF_VALUE) 234 { 235 svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data); 236 } 237 else if (leaving_state == STATUS) 238 { 239 /* Parse the status field, and remember if this is a property 240 that we wish to ignore. (Typically, if it's not a 200, the 241 status will be 404 to indicate that a property we 242 specifically requested from the server doesn't exist.) */ 243 apr_int64_t status = parse_status_code(cdata->data); 244 if (status != 200) 245 svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*"); 246 } 247 else if (leaving_state == PROPVAL) 248 { 249 const char *encoding; 250 const svn_string_t *val_str; 251 const char *ns; 252 const char *name; 253 const char *altvalue; 254 255 if ((altvalue = svn_hash_gets(attrs, "altvalue")) != NULL) 256 { 257 val_str = svn_string_create(altvalue, scratch_pool); 258 } 259 else if ((encoding = svn_hash_gets(attrs, "V:encoding")) != NULL) 260 { 261 if (strcmp(encoding, "base64") != 0) 262 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, 263 NULL, 264 _("Got unrecognized encoding '%s'"), 265 encoding); 266 267 /* Decode into the right pool. */ 268 val_str = svn_base64_decode_string(cdata, scratch_pool); 269 } 270 else 271 { 272 /* Copy into the right pool. */ 273 val_str = cdata; 274 } 275 276 /* The current path sits on the RESPONSE state. 277 278 Now, it would be nice if we could, at this point, know that 279 the status code for this property indicated a problem -- then 280 we could simply bail out here and ignore the property. 281 Sadly, though, we might get the status code *after* we get 282 the property value. So we'll carry on with our processing 283 here, setting the property and value as expected. Once we 284 know for sure the status code associate with the property, 285 we'll decide its fate. */ 286 287 ns = svn_hash_gets(attrs, "ns"); 288 name = svn_hash_gets(attrs, "name"); 289 290 set_ns_prop(ctx->ps_props, ns, name, val_str, 291 apr_hash_pool_get(ctx->ps_props)); 292 } 293 else 294 { 295 apr_hash_t *gathered; 296 297 SVN_ERR_ASSERT(leaving_state == PROPSTAT); 298 299 gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE); 300 301 /* If we've squirreled away a note that says we want to ignore 302 these properties, we'll do so. Otherwise, we need to copy 303 them from the temporary hash into the ctx->ret_props hash. */ 304 if (! svn_hash_gets(gathered, "ignore-prop")) 305 { 306 apr_hash_index_t *hi_ns; 307 const char *path; 308 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 309 310 311 path = svn_hash_gets(gathered, "path"); 312 if (!path) 313 path = ctx->path; 314 315 for (hi_ns = apr_hash_first(scratch_pool, ctx->ps_props); 316 hi_ns; 317 hi_ns = apr_hash_next(hi_ns)) 318 { 319 const char *ns = apr_hash_this_key(hi_ns); 320 apr_hash_t *props = apr_hash_this_val(hi_ns); 321 apr_hash_index_t *hi_prop; 322 323 svn_pool_clear(iterpool); 324 325 for (hi_prop = apr_hash_first(iterpool, props); 326 hi_prop; 327 hi_prop = apr_hash_next(hi_prop)) 328 { 329 const char *name = apr_hash_this_key(hi_prop); 330 const svn_string_t *value = apr_hash_this_val(hi_prop); 331 332 SVN_ERR(ctx->prop_func(ctx->prop_func_baton, path, 333 ns, name, value, iterpool)); 334 } 335 } 336 337 svn_pool_destroy(iterpool); 338 } 339 340 ctx->ps_props = NULL; /* Allocated in PROPSTAT state pool */ 341 } 342 343 return SVN_NO_ERROR; 344} 345 346 347 348static svn_error_t * 349setup_propfind_headers(serf_bucket_t *headers, 350 void *setup_baton, 351 apr_pool_t *pool /* request pool */, 352 apr_pool_t *scratch_pool) 353{ 354 propfind_context_t *ctx = setup_baton; 355 356 serf_bucket_headers_setn(headers, "Depth", ctx->depth); 357 if (ctx->label) 358 { 359 serf_bucket_headers_setn(headers, "Label", ctx->label); 360 } 361 362 return SVN_NO_ERROR; 363} 364 365#define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">" 366#define PROPFIND_TRAILER "</propfind>" 367 368/* Implements svn_ra_serf__request_body_delegate_t */ 369static svn_error_t * 370create_propfind_body(serf_bucket_t **bkt, 371 void *setup_baton, 372 serf_bucket_alloc_t *alloc, 373 apr_pool_t *pool /* request pool */, 374 apr_pool_t *scratch_pool) 375{ 376 propfind_context_t *ctx = setup_baton; 377 378 serf_bucket_t *body_bkt, *tmp; 379 const svn_ra_serf__dav_props_t *prop; 380 svn_boolean_t requested_allprop = FALSE; 381 382 body_bkt = serf_bucket_aggregate_create(alloc); 383 384 prop = ctx->find_props; 385 while (prop && prop->xmlns) 386 { 387 /* special case the allprop case. */ 388 if (strcmp(prop->name, "allprop") == 0) 389 { 390 requested_allprop = TRUE; 391 } 392 393 /* <*propname* xmlns="*propns*" /> */ 394 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc); 395 serf_bucket_aggregate_append(body_bkt, tmp); 396 397 tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc); 398 serf_bucket_aggregate_append(body_bkt, tmp); 399 400 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"", 401 sizeof(" xmlns=\"")-1, 402 alloc); 403 serf_bucket_aggregate_append(body_bkt, tmp); 404 405 tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc); 406 serf_bucket_aggregate_append(body_bkt, tmp); 407 408 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1, 409 alloc); 410 serf_bucket_aggregate_append(body_bkt, tmp); 411 412 prop++; 413 } 414 415 /* If we're not doing an allprop, add <prop> tags. */ 416 if (!requested_allprop) 417 { 418 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>", 419 sizeof("<prop>")-1, 420 alloc); 421 serf_bucket_aggregate_prepend(body_bkt, tmp); 422 } 423 424 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER, 425 sizeof(PROPFIND_HEADER)-1, 426 alloc); 427 428 serf_bucket_aggregate_prepend(body_bkt, tmp); 429 430 if (!requested_allprop) 431 { 432 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>", 433 sizeof("</prop>")-1, 434 alloc); 435 serf_bucket_aggregate_append(body_bkt, tmp); 436 } 437 438 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER, 439 sizeof(PROPFIND_TRAILER)-1, 440 alloc); 441 serf_bucket_aggregate_append(body_bkt, tmp); 442 443 *bkt = body_bkt; 444 return SVN_NO_ERROR; 445} 446 447 448svn_error_t * 449svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler, 450 svn_ra_serf__session_t *sess, 451 const char *path, 452 svn_revnum_t rev, 453 const char *depth, 454 const svn_ra_serf__dav_props_t *find_props, 455 svn_ra_serf__prop_func_t prop_func, 456 void *prop_func_baton, 457 apr_pool_t *pool) 458{ 459 propfind_context_t *new_prop_ctx; 460 svn_ra_serf__handler_t *handler; 461 svn_ra_serf__xml_context_t *xmlctx; 462 463 new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx)); 464 465 new_prop_ctx->path = path; 466 new_prop_ctx->find_props = find_props; 467 new_prop_ctx->prop_func = prop_func; 468 new_prop_ctx->prop_func_baton = prop_func_baton; 469 new_prop_ctx->depth = depth; 470 471 if (SVN_IS_VALID_REVNUM(rev)) 472 { 473 new_prop_ctx->label = apr_ltoa(pool, rev); 474 } 475 else 476 { 477 new_prop_ctx->label = NULL; 478 } 479 480 xmlctx = svn_ra_serf__xml_context_create(propfind_ttable, 481 propfind_opened, 482 propfind_closed, 483 NULL, 484 new_prop_ctx, 485 pool); 486 handler = svn_ra_serf__create_expat_handler(sess, xmlctx, 487 propfind_expected_status, 488 pool); 489 490 handler->method = "PROPFIND"; 491 handler->path = path; 492 handler->body_delegate = create_propfind_body; 493 handler->body_type = "text/xml"; 494 handler->body_delegate_baton = new_prop_ctx; 495 handler->header_delegate = setup_propfind_headers; 496 handler->header_delegate_baton = new_prop_ctx; 497 498 handler->no_dav_headers = TRUE; 499 500 new_prop_ctx->handler = handler; 501 502 *propfind_handler = handler; 503 504 return SVN_NO_ERROR; 505} 506 507svn_error_t * 508svn_ra_serf__deliver_svn_props(void *baton, 509 const char *path, 510 const char *ns, 511 const char *name, 512 const svn_string_t *value, 513 apr_pool_t *scratch_pool) 514{ 515 apr_hash_t *props = baton; 516 apr_pool_t *result_pool = apr_hash_pool_get(props); 517 const char *prop_name; 518 519 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool); 520 if (prop_name == NULL) 521 return SVN_NO_ERROR; 522 523 svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool)); 524 525 return SVN_NO_ERROR; 526} 527 528/* 529 * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties 530 * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash 531 * (const char * -> svn_string_t *) to the values. 532 */ 533static svn_error_t * 534deliver_node_props(void *baton, 535 const char *path, 536 const char *ns, 537 const char *name, 538 const svn_string_t *value, 539 apr_pool_t *scratch_pool) 540{ 541 apr_hash_t *nss = baton; 542 apr_hash_t *props; 543 apr_pool_t *result_pool = apr_hash_pool_get(nss); 544 545 props = svn_hash_gets(nss, ns); 546 547 if (!props) 548 { 549 props = apr_hash_make(result_pool); 550 551 ns = apr_pstrdup(result_pool, ns); 552 svn_hash_sets(nss, ns, props); 553 } 554 555 name = apr_pstrdup(result_pool, name); 556 svn_hash_sets(props, name, svn_string_dup(value, result_pool)); 557 558 return SVN_NO_ERROR; 559} 560 561svn_error_t * 562svn_ra_serf__fetch_node_props(apr_hash_t **results, 563 svn_ra_serf__session_t *session, 564 const char *url, 565 svn_revnum_t revision, 566 const svn_ra_serf__dav_props_t *which_props, 567 apr_pool_t *result_pool, 568 apr_pool_t *scratch_pool) 569{ 570 apr_hash_t *props; 571 svn_ra_serf__handler_t *handler; 572 573 props = apr_hash_make(result_pool); 574 575 SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, 576 url, revision, "0", which_props, 577 deliver_node_props, 578 props, scratch_pool)); 579 580 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 581 582 *results = props; 583 return SVN_NO_ERROR; 584} 585 586const char * 587svn_ra_serf__svnname_from_wirename(const char *ns, 588 const char *name, 589 apr_pool_t *result_pool) 590{ 591 if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) 592 return apr_pstrdup(result_pool, name); 593 594 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) 595 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL); 596 597 if (strcmp(ns, SVN_PROP_PREFIX) == 0) 598 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL); 599 600 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) 601 return SVN_PROP_ENTRY_COMMITTED_REV; 602 603 if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) 604 return SVN_PROP_ENTRY_COMMITTED_DATE; 605 606 if (strcmp(name, "creator-displayname") == 0) 607 return SVN_PROP_ENTRY_LAST_AUTHOR; 608 609 if (strcmp(name, "repository-uuid") == 0) 610 return SVN_PROP_ENTRY_UUID; 611 612 if (strcmp(name, "lock-token") == 0) 613 return SVN_PROP_ENTRY_LOCK_TOKEN; 614 615 if (strcmp(name, "checked-in") == 0) 616 return SVN_RA_SERF__WC_CHECKED_IN_URL; 617 618 if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) 619 { 620 /* Here DAV: properties not yet converted to svn: properties should be 621 ignored. */ 622 return NULL; 623 } 624 625 /* An unknown namespace, must be a custom property. */ 626 return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL); 627} 628 629/* 630 * Contact the server (using CONN) to calculate baseline 631 * information for BASELINE_URL at REVISION (which may be 632 * SVN_INVALID_REVNUM to query the HEAD revision). 633 * 634 * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision 635 * retrieved from the server as part of this process (which should 636 * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the 637 * baseline collection URL. 638 */ 639static svn_error_t * 640retrieve_baseline_info(svn_revnum_t *actual_revision, 641 const char **basecoll_url_p, 642 svn_ra_serf__session_t *session, 643 const char *baseline_url, 644 svn_revnum_t revision, 645 apr_pool_t *result_pool, 646 apr_pool_t *scratch_pool) 647{ 648 apr_hash_t *props; 649 apr_hash_t *dav_props; 650 const char *basecoll_url; 651 652 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, 653 baseline_url, revision, 654 baseline_props, 655 scratch_pool, scratch_pool)); 656 dav_props = apr_hash_get(props, "DAV:", 4); 657 /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */ 658 659 basecoll_url = svn_prop_get_value(dav_props, "baseline-collection"); 660 if (!basecoll_url) 661 { 662 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 663 _("The PROPFIND response did not include " 664 "the requested baseline-collection value")); 665 } 666 *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool); 667 668 if (actual_revision) 669 { 670 const char *version_name; 671 672 version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME); 673 if (version_name) 674 { 675 apr_int64_t rev; 676 677 SVN_ERR(svn_cstring_atoi64(&rev, version_name)); 678 *actual_revision = (svn_revnum_t)rev; 679 } 680 681 if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision)) 682 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 683 _("The PROPFIND response did not include " 684 "the requested version-name value")); 685 } 686 687 return SVN_NO_ERROR; 688} 689 690 691/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest 692 revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline 693 collection URL is also returned. 694 695 Do the work over CONN. 696 697 *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All 698 temporary allocations will be made in SCRATCH_POOL. */ 699static svn_error_t * 700v1_get_youngest_revnum(svn_revnum_t *youngest, 701 const char **basecoll_url, 702 svn_ra_serf__session_t *session, 703 const char *vcc_url, 704 apr_pool_t *result_pool, 705 apr_pool_t *scratch_pool) 706{ 707 const char *baseline_url; 708 const char *bc_url; 709 710 /* Fetching DAV:checked-in from the VCC (with no Label: to specify a 711 revision) will return the latest Baseline resource's URL. */ 712 SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url, 713 SVN_INVALID_REVNUM, 714 "checked-in", 715 scratch_pool, scratch_pool)); 716 if (!baseline_url) 717 { 718 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 719 _("The OPTIONS response did not include " 720 "the requested checked-in value")); 721 } 722 baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool); 723 724 /* From the Baseline resource, we can fetch the DAV:baseline-collection 725 and DAV:version-name properties. The latter is the revision number, 726 which is formally the name used in Label: headers. */ 727 728 /* First check baseline information cache. */ 729 SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url, 730 youngest, 731 session->blncache, 732 baseline_url, 733 scratch_pool)); 734 if (!bc_url) 735 { 736 SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session, 737 baseline_url, SVN_INVALID_REVNUM, 738 scratch_pool, scratch_pool)); 739 SVN_ERR(svn_ra_serf__blncache_set(session->blncache, 740 baseline_url, *youngest, 741 bc_url, scratch_pool)); 742 } 743 744 if (basecoll_url != NULL) 745 *basecoll_url = apr_pstrdup(result_pool, bc_url); 746 747 return SVN_NO_ERROR; 748} 749 750 751svn_error_t * 752svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest, 753 svn_ra_serf__session_t *session, 754 apr_pool_t *scratch_pool) 755{ 756 const char *vcc_url; 757 758 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 759 return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum( 760 youngest, session, scratch_pool)); 761 762 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool)); 763 764 return svn_error_trace(v1_get_youngest_revnum(youngest, NULL, 765 session, vcc_url, 766 scratch_pool, scratch_pool)); 767} 768 769 770/* Set *BC_URL to the baseline collection url for REVISION. If REVISION 771 is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used. 772 773 *REVNUM_USED will be set to the revision used. 774 775 Uses the specified CONN, which is part of SESSION. 776 777 All allocations (results and temporary) are performed in POOL. */ 778static svn_error_t * 779get_baseline_info(const char **bc_url, 780 svn_revnum_t *revnum_used, 781 svn_ra_serf__session_t *session, 782 svn_revnum_t revision, 783 apr_pool_t *result_pool, 784 apr_pool_t *scratch_pool) 785{ 786 /* If we detected HTTP v2 support on the server, we can construct 787 the baseline collection URL ourselves, and fetch the latest 788 revision (if needed) with an OPTIONS request. */ 789 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 790 { 791 if (SVN_IS_VALID_REVNUM(revision)) 792 { 793 *revnum_used = revision; 794 } 795 else 796 { 797 SVN_ERR(svn_ra_serf__v2_get_youngest_revnum( 798 revnum_used, session, scratch_pool)); 799 } 800 801 *bc_url = apr_psprintf(result_pool, "%s/%ld", 802 session->rev_root_stub, *revnum_used); 803 } 804 805 /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */ 806 else 807 { 808 const char *vcc_url; 809 810 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool)); 811 812 if (SVN_IS_VALID_REVNUM(revision)) 813 { 814 /* First check baseline information cache. */ 815 SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url, 816 session->blncache, 817 revision, result_pool)); 818 if (!*bc_url) 819 { 820 SVN_ERR(retrieve_baseline_info(NULL, bc_url, session, 821 vcc_url, revision, 822 result_pool, scratch_pool)); 823 SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL, 824 revision, *bc_url, 825 scratch_pool)); 826 } 827 828 *revnum_used = revision; 829 } 830 else 831 { 832 SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url, 833 session, vcc_url, 834 result_pool, scratch_pool)); 835 } 836 } 837 838 return SVN_NO_ERROR; 839} 840 841 842svn_error_t * 843svn_ra_serf__get_stable_url(const char **stable_url, 844 svn_revnum_t *latest_revnum, 845 svn_ra_serf__session_t *session, 846 const char *url, 847 svn_revnum_t revision, 848 apr_pool_t *result_pool, 849 apr_pool_t *scratch_pool) 850{ 851 const char *basecoll_url; 852 const char *repos_relpath; 853 svn_revnum_t revnum_used; 854 855 /* No URL? No sweat. We'll use the session URL. */ 856 if (! url) 857 url = session->session_url.path; 858 859 SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used, 860 session, revision, scratch_pool, scratch_pool)); 861 SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url, 862 session, scratch_pool)); 863 864 *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath, 865 result_pool); 866 if (latest_revnum) 867 *latest_revnum = revnum_used; 868 869 return SVN_NO_ERROR; 870} 871 872 873svn_error_t * 874svn_ra_serf__fetch_dav_prop(const char **value, 875 svn_ra_serf__session_t *session, 876 const char *url, 877 svn_revnum_t revision, 878 const char *propname, 879 apr_pool_t *result_pool, 880 apr_pool_t *scratch_pool) 881{ 882 apr_hash_t *props; 883 apr_hash_t *dav_props; 884 885 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision, 886 checked_in_props, 887 scratch_pool, scratch_pool)); 888 dav_props = apr_hash_get(props, "DAV:", 4); 889 if (dav_props == NULL) 890 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 891 _("The PROPFIND response did not include " 892 "the requested 'DAV:' properties")); 893 894 /* We wouldn't get here if the resource was not found (404), so the 895 property should be present. 896 897 Note: it is okay to call apr_pstrdup() with NULL. */ 898 *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname)); 899 900 return SVN_NO_ERROR; 901} 902 903/* Removes all non regular properties from PROPS */ 904void 905svn_ra_serf__keep_only_regular_props(apr_hash_t *props, 906 apr_pool_t *scratch_pool) 907{ 908 apr_hash_index_t *hi; 909 910 for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi)) 911 { 912 const char *propname = apr_hash_this_key(hi); 913 914 if (svn_property_kind2(propname) != svn_prop_regular_kind) 915 svn_hash_sets(props, propname, NULL); 916 } 917} 918