2 * util.c : serf utility routines for ra_serf
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
28 #define APR_WANT_STRFUNC
31 #include <apr_fnmatch.h>
34 #include <serf_bucket_types.h>
39 #include "svn_dirent_uri.h"
41 #include "svn_private_config.h"
42 #include "svn_string.h"
44 #include "svn_props.h"
45 #include "svn_dirent_uri.h"
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 #include "private/svn_auth_private.h"
56 /* Fix for older expat 1.95.x's that do not define
57 * XML_STATUS_OK/XML_STATUS_ERROR
60 #define XML_STATUS_OK 1
61 #define XML_STATUS_ERROR 0
64 #ifndef XML_VERSION_AT_LEAST
65 #define XML_VERSION_AT_LEAST(major,minor,patch) \
66 (((major) < XML_MAJOR_VERSION) \
67 || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \
68 || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
69 (patch) <= XML_MICRO_VERSION))
70 #endif /* APR_VERSION_AT_LEAST */
72 #if XML_VERSION_AT_LEAST(1, 95, 8)
73 #define EXPAT_HAS_STOPPARSER
76 /* Read/write chunks of this size into the spillbuf. */
77 #define PARSE_CHUNK_SIZE 8000
79 /* We will store one megabyte in memory, before switching to store content
80 into a temporary file. */
81 #define SPILL_SIZE 1000000
84 /* This structure records pending data for the parser in memory blocks,
85 and possibly into a temporary file if "too much" content arrives. */
86 struct svn_ra_serf__pending_t {
87 /* The spillbuf where we record the pending data. */
90 /* This flag is set when the network has reached EOF. The PENDING
91 processing can then properly detect when parsing has completed. */
92 svn_boolean_t network_eof;
95 #define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \
96 && svn_spillbuf__get_size((p)->buf) != 0)
100 svn_ra_serf__xml_context_t *xmlctx;
102 svn_ra_serf__handler_t *handler;
104 svn_error_t *inner_error;
106 /* Do not use this pool for allocation. It is merely recorded for running
107 the cleanup handler. */
108 apr_pool_t *cleanup_pool;
112 static const apr_uint32_t serf_failure_map[][2] =
114 { SERF_SSL_CERT_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID },
115 { SERF_SSL_CERT_EXPIRED, SVN_AUTH_SSL_EXPIRED },
116 { SERF_SSL_CERT_SELF_SIGNED, SVN_AUTH_SSL_UNKNOWNCA },
117 { SERF_SSL_CERT_UNKNOWNCA, SVN_AUTH_SSL_UNKNOWNCA }
120 /* Return a Subversion failure mask based on FAILURES, a serf SSL
121 failure mask. If anything in FAILURES is not directly mappable to
122 Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */
124 ssl_convert_serf_failures(int failures)
126 apr_uint32_t svn_failures = 0;
129 for (i = 0; i < sizeof(serf_failure_map) / (2 * sizeof(apr_uint32_t)); ++i)
131 if (failures & serf_failure_map[i][0])
133 svn_failures |= serf_failure_map[i][1];
134 failures &= ~serf_failure_map[i][0];
138 /* Map any remaining failure bits to our OTHER bit. */
141 svn_failures |= SVN_AUTH_SSL_OTHER;
149 save_error(svn_ra_serf__session_t *session,
152 if (err || session->pending_error)
154 session->pending_error = svn_error_compose_create(
155 session->pending_error,
157 return session->pending_error->apr_err;
164 /* Construct the realmstring, e.g. https://svn.collab.net:443. */
166 construct_realm(svn_ra_serf__session_t *session,
172 if (session->session_url.port_str)
174 port = session->session_url.port;
178 port = apr_uri_port_of_scheme(session->session_url.scheme);
181 realm = apr_psprintf(pool, "%s://%s:%d",
182 session->session_url.scheme,
183 session->session_url.hostname,
189 /* Convert a hash table containing the fields (as documented in X.509) of an
190 organisation to a string ORG, allocated in POOL. ORG is as returned by
191 serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */
193 convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
195 const char *org_unit = svn_hash_gets(org, "OU");
196 const char *org_name = svn_hash_gets(org, "O");
197 const char *locality = svn_hash_gets(org, "L");
198 const char *state = svn_hash_gets(org, "ST");
199 const char *country = svn_hash_gets(org, "C");
200 const char *email = svn_hash_gets(org, "E");
201 svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
205 svn_stringbuf_appendcstr(buf, org_unit);
206 svn_stringbuf_appendcstr(buf, ", ");
211 svn_stringbuf_appendcstr(buf, org_name);
212 svn_stringbuf_appendcstr(buf, ", ");
217 svn_stringbuf_appendcstr(buf, locality);
218 svn_stringbuf_appendcstr(buf, ", ");
223 svn_stringbuf_appendcstr(buf, state);
224 svn_stringbuf_appendcstr(buf, ", ");
229 svn_stringbuf_appendcstr(buf, country);
230 svn_stringbuf_appendcstr(buf, ", ");
233 /* Chop ', ' if any. */
234 svn_stringbuf_chop(buf, 2);
238 svn_stringbuf_appendcstr(buf, "(");
239 svn_stringbuf_appendcstr(buf, email);
240 svn_stringbuf_appendcstr(buf, ")");
246 static void append_reason(svn_stringbuf_t *errmsg, const char *reason, int *reasons)
249 svn_stringbuf_appendcstr(errmsg, _(": "));
251 svn_stringbuf_appendcstr(errmsg, _(", "));
252 svn_stringbuf_appendcstr(errmsg, reason);
256 /* This function is called on receiving a ssl certificate of a server when
257 opening a https connection. It allows Subversion to override the initial
258 validation done by serf.
259 Serf provides us the @a baton as provided in the call to
260 serf_ssl_server_cert_callback_set. The result of serf's initial validation
261 of the certificate @a CERT is returned as a bitmask in FAILURES. */
263 ssl_server_cert(void *baton, int failures,
264 const serf_ssl_certificate_t *cert,
265 apr_pool_t *scratch_pool)
267 svn_ra_serf__connection_t *conn = baton;
268 svn_auth_ssl_server_cert_info_t cert_info;
269 svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
270 svn_auth_iterstate_t *state;
271 const char *realmstring;
272 apr_uint32_t svn_failures;
274 apr_hash_t *subject = NULL;
275 apr_hash_t *serf_cert = NULL;
277 int found_matching_hostname = 0;
279 svn_failures = (ssl_convert_serf_failures(failures)
280 | conn->server_cert_failures);
282 if (serf_ssl_cert_depth(cert) == 0)
284 /* If the depth is 0, the hostname must match the certificate.
286 ### This should really be handled by serf, which should pass an error
287 for this case, but that has backwards compatibility issues. */
288 apr_array_header_t *san;
290 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
292 san = svn_hash_gets(serf_cert, "subjectAltName");
293 /* Try to find matching server name via subjectAltName first... */
296 for (i = 0; i < san->nelts; i++) {
297 const char *s = APR_ARRAY_IDX(san, i, const char*);
298 if (apr_fnmatch(s, conn->session->session_url.hostname,
299 APR_FNM_PERIOD | APR_FNM_CASE_BLIND) == APR_SUCCESS)
301 found_matching_hostname = 1;
307 /* Match server certificate CN with the hostname of the server */
308 if (!found_matching_hostname)
310 const char *hostname = NULL;
312 subject = serf_ssl_cert_subject(cert, scratch_pool);
315 hostname = svn_hash_gets(subject, "CN");
318 || apr_fnmatch(hostname, conn->session->session_url.hostname,
319 APR_FNM_PERIOD | APR_FNM_CASE_BLIND) != APR_SUCCESS)
321 svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
329 /* Extract the info from the certificate */
331 subject = serf_ssl_cert_subject(cert, scratch_pool);
332 issuer = serf_ssl_cert_issuer(cert, scratch_pool);
334 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
336 cert_info.hostname = svn_hash_gets(subject, "CN");
337 cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
338 if (! cert_info.fingerprint)
339 cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
340 cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
341 if (! cert_info.valid_from)
342 cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
343 cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
344 if (! cert_info.valid_until)
345 cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
346 cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
347 cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
349 /* Handle any non-server certs. */
350 if (serf_ssl_cert_depth(cert) > 0)
354 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
355 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
358 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
359 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
362 realmstring = apr_psprintf(scratch_pool, "AUTHORITY:%s",
363 cert_info.fingerprint);
365 err = svn_auth_first_credentials(&creds, &state,
366 SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
368 conn->session->wc_callbacks->auth_baton,
371 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
372 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
374 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
375 SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);
379 if (err->apr_err != SVN_ERR_AUTHN_NO_PROVIDER)
380 return svn_error_trace(err);
382 /* No provider registered that handles server authorities */
383 svn_error_clear(err);
389 server_creds = creds;
390 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
392 svn_failures &= ~server_creds->accepted_failures;
396 conn->server_cert_failures |= svn_failures;
401 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
402 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
405 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
406 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
409 realmstring = construct_realm(conn->session, conn->session->pool);
411 SVN_ERR(svn_auth_first_credentials(&creds, &state,
412 SVN_AUTH_CRED_SSL_SERVER_TRUST,
414 conn->session->wc_callbacks->auth_baton,
418 server_creds = creds;
419 svn_failures &= ~server_creds->accepted_failures;
420 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
423 while (svn_failures && creds)
425 SVN_ERR(svn_auth_next_credentials(&creds, state, scratch_pool));
429 server_creds = creds;
430 svn_failures &= ~server_creds->accepted_failures;
431 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
435 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
436 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
438 /* Are there non accepted failures left? */
441 svn_stringbuf_t *errmsg;
444 errmsg = svn_stringbuf_create(
445 _("Server SSL certificate verification failed"),
449 if (svn_failures & SVN_AUTH_SSL_NOTYETVALID)
450 append_reason(errmsg, _("certificate is not yet valid"), &reasons);
452 if (svn_failures & SVN_AUTH_SSL_EXPIRED)
453 append_reason(errmsg, _("certificate has expired"), &reasons);
455 if (svn_failures & SVN_AUTH_SSL_CNMISMATCH)
456 append_reason(errmsg,
457 _("certificate issued for a different hostname"),
460 if (svn_failures & SVN_AUTH_SSL_UNKNOWNCA)
461 append_reason(errmsg, _("issuer is not trusted"), &reasons);
463 if (svn_failures & SVN_AUTH_SSL_OTHER)
464 append_reason(errmsg, _("and other reason(s)"), &reasons);
466 return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL,
473 /* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
475 ssl_server_cert_cb(void *baton, int failures,
476 const serf_ssl_certificate_t *cert)
478 svn_ra_serf__connection_t *conn = baton;
479 svn_ra_serf__session_t *session = conn->session;
483 subpool = svn_pool_create(session->pool);
484 err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
485 svn_pool_destroy(subpool);
487 return save_error(session, err);
491 load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
494 apr_array_header_t *files = svn_cstring_split(authorities, ";",
495 TRUE /* chop_whitespace */,
499 for (i = 0; i < files->nelts; ++i)
501 const char *file = APR_ARRAY_IDX(files, i, const char *);
502 serf_ssl_certificate_t *ca_cert;
503 apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
505 if (status == APR_SUCCESS)
506 status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
508 if (status != APR_SUCCESS)
510 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
511 _("Invalid config: unable to load certificate file '%s'"),
512 svn_dirent_local_style(file, pool));
520 conn_setup(apr_socket_t *sock,
521 serf_bucket_t **read_bkt,
522 serf_bucket_t **write_bkt,
526 svn_ra_serf__connection_t *conn = baton;
528 *read_bkt = serf_context_bucket_socket_create(conn->session->context,
529 sock, conn->bkt_alloc);
531 if (conn->session->using_ssl)
534 *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
536 if (!conn->ssl_context)
538 conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
540 serf_ssl_set_hostname(conn->ssl_context,
541 conn->session->session_url.hostname);
543 serf_ssl_client_cert_provider_set(conn->ssl_context,
544 svn_ra_serf__handle_client_cert,
545 conn, conn->session->pool);
546 serf_ssl_client_cert_password_set(conn->ssl_context,
547 svn_ra_serf__handle_client_cert_pw,
548 conn, conn->session->pool);
549 serf_ssl_server_cert_callback_set(conn->ssl_context,
553 /* See if the user wants us to trust "default" openssl CAs. */
554 if (conn->session->trust_default_ca)
556 serf_ssl_use_default_certificates(conn->ssl_context);
558 /* Are there custom CAs to load? */
559 if (conn->session->ssl_authorities)
561 SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
562 conn->session->pool));
569 *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
578 /* svn_ra_serf__conn_setup is a callback for serf. This function
579 creates a read bucket and will wrap the write bucket if SSL
582 svn_ra_serf__conn_setup(apr_socket_t *sock,
583 serf_bucket_t **read_bkt,
584 serf_bucket_t **write_bkt,
588 svn_ra_serf__connection_t *conn = baton;
589 svn_ra_serf__session_t *session = conn->session;
592 err = svn_error_trace(conn_setup(sock,
597 return save_error(session, err);
601 /* Our default serf response acceptor. */
602 static serf_bucket_t *
603 accept_response(serf_request_t *request,
604 serf_bucket_t *stream,
605 void *acceptor_baton,
609 serf_bucket_alloc_t *bkt_alloc;
611 bkt_alloc = serf_request_get_alloc(request);
612 c = serf_bucket_barrier_create(stream, bkt_alloc);
614 return serf_bucket_response_create(c, bkt_alloc);
618 /* Custom response acceptor for HEAD requests. */
619 static serf_bucket_t *
620 accept_head(serf_request_t *request,
621 serf_bucket_t *stream,
622 void *acceptor_baton,
625 serf_bucket_t *response;
627 response = accept_response(request, stream, acceptor_baton, pool);
629 /* We know we shouldn't get a response body. */
630 serf_bucket_response_set_head(response);
636 connection_closed(svn_ra_serf__connection_t *conn,
642 return svn_error_wrap_apr(why, NULL);
645 if (conn->session->using_ssl)
646 conn->ssl_context = NULL;
652 svn_ra_serf__conn_closed(serf_connection_t *conn,
657 svn_ra_serf__connection_t *ra_conn = closed_baton;
660 err = svn_error_trace(connection_closed(ra_conn, why, pool));
662 (void) save_error(ra_conn->session, err);
666 /* Implementation of svn_ra_serf__handle_client_cert */
668 handle_client_cert(void *data,
669 const char **cert_path,
672 svn_ra_serf__connection_t *conn = data;
673 svn_ra_serf__session_t *session = conn->session;
679 realm = construct_realm(session, session->pool);
681 if (!conn->ssl_client_auth_state)
683 SVN_ERR(svn_auth_first_credentials(&creds,
684 &conn->ssl_client_auth_state,
685 SVN_AUTH_CRED_SSL_CLIENT_CERT,
687 session->wc_callbacks->auth_baton,
692 SVN_ERR(svn_auth_next_credentials(&creds,
693 conn->ssl_client_auth_state,
699 svn_auth_cred_ssl_client_cert_t *client_creds;
700 client_creds = creds;
701 *cert_path = client_creds->cert_file;
707 /* Implements serf_ssl_need_client_cert_t for handle_client_cert */
708 apr_status_t svn_ra_serf__handle_client_cert(void *data,
709 const char **cert_path)
711 svn_ra_serf__connection_t *conn = data;
712 svn_ra_serf__session_t *session = conn->session;
715 err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
717 return save_error(session, err);
720 /* Implementation for svn_ra_serf__handle_client_cert_pw */
722 handle_client_cert_pw(void *data,
723 const char *cert_path,
724 const char **password,
727 svn_ra_serf__connection_t *conn = data;
728 svn_ra_serf__session_t *session = conn->session;
733 if (!conn->ssl_client_pw_auth_state)
735 SVN_ERR(svn_auth_first_credentials(&creds,
736 &conn->ssl_client_pw_auth_state,
737 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
739 session->wc_callbacks->auth_baton,
744 SVN_ERR(svn_auth_next_credentials(&creds,
745 conn->ssl_client_pw_auth_state,
751 svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
753 *password = pw_creds->password;
759 /* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
760 apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
761 const char *cert_path,
762 const char **password)
764 svn_ra_serf__connection_t *conn = data;
765 svn_ra_serf__session_t *session = conn->session;
768 err = svn_error_trace(handle_client_cert_pw(data,
773 return save_error(session, err);
778 * Given a REQUEST on connection CONN, construct a request bucket for it,
779 * returning the bucket in *REQ_BKT.
781 * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
782 * corresponds to the new request.
784 * The request will be METHOD at URL.
786 * If BODY_BKT is not-NULL, it will be sent as the request body.
788 * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
790 * REQUEST_POOL should live for the duration of the request. Serf will
791 * construct this and provide it to the request_setup callback, so we
792 * should just use that one.
795 setup_serf_req(serf_request_t *request,
796 serf_bucket_t **req_bkt,
797 serf_bucket_t **hdrs_bkt,
798 svn_ra_serf__session_t *session,
799 const char *method, const char *url,
800 serf_bucket_t *body_bkt, const char *content_type,
801 const char *accept_encoding,
802 apr_pool_t *request_pool,
803 apr_pool_t *scratch_pool)
805 serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
808 svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
810 if (set_CL && body_bkt != NULL)
812 /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
813 it speaks HTTP/1.1 (and thus, chunked requests), or because the
814 server actually responded as only supporting HTTP/1.0.
816 We'll take the existing body_bkt, spool it into a spillbuf, and
817 then wrap a bucket around that spillbuf. The spillbuf will give
818 us the Content-Length value. */
819 SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
822 /* Destroy original bucket since it content is already copied
824 serf_bucket_destroy(body_bkt);
826 body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
831 /* Create a request bucket. Note that this sucker is kind enough to
832 add a "Host" header for us. */
833 *req_bkt = serf_request_bucket_request_create(request, method, url,
834 body_bkt, allocator);
836 /* Set the Content-Length value. This will also trigger an HTTP/1.0
837 request (rather than the default chunked request). */
840 if (body_bkt == NULL)
841 serf_bucket_request_set_CL(*req_bkt, 0);
843 serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
846 *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
848 /* We use serf_bucket_headers_setn() because the USERAGENT has a
849 lifetime longer than this bucket. Thus, there is no need to copy
850 the header values. */
851 serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
855 serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
860 serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
865 serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
868 /* These headers need to be sent with every request; see issue #3255
869 ("mod_dav_svn does not pass client capabilities to start-commit
871 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
872 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
873 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
879 svn_ra_serf__context_run_wait(svn_boolean_t *done,
880 svn_ra_serf__session_t *sess,
881 apr_pool_t *scratch_pool)
883 apr_pool_t *iterpool;
884 apr_interval_time_t waittime_left = sess->timeout;
886 assert(sess->pending_error == SVN_NO_ERROR);
888 iterpool = svn_pool_create(scratch_pool);
895 svn_pool_clear(iterpool);
897 if (sess->cancel_func)
898 SVN_ERR((*sess->cancel_func)(sess->cancel_baton));
900 status = serf_context_run(sess->context,
901 SVN_RA_SERF__CONTEXT_RUN_DURATION,
904 err = sess->pending_error;
905 sess->pending_error = SVN_NO_ERROR;
907 /* If the context duration timeout is up, we'll subtract that
908 duration from the total time alloted for such things. If
909 there's no time left, we fail with a message indicating that
910 the connection timed out. */
911 if (APR_STATUS_IS_TIMEUP(status))
917 if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
919 waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
924 svn_error_compose_create(
926 svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
927 _("Connection timed out")));
933 waittime_left = sess->timeout;
939 if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
941 /* apr can't translate subversion errors to text */
942 SVN_ERR_W(svn_error_create(status, NULL, NULL),
943 _("Error running context"));
946 return svn_ra_serf__wrap_err(status, _("Error running context"));
949 /* Debugging purposes only! */
950 for (i = 0; i < sess->num_conns; i++)
952 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
955 svn_pool_destroy(iterpool);
962 svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
963 apr_pool_t *scratch_pool)
967 /* Create a serf request based on HANDLER. */
968 svn_ra_serf__request_create(handler);
970 /* Wait until the response logic marks its DONE status. */
971 err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
974 /* A callback invocation has been canceled. In this simple case of
975 context_run_one, we can keep the ra-session operational by resetting
978 If we don't do this, the next context run will notice that the connection
979 is still in the error state and will just return SVN_ERR_CEASE_INVOCATION
980 (=the last error for the connection) again */
981 if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
983 apr_status_t status = serf_connection_reset(handler->conn->conn);
986 err = svn_error_compose_create(err,
987 svn_ra_serf__wrap_err(status, NULL));
990 if (handler->server_error)
992 err = svn_error_compose_create(err, handler->server_error->error);
993 handler->server_error = NULL;
996 return svn_error_trace(err);
1001 * Expat callback invoked on a start element tag for an error response.
1003 static svn_error_t *
1004 start_error(svn_ra_serf__xml_parser_t *parser,
1005 svn_ra_serf__dav_props_t name,
1007 apr_pool_t *scratch_pool)
1009 svn_ra_serf__server_error_t *ctx = parser->user_data;
1011 if (!ctx->in_error &&
1012 strcmp(name.namespace, "DAV:") == 0 &&
1013 strcmp(name.name, "error") == 0)
1015 ctx->in_error = TRUE;
1017 else if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
1019 const char *err_code;
1021 err_code = svn_xml_get_attr_value("errcode", attrs);
1026 SVN_ERR(svn_cstring_atoi64(&val, err_code));
1027 ctx->error->apr_err = (apr_status_t)val;
1030 /* If there's no error code provided, or if the provided code is
1031 0 (which can happen sometimes depending on how the error is
1032 constructed on the server-side), just pick a generic error
1033 code to run with. */
1034 if (! ctx->error->apr_err)
1036 ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
1039 /* Start collecting cdata. */
1040 svn_stringbuf_setempty(ctx->cdata);
1041 ctx->collect_cdata = TRUE;
1044 return SVN_NO_ERROR;
1048 * Expat callback invoked on an end element tag for a PROPFIND response.
1050 static svn_error_t *
1051 end_error(svn_ra_serf__xml_parser_t *parser,
1052 svn_ra_serf__dav_props_t name,
1053 apr_pool_t *scratch_pool)
1055 svn_ra_serf__server_error_t *ctx = parser->user_data;
1057 if (ctx->in_error &&
1058 strcmp(name.namespace, "DAV:") == 0 &&
1059 strcmp(name.name, "error") == 0)
1061 ctx->in_error = FALSE;
1063 if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
1065 /* On the server dav_error_response_tag() will add a leading
1066 and trailing newline if DEBUG_CR is defined in mod_dav.h,
1067 so remove any such characters here. */
1068 svn_stringbuf_strip_whitespace(ctx->cdata);
1070 ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
1072 ctx->collect_cdata = FALSE;
1075 return SVN_NO_ERROR;
1079 * Expat callback invoked on CDATA elements in an error response.
1081 * This callback can be called multiple times.
1083 static svn_error_t *
1084 cdata_error(svn_ra_serf__xml_parser_t *parser,
1087 apr_pool_t *scratch_pool)
1089 svn_ra_serf__server_error_t *ctx = parser->user_data;
1091 if (ctx->collect_cdata)
1093 svn_stringbuf_appendbytes(ctx->cdata, data, len);
1096 return SVN_NO_ERROR;
1101 drain_bucket(serf_bucket_t *bucket)
1103 /* Read whatever is in the bucket, and just drop it. */
1106 apr_status_t status;
1110 status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
1117 static svn_ra_serf__server_error_t *
1118 begin_error_parsing(svn_ra_serf__xml_start_element_t start,
1119 svn_ra_serf__xml_end_element_t end,
1120 svn_ra_serf__xml_cdata_chunk_handler_t cdata,
1121 apr_pool_t *result_pool)
1123 svn_ra_serf__server_error_t *server_err;
1125 server_err = apr_pcalloc(result_pool, sizeof(*server_err));
1126 server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
1127 server_err->contains_precondition_error = FALSE;
1128 server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool);
1129 server_err->collect_cdata = FALSE;
1130 server_err->parser.pool = server_err->error->pool;
1131 server_err->parser.user_data = server_err;
1132 server_err->parser.start = start;
1133 server_err->parser.end = end;
1134 server_err->parser.cdata = cdata;
1135 server_err->parser.ignore_errors = TRUE;
1140 /* Implements svn_ra_serf__response_handler_t */
1142 svn_ra_serf__handle_discard_body(serf_request_t *request,
1143 serf_bucket_t *response,
1147 apr_status_t status;
1149 status = drain_bucket(response);
1151 return svn_ra_serf__wrap_err(status, NULL);
1153 return SVN_NO_ERROR;
1157 svn_ra_serf__response_discard_handler(serf_request_t *request,
1158 serf_bucket_t *response,
1162 return drain_bucket(response);
1166 /* Return the value of the RESPONSE's Location header if any, or NULL
1169 response_get_location(serf_bucket_t *response,
1170 const char *base_url,
1171 apr_pool_t *result_pool,
1172 apr_pool_t *scratch_pool)
1174 serf_bucket_t *headers;
1175 const char *location;
1177 headers = serf_bucket_response_get_headers(response);
1178 location = serf_bucket_headers_get(headers, "Location");
1179 if (location == NULL)
1182 /* The RFCs say we should have received a full url in LOCATION, but
1183 older apache versions and many custom web handlers just return a
1184 relative path here...
1186 And we can't trust anything because it is network data.
1188 if (*location == '/')
1191 apr_status_t status;
1193 status = apr_uri_parse(scratch_pool, base_url, &uri);
1195 if (status != APR_SUCCESS)
1198 /* Replace the path path with what we got */
1199 uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
1201 /* And make APR produce a proper full url for us */
1202 location = apr_uri_unparse(scratch_pool, &uri, 0);
1204 /* Fall through to ensure our canonicalization rules */
1206 else if (!svn_path_is_url(location))
1208 return NULL; /* Any other formats we should support? */
1211 return svn_uri_canonicalize(location, result_pool);
1215 /* Implements svn_ra_serf__response_handler_t */
1217 svn_ra_serf__expect_empty_body(serf_request_t *request,
1218 serf_bucket_t *response,
1220 apr_pool_t *scratch_pool)
1222 svn_ra_serf__handler_t *handler = baton;
1223 serf_bucket_t *hdrs;
1226 /* This function is just like handle_multistatus_only() except for the
1227 XML parsing callbacks. We want to look for the human-readable element. */
1229 /* We should see this just once, in order to initialize SERVER_ERROR.
1230 At that point, the core error processing will take over. If we choose
1231 not to parse an error, then we'll never return here (because we
1232 change the response handler). */
1233 SVN_ERR_ASSERT(handler->server_error == NULL);
1235 hdrs = serf_bucket_response_get_headers(response);
1236 val = serf_bucket_headers_get(hdrs, "Content-Type");
1237 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1239 svn_ra_serf__server_error_t *server_err;
1241 server_err = begin_error_parsing(start_error, end_error, cdata_error,
1242 handler->handler_pool);
1244 /* Get the parser to set our DONE flag. */
1245 server_err->parser.done = &handler->done;
1247 handler->server_error = server_err;
1251 /* The body was not text/xml, so we don't know what to do with it.
1252 Toss anything that arrives. */
1253 handler->discard_body = TRUE;
1256 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1257 to call the response handler again. That will start up the XML parsing,
1258 or it will be dropped on the floor (per the decision above). */
1259 return SVN_NO_ERROR;
1263 /* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric
1264 status code into *STATUS_CODE_OUT. Ignores leading whitespace. */
1265 static svn_error_t *
1266 parse_dav_status(int *status_code_out, svn_stringbuf_t *buf,
1267 apr_pool_t *scratch_pool)
1272 svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool);
1274 svn_stringbuf_strip_whitespace(temp_buf);
1275 token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status);
1277 token = apr_strtok(NULL, " \t\r\n", &tok_status);
1279 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1280 _("Malformed DAV:status CDATA '%s'"),
1282 err = svn_cstring_atoi(status_code_out, token);
1284 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
1285 _("Malformed DAV:status CDATA '%s'"),
1288 return SVN_NO_ERROR;
1292 * Expat callback invoked on a start element tag for a 207 response.
1294 static svn_error_t *
1295 start_207(svn_ra_serf__xml_parser_t *parser,
1296 svn_ra_serf__dav_props_t name,
1298 apr_pool_t *scratch_pool)
1300 svn_ra_serf__server_error_t *ctx = parser->user_data;
1302 if (!ctx->in_error &&
1303 strcmp(name.namespace, "DAV:") == 0 &&
1304 strcmp(name.name, "multistatus") == 0)
1306 ctx->in_error = TRUE;
1308 else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
1310 /* Start collecting cdata. */
1311 svn_stringbuf_setempty(ctx->cdata);
1312 ctx->collect_cdata = TRUE;
1314 else if (ctx->in_error &&
1315 strcmp(name.namespace, "DAV:") == 0 &&
1316 strcmp(name.name, "status") == 0)
1318 /* Start collecting cdata. */
1319 svn_stringbuf_setempty(ctx->cdata);
1320 ctx->collect_cdata = TRUE;
1323 return SVN_NO_ERROR;
1327 * Expat callback invoked on an end element tag for a 207 response.
1329 static svn_error_t *
1330 end_207(svn_ra_serf__xml_parser_t *parser,
1331 svn_ra_serf__dav_props_t name,
1332 apr_pool_t *scratch_pool)
1334 svn_ra_serf__server_error_t *ctx = parser->user_data;
1336 if (ctx->in_error &&
1337 strcmp(name.namespace, "DAV:") == 0 &&
1338 strcmp(name.name, "multistatus") == 0)
1340 ctx->in_error = FALSE;
1342 if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
1344 /* Remove leading newline added by DEBUG_CR on server */
1345 svn_stringbuf_strip_whitespace(ctx->cdata);
1347 ctx->collect_cdata = FALSE;
1348 ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
1350 if (ctx->contains_precondition_error)
1351 ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
1353 ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
1355 else if (ctx->in_error &&
1356 strcmp(name.namespace, "DAV:") == 0 &&
1357 strcmp(name.name, "status") == 0)
1361 ctx->collect_cdata = FALSE;
1363 SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool));
1364 if (status_code == 412)
1365 ctx->contains_precondition_error = TRUE;
1368 return SVN_NO_ERROR;
1372 * Expat callback invoked on CDATA elements in a 207 response.
1374 * This callback can be called multiple times.
1376 static svn_error_t *
1377 cdata_207(svn_ra_serf__xml_parser_t *parser,
1380 apr_pool_t *scratch_pool)
1382 svn_ra_serf__server_error_t *ctx = parser->user_data;
1384 if (ctx->collect_cdata)
1386 svn_stringbuf_appendbytes(ctx->cdata, data, len);
1389 return SVN_NO_ERROR;
1392 /* Implements svn_ra_serf__response_handler_t */
1394 svn_ra_serf__handle_multistatus_only(serf_request_t *request,
1395 serf_bucket_t *response,
1397 apr_pool_t *scratch_pool)
1399 svn_ra_serf__handler_t *handler = baton;
1401 /* This function is just like expect_empty_body() except for the
1402 XML parsing callbacks. We are looking for very limited pieces of
1403 the multistatus response. */
1405 /* We should see this just once, in order to initialize SERVER_ERROR.
1406 At that point, the core error processing will take over. If we choose
1407 not to parse an error, then we'll never return here (because we
1408 change the response handler). */
1409 SVN_ERR_ASSERT(handler->server_error == NULL);
1412 serf_bucket_t *hdrs;
1415 hdrs = serf_bucket_response_get_headers(response);
1416 val = serf_bucket_headers_get(hdrs, "Content-Type");
1417 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1419 svn_ra_serf__server_error_t *server_err;
1421 server_err = begin_error_parsing(start_207, end_207, cdata_207,
1422 handler->handler_pool);
1424 /* Get the parser to set our DONE flag. */
1425 server_err->parser.done = &handler->done;
1427 handler->server_error = server_err;
1431 /* The body was not text/xml, so we don't know what to do with it.
1432 Toss anything that arrives. */
1433 handler->discard_body = TRUE;
1437 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1438 to call the response handler again. That will start up the XML parsing,
1439 or it will be dropped on the floor (per the decision above). */
1440 return SVN_NO_ERROR;
1444 /* Conforms to Expat's XML_StartElementHandler */
1446 start_xml(void *userData, const char *raw_name, const char **attrs)
1448 svn_ra_serf__xml_parser_t *parser = userData;
1449 svn_ra_serf__dav_props_t name;
1450 apr_pool_t *scratch_pool;
1457 svn_ra_serf__xml_push_state(parser, 0);
1459 /* ### get a real scratch_pool */
1460 scratch_pool = parser->state->pool;
1462 svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool);
1464 svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
1466 err = parser->start(parser, name, attrs, scratch_pool);
1467 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1468 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1470 parser->error = err;
1474 /* Conforms to Expat's XML_EndElementHandler */
1476 end_xml(void *userData, const char *raw_name)
1478 svn_ra_serf__xml_parser_t *parser = userData;
1479 svn_ra_serf__dav_props_t name;
1481 apr_pool_t *scratch_pool;
1486 /* ### get a real scratch_pool */
1487 scratch_pool = parser->state->pool;
1489 svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
1491 err = parser->end(parser, name, scratch_pool);
1492 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1493 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1495 parser->error = err;
1499 /* Conforms to Expat's XML_CharacterDataHandler */
1501 cdata_xml(void *userData, const char *data, int len)
1503 svn_ra_serf__xml_parser_t *parser = userData;
1505 apr_pool_t *scratch_pool;
1511 svn_ra_serf__xml_push_state(parser, 0);
1513 /* ### get a real scratch_pool */
1514 scratch_pool = parser->state->pool;
1516 err = parser->cdata(parser, data, len, scratch_pool);
1517 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1518 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1520 parser->error = err;
1523 /* Flip the requisite bits in CTX to indicate that processing of the
1524 response is complete, adding the current "done item" to the list of
1527 add_done_item(svn_ra_serf__xml_parser_t *ctx)
1529 /* Make sure we don't add to DONE_LIST twice. */
1535 ctx->done_item->data = ctx->user_data;
1536 ctx->done_item->next = *ctx->done_list;
1537 *ctx->done_list = ctx->done_item;
1543 static svn_error_t *
1544 write_to_pending(svn_ra_serf__xml_parser_t *ctx,
1547 apr_pool_t *scratch_pool)
1549 if (ctx->pending == NULL)
1551 ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
1552 ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE,
1557 /* Copy the data into one or more chunks in the spill buffer. */
1558 return svn_error_trace(svn_spillbuf__write(ctx->pending->buf,
1564 static svn_error_t *
1565 inject_to_parser(svn_ra_serf__xml_parser_t *ctx,
1568 const serf_status_line *sl)
1572 xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0);
1574 if (! ctx->ignore_errors)
1576 SVN_ERR(ctx->error);
1578 if (xml_status != XML_STATUS_OK)
1581 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1582 _("XML parsing failed"));
1584 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1585 _("XML parsing failed: (%d %s)"),
1586 sl->code, sl->reason);
1590 return SVN_NO_ERROR;
1593 /* Apr pool cleanup handler to release an XML_Parser in success and error
1596 xml_parser_cleanup(void *baton)
1598 XML_Parser *xmlp = baton;
1602 (void) XML_ParserFree(*xmlp);
1609 /* Limit the amount of pending content to parse at once to < 100KB per
1610 iteration. This number is chosen somewhat arbitrarely. Making it lower
1611 will have a drastical negative impact on performance, whereas increasing it
1612 increases the risk for connection timeouts.
1614 #define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5
1617 svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
1618 svn_boolean_t *network_eof,
1619 apr_pool_t *scratch_pool)
1621 svn_boolean_t pending_empty = FALSE;
1622 apr_size_t cur_read = 0;
1624 /* Fast path exit: already paused, nothing to do, or already done. */
1625 if (parser->paused || parser->pending == NULL || *parser->done)
1627 *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
1628 return SVN_NO_ERROR;
1631 /* Parsing the pending conten in the spillbuf will result in many disc i/o
1632 operations. This can be so slow that we don't run the network event
1633 processing loop often enough, resulting in timed out connections.
1635 So we limit the amounts of bytes parsed per iteration.
1637 while (cur_read < PENDING_TO_PARSE)
1642 /* Get a block of content, stopping the loop when we run out. */
1643 SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf,
1647 /* Inject the content into the XML parser. */
1648 SVN_ERR(inject_to_parser(parser, data, len, NULL));
1650 /* If the XML parsing callbacks paused us, then we're done for now. */
1658 /* The buffer is empty. */
1659 pending_empty = TRUE;
1664 /* If the PENDING structures are empty *and* we consumed all content from
1665 the network, then we're completely done with the parsing. */
1666 if (pending_empty &&
1667 parser->pending->network_eof)
1670 SVN_ERR_ASSERT(parser->xmlp != NULL);
1672 /* Tell the parser that no more content will be parsed. */
1673 xml_status = XML_Parse(parser->xmlp, NULL, 0, 1);
1675 apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup);
1676 parser->xmlp = NULL;
1678 if (! parser->ignore_errors)
1680 SVN_ERR(parser->error);
1682 if (xml_status != XML_STATUS_OK)
1684 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1685 _("XML parsing failed"));
1689 add_done_item(parser);
1692 *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
1694 return SVN_NO_ERROR;
1696 #undef PENDING_TO_PARSE
1699 /* ### this is still broken conceptually. just shifting incrementally... */
1700 static svn_error_t *
1701 handle_server_error(serf_request_t *request,
1702 serf_bucket_t *response,
1703 apr_pool_t *scratch_pool)
1705 svn_ra_serf__server_error_t server_err = { 0 };
1706 serf_bucket_t *hdrs;
1710 hdrs = serf_bucket_response_get_headers(response);
1711 val = serf_bucket_headers_get(hdrs, "Content-Type");
1712 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1714 /* ### we should figure out how to reuse begin_error_parsing */
1716 server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL);
1717 server_err.contains_precondition_error = FALSE;
1718 server_err.cdata = svn_stringbuf_create_empty(scratch_pool);
1719 server_err.collect_cdata = FALSE;
1720 server_err.parser.pool = server_err.error->pool;
1721 server_err.parser.user_data = &server_err;
1722 server_err.parser.start = start_error;
1723 server_err.parser.end = end_error;
1724 server_err.parser.cdata = cdata_error;
1725 server_err.parser.done = &server_err.done;
1726 server_err.parser.ignore_errors = TRUE;
1728 /* We don't care about any errors except for SERVER_ERR.ERROR */
1729 svn_error_clear(svn_ra_serf__handle_xml_parser(request,
1734 /* ### checking DONE is silly. the above only parses whatever has
1735 ### been received at the network interface. totally wrong. but
1736 ### it is what we have for now (maintaining historical code),
1737 ### until we fully migrate. */
1738 if (server_err.done && server_err.error->apr_err == APR_SUCCESS)
1740 svn_error_clear(server_err.error);
1741 server_err.error = SVN_NO_ERROR;
1744 return svn_error_trace(server_err.error);
1747 /* The only error that we will return is from the XML response body.
1748 Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to
1750 err = drain_bucket(response);
1751 if (err && !SERF_BUCKET_READ_ERROR(err))
1752 return svn_ra_serf__wrap_err(err, NULL);
1754 return SVN_NO_ERROR;
1758 /* Implements svn_ra_serf__response_handler_t */
1760 svn_ra_serf__handle_xml_parser(serf_request_t *request,
1761 serf_bucket_t *response,
1765 serf_status_line sl;
1766 apr_status_t status;
1767 svn_ra_serf__xml_parser_t *ctx = baton;
1770 /* ### get the HANDLER rather than fetching this. */
1771 status = serf_bucket_response_status(response, &sl);
1772 if (SERF_BUCKET_READ_ERROR(status))
1774 return svn_ra_serf__wrap_err(status, NULL);
1777 /* Woo-hoo. Nothing here to see. */
1778 if (sl.code == 404 && !ctx->ignore_errors)
1780 err = handle_server_error(request, response, pool);
1782 if (err && APR_STATUS_IS_EOF(err->apr_err))
1785 return svn_error_trace(err);
1790 ctx->xmlp = XML_ParserCreate(NULL);
1791 apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup,
1792 apr_pool_cleanup_null);
1793 XML_SetUserData(ctx->xmlp, ctx);
1794 XML_SetElementHandler(ctx->xmlp, start_xml, end_xml);
1797 XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml);
1806 status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
1807 if (SERF_BUCKET_READ_ERROR(status))
1809 return svn_ra_serf__wrap_err(status, NULL);
1812 /* Note: once the callbacks invoked by inject_to_parser() sets the
1813 PAUSED flag, then it will not be cleared. write_to_pending() will
1814 only save the content. Logic outside of serf_context_run() will
1815 clear that flag, as appropriate, along with processing the
1816 content that we have placed into the PENDING buffer.
1818 We want to save arriving content into the PENDING structures if
1819 the parser has been paused, or we already have data in there (so
1820 the arriving data is appended, rather than injected out of order) */
1821 if (ctx->paused || HAS_PENDING_DATA(ctx->pending))
1823 err = write_to_pending(ctx, data, len, pool);
1827 err = inject_to_parser(ctx, data, len, &sl);
1830 /* Should have no errors if IGNORE_ERRORS is set. */
1831 SVN_ERR_ASSERT(!ctx->ignore_errors);
1836 SVN_ERR_ASSERT(ctx->xmlp != NULL);
1838 apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
1840 return svn_error_trace(err);
1843 if (APR_STATUS_IS_EAGAIN(status))
1845 return svn_ra_serf__wrap_err(status, NULL);
1848 if (APR_STATUS_IS_EOF(status))
1850 if (ctx->pending != NULL)
1851 ctx->pending->network_eof = TRUE;
1853 /* We just hit the end of the network content. If we have nothing
1854 in the PENDING structures, then we're completely done. */
1855 if (!HAS_PENDING_DATA(ctx->pending))
1858 SVN_ERR_ASSERT(ctx->xmlp != NULL);
1860 xml_status = XML_Parse(ctx->xmlp, NULL, 0, 1);
1862 apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
1864 if (! ctx->ignore_errors)
1866 SVN_ERR(ctx->error);
1868 if (xml_status != XML_STATUS_OK)
1870 return svn_error_create(
1871 SVN_ERR_XML_MALFORMED, NULL,
1872 _("The XML response contains invalid XML"));
1879 return svn_ra_serf__wrap_err(status, NULL);
1889 svn_ra_serf__credentials_callback(char **username, char **password,
1890 serf_request_t *request, void *baton,
1891 int code, const char *authn_type,
1895 svn_ra_serf__handler_t *handler = baton;
1896 svn_ra_serf__session_t *session = handler->session;
1898 svn_auth_cred_simple_t *simple_creds;
1903 /* Use svn_auth_first_credentials if this is the first time we ask for
1904 credentials during this session OR if the last time we asked
1905 session->auth_state wasn't set (eg. if the credentials provider was
1906 cancelled by the user). */
1907 if (!session->auth_state)
1909 err = svn_auth_first_credentials(&creds,
1910 &session->auth_state,
1911 SVN_AUTH_CRED_SIMPLE,
1913 session->wc_callbacks->auth_baton,
1918 err = svn_auth_next_credentials(&creds,
1919 session->auth_state,
1925 (void) save_error(session, err);
1926 return err->apr_err;
1929 session->auth_attempts++;
1931 if (!creds || session->auth_attempts > 4)
1933 /* No more credentials. */
1934 (void) save_error(session,
1936 SVN_ERR_AUTHN_FAILED, NULL,
1937 _("No more credentials or we tried too many "
1938 "times.\nAuthentication failed")));
1939 return SVN_ERR_AUTHN_FAILED;
1942 simple_creds = creds;
1943 *username = apr_pstrdup(pool, simple_creds->username);
1944 *password = apr_pstrdup(pool, simple_creds->password);
1948 *username = apr_pstrdup(pool, session->proxy_username);
1949 *password = apr_pstrdup(pool, session->proxy_password);
1951 session->proxy_auth_attempts++;
1953 if (!session->proxy_username || session->proxy_auth_attempts > 4)
1955 /* No more credentials. */
1956 (void) save_error(session,
1958 SVN_ERR_AUTHN_FAILED, NULL,
1959 _("Proxy authentication failed")));
1960 return SVN_ERR_AUTHN_FAILED;
1964 handler->conn->last_status_code = code;
1969 /* Wait for HTTP response status and headers, and invoke HANDLER->
1970 response_handler() to carry out operation-specific processing.
1971 Afterwards, check for connection close.
1973 SERF_STATUS allows returning errors to serf without creating a
1974 subversion error object.
1976 static svn_error_t *
1977 handle_response(serf_request_t *request,
1978 serf_bucket_t *response,
1979 svn_ra_serf__handler_t *handler,
1980 apr_status_t *serf_status,
1981 apr_pool_t *scratch_pool)
1983 apr_status_t status;
1986 /* ### need to verify whether this already gets init'd on every
1987 ### successful exit. for an error-exit, it will (properly) be
1988 ### ignored by the caller. */
1989 *serf_status = APR_SUCCESS;
1993 /* Uh-oh. Our connection died. */
1994 if (handler->response_error)
1996 /* Give a handler chance to prevent request requeue. */
1997 SVN_ERR(handler->response_error(request, response, 0,
1998 handler->response_error_baton));
2000 svn_ra_serf__request_create(handler);
2002 /* Response error callback is not configured. Requeue another request
2003 for this handler only if we didn't started to process body.
2004 Return error otherwise. */
2005 else if (!handler->reading_body)
2007 svn_ra_serf__request_create(handler);
2011 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2012 _("%s request on '%s' failed"),
2013 handler->method, handler->path);
2016 return SVN_NO_ERROR;
2019 /* If we're reading the body, then skip all this preparation. */
2020 if (handler->reading_body)
2023 /* Copy the Status-Line info into HANDLER, if we don't yet have it. */
2024 if (handler->sline.version == 0)
2026 serf_status_line sl;
2028 status = serf_bucket_response_status(response, &sl);
2029 if (status != APR_SUCCESS)
2031 /* The response line is not (yet) ready, or some other error. */
2032 *serf_status = status;
2033 return SVN_NO_ERROR; /* Handled by serf */
2036 /* If we got APR_SUCCESS, then we should have Status-Line info. */
2037 SVN_ERR_ASSERT(sl.version != 0);
2039 handler->sline = sl;
2040 handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
2042 /* HTTP/1.1? (or later) */
2043 if (sl.version != SERF_HTTP_10)
2044 handler->session->http10 = FALSE;
2047 /* Keep reading from the network until we've read all the headers. */
2048 status = serf_bucket_response_wait_for_headers(response);
2051 /* The typical "error" will be APR_EAGAIN, meaning that more input
2052 from the network is required to complete the reading of the
2054 if (!APR_STATUS_IS_EOF(status))
2056 /* Either the headers are not (yet) complete, or there really
2058 *serf_status = status;
2059 return SVN_NO_ERROR;
2062 /* wait_for_headers() will return EOF if there is no body in this
2063 response, or if we completely read the body. The latter is not
2064 true since we would have set READING_BODY to get the body read,
2065 and we would not be back to this code block.
2067 It can also return EOF if we truly hit EOF while (say) processing
2068 the headers. aka Badness. */
2070 /* Cases where a lack of a response body (via EOF) is okay:
2072 * - 204/304 response
2074 * Otherwise, if we get an EOF here, something went really wrong: either
2075 * the server closed on us early or we're reading too much. Either way,
2078 if (strcmp(handler->method, "HEAD") != 0
2079 && handler->sline.code != 204
2080 && handler->sline.code != 304)
2082 err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
2083 svn_ra_serf__wrap_err(status, NULL),
2084 _("Premature EOF seen from server"
2085 " (http status=%d)"),
2086 handler->sline.code);
2088 /* In case anything else arrives... discard it. */
2089 handler->discard_body = TRUE;
2095 /* ... and set up the header fields in HANDLER. */
2096 handler->location = response_get_location(response,
2097 handler->session->session_url_str,
2098 handler->handler_pool,
2101 /* On the last request, we failed authentication. We succeeded this time,
2102 so let's save away these credentials. */
2103 if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
2105 SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
2106 handler->session->pool));
2107 handler->session->auth_attempts = 0;
2108 handler->session->auth_state = NULL;
2110 handler->conn->last_status_code = handler->sline.code;
2112 if (handler->sline.code == 405
2113 || handler->sline.code == 408
2114 || handler->sline.code == 409
2115 || handler->sline.code >= 500)
2117 /* 405 Method Not allowed.
2119 409 Conflict: can indicate a hook error.
2120 5xx (Internal) Server error. */
2121 serf_bucket_t *hdrs;
2124 hdrs = serf_bucket_response_get_headers(response);
2125 val = serf_bucket_headers_get(hdrs, "Content-Type");
2126 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
2128 svn_ra_serf__server_error_t *server_err;
2130 server_err = begin_error_parsing(start_error, end_error, cdata_error,
2131 handler->handler_pool);
2132 /* Get the parser to set our DONE flag. */
2133 server_err->parser.done = &handler->done;
2135 handler->server_error = server_err;
2139 handler->discard_body = TRUE;
2141 if (!handler->session->pending_error)
2143 apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
2145 /* 405 == Method Not Allowed (Occurs when trying to lock a working
2146 copy path which no longer exists at HEAD in the repository. */
2147 if (handler->sline.code == 405
2148 && strcmp(handler->method, "LOCK") == 0)
2149 apr_err = SVN_ERR_FS_OUT_OF_DATE;
2151 handler->session->pending_error =
2152 svn_error_createf(apr_err, NULL,
2153 _("%s request on '%s' failed: %d %s"),
2154 handler->method, handler->path,
2155 handler->sline.code, handler->sline.reason);
2160 /* Stop processing the above, on every packet arrival. */
2161 handler->reading_body = TRUE;
2165 /* We've been instructed to ignore the body. Drain whatever is present. */
2166 if (handler->discard_body)
2168 *serf_status = drain_bucket(response);
2170 /* If the handler hasn't set done (which it shouldn't have) and
2171 we now have the EOF, go ahead and set it so that we can stop
2174 if (!handler->done && APR_STATUS_IS_EOF(*serf_status))
2175 handler->done = TRUE;
2177 return SVN_NO_ERROR;
2180 /* If we are supposed to parse the body as a server_error, then do
2182 if (handler->server_error != NULL)
2184 err = svn_ra_serf__handle_xml_parser(request, response,
2185 &handler->server_error->parser,
2188 /* If we do not receive an error or it is a non-transient error, return
2191 APR_EOF will be returned when parsing is complete.
2193 APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through
2194 parsing and the network has no more data right now. If we receive that,
2195 clear the error and return - allowing serf to wait for more data.
2197 if (!err || SERF_BUCKET_READ_ERROR(err->apr_err))
2198 return svn_error_trace(err);
2200 if (!APR_STATUS_IS_EOF(err->apr_err))
2202 *serf_status = err->apr_err;
2203 svn_error_clear(err);
2204 return SVN_NO_ERROR;
2207 /* Clear the EOF. We don't need it. */
2208 svn_error_clear(err);
2210 /* If the parsing is done, and we did not extract an error, then
2211 simply toss everything, and anything else that might arrive.
2212 The higher-level code will need to investigate HANDLER->SLINE,
2213 as we have no further information for them. */
2215 && handler->server_error->error->apr_err == APR_SUCCESS)
2217 svn_error_clear(handler->server_error->error);
2219 /* Stop parsing for a server error. */
2220 handler->server_error = NULL;
2222 /* If anything arrives after this, then just discard it. */
2223 handler->discard_body = TRUE;
2226 *serf_status = APR_EOF;
2227 return SVN_NO_ERROR;
2230 /* Pass the body along to the registered response handler. */
2231 err = handler->response_handler(request, response,
2232 handler->response_baton,
2236 && (!SERF_BUCKET_READ_ERROR(err->apr_err)
2237 || APR_STATUS_IS_ECONNRESET(err->apr_err)
2238 || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
2240 /* These errors are special cased in serf
2241 ### We hope no handler returns these by accident. */
2242 *serf_status = err->apr_err;
2243 svn_error_clear(err);
2244 return SVN_NO_ERROR;
2247 return svn_error_trace(err);
2251 /* Implements serf_response_handler_t for handle_response. Storing
2252 errors in handler->session->pending_error if appropriate. */
2254 handle_response_cb(serf_request_t *request,
2255 serf_bucket_t *response,
2257 apr_pool_t *scratch_pool)
2259 svn_ra_serf__handler_t *handler = baton;
2261 apr_status_t inner_status;
2262 apr_status_t outer_status;
2264 err = svn_error_trace(handle_response(request, response,
2265 handler, &inner_status,
2268 /* Select the right status value to return. */
2269 outer_status = save_error(handler->session, err);
2271 outer_status = inner_status;
2273 /* Make sure the DONE flag is set properly. */
2274 if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
2275 handler->done = TRUE;
2277 return outer_status;
2280 /* Perform basic request setup, with special handling for HEAD requests,
2281 and finer-grained callbacks invoked (if non-NULL) to produce the request
2282 headers and body. */
2283 static svn_error_t *
2284 setup_request(serf_request_t *request,
2285 svn_ra_serf__handler_t *handler,
2286 serf_bucket_t **req_bkt,
2287 apr_pool_t *request_pool,
2288 apr_pool_t *scratch_pool)
2290 serf_bucket_t *body_bkt;
2291 serf_bucket_t *headers_bkt;
2292 const char *accept_encoding;
2294 if (handler->body_delegate)
2296 serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
2298 /* ### should pass the scratch_pool */
2299 SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
2300 bkt_alloc, request_pool));
2307 if (handler->custom_accept_encoding)
2309 accept_encoding = NULL;
2311 else if (handler->session->using_compression)
2313 /* Accept gzip compression if enabled. */
2314 accept_encoding = "gzip";
2318 accept_encoding = NULL;
2321 SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
2322 handler->session, handler->method, handler->path,
2323 body_bkt, handler->body_type, accept_encoding,
2324 request_pool, scratch_pool));
2326 if (handler->header_delegate)
2328 /* ### should pass the scratch_pool */
2329 SVN_ERR(handler->header_delegate(headers_bkt,
2330 handler->header_delegate_baton,
2337 /* Implements the serf_request_setup_t interface (which sets up both a
2338 request and its response handler callback). Handles errors for
2341 setup_request_cb(serf_request_t *request,
2343 serf_bucket_t **req_bkt,
2344 serf_response_acceptor_t *acceptor,
2345 void **acceptor_baton,
2346 serf_response_handler_t *s_handler,
2347 void **s_handler_baton,
2350 svn_ra_serf__handler_t *handler = setup_baton;
2353 /* ### construct a scratch_pool? serf gives us a pool that will live for
2354 ### the duration of the request. */
2355 apr_pool_t *scratch_pool = pool;
2357 if (strcmp(handler->method, "HEAD") == 0)
2358 *acceptor = accept_head;
2360 *acceptor = accept_response;
2361 *acceptor_baton = handler->session;
2363 *s_handler = handle_response_cb;
2364 *s_handler_baton = handler;
2366 err = svn_error_trace(setup_request(request, handler, req_bkt,
2367 pool /* request_pool */, scratch_pool));
2369 return save_error(handler->session, err);
2373 svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
2375 SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL);
2377 /* In case HANDLER is re-queued, reset the various transient fields.
2379 ### prior to recent changes, HANDLER was constant. maybe we should
2380 ### break out these processing fields, apart from the request
2382 handler->done = FALSE;
2383 handler->server_error = NULL;
2384 handler->sline.version = 0;
2385 handler->location = NULL;
2386 handler->reading_body = FALSE;
2387 handler->discard_body = FALSE;
2389 /* ### do we ever alter the >response_handler? */
2391 /* ### do we need to hold onto the returned request object, or just
2392 ### not worry about it (the serf ctx will manage it). */
2393 (void) serf_connection_request_create(handler->conn->conn,
2394 setup_request_cb, handler);
2399 svn_ra_serf__discover_vcc(const char **vcc_url,
2400 svn_ra_serf__session_t *session,
2401 svn_ra_serf__connection_t *conn,
2405 const char *relative_path;
2408 /* If we've already got the information our caller seeks, just return it. */
2409 if (session->vcc_url && session->repos_root_str)
2411 *vcc_url = session->vcc_url;
2412 return SVN_NO_ERROR;
2415 /* If no connection is provided, use the default one. */
2418 conn = session->conns[0];
2421 path = session->session_url.path;
2430 err = svn_ra_serf__fetch_node_props(&props, conn,
2431 path, SVN_INVALID_REVNUM,
2432 base_props, pool, pool);
2435 apr_hash_t *ns_props;
2437 ns_props = apr_hash_get(props, "DAV:", 4);
2438 *vcc_url = svn_prop_get_value(ns_props,
2439 "version-controlled-configuration");
2441 ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
2442 relative_path = svn_prop_get_value(ns_props,
2443 "baseline-relative-path");
2444 uuid = svn_prop_get_value(ns_props, "repository-uuid");
2449 if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
2450 (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
2452 return svn_error_trace(err); /* found a _real_ error */
2456 /* This happens when the file is missing in HEAD. */
2457 svn_error_clear(err);
2459 /* Okay, strip off a component from PATH. */
2460 path = svn_urlpath__dirname(path, pool);
2462 /* An error occurred on conns. serf 0.4.0 remembers that
2463 the connection had a problem. We need to reset it, in
2464 order to use it again. */
2465 serf_connection_reset(conn->conn);
2469 while ((path[0] != '\0')
2470 && (! (path[0] == '/' && path[1] == '\0')));
2474 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
2475 _("The PROPFIND response did not include the "
2476 "requested version-controlled-configuration "
2480 /* Store our VCC in our cache. */
2481 if (!session->vcc_url)
2483 session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
2486 /* Update our cached repository root URL. */
2487 if (!session->repos_root_str)
2489 svn_stringbuf_t *url_buf;
2491 url_buf = svn_stringbuf_create(path, pool);
2493 svn_path_remove_components(url_buf,
2494 svn_path_component_count(relative_path));
2496 /* Now recreate the root_url. */
2497 session->repos_root = session->session_url;
2498 session->repos_root.path =
2499 (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
2500 session->repos_root_str =
2501 svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
2502 &session->repos_root, 0),
2506 /* Store the repository UUID in the cache. */
2509 session->uuid = apr_pstrdup(session->pool, uuid);
2512 return SVN_NO_ERROR;
2516 svn_ra_serf__get_relative_path(const char **rel_path,
2517 const char *orig_path,
2518 svn_ra_serf__session_t *session,
2519 svn_ra_serf__connection_t *conn,
2522 const char *decoded_root, *decoded_orig;
2524 if (! session->repos_root.path)
2526 const char *vcc_url;
2528 /* This should only happen if we haven't detected HTTP v2
2529 support from the server. */
2530 assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
2532 /* We don't actually care about the VCC_URL, but this API
2533 promises to populate the session's root-url cache, and that's
2534 what we really want. */
2535 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
2536 conn ? conn : session->conns[0],
2540 decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
2541 decoded_orig = svn_path_uri_decode(orig_path, pool);
2542 *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
2543 SVN_ERR_ASSERT(*rel_path != NULL);
2544 return SVN_NO_ERROR;
2548 svn_ra_serf__report_resource(const char **report_target,
2549 svn_ra_serf__session_t *session,
2550 svn_ra_serf__connection_t *conn,
2553 /* If we have HTTP v2 support, we want to report against the 'me'
2555 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2556 *report_target = apr_pstrdup(pool, session->me_resource);
2558 /* Otherwise, we'll use the default VCC. */
2560 SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool));
2562 return SVN_NO_ERROR;
2566 svn_ra_serf__error_on_status(serf_status_line sline,
2568 const char *location)
2575 return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
2577 ? _("Repository moved permanently to '%s';"
2579 : _("Repository moved temporarily to '%s';"
2580 " please relocate"), location);
2582 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
2583 _("Access to '%s' forbidden"), path);
2586 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
2587 _("'%s' path not found"), path);
2589 return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
2590 _("'%s': no lock token available"), path);
2593 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2594 _("DAV request failed: 411 Content length required. The "
2595 "server or an intermediate proxy does not accept "
2596 "chunked encoding. Try setting 'http-chunked-requests' "
2597 "to 'auto' or 'no' in your client configuration."));
2599 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2600 _("The requested feature is not supported by "
2604 if (sline.code >= 300)
2605 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2606 _("Unexpected HTTP status %d '%s' on '%s'\n"),
2607 sline.code, sline.reason, path);
2609 return SVN_NO_ERROR;
2613 svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
2614 svn_delta_shim_callbacks_t *callbacks)
2616 svn_ra_serf__session_t *session = ra_session->priv;
2618 session->shim_callbacks = callbacks;
2619 return SVN_NO_ERROR;
2623 /* Conforms to Expat's XML_StartElementHandler */
2625 expat_start(void *userData, const char *raw_name, const char **attrs)
2627 struct expat_ctx_t *ectx = userData;
2629 if (ectx->inner_error != NULL)
2632 ectx->inner_error = svn_error_trace(
2633 svn_ra_serf__xml_cb_start(ectx->xmlctx,
2636 #ifdef EXPAT_HAS_STOPPARSER
2637 if (ectx->inner_error)
2638 (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2643 /* Conforms to Expat's XML_EndElementHandler */
2645 expat_end(void *userData, const char *raw_name)
2647 struct expat_ctx_t *ectx = userData;
2649 if (ectx->inner_error != NULL)
2652 ectx->inner_error = svn_error_trace(
2653 svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name));
2655 #ifdef EXPAT_HAS_STOPPARSER
2656 if (ectx->inner_error)
2657 (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2662 /* Conforms to Expat's XML_CharacterDataHandler */
2664 expat_cdata(void *userData, const char *data, int len)
2666 struct expat_ctx_t *ectx = userData;
2668 if (ectx->inner_error != NULL)
2671 ectx->inner_error = svn_error_trace(
2672 svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len));
2674 #ifdef EXPAT_HAS_STOPPARSER
2675 if (ectx->inner_error)
2676 (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2681 /* Implements svn_ra_serf__response_handler_t */
2682 static svn_error_t *
2683 expat_response_handler(serf_request_t *request,
2684 serf_bucket_t *response,
2686 apr_pool_t *scratch_pool)
2688 struct expat_ctx_t *ectx = baton;
2692 ectx->parser = XML_ParserCreate(NULL);
2693 apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
2694 xml_parser_cleanup, apr_pool_cleanup_null);
2695 XML_SetUserData(ectx->parser, ectx);
2696 XML_SetElementHandler(ectx->parser, expat_start, expat_end);
2697 XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
2700 /* ### TODO: sline.code < 200 should really be handled by the core */
2701 if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300))
2703 /* By deferring to expect_empty_body(), it will make a choice on
2704 how to handle the body. Whatever the decision, the core handler
2705 will take over, and we will not be called again. */
2706 return svn_error_trace(svn_ra_serf__expect_empty_body(
2707 request, response, ectx->handler,
2713 apr_status_t status;
2718 status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
2719 if (SERF_BUCKET_READ_ERROR(status))
2720 return svn_ra_serf__wrap_err(status, NULL);
2723 /* ### move restart/skip into the core handler */
2724 ectx->handler->read_size += len;
2727 /* ### move PAUSED behavior to a new response handler that can feed
2728 ### an inner handler, or can pause for a while. */
2730 /* ### should we have an IGNORE_ERRORS flag like the v1 parser? */
2732 expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */);
2734 /* We need to check INNER_ERROR first. This is an error from the
2735 callbacks that has been "dropped off" for us to retrieve. On
2736 current Expat parsers, we stop the parser when an error occurs,
2737 so we want to ignore EXPAT_STATUS (which reports the stoppage).
2739 If an error is not present, THEN we go ahead and look for parsing
2741 if (ectx->inner_error)
2743 apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
2744 xml_parser_cleanup);
2745 return svn_error_trace(ectx->inner_error);
2747 if (expat_status == XML_STATUS_ERROR)
2748 return svn_error_createf(SVN_ERR_XML_MALFORMED,
2750 _("The %s response contains invalid XML"
2752 ectx->handler->method,
2753 ectx->handler->sline.code,
2754 ectx->handler->sline.reason);
2756 /* The parsing went fine. What has the bucket told us? */
2758 if (APR_STATUS_IS_EOF(status))
2760 /* Tell expat we've reached the end of the content. Ignore the
2761 return status. We just don't care. */
2762 (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */);
2764 svn_ra_serf__xml_context_destroy(ectx->xmlctx);
2765 apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
2766 xml_parser_cleanup);
2768 /* ### should check XMLCTX to see if it has returned to the
2769 ### INITIAL state. we may have ended early... */
2772 if (status && !SERF_BUCKET_READ_ERROR(status))
2774 return svn_ra_serf__wrap_err(status, NULL);
2782 svn_ra_serf__handler_t *
2783 svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
2784 apr_pool_t *result_pool)
2786 svn_ra_serf__handler_t *handler;
2787 struct expat_ctx_t *ectx;
2789 ectx = apr_pcalloc(result_pool, sizeof(*ectx));
2790 ectx->xmlctx = xmlctx;
2791 ectx->parser = NULL;
2792 ectx->cleanup_pool = result_pool;
2795 handler = apr_pcalloc(result_pool, sizeof(*handler));
2796 handler->handler_pool = result_pool;
2797 handler->response_handler = expat_response_handler;
2798 handler->response_baton = ectx;
2800 ectx->handler = handler;