options.c revision 299742
167754Smsmith/* 267754Smsmith * options.c : entry point for OPTIONS RA functions for ra_serf 377424Smsmith * 467754Smsmith * ==================================================================== 567754Smsmith * Licensed to the Apache Software Foundation (ASF) under one 667754Smsmith * or more contributor license agreements. See the NOTICE file 7217365Sjkim * distributed with this work for additional information 8306536Sjkim * regarding copyright ownership. The ASF licenses this file 970243Smsmith * to you under the Apache License, Version 2.0 (the 1067754Smsmith * "License"); you may not use this file except in compliance 11217365Sjkim * with the License. You may obtain a copy of the License at 12217365Sjkim * 13217365Sjkim * http://www.apache.org/licenses/LICENSE-2.0 14217365Sjkim * 15217365Sjkim * Unless required by applicable law or agreed to in writing, 16217365Sjkim * software distributed under the License is distributed on an 17217365Sjkim * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18217365Sjkim * KIND, either express or implied. See the License for the 19217365Sjkim * specific language governing permissions and limitations 20217365Sjkim * under the License. 21217365Sjkim * ==================================================================== 22217365Sjkim */ 23217365Sjkim 24217365Sjkim 2567754Smsmith 26217365Sjkim#include <apr_uri.h> 27217365Sjkim 28217365Sjkim#include <serf.h> 2967754Smsmith 30217365Sjkim#include "svn_dirent_uri.h" 31217365Sjkim#include "svn_hash.h" 32217365Sjkim#include "svn_pools.h" 33217365Sjkim#include "svn_path.h" 34217365Sjkim#include "svn_ra.h" 35217365Sjkim#include "svn_dav.h" 36217365Sjkim#include "svn_xml.h" 37217365Sjkim#include "svn_ctype.h" 38217365Sjkim 39217365Sjkim#include "../libsvn_ra/ra_loader.h" 40217365Sjkim#include "svn_private_config.h" 41217365Sjkim#include "private/svn_fspath.h" 42217365Sjkim 4367754Smsmith#include "ra_serf.h" 44193341Sjkim 45193341Sjkim 46193341Sjkim/* In a debug build, setting this environment variable to "yes" will force 4767754Smsmith the client to speak v1, even if the server is capable of speaking v2. */ 4877424Smsmith#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2" 4991116Smsmith 5067754Smsmith 5167754Smsmith/* 52151937Sjkim * This enum represents the current state of our XML parsing for an OPTIONS. 53151937Sjkim */ 54151937Sjkimenum options_state_e { 55151937Sjkim INITIAL = XML_STATE_INITIAL, 56151937Sjkim OPTIONS, 57151937Sjkim ACTIVITY_COLLECTION, 58151937Sjkim HREF 59151937Sjkim}; 60151937Sjkim 6167754Smsmithtypedef struct options_context_t { 6267754Smsmith /* pool to allocate memory from */ 63151937Sjkim apr_pool_t *pool; 6467754Smsmith 65151937Sjkim /* Have we extracted options values from the headers already? */ 66151937Sjkim svn_boolean_t headers_processed; 67151937Sjkim 6867754Smsmith svn_ra_serf__session_t *session; 6977424Smsmith svn_ra_serf__handler_t *handler; 7067754Smsmith 71151937Sjkim svn_ra_serf__response_handler_t inner_handler; 72151937Sjkim void *inner_baton; 7367754Smsmith 7467754Smsmith const char *activity_collection; 7567754Smsmith svn_revnum_t youngest_rev; 7667754Smsmith 77151937Sjkim} options_context_t; 78151937Sjkim 79151937Sjkim#define D_ "DAV:" 80151937Sjkim#define S_ SVN_XML_NAMESPACE 8167754Smsmithstatic const svn_ra_serf__xml_transition_t options_ttable[] = { 82151937Sjkim { INITIAL, D_, "options-response", OPTIONS, 83151937Sjkim FALSE, { NULL }, FALSE }, 84151937Sjkim 85151937Sjkim { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION, 86151937Sjkim FALSE, { NULL }, FALSE }, 87151937Sjkim 88151937Sjkim { ACTIVITY_COLLECTION, D_, "href", HREF, 89151937Sjkim TRUE, { NULL }, TRUE }, 9067754Smsmith 9167754Smsmith { 0 } 92167802Sjkim}; 9367754Smsmith 9477424Smsmith 95228110Sjkim/* Conforms to svn_ra_serf__xml_closed_t */ 96228110Sjkimstatic svn_error_t * 97228110Sjkimoptions_closed(svn_ra_serf__xml_estate_t *xes, 98228110Sjkim void *baton, 99228110Sjkim int leaving_state, 100193267Sjkim const svn_string_t *cdata, 101151937Sjkim apr_hash_t *attrs, 102167802Sjkim apr_pool_t *scratch_pool) 103167802Sjkim{ 104167802Sjkim options_context_t *opt_ctx = baton; 105204773Sjkim 106167802Sjkim SVN_ERR_ASSERT(leaving_state == HREF); 107151937Sjkim SVN_ERR_ASSERT(cdata != NULL); 10867754Smsmith 109151937Sjkim opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data, 11067754Smsmith opt_ctx->pool); 111151937Sjkim 11267754Smsmith return SVN_NO_ERROR; 11367754Smsmith} 114151937Sjkim 115151937Sjkim/* Implements svn_ra_serf__request_body_delegate_t */ 11667754Smsmithstatic svn_error_t * 117151937Sjkimcreate_options_body(serf_bucket_t **body_bkt, 118151937Sjkim void *baton, 119151937Sjkim serf_bucket_alloc_t *alloc, 120151937Sjkim apr_pool_t *pool /* request pool */, 121151937Sjkim apr_pool_t *scratch_pool) 122151937Sjkim{ 123151937Sjkim serf_bucket_t *body; 124306536Sjkim body = serf_bucket_aggregate_create(alloc); 125167802Sjkim svn_ra_serf__add_xml_header_buckets(body, alloc); 12667754Smsmith svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options", 127151937Sjkim "xmlns:D", "DAV:", 128151937Sjkim SVN_VA_NULL); 129151937Sjkim svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc); 130151937Sjkim svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options"); 131151937Sjkim 132151937Sjkim *body_bkt = body; 133306536Sjkim return SVN_NO_ERROR; 134151937Sjkim} 135151937Sjkim 136151937Sjkim 13767754Smsmith/* We use these static pointers so we can employ pointer comparison 138151937Sjkim * of our capabilities hash members instead of strcmp()ing all over 139151937Sjkim * the place. 14067754Smsmith */ 141151937Sjkim/* Both server and repository support the capability. */ 14277424Smsmithstatic const char *const capability_yes = "yes"; 143151937Sjkim/* Either server or repository does not support the capability. */ 144151937Sjkimstatic const char *const capability_no = "no"; 14567754Smsmith/* Server supports the capability, but don't yet know if repository does. */ 146151937Sjkimstatic const char *const capability_server_yes = "server-yes"; 147151937Sjkim 148151937Sjkim 149151937Sjkim/* This implements serf_bucket_headers_do_callback_fn_t. 150243347Sjkim */ 151243347Sjkimstatic int 152151937Sjkimcapabilities_headers_iterator_callback(void *baton, 15367754Smsmith const char *key, 154151937Sjkim const char *val) 155151937Sjkim{ 156151937Sjkim options_context_t *opt_ctx = baton; 157151937Sjkim svn_ra_serf__session_t *session = opt_ctx->session; 158243347Sjkim 159243347Sjkim if (svn_cstring_casecmp(key, "dav") == 0) 160151937Sjkim { 16167754Smsmith /* Each header may contain multiple values, separated by commas, e.g.: 162228110Sjkim DAV: version-control,checkout,working-resource 163228110Sjkim DAV: merge,baseline,activity,version-controlled-collection 164228110Sjkim DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */ 165228110Sjkim apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE, 166243347Sjkim opt_ctx->pool); 167243347Sjkim 168228110Sjkim /* Right now we only have a few capabilities to detect, so just 169228110Sjkim seek for them directly. This could be written slightly more 170151937Sjkim efficiently, but that wouldn't be worth it until we have many 17167754Smsmith more capabilities. */ 172167802Sjkim 173243347Sjkim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals)) 17467754Smsmith { 175151937Sjkim svn_hash_sets(session->capabilities, 176151937Sjkim SVN_RA_CAPABILITY_DEPTH, capability_yes); 177151937Sjkim } 17867754Smsmith if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals)) 179151937Sjkim { 18067754Smsmith /* The server doesn't know what repository we're referring 181151937Sjkim to, so it can't just say capability_yes. */ 182243347Sjkim if (!svn_hash_gets(session->capabilities, 18367754Smsmith SVN_RA_CAPABILITY_MERGEINFO)) 184151937Sjkim { 185151937Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 186151937Sjkim capability_server_yes); 18767754Smsmith } 188228110Sjkim } 189228110Sjkim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals)) 190228110Sjkim { 191228110Sjkim svn_hash_sets(session->capabilities, 192228110Sjkim SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes); 193228110Sjkim } 194228110Sjkim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals)) 195243347Sjkim { 196228110Sjkim svn_hash_sets(session->capabilities, 197228110Sjkim SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes); 198228110Sjkim } 199228110Sjkim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals)) 200228110Sjkim { 201243347Sjkim svn_hash_sets(session->capabilities, 202228110Sjkim SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes); 203306536Sjkim } 204228110Sjkim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals)) 205228110Sjkim { 206228110Sjkim svn_hash_sets(session->capabilities, 207228110Sjkim SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes); 208228110Sjkim } 209228110Sjkim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS, 210228110Sjkim vals)) 211228110Sjkim { 212228110Sjkim svn_hash_sets(session->capabilities, 213228110Sjkim SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, 214228110Sjkim capability_yes); 215228110Sjkim } 216228110Sjkim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals)) 217228110Sjkim { 218228110Sjkim svn_hash_sets(session->capabilities, 219228110Sjkim SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes); 220228110Sjkim } 221228110Sjkim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals)) 222228110Sjkim { 223228110Sjkim session->supports_inline_props = TRUE; 224228110Sjkim } 225228110Sjkim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals)) 226228110Sjkim { 227228110Sjkim session->supports_rev_rsrc_replay = TRUE; 228228110Sjkim } 229243347Sjkim } 230228110Sjkim 231228110Sjkim /* SVN-specific headers -- if present, server supports HTTP protocol v2 */ 232228110Sjkim else if (!svn_ctype_casecmp(key[0], 'S') 233228110Sjkim && !svn_ctype_casecmp(key[1], 'V') 234228110Sjkim && !svn_ctype_casecmp(key[2], 'N')) 235228110Sjkim { 236228110Sjkim /* If we've not yet seen any information about supported POST 237243347Sjkim requests, we'll initialize the list/hash with "create-txn" 238228110Sjkim (which we know is supported by virtue of the server speaking 239228110Sjkim HTTPv2 at all. */ 240228110Sjkim if (! session->supported_posts) 241228110Sjkim { 242228110Sjkim session->supported_posts = apr_hash_make(session->pool); 243228110Sjkim apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1); 244228110Sjkim } 245228110Sjkim 246228110Sjkim if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0) 247243347Sjkim { 248228110Sjkim session->repos_root = session->session_url; 249228110Sjkim session->repos_root.path = 250151937Sjkim (char *)svn_fspath__canonicalize(val, session->pool); 25167754Smsmith session->repos_root_str = 252151937Sjkim svn_urlpath__canonicalize( 253151937Sjkim apr_uri_unparse(session->pool, &session->repos_root, 0), 25467754Smsmith session->pool); 255151937Sjkim } 256151937Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0) 257151937Sjkim { 258151937Sjkim#ifdef SVN_DEBUG 259151937Sjkim char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR); 260151937Sjkim 261151937Sjkim if (!(ignore_v2_env_var 262151937Sjkim && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0)) 263151937Sjkim session->me_resource = apr_pstrdup(session->pool, val); 264151937Sjkim#else 265151937Sjkim session->me_resource = apr_pstrdup(session->pool, val); 266151937Sjkim#endif 267167802Sjkim } 268151937Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0) 26967754Smsmith { 270228110Sjkim session->rev_stub = apr_pstrdup(session->pool, val); 271228110Sjkim } 272228110Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0) 273228110Sjkim { 274228110Sjkim session->rev_root_stub = apr_pstrdup(session->pool, val); 275306536Sjkim } 276228110Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0) 277228110Sjkim { 278228110Sjkim session->txn_stub = apr_pstrdup(session->pool, val); 279228110Sjkim } 280228110Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0) 281228110Sjkim { 282228110Sjkim session->txn_root_stub = apr_pstrdup(session->pool, val); 283228110Sjkim } 284228110Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0) 285228110Sjkim { 286228110Sjkim session->vtxn_stub = apr_pstrdup(session->pool, val); 287228110Sjkim } 288228110Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0) 289306536Sjkim { 290228110Sjkim session->vtxn_root_stub = apr_pstrdup(session->pool, val); 291228110Sjkim } 292228110Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0) 293228110Sjkim { 294228110Sjkim session->uuid = apr_pstrdup(session->pool, val); 295228110Sjkim } 296228110Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0) 297228110Sjkim { 298228110Sjkim opt_ctx->youngest_rev = SVN_STR_TO_REV(val); 299228110Sjkim } 300228110Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0) 301228110Sjkim { 302228110Sjkim session->server_allows_bulk = apr_pstrdup(session->pool, val); 303306536Sjkim } 304228110Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0) 305228110Sjkim { 306228110Sjkim /* May contain multiple values, separated by commas. */ 307228110Sjkim int i; 308228110Sjkim apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE, 309228110Sjkim session->pool); 310228110Sjkim 311228110Sjkim for (i = 0; i < vals->nelts; i++) 312228110Sjkim { 313228110Sjkim const char *post_val = APR_ARRAY_IDX(vals, i, const char *); 314228110Sjkim 315228110Sjkim svn_hash_sets(session->supported_posts, post_val, (void *)1); 316228110Sjkim } 317306536Sjkim } 318228110Sjkim else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0) 319228110Sjkim { 320228110Sjkim if (svn_cstring_casecmp(val, "yes") == 0) 321228110Sjkim { 322306536Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 323306536Sjkim capability_yes); 324228110Sjkim } 325228110Sjkim else if (svn_cstring_casecmp(val, "no") == 0) 326228110Sjkim { 327151937Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 32867754Smsmith capability_no); 329306536Sjkim } 330151937Sjkim } 33167754Smsmith } 332151937Sjkim 33367754Smsmith return 0; 334167802Sjkim} 335306536Sjkim 336151937Sjkim 33767754Smsmith/* A custom serf_response_handler_t which is mostly a wrapper around 338151937Sjkim the expat-based response handler -- it just notices OPTIONS response 339151937Sjkim headers first, before handing off to the xml parser. 340151937Sjkim Implements svn_ra_serf__response_handler_t */ 341151937Sjkimstatic svn_error_t * 342151937Sjkimoptions_response_handler(serf_request_t *request, 343151937Sjkim serf_bucket_t *response, 344151937Sjkim void *baton, 345151937Sjkim apr_pool_t *pool) 346151937Sjkim{ 34767754Smsmith options_context_t *opt_ctx = baton; 348151937Sjkim 349151937Sjkim if (!opt_ctx->headers_processed) 350151937Sjkim { 351151937Sjkim svn_ra_serf__session_t *session = opt_ctx->session; 352151937Sjkim serf_bucket_t *hdrs = serf_bucket_response_get_headers(response); 353151937Sjkim 354151937Sjkim /* Start out assuming all capabilities are unsupported. */ 355151937Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY, 35667754Smsmith capability_no); 357151937Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH, 358151937Sjkim capability_no); 359151937Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 360151937Sjkim NULL); 361151937Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS, 362228110Sjkim capability_no); 363228110Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 36467754Smsmith capability_no); 365151937Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS, 366228110Sjkim capability_no); 367228110Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, 368151937Sjkim capability_no); 369151937Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, 37067754Smsmith capability_no); 371151937Sjkim 372151937Sjkim /* Then see which ones we can discover. */ 373151937Sjkim serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback, 374151937Sjkim opt_ctx); 375167802Sjkim 376151937Sjkim /* Assume mergeinfo capability unsupported, if didn't receive information 377151937Sjkim about server or repository mergeinfo capability. */ 378152069Sjkim if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO)) 379151937Sjkim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 38077424Smsmith capability_no); 381167802Sjkim 382243347Sjkim opt_ctx->headers_processed = TRUE; 383151937Sjkim } 38467754Smsmith 385151937Sjkim /* Execute the 'real' response handler to XML-parse the response body. */ 386151937Sjkim return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool); 387151937Sjkim} 388151937Sjkim 389151937Sjkim 39067754Smsmithstatic svn_error_t * 391151937Sjkimcreate_options_req(options_context_t **opt_ctx, 392151937Sjkim svn_ra_serf__session_t *session, 393151937Sjkim apr_pool_t *pool) 394152069Sjkim{ 395151937Sjkim options_context_t *new_ctx; 396151937Sjkim svn_ra_serf__xml_context_t *xmlctx; 397167802Sjkim svn_ra_serf__handler_t *handler; 398243347Sjkim 399151937Sjkim new_ctx = apr_pcalloc(pool, sizeof(*new_ctx)); 400151937Sjkim new_ctx->pool = pool; 401151937Sjkim new_ctx->session = session; 402151937Sjkim 403151937Sjkim new_ctx->youngest_rev = SVN_INVALID_REVNUM; 404151937Sjkim 405151937Sjkim xmlctx = svn_ra_serf__xml_context_create(options_ttable, 406151937Sjkim NULL, options_closed, NULL, 407151937Sjkim new_ctx, 408250838Sjkim pool); 409151937Sjkim handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); 410151937Sjkim 411151937Sjkim handler->method = "OPTIONS"; 412151937Sjkim handler->path = session->session_url.path; 413151937Sjkim handler->body_delegate = create_options_body; 414151937Sjkim handler->body_type = "text/xml"; 415151937Sjkim 416250838Sjkim new_ctx->handler = handler; 417167802Sjkim 418151937Sjkim new_ctx->inner_handler = handler->response_handler; 419151937Sjkim new_ctx->inner_baton = handler->response_baton; 420151937Sjkim handler->response_handler = options_response_handler; 421151937Sjkim handler->response_baton = new_ctx; 422151937Sjkim 423151937Sjkim *opt_ctx = new_ctx; 424167802Sjkim 425167802Sjkim return SVN_NO_ERROR; 426151937Sjkim} 427151937Sjkim 428151937Sjkim 429151937Sjkimsvn_error_t * 430151937Sjkimsvn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest, 431151937Sjkim svn_ra_serf__session_t *session, 432167802Sjkim apr_pool_t *scratch_pool) 433151937Sjkim{ 434151937Sjkim options_context_t *opt_ctx; 435151937Sjkim 436151937Sjkim SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 437151937Sjkim 43867754Smsmith SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool)); 43967754Smsmith SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); 440151937Sjkim 441151937Sjkim if (opt_ctx->handler->sline.code != 200) 44267754Smsmith return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler)); 443167802Sjkim 44467754Smsmith if (! SVN_IS_VALID_REVNUM(opt_ctx->youngest_rev)) 445306536Sjkim return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 446306536Sjkim _("The OPTIONS response did not include " 44767754Smsmith "the youngest revision")); 44867754Smsmith 44967754Smsmith *youngest = opt_ctx->youngest_rev; 45067754Smsmith 45167754Smsmith return SVN_NO_ERROR; 45267754Smsmith} 45367754Smsmith 454151937Sjkim 45567754Smsmithsvn_error_t * 456151937Sjkimsvn_ra_serf__v1_get_activity_collection(const char **activity_url, 457151937Sjkim svn_ra_serf__session_t *session, 458151937Sjkim apr_pool_t *result_pool, 45967754Smsmith apr_pool_t *scratch_pool) 46077424Smsmith{ 46167754Smsmith options_context_t *opt_ctx; 462151937Sjkim 463151937Sjkim SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 46467754Smsmith 46567754Smsmith if (session->activity_collection_url) 46667754Smsmith { 46767754Smsmith *activity_url = apr_pstrdup(result_pool, 468151937Sjkim session->activity_collection_url); 469151937Sjkim return SVN_NO_ERROR; 470151937Sjkim } 471151937Sjkim 47267754Smsmith SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool)); 473151937Sjkim SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); 474151937Sjkim 475228110Sjkim if (opt_ctx->handler->sline.code != 200) 476151937Sjkim return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler)); 477151937Sjkim 478151937Sjkim /* Cache the result. */ 479151937Sjkim if (opt_ctx->activity_collection) 48067754Smsmith { 48167754Smsmith session->activity_collection_url = 482167802Sjkim apr_pstrdup(session->pool, opt_ctx->activity_collection); 48367754Smsmith } 48477424Smsmith else 485228110Sjkim { 486228110Sjkim return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 487228110Sjkim _("The OPTIONS response did not include the " 488228110Sjkim "requested activity-collection-set value")); 489228110Sjkim } 49067754Smsmith 491151937Sjkim *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection); 492151937Sjkim 49367754Smsmith return SVN_NO_ERROR; 494151937Sjkim 495151937Sjkim} 496151937Sjkim 49767754Smsmith 49867754Smsmith 499151937Sjkim/** Capabilities exchange. */ 500151937Sjkim 50167754Smsmithsvn_error_t * 502306536Sjkimsvn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess, 503167802Sjkim const char **corrected_url, 50467754Smsmith apr_pool_t *result_pool, 505151937Sjkim apr_pool_t *scratch_pool) 50667754Smsmith{ 507151937Sjkim options_context_t *opt_ctx; 50867754Smsmith 509306536Sjkim if (corrected_url) 510151937Sjkim *corrected_url = NULL; 511306536Sjkim 512306536Sjkim /* This routine automatically fills in serf_sess->capabilities */ 513151937Sjkim SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_pool)); 51467754Smsmith 515151937Sjkim opt_ctx->handler->no_fail_on_http_redirect_status = TRUE; 516151937Sjkim 51767754Smsmith SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); 518151937Sjkim 519151937Sjkim /* If our caller cares about server redirections, and our response 520151937Sjkim carries such a thing, report as much. We'll disregard ERR -- 521151937Sjkim it's most likely just a complaint about the response body not 522243347Sjkim successfully parsing as XML or somesuch. */ 523151937Sjkim if (corrected_url && (opt_ctx->handler->sline.code == 301)) 52467754Smsmith { 525151937Sjkim if (!opt_ctx->handler->location || !*opt_ctx->handler->location) 526151937Sjkim { 527151937Sjkim return svn_error_create( 528151937Sjkim SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL, 529243347Sjkim _("Location header not set on redirect response")); 530243347Sjkim } 531151937Sjkim else if (svn_path_is_url(opt_ctx->handler->location)) 53267754Smsmith { 533151937Sjkim *corrected_url = svn_uri_canonicalize(opt_ctx->handler->location, 534151937Sjkim result_pool); 535151937Sjkim } 536151937Sjkim else 537243347Sjkim { 538243347Sjkim /* RFC1945 and RFC2616 state that the Location header's value 539151937Sjkim (from whence this CORRECTED_URL comes), if present, must be an 54067754Smsmith absolute URI. But some Apache versions (those older than 2.2.11, 541228110Sjkim it seems) transmit only the path portion of the URI. 542228110Sjkim See issue #3775 for details. */ 543228110Sjkim 544228110Sjkim apr_uri_t corrected_URI = serf_sess->session_url; 545243347Sjkim 546243347Sjkim corrected_URI.path = (char *)corrected_url; 547228110Sjkim *corrected_url = svn_uri_canonicalize( 548228110Sjkim apr_uri_unparse(scratch_pool, &corrected_URI, 0), 549151937Sjkim result_pool); 55077424Smsmith } 551167802Sjkim 552243347Sjkim return SVN_NO_ERROR; 55367754Smsmith } 554306536Sjkim else if (opt_ctx->handler->sline.code >= 300 555306536Sjkim && opt_ctx->handler->sline.code < 399) 556151937Sjkim { 55767754Smsmith return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL, 558151937Sjkim (opt_ctx->handler->sline.code == 301 55967754Smsmith ? _("Repository moved permanently to '%s'") 560167802Sjkim : _("Repository moved temporarily to '%s'")), 561151937Sjkim opt_ctx->handler->location); 562151937Sjkim } 563151937Sjkim 56467754Smsmith if (opt_ctx->handler->sline.code != 200) 565228110Sjkim return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler)); 566228110Sjkim 567228110Sjkim /* Opportunistically cache any reported activity URL. (We don't 568243347Sjkim want to have to ask for this again later, potentially against an 569228110Sjkim unreadable commit anchor URL.) */ 570228110Sjkim if (opt_ctx->activity_collection) 571228110Sjkim { 572243347Sjkim serf_sess->activity_collection_url = 573228110Sjkim apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection); 574228110Sjkim } 575228110Sjkim 576228110Sjkim return SVN_NO_ERROR; 577228110Sjkim} 578228110Sjkim 579243347Sjkim/* Implements svn_ra_serf__request_body_delegate_t */ 580228110Sjkimstatic svn_error_t * 581306536Sjkimcreate_simple_options_body(serf_bucket_t **body_bkt, 582306536Sjkim void *baton, 583228110Sjkim serf_bucket_alloc_t *alloc, 584228110Sjkim apr_pool_t *pool /* request pool */, 585228110Sjkim apr_pool_t *scratch_pool) 586228110Sjkim{ 587228110Sjkim serf_bucket_t *body; 588228110Sjkim serf_bucket_t *s; 589228110Sjkim 590228110Sjkim body = serf_bucket_aggregate_create(alloc); 591243347Sjkim svn_ra_serf__add_xml_header_buckets(body, alloc); 592228110Sjkim 593228110Sjkim s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc); 594228110Sjkim serf_bucket_aggregate_append(body, s); 595228110Sjkim 596228110Sjkim *body_bkt = body; 597228110Sjkim return SVN_NO_ERROR; 598228110Sjkim} 599228110Sjkim 600228110Sjkim 601228110Sjkimsvn_error_t * 602243347Sjkimsvn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess, 603228110Sjkim apr_pool_t *scratch_pool) 604228110Sjkim{ 605228110Sjkim svn_ra_serf__handler_t *handler; 606228110Sjkim 607228110Sjkim handler = svn_ra_serf__create_handler(serf_sess, scratch_pool); 608228110Sjkim handler->method = "OPTIONS"; 609228110Sjkim handler->path = serf_sess->session_url.path; 610228110Sjkim 611243347Sjkim /* We don't care about the response body, so discard it. */ 612228110Sjkim handler->response_handler = svn_ra_serf__handle_discard_body; 613228110Sjkim 614228110Sjkim /* We need a simple body, in order to send it in chunked format. */ 615228110Sjkim handler->body_delegate = create_simple_options_body; 616228110Sjkim handler->no_fail_on_http_failure_status = TRUE; 617228110Sjkim 618228110Sjkim /* No special headers. */ 619228110Sjkim 620228110Sjkim SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 621228110Sjkim /* Some versions of nginx in reverse proxy mode will return 411. They want 622228110Sjkim a Content-Length header, rather than chunked requests. We can keep other 623151937Sjkim HTTP/1.1 features, but will disable the chunking. */ 62467754Smsmith if (handler->sline.code == 411) 625151937Sjkim { 626151937Sjkim serf_sess->using_chunked_requests = FALSE; 62767754Smsmith 628151937Sjkim return SVN_NO_ERROR; 629151937Sjkim } 630151937Sjkim if (handler->sline.code != 200) 631151937Sjkim SVN_ERR(svn_ra_serf__unexpected_status(handler)); 63277424Smsmith 633151937Sjkim return SVN_NO_ERROR; 634151937Sjkim} 635151937Sjkim 636151937Sjkim 637151937Sjkimsvn_error_t * 638151937Sjkimsvn_ra_serf__has_capability(svn_ra_session_t *ra_session, 63967754Smsmith svn_boolean_t *has, 640228110Sjkim const char *capability, 641228110Sjkim apr_pool_t *pool) 642228110Sjkim{ 643306536Sjkim svn_ra_serf__session_t *serf_sess = ra_session->priv; 644228110Sjkim const char *cap_result; 645228110Sjkim 646228110Sjkim /* This capability doesn't rely on anything server side. */ 647228110Sjkim if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0) 648228110Sjkim { 649228110Sjkim *has = TRUE; 650228110Sjkim return SVN_NO_ERROR; 651228110Sjkim } 652228110Sjkim 653306536Sjkim cap_result = svn_hash_gets(serf_sess->capabilities, capability); 654228110Sjkim 655228110Sjkim /* If any capability is unknown, they're all unknown, so ask. */ 656228110Sjkim if (cap_result == NULL) 657228110Sjkim SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool, pool)); 658228110Sjkim 659228110Sjkim /* Try again, now that we've fetched the capabilities. */ 660228110Sjkim cap_result = svn_hash_gets(serf_sess->capabilities, capability); 661306536Sjkim 662228110Sjkim /* Some capabilities depend on the repository as well as the server. */ 663228110Sjkim if (cap_result == capability_server_yes) 664228110Sjkim { 665228110Sjkim if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0) 666228110Sjkim { 667228110Sjkim /* Handle mergeinfo specially. Mergeinfo depends on the 668228110Sjkim repository as well as the server, but the server routine 669306536Sjkim that answered our svn_ra_serf__exchange_capabilities() call above 670228110Sjkim didn't even know which repository we were interested in 671228110Sjkim -- it just told us whether the server supports mergeinfo. 672228110Sjkim If the answer was 'no', there's no point checking the 673228110Sjkim particular repository; but if it was 'yes', we still must 674151937Sjkim change it to 'no' iff the repository itself doesn't 67567754Smsmith support mergeinfo. */ 676151937Sjkim svn_mergeinfo_catalog_t ignored; 67767754Smsmith svn_error_t *err; 678151937Sjkim apr_array_header_t *paths = apr_array_make(pool, 1, 679151937Sjkim sizeof(char *)); 68067754Smsmith APR_ARRAY_PUSH(paths, const char *) = ""; 681151937Sjkim 682151937Sjkim err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0, 683151937Sjkim svn_mergeinfo_explicit, 684151937Sjkim FALSE /* include_descendants */, 685151937Sjkim pool); 686306536Sjkim 687151937Sjkim if (err) 688151937Sjkim { 68967754Smsmith if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE) 690151937Sjkim { 691151937Sjkim svn_error_clear(err); 692151937Sjkim cap_result = capability_no; 693151937Sjkim } 694151937Sjkim else if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 695151937Sjkim { 696151937Sjkim /* Mergeinfo requests use relative paths, and 697151937Sjkim anyway we're in r0, so this is a likely error, 69867754Smsmith but it means the repository supports mergeinfo! */ 699151937Sjkim svn_error_clear(err); 700151937Sjkim cap_result = capability_yes; 701151937Sjkim } 702151937Sjkim else 703243347Sjkim return svn_error_trace(err); 704167802Sjkim } 705243347Sjkim else 706151937Sjkim cap_result = capability_yes; 70767754Smsmith 708151937Sjkim svn_hash_sets(serf_sess->capabilities, 709151937Sjkim SVN_RA_CAPABILITY_MERGEINFO, cap_result); 710151937Sjkim } 711151937Sjkim else 712306536Sjkim { 713306536Sjkim return svn_error_createf 714151937Sjkim (SVN_ERR_UNKNOWN_CAPABILITY, NULL, 715151937Sjkim _("Don't know how to handle '%s' for capability '%s'"), 716151937Sjkim capability_server_yes, capability); 717151937Sjkim } 718151937Sjkim } 719151937Sjkim 720151937Sjkim if (cap_result == capability_yes) 721151937Sjkim { 722151937Sjkim *has = TRUE; 723151937Sjkim } 724151937Sjkim else if (cap_result == capability_no) 725151937Sjkim { 726151937Sjkim *has = FALSE; 727151937Sjkim } 728151937Sjkim else if (cap_result == NULL) 729151937Sjkim { 730151937Sjkim return svn_error_createf 731151937Sjkim (SVN_ERR_UNKNOWN_CAPABILITY, NULL, 732151937Sjkim _("Don't know anything about capability '%s'"), capability); 733151937Sjkim } 734167802Sjkim else /* "can't happen" */ 735167802Sjkim { 736306536Sjkim /* Well, let's hope it's a string. */ 737151937Sjkim return svn_error_createf 738151937Sjkim (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 739151937Sjkim _("Attempt to fetch capability '%s' resulted in '%s'"), 740151937Sjkim capability, cap_result); 741151937Sjkim } 742151937Sjkim 743167802Sjkim return SVN_NO_ERROR; 744167802Sjkim} 745151937Sjkim