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