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#include <apr_fnmatch.h> 32 33#include <serf.h> 34#include <serf_bucket_types.h> 35 36#include <expat.h> 37 38#include "svn_hash.h" 39#include "svn_dirent_uri.h" 40#include "svn_path.h" 41#include "svn_private_config.h" 42#include "svn_string.h" 43#include "svn_xml.h" 44#include "svn_props.h" 45#include "svn_dirent_uri.h" 46 47#include "../libsvn_ra/ra_loader.h" 48#include "private/svn_dep_compat.h" 49#include "private/svn_fspath.h" 50#include "private/svn_subr_private.h" 51 52#include "ra_serf.h" 53 54 55/* Fix for older expat 1.95.x's that do not define 56 * XML_STATUS_OK/XML_STATUS_ERROR 57 */ 58#ifndef XML_STATUS_OK 59#define XML_STATUS_OK 1 60#define XML_STATUS_ERROR 0 61#endif 62 63#ifndef XML_VERSION_AT_LEAST 64#define XML_VERSION_AT_LEAST(major,minor,patch) \ 65(((major) < XML_MAJOR_VERSION) \ 66 || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \ 67 || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \ 68 (patch) <= XML_MICRO_VERSION)) 69#endif /* APR_VERSION_AT_LEAST */ 70 71#if XML_VERSION_AT_LEAST(1, 95, 8) 72#define EXPAT_HAS_STOPPARSER 73#endif 74 75/* Read/write chunks of this size into the spillbuf. */ 76#define PARSE_CHUNK_SIZE 8000 77 78/* We will store one megabyte in memory, before switching to store content 79 into a temporary file. */ 80#define SPILL_SIZE 1000000 81 82 83/* This structure records pending data for the parser in memory blocks, 84 and possibly into a temporary file if "too much" content arrives. */ 85struct svn_ra_serf__pending_t { 86 /* The spillbuf where we record the pending data. */ 87 svn_spillbuf_t *buf; 88 89 /* This flag is set when the network has reached EOF. The PENDING 90 processing can then properly detect when parsing has completed. */ 91 svn_boolean_t network_eof; 92}; 93 94#define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \ 95 && svn_spillbuf__get_size((p)->buf) != 0) 96 97 98struct expat_ctx_t { 99 svn_ra_serf__xml_context_t *xmlctx; 100 XML_Parser parser; 101 svn_ra_serf__handler_t *handler; 102 103 svn_error_t *inner_error; 104 105 /* Do not use this pool for allocation. It is merely recorded for running 106 the cleanup handler. */ 107 apr_pool_t *cleanup_pool; 108}; 109 110 111static const apr_uint32_t serf_failure_map[][2] = 112{ 113 { SERF_SSL_CERT_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID }, 114 { SERF_SSL_CERT_EXPIRED, SVN_AUTH_SSL_EXPIRED }, 115 { SERF_SSL_CERT_SELF_SIGNED, SVN_AUTH_SSL_UNKNOWNCA }, 116 { SERF_SSL_CERT_UNKNOWNCA, SVN_AUTH_SSL_UNKNOWNCA } 117}; 118 119/* Return a Subversion failure mask based on FAILURES, a serf SSL 120 failure mask. If anything in FAILURES is not directly mappable to 121 Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */ 122static apr_uint32_t 123ssl_convert_serf_failures(int failures) 124{ 125 apr_uint32_t svn_failures = 0; 126 apr_size_t i; 127 128 for (i = 0; i < sizeof(serf_failure_map) / (2 * sizeof(apr_uint32_t)); ++i) 129 { 130 if (failures & serf_failure_map[i][0]) 131 { 132 svn_failures |= serf_failure_map[i][1]; 133 failures &= ~serf_failure_map[i][0]; 134 } 135 } 136 137 /* Map any remaining failure bits to our OTHER bit. */ 138 if (failures) 139 { 140 svn_failures |= SVN_AUTH_SSL_OTHER; 141 } 142 143 return svn_failures; 144} 145 146 147static apr_status_t 148save_error(svn_ra_serf__session_t *session, 149 svn_error_t *err) 150{ 151 if (err || session->pending_error) 152 { 153 session->pending_error = svn_error_compose_create( 154 session->pending_error, 155 err); 156 return session->pending_error->apr_err; 157 } 158 159 return APR_SUCCESS; 160} 161 162 163/* Construct the realmstring, e.g. https://svn.collab.net:443. */ 164static const char * 165construct_realm(svn_ra_serf__session_t *session, 166 apr_pool_t *pool) 167{ 168 const char *realm; 169 apr_port_t port; 170 171 if (session->session_url.port_str) 172 { 173 port = session->session_url.port; 174 } 175 else 176 { 177 port = apr_uri_port_of_scheme(session->session_url.scheme); 178 } 179 180 realm = apr_psprintf(pool, "%s://%s:%d", 181 session->session_url.scheme, 182 session->session_url.hostname, 183 port); 184 185 return realm; 186} 187 188/* Convert a hash table containing the fields (as documented in X.509) of an 189 organisation to a string ORG, allocated in POOL. ORG is as returned by 190 serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */ 191static char * 192convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool) 193{ 194 return apr_psprintf(pool, "%s, %s, %s, %s, %s (%s)", 195 (char*)svn_hash_gets(org, "OU"), 196 (char*)svn_hash_gets(org, "O"), 197 (char*)svn_hash_gets(org, "L"), 198 (char*)svn_hash_gets(org, "ST"), 199 (char*)svn_hash_gets(org, "C"), 200 (char*)svn_hash_gets(org, "E")); 201} 202 203/* This function is called on receiving a ssl certificate of a server when 204 opening a https connection. It allows Subversion to override the initial 205 validation done by serf. 206 Serf provides us the @a baton as provided in the call to 207 serf_ssl_server_cert_callback_set. The result of serf's initial validation 208 of the certificate @a CERT is returned as a bitmask in FAILURES. */ 209static svn_error_t * 210ssl_server_cert(void *baton, int failures, 211 const serf_ssl_certificate_t *cert, 212 apr_pool_t *scratch_pool) 213{ 214 svn_ra_serf__connection_t *conn = baton; 215 svn_auth_ssl_server_cert_info_t cert_info; 216 svn_auth_cred_ssl_server_trust_t *server_creds = NULL; 217 svn_auth_iterstate_t *state; 218 const char *realmstring; 219 apr_uint32_t svn_failures; 220 apr_hash_t *issuer, *subject, *serf_cert; 221 apr_array_header_t *san; 222 void *creds; 223 int found_matching_hostname = 0; 224 225 /* Implicitly approve any non-server certs. */ 226 if (serf_ssl_cert_depth(cert) > 0) 227 { 228 if (failures) 229 conn->server_cert_failures |= ssl_convert_serf_failures(failures); 230 return APR_SUCCESS; 231 } 232 233 /* Extract the info from the certificate */ 234 subject = serf_ssl_cert_subject(cert, scratch_pool); 235 issuer = serf_ssl_cert_issuer(cert, scratch_pool); 236 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool); 237 238 cert_info.hostname = svn_hash_gets(subject, "CN"); 239 san = svn_hash_gets(serf_cert, "subjectAltName"); 240 cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1"); 241 if (! cert_info.fingerprint) 242 cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>"); 243 cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore"); 244 if (! cert_info.valid_from) 245 cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]"); 246 cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter"); 247 if (! cert_info.valid_until) 248 cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]"); 249 cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool); 250 cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool); 251 252 svn_failures = (ssl_convert_serf_failures(failures) 253 | conn->server_cert_failures); 254 255 /* Try to find matching server name via subjectAltName first... */ 256 if (san) { 257 int i; 258 for (i = 0; i < san->nelts; i++) { 259 char *s = APR_ARRAY_IDX(san, i, char*); 260 if (apr_fnmatch(s, conn->session->session_url.hostname, 261 APR_FNM_PERIOD) == APR_SUCCESS) { 262 found_matching_hostname = 1; 263 cert_info.hostname = s; 264 break; 265 } 266 } 267 } 268 269 /* Match server certificate CN with the hostname of the server */ 270 if (!found_matching_hostname && cert_info.hostname) 271 { 272 if (apr_fnmatch(cert_info.hostname, conn->session->session_url.hostname, 273 APR_FNM_PERIOD) == APR_FNM_NOMATCH) 274 { 275 svn_failures |= SVN_AUTH_SSL_CNMISMATCH; 276 } 277 } 278 279 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, 280 SVN_AUTH_PARAM_SSL_SERVER_FAILURES, 281 &svn_failures); 282 283 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, 284 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, 285 &cert_info); 286 287 realmstring = construct_realm(conn->session, conn->session->pool); 288 289 SVN_ERR(svn_auth_first_credentials(&creds, &state, 290 SVN_AUTH_CRED_SSL_SERVER_TRUST, 291 realmstring, 292 conn->session->wc_callbacks->auth_baton, 293 scratch_pool)); 294 if (creds) 295 { 296 server_creds = creds; 297 SVN_ERR(svn_auth_save_credentials(state, scratch_pool)); 298 } 299 300 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, 301 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL); 302 303 if (!server_creds) 304 return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL, NULL); 305 306 return SVN_NO_ERROR; 307} 308 309/* Implements serf_ssl_need_server_cert_t for ssl_server_cert */ 310static apr_status_t 311ssl_server_cert_cb(void *baton, int failures, 312 const serf_ssl_certificate_t *cert) 313{ 314 svn_ra_serf__connection_t *conn = baton; 315 svn_ra_serf__session_t *session = conn->session; 316 apr_pool_t *subpool; 317 svn_error_t *err; 318 319 subpool = svn_pool_create(session->pool); 320 err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool)); 321 svn_pool_destroy(subpool); 322 323 return save_error(session, err); 324} 325 326static svn_error_t * 327load_authorities(svn_ra_serf__connection_t *conn, const char *authorities, 328 apr_pool_t *pool) 329{ 330 apr_array_header_t *files = svn_cstring_split(authorities, ";", 331 TRUE /* chop_whitespace */, 332 pool); 333 int i; 334 335 for (i = 0; i < files->nelts; ++i) 336 { 337 const char *file = APR_ARRAY_IDX(files, i, const char *); 338 serf_ssl_certificate_t *ca_cert; 339 apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool); 340 341 if (status == APR_SUCCESS) 342 status = serf_ssl_trust_cert(conn->ssl_context, ca_cert); 343 344 if (status != APR_SUCCESS) 345 { 346 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, 347 _("Invalid config: unable to load certificate file '%s'"), 348 svn_dirent_local_style(file, pool)); 349 } 350 } 351 352 return SVN_NO_ERROR; 353} 354 355static svn_error_t * 356conn_setup(apr_socket_t *sock, 357 serf_bucket_t **read_bkt, 358 serf_bucket_t **write_bkt, 359 void *baton, 360 apr_pool_t *pool) 361{ 362 svn_ra_serf__connection_t *conn = baton; 363 364 *read_bkt = serf_context_bucket_socket_create(conn->session->context, 365 sock, conn->bkt_alloc); 366 367 if (conn->session->using_ssl) 368 { 369 /* input stream */ 370 *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context, 371 conn->bkt_alloc); 372 if (!conn->ssl_context) 373 { 374 conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt); 375 376 serf_ssl_set_hostname(conn->ssl_context, 377 conn->session->session_url.hostname); 378 379 serf_ssl_client_cert_provider_set(conn->ssl_context, 380 svn_ra_serf__handle_client_cert, 381 conn, conn->session->pool); 382 serf_ssl_client_cert_password_set(conn->ssl_context, 383 svn_ra_serf__handle_client_cert_pw, 384 conn, conn->session->pool); 385 serf_ssl_server_cert_callback_set(conn->ssl_context, 386 ssl_server_cert_cb, 387 conn); 388 389 /* See if the user wants us to trust "default" openssl CAs. */ 390 if (conn->session->trust_default_ca) 391 { 392 serf_ssl_use_default_certificates(conn->ssl_context); 393 } 394 /* Are there custom CAs to load? */ 395 if (conn->session->ssl_authorities) 396 { 397 SVN_ERR(load_authorities(conn, conn->session->ssl_authorities, 398 conn->session->pool)); 399 } 400 } 401 402 if (write_bkt) 403 { 404 /* output stream */ 405 *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt, 406 conn->ssl_context, 407 conn->bkt_alloc); 408 } 409 } 410 411 return SVN_NO_ERROR; 412} 413 414/* svn_ra_serf__conn_setup is a callback for serf. This function 415 creates a read bucket and will wrap the write bucket if SSL 416 is needed. */ 417apr_status_t 418svn_ra_serf__conn_setup(apr_socket_t *sock, 419 serf_bucket_t **read_bkt, 420 serf_bucket_t **write_bkt, 421 void *baton, 422 apr_pool_t *pool) 423{ 424 svn_ra_serf__connection_t *conn = baton; 425 svn_ra_serf__session_t *session = conn->session; 426 svn_error_t *err; 427 428 err = svn_error_trace(conn_setup(sock, 429 read_bkt, 430 write_bkt, 431 baton, 432 pool)); 433 return save_error(session, err); 434} 435 436 437/* Our default serf response acceptor. */ 438static serf_bucket_t * 439accept_response(serf_request_t *request, 440 serf_bucket_t *stream, 441 void *acceptor_baton, 442 apr_pool_t *pool) 443{ 444 serf_bucket_t *c; 445 serf_bucket_alloc_t *bkt_alloc; 446 447 bkt_alloc = serf_request_get_alloc(request); 448 c = serf_bucket_barrier_create(stream, bkt_alloc); 449 450 return serf_bucket_response_create(c, bkt_alloc); 451} 452 453 454/* Custom response acceptor for HEAD requests. */ 455static serf_bucket_t * 456accept_head(serf_request_t *request, 457 serf_bucket_t *stream, 458 void *acceptor_baton, 459 apr_pool_t *pool) 460{ 461 serf_bucket_t *response; 462 463 response = accept_response(request, stream, acceptor_baton, pool); 464 465 /* We know we shouldn't get a response body. */ 466 serf_bucket_response_set_head(response); 467 468 return response; 469} 470 471static svn_error_t * 472connection_closed(svn_ra_serf__connection_t *conn, 473 apr_status_t why, 474 apr_pool_t *pool) 475{ 476 if (why) 477 { 478 return svn_error_wrap_apr(why, NULL); 479 } 480 481 if (conn->session->using_ssl) 482 conn->ssl_context = NULL; 483 484 return SVN_NO_ERROR; 485} 486 487void 488svn_ra_serf__conn_closed(serf_connection_t *conn, 489 void *closed_baton, 490 apr_status_t why, 491 apr_pool_t *pool) 492{ 493 svn_ra_serf__connection_t *ra_conn = closed_baton; 494 svn_error_t *err; 495 496 err = svn_error_trace(connection_closed(ra_conn, why, pool)); 497 498 (void) save_error(ra_conn->session, err); 499} 500 501 502/* Implementation of svn_ra_serf__handle_client_cert */ 503static svn_error_t * 504handle_client_cert(void *data, 505 const char **cert_path, 506 apr_pool_t *pool) 507{ 508 svn_ra_serf__connection_t *conn = data; 509 svn_ra_serf__session_t *session = conn->session; 510 const char *realm; 511 void *creds; 512 513 *cert_path = NULL; 514 515 realm = construct_realm(session, session->pool); 516 517 if (!conn->ssl_client_auth_state) 518 { 519 SVN_ERR(svn_auth_first_credentials(&creds, 520 &conn->ssl_client_auth_state, 521 SVN_AUTH_CRED_SSL_CLIENT_CERT, 522 realm, 523 session->wc_callbacks->auth_baton, 524 pool)); 525 } 526 else 527 { 528 SVN_ERR(svn_auth_next_credentials(&creds, 529 conn->ssl_client_auth_state, 530 session->pool)); 531 } 532 533 if (creds) 534 { 535 svn_auth_cred_ssl_client_cert_t *client_creds; 536 client_creds = creds; 537 *cert_path = client_creds->cert_file; 538 } 539 540 return SVN_NO_ERROR; 541} 542 543/* Implements serf_ssl_need_client_cert_t for handle_client_cert */ 544apr_status_t svn_ra_serf__handle_client_cert(void *data, 545 const char **cert_path) 546{ 547 svn_ra_serf__connection_t *conn = data; 548 svn_ra_serf__session_t *session = conn->session; 549 svn_error_t *err; 550 551 err = svn_error_trace(handle_client_cert(data, cert_path, session->pool)); 552 553 return save_error(session, err); 554} 555 556/* Implementation for svn_ra_serf__handle_client_cert_pw */ 557static svn_error_t * 558handle_client_cert_pw(void *data, 559 const char *cert_path, 560 const char **password, 561 apr_pool_t *pool) 562{ 563 svn_ra_serf__connection_t *conn = data; 564 svn_ra_serf__session_t *session = conn->session; 565 void *creds; 566 567 *password = NULL; 568 569 if (!conn->ssl_client_pw_auth_state) 570 { 571 SVN_ERR(svn_auth_first_credentials(&creds, 572 &conn->ssl_client_pw_auth_state, 573 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, 574 cert_path, 575 session->wc_callbacks->auth_baton, 576 pool)); 577 } 578 else 579 { 580 SVN_ERR(svn_auth_next_credentials(&creds, 581 conn->ssl_client_pw_auth_state, 582 pool)); 583 } 584 585 if (creds) 586 { 587 svn_auth_cred_ssl_client_cert_pw_t *pw_creds; 588 pw_creds = creds; 589 *password = pw_creds->password; 590 } 591 592 return APR_SUCCESS; 593} 594 595/* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */ 596apr_status_t svn_ra_serf__handle_client_cert_pw(void *data, 597 const char *cert_path, 598 const char **password) 599{ 600 svn_ra_serf__connection_t *conn = data; 601 svn_ra_serf__session_t *session = conn->session; 602 svn_error_t *err; 603 604 err = svn_error_trace(handle_client_cert_pw(data, 605 cert_path, 606 password, 607 session->pool)); 608 609 return save_error(session, err); 610} 611 612 613/* 614 * Given a REQUEST on connection CONN, construct a request bucket for it, 615 * returning the bucket in *REQ_BKT. 616 * 617 * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that 618 * corresponds to the new request. 619 * 620 * The request will be METHOD at URL. 621 * 622 * If BODY_BKT is not-NULL, it will be sent as the request body. 623 * 624 * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header. 625 * 626 * REQUEST_POOL should live for the duration of the request. Serf will 627 * construct this and provide it to the request_setup callback, so we 628 * should just use that one. 629 */ 630static svn_error_t * 631setup_serf_req(serf_request_t *request, 632 serf_bucket_t **req_bkt, 633 serf_bucket_t **hdrs_bkt, 634 svn_ra_serf__session_t *session, 635 const char *method, const char *url, 636 serf_bucket_t *body_bkt, const char *content_type, 637 const char *accept_encoding, 638 apr_pool_t *request_pool, 639 apr_pool_t *scratch_pool) 640{ 641 serf_bucket_alloc_t *allocator = serf_request_get_alloc(request); 642 643 svn_spillbuf_t *buf; 644 svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests; 645 646 if (set_CL && body_bkt != NULL) 647 { 648 /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if 649 it speaks HTTP/1.1 (and thus, chunked requests), or because the 650 server actually responded as only supporting HTTP/1.0. 651 652 We'll take the existing body_bkt, spool it into a spillbuf, and 653 then wrap a bucket around that spillbuf. The spillbuf will give 654 us the Content-Length value. */ 655 SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt, 656 request_pool, 657 scratch_pool)); 658 /* Destroy original bucket since it content is already copied 659 to spillbuf. */ 660 serf_bucket_destroy(body_bkt); 661 662 body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator, 663 request_pool, 664 scratch_pool); 665 } 666 667 /* Create a request bucket. Note that this sucker is kind enough to 668 add a "Host" header for us. */ 669 *req_bkt = serf_request_bucket_request_create(request, method, url, 670 body_bkt, allocator); 671 672 /* Set the Content-Length value. This will also trigger an HTTP/1.0 673 request (rather than the default chunked request). */ 674 if (set_CL) 675 { 676 if (body_bkt == NULL) 677 serf_bucket_request_set_CL(*req_bkt, 0); 678 else 679 serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf)); 680 } 681 682 *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt); 683 684 /* We use serf_bucket_headers_setn() because the USERAGENT has a 685 lifetime longer than this bucket. Thus, there is no need to copy 686 the header values. */ 687 serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent); 688 689 if (content_type) 690 { 691 serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type); 692 } 693 694 if (session->http10) 695 { 696 serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive"); 697 } 698 699 if (accept_encoding) 700 { 701 serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding); 702 } 703 704 /* These headers need to be sent with every request; see issue #3255 705 ("mod_dav_svn does not pass client capabilities to start-commit 706 hooks") for why. */ 707 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH); 708 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO); 709 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS); 710 711 return SVN_NO_ERROR; 712} 713 714svn_error_t * 715svn_ra_serf__context_run_wait(svn_boolean_t *done, 716 svn_ra_serf__session_t *sess, 717 apr_pool_t *scratch_pool) 718{ 719 apr_pool_t *iterpool; 720 apr_interval_time_t waittime_left = sess->timeout; 721 722 assert(sess->pending_error == SVN_NO_ERROR); 723 724 iterpool = svn_pool_create(scratch_pool); 725 while (!*done) 726 { 727 apr_status_t status; 728 svn_error_t *err; 729 int i; 730 731 svn_pool_clear(iterpool); 732 733 if (sess->cancel_func) 734 SVN_ERR((*sess->cancel_func)(sess->cancel_baton)); 735 736 status = serf_context_run(sess->context, 737 SVN_RA_SERF__CONTEXT_RUN_DURATION, 738 iterpool); 739 740 err = sess->pending_error; 741 sess->pending_error = SVN_NO_ERROR; 742 743 /* If the context duration timeout is up, we'll subtract that 744 duration from the total time alloted for such things. If 745 there's no time left, we fail with a message indicating that 746 the connection timed out. */ 747 if (APR_STATUS_IS_TIMEUP(status)) 748 { 749 svn_error_clear(err); 750 err = SVN_NO_ERROR; 751 status = 0; 752 753 if (sess->timeout) 754 { 755 if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) 756 { 757 waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; 758 } 759 else 760 { 761 return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, 762 _("Connection timed out")); 763 } 764 } 765 } 766 else 767 { 768 waittime_left = sess->timeout; 769 } 770 771 SVN_ERR(err); 772 if (status) 773 { 774 if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST) 775 { 776 /* apr can't translate subversion errors to text */ 777 SVN_ERR_W(svn_error_create(status, NULL, NULL), 778 _("Error running context")); 779 } 780 781 return svn_ra_serf__wrap_err(status, _("Error running context")); 782 } 783 784 /* Debugging purposes only! */ 785 for (i = 0; i < sess->num_conns; i++) 786 { 787 serf_debug__closed_conn(sess->conns[i]->bkt_alloc); 788 } 789 } 790 svn_pool_destroy(iterpool); 791 792 return SVN_NO_ERROR; 793} 794 795 796svn_error_t * 797svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler, 798 apr_pool_t *scratch_pool) 799{ 800 svn_error_t *err; 801 802 /* Create a serf request based on HANDLER. */ 803 svn_ra_serf__request_create(handler); 804 805 /* Wait until the response logic marks its DONE status. */ 806 err = svn_ra_serf__context_run_wait(&handler->done, handler->session, 807 scratch_pool); 808 if (handler->server_error) 809 { 810 err = svn_error_compose_create(err, handler->server_error->error); 811 handler->server_error = NULL; 812 } 813 814 return svn_error_trace(err); 815} 816 817 818/* 819 * Expat callback invoked on a start element tag for an error response. 820 */ 821static svn_error_t * 822start_error(svn_ra_serf__xml_parser_t *parser, 823 svn_ra_serf__dav_props_t name, 824 const char **attrs, 825 apr_pool_t *scratch_pool) 826{ 827 svn_ra_serf__server_error_t *ctx = parser->user_data; 828 829 if (!ctx->in_error && 830 strcmp(name.namespace, "DAV:") == 0 && 831 strcmp(name.name, "error") == 0) 832 { 833 ctx->in_error = TRUE; 834 } 835 else if (ctx->in_error && strcmp(name.name, "human-readable") == 0) 836 { 837 const char *err_code; 838 839 err_code = svn_xml_get_attr_value("errcode", attrs); 840 if (err_code) 841 { 842 apr_int64_t val; 843 844 SVN_ERR(svn_cstring_atoi64(&val, err_code)); 845 ctx->error->apr_err = (apr_status_t)val; 846 } 847 848 /* If there's no error code provided, or if the provided code is 849 0 (which can happen sometimes depending on how the error is 850 constructed on the server-side), just pick a generic error 851 code to run with. */ 852 if (! ctx->error->apr_err) 853 { 854 ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; 855 } 856 857 /* Start collecting cdata. */ 858 svn_stringbuf_setempty(ctx->cdata); 859 ctx->collect_cdata = TRUE; 860 } 861 862 return SVN_NO_ERROR; 863} 864 865/* 866 * Expat callback invoked on an end element tag for a PROPFIND response. 867 */ 868static svn_error_t * 869end_error(svn_ra_serf__xml_parser_t *parser, 870 svn_ra_serf__dav_props_t name, 871 apr_pool_t *scratch_pool) 872{ 873 svn_ra_serf__server_error_t *ctx = parser->user_data; 874 875 if (ctx->in_error && 876 strcmp(name.namespace, "DAV:") == 0 && 877 strcmp(name.name, "error") == 0) 878 { 879 ctx->in_error = FALSE; 880 } 881 if (ctx->in_error && strcmp(name.name, "human-readable") == 0) 882 { 883 /* On the server dav_error_response_tag() will add a leading 884 and trailing newline if DEBUG_CR is defined in mod_dav.h, 885 so remove any such characters here. */ 886 svn_stringbuf_strip_whitespace(ctx->cdata); 887 888 ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data, 889 ctx->cdata->len); 890 ctx->collect_cdata = FALSE; 891 } 892 893 return SVN_NO_ERROR; 894} 895 896/* 897 * Expat callback invoked on CDATA elements in an error response. 898 * 899 * This callback can be called multiple times. 900 */ 901static svn_error_t * 902cdata_error(svn_ra_serf__xml_parser_t *parser, 903 const char *data, 904 apr_size_t len, 905 apr_pool_t *scratch_pool) 906{ 907 svn_ra_serf__server_error_t *ctx = parser->user_data; 908 909 if (ctx->collect_cdata) 910 { 911 svn_stringbuf_appendbytes(ctx->cdata, data, len); 912 } 913 914 return SVN_NO_ERROR; 915} 916 917 918static apr_status_t 919drain_bucket(serf_bucket_t *bucket) 920{ 921 /* Read whatever is in the bucket, and just drop it. */ 922 while (1) 923 { 924 apr_status_t status; 925 const char *data; 926 apr_size_t len; 927 928 status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len); 929 if (status) 930 return status; 931 } 932} 933 934 935static svn_ra_serf__server_error_t * 936begin_error_parsing(svn_ra_serf__xml_start_element_t start, 937 svn_ra_serf__xml_end_element_t end, 938 svn_ra_serf__xml_cdata_chunk_handler_t cdata, 939 apr_pool_t *result_pool) 940{ 941 svn_ra_serf__server_error_t *server_err; 942 943 server_err = apr_pcalloc(result_pool, sizeof(*server_err)); 944 server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL); 945 server_err->contains_precondition_error = FALSE; 946 server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool); 947 server_err->collect_cdata = FALSE; 948 server_err->parser.pool = server_err->error->pool; 949 server_err->parser.user_data = server_err; 950 server_err->parser.start = start; 951 server_err->parser.end = end; 952 server_err->parser.cdata = cdata; 953 server_err->parser.ignore_errors = TRUE; 954 955 return server_err; 956} 957 958/* Implements svn_ra_serf__response_handler_t */ 959svn_error_t * 960svn_ra_serf__handle_discard_body(serf_request_t *request, 961 serf_bucket_t *response, 962 void *baton, 963 apr_pool_t *pool) 964{ 965 apr_status_t status; 966 967 status = drain_bucket(response); 968 if (status) 969 return svn_ra_serf__wrap_err(status, NULL); 970 971 return SVN_NO_ERROR; 972} 973 974apr_status_t 975svn_ra_serf__response_discard_handler(serf_request_t *request, 976 serf_bucket_t *response, 977 void *baton, 978 apr_pool_t *pool) 979{ 980 return drain_bucket(response); 981} 982 983 984/* Return the value of the RESPONSE's Location header if any, or NULL 985 otherwise. */ 986static const char * 987response_get_location(serf_bucket_t *response, 988 const char *base_url, 989 apr_pool_t *result_pool, 990 apr_pool_t *scratch_pool) 991{ 992 serf_bucket_t *headers; 993 const char *location; 994 995 headers = serf_bucket_response_get_headers(response); 996 location = serf_bucket_headers_get(headers, "Location"); 997 if (location == NULL) 998 return NULL; 999 1000 /* The RFCs say we should have received a full url in LOCATION, but 1001 older apache versions and many custom web handlers just return a 1002 relative path here... 1003 1004 And we can't trust anything because it is network data. 1005 */ 1006 if (*location == '/') 1007 { 1008 apr_uri_t uri; 1009 apr_status_t status; 1010 1011 status = apr_uri_parse(scratch_pool, base_url, &uri); 1012 1013 if (status != APR_SUCCESS) 1014 return NULL; 1015 1016 /* Replace the path path with what we got */ 1017 uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool); 1018 1019 /* And make APR produce a proper full url for us */ 1020 location = apr_uri_unparse(scratch_pool, &uri, 0); 1021 1022 /* Fall through to ensure our canonicalization rules */ 1023 } 1024 else if (!svn_path_is_url(location)) 1025 { 1026 return NULL; /* Any other formats we should support? */ 1027 } 1028 1029 return svn_uri_canonicalize(location, result_pool); 1030} 1031 1032 1033/* Implements svn_ra_serf__response_handler_t */ 1034svn_error_t * 1035svn_ra_serf__expect_empty_body(serf_request_t *request, 1036 serf_bucket_t *response, 1037 void *baton, 1038 apr_pool_t *scratch_pool) 1039{ 1040 svn_ra_serf__handler_t *handler = baton; 1041 serf_bucket_t *hdrs; 1042 const char *val; 1043 1044 /* This function is just like handle_multistatus_only() except for the 1045 XML parsing callbacks. We want to look for the human-readable element. */ 1046 1047 /* We should see this just once, in order to initialize SERVER_ERROR. 1048 At that point, the core error processing will take over. If we choose 1049 not to parse an error, then we'll never return here (because we 1050 change the response handler). */ 1051 SVN_ERR_ASSERT(handler->server_error == NULL); 1052 1053 hdrs = serf_bucket_response_get_headers(response); 1054 val = serf_bucket_headers_get(hdrs, "Content-Type"); 1055 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) 1056 { 1057 svn_ra_serf__server_error_t *server_err; 1058 1059 server_err = begin_error_parsing(start_error, end_error, cdata_error, 1060 handler->handler_pool); 1061 1062 /* Get the parser to set our DONE flag. */ 1063 server_err->parser.done = &handler->done; 1064 1065 handler->server_error = server_err; 1066 } 1067 else 1068 { 1069 /* The body was not text/xml, so we don't know what to do with it. 1070 Toss anything that arrives. */ 1071 handler->discard_body = TRUE; 1072 } 1073 1074 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it 1075 to call the response handler again. That will start up the XML parsing, 1076 or it will be dropped on the floor (per the decision above). */ 1077 return SVN_NO_ERROR; 1078} 1079 1080 1081/* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric 1082 status code into *STATUS_CODE_OUT. Ignores leading whitespace. */ 1083static svn_error_t * 1084parse_dav_status(int *status_code_out, svn_stringbuf_t *buf, 1085 apr_pool_t *scratch_pool) 1086{ 1087 svn_error_t *err; 1088 const char *token; 1089 char *tok_status; 1090 svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool); 1091 1092 svn_stringbuf_strip_whitespace(temp_buf); 1093 token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status); 1094 if (token) 1095 token = apr_strtok(NULL, " \t\r\n", &tok_status); 1096 if (!token) 1097 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1098 _("Malformed DAV:status CDATA '%s'"), 1099 buf->data); 1100 err = svn_cstring_atoi(status_code_out, token); 1101 if (err) 1102 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err, 1103 _("Malformed DAV:status CDATA '%s'"), 1104 buf->data); 1105 1106 return SVN_NO_ERROR; 1107} 1108 1109/* 1110 * Expat callback invoked on a start element tag for a 207 response. 1111 */ 1112static svn_error_t * 1113start_207(svn_ra_serf__xml_parser_t *parser, 1114 svn_ra_serf__dav_props_t name, 1115 const char **attrs, 1116 apr_pool_t *scratch_pool) 1117{ 1118 svn_ra_serf__server_error_t *ctx = parser->user_data; 1119 1120 if (!ctx->in_error && 1121 strcmp(name.namespace, "DAV:") == 0 && 1122 strcmp(name.name, "multistatus") == 0) 1123 { 1124 ctx->in_error = TRUE; 1125 } 1126 else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0) 1127 { 1128 /* Start collecting cdata. */ 1129 svn_stringbuf_setempty(ctx->cdata); 1130 ctx->collect_cdata = TRUE; 1131 } 1132 else if (ctx->in_error && 1133 strcmp(name.namespace, "DAV:") == 0 && 1134 strcmp(name.name, "status") == 0) 1135 { 1136 /* Start collecting cdata. */ 1137 svn_stringbuf_setempty(ctx->cdata); 1138 ctx->collect_cdata = TRUE; 1139 } 1140 1141 return SVN_NO_ERROR; 1142} 1143 1144/* 1145 * Expat callback invoked on an end element tag for a 207 response. 1146 */ 1147static svn_error_t * 1148end_207(svn_ra_serf__xml_parser_t *parser, 1149 svn_ra_serf__dav_props_t name, 1150 apr_pool_t *scratch_pool) 1151{ 1152 svn_ra_serf__server_error_t *ctx = parser->user_data; 1153 1154 if (ctx->in_error && 1155 strcmp(name.namespace, "DAV:") == 0 && 1156 strcmp(name.name, "multistatus") == 0) 1157 { 1158 ctx->in_error = FALSE; 1159 } 1160 if (ctx->in_error && strcmp(name.name, "responsedescription") == 0) 1161 { 1162 /* Remove leading newline added by DEBUG_CR on server */ 1163 svn_stringbuf_strip_whitespace(ctx->cdata); 1164 1165 ctx->collect_cdata = FALSE; 1166 ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data, 1167 ctx->cdata->len); 1168 if (ctx->contains_precondition_error) 1169 ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH; 1170 else 1171 ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; 1172 } 1173 else if (ctx->in_error && 1174 strcmp(name.namespace, "DAV:") == 0 && 1175 strcmp(name.name, "status") == 0) 1176 { 1177 int status_code; 1178 1179 ctx->collect_cdata = FALSE; 1180 1181 SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool)); 1182 if (status_code == 412) 1183 ctx->contains_precondition_error = TRUE; 1184 } 1185 1186 return SVN_NO_ERROR; 1187} 1188 1189/* 1190 * Expat callback invoked on CDATA elements in a 207 response. 1191 * 1192 * This callback can be called multiple times. 1193 */ 1194static svn_error_t * 1195cdata_207(svn_ra_serf__xml_parser_t *parser, 1196 const char *data, 1197 apr_size_t len, 1198 apr_pool_t *scratch_pool) 1199{ 1200 svn_ra_serf__server_error_t *ctx = parser->user_data; 1201 1202 if (ctx->collect_cdata) 1203 { 1204 svn_stringbuf_appendbytes(ctx->cdata, data, len); 1205 } 1206 1207 return SVN_NO_ERROR; 1208} 1209 1210/* Implements svn_ra_serf__response_handler_t */ 1211svn_error_t * 1212svn_ra_serf__handle_multistatus_only(serf_request_t *request, 1213 serf_bucket_t *response, 1214 void *baton, 1215 apr_pool_t *scratch_pool) 1216{ 1217 svn_ra_serf__handler_t *handler = baton; 1218 1219 /* This function is just like expect_empty_body() except for the 1220 XML parsing callbacks. We are looking for very limited pieces of 1221 the multistatus response. */ 1222 1223 /* We should see this just once, in order to initialize SERVER_ERROR. 1224 At that point, the core error processing will take over. If we choose 1225 not to parse an error, then we'll never return here (because we 1226 change the response handler). */ 1227 SVN_ERR_ASSERT(handler->server_error == NULL); 1228 1229 { 1230 serf_bucket_t *hdrs; 1231 const char *val; 1232 1233 hdrs = serf_bucket_response_get_headers(response); 1234 val = serf_bucket_headers_get(hdrs, "Content-Type"); 1235 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) 1236 { 1237 svn_ra_serf__server_error_t *server_err; 1238 1239 server_err = begin_error_parsing(start_207, end_207, cdata_207, 1240 handler->handler_pool); 1241 1242 /* Get the parser to set our DONE flag. */ 1243 server_err->parser.done = &handler->done; 1244 1245 handler->server_error = server_err; 1246 } 1247 else 1248 { 1249 /* The body was not text/xml, so we don't know what to do with it. 1250 Toss anything that arrives. */ 1251 handler->discard_body = TRUE; 1252 } 1253 } 1254 1255 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it 1256 to call the response handler again. That will start up the XML parsing, 1257 or it will be dropped on the floor (per the decision above). */ 1258 return SVN_NO_ERROR; 1259} 1260 1261 1262/* Conforms to Expat's XML_StartElementHandler */ 1263static void 1264start_xml(void *userData, const char *raw_name, const char **attrs) 1265{ 1266 svn_ra_serf__xml_parser_t *parser = userData; 1267 svn_ra_serf__dav_props_t name; 1268 apr_pool_t *scratch_pool; 1269 svn_error_t *err; 1270 1271 if (parser->error) 1272 return; 1273 1274 if (!parser->state) 1275 svn_ra_serf__xml_push_state(parser, 0); 1276 1277 /* ### get a real scratch_pool */ 1278 scratch_pool = parser->state->pool; 1279 1280 svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool); 1281 1282 svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name); 1283 1284 err = parser->start(parser, name, attrs, scratch_pool); 1285 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) 1286 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); 1287 1288 parser->error = err; 1289} 1290 1291 1292/* Conforms to Expat's XML_EndElementHandler */ 1293static void 1294end_xml(void *userData, const char *raw_name) 1295{ 1296 svn_ra_serf__xml_parser_t *parser = userData; 1297 svn_ra_serf__dav_props_t name; 1298 svn_error_t *err; 1299 apr_pool_t *scratch_pool; 1300 1301 if (parser->error) 1302 return; 1303 1304 /* ### get a real scratch_pool */ 1305 scratch_pool = parser->state->pool; 1306 1307 svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name); 1308 1309 err = parser->end(parser, name, scratch_pool); 1310 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) 1311 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); 1312 1313 parser->error = err; 1314} 1315 1316 1317/* Conforms to Expat's XML_CharacterDataHandler */ 1318static void 1319cdata_xml(void *userData, const char *data, int len) 1320{ 1321 svn_ra_serf__xml_parser_t *parser = userData; 1322 svn_error_t *err; 1323 apr_pool_t *scratch_pool; 1324 1325 if (parser->error) 1326 return; 1327 1328 if (!parser->state) 1329 svn_ra_serf__xml_push_state(parser, 0); 1330 1331 /* ### get a real scratch_pool */ 1332 scratch_pool = parser->state->pool; 1333 1334 err = parser->cdata(parser, data, len, scratch_pool); 1335 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) 1336 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); 1337 1338 parser->error = err; 1339} 1340 1341/* Flip the requisite bits in CTX to indicate that processing of the 1342 response is complete, adding the current "done item" to the list of 1343 completed items. */ 1344static void 1345add_done_item(svn_ra_serf__xml_parser_t *ctx) 1346{ 1347 /* Make sure we don't add to DONE_LIST twice. */ 1348 if (!*ctx->done) 1349 { 1350 *ctx->done = TRUE; 1351 if (ctx->done_list) 1352 { 1353 ctx->done_item->data = ctx->user_data; 1354 ctx->done_item->next = *ctx->done_list; 1355 *ctx->done_list = ctx->done_item; 1356 } 1357 } 1358} 1359 1360 1361static svn_error_t * 1362write_to_pending(svn_ra_serf__xml_parser_t *ctx, 1363 const char *data, 1364 apr_size_t len, 1365 apr_pool_t *scratch_pool) 1366{ 1367 if (ctx->pending == NULL) 1368 { 1369 ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending)); 1370 ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE, 1371 SPILL_SIZE, 1372 ctx->pool); 1373 } 1374 1375 /* Copy the data into one or more chunks in the spill buffer. */ 1376 return svn_error_trace(svn_spillbuf__write(ctx->pending->buf, 1377 data, len, 1378 scratch_pool)); 1379} 1380 1381 1382static svn_error_t * 1383inject_to_parser(svn_ra_serf__xml_parser_t *ctx, 1384 const char *data, 1385 apr_size_t len, 1386 const serf_status_line *sl) 1387{ 1388 int xml_status; 1389 1390 xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0); 1391 if (xml_status == XML_STATUS_ERROR && !ctx->ignore_errors) 1392 { 1393 if (sl == NULL) 1394 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1395 _("XML parsing failed")); 1396 1397 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1398 _("XML parsing failed: (%d %s)"), 1399 sl->code, sl->reason); 1400 } 1401 1402 if (ctx->error && !ctx->ignore_errors) 1403 return svn_error_trace(ctx->error); 1404 1405 return SVN_NO_ERROR; 1406} 1407 1408/* Apr pool cleanup handler to release an XML_Parser in success and error 1409 conditions */ 1410static apr_status_t 1411xml_parser_cleanup(void *baton) 1412{ 1413 XML_Parser *xmlp = baton; 1414 1415 if (*xmlp) 1416 { 1417 (void) XML_ParserFree(*xmlp); 1418 *xmlp = NULL; 1419 } 1420 1421 return APR_SUCCESS; 1422} 1423 1424/* Limit the amount of pending content to parse at once to < 100KB per 1425 iteration. This number is chosen somewhat arbitrarely. Making it lower 1426 will have a drastical negative impact on performance, whereas increasing it 1427 increases the risk for connection timeouts. 1428 */ 1429#define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5 1430 1431svn_error_t * 1432svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser, 1433 svn_boolean_t *network_eof, 1434 apr_pool_t *scratch_pool) 1435{ 1436 svn_boolean_t pending_empty = FALSE; 1437 apr_size_t cur_read = 0; 1438 1439 /* Fast path exit: already paused, nothing to do, or already done. */ 1440 if (parser->paused || parser->pending == NULL || *parser->done) 1441 { 1442 *network_eof = parser->pending ? parser->pending->network_eof : FALSE; 1443 return SVN_NO_ERROR; 1444 } 1445 1446 /* Parsing the pending conten in the spillbuf will result in many disc i/o 1447 operations. This can be so slow that we don't run the network event 1448 processing loop often enough, resulting in timed out connections. 1449 1450 So we limit the amounts of bytes parsed per iteration. 1451 */ 1452 while (cur_read < PENDING_TO_PARSE) 1453 { 1454 const char *data; 1455 apr_size_t len; 1456 1457 /* Get a block of content, stopping the loop when we run out. */ 1458 SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf, 1459 scratch_pool)); 1460 if (data) 1461 { 1462 /* Inject the content into the XML parser. */ 1463 SVN_ERR(inject_to_parser(parser, data, len, NULL)); 1464 1465 /* If the XML parsing callbacks paused us, then we're done for now. */ 1466 if (parser->paused) 1467 break; 1468 1469 cur_read += len; 1470 } 1471 else 1472 { 1473 /* The buffer is empty. */ 1474 pending_empty = TRUE; 1475 break; 1476 } 1477 } 1478 1479 /* If the PENDING structures are empty *and* we consumed all content from 1480 the network, then we're completely done with the parsing. */ 1481 if (pending_empty && 1482 parser->pending->network_eof) 1483 { 1484 SVN_ERR_ASSERT(parser->xmlp != NULL); 1485 1486 /* Tell the parser that no more content will be parsed. Ignore the 1487 return status. We just don't care. */ 1488 (void) XML_Parse(parser->xmlp, NULL, 0, 1); 1489 1490 apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup); 1491 parser->xmlp = NULL; 1492 add_done_item(parser); 1493 } 1494 1495 *network_eof = parser->pending ? parser->pending->network_eof : FALSE; 1496 1497 return SVN_NO_ERROR; 1498} 1499#undef PENDING_TO_PARSE 1500 1501 1502/* ### this is still broken conceptually. just shifting incrementally... */ 1503static svn_error_t * 1504handle_server_error(serf_request_t *request, 1505 serf_bucket_t *response, 1506 apr_pool_t *scratch_pool) 1507{ 1508 svn_ra_serf__server_error_t server_err = { 0 }; 1509 serf_bucket_t *hdrs; 1510 const char *val; 1511 apr_status_t err; 1512 1513 hdrs = serf_bucket_response_get_headers(response); 1514 val = serf_bucket_headers_get(hdrs, "Content-Type"); 1515 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) 1516 { 1517 /* ### we should figure out how to reuse begin_error_parsing */ 1518 1519 server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL); 1520 server_err.contains_precondition_error = FALSE; 1521 server_err.cdata = svn_stringbuf_create_empty(scratch_pool); 1522 server_err.collect_cdata = FALSE; 1523 server_err.parser.pool = server_err.error->pool; 1524 server_err.parser.user_data = &server_err; 1525 server_err.parser.start = start_error; 1526 server_err.parser.end = end_error; 1527 server_err.parser.cdata = cdata_error; 1528 server_err.parser.done = &server_err.done; 1529 server_err.parser.ignore_errors = TRUE; 1530 1531 /* We don't care about any errors except for SERVER_ERR.ERROR */ 1532 svn_error_clear(svn_ra_serf__handle_xml_parser(request, 1533 response, 1534 &server_err.parser, 1535 scratch_pool)); 1536 1537 /* ### checking DONE is silly. the above only parses whatever has 1538 ### been received at the network interface. totally wrong. but 1539 ### it is what we have for now (maintaining historical code), 1540 ### until we fully migrate. */ 1541 if (server_err.done && server_err.error->apr_err == APR_SUCCESS) 1542 { 1543 svn_error_clear(server_err.error); 1544 server_err.error = SVN_NO_ERROR; 1545 } 1546 1547 return svn_error_trace(server_err.error); 1548 } 1549 1550 /* The only error that we will return is from the XML response body. 1551 Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to 1552 surface. */ 1553 err = drain_bucket(response); 1554 if (err && !SERF_BUCKET_READ_ERROR(err)) 1555 return svn_ra_serf__wrap_err(err, NULL); 1556 1557 return SVN_NO_ERROR; 1558} 1559 1560 1561/* Implements svn_ra_serf__response_handler_t */ 1562svn_error_t * 1563svn_ra_serf__handle_xml_parser(serf_request_t *request, 1564 serf_bucket_t *response, 1565 void *baton, 1566 apr_pool_t *pool) 1567{ 1568 serf_status_line sl; 1569 apr_status_t status; 1570 svn_ra_serf__xml_parser_t *ctx = baton; 1571 svn_error_t *err; 1572 1573 /* ### get the HANDLER rather than fetching this. */ 1574 status = serf_bucket_response_status(response, &sl); 1575 if (SERF_BUCKET_READ_ERROR(status)) 1576 { 1577 return svn_ra_serf__wrap_err(status, NULL); 1578 } 1579 1580 /* Woo-hoo. Nothing here to see. */ 1581 if (sl.code == 404 && !ctx->ignore_errors) 1582 { 1583 err = handle_server_error(request, response, pool); 1584 1585 if (err && APR_STATUS_IS_EOF(err->apr_err)) 1586 add_done_item(ctx); 1587 1588 return svn_error_trace(err); 1589 } 1590 1591 if (ctx->headers_baton == NULL) 1592 ctx->headers_baton = serf_bucket_response_get_headers(response); 1593 else if (ctx->headers_baton != serf_bucket_response_get_headers(response)) 1594 { 1595 /* We got a new response to an existing parser... 1596 This tells us the connection has restarted and we should continue 1597 where we stopped last time. 1598 */ 1599 1600 /* Is this a second attempt?? */ 1601 if (!ctx->skip_size) 1602 ctx->skip_size = ctx->read_size; 1603 1604 ctx->read_size = 0; /* New request, nothing read */ 1605 } 1606 1607 if (!ctx->xmlp) 1608 { 1609 ctx->xmlp = XML_ParserCreate(NULL); 1610 apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup, 1611 apr_pool_cleanup_null); 1612 XML_SetUserData(ctx->xmlp, ctx); 1613 XML_SetElementHandler(ctx->xmlp, start_xml, end_xml); 1614 if (ctx->cdata) 1615 { 1616 XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml); 1617 } 1618 } 1619 1620 while (1) 1621 { 1622 const char *data; 1623 apr_size_t len; 1624 1625 status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len); 1626 1627 if (SERF_BUCKET_READ_ERROR(status)) 1628 { 1629 return svn_ra_serf__wrap_err(status, NULL); 1630 } 1631 1632 ctx->read_size += len; 1633 1634 if (ctx->skip_size) 1635 { 1636 /* Handle restarted requests correctly: Skip what we already read */ 1637 apr_size_t skip; 1638 1639 if (ctx->skip_size >= ctx->read_size) 1640 { 1641 /* Eek. What did the file shrink or something? */ 1642 if (APR_STATUS_IS_EOF(status)) 1643 { 1644 SVN_ERR_MALFUNCTION(); 1645 } 1646 1647 /* Skip on to the next iteration of this loop. */ 1648 if (APR_STATUS_IS_EAGAIN(status)) 1649 { 1650 return svn_ra_serf__wrap_err(status, NULL); 1651 } 1652 continue; 1653 } 1654 1655 skip = (apr_size_t)(len - (ctx->read_size - ctx->skip_size)); 1656 data += skip; 1657 len -= skip; 1658 ctx->skip_size = 0; 1659 } 1660 1661 /* Note: once the callbacks invoked by inject_to_parser() sets the 1662 PAUSED flag, then it will not be cleared. write_to_pending() will 1663 only save the content. Logic outside of serf_context_run() will 1664 clear that flag, as appropriate, along with processing the 1665 content that we have placed into the PENDING buffer. 1666 1667 We want to save arriving content into the PENDING structures if 1668 the parser has been paused, or we already have data in there (so 1669 the arriving data is appended, rather than injected out of order) */ 1670 if (ctx->paused || HAS_PENDING_DATA(ctx->pending)) 1671 { 1672 err = write_to_pending(ctx, data, len, pool); 1673 } 1674 else 1675 { 1676 err = inject_to_parser(ctx, data, len, &sl); 1677 if (err) 1678 { 1679 /* Should have no errors if IGNORE_ERRORS is set. */ 1680 SVN_ERR_ASSERT(!ctx->ignore_errors); 1681 } 1682 } 1683 if (err) 1684 { 1685 SVN_ERR_ASSERT(ctx->xmlp != NULL); 1686 1687 apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup); 1688 add_done_item(ctx); 1689 return svn_error_trace(err); 1690 } 1691 1692 if (APR_STATUS_IS_EAGAIN(status)) 1693 { 1694 return svn_ra_serf__wrap_err(status, NULL); 1695 } 1696 1697 if (APR_STATUS_IS_EOF(status)) 1698 { 1699 if (ctx->pending != NULL) 1700 ctx->pending->network_eof = TRUE; 1701 1702 /* We just hit the end of the network content. If we have nothing 1703 in the PENDING structures, then we're completely done. */ 1704 if (!HAS_PENDING_DATA(ctx->pending)) 1705 { 1706 SVN_ERR_ASSERT(ctx->xmlp != NULL); 1707 1708 /* Ignore the return status. We just don't care. */ 1709 (void) XML_Parse(ctx->xmlp, NULL, 0, 1); 1710 1711 apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup); 1712 add_done_item(ctx); 1713 } 1714 1715 return svn_ra_serf__wrap_err(status, NULL); 1716 } 1717 1718 /* feed me! */ 1719 } 1720 /* not reached */ 1721} 1722 1723 1724apr_status_t 1725svn_ra_serf__credentials_callback(char **username, char **password, 1726 serf_request_t *request, void *baton, 1727 int code, const char *authn_type, 1728 const char *realm, 1729 apr_pool_t *pool) 1730{ 1731 svn_ra_serf__handler_t *handler = baton; 1732 svn_ra_serf__session_t *session = handler->session; 1733 void *creds; 1734 svn_auth_cred_simple_t *simple_creds; 1735 svn_error_t *err; 1736 1737 if (code == 401) 1738 { 1739 /* Use svn_auth_first_credentials if this is the first time we ask for 1740 credentials during this session OR if the last time we asked 1741 session->auth_state wasn't set (eg. if the credentials provider was 1742 cancelled by the user). */ 1743 if (!session->auth_state) 1744 { 1745 err = svn_auth_first_credentials(&creds, 1746 &session->auth_state, 1747 SVN_AUTH_CRED_SIMPLE, 1748 realm, 1749 session->wc_callbacks->auth_baton, 1750 session->pool); 1751 } 1752 else 1753 { 1754 err = svn_auth_next_credentials(&creds, 1755 session->auth_state, 1756 session->pool); 1757 } 1758 1759 if (err) 1760 { 1761 (void) save_error(session, err); 1762 return err->apr_err; 1763 } 1764 1765 session->auth_attempts++; 1766 1767 if (!creds || session->auth_attempts > 4) 1768 { 1769 /* No more credentials. */ 1770 (void) save_error(session, 1771 svn_error_create( 1772 SVN_ERR_AUTHN_FAILED, NULL, 1773 _("No more credentials or we tried too many " 1774 "times.\nAuthentication failed"))); 1775 return SVN_ERR_AUTHN_FAILED; 1776 } 1777 1778 simple_creds = creds; 1779 *username = apr_pstrdup(pool, simple_creds->username); 1780 *password = apr_pstrdup(pool, simple_creds->password); 1781 } 1782 else 1783 { 1784 *username = apr_pstrdup(pool, session->proxy_username); 1785 *password = apr_pstrdup(pool, session->proxy_password); 1786 1787 session->proxy_auth_attempts++; 1788 1789 if (!session->proxy_username || session->proxy_auth_attempts > 4) 1790 { 1791 /* No more credentials. */ 1792 (void) save_error(session, 1793 svn_error_create( 1794 SVN_ERR_AUTHN_FAILED, NULL, 1795 _("Proxy authentication failed"))); 1796 return SVN_ERR_AUTHN_FAILED; 1797 } 1798 } 1799 1800 handler->conn->last_status_code = code; 1801 1802 return APR_SUCCESS; 1803} 1804 1805/* Wait for HTTP response status and headers, and invoke HANDLER-> 1806 response_handler() to carry out operation-specific processing. 1807 Afterwards, check for connection close. 1808 1809 SERF_STATUS allows returning errors to serf without creating a 1810 subversion error object. 1811 */ 1812static svn_error_t * 1813handle_response(serf_request_t *request, 1814 serf_bucket_t *response, 1815 svn_ra_serf__handler_t *handler, 1816 apr_status_t *serf_status, 1817 apr_pool_t *scratch_pool) 1818{ 1819 apr_status_t status; 1820 svn_error_t *err; 1821 1822 /* ### need to verify whether this already gets init'd on every 1823 ### successful exit. for an error-exit, it will (properly) be 1824 ### ignored by the caller. */ 1825 *serf_status = APR_SUCCESS; 1826 1827 if (!response) 1828 { 1829 /* Uh-oh. Our connection died. */ 1830 if (handler->response_error) 1831 SVN_ERR(handler->response_error(request, response, 0, 1832 handler->response_error_baton)); 1833 1834 /* Requeue another request for this handler. 1835 ### how do we know if the handler can deal with this?! */ 1836 svn_ra_serf__request_create(handler); 1837 1838 return SVN_NO_ERROR; 1839 } 1840 1841 /* If we're reading the body, then skip all this preparation. */ 1842 if (handler->reading_body) 1843 goto process_body; 1844 1845 /* Copy the Status-Line info into HANDLER, if we don't yet have it. */ 1846 if (handler->sline.version == 0) 1847 { 1848 serf_status_line sl; 1849 1850 status = serf_bucket_response_status(response, &sl); 1851 if (status != APR_SUCCESS) 1852 { 1853 /* The response line is not (yet) ready, or some other error. */ 1854 *serf_status = status; 1855 return SVN_NO_ERROR; /* Handled by serf */ 1856 } 1857 1858 /* If we got APR_SUCCESS, then we should have Status-Line info. */ 1859 SVN_ERR_ASSERT(sl.version != 0); 1860 1861 handler->sline = sl; 1862 handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason); 1863 1864 /* HTTP/1.1? (or later) */ 1865 if (sl.version != SERF_HTTP_10) 1866 handler->session->http10 = FALSE; 1867 } 1868 1869 /* Keep reading from the network until we've read all the headers. */ 1870 status = serf_bucket_response_wait_for_headers(response); 1871 if (status) 1872 { 1873 /* The typical "error" will be APR_EAGAIN, meaning that more input 1874 from the network is required to complete the reading of the 1875 headers. */ 1876 if (!APR_STATUS_IS_EOF(status)) 1877 { 1878 /* Either the headers are not (yet) complete, or there really 1879 was an error. */ 1880 *serf_status = status; 1881 return SVN_NO_ERROR; 1882 } 1883 1884 /* wait_for_headers() will return EOF if there is no body in this 1885 response, or if we completely read the body. The latter is not 1886 true since we would have set READING_BODY to get the body read, 1887 and we would not be back to this code block. 1888 1889 It can also return EOF if we truly hit EOF while (say) processing 1890 the headers. aka Badness. */ 1891 1892 /* Cases where a lack of a response body (via EOF) is okay: 1893 * - A HEAD request 1894 * - 204/304 response 1895 * 1896 * Otherwise, if we get an EOF here, something went really wrong: either 1897 * the server closed on us early or we're reading too much. Either way, 1898 * scream loudly. 1899 */ 1900 if (strcmp(handler->method, "HEAD") != 0 1901 && handler->sline.code != 204 1902 && handler->sline.code != 304) 1903 { 1904 err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, 1905 svn_ra_serf__wrap_err(status, NULL), 1906 _("Premature EOF seen from server" 1907 " (http status=%d)"), 1908 handler->sline.code); 1909 1910 /* In case anything else arrives... discard it. */ 1911 handler->discard_body = TRUE; 1912 1913 return err; 1914 } 1915 } 1916 1917 /* ... and set up the header fields in HANDLER. */ 1918 handler->location = response_get_location(response, 1919 handler->session->session_url_str, 1920 handler->handler_pool, 1921 scratch_pool); 1922 1923 /* On the last request, we failed authentication. We succeeded this time, 1924 so let's save away these credentials. */ 1925 if (handler->conn->last_status_code == 401 && handler->sline.code < 400) 1926 { 1927 SVN_ERR(svn_auth_save_credentials(handler->session->auth_state, 1928 handler->session->pool)); 1929 handler->session->auth_attempts = 0; 1930 handler->session->auth_state = NULL; 1931 } 1932 handler->conn->last_status_code = handler->sline.code; 1933 1934 if (handler->sline.code == 405 1935 || handler->sline.code == 408 1936 || handler->sline.code == 409 1937 || handler->sline.code >= 500) 1938 { 1939 /* 405 Method Not allowed. 1940 408 Request Timeout 1941 409 Conflict: can indicate a hook error. 1942 5xx (Internal) Server error. */ 1943 serf_bucket_t *hdrs; 1944 const char *val; 1945 1946 hdrs = serf_bucket_response_get_headers(response); 1947 val = serf_bucket_headers_get(hdrs, "Content-Type"); 1948 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) 1949 { 1950 svn_ra_serf__server_error_t *server_err; 1951 1952 server_err = begin_error_parsing(start_error, end_error, cdata_error, 1953 handler->handler_pool); 1954 /* Get the parser to set our DONE flag. */ 1955 server_err->parser.done = &handler->done; 1956 1957 handler->server_error = server_err; 1958 } 1959 else 1960 { 1961 handler->discard_body = TRUE; 1962 1963 if (!handler->session->pending_error) 1964 { 1965 apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; 1966 1967 /* 405 == Method Not Allowed (Occurs when trying to lock a working 1968 copy path which no longer exists at HEAD in the repository. */ 1969 if (handler->sline.code == 405 1970 && strcmp(handler->method, "LOCK") == 0) 1971 apr_err = SVN_ERR_FS_OUT_OF_DATE; 1972 1973 handler->session->pending_error = 1974 svn_error_createf(apr_err, NULL, 1975 _("%s request on '%s' failed: %d %s"), 1976 handler->method, handler->path, 1977 handler->sline.code, handler->sline.reason); 1978 } 1979 } 1980 } 1981 1982 /* Stop processing the above, on every packet arrival. */ 1983 handler->reading_body = TRUE; 1984 1985 process_body: 1986 1987 /* We've been instructed to ignore the body. Drain whatever is present. */ 1988 if (handler->discard_body) 1989 { 1990 *serf_status = drain_bucket(response); 1991 1992 /* If the handler hasn't set done (which it shouldn't have) and 1993 we now have the EOF, go ahead and set it so that we can stop 1994 our context loops. 1995 */ 1996 if (!handler->done && APR_STATUS_IS_EOF(*serf_status)) 1997 handler->done = TRUE; 1998 1999 return SVN_NO_ERROR; 2000 } 2001 2002 /* If we are supposed to parse the body as a server_error, then do 2003 that now. */ 2004 if (handler->server_error != NULL) 2005 { 2006 err = svn_ra_serf__handle_xml_parser(request, response, 2007 &handler->server_error->parser, 2008 scratch_pool); 2009 2010 /* If we do not receive an error or it is a non-transient error, return 2011 immediately. 2012 2013 APR_EOF will be returned when parsing is complete. 2014 2015 APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through 2016 parsing and the network has no more data right now. If we receive that, 2017 clear the error and return - allowing serf to wait for more data. 2018 */ 2019 if (!err || SERF_BUCKET_READ_ERROR(err->apr_err)) 2020 return svn_error_trace(err); 2021 2022 if (!APR_STATUS_IS_EOF(err->apr_err)) 2023 { 2024 *serf_status = err->apr_err; 2025 svn_error_clear(err); 2026 return SVN_NO_ERROR; 2027 } 2028 2029 /* Clear the EOF. We don't need it. */ 2030 svn_error_clear(err); 2031 2032 /* If the parsing is done, and we did not extract an error, then 2033 simply toss everything, and anything else that might arrive. 2034 The higher-level code will need to investigate HANDLER->SLINE, 2035 as we have no further information for them. */ 2036 if (handler->done 2037 && handler->server_error->error->apr_err == APR_SUCCESS) 2038 { 2039 svn_error_clear(handler->server_error->error); 2040 2041 /* Stop parsing for a server error. */ 2042 handler->server_error = NULL; 2043 2044 /* If anything arrives after this, then just discard it. */ 2045 handler->discard_body = TRUE; 2046 } 2047 2048 *serf_status = APR_EOF; 2049 return SVN_NO_ERROR; 2050 } 2051 2052 /* Pass the body along to the registered response handler. */ 2053 err = handler->response_handler(request, response, 2054 handler->response_baton, 2055 scratch_pool); 2056 2057 if (err 2058 && (!SERF_BUCKET_READ_ERROR(err->apr_err) 2059 || APR_STATUS_IS_ECONNRESET(err->apr_err) 2060 || APR_STATUS_IS_ECONNABORTED(err->apr_err))) 2061 { 2062 /* These errors are special cased in serf 2063 ### We hope no handler returns these by accident. */ 2064 *serf_status = err->apr_err; 2065 svn_error_clear(err); 2066 return SVN_NO_ERROR; 2067 } 2068 2069 return svn_error_trace(err); 2070} 2071 2072 2073/* Implements serf_response_handler_t for handle_response. Storing 2074 errors in handler->session->pending_error if appropriate. */ 2075static apr_status_t 2076handle_response_cb(serf_request_t *request, 2077 serf_bucket_t *response, 2078 void *baton, 2079 apr_pool_t *scratch_pool) 2080{ 2081 svn_ra_serf__handler_t *handler = baton; 2082 svn_error_t *err; 2083 apr_status_t inner_status; 2084 apr_status_t outer_status; 2085 2086 err = svn_error_trace(handle_response(request, response, 2087 handler, &inner_status, 2088 scratch_pool)); 2089 2090 /* Select the right status value to return. */ 2091 outer_status = save_error(handler->session, err); 2092 if (!outer_status) 2093 outer_status = inner_status; 2094 2095 /* Make sure the DONE flag is set properly. */ 2096 if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status)) 2097 handler->done = TRUE; 2098 2099 return outer_status; 2100} 2101 2102/* Perform basic request setup, with special handling for HEAD requests, 2103 and finer-grained callbacks invoked (if non-NULL) to produce the request 2104 headers and body. */ 2105static svn_error_t * 2106setup_request(serf_request_t *request, 2107 svn_ra_serf__handler_t *handler, 2108 serf_bucket_t **req_bkt, 2109 apr_pool_t *request_pool, 2110 apr_pool_t *scratch_pool) 2111{ 2112 serf_bucket_t *body_bkt; 2113 serf_bucket_t *headers_bkt; 2114 const char *accept_encoding; 2115 2116 if (handler->body_delegate) 2117 { 2118 serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request); 2119 2120 /* ### should pass the scratch_pool */ 2121 SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton, 2122 bkt_alloc, request_pool)); 2123 } 2124 else 2125 { 2126 body_bkt = NULL; 2127 } 2128 2129 if (handler->custom_accept_encoding) 2130 { 2131 accept_encoding = NULL; 2132 } 2133 else if (handler->session->using_compression) 2134 { 2135 /* Accept gzip compression if enabled. */ 2136 accept_encoding = "gzip"; 2137 } 2138 else 2139 { 2140 accept_encoding = NULL; 2141 } 2142 2143 SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt, 2144 handler->session, handler->method, handler->path, 2145 body_bkt, handler->body_type, accept_encoding, 2146 request_pool, scratch_pool)); 2147 2148 if (handler->header_delegate) 2149 { 2150 /* ### should pass the scratch_pool */ 2151 SVN_ERR(handler->header_delegate(headers_bkt, 2152 handler->header_delegate_baton, 2153 request_pool)); 2154 } 2155 2156 return APR_SUCCESS; 2157} 2158 2159/* Implements the serf_request_setup_t interface (which sets up both a 2160 request and its response handler callback). Handles errors for 2161 setup_request_cb */ 2162static apr_status_t 2163setup_request_cb(serf_request_t *request, 2164 void *setup_baton, 2165 serf_bucket_t **req_bkt, 2166 serf_response_acceptor_t *acceptor, 2167 void **acceptor_baton, 2168 serf_response_handler_t *s_handler, 2169 void **s_handler_baton, 2170 apr_pool_t *pool) 2171{ 2172 svn_ra_serf__handler_t *handler = setup_baton; 2173 svn_error_t *err; 2174 2175 /* ### construct a scratch_pool? serf gives us a pool that will live for 2176 ### the duration of the request. */ 2177 apr_pool_t *scratch_pool = pool; 2178 2179 if (strcmp(handler->method, "HEAD") == 0) 2180 *acceptor = accept_head; 2181 else 2182 *acceptor = accept_response; 2183 *acceptor_baton = handler->session; 2184 2185 *s_handler = handle_response_cb; 2186 *s_handler_baton = handler; 2187 2188 err = svn_error_trace(setup_request(request, handler, req_bkt, 2189 pool /* request_pool */, scratch_pool)); 2190 2191 return save_error(handler->session, err); 2192} 2193 2194void 2195svn_ra_serf__request_create(svn_ra_serf__handler_t *handler) 2196{ 2197 SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL); 2198 2199 /* In case HANDLER is re-queued, reset the various transient fields. 2200 2201 ### prior to recent changes, HANDLER was constant. maybe we should 2202 ### break out these processing fields, apart from the request 2203 ### definition. */ 2204 handler->done = FALSE; 2205 handler->server_error = NULL; 2206 handler->sline.version = 0; 2207 handler->location = NULL; 2208 handler->reading_body = FALSE; 2209 handler->discard_body = FALSE; 2210 2211 /* ### do we ever alter the >response_handler? */ 2212 2213 /* ### do we need to hold onto the returned request object, or just 2214 ### not worry about it (the serf ctx will manage it). */ 2215 (void) serf_connection_request_create(handler->conn->conn, 2216 setup_request_cb, handler); 2217} 2218 2219 2220svn_error_t * 2221svn_ra_serf__discover_vcc(const char **vcc_url, 2222 svn_ra_serf__session_t *session, 2223 svn_ra_serf__connection_t *conn, 2224 apr_pool_t *pool) 2225{ 2226 const char *path; 2227 const char *relative_path; 2228 const char *uuid; 2229 2230 /* If we've already got the information our caller seeks, just return it. */ 2231 if (session->vcc_url && session->repos_root_str) 2232 { 2233 *vcc_url = session->vcc_url; 2234 return SVN_NO_ERROR; 2235 } 2236 2237 /* If no connection is provided, use the default one. */ 2238 if (! conn) 2239 { 2240 conn = session->conns[0]; 2241 } 2242 2243 path = session->session_url.path; 2244 *vcc_url = NULL; 2245 uuid = NULL; 2246 2247 do 2248 { 2249 apr_hash_t *props; 2250 svn_error_t *err; 2251 2252 err = svn_ra_serf__fetch_node_props(&props, conn, 2253 path, SVN_INVALID_REVNUM, 2254 base_props, pool, pool); 2255 if (! err) 2256 { 2257 apr_hash_t *ns_props; 2258 2259 ns_props = apr_hash_get(props, "DAV:", 4); 2260 *vcc_url = svn_prop_get_value(ns_props, 2261 "version-controlled-configuration"); 2262 2263 ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV); 2264 relative_path = svn_prop_get_value(ns_props, 2265 "baseline-relative-path"); 2266 uuid = svn_prop_get_value(ns_props, "repository-uuid"); 2267 break; 2268 } 2269 else 2270 { 2271 if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) && 2272 (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN)) 2273 { 2274 return svn_error_trace(err); /* found a _real_ error */ 2275 } 2276 else 2277 { 2278 /* This happens when the file is missing in HEAD. */ 2279 svn_error_clear(err); 2280 2281 /* Okay, strip off a component from PATH. */ 2282 path = svn_urlpath__dirname(path, pool); 2283 2284 /* An error occurred on conns. serf 0.4.0 remembers that 2285 the connection had a problem. We need to reset it, in 2286 order to use it again. */ 2287 serf_connection_reset(conn->conn); 2288 } 2289 } 2290 } 2291 while ((path[0] != '\0') 2292 && (! (path[0] == '/' && path[1] == '\0'))); 2293 2294 if (!*vcc_url) 2295 { 2296 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 2297 _("The PROPFIND response did not include the " 2298 "requested version-controlled-configuration " 2299 "value")); 2300 } 2301 2302 /* Store our VCC in our cache. */ 2303 if (!session->vcc_url) 2304 { 2305 session->vcc_url = apr_pstrdup(session->pool, *vcc_url); 2306 } 2307 2308 /* Update our cached repository root URL. */ 2309 if (!session->repos_root_str) 2310 { 2311 svn_stringbuf_t *url_buf; 2312 2313 url_buf = svn_stringbuf_create(path, pool); 2314 2315 svn_path_remove_components(url_buf, 2316 svn_path_component_count(relative_path)); 2317 2318 /* Now recreate the root_url. */ 2319 session->repos_root = session->session_url; 2320 session->repos_root.path = 2321 (char *)svn_fspath__canonicalize(url_buf->data, session->pool); 2322 session->repos_root_str = 2323 svn_urlpath__canonicalize(apr_uri_unparse(session->pool, 2324 &session->repos_root, 0), 2325 session->pool); 2326 } 2327 2328 /* Store the repository UUID in the cache. */ 2329 if (!session->uuid) 2330 { 2331 session->uuid = apr_pstrdup(session->pool, uuid); 2332 } 2333 2334 return SVN_NO_ERROR; 2335} 2336 2337svn_error_t * 2338svn_ra_serf__get_relative_path(const char **rel_path, 2339 const char *orig_path, 2340 svn_ra_serf__session_t *session, 2341 svn_ra_serf__connection_t *conn, 2342 apr_pool_t *pool) 2343{ 2344 const char *decoded_root, *decoded_orig; 2345 2346 if (! session->repos_root.path) 2347 { 2348 const char *vcc_url; 2349 2350 /* This should only happen if we haven't detected HTTP v2 2351 support from the server. */ 2352 assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 2353 2354 /* We don't actually care about the VCC_URL, but this API 2355 promises to populate the session's root-url cache, and that's 2356 what we really want. */ 2357 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, 2358 conn ? conn : session->conns[0], 2359 pool)); 2360 } 2361 2362 decoded_root = svn_path_uri_decode(session->repos_root.path, pool); 2363 decoded_orig = svn_path_uri_decode(orig_path, pool); 2364 *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig); 2365 SVN_ERR_ASSERT(*rel_path != NULL); 2366 return SVN_NO_ERROR; 2367} 2368 2369svn_error_t * 2370svn_ra_serf__report_resource(const char **report_target, 2371 svn_ra_serf__session_t *session, 2372 svn_ra_serf__connection_t *conn, 2373 apr_pool_t *pool) 2374{ 2375 /* If we have HTTP v2 support, we want to report against the 'me' 2376 resource. */ 2377 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 2378 *report_target = apr_pstrdup(pool, session->me_resource); 2379 2380 /* Otherwise, we'll use the default VCC. */ 2381 else 2382 SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool)); 2383 2384 return SVN_NO_ERROR; 2385} 2386 2387svn_error_t * 2388svn_ra_serf__error_on_status(serf_status_line sline, 2389 const char *path, 2390 const char *location) 2391{ 2392 switch(sline.code) 2393 { 2394 case 301: 2395 case 302: 2396 case 307: 2397 return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL, 2398 (sline.code == 301) 2399 ? _("Repository moved permanently to '%s';" 2400 " please relocate") 2401 : _("Repository moved temporarily to '%s';" 2402 " please relocate"), location); 2403 case 403: 2404 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL, 2405 _("Access to '%s' forbidden"), path); 2406 2407 case 404: 2408 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 2409 _("'%s' path not found"), path); 2410 case 423: 2411 return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL, 2412 _("'%s': no lock token available"), path); 2413 2414 case 411: 2415 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 2416 _("DAV request failed: 411 Content length required. The " 2417 "server or an intermediate proxy does not accept " 2418 "chunked encoding. Try setting 'http-chunked-requests' " 2419 "to 'auto' or 'no' in your client configuration.")); 2420 } 2421 2422 if (sline.code >= 300) 2423 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 2424 _("Unexpected HTTP status %d '%s' on '%s'\n"), 2425 sline.code, sline.reason, path); 2426 2427 return SVN_NO_ERROR; 2428} 2429 2430svn_error_t * 2431svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session, 2432 svn_delta_shim_callbacks_t *callbacks) 2433{ 2434 svn_ra_serf__session_t *session = ra_session->priv; 2435 2436 session->shim_callbacks = callbacks; 2437 return SVN_NO_ERROR; 2438} 2439 2440 2441/* Conforms to Expat's XML_StartElementHandler */ 2442static void 2443expat_start(void *userData, const char *raw_name, const char **attrs) 2444{ 2445 struct expat_ctx_t *ectx = userData; 2446 2447 if (ectx->inner_error != NULL) 2448 return; 2449 2450 ectx->inner_error = svn_error_trace( 2451 svn_ra_serf__xml_cb_start(ectx->xmlctx, 2452 raw_name, attrs)); 2453 2454#ifdef EXPAT_HAS_STOPPARSER 2455 if (ectx->inner_error) 2456 (void) XML_StopParser(ectx->parser, 0 /* resumable */); 2457#endif 2458} 2459 2460 2461/* Conforms to Expat's XML_EndElementHandler */ 2462static void 2463expat_end(void *userData, const char *raw_name) 2464{ 2465 struct expat_ctx_t *ectx = userData; 2466 2467 if (ectx->inner_error != NULL) 2468 return; 2469 2470 ectx->inner_error = svn_error_trace( 2471 svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name)); 2472 2473#ifdef EXPAT_HAS_STOPPARSER 2474 if (ectx->inner_error) 2475 (void) XML_StopParser(ectx->parser, 0 /* resumable */); 2476#endif 2477} 2478 2479 2480/* Conforms to Expat's XML_CharacterDataHandler */ 2481static void 2482expat_cdata(void *userData, const char *data, int len) 2483{ 2484 struct expat_ctx_t *ectx = userData; 2485 2486 if (ectx->inner_error != NULL) 2487 return; 2488 2489 ectx->inner_error = svn_error_trace( 2490 svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len)); 2491 2492#ifdef EXPAT_HAS_STOPPARSER 2493 if (ectx->inner_error) 2494 (void) XML_StopParser(ectx->parser, 0 /* resumable */); 2495#endif 2496} 2497 2498 2499/* Implements svn_ra_serf__response_handler_t */ 2500static svn_error_t * 2501expat_response_handler(serf_request_t *request, 2502 serf_bucket_t *response, 2503 void *baton, 2504 apr_pool_t *scratch_pool) 2505{ 2506 struct expat_ctx_t *ectx = baton; 2507 2508 if (!ectx->parser) 2509 { 2510 ectx->parser = XML_ParserCreate(NULL); 2511 apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser, 2512 xml_parser_cleanup, apr_pool_cleanup_null); 2513 XML_SetUserData(ectx->parser, ectx); 2514 XML_SetElementHandler(ectx->parser, expat_start, expat_end); 2515 XML_SetCharacterDataHandler(ectx->parser, expat_cdata); 2516 } 2517 2518 /* ### TODO: sline.code < 200 should really be handled by the core */ 2519 if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300)) 2520 { 2521 /* By deferring to expect_empty_body(), it will make a choice on 2522 how to handle the body. Whatever the decision, the core handler 2523 will take over, and we will not be called again. */ 2524 return svn_error_trace(svn_ra_serf__expect_empty_body( 2525 request, response, ectx->handler, 2526 scratch_pool)); 2527 } 2528 2529 while (1) 2530 { 2531 apr_status_t status; 2532 const char *data; 2533 apr_size_t len; 2534 int expat_status; 2535 2536 status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len); 2537 if (SERF_BUCKET_READ_ERROR(status)) 2538 return svn_ra_serf__wrap_err(status, NULL); 2539 2540#if 0 2541 /* ### move restart/skip into the core handler */ 2542 ectx->handler->read_size += len; 2543#endif 2544 2545 /* ### move PAUSED behavior to a new response handler that can feed 2546 ### an inner handler, or can pause for a while. */ 2547 2548 /* ### should we have an IGNORE_ERRORS flag like the v1 parser? */ 2549 2550 expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */); 2551 2552 /* We need to check INNER_ERROR first. This is an error from the 2553 callbacks that has been "dropped off" for us to retrieve. On 2554 current Expat parsers, we stop the parser when an error occurs, 2555 so we want to ignore EXPAT_STATUS (which reports the stoppage). 2556 2557 If an error is not present, THEN we go ahead and look for parsing 2558 errors. */ 2559 if (ectx->inner_error) 2560 { 2561 apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser, 2562 xml_parser_cleanup); 2563 return svn_error_trace(ectx->inner_error); 2564 } 2565 if (expat_status == XML_STATUS_ERROR) 2566 return svn_error_createf(SVN_ERR_XML_MALFORMED, 2567 ectx->inner_error, 2568 _("The %s response contains invalid XML" 2569 " (%d %s)"), 2570 ectx->handler->method, 2571 ectx->handler->sline.code, 2572 ectx->handler->sline.reason); 2573 2574 /* The parsing went fine. What has the bucket told us? */ 2575 2576 if (APR_STATUS_IS_EOF(status)) 2577 { 2578 /* Tell expat we've reached the end of the content. Ignore the 2579 return status. We just don't care. */ 2580 (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */); 2581 2582 svn_ra_serf__xml_context_destroy(ectx->xmlctx); 2583 apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser, 2584 xml_parser_cleanup); 2585 2586 /* ### should check XMLCTX to see if it has returned to the 2587 ### INITIAL state. we may have ended early... */ 2588 } 2589 2590 if (status && !SERF_BUCKET_READ_ERROR(status)) 2591 { 2592 return svn_ra_serf__wrap_err(status, NULL); 2593 } 2594 } 2595 2596 /* NOTREACHED */ 2597} 2598 2599 2600svn_ra_serf__handler_t * 2601svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx, 2602 apr_pool_t *result_pool) 2603{ 2604 svn_ra_serf__handler_t *handler; 2605 struct expat_ctx_t *ectx; 2606 2607 ectx = apr_pcalloc(result_pool, sizeof(*ectx)); 2608 ectx->xmlctx = xmlctx; 2609 ectx->parser = NULL; 2610 ectx->cleanup_pool = result_pool; 2611 2612 2613 handler = apr_pcalloc(result_pool, sizeof(*handler)); 2614 handler->handler_pool = result_pool; 2615 handler->response_handler = expat_response_handler; 2616 handler->response_baton = ectx; 2617 2618 ectx->handler = handler; 2619 2620 return handler; 2621} 2622