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 prop++; 394 } 395 396 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER, 397 sizeof(PROPFIND_HEADER)-1, 398 alloc); 399 serf_bucket_aggregate_append(body_bkt, tmp); 400 401 /* If we're not doing an allprop, add <prop> tags. */ 402 if (!requested_allprop) 403 { 404 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>", 405 sizeof("<prop>")-1, 406 alloc); 407 serf_bucket_aggregate_append(body_bkt, tmp); 408 } 409 410 prop = ctx->find_props; 411 while (prop && prop->xmlns) 412 { 413 /* <*propname* xmlns="*propns*" /> */ 414 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc); 415 serf_bucket_aggregate_append(body_bkt, tmp); 416 417 tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc); 418 serf_bucket_aggregate_append(body_bkt, tmp); 419 420 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"", 421 sizeof(" xmlns=\"")-1, 422 alloc); 423 serf_bucket_aggregate_append(body_bkt, tmp); 424 425 tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc); 426 serf_bucket_aggregate_append(body_bkt, tmp); 427 428 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1, 429 alloc); 430 serf_bucket_aggregate_append(body_bkt, tmp); 431 432 prop++; 433 } 434 435 if (!requested_allprop) 436 { 437 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>", 438 sizeof("</prop>")-1, 439 alloc); 440 serf_bucket_aggregate_append(body_bkt, tmp); 441 } 442 443 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER, 444 sizeof(PROPFIND_TRAILER)-1, 445 alloc); 446 serf_bucket_aggregate_append(body_bkt, tmp); 447 448 *bkt = body_bkt; 449 return SVN_NO_ERROR; 450} 451 452 453svn_error_t * 454svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler, 455 svn_ra_serf__session_t *sess, 456 const char *path, 457 svn_revnum_t rev, 458 const char *depth, 459 const svn_ra_serf__dav_props_t *find_props, 460 svn_ra_serf__prop_func_t prop_func, 461 void *prop_func_baton, 462 apr_pool_t *pool) 463{ 464 propfind_context_t *new_prop_ctx; 465 svn_ra_serf__handler_t *handler; 466 svn_ra_serf__xml_context_t *xmlctx; 467 468 new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx)); 469 470 new_prop_ctx->path = path; 471 new_prop_ctx->find_props = find_props; 472 new_prop_ctx->prop_func = prop_func; 473 new_prop_ctx->prop_func_baton = prop_func_baton; 474 new_prop_ctx->depth = depth; 475 476 if (SVN_IS_VALID_REVNUM(rev)) 477 { 478 new_prop_ctx->label = apr_ltoa(pool, rev); 479 } 480 else 481 { 482 new_prop_ctx->label = NULL; 483 } 484 485 xmlctx = svn_ra_serf__xml_context_create(propfind_ttable, 486 propfind_opened, 487 propfind_closed, 488 NULL, 489 new_prop_ctx, 490 pool); 491 handler = svn_ra_serf__create_expat_handler(sess, xmlctx, 492 propfind_expected_status, 493 pool); 494 495 handler->method = "PROPFIND"; 496 handler->path = path; 497 handler->body_delegate = create_propfind_body; 498 handler->body_type = "text/xml"; 499 handler->body_delegate_baton = new_prop_ctx; 500 handler->header_delegate = setup_propfind_headers; 501 handler->header_delegate_baton = new_prop_ctx; 502 503 handler->no_dav_headers = TRUE; 504 505 new_prop_ctx->handler = handler; 506 507 *propfind_handler = handler; 508 509 return SVN_NO_ERROR; 510} 511 512svn_error_t * 513svn_ra_serf__deliver_svn_props(void *baton, 514 const char *path, 515 const char *ns, 516 const char *name, 517 const svn_string_t *value, 518 apr_pool_t *scratch_pool) 519{ 520 apr_hash_t *props = baton; 521 apr_pool_t *result_pool = apr_hash_pool_get(props); 522 const char *prop_name; 523 524 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool); 525 if (prop_name == NULL) 526 return SVN_NO_ERROR; 527 528 svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool)); 529 530 return SVN_NO_ERROR; 531} 532 533/* 534 * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties 535 * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash 536 * (const char * -> svn_string_t *) to the values. 537 */ 538static svn_error_t * 539deliver_node_props(void *baton, 540 const char *path, 541 const char *ns, 542 const char *name, 543 const svn_string_t *value, 544 apr_pool_t *scratch_pool) 545{ 546 apr_hash_t *nss = baton; 547 apr_hash_t *props; 548 apr_pool_t *result_pool = apr_hash_pool_get(nss); 549 550 props = svn_hash_gets(nss, ns); 551 552 if (!props) 553 { 554 props = apr_hash_make(result_pool); 555 556 ns = apr_pstrdup(result_pool, ns); 557 svn_hash_sets(nss, ns, props); 558 } 559 560 name = apr_pstrdup(result_pool, name); 561 svn_hash_sets(props, name, svn_string_dup(value, result_pool)); 562 563 return SVN_NO_ERROR; 564} 565 566svn_error_t * 567svn_ra_serf__fetch_node_props(apr_hash_t **results, 568 svn_ra_serf__session_t *session, 569 const char *url, 570 svn_revnum_t revision, 571 const svn_ra_serf__dav_props_t *which_props, 572 apr_pool_t *result_pool, 573 apr_pool_t *scratch_pool) 574{ 575 apr_hash_t *props; 576 svn_ra_serf__handler_t *handler; 577 578 props = apr_hash_make(result_pool); 579 580 SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, 581 url, revision, "0", which_props, 582 deliver_node_props, 583 props, scratch_pool)); 584 585 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 586 587 *results = props; 588 return SVN_NO_ERROR; 589} 590 591const char * 592svn_ra_serf__svnname_from_wirename(const char *ns, 593 const char *name, 594 apr_pool_t *result_pool) 595{ 596 if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) 597 return apr_pstrdup(result_pool, name); 598 599 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) 600 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL); 601 602 if (strcmp(ns, SVN_PROP_PREFIX) == 0) 603 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL); 604 605 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) 606 return SVN_PROP_ENTRY_COMMITTED_REV; 607 608 if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) 609 return SVN_PROP_ENTRY_COMMITTED_DATE; 610 611 if (strcmp(name, "creator-displayname") == 0) 612 return SVN_PROP_ENTRY_LAST_AUTHOR; 613 614 if (strcmp(name, "repository-uuid") == 0) 615 return SVN_PROP_ENTRY_UUID; 616 617 if (strcmp(name, "lock-token") == 0) 618 return SVN_PROP_ENTRY_LOCK_TOKEN; 619 620 if (strcmp(name, "checked-in") == 0) 621 return SVN_RA_SERF__WC_CHECKED_IN_URL; 622 623 if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) 624 { 625 /* Here DAV: properties not yet converted to svn: properties should be 626 ignored. */ 627 return NULL; 628 } 629 630 /* An unknown namespace, must be a custom property. */ 631 return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL); 632} 633 634/* 635 * Contact the server (using CONN) to calculate baseline 636 * information for BASELINE_URL at REVISION (which may be 637 * SVN_INVALID_REVNUM to query the HEAD revision). 638 * 639 * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision 640 * retrieved from the server as part of this process (which should 641 * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the 642 * baseline collection URL. 643 */ 644static svn_error_t * 645retrieve_baseline_info(svn_revnum_t *actual_revision, 646 const char **basecoll_url_p, 647 svn_ra_serf__session_t *session, 648 const char *baseline_url, 649 svn_revnum_t revision, 650 apr_pool_t *result_pool, 651 apr_pool_t *scratch_pool) 652{ 653 apr_hash_t *props; 654 apr_hash_t *dav_props; 655 const char *basecoll_url; 656 657 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, 658 baseline_url, revision, 659 baseline_props, 660 scratch_pool, scratch_pool)); 661 dav_props = apr_hash_get(props, "DAV:", 4); 662 /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */ 663 664 basecoll_url = svn_prop_get_value(dav_props, "baseline-collection"); 665 if (!basecoll_url) 666 { 667 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 668 _("The PROPFIND response did not include " 669 "the requested baseline-collection value")); 670 } 671 *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool); 672 673 if (actual_revision) 674 { 675 const char *version_name; 676 677 version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME); 678 if (version_name) 679 { 680 apr_int64_t rev; 681 682 SVN_ERR(svn_cstring_atoi64(&rev, version_name)); 683 *actual_revision = (svn_revnum_t)rev; 684 } 685 686 if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision)) 687 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 688 _("The PROPFIND response did not include " 689 "the requested version-name value")); 690 } 691 692 return SVN_NO_ERROR; 693} 694 695 696/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest 697 revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline 698 collection URL is also returned. 699 700 Do the work over CONN. 701 702 *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All 703 temporary allocations will be made in SCRATCH_POOL. */ 704static svn_error_t * 705v1_get_youngest_revnum(svn_revnum_t *youngest, 706 const char **basecoll_url, 707 svn_ra_serf__session_t *session, 708 const char *vcc_url, 709 apr_pool_t *result_pool, 710 apr_pool_t *scratch_pool) 711{ 712 const char *baseline_url; 713 const char *bc_url; 714 715 /* Fetching DAV:checked-in from the VCC (with no Label: to specify a 716 revision) will return the latest Baseline resource's URL. */ 717 SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url, 718 SVN_INVALID_REVNUM, 719 "checked-in", 720 scratch_pool, scratch_pool)); 721 if (!baseline_url) 722 { 723 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 724 _("The OPTIONS response did not include " 725 "the requested checked-in value")); 726 } 727 baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool); 728 729 /* From the Baseline resource, we can fetch the DAV:baseline-collection 730 and DAV:version-name properties. The latter is the revision number, 731 which is formally the name used in Label: headers. */ 732 733 /* First check baseline information cache. */ 734 SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url, 735 youngest, 736 session->blncache, 737 baseline_url, 738 scratch_pool)); 739 if (!bc_url) 740 { 741 SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session, 742 baseline_url, SVN_INVALID_REVNUM, 743 scratch_pool, scratch_pool)); 744 SVN_ERR(svn_ra_serf__blncache_set(session->blncache, 745 baseline_url, *youngest, 746 bc_url, scratch_pool)); 747 } 748 749 if (basecoll_url != NULL) 750 *basecoll_url = apr_pstrdup(result_pool, bc_url); 751 752 return SVN_NO_ERROR; 753} 754 755 756svn_error_t * 757svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest, 758 svn_ra_serf__session_t *session, 759 apr_pool_t *scratch_pool) 760{ 761 const char *vcc_url; 762 763 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 764 return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum( 765 youngest, session, scratch_pool)); 766 767 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool)); 768 769 return svn_error_trace(v1_get_youngest_revnum(youngest, NULL, 770 session, vcc_url, 771 scratch_pool, scratch_pool)); 772} 773 774 775/* Set *BC_URL to the baseline collection url for REVISION. If REVISION 776 is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used. 777 778 *REVNUM_USED will be set to the revision used. 779 780 Uses the specified CONN, which is part of SESSION. 781 782 All allocations (results and temporary) are performed in POOL. */ 783static svn_error_t * 784get_baseline_info(const char **bc_url, 785 svn_revnum_t *revnum_used, 786 svn_ra_serf__session_t *session, 787 svn_revnum_t revision, 788 apr_pool_t *result_pool, 789 apr_pool_t *scratch_pool) 790{ 791 /* If we detected HTTP v2 support on the server, we can construct 792 the baseline collection URL ourselves, and fetch the latest 793 revision (if needed) with an OPTIONS request. */ 794 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 795 { 796 if (SVN_IS_VALID_REVNUM(revision)) 797 { 798 *revnum_used = revision; 799 } 800 else 801 { 802 SVN_ERR(svn_ra_serf__v2_get_youngest_revnum( 803 revnum_used, session, scratch_pool)); 804 } 805 806 *bc_url = apr_psprintf(result_pool, "%s/%ld", 807 session->rev_root_stub, *revnum_used); 808 } 809 810 /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */ 811 else 812 { 813 const char *vcc_url; 814 815 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool)); 816 817 if (SVN_IS_VALID_REVNUM(revision)) 818 { 819 /* First check baseline information cache. */ 820 SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url, 821 session->blncache, 822 revision, result_pool)); 823 if (!*bc_url) 824 { 825 SVN_ERR(retrieve_baseline_info(NULL, bc_url, session, 826 vcc_url, revision, 827 result_pool, scratch_pool)); 828 SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL, 829 revision, *bc_url, 830 scratch_pool)); 831 } 832 833 *revnum_used = revision; 834 } 835 else 836 { 837 SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url, 838 session, vcc_url, 839 result_pool, scratch_pool)); 840 } 841 } 842 843 return SVN_NO_ERROR; 844} 845 846 847svn_error_t * 848svn_ra_serf__get_stable_url(const char **stable_url, 849 svn_revnum_t *latest_revnum, 850 svn_ra_serf__session_t *session, 851 const char *url, 852 svn_revnum_t revision, 853 apr_pool_t *result_pool, 854 apr_pool_t *scratch_pool) 855{ 856 const char *basecoll_url; 857 const char *repos_relpath; 858 svn_revnum_t revnum_used; 859 860 /* No URL? No sweat. We'll use the session URL. */ 861 if (! url) 862 url = session->session_url.path; 863 864 SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used, 865 session, revision, scratch_pool, scratch_pool)); 866 SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url, 867 session, scratch_pool)); 868 869 *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath, 870 result_pool); 871 if (latest_revnum) 872 *latest_revnum = revnum_used; 873 874 return SVN_NO_ERROR; 875} 876 877 878svn_error_t * 879svn_ra_serf__fetch_dav_prop(const char **value, 880 svn_ra_serf__session_t *session, 881 const char *url, 882 svn_revnum_t revision, 883 const char *propname, 884 apr_pool_t *result_pool, 885 apr_pool_t *scratch_pool) 886{ 887 apr_hash_t *props; 888 apr_hash_t *dav_props; 889 890 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision, 891 checked_in_props, 892 scratch_pool, scratch_pool)); 893 dav_props = apr_hash_get(props, "DAV:", 4); 894 if (dav_props == NULL) 895 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 896 _("The PROPFIND response did not include " 897 "the requested 'DAV:' properties")); 898 899 /* We wouldn't get here if the resource was not found (404), so the 900 property should be present. 901 902 Note: it is okay to call apr_pstrdup() with NULL. */ 903 *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname)); 904 905 return SVN_NO_ERROR; 906} 907 908/* Removes all non regular properties from PROPS */ 909void 910svn_ra_serf__keep_only_regular_props(apr_hash_t *props, 911 apr_pool_t *scratch_pool) 912{ 913 apr_hash_index_t *hi; 914 915 for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi)) 916 { 917 const char *propname = apr_hash_this_key(hi); 918 919 if (svn_property_kind2(propname) != svn_prop_regular_kind) 920 svn_hash_sets(props, propname, NULL); 921 } 922} 923