util.c revision 362181
1/* 2 * util.c : serf utility routines 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#include <assert.h> 27 28#define APR_WANT_STRFUNC 29#include <apr.h> 30#include <apr_want.h> 31 32#include <serf.h> 33#include <serf_bucket_types.h> 34 35#include "svn_hash.h" 36#include "svn_dirent_uri.h" 37#include "svn_path.h" 38#include "svn_private_config.h" 39#include "svn_string.h" 40#include "svn_props.h" 41#include "svn_dirent_uri.h" 42 43#include "../libsvn_ra/ra_loader.h" 44#include "private/svn_dep_compat.h" 45#include "private/svn_fspath.h" 46#include "private/svn_auth_private.h" 47#include "private/svn_cert.h" 48 49#include "ra_serf.h" 50 51static const apr_uint32_t serf_failure_map[][2] = 52{ 53 { SERF_SSL_CERT_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID }, 54 { SERF_SSL_CERT_EXPIRED, SVN_AUTH_SSL_EXPIRED }, 55 { SERF_SSL_CERT_SELF_SIGNED, SVN_AUTH_SSL_UNKNOWNCA }, 56 { SERF_SSL_CERT_UNKNOWNCA, SVN_AUTH_SSL_UNKNOWNCA } 57}; 58 59/* Return a Subversion failure mask based on FAILURES, a serf SSL 60 failure mask. If anything in FAILURES is not directly mappable to 61 Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */ 62static apr_uint32_t 63ssl_convert_serf_failures(int failures) 64{ 65 apr_uint32_t svn_failures = 0; 66 apr_size_t i; 67 68 for (i = 0; 69 i < sizeof(serf_failure_map) / (sizeof(serf_failure_map[0])); 70 ++i) 71 { 72 if (failures & serf_failure_map[i][0]) 73 { 74 svn_failures |= serf_failure_map[i][1]; 75 failures &= ~serf_failure_map[i][0]; 76 } 77 } 78 79 /* Map any remaining failure bits to our OTHER bit. */ 80 if (failures) 81 { 82 svn_failures |= SVN_AUTH_SSL_OTHER; 83 } 84 85 return svn_failures; 86} 87 88 89static apr_status_t 90save_error(svn_ra_serf__session_t *session, 91 svn_error_t *err) 92{ 93 if (err || session->pending_error) 94 { 95 session->pending_error = svn_error_compose_create( 96 session->pending_error, 97 err); 98 return session->pending_error->apr_err; 99 } 100 101 return APR_SUCCESS; 102} 103 104 105/* Construct the realmstring, e.g. https://svn.collab.net:443. */ 106static const char * 107construct_realm(svn_ra_serf__session_t *session, 108 apr_pool_t *pool) 109{ 110 const char *realm; 111 apr_port_t port; 112 113 if (session->session_url.port_str) 114 { 115 port = session->session_url.port; 116 } 117 else 118 { 119 port = apr_uri_port_of_scheme(session->session_url.scheme); 120 } 121 122 realm = apr_psprintf(pool, "%s://%s:%d", 123 session->session_url.scheme, 124 session->session_url.hostname, 125 port); 126 127 return realm; 128} 129 130/* Convert a hash table containing the fields (as documented in X.509) of an 131 organisation to a string ORG, allocated in POOL. ORG is as returned by 132 serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */ 133static char * 134convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool) 135{ 136 const char *cn = svn_hash_gets(org, "CN"); 137 const char *org_unit = svn_hash_gets(org, "OU"); 138 const char *org_name = svn_hash_gets(org, "O"); 139 const char *locality = svn_hash_gets(org, "L"); 140 const char *state = svn_hash_gets(org, "ST"); 141 const char *country = svn_hash_gets(org, "C"); 142 const char *email = svn_hash_gets(org, "E"); 143 svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool); 144 145 if (cn) 146 { 147 svn_stringbuf_appendcstr(buf, cn); 148 svn_stringbuf_appendcstr(buf, ", "); 149 } 150 151 if (org_unit) 152 { 153 svn_stringbuf_appendcstr(buf, org_unit); 154 svn_stringbuf_appendcstr(buf, ", "); 155 } 156 157 if (org_name) 158 { 159 svn_stringbuf_appendcstr(buf, org_name); 160 svn_stringbuf_appendcstr(buf, ", "); 161 } 162 163 if (locality) 164 { 165 svn_stringbuf_appendcstr(buf, locality); 166 svn_stringbuf_appendcstr(buf, ", "); 167 } 168 169 if (state) 170 { 171 svn_stringbuf_appendcstr(buf, state); 172 svn_stringbuf_appendcstr(buf, ", "); 173 } 174 175 if (country) 176 { 177 svn_stringbuf_appendcstr(buf, country); 178 svn_stringbuf_appendcstr(buf, ", "); 179 } 180 181 /* Chop ', ' if any. */ 182 svn_stringbuf_chop(buf, 2); 183 184 if (email) 185 { 186 svn_stringbuf_appendcstr(buf, "("); 187 svn_stringbuf_appendcstr(buf, email); 188 svn_stringbuf_appendcstr(buf, ")"); 189 } 190 191 return buf->data; 192} 193 194static void append_reason(svn_stringbuf_t *errmsg, const char *reason, int *reasons) 195{ 196 if (*reasons < 1) 197 svn_stringbuf_appendcstr(errmsg, _(": ")); 198 else 199 svn_stringbuf_appendcstr(errmsg, _(", ")); 200 svn_stringbuf_appendcstr(errmsg, reason); 201 (*reasons)++; 202} 203 204/* This function is called on receiving a ssl certificate of a server when 205 opening a https connection. It allows Subversion to override the initial 206 validation done by serf. 207 Serf provides us the @a baton as provided in the call to 208 serf_ssl_server_cert_callback_set. The result of serf's initial validation 209 of the certificate @a CERT is returned as a bitmask in FAILURES. */ 210static svn_error_t * 211ssl_server_cert(void *baton, int failures, 212 const serf_ssl_certificate_t *cert, 213 apr_pool_t *scratch_pool) 214{ 215 svn_ra_serf__connection_t *conn = baton; 216 svn_auth_ssl_server_cert_info_t cert_info; 217 svn_auth_cred_ssl_server_trust_t *server_creds = NULL; 218 svn_auth_iterstate_t *state; 219 const char *realmstring; 220 apr_uint32_t svn_failures; 221 apr_hash_t *issuer; 222 apr_hash_t *subject = NULL; 223 apr_hash_t *serf_cert = NULL; 224 void *creds; 225 226 svn_failures = (ssl_convert_serf_failures(failures) 227 | conn->server_cert_failures); 228 229 if (serf_ssl_cert_depth(cert) == 0) 230 { 231 /* If the depth is 0, the hostname must match the certificate. 232 233 ### This should really be handled by serf, which should pass an error 234 for this case, but that has backwards compatibility issues. */ 235 apr_array_header_t *san; 236 svn_boolean_t found_matching_hostname = FALSE; 237 svn_string_t *actual_hostname = 238 svn_string_create(conn->session->session_url.hostname, scratch_pool); 239 240 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool); 241 242 san = svn_hash_gets(serf_cert, "subjectAltName"); 243 /* Match server certificate CN with the hostname of the server iff 244 * we didn't find any subjectAltName fields and try to match them. 245 * Per RFC 2818 they are authoritative if present and CommonName 246 * should be ignored. NOTE: This isn't 100% correct since serf 247 * only loads the subjectAltName hash with dNSNames, technically 248 * we should ignore the CommonName if any subjectAltName entry 249 * exists even if it is one we don't support. */ 250 if (san && san->nelts > 0) 251 { 252 int i; 253 for (i = 0; i < san->nelts; i++) 254 { 255 const char *s = APR_ARRAY_IDX(san, i, const char*); 256 svn_string_t *cert_hostname = svn_string_create(s, scratch_pool); 257 258 if (svn_cert__match_dns_identity(cert_hostname, actual_hostname)) 259 { 260 found_matching_hostname = TRUE; 261 break; 262 } 263 } 264 } 265 else 266 { 267 const char *hostname = NULL; 268 269 subject = serf_ssl_cert_subject(cert, scratch_pool); 270 271 if (subject) 272 hostname = svn_hash_gets(subject, "CN"); 273 274 if (hostname) 275 { 276 svn_string_t *cert_hostname = svn_string_create(hostname, 277 scratch_pool); 278 279 if (svn_cert__match_dns_identity(cert_hostname, actual_hostname)) 280 { 281 found_matching_hostname = TRUE; 282 } 283 } 284 } 285 286 if (!found_matching_hostname) 287 svn_failures |= SVN_AUTH_SSL_CNMISMATCH; 288 } 289 290 if (!svn_failures) 291 return SVN_NO_ERROR; 292 293 /* Extract the info from the certificate */ 294 if (! subject) 295 subject = serf_ssl_cert_subject(cert, scratch_pool); 296 issuer = serf_ssl_cert_issuer(cert, scratch_pool); 297 if (! serf_cert) 298 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool); 299 300 cert_info.hostname = svn_hash_gets(subject, "CN"); 301 cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1"); 302 if (! cert_info.fingerprint) 303 cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>"); 304 cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore"); 305 if (! cert_info.valid_from) 306 cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]"); 307 cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter"); 308 if (! cert_info.valid_until) 309 cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]"); 310 cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool); 311 cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool); 312 313 /* Handle any non-server certs. */ 314 if (serf_ssl_cert_depth(cert) > 0) 315 { 316 svn_error_t *err; 317 318 svn_auth_set_parameter(conn->session->auth_baton, 319 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, 320 &cert_info); 321 322 svn_auth_set_parameter(conn->session->auth_baton, 323 SVN_AUTH_PARAM_SSL_SERVER_FAILURES, 324 &svn_failures); 325 326 realmstring = apr_psprintf(scratch_pool, "AUTHORITY:%s", 327 cert_info.fingerprint); 328 329 err = svn_auth_first_credentials(&creds, &state, 330 SVN_AUTH_CRED_SSL_SERVER_AUTHORITY, 331 realmstring, 332 conn->session->auth_baton, 333 scratch_pool); 334 335 svn_auth_set_parameter(conn->session->auth_baton, 336 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL); 337 338 svn_auth_set_parameter(conn->session->auth_baton, 339 SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL); 340 341 if (err) 342 { 343 if (err->apr_err != SVN_ERR_AUTHN_NO_PROVIDER) 344 return svn_error_trace(err); 345 346 /* No provider registered that handles server authorities */ 347 svn_error_clear(err); 348 creds = NULL; 349 } 350 351 if (creds) 352 { 353 server_creds = creds; 354 SVN_ERR(svn_auth_save_credentials(state, scratch_pool)); 355 356 svn_failures &= ~server_creds->accepted_failures; 357 } 358 359 if (svn_failures) 360 conn->server_cert_failures |= svn_failures; 361 362 return APR_SUCCESS; 363 } 364 365 svn_auth_set_parameter(conn->session->auth_baton, 366 SVN_AUTH_PARAM_SSL_SERVER_FAILURES, 367 &svn_failures); 368 369 svn_auth_set_parameter(conn->session->auth_baton, 370 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, 371 &cert_info); 372 373 realmstring = construct_realm(conn->session, conn->session->pool); 374 375 SVN_ERR(svn_auth_first_credentials(&creds, &state, 376 SVN_AUTH_CRED_SSL_SERVER_TRUST, 377 realmstring, 378 conn->session->auth_baton, 379 scratch_pool)); 380 if (creds) 381 { 382 server_creds = creds; 383 svn_failures &= ~server_creds->accepted_failures; 384 SVN_ERR(svn_auth_save_credentials(state, scratch_pool)); 385 } 386 387 while (svn_failures && creds) 388 { 389 SVN_ERR(svn_auth_next_credentials(&creds, state, scratch_pool)); 390 391 if (creds) 392 { 393 server_creds = creds; 394 svn_failures &= ~server_creds->accepted_failures; 395 SVN_ERR(svn_auth_save_credentials(state, scratch_pool)); 396 } 397 } 398 399 svn_auth_set_parameter(conn->session->auth_baton, 400 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL); 401 402 /* Are there non accepted failures left? */ 403 if (svn_failures) 404 { 405 svn_stringbuf_t *errmsg; 406 int reasons = 0; 407 408 errmsg = svn_stringbuf_create( 409 _("Server SSL certificate verification failed"), 410 scratch_pool); 411 412 413 if (svn_failures & SVN_AUTH_SSL_NOTYETVALID) 414 append_reason(errmsg, _("certificate is not yet valid"), &reasons); 415 416 if (svn_failures & SVN_AUTH_SSL_EXPIRED) 417 append_reason(errmsg, _("certificate has expired"), &reasons); 418 419 if (svn_failures & SVN_AUTH_SSL_CNMISMATCH) 420 append_reason(errmsg, 421 _("certificate issued for a different hostname"), 422 &reasons); 423 424 if (svn_failures & SVN_AUTH_SSL_UNKNOWNCA) 425 append_reason(errmsg, _("issuer is not trusted"), &reasons); 426 427 if (svn_failures & SVN_AUTH_SSL_OTHER) 428 append_reason(errmsg, _("and other reason(s)"), &reasons); 429 430 return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL, 431 errmsg->data); 432 } 433 434 return SVN_NO_ERROR; 435} 436 437/* Implements serf_ssl_need_server_cert_t for ssl_server_cert */ 438static apr_status_t 439ssl_server_cert_cb(void *baton, int failures, 440 const serf_ssl_certificate_t *cert) 441{ 442 svn_ra_serf__connection_t *conn = baton; 443 svn_ra_serf__session_t *session = conn->session; 444 apr_pool_t *subpool; 445 svn_error_t *err; 446 447 subpool = svn_pool_create(session->pool); 448 err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool)); 449 svn_pool_destroy(subpool); 450 451 return save_error(session, err); 452} 453 454static svn_error_t * 455load_authorities(svn_ra_serf__connection_t *conn, const char *authorities, 456 apr_pool_t *pool) 457{ 458 apr_array_header_t *files = svn_cstring_split(authorities, ";", 459 TRUE /* chop_whitespace */, 460 pool); 461 int i; 462 463 for (i = 0; i < files->nelts; ++i) 464 { 465 const char *file = APR_ARRAY_IDX(files, i, const char *); 466 serf_ssl_certificate_t *ca_cert; 467 apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool); 468 469 if (status == APR_SUCCESS) 470 status = serf_ssl_trust_cert(conn->ssl_context, ca_cert); 471 472 if (status != APR_SUCCESS) 473 { 474 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, 475 _("Invalid config: unable to load certificate file '%s'"), 476 svn_dirent_local_style(file, pool)); 477 } 478 } 479 480 return SVN_NO_ERROR; 481} 482 483#if SERF_VERSION_AT_LEAST(1, 4, 0) && defined(SVN__SERF_TEST_HTTP2) 484/* Implements serf_ssl_protocol_result_cb_t */ 485static apr_status_t 486conn_negotiate_protocol(void *data, 487 const char *protocol) 488{ 489 svn_ra_serf__connection_t *conn = data; 490 491 if (!strcmp(protocol, "h2")) 492 { 493 serf_connection_set_framing_type( 494 conn->conn, 495 SERF_CONNECTION_FRAMING_TYPE_HTTP2); 496 497 /* Disable generating content-length headers. */ 498 conn->session->http10 = FALSE; 499 conn->session->http20 = TRUE; 500 conn->session->using_chunked_requests = TRUE; 501 conn->session->detect_chunking = FALSE; 502 } 503 else 504 { 505 /* protocol should be "" or "http/1.1" */ 506 serf_connection_set_framing_type( 507 conn->conn, 508 SERF_CONNECTION_FRAMING_TYPE_HTTP1); 509 } 510 511 return APR_SUCCESS; 512} 513#endif 514 515static svn_error_t * 516conn_setup(apr_socket_t *sock, 517 serf_bucket_t **read_bkt, 518 serf_bucket_t **write_bkt, 519 void *baton, 520 apr_pool_t *pool) 521{ 522 svn_ra_serf__connection_t *conn = baton; 523 524 *read_bkt = serf_context_bucket_socket_create(conn->session->context, 525 sock, conn->bkt_alloc); 526 527 if (conn->session->using_ssl) 528 { 529 /* input stream */ 530 *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context, 531 conn->bkt_alloc); 532 if (!conn->ssl_context) 533 { 534 conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt); 535 536 serf_ssl_set_hostname(conn->ssl_context, 537 conn->session->session_url.hostname); 538 539 serf_ssl_client_cert_provider_set(conn->ssl_context, 540 svn_ra_serf__handle_client_cert, 541 conn, conn->session->pool); 542 serf_ssl_client_cert_password_set(conn->ssl_context, 543 svn_ra_serf__handle_client_cert_pw, 544 conn, conn->session->pool); 545 serf_ssl_server_cert_callback_set(conn->ssl_context, 546 ssl_server_cert_cb, 547 conn); 548 549 /* See if the user wants us to trust "default" openssl CAs. */ 550 if (conn->session->trust_default_ca) 551 { 552 serf_ssl_use_default_certificates(conn->ssl_context); 553 } 554 /* Are there custom CAs to load? */ 555 if (conn->session->ssl_authorities) 556 { 557 SVN_ERR(load_authorities(conn, conn->session->ssl_authorities, 558 conn->session->pool)); 559 } 560#if SERF_VERSION_AT_LEAST(1, 4, 0) && defined(SVN__SERF_TEST_HTTP2) 561 if (APR_SUCCESS == 562 serf_ssl_negotiate_protocol(conn->ssl_context, "h2,http/1.1", 563 conn_negotiate_protocol, conn)) 564 { 565 serf_connection_set_framing_type( 566 conn->conn, 567 SERF_CONNECTION_FRAMING_TYPE_NONE); 568 } 569#endif 570 } 571 572 if (write_bkt) 573 { 574 /* output stream */ 575 *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt, 576 conn->ssl_context, 577 conn->bkt_alloc); 578 } 579 } 580 581 return SVN_NO_ERROR; 582} 583 584/* svn_ra_serf__conn_setup is a callback for serf. This function 585 creates a read bucket and will wrap the write bucket if SSL 586 is needed. */ 587apr_status_t 588svn_ra_serf__conn_setup(apr_socket_t *sock, 589 serf_bucket_t **read_bkt, 590 serf_bucket_t **write_bkt, 591 void *baton, 592 apr_pool_t *pool) 593{ 594 svn_ra_serf__connection_t *conn = baton; 595 svn_ra_serf__session_t *session = conn->session; 596 svn_error_t *err; 597 598 err = svn_error_trace(conn_setup(sock, 599 read_bkt, 600 write_bkt, 601 baton, 602 pool)); 603 return save_error(session, err); 604} 605 606 607/* Our default serf response acceptor. */ 608static serf_bucket_t * 609accept_response(serf_request_t *request, 610 serf_bucket_t *stream, 611 void *acceptor_baton, 612 apr_pool_t *pool) 613{ 614 /* svn_ra_serf__handler_t *handler = acceptor_baton; */ 615 serf_bucket_t *c; 616 serf_bucket_alloc_t *bkt_alloc; 617 618 bkt_alloc = serf_request_get_alloc(request); 619 c = serf_bucket_barrier_create(stream, bkt_alloc); 620 621 return serf_bucket_response_create(c, bkt_alloc); 622} 623 624 625/* Custom response acceptor for HEAD requests. */ 626static serf_bucket_t * 627accept_head(serf_request_t *request, 628 serf_bucket_t *stream, 629 void *acceptor_baton, 630 apr_pool_t *pool) 631{ 632 /* svn_ra_serf__handler_t *handler = acceptor_baton; */ 633 serf_bucket_t *response; 634 635 response = accept_response(request, stream, acceptor_baton, pool); 636 637 /* We know we shouldn't get a response body. */ 638 serf_bucket_response_set_head(response); 639 640 return response; 641} 642 643static svn_error_t * 644connection_closed(svn_ra_serf__connection_t *conn, 645 apr_status_t why, 646 apr_pool_t *pool) 647{ 648 if (why) 649 { 650 return svn_ra_serf__wrap_err(why, NULL); 651 } 652 653 if (conn->session->using_ssl) 654 conn->ssl_context = NULL; 655 656 return SVN_NO_ERROR; 657} 658 659void 660svn_ra_serf__conn_closed(serf_connection_t *conn, 661 void *closed_baton, 662 apr_status_t why, 663 apr_pool_t *pool) 664{ 665 svn_ra_serf__connection_t *ra_conn = closed_baton; 666 svn_error_t *err; 667 668 err = svn_error_trace(connection_closed(ra_conn, why, pool)); 669 670 (void) save_error(ra_conn->session, err); 671} 672 673 674/* Implementation of svn_ra_serf__handle_client_cert */ 675static svn_error_t * 676handle_client_cert(void *data, 677 const char **cert_path, 678 apr_pool_t *pool) 679{ 680 svn_ra_serf__connection_t *conn = data; 681 svn_ra_serf__session_t *session = conn->session; 682 const char *realm; 683 void *creds; 684 685 *cert_path = NULL; 686 687 realm = construct_realm(session, session->pool); 688 689 if (!conn->ssl_client_auth_state) 690 { 691 SVN_ERR(svn_auth_first_credentials(&creds, 692 &conn->ssl_client_auth_state, 693 SVN_AUTH_CRED_SSL_CLIENT_CERT, 694 realm, 695 session->auth_baton, 696 pool)); 697 } 698 else 699 { 700 SVN_ERR(svn_auth_next_credentials(&creds, 701 conn->ssl_client_auth_state, 702 session->pool)); 703 } 704 705 if (creds) 706 { 707 svn_auth_cred_ssl_client_cert_t *client_creds; 708 client_creds = creds; 709 *cert_path = client_creds->cert_file; 710 } 711 712 return SVN_NO_ERROR; 713} 714 715/* Implements serf_ssl_need_client_cert_t for handle_client_cert */ 716apr_status_t svn_ra_serf__handle_client_cert(void *data, 717 const char **cert_path) 718{ 719 svn_ra_serf__connection_t *conn = data; 720 svn_ra_serf__session_t *session = conn->session; 721 svn_error_t *err; 722 723 err = svn_error_trace(handle_client_cert(data, cert_path, session->pool)); 724 725 return save_error(session, err); 726} 727 728/* Implementation for svn_ra_serf__handle_client_cert_pw */ 729static svn_error_t * 730handle_client_cert_pw(void *data, 731 const char *cert_path, 732 const char **password, 733 apr_pool_t *pool) 734{ 735 svn_ra_serf__connection_t *conn = data; 736 svn_ra_serf__session_t *session = conn->session; 737 void *creds; 738 739 *password = NULL; 740 741 if (!conn->ssl_client_pw_auth_state) 742 { 743 SVN_ERR(svn_auth_first_credentials(&creds, 744 &conn->ssl_client_pw_auth_state, 745 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, 746 cert_path, 747 session->auth_baton, 748 pool)); 749 } 750 else 751 { 752 SVN_ERR(svn_auth_next_credentials(&creds, 753 conn->ssl_client_pw_auth_state, 754 pool)); 755 } 756 757 if (creds) 758 { 759 /* At this stage we are unable to check whether the password 760 is correct; if it is incorrect serf will fail to establish 761 an SSL connection and will return a generic SSL error. */ 762 svn_auth_cred_ssl_client_cert_pw_t *pw_creds; 763 pw_creds = creds; 764 *password = pw_creds->password; 765 } 766 767 return APR_SUCCESS; 768} 769 770/* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */ 771apr_status_t svn_ra_serf__handle_client_cert_pw(void *data, 772 const char *cert_path, 773 const char **password) 774{ 775 svn_ra_serf__connection_t *conn = data; 776 svn_ra_serf__session_t *session = conn->session; 777 svn_error_t *err; 778 779 err = svn_error_trace(handle_client_cert_pw(data, 780 cert_path, 781 password, 782 session->pool)); 783 784 return save_error(session, err); 785} 786 787 788/* 789 * Given a REQUEST on connection CONN, construct a request bucket for it, 790 * returning the bucket in *REQ_BKT. 791 * 792 * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that 793 * corresponds to the new request. 794 * 795 * The request will be METHOD at URL. 796 * 797 * If BODY_BKT is not-NULL, it will be sent as the request body. 798 * 799 * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header. 800 * 801 * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers 802 * to request. 803 * 804 * REQUEST_POOL should live for the duration of the request. Serf will 805 * construct this and provide it to the request_setup callback, so we 806 * should just use that one. 807 */ 808static svn_error_t * 809setup_serf_req(serf_request_t *request, 810 serf_bucket_t **req_bkt, 811 serf_bucket_t **hdrs_bkt, 812 svn_ra_serf__session_t *session, 813 const char *method, const char *url, 814 serf_bucket_t *body_bkt, const char *content_type, 815 const char *accept_encoding, 816 svn_boolean_t dav_headers, 817 apr_pool_t *request_pool, 818 apr_pool_t *scratch_pool) 819{ 820 serf_bucket_alloc_t *allocator = serf_request_get_alloc(request); 821 822 svn_spillbuf_t *buf; 823 svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests; 824 825 if (set_CL && body_bkt != NULL) 826 { 827 /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if 828 it speaks HTTP/1.1 (and thus, chunked requests), or because the 829 server actually responded as only supporting HTTP/1.0. 830 831 We'll take the existing body_bkt, spool it into a spillbuf, and 832 then wrap a bucket around that spillbuf. The spillbuf will give 833 us the Content-Length value. */ 834 SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt, 835 request_pool, 836 scratch_pool)); 837 /* Destroy original bucket since it content is already copied 838 to spillbuf. */ 839 serf_bucket_destroy(body_bkt); 840 841 body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator, 842 request_pool, 843 scratch_pool); 844 } 845 846 /* Create a request bucket. Note that this sucker is kind enough to 847 add a "Host" header for us. */ 848 *req_bkt = serf_request_bucket_request_create(request, method, url, 849 body_bkt, allocator); 850 851 /* Set the Content-Length value. This will also trigger an HTTP/1.0 852 request (rather than the default chunked request). */ 853 if (set_CL) 854 { 855 if (body_bkt == NULL) 856 serf_bucket_request_set_CL(*req_bkt, 0); 857 else 858 serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf)); 859 } 860 861 *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt); 862 863 /* We use serf_bucket_headers_setn() because the USERAGENT has a 864 lifetime longer than this bucket. Thus, there is no need to copy 865 the header values. */ 866 serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent); 867 868 if (content_type) 869 { 870 serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type); 871 } 872 873 if (session->http10) 874 { 875 serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive"); 876 } 877 878 if (accept_encoding) 879 { 880 serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding); 881 } 882 883 /* These headers need to be sent with every request that might need 884 capability processing (e.g. during commit, reports, etc.), see 885 issue #3255 ("mod_dav_svn does not pass client capabilities to 886 start-commit hooks") for why. 887 888 Some request types like GET/HEAD/PROPFIND are unaware of capability 889 handling; and in some cases the responses can even be cached by 890 proxies, so we don't have to send these hearders there. */ 891 if (dav_headers) 892 { 893 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH); 894 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO); 895 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS); 896 } 897 898 return SVN_NO_ERROR; 899} 900 901svn_error_t * 902svn_ra_serf__context_run(svn_ra_serf__session_t *sess, 903 apr_interval_time_t *waittime_left, 904 apr_pool_t *scratch_pool) 905{ 906 apr_status_t status; 907 svn_error_t *err; 908 assert(sess->pending_error == SVN_NO_ERROR); 909 910 if (sess->cancel_func) 911 SVN_ERR(sess->cancel_func(sess->cancel_baton)); 912 913 status = serf_context_run(sess->context, 914 SVN_RA_SERF__CONTEXT_RUN_DURATION, 915 scratch_pool); 916 917 err = sess->pending_error; 918 sess->pending_error = SVN_NO_ERROR; 919 920 /* If the context duration timeout is up, we'll subtract that 921 duration from the total time alloted for such things. If 922 there's no time left, we fail with a message indicating that 923 the connection timed out. */ 924 if (APR_STATUS_IS_TIMEUP(status)) 925 { 926 status = 0; 927 928 if (sess->timeout) 929 { 930 if (*waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) 931 { 932 *waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; 933 } 934 else 935 { 936 return 937 svn_error_compose_create( 938 err, 939 svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, 940 _("Connection timed out"))); 941 } 942 } 943 } 944 else 945 { 946 *waittime_left = sess->timeout; 947 } 948 949 SVN_ERR(err); 950 if (status) 951 { 952 /* ### This omits SVN_WARNING, and possibly relies on the fact that 953 ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */ 954 if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST) 955 { 956 /* apr can't translate subversion errors to text */ 957 SVN_ERR_W(svn_error_create(status, NULL, NULL), 958 _("Error running context")); 959 } 960 961 return svn_ra_serf__wrap_err(status, _("Error running context")); 962 } 963 964 return SVN_NO_ERROR; 965} 966 967svn_error_t * 968svn_ra_serf__context_run_wait(svn_boolean_t *done, 969 svn_ra_serf__session_t *sess, 970 apr_pool_t *scratch_pool) 971{ 972 apr_pool_t *iterpool; 973 apr_interval_time_t waittime_left = sess->timeout; 974 975 assert(sess->pending_error == SVN_NO_ERROR); 976 977 iterpool = svn_pool_create(scratch_pool); 978 while (!*done) 979 { 980 int i; 981 982 svn_pool_clear(iterpool); 983 984 SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool)); 985 986 /* Debugging purposes only! */ 987 for (i = 0; i < sess->num_conns; i++) 988 { 989 serf_debug__closed_conn(sess->conns[i]->bkt_alloc); 990 } 991 } 992 svn_pool_destroy(iterpool); 993 994 return SVN_NO_ERROR; 995} 996 997/* Ensure that a handler is no longer scheduled on the connection. 998 999 Eventually serf will have a reliable way to cancel existing requests, 1000 but currently it doesn't even have a way to relyable identify a request 1001 after rescheduling, for auth reasons. 1002 1003 So the only thing we can do today is reset the connection, which 1004 will cancel all outstanding requests and prepare the connection 1005 for re-use. 1006*/ 1007void 1008svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler) 1009{ 1010 serf_connection_reset(handler->conn->conn); 1011 handler->scheduled = FALSE; 1012} 1013 1014svn_error_t * 1015svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler, 1016 apr_pool_t *scratch_pool) 1017{ 1018 svn_error_t *err; 1019 1020 /* Create a serf request based on HANDLER. */ 1021 svn_ra_serf__request_create(handler); 1022 1023 /* Wait until the response logic marks its DONE status. */ 1024 err = svn_ra_serf__context_run_wait(&handler->done, handler->session, 1025 scratch_pool); 1026 1027 if (handler->scheduled) 1028 { 1029 /* We reset the connection (breaking pipelining, etc.), as 1030 if we didn't the next data would still be handled by this handler, 1031 which is done as far as our caller is concerned. */ 1032 svn_ra_serf__unschedule_handler(handler); 1033 } 1034 1035 return svn_error_trace(err); 1036} 1037 1038 1039 1040 1041static apr_status_t 1042drain_bucket(serf_bucket_t *bucket) 1043{ 1044 /* Read whatever is in the bucket, and just drop it. */ 1045 while (1) 1046 { 1047 apr_status_t status; 1048 const char *data; 1049 apr_size_t len; 1050 1051 status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len); 1052 if (status) 1053 return status; 1054 } 1055} 1056 1057 1058 1059 1060/* Implements svn_ra_serf__response_handler_t */ 1061svn_error_t * 1062svn_ra_serf__handle_discard_body(serf_request_t *request, 1063 serf_bucket_t *response, 1064 void *baton, 1065 apr_pool_t *pool) 1066{ 1067 apr_status_t status; 1068 1069 status = drain_bucket(response); 1070 if (status) 1071 return svn_ra_serf__wrap_err(status, NULL); 1072 1073 return SVN_NO_ERROR; 1074} 1075 1076apr_status_t 1077svn_ra_serf__response_discard_handler(serf_request_t *request, 1078 serf_bucket_t *response, 1079 void *baton, 1080 apr_pool_t *pool) 1081{ 1082 return drain_bucket(response); 1083} 1084 1085 1086/* Return the value of the RESPONSE's Location header if any, or NULL 1087 otherwise. */ 1088static const char * 1089response_get_location(serf_bucket_t *response, 1090 const char *base_url, 1091 apr_pool_t *result_pool, 1092 apr_pool_t *scratch_pool) 1093{ 1094 serf_bucket_t *headers; 1095 const char *location; 1096 1097 headers = serf_bucket_response_get_headers(response); 1098 location = serf_bucket_headers_get(headers, "Location"); 1099 if (location == NULL) 1100 return NULL; 1101 1102 /* The RFCs say we should have received a full url in LOCATION, but 1103 older apache versions and many custom web handlers just return a 1104 relative path here... 1105 1106 And we can't trust anything because it is network data. 1107 */ 1108 if (*location == '/') 1109 { 1110 apr_uri_t uri; 1111 apr_status_t status; 1112 1113 status = apr_uri_parse(scratch_pool, base_url, &uri); 1114 1115 if (status != APR_SUCCESS) 1116 return NULL; 1117 1118 /* Replace the path path with what we got */ 1119 uri.path = apr_pstrdup(scratch_pool, location); 1120 1121 /* And make APR produce a proper full url for us */ 1122 return apr_uri_unparse(result_pool, &uri, 0); 1123 } 1124 else if (!svn_path_is_url(location)) 1125 { 1126 return NULL; /* Any other formats we should support? */ 1127 } 1128 1129 return apr_pstrdup(result_pool, location); 1130} 1131 1132 1133/* Implements svn_ra_serf__response_handler_t */ 1134svn_error_t * 1135svn_ra_serf__expect_empty_body(serf_request_t *request, 1136 serf_bucket_t *response, 1137 void *baton, 1138 apr_pool_t *scratch_pool) 1139{ 1140 svn_ra_serf__handler_t *handler = baton; 1141 serf_bucket_t *hdrs; 1142 const char *val; 1143 1144 /* This function is just like handle_multistatus_only() except for the 1145 XML parsing callbacks. We want to look for the -readable element. */ 1146 1147 /* We should see this just once, in order to initialize SERVER_ERROR. 1148 At that point, the core error processing will take over. If we choose 1149 not to parse an error, then we'll never return here (because we 1150 change the response handler). */ 1151 SVN_ERR_ASSERT(handler->server_error == NULL); 1152 1153 hdrs = serf_bucket_response_get_headers(response); 1154 val = serf_bucket_headers_get(hdrs, "Content-Type"); 1155 if (val 1156 && (handler->sline.code < 200 || handler->sline.code >= 300) 1157 && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) 1158 { 1159 svn_ra_serf__server_error_t *server_err; 1160 1161 SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler, 1162 FALSE, 1163 handler->handler_pool, 1164 handler->handler_pool)); 1165 1166 handler->server_error = server_err; 1167 } 1168 else 1169 { 1170 /* The body was not text/xml, or we got a success code. 1171 Toss anything that arrives. */ 1172 handler->discard_body = TRUE; 1173 } 1174 1175 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it 1176 to call the response handler again. That will start up the XML parsing, 1177 or it will be dropped on the floor (per the decision above). */ 1178 return SVN_NO_ERROR; 1179} 1180 1181 1182apr_status_t 1183svn_ra_serf__credentials_callback(char **username, char **password, 1184 serf_request_t *request, void *baton, 1185 int code, const char *authn_type, 1186 const char *realm, 1187 apr_pool_t *pool) 1188{ 1189 svn_ra_serf__handler_t *handler = baton; 1190 svn_ra_serf__session_t *session = handler->session; 1191 void *creds; 1192 svn_auth_cred_simple_t *simple_creds; 1193 svn_error_t *err; 1194 1195 if (code == 401) 1196 { 1197 /* Use svn_auth_first_credentials if this is the first time we ask for 1198 credentials during this session OR if the last time we asked 1199 session->auth_state wasn't set (eg. if the credentials provider was 1200 cancelled by the user). */ 1201 if (!session->auth_state) 1202 { 1203 err = svn_auth_first_credentials(&creds, 1204 &session->auth_state, 1205 SVN_AUTH_CRED_SIMPLE, 1206 realm, 1207 session->auth_baton, 1208 session->pool); 1209 } 1210 else 1211 { 1212 err = svn_auth_next_credentials(&creds, 1213 session->auth_state, 1214 session->pool); 1215 } 1216 1217 if (err) 1218 { 1219 (void) save_error(session, err); 1220 return err->apr_err; 1221 } 1222 1223 session->auth_attempts++; 1224 1225 if (!creds || session->auth_attempts > 4) 1226 { 1227 /* No more credentials. */ 1228 (void) save_error(session, 1229 svn_error_create( 1230 SVN_ERR_AUTHN_FAILED, NULL, 1231 _("No more credentials or we tried too many " 1232 "times.\nAuthentication failed"))); 1233 return SVN_ERR_AUTHN_FAILED; 1234 } 1235 1236 simple_creds = creds; 1237 *username = apr_pstrdup(pool, simple_creds->username); 1238 *password = apr_pstrdup(pool, simple_creds->password); 1239 } 1240 else 1241 { 1242 *username = apr_pstrdup(pool, session->proxy_username); 1243 *password = apr_pstrdup(pool, session->proxy_password); 1244 1245 session->proxy_auth_attempts++; 1246 1247 if (!session->proxy_username || session->proxy_auth_attempts > 4) 1248 { 1249 /* No more credentials. */ 1250 (void) save_error(session, 1251 svn_error_create( 1252 SVN_ERR_AUTHN_FAILED, NULL, 1253 _("Proxy authentication failed"))); 1254 return SVN_ERR_AUTHN_FAILED; 1255 } 1256 } 1257 1258 handler->conn->last_status_code = code; 1259 1260 return APR_SUCCESS; 1261} 1262 1263/* Wait for HTTP response status and headers, and invoke HANDLER-> 1264 response_handler() to carry out operation-specific processing. 1265 Afterwards, check for connection close. 1266 1267 SERF_STATUS allows returning errors to serf without creating a 1268 subversion error object. 1269 */ 1270static svn_error_t * 1271handle_response(serf_request_t *request, 1272 serf_bucket_t *response, 1273 svn_ra_serf__handler_t *handler, 1274 apr_status_t *serf_status, 1275 apr_pool_t *scratch_pool) 1276{ 1277 apr_status_t status; 1278 svn_error_t *err; 1279 1280 /* ### need to verify whether this already gets init'd on every 1281 ### successful exit. for an error-exit, it will (properly) be 1282 ### ignored by the caller. */ 1283 *serf_status = APR_SUCCESS; 1284 1285 if (!response) 1286 { 1287 /* Uh-oh. Our connection died. */ 1288 handler->scheduled = FALSE; 1289 1290 if (handler->response_error) 1291 { 1292 /* Give a handler chance to prevent request requeue. */ 1293 SVN_ERR(handler->response_error(request, response, 0, 1294 handler->response_error_baton)); 1295 1296 svn_ra_serf__request_create(handler); 1297 } 1298 /* Response error callback is not configured. Requeue another request 1299 for this handler only if we didn't started to process body. 1300 Return error otherwise. */ 1301 else if (!handler->reading_body) 1302 { 1303 svn_ra_serf__request_create(handler); 1304 } 1305 else 1306 { 1307 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1308 _("%s request on '%s' failed"), 1309 handler->method, handler->path); 1310 } 1311 1312 return SVN_NO_ERROR; 1313 } 1314 1315 /* If we're reading the body, then skip all this preparation. */ 1316 if (handler->reading_body) 1317 goto process_body; 1318 1319 /* Copy the Status-Line info into HANDLER, if we don't yet have it. */ 1320 if (handler->sline.version == 0) 1321 { 1322 serf_status_line sl; 1323 1324 status = serf_bucket_response_status(response, &sl); 1325 if (status != APR_SUCCESS) 1326 { 1327 /* The response line is not (yet) ready, or some other error. */ 1328 *serf_status = status; 1329 return SVN_NO_ERROR; /* Handled by serf */ 1330 } 1331 1332 /* If we got APR_SUCCESS, then we should have Status-Line info. */ 1333 SVN_ERR_ASSERT(sl.version != 0); 1334 1335 handler->sline = sl; 1336 handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason); 1337 1338 /* HTTP/1.1? (or later) */ 1339 if (sl.version != SERF_HTTP_10) 1340 handler->session->http10 = FALSE; 1341 1342 if (sl.version >= SERF_HTTP_VERSION(2, 0)) { 1343 handler->session->http20 = TRUE; 1344 } 1345 } 1346 1347 /* Keep reading from the network until we've read all the headers. */ 1348 status = serf_bucket_response_wait_for_headers(response); 1349 if (status) 1350 { 1351 /* The typical "error" will be APR_EAGAIN, meaning that more input 1352 from the network is required to complete the reading of the 1353 headers. */ 1354 if (!APR_STATUS_IS_EOF(status)) 1355 { 1356 /* Either the headers are not (yet) complete, or there really 1357 was an error. */ 1358 *serf_status = status; 1359 return SVN_NO_ERROR; 1360 } 1361 1362 /* wait_for_headers() will return EOF if there is no body in this 1363 response, or if we completely read the body. The latter is not 1364 true since we would have set READING_BODY to get the body read, 1365 and we would not be back to this code block. 1366 1367 It can also return EOF if we truly hit EOF while (say) processing 1368 the headers. aka Badness. */ 1369 1370 /* Cases where a lack of a response body (via EOF) is okay: 1371 * - A HEAD request 1372 * - 204/304 response 1373 * 1374 * Otherwise, if we get an EOF here, something went really wrong: either 1375 * the server closed on us early or we're reading too much. Either way, 1376 * scream loudly. 1377 */ 1378 if (strcmp(handler->method, "HEAD") != 0 1379 && handler->sline.code != 204 1380 && handler->sline.code != 304) 1381 { 1382 err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, 1383 svn_ra_serf__wrap_err(status, NULL), 1384 _("Premature EOF seen from server" 1385 " (http status=%d)"), 1386 handler->sline.code); 1387 1388 /* In case anything else arrives... discard it. */ 1389 handler->discard_body = TRUE; 1390 1391 return err; 1392 } 1393 } 1394 1395 /* ... and set up the header fields in HANDLER. */ 1396 handler->location = response_get_location(response, 1397 handler->session->session_url_str, 1398 handler->handler_pool, 1399 scratch_pool); 1400 1401 /* On the last request, we failed authentication. We succeeded this time, 1402 so let's save away these credentials. */ 1403 if (handler->conn->last_status_code == 401 && handler->sline.code < 400) 1404 { 1405 SVN_ERR(svn_auth_save_credentials(handler->session->auth_state, 1406 handler->session->pool)); 1407 handler->session->auth_attempts = 0; 1408 handler->session->auth_state = NULL; 1409 } 1410 handler->conn->last_status_code = handler->sline.code; 1411 1412 if (handler->sline.code >= 400) 1413 { 1414 /* 405 Method Not allowed. 1415 408 Request Timeout 1416 409 Conflict: can indicate a hook error. 1417 5xx (Internal) Server error. */ 1418 serf_bucket_t *hdrs; 1419 const char *val; 1420 1421 hdrs = serf_bucket_response_get_headers(response); 1422 val = serf_bucket_headers_get(hdrs, "Content-Type"); 1423 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) 1424 { 1425 svn_ra_serf__server_error_t *server_err; 1426 1427 SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler, 1428 FALSE, 1429 handler->handler_pool, 1430 handler->handler_pool)); 1431 1432 handler->server_error = server_err; 1433 } 1434 else 1435 { 1436 handler->discard_body = TRUE; 1437 } 1438 } 1439 else if (handler->sline.code <= 199) 1440 { 1441 handler->discard_body = TRUE; 1442 } 1443 1444 /* Stop processing the above, on every packet arrival. */ 1445 handler->reading_body = TRUE; 1446 1447 process_body: 1448 1449 /* A client cert file password was obtained and worked (any HTTP 1450 response means that the SSL connection was established.) */ 1451 if (handler->conn->ssl_client_pw_auth_state) 1452 { 1453 SVN_ERR(svn_auth_save_credentials(handler->conn->ssl_client_pw_auth_state, 1454 handler->session->pool)); 1455 handler->conn->ssl_client_pw_auth_state = NULL; 1456 } 1457 if (handler->conn->ssl_client_auth_state) 1458 { 1459 /* The cert file provider doesn't have any code to save creds so 1460 this is currently a no-op. */ 1461 SVN_ERR(svn_auth_save_credentials(handler->conn->ssl_client_auth_state, 1462 handler->session->pool)); 1463 handler->conn->ssl_client_auth_state = NULL; 1464 } 1465 1466 /* We've been instructed to ignore the body. Drain whatever is present. */ 1467 if (handler->discard_body) 1468 { 1469 *serf_status = drain_bucket(response); 1470 1471 return SVN_NO_ERROR; 1472 } 1473 1474 /* If we are supposed to parse the body as a server_error, then do 1475 that now. */ 1476 if (handler->server_error != NULL) 1477 { 1478 return svn_error_trace( 1479 svn_ra_serf__handle_server_error(handler->server_error, 1480 handler, 1481 request, response, 1482 serf_status, 1483 scratch_pool)); 1484 } 1485 1486 /* Pass the body along to the registered response handler. */ 1487 err = handler->response_handler(request, response, 1488 handler->response_baton, 1489 scratch_pool); 1490 1491 if (err 1492 && (!SERF_BUCKET_READ_ERROR(err->apr_err) 1493 || APR_STATUS_IS_ECONNRESET(err->apr_err) 1494 || APR_STATUS_IS_ECONNABORTED(err->apr_err))) 1495 { 1496 /* These errors are special cased in serf 1497 ### We hope no handler returns these by accident. */ 1498 *serf_status = err->apr_err; 1499 svn_error_clear(err); 1500 return SVN_NO_ERROR; 1501 } 1502 1503 return svn_error_trace(err); 1504} 1505 1506 1507/* Implements serf_response_handler_t for handle_response. Storing 1508 errors in handler->session->pending_error if appropriate. */ 1509static apr_status_t 1510handle_response_cb(serf_request_t *request, 1511 serf_bucket_t *response, 1512 void *baton, 1513 apr_pool_t *response_pool) 1514{ 1515 svn_ra_serf__handler_t *handler = baton; 1516 svn_error_t *err; 1517 apr_status_t inner_status; 1518 apr_status_t outer_status; 1519 apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */ 1520 1521 err = svn_error_trace(handle_response(request, response, 1522 handler, &inner_status, 1523 scratch_pool)); 1524 1525 /* Select the right status value to return. */ 1526 outer_status = save_error(handler->session, err); 1527 if (!outer_status) 1528 outer_status = inner_status; 1529 1530 /* Make sure the DONE flag is set properly and requests are cleaned up. */ 1531 if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status)) 1532 { 1533 svn_ra_serf__session_t *sess = handler->session; 1534 handler->done = TRUE; 1535 handler->scheduled = FALSE; 1536 outer_status = APR_EOF; 1537 1538 /* We use a cached handler->session here to allow handler to free the 1539 memory containing the handler */ 1540 save_error(sess, 1541 handler->done_delegate(request, handler->done_delegate_baton, 1542 scratch_pool)); 1543 } 1544 else if (SERF_BUCKET_READ_ERROR(outer_status) 1545 && handler->session->pending_error) 1546 { 1547 handler->discard_body = TRUE; /* Discard further data */ 1548 handler->done = TRUE; /* Mark as done */ 1549 /* handler->scheduled is still TRUE, as we still expect data. 1550 If we would return an error outer-status the connection 1551 would have to be restarted. With scheduled still TRUE 1552 destroying the handler's pool will still reset the 1553 connection, avoiding the posibility of returning 1554 an error for this handler when a new request is 1555 scheduled. */ 1556 outer_status = APR_EAGAIN; /* Exit context loop */ 1557 } 1558 1559 return outer_status; 1560} 1561 1562/* Perform basic request setup, with special handling for HEAD requests, 1563 and finer-grained callbacks invoked (if non-NULL) to produce the request 1564 headers and body. */ 1565static svn_error_t * 1566setup_request(serf_request_t *request, 1567 svn_ra_serf__handler_t *handler, 1568 serf_bucket_t **req_bkt, 1569 apr_pool_t *request_pool, 1570 apr_pool_t *scratch_pool) 1571{ 1572 serf_bucket_t *body_bkt; 1573 serf_bucket_t *headers_bkt; 1574 const char *accept_encoding; 1575 1576 if (handler->body_delegate) 1577 { 1578 serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request); 1579 1580 SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton, 1581 bkt_alloc, request_pool, scratch_pool)); 1582 } 1583 else 1584 { 1585 body_bkt = NULL; 1586 } 1587 1588 if (handler->custom_accept_encoding) 1589 { 1590 accept_encoding = NULL; 1591 } 1592 else if (handler->session->using_compression != svn_tristate_false) 1593 { 1594 /* Accept gzip compression if enabled. */ 1595 accept_encoding = "gzip"; 1596 } 1597 else 1598 { 1599 accept_encoding = NULL; 1600 } 1601 1602 SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt, 1603 handler->session, handler->method, handler->path, 1604 body_bkt, handler->body_type, accept_encoding, 1605 !handler->no_dav_headers, request_pool, 1606 scratch_pool)); 1607 1608 if (handler->header_delegate) 1609 { 1610 SVN_ERR(handler->header_delegate(headers_bkt, 1611 handler->header_delegate_baton, 1612 request_pool, scratch_pool)); 1613 } 1614 1615 return SVN_NO_ERROR; 1616} 1617 1618/* Implements the serf_request_setup_t interface (which sets up both a 1619 request and its response handler callback). Handles errors for 1620 setup_request_cb */ 1621static apr_status_t 1622setup_request_cb(serf_request_t *request, 1623 void *setup_baton, 1624 serf_bucket_t **req_bkt, 1625 serf_response_acceptor_t *acceptor, 1626 void **acceptor_baton, 1627 serf_response_handler_t *s_handler, 1628 void **s_handler_baton, 1629 apr_pool_t *request_pool) 1630{ 1631 svn_ra_serf__handler_t *handler = setup_baton; 1632 apr_pool_t *scratch_pool; 1633 svn_error_t *err; 1634 1635 /* Construct a scratch_pool? serf gives us a pool that will live for 1636 the duration of the request. But requests are retried in some cases */ 1637 scratch_pool = svn_pool_create(request_pool); 1638 1639 if (strcmp(handler->method, "HEAD") == 0) 1640 *acceptor = accept_head; 1641 else 1642 *acceptor = accept_response; 1643 *acceptor_baton = handler; 1644 1645 *s_handler = handle_response_cb; 1646 *s_handler_baton = handler; 1647 1648 err = svn_error_trace(setup_request(request, handler, req_bkt, 1649 request_pool, scratch_pool)); 1650 1651 svn_pool_destroy(scratch_pool); 1652 return save_error(handler->session, err); 1653} 1654 1655void 1656svn_ra_serf__request_create(svn_ra_serf__handler_t *handler) 1657{ 1658 SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL 1659 && !handler->scheduled); 1660 1661 /* In case HANDLER is re-queued, reset the various transient fields. */ 1662 handler->done = FALSE; 1663 handler->server_error = NULL; 1664 handler->sline.version = 0; 1665 handler->location = NULL; 1666 handler->reading_body = FALSE; 1667 handler->discard_body = FALSE; 1668 handler->scheduled = TRUE; 1669 1670 /* Keeping track of the returned request object would be nice, but doesn't 1671 work the way we would expect in ra_serf.. 1672 1673 Serf sometimes creates a new request for us (and destroys the old one) 1674 without telling, like when authentication failed (401/407 response. 1675 1676 We 'just' trust serf to do the right thing and expect it to tell us 1677 when the state of the request changes. 1678 1679 ### I fixed a request leak in serf in r2258 on auth failures. 1680 */ 1681 (void) serf_connection_request_create(handler->conn->conn, 1682 setup_request_cb, handler); 1683} 1684 1685 1686svn_error_t * 1687svn_ra_serf__discover_vcc(const char **vcc_url, 1688 svn_ra_serf__session_t *session, 1689 apr_pool_t *scratch_pool) 1690{ 1691 const char *path; 1692 const char *relative_path; 1693 const char *uuid; 1694 1695 /* If we've already got the information our caller seeks, just return it. */ 1696 if (session->vcc_url && session->repos_root_str) 1697 { 1698 *vcc_url = session->vcc_url; 1699 return SVN_NO_ERROR; 1700 } 1701 1702 path = session->session_url.path; 1703 *vcc_url = NULL; 1704 uuid = NULL; 1705 1706 do 1707 { 1708 apr_hash_t *props; 1709 svn_error_t *err; 1710 1711 err = svn_ra_serf__fetch_node_props(&props, session, 1712 path, SVN_INVALID_REVNUM, 1713 base_props, 1714 scratch_pool, scratch_pool); 1715 if (! err) 1716 { 1717 apr_hash_t *ns_props; 1718 1719 ns_props = apr_hash_get(props, "DAV:", 4); 1720 *vcc_url = svn_prop_get_value(ns_props, 1721 "version-controlled-configuration"); 1722 1723 ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV); 1724 relative_path = svn_prop_get_value(ns_props, 1725 "baseline-relative-path"); 1726 uuid = svn_prop_get_value(ns_props, "repository-uuid"); 1727 break; 1728 } 1729 else 1730 { 1731 if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) && 1732 (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN)) 1733 { 1734 return svn_error_trace(err); /* found a _real_ error */ 1735 } 1736 else 1737 { 1738 /* This happens when the file is missing in HEAD. */ 1739 svn_error_clear(err); 1740 1741 /* Okay, strip off a component from PATH. */ 1742 path = svn_urlpath__dirname(path, scratch_pool); 1743 } 1744 } 1745 } 1746 while ((path[0] != '\0') 1747 && (! (path[0] == '/' && path[1] == '\0'))); 1748 1749 if (!*vcc_url) 1750 { 1751 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 1752 _("The PROPFIND response did not include the " 1753 "requested version-controlled-configuration " 1754 "value")); 1755 } 1756 1757 /* Store our VCC in our cache. */ 1758 if (!session->vcc_url) 1759 { 1760 session->vcc_url = apr_pstrdup(session->pool, *vcc_url); 1761 } 1762 1763 /* Update our cached repository root URL. */ 1764 if (!session->repos_root_str) 1765 { 1766 svn_stringbuf_t *url_buf; 1767 1768 url_buf = svn_stringbuf_create(path, scratch_pool); 1769 1770 svn_path_remove_components(url_buf, 1771 svn_path_component_count(relative_path)); 1772 1773 /* Now recreate the root_url. */ 1774 session->repos_root = session->session_url; 1775 session->repos_root.path = 1776 (char *)svn_fspath__canonicalize(url_buf->data, session->pool); 1777 session->repos_root_str = 1778 svn_urlpath__canonicalize(apr_uri_unparse(session->pool, 1779 &session->repos_root, 0), 1780 session->pool); 1781 } 1782 1783 /* Store the repository UUID in the cache. */ 1784 if (!session->uuid) 1785 { 1786 session->uuid = apr_pstrdup(session->pool, uuid); 1787 } 1788 1789 return SVN_NO_ERROR; 1790} 1791 1792svn_error_t * 1793svn_ra_serf__get_relative_path(const char **rel_path, 1794 const char *orig_path, 1795 svn_ra_serf__session_t *session, 1796 apr_pool_t *pool) 1797{ 1798 const char *decoded_root, *decoded_orig; 1799 1800 if (! session->repos_root.path) 1801 { 1802 const char *vcc_url; 1803 1804 /* This should only happen if we haven't detected HTTP v2 1805 support from the server. */ 1806 assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 1807 1808 /* We don't actually care about the VCC_URL, but this API 1809 promises to populate the session's root-url cache, and that's 1810 what we really want. */ 1811 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, 1812 pool)); 1813 } 1814 1815 decoded_root = svn_path_uri_decode(session->repos_root.path, pool); 1816 decoded_orig = svn_path_uri_decode(orig_path, pool); 1817 *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig); 1818 SVN_ERR_ASSERT(*rel_path != NULL); 1819 return SVN_NO_ERROR; 1820} 1821 1822svn_error_t * 1823svn_ra_serf__report_resource(const char **report_target, 1824 svn_ra_serf__session_t *session, 1825 apr_pool_t *pool) 1826{ 1827 /* If we have HTTP v2 support, we want to report against the 'me' 1828 resource. */ 1829 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 1830 *report_target = apr_pstrdup(pool, session->me_resource); 1831 1832 /* Otherwise, we'll use the default VCC. */ 1833 else 1834 SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, pool)); 1835 1836 return SVN_NO_ERROR; 1837} 1838 1839svn_error_t * 1840svn_ra_serf__error_on_status(serf_status_line sline, 1841 const char *path, 1842 const char *location) 1843{ 1844 switch(sline.code) 1845 { 1846 case 301: 1847 case 302: 1848 case 303: 1849 case 307: 1850 case 308: 1851 return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL, 1852 (sline.code == 301) 1853 ? _("Repository moved permanently to '%s'") 1854 : _("Repository moved temporarily to '%s'"), 1855 location); 1856 case 403: 1857 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL, 1858 _("Access to '%s' forbidden"), path); 1859 1860 case 404: 1861 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1862 _("'%s' path not found"), path); 1863 case 405: 1864 return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL, 1865 _("HTTP method is not allowed on '%s'"), 1866 path); 1867 case 409: 1868 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, 1869 _("'%s' conflicts"), path); 1870 case 412: 1871 return svn_error_createf(SVN_ERR_RA_DAV_PRECONDITION_FAILED, NULL, 1872 _("Precondition on '%s' failed"), path); 1873 case 423: 1874 return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL, 1875 _("'%s': no lock token available"), path); 1876 1877 case 411: 1878 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1879 _("DAV request failed: 411 Content length required. The " 1880 "server or an intermediate proxy does not accept " 1881 "chunked encoding. Try setting 'http-chunked-requests' " 1882 "to 'auto' or 'no' in your client configuration.")); 1883 case 500: 1884 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1885 _("Unexpected server error %d '%s' on '%s'"), 1886 sline.code, sline.reason, path); 1887 case 501: 1888 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 1889 _("The requested feature is not supported by " 1890 "'%s'"), path); 1891 } 1892 1893 if (sline.code >= 300 || sline.code <= 199) 1894 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1895 _("Unexpected HTTP status %d '%s' on '%s'"), 1896 sline.code, sline.reason, path); 1897 1898 return SVN_NO_ERROR; 1899} 1900 1901svn_error_t * 1902svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler) 1903{ 1904 /* Is it a standard error status? */ 1905 if (handler->sline.code != 405) 1906 SVN_ERR(svn_ra_serf__error_on_status(handler->sline, 1907 handler->path, 1908 handler->location)); 1909 1910 switch (handler->sline.code) 1911 { 1912 case 201: 1913 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1914 _("Path '%s' unexpectedly created"), 1915 handler->path); 1916 case 204: 1917 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1918 _("Path '%s' already exists"), 1919 handler->path); 1920 1921 case 405: 1922 return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL, 1923 _("The HTTP method '%s' is not allowed" 1924 " on '%s'"), 1925 handler->method, handler->path); 1926 default: 1927 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1928 _("Unexpected HTTP status %d '%s' on '%s' " 1929 "request to '%s'"), 1930 handler->sline.code, handler->sline.reason, 1931 handler->method, handler->path); 1932 } 1933} 1934 1935svn_error_t * 1936svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session, 1937 svn_delta_shim_callbacks_t *callbacks) 1938{ 1939 svn_ra_serf__session_t *session = ra_session->priv; 1940 1941 session->shim_callbacks = callbacks; 1942 return SVN_NO_ERROR; 1943} 1944 1945/* Shared/standard done_delegate handler */ 1946static svn_error_t * 1947response_done(serf_request_t *request, 1948 void *handler_baton, 1949 apr_pool_t *scratch_pool) 1950{ 1951 svn_ra_serf__handler_t *handler = handler_baton; 1952 1953 assert(handler->done); 1954 1955 if (handler->no_fail_on_http_failure_status) 1956 return SVN_NO_ERROR; 1957 1958 if (handler->server_error) 1959 return svn_ra_serf__server_error_create(handler, scratch_pool); 1960 1961 if (handler->sline.code >= 400 || handler->sline.code <= 199) 1962 { 1963 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 1964 } 1965 1966 if ((handler->sline.code >= 300 && handler->sline.code < 399) 1967 && !handler->no_fail_on_http_redirect_status) 1968 { 1969 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 1970 } 1971 1972 return SVN_NO_ERROR; 1973} 1974 1975/* Pool cleanup handler for request handlers. 1976 1977 If a serf context run stops for some outside error, like when the user 1978 cancels a request via ^C in the context loop, the handler is still 1979 registered in the serf context. With the pool cleanup there would be 1980 handlers registered in no freed memory. 1981 1982 This fallback kills the connection for this case, which will make serf 1983 unregister any outstanding requests on it. */ 1984static apr_status_t 1985handler_cleanup(void *baton) 1986{ 1987 svn_ra_serf__handler_t *handler = baton; 1988 if (handler->scheduled) 1989 { 1990 svn_ra_serf__unschedule_handler(handler); 1991 } 1992 1993 return APR_SUCCESS; 1994} 1995 1996svn_ra_serf__handler_t * 1997svn_ra_serf__create_handler(svn_ra_serf__session_t *session, 1998 apr_pool_t *result_pool) 1999{ 2000 svn_ra_serf__handler_t *handler; 2001 2002 handler = apr_pcalloc(result_pool, sizeof(*handler)); 2003 handler->handler_pool = result_pool; 2004 2005 apr_pool_cleanup_register(result_pool, handler, handler_cleanup, 2006 apr_pool_cleanup_null); 2007 2008 handler->session = session; 2009 handler->conn = session->conns[0]; 2010 2011 /* Setup the default done handler, to handle server errors */ 2012 handler->done_delegate_baton = handler; 2013 handler->done_delegate = response_done; 2014 2015 return handler; 2016} 2017 2018svn_error_t * 2019svn_ra_serf__uri_parse(apr_uri_t *uri, 2020 const char *url_str, 2021 apr_pool_t *result_pool) 2022{ 2023 apr_status_t status; 2024 2025 status = apr_uri_parse(result_pool, url_str, uri); 2026 if (status) 2027 { 2028 /* Do not use returned error status in error message because currently 2029 apr_uri_parse() returns APR_EGENERAL for all parsing errors. */ 2030 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 2031 _("Illegal URL '%s'"), 2032 url_str); 2033 } 2034 2035 /* Depending the version of apr-util in use, for root paths uri.path 2036 will be NULL or "", where serf requires "/". */ 2037 if (uri->path == NULL || uri->path[0] == '\0') 2038 { 2039 uri->path = apr_pstrdup(result_pool, "/"); 2040 } 2041 2042 return SVN_NO_ERROR; 2043} 2044 2045void 2046svn_ra_serf__setup_svndiff_accept_encoding(serf_bucket_t *headers, 2047 svn_ra_serf__session_t *session) 2048{ 2049 if (session->using_compression == svn_tristate_false) 2050 { 2051 /* Don't advertise support for compressed svndiff formats if 2052 compression is disabled. */ 2053 serf_bucket_headers_setn( 2054 headers, "Accept-Encoding", "svndiff"); 2055 } 2056 else if (session->using_compression == svn_tristate_unknown && 2057 svn_ra_serf__is_low_latency_connection(session)) 2058 { 2059 /* With http-compression=auto, advertise that we prefer svndiff2 2060 to svndiff1 with a low latency connection (assuming the underlying 2061 network has high bandwidth), as it is faster and in this case, we 2062 don't care about worse compression ratio. */ 2063 serf_bucket_headers_setn( 2064 headers, "Accept-Encoding", 2065 "gzip,svndiff2;q=0.9,svndiff1;q=0.8,svndiff;q=0.7"); 2066 } 2067 else 2068 { 2069 /* Otherwise, advertise that we prefer svndiff1 over svndiff2. 2070 svndiff2 is not a reasonable substitute for svndiff1 with default 2071 compression level, because, while it is faster, it also gives worse 2072 compression ratio. While we can use svndiff2 in some cases (see 2073 above), we can't do this generally. */ 2074 serf_bucket_headers_setn( 2075 headers, "Accept-Encoding", 2076 "gzip,svndiff1;q=0.9,svndiff2;q=0.8,svndiff;q=0.7"); 2077 } 2078} 2079 2080svn_boolean_t 2081svn_ra_serf__is_low_latency_connection(svn_ra_serf__session_t *session) 2082{ 2083 return session->conn_latency >= 0 && 2084 session->conn_latency < apr_time_from_msec(5); 2085} 2086 2087apr_array_header_t * 2088svn_ra_serf__get_dirent_props(apr_uint32_t dirent_fields, 2089 svn_ra_serf__session_t *session, 2090 apr_pool_t *result_pool) 2091{ 2092 svn_ra_serf__dav_props_t *prop; 2093 apr_array_header_t *props = apr_array_make 2094 (result_pool, 7, sizeof(svn_ra_serf__dav_props_t)); 2095 2096 if (session->supports_deadprop_count != svn_tristate_false 2097 || ! (dirent_fields & SVN_DIRENT_HAS_PROPS)) 2098 { 2099 if (dirent_fields & SVN_DIRENT_KIND) 2100 { 2101 prop = apr_array_push(props); 2102 prop->xmlns = "DAV:"; 2103 prop->name = "resourcetype"; 2104 } 2105 2106 if (dirent_fields & SVN_DIRENT_SIZE) 2107 { 2108 prop = apr_array_push(props); 2109 prop->xmlns = "DAV:"; 2110 prop->name = "getcontentlength"; 2111 } 2112 2113 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 2114 { 2115 prop = apr_array_push(props); 2116 prop->xmlns = SVN_DAV_PROP_NS_DAV; 2117 prop->name = "deadprop-count"; 2118 } 2119 2120 if (dirent_fields & SVN_DIRENT_CREATED_REV) 2121 { 2122 svn_ra_serf__dav_props_t *p = apr_array_push(props); 2123 p->xmlns = "DAV:"; 2124 p->name = SVN_DAV__VERSION_NAME; 2125 } 2126 2127 if (dirent_fields & SVN_DIRENT_TIME) 2128 { 2129 prop = apr_array_push(props); 2130 prop->xmlns = "DAV:"; 2131 prop->name = SVN_DAV__CREATIONDATE; 2132 } 2133 2134 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 2135 { 2136 prop = apr_array_push(props); 2137 prop->xmlns = "DAV:"; 2138 prop->name = "creator-displayname"; 2139 } 2140 } 2141 else 2142 { 2143 /* We found an old subversion server that can't handle 2144 the deadprop-count property in the way we expect. 2145 2146 The neon behavior is to retrieve all properties in this case */ 2147 prop = apr_array_push(props); 2148 prop->xmlns = "DAV:"; 2149 prop->name = "allprop"; 2150 } 2151 2152 return props; 2153} 2154 2155