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