serf.c revision 289180
1/* 2 * serf.c : entry point 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#define APR_WANT_STRFUNC 27#include <apr_want.h> 28 29#include <apr_uri.h> 30#include <serf.h> 31 32#include "svn_pools.h" 33#include "svn_ra.h" 34#include "svn_dav.h" 35#include "svn_xml.h" 36#include "../libsvn_ra/ra_loader.h" 37#include "svn_config.h" 38#include "svn_delta.h" 39#include "svn_dirent_uri.h" 40#include "svn_hash.h" 41#include "svn_path.h" 42#include "svn_props.h" 43#include "svn_time.h" 44#include "svn_version.h" 45 46#include "private/svn_dav_protocol.h" 47#include "private/svn_dep_compat.h" 48#include "private/svn_fspath.h" 49#include "private/svn_subr_private.h" 50#include "svn_private_config.h" 51 52#include "ra_serf.h" 53 54 55/* Implements svn_ra__vtable_t.get_version(). */ 56static const svn_version_t * 57ra_serf_version(void) 58{ 59 SVN_VERSION_BODY; 60} 61 62#define RA_SERF_DESCRIPTION \ 63 N_("Module for accessing a repository via WebDAV protocol using serf.") 64 65#define RA_SERF_DESCRIPTION_VER \ 66 N_("Module for accessing a repository via WebDAV protocol using serf.\n" \ 67 " - using serf %d.%d.%d") 68 69/* Implements svn_ra__vtable_t.get_description(). */ 70static const char * 71ra_serf_get_description(apr_pool_t *pool) 72{ 73 int major, minor, patch; 74 75 serf_lib_version(&major, &minor, &patch); 76 return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), major, minor, patch); 77} 78 79/* Implements svn_ra__vtable_t.get_schemes(). */ 80static const char * const * 81ra_serf_get_schemes(apr_pool_t *pool) 82{ 83 static const char *serf_ssl[] = { "http", "https", NULL }; 84#if 0 85 /* ### Temporary: to shut up a warning. */ 86 static const char *serf_no_ssl[] = { "http", NULL }; 87#endif 88 89 /* TODO: Runtime detection. */ 90 return serf_ssl; 91} 92 93/* Load the setting http-auth-types from the global or server specific 94 section, parse its value and set the types of authentication we should 95 accept from the server. */ 96static svn_error_t * 97load_http_auth_types(apr_pool_t *pool, svn_config_t *config, 98 const char *server_group, 99 int *authn_types) 100{ 101 const char *http_auth_types = NULL; 102 *authn_types = SERF_AUTHN_NONE; 103 104 svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL, 105 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL); 106 107 if (server_group) 108 { 109 svn_config_get(config, &http_auth_types, server_group, 110 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types); 111 } 112 113 if (http_auth_types) 114 { 115 char *token; 116 char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1); 117 apr_collapse_spaces(auth_types_list, http_auth_types); 118 while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL) 119 { 120 if (svn_cstring_casecmp("basic", token) == 0) 121 *authn_types |= SERF_AUTHN_BASIC; 122 else if (svn_cstring_casecmp("digest", token) == 0) 123 *authn_types |= SERF_AUTHN_DIGEST; 124 else if (svn_cstring_casecmp("ntlm", token) == 0) 125 *authn_types |= SERF_AUTHN_NTLM; 126 else if (svn_cstring_casecmp("negotiate", token) == 0) 127 *authn_types |= SERF_AUTHN_NEGOTIATE; 128 else 129 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, 130 _("Invalid config: unknown %s " 131 "'%s'"), 132 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token); 133 } 134 } 135 else 136 { 137 /* Nothing specified by the user, so accept all types. */ 138 *authn_types = SERF_AUTHN_ALL; 139 } 140 141 return SVN_NO_ERROR; 142} 143 144/* Default HTTP timeout (in seconds); overridden by the 'http-timeout' 145 runtime configuration variable. */ 146#define DEFAULT_HTTP_TIMEOUT 600 147 148static svn_error_t * 149load_config(svn_ra_serf__session_t *session, 150 apr_hash_t *config_hash, 151 apr_pool_t *pool) 152{ 153 svn_config_t *config, *config_client; 154 const char *server_group; 155 const char *proxy_host = NULL; 156 const char *port_str = NULL; 157 const char *timeout_str = NULL; 158 const char *exceptions; 159 apr_port_t proxy_port; 160 svn_tristate_t chunked_requests; 161#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) 162 apr_int64_t log_components; 163 apr_int64_t log_level; 164#endif 165 166 if (config_hash) 167 { 168 config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS); 169 config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG); 170 } 171 else 172 { 173 config = NULL; 174 config_client = NULL; 175 } 176 177 SVN_ERR(svn_config_get_bool(config, &session->using_compression, 178 SVN_CONFIG_SECTION_GLOBAL, 179 SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE)); 180 svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL, 181 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL); 182 183 if (session->auth_baton) 184 { 185 if (config_client) 186 { 187 svn_auth_set_parameter(session->auth_baton, 188 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, 189 config_client); 190 } 191 if (config) 192 { 193 svn_auth_set_parameter(session->auth_baton, 194 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, 195 config); 196 } 197 } 198 199 /* Use the default proxy-specific settings if and only if 200 "http-proxy-exceptions" is not set to exclude this host. */ 201 svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL, 202 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, ""); 203 if (! svn_cstring_match_glob_list(session->session_url.hostname, 204 svn_cstring_split(exceptions, ",", 205 TRUE, pool))) 206 { 207 svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL, 208 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL); 209 svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL, 210 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL); 211 svn_config_get(config, &session->proxy_username, 212 SVN_CONFIG_SECTION_GLOBAL, 213 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL); 214 svn_config_get(config, &session->proxy_password, 215 SVN_CONFIG_SECTION_GLOBAL, 216 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL); 217 } 218 219 /* Load the global ssl settings, if set. */ 220 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca, 221 SVN_CONFIG_SECTION_GLOBAL, 222 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA, 223 TRUE)); 224 svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL, 225 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL); 226 227 /* If set, read the flag that tells us to do bulk updates or not. Defaults 228 to skelta updates. */ 229 SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates, 230 SVN_CONFIG_SECTION_GLOBAL, 231 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, 232 "auto", 233 svn_tristate_unknown)); 234 235 /* Load the maximum number of parallel session connections. */ 236 SVN_ERR(svn_config_get_int64(config, &session->max_connections, 237 SVN_CONFIG_SECTION_GLOBAL, 238 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 239 SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS)); 240 241 /* Should we use chunked transfer encoding. */ 242 SVN_ERR(svn_config_get_tristate(config, &chunked_requests, 243 SVN_CONFIG_SECTION_GLOBAL, 244 SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS, 245 "auto", svn_tristate_unknown)); 246 247#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) 248 SVN_ERR(svn_config_get_int64(config, &log_components, 249 SVN_CONFIG_SECTION_GLOBAL, 250 SVN_CONFIG_OPTION_SERF_LOG_COMPONENTS, 251 SERF_LOGCOMP_NONE)); 252 SVN_ERR(svn_config_get_int64(config, &log_level, 253 SVN_CONFIG_SECTION_GLOBAL, 254 SVN_CONFIG_OPTION_SERF_LOG_LEVEL, 255 SERF_LOG_INFO)); 256#endif 257 258 server_group = svn_auth_get_parameter(session->auth_baton, 259 SVN_AUTH_PARAM_SERVER_GROUP); 260 261 if (server_group) 262 { 263 SVN_ERR(svn_config_get_bool(config, &session->using_compression, 264 server_group, 265 SVN_CONFIG_OPTION_HTTP_COMPRESSION, 266 session->using_compression)); 267 svn_config_get(config, &timeout_str, server_group, 268 SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str); 269 270 /* Load the group proxy server settings, overriding global 271 settings. We intentionally ignore 'http-proxy-exceptions' 272 here because, well, if this site was an exception, why is 273 there a per-server proxy configuration for it? */ 274 svn_config_get(config, &proxy_host, server_group, 275 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host); 276 svn_config_get(config, &port_str, server_group, 277 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str); 278 svn_config_get(config, &session->proxy_username, server_group, 279 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, 280 session->proxy_username); 281 svn_config_get(config, &session->proxy_password, server_group, 282 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, 283 session->proxy_password); 284 285 /* Load the group ssl settings. */ 286 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca, 287 server_group, 288 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA, 289 session->trust_default_ca)); 290 svn_config_get(config, &session->ssl_authorities, server_group, 291 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, 292 session->ssl_authorities); 293 294 /* Load the group bulk updates flag. */ 295 SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates, 296 server_group, 297 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, 298 "auto", 299 session->bulk_updates)); 300 301 /* Load the maximum number of parallel session connections, 302 overriding global values. */ 303 SVN_ERR(svn_config_get_int64(config, &session->max_connections, 304 server_group, 305 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 306 session->max_connections)); 307 308 /* Should we use chunked transfer encoding. */ 309 SVN_ERR(svn_config_get_tristate(config, &chunked_requests, 310 server_group, 311 SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS, 312 "auto", chunked_requests)); 313 314#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) 315 SVN_ERR(svn_config_get_int64(config, &log_components, 316 server_group, 317 SVN_CONFIG_OPTION_SERF_LOG_COMPONENTS, 318 log_components)); 319 SVN_ERR(svn_config_get_int64(config, &log_level, 320 server_group, 321 SVN_CONFIG_OPTION_SERF_LOG_LEVEL, 322 log_level)); 323#endif 324 } 325 326#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) 327 if (log_components != SERF_LOGCOMP_NONE) 328 { 329 serf_log_output_t *output; 330 apr_status_t status; 331 332 status = serf_logging_create_stream_output(&output, 333 session->context, 334 (apr_uint32_t)log_level, 335 (apr_uint32_t)log_components, 336 SERF_LOG_DEFAULT_LAYOUT, 337 stderr, 338 pool); 339 340 if (!status) 341 serf_logging_add_output(session->context, output); 342 } 343#endif 344 345 /* Don't allow the http-max-connections value to be larger than our 346 compiled-in limit, or to be too small to operate. Broken 347 functionality and angry administrators are equally undesirable. */ 348 if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT) 349 session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT; 350 if (session->max_connections < 2) 351 session->max_connections = 2; 352 353 /* Parse the connection timeout value, if any. */ 354 session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT); 355 if (timeout_str) 356 { 357 char *endstr; 358 const long int timeout = strtol(timeout_str, &endstr, 10); 359 360 if (*endstr) 361 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL, 362 _("Invalid config: illegal character in " 363 "timeout value")); 364 if (timeout < 0) 365 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL, 366 _("Invalid config: negative timeout value")); 367 session->timeout = apr_time_from_sec(timeout); 368 } 369 SVN_ERR_ASSERT(session->timeout >= 0); 370 371 /* Convert the proxy port value, if any. */ 372 if (port_str) 373 { 374 char *endstr; 375 const long int port = strtol(port_str, &endstr, 10); 376 377 if (*endstr) 378 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 379 _("Invalid URL: illegal character in proxy " 380 "port number")); 381 if (port < 0) 382 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 383 _("Invalid URL: negative proxy port number")); 384 if (port > 65535) 385 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 386 _("Invalid URL: proxy port number greater " 387 "than maximum TCP port number 65535")); 388 proxy_port = (apr_port_t) port; 389 } 390 else 391 { 392 proxy_port = 80; 393 } 394 395 if (proxy_host) 396 { 397 apr_sockaddr_t *proxy_addr; 398 apr_status_t status; 399 400 status = apr_sockaddr_info_get(&proxy_addr, proxy_host, 401 APR_UNSPEC, proxy_port, 0, 402 session->pool); 403 if (status) 404 { 405 return svn_ra_serf__wrap_err( 406 status, _("Could not resolve proxy server '%s'"), 407 proxy_host); 408 } 409 session->using_proxy = TRUE; 410 serf_config_proxy(session->context, proxy_addr); 411 } 412 else 413 { 414 session->using_proxy = FALSE; 415 } 416 417 /* Setup detect_chunking and using_chunked_requests based on 418 * the chunked_requests tristate */ 419 if (chunked_requests == svn_tristate_unknown) 420 { 421 session->detect_chunking = TRUE; 422 session->using_chunked_requests = TRUE; 423 } 424 else if (chunked_requests == svn_tristate_true) 425 { 426 session->detect_chunking = FALSE; 427 session->using_chunked_requests = TRUE; 428 } 429 else /* chunked_requests == svn_tristate_false */ 430 { 431 session->detect_chunking = FALSE; 432 session->using_chunked_requests = FALSE; 433 } 434 435 /* Setup authentication. */ 436 SVN_ERR(load_http_auth_types(pool, config, server_group, 437 &session->authn_types)); 438 serf_config_authn_types(session->context, session->authn_types); 439 serf_config_credentials_callback(session->context, 440 svn_ra_serf__credentials_callback); 441 442 return SVN_NO_ERROR; 443} 444#undef DEFAULT_HTTP_TIMEOUT 445 446static void 447svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written) 448{ 449 const svn_ra_serf__session_t *serf_sess = progress_baton; 450 if (serf_sess->progress_func) 451 { 452 serf_sess->progress_func(read + written, -1, 453 serf_sess->progress_baton, 454 serf_sess->pool); 455 } 456} 457 458/** Our User-Agent string. */ 459static const char * 460get_user_agent_string(apr_pool_t *pool) 461{ 462 int major, minor, patch; 463 serf_lib_version(&major, &minor, &patch); 464 465 return apr_psprintf(pool, "SVN/%s (%s) serf/%d.%d.%d", 466 SVN_VER_NUMBER, SVN_BUILD_TARGET, 467 major, minor, patch); 468} 469 470/* Implements svn_ra__vtable_t.open_session(). */ 471static svn_error_t * 472svn_ra_serf__open(svn_ra_session_t *session, 473 const char **corrected_url, 474 const char *session_URL, 475 const svn_ra_callbacks2_t *callbacks, 476 void *callback_baton, 477 svn_auth_baton_t *auth_baton, 478 apr_hash_t *config, 479 apr_pool_t *result_pool, 480 apr_pool_t *scratch_pool) 481{ 482 apr_status_t status; 483 svn_ra_serf__session_t *serf_sess; 484 apr_uri_t url; 485 const char *client_string = NULL; 486 svn_error_t *err; 487 488 if (corrected_url) 489 *corrected_url = NULL; 490 491 serf_sess = apr_pcalloc(result_pool, sizeof(*serf_sess)); 492 serf_sess->pool = result_pool; 493 if (config) 494 SVN_ERR(svn_config_copy_config(&serf_sess->config, config, result_pool)); 495 else 496 serf_sess->config = NULL; 497 serf_sess->wc_callbacks = callbacks; 498 serf_sess->wc_callback_baton = callback_baton; 499 serf_sess->auth_baton = auth_baton; 500 serf_sess->progress_func = callbacks->progress_func; 501 serf_sess->progress_baton = callbacks->progress_baton; 502 serf_sess->cancel_func = callbacks->cancel_func; 503 serf_sess->cancel_baton = callback_baton; 504 505 /* todo: reuse serf context across sessions */ 506 serf_sess->context = serf_context_create(serf_sess->pool); 507 508 SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache, 509 serf_sess->pool)); 510 511 512 status = apr_uri_parse(serf_sess->pool, session_URL, &url); 513 if (status) 514 { 515 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 516 _("Illegal URL '%s'"), 517 session_URL); 518 } 519 /* Depending the version of apr-util in use, for root paths url.path 520 will be NULL or "", where serf requires "/". */ 521 if (url.path == NULL || url.path[0] == '\0') 522 { 523 url.path = apr_pstrdup(serf_sess->pool, "/"); 524 } 525 if (!url.port) 526 { 527 url.port = apr_uri_port_of_scheme(url.scheme); 528 } 529 serf_sess->session_url = url; 530 serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL); 531 serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0); 532 533 serf_sess->supports_deadprop_count = svn_tristate_unknown; 534 535 serf_sess->capabilities = apr_hash_make(serf_sess->pool); 536 537 /* We have to assume that the server only supports HTTP/1.0. Once it's clear 538 HTTP/1.1 is supported, we can upgrade. */ 539 serf_sess->http10 = TRUE; 540 541 /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable 542 this, if we find an intervening proxy does not support chunked requests. */ 543 serf_sess->using_chunked_requests = TRUE; 544 545 SVN_ERR(load_config(serf_sess, config, serf_sess->pool)); 546 547 serf_sess->conns[0] = apr_pcalloc(serf_sess->pool, 548 sizeof(*serf_sess->conns[0])); 549 serf_sess->conns[0]->bkt_alloc = 550 serf_bucket_allocator_create(serf_sess->pool, NULL, NULL); 551 serf_sess->conns[0]->session = serf_sess; 552 serf_sess->conns[0]->last_status_code = -1; 553 554 /* create the user agent string */ 555 if (callbacks->get_client_string) 556 SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, 557 scratch_pool)); 558 559 if (client_string) 560 serf_sess->useragent = apr_pstrcat(result_pool, 561 get_user_agent_string(scratch_pool), 562 " ", 563 client_string, SVN_VA_NULL); 564 else 565 serf_sess->useragent = get_user_agent_string(result_pool); 566 567 /* go ahead and tell serf about the connection. */ 568 status = 569 serf_connection_create2(&serf_sess->conns[0]->conn, 570 serf_sess->context, 571 url, 572 svn_ra_serf__conn_setup, serf_sess->conns[0], 573 svn_ra_serf__conn_closed, serf_sess->conns[0], 574 serf_sess->pool); 575 if (status) 576 return svn_ra_serf__wrap_err(status, NULL); 577 578 /* Set the progress callback. */ 579 serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress, 580 serf_sess); 581 582 serf_sess->num_conns = 1; 583 584 session->priv = serf_sess; 585 586 /* The following code explicitly works around a bug in serf <= r2319 / 1.3.8 587 where serf doesn't report the request as failed/cancelled when the 588 authorization request handler fails to handle the request. 589 590 As long as we allocate the request in a subpool of the serf connection 591 pool, we know that the handler is always cleaned before the connection. 592 593 Luckily our caller now passes us two pools which handle this case. 594 */ 595#if defined(SVN_DEBUG) && !SERF_VERSION_AT_LEAST(1,4,0) 596 /* Currently ensured by svn_ra_open4(). 597 If failing causes segfault in basic_tests.py 48, "basic auth test" */ 598 SVN_ERR_ASSERT((serf_sess->pool != scratch_pool) 599 && apr_pool_is_ancestor(serf_sess->pool, scratch_pool)); 600#endif 601 602 err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, 603 result_pool, scratch_pool); 604 605 /* serf should produce a usable error code instead of APR_EGENERAL */ 606 if (err && err->apr_err == APR_EGENERAL) 607 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err, 608 _("Connection to '%s' failed"), session_URL); 609 SVN_ERR(err); 610 611 /* We have set up a useful connection (that doesn't indication a redirect). 612 If we've been told there is possibly a worrisome proxy in our path to the 613 server AND we switched to HTTP/1.1 (chunked requests), then probe for 614 problems in any proxy. */ 615 if ((corrected_url == NULL || *corrected_url == NULL) 616 && serf_sess->detect_chunking && !serf_sess->http10) 617 SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, scratch_pool)); 618 619 return SVN_NO_ERROR; 620} 621 622/* Implements svn_ra__vtable_t.dup_session */ 623static svn_error_t * 624ra_serf_dup_session(svn_ra_session_t *new_session, 625 svn_ra_session_t *old_session, 626 const char *new_session_url, 627 apr_pool_t *result_pool, 628 apr_pool_t *scratch_pool) 629{ 630 svn_ra_serf__session_t *old_sess = old_session->priv; 631 svn_ra_serf__session_t *new_sess; 632 apr_status_t status; 633 634 new_sess = apr_pmemdup(result_pool, old_sess, sizeof(*new_sess)); 635 636 new_sess->pool = result_pool; 637 638 if (new_sess->config) 639 SVN_ERR(svn_config_copy_config(&new_sess->config, new_sess->config, 640 result_pool)); 641 642 /* max_connections */ 643 /* using_ssl */ 644 /* using_compression */ 645 /* http10 */ 646 /* using_chunked_requests */ 647 /* detect_chunking */ 648 649 if (new_sess->useragent) 650 new_sess->useragent = apr_pstrdup(result_pool, new_sess->useragent); 651 652 if (new_sess->vcc_url) 653 new_sess->vcc_url = apr_pstrdup(result_pool, new_sess->vcc_url); 654 655 new_sess->auth_state = NULL; 656 new_sess->auth_attempts = 0; 657 658 /* Callback functions to get info from WC */ 659 /* wc_callbacks */ 660 /* wc_callback_baton */ 661 662 /* progress_func */ 663 /* progress_baton */ 664 665 /* cancel_func */ 666 /* cancel_baton */ 667 668 /* shim_callbacks */ 669 670 new_sess->pending_error = NULL; 671 672 /* authn_types */ 673 674 /* Keys and values are static */ 675 if (new_sess->capabilities) 676 new_sess->capabilities = apr_hash_copy(result_pool, new_sess->capabilities); 677 678 if (new_sess->activity_collection_url) 679 { 680 new_sess->activity_collection_url 681 = apr_pstrdup(result_pool, new_sess->activity_collection_url); 682 } 683 684 /* using_proxy */ 685 686 if (new_sess->proxy_username) 687 { 688 new_sess->proxy_username 689 = apr_pstrdup(result_pool, new_sess->proxy_username); 690 } 691 692 if (new_sess->proxy_password) 693 { 694 new_sess->proxy_username 695 = apr_pstrdup(result_pool, new_sess->proxy_password); 696 } 697 698 new_sess->proxy_auth_attempts = 0; 699 700 /* trust_default_ca */ 701 702 if (new_sess->ssl_authorities) 703 { 704 new_sess->ssl_authorities = apr_pstrdup(result_pool, 705 new_sess->ssl_authorities); 706 } 707 708 if (new_sess->uuid) 709 new_sess->uuid = apr_pstrdup(result_pool, new_sess->uuid); 710 711 /* timeout */ 712 /* supports_deadprop_count */ 713 714 if (new_sess->me_resource) 715 new_sess->me_resource = apr_pstrdup(result_pool, new_sess->me_resource); 716 if (new_sess->rev_stub) 717 new_sess->rev_stub = apr_pstrdup(result_pool, new_sess->rev_stub); 718 if (new_sess->txn_stub) 719 new_sess->txn_stub = apr_pstrdup(result_pool, new_sess->txn_stub); 720 if (new_sess->txn_root_stub) 721 new_sess->txn_root_stub = apr_pstrdup(result_pool, 722 new_sess->txn_root_stub); 723 if (new_sess->vtxn_stub) 724 new_sess->vtxn_stub = apr_pstrdup(result_pool, new_sess->vtxn_stub); 725 if (new_sess->vtxn_root_stub) 726 new_sess->vtxn_root_stub = apr_pstrdup(result_pool, 727 new_sess->vtxn_root_stub); 728 729 /* Keys and values are static */ 730 if (new_sess->supported_posts) 731 new_sess->supported_posts = apr_hash_copy(result_pool, 732 new_sess->supported_posts); 733 734 /* ### Can we copy this? */ 735 SVN_ERR(svn_ra_serf__blncache_create(&new_sess->blncache, 736 new_sess->pool)); 737 738 if (new_sess->server_allows_bulk) 739 new_sess->server_allows_bulk = apr_pstrdup(result_pool, 740 new_sess->server_allows_bulk); 741 742 new_sess->repos_root_str = apr_pstrdup(result_pool, 743 new_sess->repos_root_str); 744 status = apr_uri_parse(result_pool, new_sess->repos_root_str, 745 &new_sess->repos_root); 746 if (status) 747 return svn_ra_serf__wrap_err(status, NULL); 748 749 new_sess->session_url_str = apr_pstrdup(result_pool, new_session_url); 750 751 status = apr_uri_parse(result_pool, new_sess->session_url_str, 752 &new_sess->session_url); 753 754 if (status) 755 return svn_ra_serf__wrap_err(status, NULL); 756 757 /* svn_boolean_t supports_inline_props */ 758 /* supports_rev_rsrc_replay */ 759 760 new_sess->context = serf_context_create(result_pool); 761 762 SVN_ERR(load_config(new_sess, old_sess->config, result_pool)); 763 764 new_sess->conns[0] = apr_pcalloc(result_pool, 765 sizeof(*new_sess->conns[0])); 766 new_sess->conns[0]->bkt_alloc = 767 serf_bucket_allocator_create(result_pool, NULL, NULL); 768 new_sess->conns[0]->session = new_sess; 769 new_sess->conns[0]->last_status_code = -1; 770 771 /* go ahead and tell serf about the connection. */ 772 status = 773 serf_connection_create2(&new_sess->conns[0]->conn, 774 new_sess->context, 775 new_sess->session_url, 776 svn_ra_serf__conn_setup, new_sess->conns[0], 777 svn_ra_serf__conn_closed, new_sess->conns[0], 778 result_pool); 779 if (status) 780 return svn_ra_serf__wrap_err(status, NULL); 781 782 /* Set the progress callback. */ 783 serf_context_set_progress_cb(new_sess->context, svn_ra_serf__progress, 784 new_sess); 785 786 new_sess->num_conns = 1; 787 new_sess->cur_conn = 0; 788 789 new_session->priv = new_sess; 790 791 return SVN_NO_ERROR; 792} 793 794/* Implements svn_ra__vtable_t.reparent(). */ 795svn_error_t * 796svn_ra_serf__reparent(svn_ra_session_t *ra_session, 797 const char *url, 798 apr_pool_t *pool) 799{ 800 svn_ra_serf__session_t *session = ra_session->priv; 801 apr_uri_t new_url; 802 apr_status_t status; 803 804 /* If it's the URL we already have, wave our hands and do nothing. */ 805 if (strcmp(session->session_url_str, url) == 0) 806 { 807 return SVN_NO_ERROR; 808 } 809 810 if (!session->repos_root_str) 811 { 812 const char *vcc_url; 813 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool)); 814 } 815 816 if (!svn_uri__is_ancestor(session->repos_root_str, url)) 817 { 818 return svn_error_createf( 819 SVN_ERR_RA_ILLEGAL_URL, NULL, 820 _("URL '%s' is not a child of the session's repository root " 821 "URL '%s'"), url, session->repos_root_str); 822 } 823 824 status = apr_uri_parse(pool, url, &new_url); 825 if (status) 826 { 827 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 828 _("Illegal repository URL '%s'"), url); 829 } 830 831 /* Depending the version of apr-util in use, for root paths url.path 832 will be NULL or "", where serf requires "/". */ 833 /* ### Maybe we should use a string buffer for these strings so we 834 ### don't allocate memory in the session on every reparent? */ 835 if (new_url.path == NULL || new_url.path[0] == '\0') 836 { 837 session->session_url.path = apr_pstrdup(session->pool, "/"); 838 } 839 else 840 { 841 session->session_url.path = apr_pstrdup(session->pool, new_url.path); 842 } 843 session->session_url_str = apr_pstrdup(session->pool, url); 844 845 return SVN_NO_ERROR; 846} 847 848/* Implements svn_ra__vtable_t.get_session_url(). */ 849static svn_error_t * 850svn_ra_serf__get_session_url(svn_ra_session_t *ra_session, 851 const char **url, 852 apr_pool_t *pool) 853{ 854 svn_ra_serf__session_t *session = ra_session->priv; 855 *url = apr_pstrdup(pool, session->session_url_str); 856 return SVN_NO_ERROR; 857} 858 859/* Implements svn_ra__vtable_t.get_latest_revnum(). */ 860static svn_error_t * 861svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session, 862 svn_revnum_t *latest_revnum, 863 apr_pool_t *pool) 864{ 865 svn_ra_serf__session_t *session = ra_session->priv; 866 867 return svn_error_trace(svn_ra_serf__get_youngest_revnum( 868 latest_revnum, session, pool)); 869} 870 871/* Implementation of svn_ra_serf__rev_proplist(). */ 872static svn_error_t * 873serf__rev_proplist(svn_ra_session_t *ra_session, 874 svn_revnum_t rev, 875 const svn_ra_serf__dav_props_t *fetch_props, 876 apr_hash_t **ret_props, 877 apr_pool_t *result_pool, 878 apr_pool_t *scratch_pool) 879{ 880 svn_ra_serf__session_t *session = ra_session->priv; 881 apr_hash_t *props; 882 const char *propfind_path; 883 svn_ra_serf__handler_t *handler; 884 885 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 886 { 887 propfind_path = apr_psprintf(scratch_pool, "%s/%ld", session->rev_stub, 888 rev); 889 890 /* svn_ra_serf__retrieve_props() wants to added the revision as 891 a Label to the PROPFIND, which isn't really necessary when 892 querying a rev-stub URI. *Shrug* Probably okay to leave the 893 Label, but whatever. */ 894 rev = SVN_INVALID_REVNUM; 895 } 896 else 897 { 898 /* Use the VCC as the propfind target path. */ 899 SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, 900 scratch_pool)); 901 } 902 903 props = apr_hash_make(result_pool); 904 SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, 905 propfind_path, rev, "0", 906 fetch_props, 907 svn_ra_serf__deliver_svn_props, 908 props, 909 scratch_pool)); 910 911 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 912 913 svn_ra_serf__keep_only_regular_props(props, scratch_pool); 914 915 *ret_props = props; 916 917 return SVN_NO_ERROR; 918} 919 920/* Implements svn_ra__vtable_t.rev_proplist(). */ 921static svn_error_t * 922svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session, 923 svn_revnum_t rev, 924 apr_hash_t **ret_props, 925 apr_pool_t *result_pool) 926{ 927 apr_pool_t *scratch_pool = svn_pool_create(result_pool); 928 svn_error_t *err; 929 930 err = serf__rev_proplist(ra_session, rev, all_props, ret_props, 931 result_pool, scratch_pool); 932 933 svn_pool_destroy(scratch_pool); 934 return svn_error_trace(err); 935} 936 937 938/* Implements svn_ra__vtable_t.rev_prop(). */ 939svn_error_t * 940svn_ra_serf__rev_prop(svn_ra_session_t *session, 941 svn_revnum_t rev, 942 const char *name, 943 svn_string_t **value, 944 apr_pool_t *result_pool) 945{ 946 apr_pool_t *scratch_pool = svn_pool_create(result_pool); 947 apr_hash_t *props; 948 svn_ra_serf__dav_props_t specific_props[2]; 949 const svn_ra_serf__dav_props_t *fetch_props = all_props; 950 951 /* The DAV propfind doesn't allow property fetches for any property name 952 as there is no defined way to quote values. If we are just fetching a 953 "svn:property" we can safely do this. In other cases we just fetch all 954 revision properties and filter the right one out */ 955 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX)-1) == 0 956 && !strchr(name + sizeof(SVN_PROP_PREFIX)-1, ':')) 957 { 958 specific_props[0].xmlns = SVN_DAV_PROP_NS_SVN; 959 specific_props[0].name = name + sizeof(SVN_PROP_PREFIX)-1; 960 specific_props[1].xmlns = NULL; 961 specific_props[1].name = NULL; 962 963 fetch_props = specific_props; 964 } 965 966 SVN_ERR(serf__rev_proplist(session, rev, fetch_props, &props, 967 result_pool, scratch_pool)); 968 969 *value = svn_hash_gets(props, name); 970 971 svn_pool_destroy(scratch_pool); 972 973 return SVN_NO_ERROR; 974} 975 976svn_error_t * 977svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session, 978 const char **url, 979 apr_pool_t *pool) 980{ 981 svn_ra_serf__session_t *session = ra_session->priv; 982 983 if (!session->repos_root_str) 984 { 985 const char *vcc_url; 986 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool)); 987 } 988 989 *url = session->repos_root_str; 990 return SVN_NO_ERROR; 991} 992 993/* TODO: to fetch the uuid from the repository, we need: 994 1. a path that exists in HEAD 995 2. a path that's readable 996 997 get_uuid handles the case where a path doesn't exist in HEAD and also the 998 case where the root of the repository is not readable. 999 However, it does not handle the case where we're fetching path not existing 1000 in HEAD of a repository with unreadable root directory. 1001 1002 Implements svn_ra__vtable_t.get_uuid(). 1003 */ 1004static svn_error_t * 1005svn_ra_serf__get_uuid(svn_ra_session_t *ra_session, 1006 const char **uuid, 1007 apr_pool_t *pool) 1008{ 1009 svn_ra_serf__session_t *session = ra_session->priv; 1010 1011 if (!session->uuid) 1012 { 1013 const char *vcc_url; 1014 1015 /* We should never get here if we have HTTP v2 support, because 1016 any server with that support should be transmitting the 1017 UUID in the initial OPTIONS response. */ 1018 SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 1019 1020 /* We're not interested in vcc_url and relative_url, but this call also 1021 stores the repository's uuid in the session. */ 1022 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool)); 1023 if (!session->uuid) 1024 { 1025 return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL, 1026 _("The UUID property was not found on the " 1027 "resource or any of its parents")); 1028 } 1029 } 1030 1031 *uuid = session->uuid; 1032 1033 return SVN_NO_ERROR; 1034} 1035 1036 1037static const svn_ra__vtable_t serf_vtable = { 1038 ra_serf_version, 1039 ra_serf_get_description, 1040 ra_serf_get_schemes, 1041 svn_ra_serf__open, 1042 ra_serf_dup_session, 1043 svn_ra_serf__reparent, 1044 svn_ra_serf__get_session_url, 1045 svn_ra_serf__get_latest_revnum, 1046 svn_ra_serf__get_dated_revision, 1047 svn_ra_serf__change_rev_prop, 1048 svn_ra_serf__rev_proplist, 1049 svn_ra_serf__rev_prop, 1050 svn_ra_serf__get_commit_editor, 1051 svn_ra_serf__get_file, 1052 svn_ra_serf__get_dir, 1053 svn_ra_serf__get_mergeinfo, 1054 svn_ra_serf__do_update, 1055 svn_ra_serf__do_switch, 1056 svn_ra_serf__do_status, 1057 svn_ra_serf__do_diff, 1058 svn_ra_serf__get_log, 1059 svn_ra_serf__check_path, 1060 svn_ra_serf__stat, 1061 svn_ra_serf__get_uuid, 1062 svn_ra_serf__get_repos_root, 1063 svn_ra_serf__get_locations, 1064 svn_ra_serf__get_location_segments, 1065 svn_ra_serf__get_file_revs, 1066 svn_ra_serf__lock, 1067 svn_ra_serf__unlock, 1068 svn_ra_serf__get_lock, 1069 svn_ra_serf__get_locks, 1070 svn_ra_serf__replay, 1071 svn_ra_serf__has_capability, 1072 svn_ra_serf__replay_range, 1073 svn_ra_serf__get_deleted_rev, 1074 svn_ra_serf__register_editor_shim_callbacks, 1075 svn_ra_serf__get_inherited_props 1076}; 1077 1078svn_error_t * 1079svn_ra_serf__init(const svn_version_t *loader_version, 1080 const svn_ra__vtable_t **vtable, 1081 apr_pool_t *pool) 1082{ 1083 static const svn_version_checklist_t checklist[] = 1084 { 1085 { "svn_subr", svn_subr_version }, 1086 { "svn_delta", svn_delta_version }, 1087 { NULL, NULL } 1088 }; 1089 int serf_major; 1090 int serf_minor; 1091 int serf_patch; 1092 1093 SVN_ERR(svn_ver_check_list2(ra_serf_version(), checklist, svn_ver_equal)); 1094 1095 /* Simplified version check to make sure we can safely use the 1096 VTABLE parameter. The RA loader does a more exhaustive check. */ 1097 if (loader_version->major != SVN_VER_MAJOR) 1098 { 1099 return svn_error_createf( 1100 SVN_ERR_VERSION_MISMATCH, NULL, 1101 _("Unsupported RA loader version (%d) for ra_serf"), 1102 loader_version->major); 1103 } 1104 1105 /* Make sure that we have loaded a compatible library: the MAJOR must 1106 match, and the minor must be at *least* what we compiled against. 1107 The patch level is simply ignored. */ 1108 serf_lib_version(&serf_major, &serf_minor, &serf_patch); 1109 if (serf_major != SERF_MAJOR_VERSION 1110 || serf_minor < SERF_MINOR_VERSION) 1111 { 1112 return svn_error_createf( 1113 /* ### should return a unique error */ 1114 SVN_ERR_VERSION_MISMATCH, NULL, 1115 _("ra_serf was compiled for serf %d.%d.%d but loaded " 1116 "an incompatible %d.%d.%d library"), 1117 SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION, 1118 serf_major, serf_minor, serf_patch); 1119 } 1120 1121 *vtable = &serf_vtable; 1122 1123 return SVN_NO_ERROR; 1124} 1125 1126/* Compatibility wrapper for pre-1.2 subversions. Needed? */ 1127#define NAME "ra_serf" 1128#define DESCRIPTION RA_SERF_DESCRIPTION 1129#define VTBL serf_vtable 1130#define INITFUNC svn_ra_serf__init 1131#define COMPAT_INITFUNC svn_ra_serf_init 1132#include "../libsvn_ra/wrapper_template.h" 1133