options.c revision 269847
1/* 2 * options.c : entry point for OPTIONS 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 28#include <serf.h> 29 30#include "svn_dirent_uri.h" 31#include "svn_hash.h" 32#include "svn_pools.h" 33#include "svn_ra.h" 34#include "svn_dav.h" 35#include "svn_xml.h" 36 37#include "../libsvn_ra/ra_loader.h" 38#include "svn_private_config.h" 39#include "private/svn_fspath.h" 40 41#include "ra_serf.h" 42 43 44/* In a debug build, setting this environment variable to "yes" will force 45 the client to speak v1, even if the server is capable of speaking v2. */ 46#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2" 47 48 49/* 50 * This enum represents the current state of our XML parsing for an OPTIONS. 51 */ 52enum options_state_e { 53 INITIAL = 0, 54 OPTIONS, 55 ACTIVITY_COLLECTION, 56 HREF 57}; 58 59typedef struct options_context_t { 60 /* pool to allocate memory from */ 61 apr_pool_t *pool; 62 63 /* Have we extracted options values from the headers already? */ 64 svn_boolean_t headers_processed; 65 66 svn_ra_serf__session_t *session; 67 svn_ra_serf__connection_t *conn; 68 svn_ra_serf__handler_t *handler; 69 70 svn_ra_serf__response_handler_t inner_handler; 71 void *inner_baton; 72 73 const char *activity_collection; 74 svn_revnum_t youngest_rev; 75 76} options_context_t; 77 78#define D_ "DAV:" 79#define S_ SVN_XML_NAMESPACE 80static const svn_ra_serf__xml_transition_t options_ttable[] = { 81 { INITIAL, D_, "options-response", OPTIONS, 82 FALSE, { NULL }, FALSE }, 83 84 { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION, 85 FALSE, { NULL }, FALSE }, 86 87 { ACTIVITY_COLLECTION, D_, "href", HREF, 88 TRUE, { NULL }, TRUE }, 89 90 { 0 } 91}; 92 93 94/* Conforms to svn_ra_serf__xml_closed_t */ 95static svn_error_t * 96options_closed(svn_ra_serf__xml_estate_t *xes, 97 void *baton, 98 int leaving_state, 99 const svn_string_t *cdata, 100 apr_hash_t *attrs, 101 apr_pool_t *scratch_pool) 102{ 103 options_context_t *opt_ctx = baton; 104 105 SVN_ERR_ASSERT(leaving_state == HREF); 106 SVN_ERR_ASSERT(cdata != NULL); 107 108 opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data, 109 opt_ctx->pool); 110 111 return SVN_NO_ERROR; 112} 113 114 115static svn_error_t * 116create_options_body(serf_bucket_t **body_bkt, 117 void *baton, 118 serf_bucket_alloc_t *alloc, 119 apr_pool_t *pool) 120{ 121 serf_bucket_t *body; 122 body = serf_bucket_aggregate_create(alloc); 123 svn_ra_serf__add_xml_header_buckets(body, alloc); 124 svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options", 125 "xmlns:D", "DAV:", 126 NULL); 127 svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc); 128 svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options"); 129 130 *body_bkt = body; 131 return SVN_NO_ERROR; 132} 133 134 135/* We use these static pointers so we can employ pointer comparison 136 * of our capabilities hash members instead of strcmp()ing all over 137 * the place. 138 */ 139/* Both server and repository support the capability. */ 140static const char *const capability_yes = "yes"; 141/* Either server or repository does not support the capability. */ 142static const char *const capability_no = "no"; 143/* Server supports the capability, but don't yet know if repository does. */ 144static const char *const capability_server_yes = "server-yes"; 145 146 147/* This implements serf_bucket_headers_do_callback_fn_t. 148 */ 149static int 150capabilities_headers_iterator_callback(void *baton, 151 const char *key, 152 const char *val) 153{ 154 options_context_t *opt_ctx = baton; 155 svn_ra_serf__session_t *session = opt_ctx->session; 156 157 if (svn_cstring_casecmp(key, "dav") == 0) 158 { 159 /* Each header may contain multiple values, separated by commas, e.g.: 160 DAV: version-control,checkout,working-resource 161 DAV: merge,baseline,activity,version-controlled-collection 162 DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */ 163 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE, 164 opt_ctx->pool); 165 166 /* Right now we only have a few capabilities to detect, so just 167 seek for them directly. This could be written slightly more 168 efficiently, but that wouldn't be worth it until we have many 169 more capabilities. */ 170 171 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals)) 172 { 173 svn_hash_sets(session->capabilities, 174 SVN_RA_CAPABILITY_DEPTH, capability_yes); 175 } 176 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals)) 177 { 178 /* The server doesn't know what repository we're referring 179 to, so it can't just say capability_yes. */ 180 if (!svn_hash_gets(session->capabilities, 181 SVN_RA_CAPABILITY_MERGEINFO)) 182 { 183 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 184 capability_server_yes); 185 } 186 } 187 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals)) 188 { 189 svn_hash_sets(session->capabilities, 190 SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes); 191 } 192 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals)) 193 { 194 svn_hash_sets(session->capabilities, 195 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes); 196 } 197 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals)) 198 { 199 svn_hash_sets(session->capabilities, 200 SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes); 201 } 202 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals)) 203 { 204 svn_hash_sets(session->capabilities, 205 SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes); 206 } 207 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS, 208 vals)) 209 { 210 svn_hash_sets(session->capabilities, 211 SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, 212 capability_yes); 213 } 214 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals)) 215 { 216 svn_hash_sets(session->capabilities, 217 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes); 218 } 219 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals)) 220 { 221 session->supports_inline_props = TRUE; 222 } 223 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals)) 224 { 225 session->supports_rev_rsrc_replay = TRUE; 226 } 227 } 228 229 /* SVN-specific headers -- if present, server supports HTTP protocol v2 */ 230 else if (strncmp(key, "SVN", 3) == 0) 231 { 232 /* If we've not yet seen any information about supported POST 233 requests, we'll initialize the list/hash with "create-txn" 234 (which we know is supported by virtue of the server speaking 235 HTTPv2 at all. */ 236 if (! session->supported_posts) 237 { 238 session->supported_posts = apr_hash_make(session->pool); 239 apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1); 240 } 241 242 if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0) 243 { 244 session->repos_root = session->session_url; 245 session->repos_root.path = 246 (char *)svn_fspath__canonicalize(val, session->pool); 247 session->repos_root_str = 248 svn_urlpath__canonicalize( 249 apr_uri_unparse(session->pool, &session->repos_root, 0), 250 session->pool); 251 } 252 else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0) 253 { 254#ifdef SVN_DEBUG 255 char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR); 256 257 if (!(ignore_v2_env_var 258 && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0)) 259 session->me_resource = apr_pstrdup(session->pool, val); 260#else 261 session->me_resource = apr_pstrdup(session->pool, val); 262#endif 263 } 264 else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0) 265 { 266 session->rev_stub = apr_pstrdup(session->pool, val); 267 } 268 else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0) 269 { 270 session->rev_root_stub = apr_pstrdup(session->pool, val); 271 } 272 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0) 273 { 274 session->txn_stub = apr_pstrdup(session->pool, val); 275 } 276 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0) 277 { 278 session->txn_root_stub = apr_pstrdup(session->pool, val); 279 } 280 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0) 281 { 282 session->vtxn_stub = apr_pstrdup(session->pool, val); 283 } 284 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0) 285 { 286 session->vtxn_root_stub = apr_pstrdup(session->pool, val); 287 } 288 else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0) 289 { 290 session->uuid = apr_pstrdup(session->pool, val); 291 } 292 else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0) 293 { 294 opt_ctx->youngest_rev = SVN_STR_TO_REV(val); 295 } 296 else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0) 297 { 298 session->server_allows_bulk = apr_pstrdup(session->pool, val); 299 } 300 else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0) 301 { 302 /* May contain multiple values, separated by commas. */ 303 int i; 304 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE, 305 session->pool); 306 307 for (i = 0; i < vals->nelts; i++) 308 { 309 const char *post_val = APR_ARRAY_IDX(vals, i, const char *); 310 311 svn_hash_sets(session->supported_posts, post_val, (void *)1); 312 } 313 } 314 else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0) 315 { 316 if (svn_cstring_casecmp(val, "yes") == 0) 317 { 318 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 319 capability_yes); 320 } 321 else if (svn_cstring_casecmp(val, "no") == 0) 322 { 323 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 324 capability_no); 325 } 326 } 327 } 328 329 return 0; 330} 331 332 333/* A custom serf_response_handler_t which is mostly a wrapper around 334 the expat-based response handler -- it just notices OPTIONS response 335 headers first, before handing off to the xml parser. 336 Implements svn_ra_serf__response_handler_t */ 337static svn_error_t * 338options_response_handler(serf_request_t *request, 339 serf_bucket_t *response, 340 void *baton, 341 apr_pool_t *pool) 342{ 343 options_context_t *opt_ctx = baton; 344 345 if (!opt_ctx->headers_processed) 346 { 347 svn_ra_serf__session_t *session = opt_ctx->session; 348 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response); 349 350 /* Start out assuming all capabilities are unsupported. */ 351 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY, 352 capability_no); 353 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH, 354 capability_no); 355 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 356 NULL); 357 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS, 358 capability_no); 359 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 360 capability_no); 361 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS, 362 capability_no); 363 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, 364 capability_no); 365 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, 366 capability_no); 367 368 /* Then see which ones we can discover. */ 369 serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback, 370 opt_ctx); 371 372 /* Assume mergeinfo capability unsupported, if didn't recieve information 373 about server or repository mergeinfo capability. */ 374 if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO)) 375 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 376 capability_no); 377 378 opt_ctx->headers_processed = TRUE; 379 } 380 381 /* Execute the 'real' response handler to XML-parse the response body. */ 382 return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool); 383} 384 385 386static svn_error_t * 387create_options_req(options_context_t **opt_ctx, 388 svn_ra_serf__session_t *session, 389 svn_ra_serf__connection_t *conn, 390 apr_pool_t *pool) 391{ 392 options_context_t *new_ctx; 393 svn_ra_serf__xml_context_t *xmlctx; 394 svn_ra_serf__handler_t *handler; 395 396 new_ctx = apr_pcalloc(pool, sizeof(*new_ctx)); 397 new_ctx->pool = pool; 398 new_ctx->session = session; 399 new_ctx->conn = conn; 400 401 new_ctx->youngest_rev = SVN_INVALID_REVNUM; 402 403 xmlctx = svn_ra_serf__xml_context_create(options_ttable, 404 NULL, options_closed, NULL, 405 new_ctx, 406 pool); 407 handler = svn_ra_serf__create_expat_handler(xmlctx, pool); 408 409 handler->method = "OPTIONS"; 410 handler->path = session->session_url.path; 411 handler->body_delegate = create_options_body; 412 handler->body_type = "text/xml"; 413 handler->conn = conn; 414 handler->session = session; 415 416 new_ctx->handler = handler; 417 418 new_ctx->inner_handler = handler->response_handler; 419 new_ctx->inner_baton = handler->response_baton; 420 handler->response_handler = options_response_handler; 421 handler->response_baton = new_ctx; 422 423 *opt_ctx = new_ctx; 424 425 return SVN_NO_ERROR; 426} 427 428 429svn_error_t * 430svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest, 431 svn_ra_serf__connection_t *conn, 432 apr_pool_t *scratch_pool) 433{ 434 svn_ra_serf__session_t *session = conn->session; 435 options_context_t *opt_ctx; 436 437 SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 438 439 SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool)); 440 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); 441 SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline, 442 opt_ctx->handler->path, 443 opt_ctx->handler->location)); 444 445 *youngest = opt_ctx->youngest_rev; 446 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*youngest)); 447 448 return SVN_NO_ERROR; 449} 450 451 452svn_error_t * 453svn_ra_serf__v1_get_activity_collection(const char **activity_url, 454 svn_ra_serf__connection_t *conn, 455 apr_pool_t *result_pool, 456 apr_pool_t *scratch_pool) 457{ 458 svn_ra_serf__session_t *session = conn->session; 459 options_context_t *opt_ctx; 460 461 SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 462 463 SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool)); 464 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); 465 466 SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline, 467 opt_ctx->handler->path, 468 opt_ctx->handler->location)); 469 470 *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection); 471 472 return SVN_NO_ERROR; 473 474} 475 476 477 478/** Capabilities exchange. */ 479 480svn_error_t * 481svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess, 482 const char **corrected_url, 483 apr_pool_t *pool) 484{ 485 options_context_t *opt_ctx; 486 svn_error_t *err; 487 488 /* This routine automatically fills in serf_sess->capabilities */ 489 SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool)); 490 491 err = svn_ra_serf__context_run_one(opt_ctx->handler, pool); 492 493 /* If our caller cares about server redirections, and our response 494 carries such a thing, report as much. We'll disregard ERR -- 495 it's most likely just a complaint about the response body not 496 successfully parsing as XML or somesuch. */ 497 if (corrected_url && (opt_ctx->handler->sline.code == 301)) 498 { 499 svn_error_clear(err); 500 *corrected_url = opt_ctx->handler->location; 501 return SVN_NO_ERROR; 502 } 503 504 SVN_ERR(svn_error_compose_create( 505 svn_ra_serf__error_on_status(opt_ctx->handler->sline, 506 serf_sess->session_url.path, 507 opt_ctx->handler->location), 508 err)); 509 510 /* Opportunistically cache any reported activity URL. (We don't 511 want to have to ask for this again later, potentially against an 512 unreadable commit anchor URL.) */ 513 if (opt_ctx->activity_collection) 514 { 515 serf_sess->activity_collection_url = 516 apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection); 517 } 518 519 return SVN_NO_ERROR; 520} 521 522 523static svn_error_t * 524create_simple_options_body(serf_bucket_t **body_bkt, 525 void *baton, 526 serf_bucket_alloc_t *alloc, 527 apr_pool_t *pool) 528{ 529 serf_bucket_t *body; 530 serf_bucket_t *s; 531 532 body = serf_bucket_aggregate_create(alloc); 533 svn_ra_serf__add_xml_header_buckets(body, alloc); 534 535 s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc); 536 serf_bucket_aggregate_append(body, s); 537 538 *body_bkt = body; 539 return SVN_NO_ERROR; 540} 541 542 543svn_error_t * 544svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess, 545 apr_pool_t *scratch_pool) 546{ 547 svn_ra_serf__handler_t *handler; 548 549 handler = apr_pcalloc(scratch_pool, sizeof(*handler)); 550 handler->handler_pool = scratch_pool; 551 handler->method = "OPTIONS"; 552 handler->path = serf_sess->session_url.path; 553 handler->conn = serf_sess->conns[0]; 554 handler->session = serf_sess; 555 556 /* We don't care about the response body, so discard it. */ 557 handler->response_handler = svn_ra_serf__handle_discard_body; 558 559 /* We need a simple body, in order to send it in chunked format. */ 560 handler->body_delegate = create_simple_options_body; 561 562 /* No special headers. */ 563 564 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 565 /* Some versions of nginx in reverse proxy mode will return 411. They want 566 a Content-Length header, rather than chunked requests. We can keep other 567 HTTP/1.1 features, but will disable the chunking. */ 568 if (handler->sline.code == 411) 569 { 570 serf_sess->using_chunked_requests = FALSE; 571 572 return SVN_NO_ERROR; 573 } 574 SVN_ERR(svn_ra_serf__error_on_status(handler->sline, 575 handler->path, 576 handler->location)); 577 578 return SVN_NO_ERROR; 579} 580 581 582svn_error_t * 583svn_ra_serf__has_capability(svn_ra_session_t *ra_session, 584 svn_boolean_t *has, 585 const char *capability, 586 apr_pool_t *pool) 587{ 588 svn_ra_serf__session_t *serf_sess = ra_session->priv; 589 const char *cap_result; 590 591 /* This capability doesn't rely on anything server side. */ 592 if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0) 593 { 594 *has = TRUE; 595 return SVN_NO_ERROR; 596 } 597 598 cap_result = svn_hash_gets(serf_sess->capabilities, capability); 599 600 /* If any capability is unknown, they're all unknown, so ask. */ 601 if (cap_result == NULL) 602 SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool)); 603 604 /* Try again, now that we've fetched the capabilities. */ 605 cap_result = svn_hash_gets(serf_sess->capabilities, capability); 606 607 /* Some capabilities depend on the repository as well as the server. */ 608 if (cap_result == capability_server_yes) 609 { 610 if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0) 611 { 612 /* Handle mergeinfo specially. Mergeinfo depends on the 613 repository as well as the server, but the server routine 614 that answered our svn_ra_serf__exchange_capabilities() call above 615 didn't even know which repository we were interested in 616 -- it just told us whether the server supports mergeinfo. 617 If the answer was 'no', there's no point checking the 618 particular repository; but if it was 'yes', we still must 619 change it to 'no' iff the repository itself doesn't 620 support mergeinfo. */ 621 svn_mergeinfo_catalog_t ignored; 622 svn_error_t *err; 623 apr_array_header_t *paths = apr_array_make(pool, 1, 624 sizeof(char *)); 625 APR_ARRAY_PUSH(paths, const char *) = ""; 626 627 err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0, 628 FALSE, FALSE, pool); 629 630 if (err) 631 { 632 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE) 633 { 634 svn_error_clear(err); 635 cap_result = capability_no; 636 } 637 else if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 638 { 639 /* Mergeinfo requests use relative paths, and 640 anyway we're in r0, so this is a likely error, 641 but it means the repository supports mergeinfo! */ 642 svn_error_clear(err); 643 cap_result = capability_yes; 644 } 645 else 646 return err; 647 } 648 else 649 cap_result = capability_yes; 650 651 svn_hash_sets(serf_sess->capabilities, 652 SVN_RA_CAPABILITY_MERGEINFO, cap_result); 653 } 654 else 655 { 656 return svn_error_createf 657 (SVN_ERR_UNKNOWN_CAPABILITY, NULL, 658 _("Don't know how to handle '%s' for capability '%s'"), 659 capability_server_yes, capability); 660 } 661 } 662 663 if (cap_result == capability_yes) 664 { 665 *has = TRUE; 666 } 667 else if (cap_result == capability_no) 668 { 669 *has = FALSE; 670 } 671 else if (cap_result == NULL) 672 { 673 return svn_error_createf 674 (SVN_ERR_UNKNOWN_CAPABILITY, NULL, 675 _("Don't know anything about capability '%s'"), capability); 676 } 677 else /* "can't happen" */ 678 { 679 /* Well, let's hope it's a string. */ 680 return svn_error_createf 681 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 682 _("Attempt to fetch capability '%s' resulted in '%s'"), 683 capability, cap_result); 684 } 685 686 return SVN_NO_ERROR; 687} 688