1251881Speter/* 2251881Speter * options.c : entry point for OPTIONS RA functions for ra_serf 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#include <apr_uri.h> 27251881Speter 28251881Speter#include <serf.h> 29251881Speter 30251881Speter#include "svn_dirent_uri.h" 31251881Speter#include "svn_hash.h" 32251881Speter#include "svn_pools.h" 33289180Speter#include "svn_path.h" 34251881Speter#include "svn_ra.h" 35251881Speter#include "svn_dav.h" 36251881Speter#include "svn_xml.h" 37286506Speter#include "svn_ctype.h" 38251881Speter 39251881Speter#include "../libsvn_ra/ra_loader.h" 40251881Speter#include "svn_private_config.h" 41251881Speter#include "private/svn_fspath.h" 42251881Speter 43251881Speter#include "ra_serf.h" 44251881Speter 45251881Speter 46251881Speter/* In a debug build, setting this environment variable to "yes" will force 47251881Speter the client to speak v1, even if the server is capable of speaking v2. */ 48251881Speter#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2" 49251881Speter 50251881Speter 51251881Speter/* 52251881Speter * This enum represents the current state of our XML parsing for an OPTIONS. 53251881Speter */ 54251881Speterenum options_state_e { 55289180Speter INITIAL = XML_STATE_INITIAL, 56251881Speter OPTIONS, 57251881Speter ACTIVITY_COLLECTION, 58251881Speter HREF 59251881Speter}; 60251881Speter 61251881Spetertypedef struct options_context_t { 62251881Speter /* pool to allocate memory from */ 63251881Speter apr_pool_t *pool; 64251881Speter 65251881Speter /* Have we extracted options values from the headers already? */ 66251881Speter svn_boolean_t headers_processed; 67251881Speter 68251881Speter svn_ra_serf__session_t *session; 69251881Speter svn_ra_serf__handler_t *handler; 70251881Speter 71251881Speter svn_ra_serf__response_handler_t inner_handler; 72251881Speter void *inner_baton; 73251881Speter 74362181Sdim /* Have we received any DAV headers at all? */ 75362181Sdim svn_boolean_t received_dav_header; 76362181Sdim 77251881Speter const char *activity_collection; 78251881Speter svn_revnum_t youngest_rev; 79251881Speter 80251881Speter} options_context_t; 81251881Speter 82251881Speter#define D_ "DAV:" 83251881Speter#define S_ SVN_XML_NAMESPACE 84251881Speterstatic const svn_ra_serf__xml_transition_t options_ttable[] = { 85251881Speter { INITIAL, D_, "options-response", OPTIONS, 86251881Speter FALSE, { NULL }, FALSE }, 87251881Speter 88251881Speter { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION, 89251881Speter FALSE, { NULL }, FALSE }, 90251881Speter 91251881Speter { ACTIVITY_COLLECTION, D_, "href", HREF, 92251881Speter TRUE, { NULL }, TRUE }, 93251881Speter 94251881Speter { 0 } 95251881Speter}; 96251881Speter 97251881Speter 98251881Speter/* Conforms to svn_ra_serf__xml_closed_t */ 99251881Speterstatic svn_error_t * 100251881Speteroptions_closed(svn_ra_serf__xml_estate_t *xes, 101251881Speter void *baton, 102251881Speter int leaving_state, 103251881Speter const svn_string_t *cdata, 104251881Speter apr_hash_t *attrs, 105251881Speter apr_pool_t *scratch_pool) 106251881Speter{ 107251881Speter options_context_t *opt_ctx = baton; 108251881Speter 109251881Speter SVN_ERR_ASSERT(leaving_state == HREF); 110251881Speter SVN_ERR_ASSERT(cdata != NULL); 111251881Speter 112251881Speter opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data, 113251881Speter opt_ctx->pool); 114251881Speter 115251881Speter return SVN_NO_ERROR; 116251881Speter} 117251881Speter 118289180Speter/* Implements svn_ra_serf__request_body_delegate_t */ 119251881Speterstatic svn_error_t * 120251881Spetercreate_options_body(serf_bucket_t **body_bkt, 121251881Speter void *baton, 122251881Speter serf_bucket_alloc_t *alloc, 123289180Speter apr_pool_t *pool /* request pool */, 124289180Speter apr_pool_t *scratch_pool) 125251881Speter{ 126251881Speter serf_bucket_t *body; 127251881Speter body = serf_bucket_aggregate_create(alloc); 128251881Speter svn_ra_serf__add_xml_header_buckets(body, alloc); 129251881Speter svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options", 130251881Speter "xmlns:D", "DAV:", 131289180Speter SVN_VA_NULL); 132251881Speter svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc); 133251881Speter svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options"); 134251881Speter 135251881Speter *body_bkt = body; 136251881Speter return SVN_NO_ERROR; 137251881Speter} 138251881Speter 139251881Speter 140251881Speter/* We use these static pointers so we can employ pointer comparison 141251881Speter * of our capabilities hash members instead of strcmp()ing all over 142251881Speter * the place. 143251881Speter */ 144251881Speter/* Both server and repository support the capability. */ 145251881Speterstatic const char *const capability_yes = "yes"; 146251881Speter/* Either server or repository does not support the capability. */ 147251881Speterstatic const char *const capability_no = "no"; 148251881Speter/* Server supports the capability, but don't yet know if repository does. */ 149251881Speterstatic const char *const capability_server_yes = "server-yes"; 150251881Speter 151251881Speter 152251881Speter/* This implements serf_bucket_headers_do_callback_fn_t. 153251881Speter */ 154251881Speterstatic int 155251881Spetercapabilities_headers_iterator_callback(void *baton, 156251881Speter const char *key, 157251881Speter const char *val) 158251881Speter{ 159251881Speter options_context_t *opt_ctx = baton; 160251881Speter svn_ra_serf__session_t *session = opt_ctx->session; 161251881Speter 162251881Speter if (svn_cstring_casecmp(key, "dav") == 0) 163251881Speter { 164251881Speter /* Each header may contain multiple values, separated by commas, e.g.: 165251881Speter DAV: version-control,checkout,working-resource 166251881Speter DAV: merge,baseline,activity,version-controlled-collection 167251881Speter DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */ 168251881Speter apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE, 169251881Speter opt_ctx->pool); 170251881Speter 171362181Sdim opt_ctx->received_dav_header = TRUE; 172362181Sdim 173251881Speter /* Right now we only have a few capabilities to detect, so just 174251881Speter seek for them directly. This could be written slightly more 175251881Speter efficiently, but that wouldn't be worth it until we have many 176251881Speter more capabilities. */ 177251881Speter 178251881Speter if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals)) 179251881Speter { 180251881Speter svn_hash_sets(session->capabilities, 181251881Speter SVN_RA_CAPABILITY_DEPTH, capability_yes); 182251881Speter } 183251881Speter if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals)) 184251881Speter { 185251881Speter /* The server doesn't know what repository we're referring 186251881Speter to, so it can't just say capability_yes. */ 187251881Speter if (!svn_hash_gets(session->capabilities, 188251881Speter SVN_RA_CAPABILITY_MERGEINFO)) 189251881Speter { 190251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 191251881Speter capability_server_yes); 192251881Speter } 193251881Speter } 194251881Speter if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals)) 195251881Speter { 196251881Speter svn_hash_sets(session->capabilities, 197251881Speter SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes); 198251881Speter } 199251881Speter if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals)) 200251881Speter { 201251881Speter svn_hash_sets(session->capabilities, 202251881Speter SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes); 203251881Speter } 204251881Speter if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals)) 205251881Speter { 206251881Speter svn_hash_sets(session->capabilities, 207251881Speter SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes); 208251881Speter } 209251881Speter if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals)) 210251881Speter { 211251881Speter svn_hash_sets(session->capabilities, 212251881Speter SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes); 213251881Speter } 214251881Speter if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS, 215251881Speter vals)) 216251881Speter { 217251881Speter svn_hash_sets(session->capabilities, 218251881Speter SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, 219251881Speter capability_yes); 220251881Speter } 221251881Speter if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals)) 222251881Speter { 223251881Speter svn_hash_sets(session->capabilities, 224251881Speter SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes); 225251881Speter } 226251881Speter if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals)) 227251881Speter { 228251881Speter session->supports_inline_props = TRUE; 229251881Speter } 230251881Speter if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals)) 231251881Speter { 232251881Speter session->supports_rev_rsrc_replay = TRUE; 233251881Speter } 234362181Sdim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF1, vals)) 235362181Sdim { 236362181Sdim /* Use compressed svndiff1 format for servers that properly 237362181Sdim advertise this capability (Subversion 1.10 and greater). */ 238362181Sdim session->supports_svndiff1 = TRUE; 239362181Sdim } 240362181Sdim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LIST, vals)) 241362181Sdim { 242362181Sdim svn_hash_sets(session->capabilities, 243362181Sdim SVN_RA_CAPABILITY_LIST, capability_yes); 244362181Sdim } 245362181Sdim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF2, vals)) 246362181Sdim { 247362181Sdim /* Same for svndiff2. */ 248362181Sdim session->supports_svndiff2 = TRUE; 249362181Sdim } 250362181Sdim if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PUT_RESULT_CHECKSUM, vals)) 251362181Sdim { 252362181Sdim session->supports_put_result_checksum = TRUE; 253362181Sdim } 254251881Speter } 255251881Speter 256251881Speter /* SVN-specific headers -- if present, server supports HTTP protocol v2 */ 257286506Speter else if (!svn_ctype_casecmp(key[0], 'S') 258286506Speter && !svn_ctype_casecmp(key[1], 'V') 259286506Speter && !svn_ctype_casecmp(key[2], 'N')) 260251881Speter { 261251881Speter /* If we've not yet seen any information about supported POST 262251881Speter requests, we'll initialize the list/hash with "create-txn" 263251881Speter (which we know is supported by virtue of the server speaking 264251881Speter HTTPv2 at all. */ 265251881Speter if (! session->supported_posts) 266251881Speter { 267251881Speter session->supported_posts = apr_hash_make(session->pool); 268251881Speter apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1); 269251881Speter } 270251881Speter 271251881Speter if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0) 272251881Speter { 273251881Speter session->repos_root = session->session_url; 274251881Speter session->repos_root.path = 275251881Speter (char *)svn_fspath__canonicalize(val, session->pool); 276251881Speter session->repos_root_str = 277251881Speter svn_urlpath__canonicalize( 278251881Speter apr_uri_unparse(session->pool, &session->repos_root, 0), 279251881Speter session->pool); 280251881Speter } 281251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0) 282251881Speter { 283251881Speter#ifdef SVN_DEBUG 284251881Speter char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR); 285251881Speter 286251881Speter if (!(ignore_v2_env_var 287251881Speter && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0)) 288251881Speter session->me_resource = apr_pstrdup(session->pool, val); 289251881Speter#else 290251881Speter session->me_resource = apr_pstrdup(session->pool, val); 291251881Speter#endif 292251881Speter } 293251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0) 294251881Speter { 295251881Speter session->rev_stub = apr_pstrdup(session->pool, val); 296251881Speter } 297251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0) 298251881Speter { 299251881Speter session->rev_root_stub = apr_pstrdup(session->pool, val); 300251881Speter } 301251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0) 302251881Speter { 303251881Speter session->txn_stub = apr_pstrdup(session->pool, val); 304251881Speter } 305251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0) 306251881Speter { 307251881Speter session->txn_root_stub = apr_pstrdup(session->pool, val); 308251881Speter } 309251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0) 310251881Speter { 311251881Speter session->vtxn_stub = apr_pstrdup(session->pool, val); 312251881Speter } 313251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0) 314251881Speter { 315251881Speter session->vtxn_root_stub = apr_pstrdup(session->pool, val); 316251881Speter } 317251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0) 318251881Speter { 319251881Speter session->uuid = apr_pstrdup(session->pool, val); 320251881Speter } 321251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0) 322251881Speter { 323251881Speter opt_ctx->youngest_rev = SVN_STR_TO_REV(val); 324251881Speter } 325251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0) 326251881Speter { 327251881Speter session->server_allows_bulk = apr_pstrdup(session->pool, val); 328251881Speter } 329251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0) 330251881Speter { 331251881Speter /* May contain multiple values, separated by commas. */ 332251881Speter int i; 333251881Speter apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE, 334269833Speter session->pool); 335251881Speter 336251881Speter for (i = 0; i < vals->nelts; i++) 337251881Speter { 338251881Speter const char *post_val = APR_ARRAY_IDX(vals, i, const char *); 339251881Speter 340251881Speter svn_hash_sets(session->supported_posts, post_val, (void *)1); 341251881Speter } 342251881Speter } 343251881Speter else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0) 344251881Speter { 345251881Speter if (svn_cstring_casecmp(val, "yes") == 0) 346251881Speter { 347251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 348251881Speter capability_yes); 349251881Speter } 350251881Speter else if (svn_cstring_casecmp(val, "no") == 0) 351251881Speter { 352251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 353251881Speter capability_no); 354251881Speter } 355251881Speter } 356251881Speter } 357251881Speter 358251881Speter return 0; 359251881Speter} 360251881Speter 361251881Speter 362251881Speter/* A custom serf_response_handler_t which is mostly a wrapper around 363251881Speter the expat-based response handler -- it just notices OPTIONS response 364251881Speter headers first, before handing off to the xml parser. 365251881Speter Implements svn_ra_serf__response_handler_t */ 366251881Speterstatic svn_error_t * 367251881Speteroptions_response_handler(serf_request_t *request, 368251881Speter serf_bucket_t *response, 369251881Speter void *baton, 370251881Speter apr_pool_t *pool) 371251881Speter{ 372251881Speter options_context_t *opt_ctx = baton; 373251881Speter 374251881Speter if (!opt_ctx->headers_processed) 375251881Speter { 376251881Speter svn_ra_serf__session_t *session = opt_ctx->session; 377251881Speter serf_bucket_t *hdrs = serf_bucket_response_get_headers(response); 378362181Sdim serf_connection_t *conn; 379251881Speter 380251881Speter /* Start out assuming all capabilities are unsupported. */ 381251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY, 382251881Speter capability_no); 383251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH, 384251881Speter capability_no); 385251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 386251881Speter NULL); 387251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS, 388251881Speter capability_no); 389251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 390251881Speter capability_no); 391251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS, 392251881Speter capability_no); 393251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, 394251881Speter capability_no); 395253734Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, 396253734Speter capability_no); 397362181Sdim svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LIST, 398362181Sdim capability_no); 399251881Speter 400251881Speter /* Then see which ones we can discover. */ 401251881Speter serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback, 402251881Speter opt_ctx); 403251881Speter 404362181Sdim /* Bail out early if we're not talking to a DAV server. 405362181Sdim Note that this check is only valid if we've received a success 406362181Sdim response; redirects and errors don't count. */ 407362181Sdim if (opt_ctx->handler->sline.code >= 200 408362181Sdim && opt_ctx->handler->sline.code < 300 409362181Sdim && !opt_ctx->received_dav_header) 410362181Sdim { 411362181Sdim return svn_error_createf 412362181Sdim (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 413362181Sdim _("The server at '%s' does not support the HTTP/DAV protocol"), 414362181Sdim session->session_url_str); 415362181Sdim } 416362181Sdim 417289180Speter /* Assume mergeinfo capability unsupported, if didn't receive information 418251881Speter about server or repository mergeinfo capability. */ 419251881Speter if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO)) 420251881Speter svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, 421251881Speter capability_no); 422251881Speter 423362181Sdim /* Remember our latency. */ 424362181Sdim conn = serf_request_get_conn(request); 425362181Sdim session->conn_latency = serf_connection_get_latency(conn); 426362181Sdim 427251881Speter opt_ctx->headers_processed = TRUE; 428251881Speter } 429251881Speter 430251881Speter /* Execute the 'real' response handler to XML-parse the response body. */ 431251881Speter return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool); 432251881Speter} 433251881Speter 434251881Speter 435251881Speterstatic svn_error_t * 436251881Spetercreate_options_req(options_context_t **opt_ctx, 437251881Speter svn_ra_serf__session_t *session, 438251881Speter apr_pool_t *pool) 439251881Speter{ 440251881Speter options_context_t *new_ctx; 441251881Speter svn_ra_serf__xml_context_t *xmlctx; 442251881Speter svn_ra_serf__handler_t *handler; 443251881Speter 444251881Speter new_ctx = apr_pcalloc(pool, sizeof(*new_ctx)); 445251881Speter new_ctx->pool = pool; 446251881Speter new_ctx->session = session; 447251881Speter 448251881Speter new_ctx->youngest_rev = SVN_INVALID_REVNUM; 449251881Speter 450251881Speter xmlctx = svn_ra_serf__xml_context_create(options_ttable, 451251881Speter NULL, options_closed, NULL, 452251881Speter new_ctx, 453251881Speter pool); 454289180Speter handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); 455251881Speter 456251881Speter handler->method = "OPTIONS"; 457251881Speter handler->path = session->session_url.path; 458251881Speter handler->body_delegate = create_options_body; 459251881Speter handler->body_type = "text/xml"; 460251881Speter 461251881Speter new_ctx->handler = handler; 462251881Speter 463251881Speter new_ctx->inner_handler = handler->response_handler; 464251881Speter new_ctx->inner_baton = handler->response_baton; 465251881Speter handler->response_handler = options_response_handler; 466251881Speter handler->response_baton = new_ctx; 467251881Speter 468251881Speter *opt_ctx = new_ctx; 469251881Speter 470251881Speter return SVN_NO_ERROR; 471251881Speter} 472251881Speter 473251881Speter 474251881Spetersvn_error_t * 475251881Spetersvn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest, 476289180Speter svn_ra_serf__session_t *session, 477251881Speter apr_pool_t *scratch_pool) 478251881Speter{ 479251881Speter options_context_t *opt_ctx; 480251881Speter 481251881Speter SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 482251881Speter 483289180Speter SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool)); 484251881Speter SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); 485251881Speter 486289180Speter if (opt_ctx->handler->sline.code != 200) 487289180Speter return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler)); 488289180Speter 489289180Speter if (! SVN_IS_VALID_REVNUM(opt_ctx->youngest_rev)) 490289180Speter return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 491289180Speter _("The OPTIONS response did not include " 492289180Speter "the youngest revision")); 493289180Speter 494251881Speter *youngest = opt_ctx->youngest_rev; 495251881Speter 496251881Speter return SVN_NO_ERROR; 497251881Speter} 498251881Speter 499251881Speter 500251881Spetersvn_error_t * 501251881Spetersvn_ra_serf__v1_get_activity_collection(const char **activity_url, 502289180Speter svn_ra_serf__session_t *session, 503251881Speter apr_pool_t *result_pool, 504251881Speter apr_pool_t *scratch_pool) 505251881Speter{ 506251881Speter options_context_t *opt_ctx; 507251881Speter 508251881Speter SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 509251881Speter 510289180Speter if (session->activity_collection_url) 511289180Speter { 512289180Speter *activity_url = apr_pstrdup(result_pool, 513289180Speter session->activity_collection_url); 514289180Speter return SVN_NO_ERROR; 515289180Speter } 516289180Speter 517289180Speter SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool)); 518251881Speter SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); 519251881Speter 520289180Speter if (opt_ctx->handler->sline.code != 200) 521289180Speter return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler)); 522251881Speter 523289180Speter /* Cache the result. */ 524289180Speter if (opt_ctx->activity_collection) 525289180Speter { 526289180Speter session->activity_collection_url = 527289180Speter apr_pstrdup(session->pool, opt_ctx->activity_collection); 528289180Speter } 529289180Speter else 530289180Speter { 531289180Speter return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 532289180Speter _("The OPTIONS response did not include the " 533289180Speter "requested activity-collection-set value")); 534289180Speter } 535289180Speter 536251881Speter *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection); 537251881Speter 538251881Speter return SVN_NO_ERROR; 539251881Speter 540251881Speter} 541251881Speter 542251881Speter 543251881Speter 544251881Speter/** Capabilities exchange. */ 545251881Speter 546251881Spetersvn_error_t * 547251881Spetersvn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess, 548251881Speter const char **corrected_url, 549362181Sdim const char **redirect_url, 550289180Speter apr_pool_t *result_pool, 551289180Speter apr_pool_t *scratch_pool) 552251881Speter{ 553251881Speter options_context_t *opt_ctx; 554251881Speter 555289180Speter if (corrected_url) 556289180Speter *corrected_url = NULL; 557362181Sdim if (redirect_url) 558362181Sdim *redirect_url = NULL; 559289180Speter 560251881Speter /* This routine automatically fills in serf_sess->capabilities */ 561289180Speter SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_pool)); 562251881Speter 563289180Speter opt_ctx->handler->no_fail_on_http_redirect_status = TRUE; 564251881Speter 565289180Speter SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); 566289180Speter 567251881Speter /* If our caller cares about server redirections, and our response 568251881Speter carries such a thing, report as much. We'll disregard ERR -- 569251881Speter it's most likely just a complaint about the response body not 570251881Speter successfully parsing as XML or somesuch. */ 571251881Speter if (corrected_url && (opt_ctx->handler->sline.code == 301)) 572251881Speter { 573289180Speter if (!opt_ctx->handler->location || !*opt_ctx->handler->location) 574289180Speter { 575289180Speter return svn_error_create( 576289180Speter SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL, 577289180Speter _("Location header not set on redirect response")); 578289180Speter } 579289180Speter else if (svn_path_is_url(opt_ctx->handler->location)) 580289180Speter { 581362181Sdim SVN_ERR(svn_uri_canonicalize_safe(corrected_url, NULL, 582362181Sdim opt_ctx->handler->location, result_pool, scratch_pool)); 583362181Sdim if (redirect_url) 584362181Sdim *redirect_url = apr_pstrdup(result_pool, 585362181Sdim opt_ctx->handler->location); 586289180Speter } 587289180Speter else 588289180Speter { 589289180Speter /* RFC1945 and RFC2616 state that the Location header's value 590289180Speter (from whence this CORRECTED_URL comes), if present, must be an 591289180Speter absolute URI. But some Apache versions (those older than 2.2.11, 592289180Speter it seems) transmit only the path portion of the URI. 593289180Speter See issue #3775 for details. */ 594289180Speter 595289180Speter apr_uri_t corrected_URI = serf_sess->session_url; 596362181Sdim char *absolute_uri; 597289180Speter 598289180Speter corrected_URI.path = (char *)corrected_url; 599362181Sdim absolute_uri = apr_uri_unparse(scratch_pool, &corrected_URI, 0); 600362181Sdim SVN_ERR(svn_uri_canonicalize_safe(corrected_url, NULL, 601362181Sdim absolute_uri, result_pool, scratch_pool)); 602362181Sdim if (redirect_url) 603362181Sdim *redirect_url = apr_pstrdup(result_pool, absolute_uri); 604289180Speter } 605289180Speter 606251881Speter return SVN_NO_ERROR; 607251881Speter } 608289180Speter else if (opt_ctx->handler->sline.code >= 300 609289180Speter && opt_ctx->handler->sline.code < 399) 610289180Speter { 611289180Speter return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL, 612289180Speter (opt_ctx->handler->sline.code == 301 613289180Speter ? _("Repository moved permanently to '%s'") 614289180Speter : _("Repository moved temporarily to '%s'")), 615289180Speter opt_ctx->handler->location); 616289180Speter } 617251881Speter 618289180Speter if (opt_ctx->handler->sline.code != 200) 619289180Speter return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler)); 620251881Speter 621251881Speter /* Opportunistically cache any reported activity URL. (We don't 622251881Speter want to have to ask for this again later, potentially against an 623251881Speter unreadable commit anchor URL.) */ 624251881Speter if (opt_ctx->activity_collection) 625251881Speter { 626251881Speter serf_sess->activity_collection_url = 627251881Speter apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection); 628251881Speter } 629251881Speter 630251881Speter return SVN_NO_ERROR; 631251881Speter} 632251881Speter 633289180Speter/* Implements svn_ra_serf__request_body_delegate_t */ 634253734Speterstatic svn_error_t * 635253734Spetercreate_simple_options_body(serf_bucket_t **body_bkt, 636253734Speter void *baton, 637253734Speter serf_bucket_alloc_t *alloc, 638289180Speter apr_pool_t *pool /* request pool */, 639289180Speter apr_pool_t *scratch_pool) 640253734Speter{ 641253734Speter serf_bucket_t *body; 642253734Speter serf_bucket_t *s; 643253734Speter 644253734Speter body = serf_bucket_aggregate_create(alloc); 645253734Speter svn_ra_serf__add_xml_header_buckets(body, alloc); 646253734Speter 647253734Speter s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc); 648253734Speter serf_bucket_aggregate_append(body, s); 649253734Speter 650253734Speter *body_bkt = body; 651253734Speter return SVN_NO_ERROR; 652253734Speter} 653253734Speter 654253734Speter 655251881Spetersvn_error_t * 656253734Spetersvn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess, 657253734Speter apr_pool_t *scratch_pool) 658253734Speter{ 659253734Speter svn_ra_serf__handler_t *handler; 660253734Speter 661289180Speter handler = svn_ra_serf__create_handler(serf_sess, scratch_pool); 662253734Speter handler->method = "OPTIONS"; 663253734Speter handler->path = serf_sess->session_url.path; 664253734Speter 665253734Speter /* We don't care about the response body, so discard it. */ 666253734Speter handler->response_handler = svn_ra_serf__handle_discard_body; 667253734Speter 668253734Speter /* We need a simple body, in order to send it in chunked format. */ 669253734Speter handler->body_delegate = create_simple_options_body; 670289180Speter handler->no_fail_on_http_failure_status = TRUE; 671253734Speter 672253734Speter /* No special headers. */ 673253734Speter 674253734Speter SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 675253734Speter /* Some versions of nginx in reverse proxy mode will return 411. They want 676253734Speter a Content-Length header, rather than chunked requests. We can keep other 677253734Speter HTTP/1.1 features, but will disable the chunking. */ 678253734Speter if (handler->sline.code == 411) 679253734Speter { 680253734Speter serf_sess->using_chunked_requests = FALSE; 681253734Speter 682253734Speter return SVN_NO_ERROR; 683253734Speter } 684289180Speter if (handler->sline.code != 200) 685289180Speter SVN_ERR(svn_ra_serf__unexpected_status(handler)); 686253734Speter 687253734Speter return SVN_NO_ERROR; 688253734Speter} 689253734Speter 690253734Speter 691253734Spetersvn_error_t * 692251881Spetersvn_ra_serf__has_capability(svn_ra_session_t *ra_session, 693251881Speter svn_boolean_t *has, 694251881Speter const char *capability, 695251881Speter apr_pool_t *pool) 696251881Speter{ 697251881Speter svn_ra_serf__session_t *serf_sess = ra_session->priv; 698251881Speter const char *cap_result; 699251881Speter 700251881Speter /* This capability doesn't rely on anything server side. */ 701251881Speter if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0) 702251881Speter { 703251881Speter *has = TRUE; 704251881Speter return SVN_NO_ERROR; 705251881Speter } 706251881Speter 707251881Speter cap_result = svn_hash_gets(serf_sess->capabilities, capability); 708251881Speter 709251881Speter /* If any capability is unknown, they're all unknown, so ask. */ 710251881Speter if (cap_result == NULL) 711362181Sdim SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, NULL, 712362181Sdim pool, pool)); 713251881Speter 714251881Speter /* Try again, now that we've fetched the capabilities. */ 715251881Speter cap_result = svn_hash_gets(serf_sess->capabilities, capability); 716251881Speter 717251881Speter /* Some capabilities depend on the repository as well as the server. */ 718251881Speter if (cap_result == capability_server_yes) 719251881Speter { 720251881Speter if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0) 721251881Speter { 722251881Speter /* Handle mergeinfo specially. Mergeinfo depends on the 723251881Speter repository as well as the server, but the server routine 724251881Speter that answered our svn_ra_serf__exchange_capabilities() call above 725251881Speter didn't even know which repository we were interested in 726251881Speter -- it just told us whether the server supports mergeinfo. 727251881Speter If the answer was 'no', there's no point checking the 728251881Speter particular repository; but if it was 'yes', we still must 729251881Speter change it to 'no' iff the repository itself doesn't 730251881Speter support mergeinfo. */ 731251881Speter svn_mergeinfo_catalog_t ignored; 732251881Speter svn_error_t *err; 733251881Speter apr_array_header_t *paths = apr_array_make(pool, 1, 734251881Speter sizeof(char *)); 735251881Speter APR_ARRAY_PUSH(paths, const char *) = ""; 736251881Speter 737251881Speter err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0, 738289180Speter svn_mergeinfo_explicit, 739289180Speter FALSE /* include_descendants */, 740289180Speter pool); 741251881Speter 742251881Speter if (err) 743251881Speter { 744251881Speter if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE) 745251881Speter { 746251881Speter svn_error_clear(err); 747251881Speter cap_result = capability_no; 748251881Speter } 749251881Speter else if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 750251881Speter { 751251881Speter /* Mergeinfo requests use relative paths, and 752251881Speter anyway we're in r0, so this is a likely error, 753251881Speter but it means the repository supports mergeinfo! */ 754251881Speter svn_error_clear(err); 755251881Speter cap_result = capability_yes; 756251881Speter } 757251881Speter else 758289180Speter return svn_error_trace(err); 759251881Speter } 760251881Speter else 761251881Speter cap_result = capability_yes; 762251881Speter 763251881Speter svn_hash_sets(serf_sess->capabilities, 764251881Speter SVN_RA_CAPABILITY_MERGEINFO, cap_result); 765251881Speter } 766251881Speter else 767251881Speter { 768251881Speter return svn_error_createf 769251881Speter (SVN_ERR_UNKNOWN_CAPABILITY, NULL, 770251881Speter _("Don't know how to handle '%s' for capability '%s'"), 771251881Speter capability_server_yes, capability); 772251881Speter } 773251881Speter } 774251881Speter 775251881Speter if (cap_result == capability_yes) 776251881Speter { 777251881Speter *has = TRUE; 778251881Speter } 779251881Speter else if (cap_result == capability_no) 780251881Speter { 781251881Speter *has = FALSE; 782251881Speter } 783251881Speter else if (cap_result == NULL) 784251881Speter { 785251881Speter return svn_error_createf 786251881Speter (SVN_ERR_UNKNOWN_CAPABILITY, NULL, 787251881Speter _("Don't know anything about capability '%s'"), capability); 788251881Speter } 789251881Speter else /* "can't happen" */ 790251881Speter { 791251881Speter /* Well, let's hope it's a string. */ 792251881Speter return svn_error_createf 793251881Speter (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 794251881Speter _("Attempt to fetch capability '%s' resulted in '%s'"), 795251881Speter capability, cap_result); 796251881Speter } 797251881Speter 798251881Speter return SVN_NO_ERROR; 799251881Speter} 800