serf.c revision 251881
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_time.h" 43#include "svn_version.h" 44 45#include "private/svn_dav_protocol.h" 46#include "private/svn_dep_compat.h" 47#include "private/svn_fspath.h" 48#include "private/svn_subr_private.h" 49#include "svn_private_config.h" 50 51#include "ra_serf.h" 52 53 54/* Implements svn_ra__vtable_t.get_version(). */ 55static const svn_version_t * 56ra_serf_version(void) 57{ 58 SVN_VERSION_BODY; 59} 60 61#define RA_SERF_DESCRIPTION \ 62 N_("Module for accessing a repository via WebDAV protocol using serf.") 63 64/* Implements svn_ra__vtable_t.get_description(). */ 65static const char * 66ra_serf_get_description(void) 67{ 68 return _(RA_SERF_DESCRIPTION); 69} 70 71/* Implements svn_ra__vtable_t.get_schemes(). */ 72static const char * const * 73ra_serf_get_schemes(apr_pool_t *pool) 74{ 75 static const char *serf_ssl[] = { "http", "https", NULL }; 76#if 0 77 /* ### Temporary: to shut up a warning. */ 78 static const char *serf_no_ssl[] = { "http", NULL }; 79#endif 80 81 /* TODO: Runtime detection. */ 82 return serf_ssl; 83} 84 85/* Load the setting http-auth-types from the global or server specific 86 section, parse its value and set the types of authentication we should 87 accept from the server. */ 88static svn_error_t * 89load_http_auth_types(apr_pool_t *pool, svn_config_t *config, 90 const char *server_group, 91 int *authn_types) 92{ 93 const char *http_auth_types = NULL; 94 *authn_types = SERF_AUTHN_NONE; 95 96 svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL, 97 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL); 98 99 if (server_group) 100 { 101 svn_config_get(config, &http_auth_types, server_group, 102 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types); 103 } 104 105 if (http_auth_types) 106 { 107 char *token; 108 char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1); 109 apr_collapse_spaces(auth_types_list, http_auth_types); 110 while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL) 111 { 112 if (svn_cstring_casecmp("basic", token) == 0) 113 *authn_types |= SERF_AUTHN_BASIC; 114 else if (svn_cstring_casecmp("digest", token) == 0) 115 *authn_types |= SERF_AUTHN_DIGEST; 116 else if (svn_cstring_casecmp("ntlm", token) == 0) 117 *authn_types |= SERF_AUTHN_NTLM; 118 else if (svn_cstring_casecmp("negotiate", token) == 0) 119 *authn_types |= SERF_AUTHN_NEGOTIATE; 120 else 121 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, 122 _("Invalid config: unknown %s " 123 "'%s'"), 124 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token); 125 } 126 } 127 else 128 { 129 /* Nothing specified by the user, so accept all types. */ 130 *authn_types = SERF_AUTHN_ALL; 131 } 132 133 return SVN_NO_ERROR; 134} 135 136/* Default HTTP timeout (in seconds); overridden by the 'http-timeout' 137 runtime configuration variable. */ 138#define DEFAULT_HTTP_TIMEOUT 600 139 140static svn_error_t * 141load_config(svn_ra_serf__session_t *session, 142 apr_hash_t *config_hash, 143 apr_pool_t *pool) 144{ 145 svn_config_t *config, *config_client; 146 const char *server_group; 147 const char *proxy_host = NULL; 148 const char *port_str = NULL; 149 const char *timeout_str = NULL; 150 const char *exceptions; 151 apr_port_t proxy_port; 152 153 if (config_hash) 154 { 155 config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS); 156 config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG); 157 } 158 else 159 { 160 config = NULL; 161 config_client = NULL; 162 } 163 164 SVN_ERR(svn_config_get_bool(config, &session->using_compression, 165 SVN_CONFIG_SECTION_GLOBAL, 166 SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE)); 167 svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL, 168 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL); 169 170 if (session->wc_callbacks->auth_baton) 171 { 172 if (config_client) 173 { 174 svn_auth_set_parameter(session->wc_callbacks->auth_baton, 175 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, 176 config_client); 177 } 178 if (config) 179 { 180 svn_auth_set_parameter(session->wc_callbacks->auth_baton, 181 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, 182 config); 183 } 184 } 185 186 /* Use the default proxy-specific settings if and only if 187 "http-proxy-exceptions" is not set to exclude this host. */ 188 svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL, 189 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, ""); 190 if (! svn_cstring_match_glob_list(session->session_url.hostname, 191 svn_cstring_split(exceptions, ",", 192 TRUE, pool))) 193 { 194 svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL, 195 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL); 196 svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL, 197 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL); 198 svn_config_get(config, &session->proxy_username, 199 SVN_CONFIG_SECTION_GLOBAL, 200 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL); 201 svn_config_get(config, &session->proxy_password, 202 SVN_CONFIG_SECTION_GLOBAL, 203 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL); 204 } 205 206 /* Load the global ssl settings, if set. */ 207 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca, 208 SVN_CONFIG_SECTION_GLOBAL, 209 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA, 210 TRUE)); 211 svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL, 212 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL); 213 214 /* If set, read the flag that tells us to do bulk updates or not. Defaults 215 to skelta updates. */ 216 SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates, 217 SVN_CONFIG_SECTION_GLOBAL, 218 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, 219 "auto", 220 svn_tristate_unknown)); 221 222 /* Load the maximum number of parallel session connections. */ 223 SVN_ERR(svn_config_get_int64(config, &session->max_connections, 224 SVN_CONFIG_SECTION_GLOBAL, 225 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 226 SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS)); 227 228 if (config) 229 server_group = svn_config_find_group(config, 230 session->session_url.hostname, 231 SVN_CONFIG_SECTION_GROUPS, pool); 232 else 233 server_group = NULL; 234 235 if (server_group) 236 { 237 SVN_ERR(svn_config_get_bool(config, &session->using_compression, 238 server_group, 239 SVN_CONFIG_OPTION_HTTP_COMPRESSION, 240 session->using_compression)); 241 svn_config_get(config, &timeout_str, server_group, 242 SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str); 243 244 svn_auth_set_parameter(session->wc_callbacks->auth_baton, 245 SVN_AUTH_PARAM_SERVER_GROUP, server_group); 246 247 /* Load the group proxy server settings, overriding global 248 settings. We intentionally ignore 'http-proxy-exceptions' 249 here because, well, if this site was an exception, why is 250 there a per-server proxy configuration for it? */ 251 svn_config_get(config, &proxy_host, server_group, 252 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host); 253 svn_config_get(config, &port_str, server_group, 254 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str); 255 svn_config_get(config, &session->proxy_username, server_group, 256 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, 257 session->proxy_username); 258 svn_config_get(config, &session->proxy_password, server_group, 259 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, 260 session->proxy_password); 261 262 /* Load the group ssl settings. */ 263 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca, 264 server_group, 265 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA, 266 session->trust_default_ca)); 267 svn_config_get(config, &session->ssl_authorities, server_group, 268 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, 269 session->ssl_authorities); 270 271 /* Load the group bulk updates flag. */ 272 SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates, 273 server_group, 274 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, 275 "auto", 276 session->bulk_updates)); 277 278 /* Load the maximum number of parallel session connections, 279 overriding global values. */ 280 SVN_ERR(svn_config_get_int64(config, &session->max_connections, 281 server_group, 282 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 283 session->max_connections)); 284 } 285 286 /* Don't allow the http-max-connections value to be larger than our 287 compiled-in limit, or to be too small to operate. Broken 288 functionality and angry administrators are equally undesirable. */ 289 if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT) 290 session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT; 291 if (session->max_connections < 2) 292 session->max_connections = 2; 293 294 /* Parse the connection timeout value, if any. */ 295 session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT); 296 if (timeout_str) 297 { 298 char *endstr; 299 const long int timeout = strtol(timeout_str, &endstr, 10); 300 301 if (*endstr) 302 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL, 303 _("Invalid config: illegal character in " 304 "timeout value")); 305 if (timeout < 0) 306 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL, 307 _("Invalid config: negative timeout value")); 308 session->timeout = apr_time_from_sec(timeout); 309 } 310 SVN_ERR_ASSERT(session->timeout >= 0); 311 312 /* Convert the proxy port value, if any. */ 313 if (port_str) 314 { 315 char *endstr; 316 const long int port = strtol(port_str, &endstr, 10); 317 318 if (*endstr) 319 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 320 _("Invalid URL: illegal character in proxy " 321 "port number")); 322 if (port < 0) 323 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 324 _("Invalid URL: negative proxy port number")); 325 if (port > 65535) 326 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 327 _("Invalid URL: proxy port number greater " 328 "than maximum TCP port number 65535")); 329 proxy_port = (apr_port_t) port; 330 } 331 else 332 { 333 proxy_port = 80; 334 } 335 336 if (proxy_host) 337 { 338 apr_sockaddr_t *proxy_addr; 339 apr_status_t status; 340 341 status = apr_sockaddr_info_get(&proxy_addr, proxy_host, 342 APR_UNSPEC, proxy_port, 0, 343 session->pool); 344 if (status) 345 { 346 return svn_ra_serf__wrap_err( 347 status, _("Could not resolve proxy server '%s'"), 348 proxy_host); 349 } 350 session->using_proxy = TRUE; 351 serf_config_proxy(session->context, proxy_addr); 352 } 353 else 354 { 355 session->using_proxy = FALSE; 356 } 357 358 /* Setup authentication. */ 359 SVN_ERR(load_http_auth_types(pool, config, server_group, 360 &session->authn_types)); 361 serf_config_authn_types(session->context, session->authn_types); 362 serf_config_credentials_callback(session->context, 363 svn_ra_serf__credentials_callback); 364 365 return SVN_NO_ERROR; 366} 367#undef DEFAULT_HTTP_TIMEOUT 368 369static void 370svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written) 371{ 372 const svn_ra_serf__session_t *serf_sess = progress_baton; 373 if (serf_sess->progress_func) 374 { 375 serf_sess->progress_func(read + written, -1, 376 serf_sess->progress_baton, 377 serf_sess->pool); 378 } 379} 380 381/* Implements svn_ra__vtable_t.open_session(). */ 382static svn_error_t * 383svn_ra_serf__open(svn_ra_session_t *session, 384 const char **corrected_url, 385 const char *session_URL, 386 const svn_ra_callbacks2_t *callbacks, 387 void *callback_baton, 388 apr_hash_t *config, 389 apr_pool_t *pool) 390{ 391 apr_status_t status; 392 svn_ra_serf__session_t *serf_sess; 393 apr_uri_t url; 394 const char *client_string = NULL; 395 396 if (corrected_url) 397 *corrected_url = NULL; 398 399 serf_sess = apr_pcalloc(pool, sizeof(*serf_sess)); 400 serf_sess->pool = svn_pool_create(pool); 401 serf_sess->wc_callbacks = callbacks; 402 serf_sess->wc_callback_baton = callback_baton; 403 serf_sess->progress_func = callbacks->progress_func; 404 serf_sess->progress_baton = callbacks->progress_baton; 405 serf_sess->cancel_func = callbacks->cancel_func; 406 serf_sess->cancel_baton = callback_baton; 407 408 /* todo: reuse serf context across sessions */ 409 serf_sess->context = serf_context_create(serf_sess->pool); 410 411 SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache, 412 serf_sess->pool)); 413 414 415 status = apr_uri_parse(serf_sess->pool, session_URL, &url); 416 if (status) 417 { 418 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 419 _("Illegal URL '%s'"), 420 session_URL); 421 } 422 /* Depending the version of apr-util in use, for root paths url.path 423 will be NULL or "", where serf requires "/". */ 424 if (url.path == NULL || url.path[0] == '\0') 425 { 426 url.path = apr_pstrdup(serf_sess->pool, "/"); 427 } 428 if (!url.port) 429 { 430 url.port = apr_uri_port_of_scheme(url.scheme); 431 } 432 serf_sess->session_url = url; 433 serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL); 434 serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0); 435 436 serf_sess->supports_deadprop_count = svn_tristate_unknown; 437 438 serf_sess->capabilities = apr_hash_make(serf_sess->pool); 439 440 /* We have to assume that the server only supports HTTP/1.0. Once it's clear 441 HTTP/1.1 is supported, we can upgrade. */ 442 serf_sess->http10 = TRUE; 443 444 SVN_ERR(load_config(serf_sess, config, serf_sess->pool)); 445 446 serf_sess->conns[0] = apr_pcalloc(serf_sess->pool, 447 sizeof(*serf_sess->conns[0])); 448 serf_sess->conns[0]->bkt_alloc = 449 serf_bucket_allocator_create(serf_sess->pool, NULL, NULL); 450 serf_sess->conns[0]->session = serf_sess; 451 serf_sess->conns[0]->last_status_code = -1; 452 453 /* create the user agent string */ 454 if (callbacks->get_client_string) 455 SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool)); 456 457 if (client_string) 458 serf_sess->useragent = apr_pstrcat(pool, USER_AGENT, " ", 459 client_string, (char *)NULL); 460 else 461 serf_sess->useragent = USER_AGENT; 462 463 /* go ahead and tell serf about the connection. */ 464 status = 465 serf_connection_create2(&serf_sess->conns[0]->conn, 466 serf_sess->context, 467 url, 468 svn_ra_serf__conn_setup, serf_sess->conns[0], 469 svn_ra_serf__conn_closed, serf_sess->conns[0], 470 serf_sess->pool); 471 if (status) 472 return svn_ra_serf__wrap_err(status, NULL); 473 474 /* Set the progress callback. */ 475 serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress, 476 serf_sess); 477 478 serf_sess->num_conns = 1; 479 480 session->priv = serf_sess; 481 482 return svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool); 483} 484 485/* Implements svn_ra__vtable_t.reparent(). */ 486static svn_error_t * 487svn_ra_serf__reparent(svn_ra_session_t *ra_session, 488 const char *url, 489 apr_pool_t *pool) 490{ 491 svn_ra_serf__session_t *session = ra_session->priv; 492 apr_uri_t new_url; 493 apr_status_t status; 494 495 /* If it's the URL we already have, wave our hands and do nothing. */ 496 if (strcmp(session->session_url_str, url) == 0) 497 { 498 return SVN_NO_ERROR; 499 } 500 501 if (!session->repos_root_str) 502 { 503 const char *vcc_url; 504 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); 505 } 506 507 if (!svn_uri__is_ancestor(session->repos_root_str, url)) 508 { 509 return svn_error_createf( 510 SVN_ERR_RA_ILLEGAL_URL, NULL, 511 _("URL '%s' is not a child of the session's repository root " 512 "URL '%s'"), url, session->repos_root_str); 513 } 514 515 status = apr_uri_parse(pool, url, &new_url); 516 if (status) 517 { 518 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 519 _("Illegal repository URL '%s'"), url); 520 } 521 522 /* Depending the version of apr-util in use, for root paths url.path 523 will be NULL or "", where serf requires "/". */ 524 /* ### Maybe we should use a string buffer for these strings so we 525 ### don't allocate memory in the session on every reparent? */ 526 if (new_url.path == NULL || new_url.path[0] == '\0') 527 { 528 session->session_url.path = apr_pstrdup(session->pool, "/"); 529 } 530 else 531 { 532 session->session_url.path = apr_pstrdup(session->pool, new_url.path); 533 } 534 session->session_url_str = apr_pstrdup(session->pool, url); 535 536 return SVN_NO_ERROR; 537} 538 539/* Implements svn_ra__vtable_t.get_session_url(). */ 540static svn_error_t * 541svn_ra_serf__get_session_url(svn_ra_session_t *ra_session, 542 const char **url, 543 apr_pool_t *pool) 544{ 545 svn_ra_serf__session_t *session = ra_session->priv; 546 *url = apr_pstrdup(pool, session->session_url_str); 547 return SVN_NO_ERROR; 548} 549 550/* Implements svn_ra__vtable_t.get_latest_revnum(). */ 551static svn_error_t * 552svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session, 553 svn_revnum_t *latest_revnum, 554 apr_pool_t *pool) 555{ 556 svn_ra_serf__session_t *session = ra_session->priv; 557 558 return svn_error_trace(svn_ra_serf__get_youngest_revnum( 559 latest_revnum, session, pool)); 560} 561 562/* Implements svn_ra__vtable_t.rev_proplist(). */ 563static svn_error_t * 564svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session, 565 svn_revnum_t rev, 566 apr_hash_t **ret_props, 567 apr_pool_t *pool) 568{ 569 svn_ra_serf__session_t *session = ra_session->priv; 570 apr_hash_t *props; 571 const char *propfind_path; 572 573 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 574 { 575 propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev); 576 577 /* svn_ra_serf__retrieve_props() wants to added the revision as 578 a Label to the PROPFIND, which isn't really necessary when 579 querying a rev-stub URI. *Shrug* Probably okay to leave the 580 Label, but whatever. */ 581 rev = SVN_INVALID_REVNUM; 582 } 583 else 584 { 585 /* Use the VCC as the propfind target path. */ 586 SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool)); 587 } 588 589 /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash. */ 590 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0], 591 propfind_path, rev, "0", all_props, 592 pool, pool)); 593 594 SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props, 595 pool, pool)); 596 597 return SVN_NO_ERROR; 598} 599 600/* Implements svn_ra__vtable_t.rev_prop(). */ 601static svn_error_t * 602svn_ra_serf__rev_prop(svn_ra_session_t *session, 603 svn_revnum_t rev, 604 const char *name, 605 svn_string_t **value, 606 apr_pool_t *pool) 607{ 608 apr_hash_t *props; 609 610 SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool)); 611 612 *value = svn_hash_gets(props, name); 613 614 return SVN_NO_ERROR; 615} 616 617static svn_error_t * 618fetch_path_props(apr_hash_t **props, 619 svn_ra_serf__session_t *session, 620 const char *session_relpath, 621 svn_revnum_t revision, 622 const svn_ra_serf__dav_props_t *desired_props, 623 apr_pool_t *result_pool, 624 apr_pool_t *scratch_pool) 625{ 626 const char *url; 627 628 url = session->session_url.path; 629 630 /* If we have a relative path, append it. */ 631 if (session_relpath) 632 url = svn_path_url_add_component2(url, session_relpath, scratch_pool); 633 634 /* If we were given a specific revision, get a URL that refers to that 635 specific revision (rather than floating with HEAD). */ 636 if (SVN_IS_VALID_REVNUM(revision)) 637 { 638 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */, 639 session, NULL /* conn */, 640 url, revision, 641 scratch_pool, scratch_pool)); 642 } 643 644 /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant. 645 Or we started with SVN_INVALID_REVNUM and URL may be floating. */ 646 SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0], 647 url, SVN_INVALID_REVNUM, 648 desired_props, 649 result_pool, scratch_pool)); 650 651 return SVN_NO_ERROR; 652} 653 654/* Implements svn_ra__vtable_t.check_path(). */ 655static svn_error_t * 656svn_ra_serf__check_path(svn_ra_session_t *ra_session, 657 const char *rel_path, 658 svn_revnum_t revision, 659 svn_node_kind_t *kind, 660 apr_pool_t *pool) 661{ 662 svn_ra_serf__session_t *session = ra_session->priv; 663 apr_hash_t *props; 664 665 svn_error_t *err = fetch_path_props(&props, session, rel_path, 666 revision, check_path_props, 667 pool, pool); 668 669 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 670 { 671 svn_error_clear(err); 672 *kind = svn_node_none; 673 } 674 else 675 { 676 /* Any other error, raise to caller. */ 677 if (err) 678 return svn_error_trace(err); 679 680 SVN_ERR(svn_ra_serf__get_resource_type(kind, props)); 681 } 682 683 return SVN_NO_ERROR; 684} 685 686 687struct dirent_walker_baton_t { 688 /* Update the fields in this entry. */ 689 svn_dirent_t *entry; 690 691 svn_tristate_t *supports_deadprop_count; 692 693 /* If allocations are necessary, then use this pool. */ 694 apr_pool_t *result_pool; 695}; 696 697static svn_error_t * 698dirent_walker(void *baton, 699 const char *ns, 700 const char *name, 701 const svn_string_t *val, 702 apr_pool_t *scratch_pool) 703{ 704 struct dirent_walker_baton_t *dwb = baton; 705 706 if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) 707 { 708 dwb->entry->has_props = TRUE; 709 } 710 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) 711 { 712 dwb->entry->has_props = TRUE; 713 } 714 else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) 715 { 716 if(strcmp(name, "deadprop-count") == 0) 717 { 718 if (*val->data) 719 { 720 apr_int64_t deadprop_count; 721 SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data)); 722 dwb->entry->has_props = deadprop_count > 0; 723 if (dwb->supports_deadprop_count) 724 *dwb->supports_deadprop_count = svn_tristate_true; 725 } 726 else if (dwb->supports_deadprop_count) 727 *dwb->supports_deadprop_count = svn_tristate_false; 728 } 729 } 730 else if (strcmp(ns, "DAV:") == 0) 731 { 732 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) 733 { 734 dwb->entry->created_rev = SVN_STR_TO_REV(val->data); 735 } 736 else if (strcmp(name, "creator-displayname") == 0) 737 { 738 dwb->entry->last_author = val->data; 739 } 740 else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) 741 { 742 SVN_ERR(svn_time_from_cstring(&dwb->entry->time, 743 val->data, 744 dwb->result_pool)); 745 } 746 else if (strcmp(name, "getcontentlength") == 0) 747 { 748 /* 'getcontentlength' property is empty for directories. */ 749 if (val->len) 750 { 751 SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data)); 752 } 753 } 754 else if (strcmp(name, "resourcetype") == 0) 755 { 756 if (strcmp(val->data, "collection") == 0) 757 { 758 dwb->entry->kind = svn_node_dir; 759 } 760 else 761 { 762 dwb->entry->kind = svn_node_file; 763 } 764 } 765 } 766 767 return SVN_NO_ERROR; 768} 769 770struct path_dirent_visitor_t { 771 apr_hash_t *full_paths; 772 apr_hash_t *base_paths; 773 const char *orig_path; 774 svn_tristate_t supports_deadprop_count; 775 apr_pool_t *result_pool; 776}; 777 778static svn_error_t * 779path_dirent_walker(void *baton, 780 const char *path, apr_ssize_t path_len, 781 const char *ns, apr_ssize_t ns_len, 782 const char *name, apr_ssize_t name_len, 783 const svn_string_t *val, 784 apr_pool_t *pool) 785{ 786 struct path_dirent_visitor_t *dirents = baton; 787 struct dirent_walker_baton_t dwb; 788 svn_dirent_t *entry; 789 790 /* Skip our original path. */ 791 if (strcmp(path, dirents->orig_path) == 0) 792 { 793 return SVN_NO_ERROR; 794 } 795 796 entry = apr_hash_get(dirents->full_paths, path, path_len); 797 798 if (!entry) 799 { 800 const char *base_name; 801 802 entry = svn_dirent_create(pool); 803 804 apr_hash_set(dirents->full_paths, path, path_len, entry); 805 806 base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool), 807 pool); 808 809 svn_hash_sets(dirents->base_paths, base_name, entry); 810 } 811 812 dwb.entry = entry; 813 dwb.supports_deadprop_count = &dirents->supports_deadprop_count; 814 dwb.result_pool = dirents->result_pool; 815 return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool)); 816} 817 818static const svn_ra_serf__dav_props_t * 819get_dirent_props(apr_uint32_t dirent_fields, 820 svn_ra_serf__session_t *session, 821 apr_pool_t *pool) 822{ 823 svn_ra_serf__dav_props_t *prop; 824 apr_array_header_t *props = apr_array_make 825 (pool, 7, sizeof(svn_ra_serf__dav_props_t)); 826 827 if (session->supports_deadprop_count != svn_tristate_false 828 || ! (dirent_fields & SVN_DIRENT_HAS_PROPS)) 829 { 830 if (dirent_fields & SVN_DIRENT_KIND) 831 { 832 prop = apr_array_push(props); 833 prop->namespace = "DAV:"; 834 prop->name = "resourcetype"; 835 } 836 837 if (dirent_fields & SVN_DIRENT_SIZE) 838 { 839 prop = apr_array_push(props); 840 prop->namespace = "DAV:"; 841 prop->name = "getcontentlength"; 842 } 843 844 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 845 { 846 prop = apr_array_push(props); 847 prop->namespace = SVN_DAV_PROP_NS_DAV; 848 prop->name = "deadprop-count"; 849 } 850 851 if (dirent_fields & SVN_DIRENT_CREATED_REV) 852 { 853 svn_ra_serf__dav_props_t *p = apr_array_push(props); 854 p->namespace = "DAV:"; 855 p->name = SVN_DAV__VERSION_NAME; 856 } 857 858 if (dirent_fields & SVN_DIRENT_TIME) 859 { 860 prop = apr_array_push(props); 861 prop->namespace = "DAV:"; 862 prop->name = SVN_DAV__CREATIONDATE; 863 } 864 865 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 866 { 867 prop = apr_array_push(props); 868 prop->namespace = "DAV:"; 869 prop->name = "creator-displayname"; 870 } 871 } 872 else 873 { 874 /* We found an old subversion server that can't handle 875 the deadprop-count property in the way we expect. 876 877 The neon behavior is to retrieve all properties in this case */ 878 prop = apr_array_push(props); 879 prop->namespace = "DAV:"; 880 prop->name = "allprop"; 881 } 882 883 prop = apr_array_push(props); 884 prop->namespace = NULL; 885 prop->name = NULL; 886 887 return (svn_ra_serf__dav_props_t *) props->elts; 888} 889 890/* Implements svn_ra__vtable_t.stat(). */ 891static svn_error_t * 892svn_ra_serf__stat(svn_ra_session_t *ra_session, 893 const char *rel_path, 894 svn_revnum_t revision, 895 svn_dirent_t **dirent, 896 apr_pool_t *pool) 897{ 898 svn_ra_serf__session_t *session = ra_session->priv; 899 apr_hash_t *props; 900 svn_error_t *err; 901 struct dirent_walker_baton_t dwb; 902 svn_tristate_t deadprop_count = svn_tristate_unknown; 903 904 err = fetch_path_props(&props, 905 session, rel_path, revision, 906 get_dirent_props(SVN_DIRENT_ALL, session, pool), 907 pool, pool); 908 if (err) 909 { 910 if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 911 { 912 svn_error_clear(err); 913 *dirent = NULL; 914 return SVN_NO_ERROR; 915 } 916 else 917 return svn_error_trace(err); 918 } 919 920 dwb.entry = svn_dirent_create(pool); 921 dwb.supports_deadprop_count = &deadprop_count; 922 dwb.result_pool = pool; 923 SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool)); 924 925 if (deadprop_count == svn_tristate_false 926 && session->supports_deadprop_count == svn_tristate_unknown 927 && !dwb.entry->has_props) 928 { 929 /* We have to requery as the server didn't give us the right 930 information */ 931 session->supports_deadprop_count = svn_tristate_false; 932 933 SVN_ERR(fetch_path_props(&props, 934 session, rel_path, SVN_INVALID_REVNUM, 935 get_dirent_props(SVN_DIRENT_ALL, session, pool), 936 pool, pool)); 937 938 SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool)); 939 } 940 941 if (deadprop_count != svn_tristate_unknown) 942 session->supports_deadprop_count = deadprop_count; 943 944 *dirent = dwb.entry; 945 946 return SVN_NO_ERROR; 947} 948 949/* Reads the 'resourcetype' property from the list PROPS and checks if the 950 * resource at PATH@REVISION really is a directory. Returns 951 * SVN_ERR_FS_NOT_DIRECTORY if not. 952 */ 953static svn_error_t * 954resource_is_directory(apr_hash_t *props) 955{ 956 svn_node_kind_t kind; 957 958 SVN_ERR(svn_ra_serf__get_resource_type(&kind, props)); 959 960 if (kind != svn_node_dir) 961 { 962 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, 963 _("Can't get entries of non-directory")); 964 } 965 966 return SVN_NO_ERROR; 967} 968 969/* Implements svn_ra__vtable_t.get_dir(). */ 970static svn_error_t * 971svn_ra_serf__get_dir(svn_ra_session_t *ra_session, 972 apr_hash_t **dirents, 973 svn_revnum_t *fetched_rev, 974 apr_hash_t **ret_props, 975 const char *rel_path, 976 svn_revnum_t revision, 977 apr_uint32_t dirent_fields, 978 apr_pool_t *pool) 979{ 980 svn_ra_serf__session_t *session = ra_session->priv; 981 const char *path; 982 983 path = session->session_url.path; 984 985 /* If we have a relative path, URI encode and append it. */ 986 if (rel_path) 987 { 988 path = svn_path_url_add_component2(path, rel_path, pool); 989 } 990 991 /* If the user specified a peg revision other than HEAD, we have to fetch 992 the baseline collection url for that revision. If not, we can use the 993 public url. */ 994 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) 995 { 996 SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev, 997 session, NULL /* conn */, 998 path, revision, 999 pool, pool)); 1000 revision = SVN_INVALID_REVNUM; 1001 } 1002 /* REVISION is always SVN_INVALID_REVNUM */ 1003 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); 1004 1005 /* If we're asked for children, fetch them now. */ 1006 if (dirents) 1007 { 1008 struct path_dirent_visitor_t dirent_walk; 1009 apr_hash_t *props; 1010 const char *rtype; 1011 1012 /* Always request node kind to check that path is really a 1013 * directory. 1014 */ 1015 dirent_fields |= SVN_DIRENT_KIND; 1016 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0], 1017 path, SVN_INVALID_REVNUM, "1", 1018 get_dirent_props(dirent_fields, 1019 session, pool), 1020 pool, pool)); 1021 1022 /* Check if the path is really a directory. */ 1023 rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype"); 1024 if (rtype == NULL || strcmp(rtype, "collection") != 0) 1025 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, 1026 _("Can't get entries of non-directory")); 1027 1028 /* We're going to create two hashes to help the walker along. 1029 * We're going to return the 2nd one back to the caller as it 1030 * will have the basenames it expects. 1031 */ 1032 dirent_walk.full_paths = apr_hash_make(pool); 1033 dirent_walk.base_paths = apr_hash_make(pool); 1034 dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool); 1035 dirent_walk.supports_deadprop_count = svn_tristate_unknown; 1036 dirent_walk.result_pool = pool; 1037 1038 SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM, 1039 path_dirent_walker, &dirent_walk, 1040 pool)); 1041 1042 if (dirent_walk.supports_deadprop_count == svn_tristate_false 1043 && session->supports_deadprop_count == svn_tristate_unknown 1044 && dirent_fields & SVN_DIRENT_HAS_PROPS) 1045 { 1046 /* We have to requery as the server didn't give us the right 1047 information */ 1048 session->supports_deadprop_count = svn_tristate_false; 1049 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, 1050 session->conns[0], 1051 path, SVN_INVALID_REVNUM, "1", 1052 get_dirent_props(dirent_fields, 1053 session, pool), 1054 pool, pool)); 1055 1056 apr_hash_clear(dirent_walk.full_paths); 1057 apr_hash_clear(dirent_walk.base_paths); 1058 1059 SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM, 1060 path_dirent_walker, 1061 &dirent_walk, pool)); 1062 } 1063 1064 *dirents = dirent_walk.base_paths; 1065 1066 if (dirent_walk.supports_deadprop_count != svn_tristate_unknown) 1067 session->supports_deadprop_count = dirent_walk.supports_deadprop_count; 1068 } 1069 1070 /* If we're asked for the directory properties, fetch them too. */ 1071 if (ret_props) 1072 { 1073 apr_hash_t *props; 1074 1075 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0], 1076 path, SVN_INVALID_REVNUM, 1077 all_props, 1078 pool, pool)); 1079 1080 /* Check if the path is really a directory. */ 1081 SVN_ERR(resource_is_directory(props)); 1082 1083 /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props() 1084 ### put them into POOL, so we're okay. */ 1085 SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool)); 1086 } 1087 1088 return SVN_NO_ERROR; 1089} 1090 1091svn_error_t * 1092svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session, 1093 const char **url, 1094 apr_pool_t *pool) 1095{ 1096 svn_ra_serf__session_t *session = ra_session->priv; 1097 1098 if (!session->repos_root_str) 1099 { 1100 const char *vcc_url; 1101 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); 1102 } 1103 1104 *url = session->repos_root_str; 1105 return SVN_NO_ERROR; 1106} 1107 1108/* TODO: to fetch the uuid from the repository, we need: 1109 1. a path that exists in HEAD 1110 2. a path that's readable 1111 1112 get_uuid handles the case where a path doesn't exist in HEAD and also the 1113 case where the root of the repository is not readable. 1114 However, it does not handle the case where we're fetching path not existing 1115 in HEAD of a repository with unreadable root directory. 1116 1117 Implements svn_ra__vtable_t.get_uuid(). 1118 */ 1119static svn_error_t * 1120svn_ra_serf__get_uuid(svn_ra_session_t *ra_session, 1121 const char **uuid, 1122 apr_pool_t *pool) 1123{ 1124 svn_ra_serf__session_t *session = ra_session->priv; 1125 1126 if (!session->uuid) 1127 { 1128 const char *vcc_url; 1129 1130 /* We should never get here if we have HTTP v2 support, because 1131 any server with that support should be transmitting the 1132 UUID in the initial OPTIONS response. */ 1133 SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 1134 1135 /* We're not interested in vcc_url and relative_url, but this call also 1136 stores the repository's uuid in the session. */ 1137 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); 1138 if (!session->uuid) 1139 { 1140 return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL, 1141 _("The UUID property was not found on the " 1142 "resource or any of its parents")); 1143 } 1144 } 1145 1146 *uuid = session->uuid; 1147 1148 return SVN_NO_ERROR; 1149} 1150 1151 1152static const svn_ra__vtable_t serf_vtable = { 1153 ra_serf_version, 1154 ra_serf_get_description, 1155 ra_serf_get_schemes, 1156 svn_ra_serf__open, 1157 svn_ra_serf__reparent, 1158 svn_ra_serf__get_session_url, 1159 svn_ra_serf__get_latest_revnum, 1160 svn_ra_serf__get_dated_revision, 1161 svn_ra_serf__change_rev_prop, 1162 svn_ra_serf__rev_proplist, 1163 svn_ra_serf__rev_prop, 1164 svn_ra_serf__get_commit_editor, 1165 svn_ra_serf__get_file, 1166 svn_ra_serf__get_dir, 1167 svn_ra_serf__get_mergeinfo, 1168 svn_ra_serf__do_update, 1169 svn_ra_serf__do_switch, 1170 svn_ra_serf__do_status, 1171 svn_ra_serf__do_diff, 1172 svn_ra_serf__get_log, 1173 svn_ra_serf__check_path, 1174 svn_ra_serf__stat, 1175 svn_ra_serf__get_uuid, 1176 svn_ra_serf__get_repos_root, 1177 svn_ra_serf__get_locations, 1178 svn_ra_serf__get_location_segments, 1179 svn_ra_serf__get_file_revs, 1180 svn_ra_serf__lock, 1181 svn_ra_serf__unlock, 1182 svn_ra_serf__get_lock, 1183 svn_ra_serf__get_locks, 1184 svn_ra_serf__replay, 1185 svn_ra_serf__has_capability, 1186 svn_ra_serf__replay_range, 1187 svn_ra_serf__get_deleted_rev, 1188 svn_ra_serf__register_editor_shim_callbacks, 1189 svn_ra_serf__get_inherited_props 1190}; 1191 1192svn_error_t * 1193svn_ra_serf__init(const svn_version_t *loader_version, 1194 const svn_ra__vtable_t **vtable, 1195 apr_pool_t *pool) 1196{ 1197 static const svn_version_checklist_t checklist[] = 1198 { 1199 { "svn_subr", svn_subr_version }, 1200 { "svn_delta", svn_delta_version }, 1201 { NULL, NULL } 1202 }; 1203 int serf_major; 1204 int serf_minor; 1205 int serf_patch; 1206 1207 SVN_ERR(svn_ver_check_list(ra_serf_version(), checklist)); 1208 1209 /* Simplified version check to make sure we can safely use the 1210 VTABLE parameter. The RA loader does a more exhaustive check. */ 1211 if (loader_version->major != SVN_VER_MAJOR) 1212 { 1213 return svn_error_createf( 1214 SVN_ERR_VERSION_MISMATCH, NULL, 1215 _("Unsupported RA loader version (%d) for ra_serf"), 1216 loader_version->major); 1217 } 1218 1219 /* Make sure that we have loaded a compatible library: the MAJOR must 1220 match, and the minor must be at *least* what we compiled against. 1221 The patch level is simply ignored. */ 1222 serf_lib_version(&serf_major, &serf_minor, &serf_patch); 1223 if (serf_major != SERF_MAJOR_VERSION 1224 || serf_minor < SERF_MINOR_VERSION) 1225 { 1226 return svn_error_createf( 1227 /* ### should return a unique error */ 1228 SVN_ERR_VERSION_MISMATCH, NULL, 1229 _("ra_serf was compiled for serf %d.%d.%d but loaded " 1230 "an incompatible %d.%d.%d library"), 1231 SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION, 1232 serf_major, serf_minor, serf_patch); 1233 } 1234 1235 *vtable = &serf_vtable; 1236 1237 return SVN_NO_ERROR; 1238} 1239 1240/* Compatibility wrapper for pre-1.2 subversions. Needed? */ 1241#define NAME "ra_serf" 1242#define DESCRIPTION RA_SERF_DESCRIPTION 1243#define VTBL serf_vtable 1244#define INITFUNC svn_ra_serf__init 1245#define COMPAT_INITFUNC svn_ra_serf_init 1246#include "../libsvn_ra/wrapper_template.h" 1247