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