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