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
33 #include <serf_bucket_types.h>
38 #include "svn_dirent_uri.h"
40 #include "svn_private_config.h"
41 #include "svn_string.h"
43 #include "svn_props.h"
44 #include "svn_dirent_uri.h"
46 #include "../libsvn_ra/ra_loader.h"
47 #include "private/svn_dep_compat.h"
48 #include "private/svn_fspath.h"
49 #include "private/svn_subr_private.h"
50 #include "private/svn_auth_private.h"
51 #include "private/svn_cert.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;
278 svn_failures = (ssl_convert_serf_failures(failures)
279 | conn->server_cert_failures);
281 if (serf_ssl_cert_depth(cert) == 0)
283 /* If the depth is 0, the hostname must match the certificate.
285 ### This should really be handled by serf, which should pass an error
286 for this case, but that has backwards compatibility issues. */
287 apr_array_header_t *san;
288 svn_boolean_t found_san_entry = FALSE;
289 svn_boolean_t found_matching_hostname = FALSE;
290 svn_string_t *actual_hostname =
291 svn_string_create(conn->session->session_url.hostname, scratch_pool);
293 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
295 san = svn_hash_gets(serf_cert, "subjectAltName");
296 /* Try to find matching server name via subjectAltName first... */
300 found_san_entry = san->nelts > 0;
301 for (i = 0; i < san->nelts; i++)
303 const char *s = APR_ARRAY_IDX(san, i, const char*);
304 svn_string_t *cert_hostname = svn_string_create(s, scratch_pool);
306 if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
308 found_matching_hostname = TRUE;
314 /* Match server certificate CN with the hostname of the server iff
315 * we didn't find any subjectAltName fields and try to match them.
316 * Per RFC 2818 they are authoritative if present and CommonName
317 * should be ignored. */
318 if (!found_matching_hostname && !found_san_entry)
320 const char *hostname = NULL;
322 subject = serf_ssl_cert_subject(cert, scratch_pool);
325 hostname = svn_hash_gets(subject, "CN");
329 svn_string_t *cert_hostname = svn_string_create(hostname,
332 if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
334 found_matching_hostname = TRUE;
339 if (!found_matching_hostname)
340 svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
346 /* Extract the info from the certificate */
348 subject = serf_ssl_cert_subject(cert, scratch_pool);
349 issuer = serf_ssl_cert_issuer(cert, scratch_pool);
351 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
353 cert_info.hostname = svn_hash_gets(subject, "CN");
354 cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
355 if (! cert_info.fingerprint)
356 cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
357 cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
358 if (! cert_info.valid_from)
359 cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
360 cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
361 if (! cert_info.valid_until)
362 cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
363 cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
364 cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
366 /* Handle any non-server certs. */
367 if (serf_ssl_cert_depth(cert) > 0)
371 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
372 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
375 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
376 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
379 realmstring = apr_psprintf(scratch_pool, "AUTHORITY:%s",
380 cert_info.fingerprint);
382 err = svn_auth_first_credentials(&creds, &state,
383 SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
385 conn->session->wc_callbacks->auth_baton,
388 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
389 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
391 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
392 SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);
396 if (err->apr_err != SVN_ERR_AUTHN_NO_PROVIDER)
397 return svn_error_trace(err);
399 /* No provider registered that handles server authorities */
400 svn_error_clear(err);
406 server_creds = creds;
407 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
409 svn_failures &= ~server_creds->accepted_failures;
413 conn->server_cert_failures |= svn_failures;
418 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
419 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
422 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
423 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
426 realmstring = construct_realm(conn->session, conn->session->pool);
428 SVN_ERR(svn_auth_first_credentials(&creds, &state,
429 SVN_AUTH_CRED_SSL_SERVER_TRUST,
431 conn->session->wc_callbacks->auth_baton,
435 server_creds = creds;
436 svn_failures &= ~server_creds->accepted_failures;
437 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
440 while (svn_failures && creds)
442 SVN_ERR(svn_auth_next_credentials(&creds, state, scratch_pool));
446 server_creds = creds;
447 svn_failures &= ~server_creds->accepted_failures;
448 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
452 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
453 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
455 /* Are there non accepted failures left? */
458 svn_stringbuf_t *errmsg;
461 errmsg = svn_stringbuf_create(
462 _("Server SSL certificate verification failed"),
466 if (svn_failures & SVN_AUTH_SSL_NOTYETVALID)
467 append_reason(errmsg, _("certificate is not yet valid"), &reasons);
469 if (svn_failures & SVN_AUTH_SSL_EXPIRED)
470 append_reason(errmsg, _("certificate has expired"), &reasons);
472 if (svn_failures & SVN_AUTH_SSL_CNMISMATCH)
473 append_reason(errmsg,
474 _("certificate issued for a different hostname"),
477 if (svn_failures & SVN_AUTH_SSL_UNKNOWNCA)
478 append_reason(errmsg, _("issuer is not trusted"), &reasons);
480 if (svn_failures & SVN_AUTH_SSL_OTHER)
481 append_reason(errmsg, _("and other reason(s)"), &reasons);
483 return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL,
490 /* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
492 ssl_server_cert_cb(void *baton, int failures,
493 const serf_ssl_certificate_t *cert)
495 svn_ra_serf__connection_t *conn = baton;
496 svn_ra_serf__session_t *session = conn->session;
500 subpool = svn_pool_create(session->pool);
501 err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
502 svn_pool_destroy(subpool);
504 return save_error(session, err);
508 load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
511 apr_array_header_t *files = svn_cstring_split(authorities, ";",
512 TRUE /* chop_whitespace */,
516 for (i = 0; i < files->nelts; ++i)
518 const char *file = APR_ARRAY_IDX(files, i, const char *);
519 serf_ssl_certificate_t *ca_cert;
520 apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
522 if (status == APR_SUCCESS)
523 status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
525 if (status != APR_SUCCESS)
527 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
528 _("Invalid config: unable to load certificate file '%s'"),
529 svn_dirent_local_style(file, pool));
537 conn_setup(apr_socket_t *sock,
538 serf_bucket_t **read_bkt,
539 serf_bucket_t **write_bkt,
543 svn_ra_serf__connection_t *conn = baton;
545 *read_bkt = serf_context_bucket_socket_create(conn->session->context,
546 sock, conn->bkt_alloc);
548 if (conn->session->using_ssl)
551 *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
553 if (!conn->ssl_context)
555 conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
557 serf_ssl_set_hostname(conn->ssl_context,
558 conn->session->session_url.hostname);
560 serf_ssl_client_cert_provider_set(conn->ssl_context,
561 svn_ra_serf__handle_client_cert,
562 conn, conn->session->pool);
563 serf_ssl_client_cert_password_set(conn->ssl_context,
564 svn_ra_serf__handle_client_cert_pw,
565 conn, conn->session->pool);
566 serf_ssl_server_cert_callback_set(conn->ssl_context,
570 /* See if the user wants us to trust "default" openssl CAs. */
571 if (conn->session->trust_default_ca)
573 serf_ssl_use_default_certificates(conn->ssl_context);
575 /* Are there custom CAs to load? */
576 if (conn->session->ssl_authorities)
578 SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
579 conn->session->pool));
586 *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
595 /* svn_ra_serf__conn_setup is a callback for serf. This function
596 creates a read bucket and will wrap the write bucket if SSL
599 svn_ra_serf__conn_setup(apr_socket_t *sock,
600 serf_bucket_t **read_bkt,
601 serf_bucket_t **write_bkt,
605 svn_ra_serf__connection_t *conn = baton;
606 svn_ra_serf__session_t *session = conn->session;
609 err = svn_error_trace(conn_setup(sock,
614 return save_error(session, err);
618 /* Our default serf response acceptor. */
619 static serf_bucket_t *
620 accept_response(serf_request_t *request,
621 serf_bucket_t *stream,
622 void *acceptor_baton,
626 serf_bucket_alloc_t *bkt_alloc;
628 bkt_alloc = serf_request_get_alloc(request);
629 c = serf_bucket_barrier_create(stream, bkt_alloc);
631 return serf_bucket_response_create(c, bkt_alloc);
635 /* Custom response acceptor for HEAD requests. */
636 static serf_bucket_t *
637 accept_head(serf_request_t *request,
638 serf_bucket_t *stream,
639 void *acceptor_baton,
642 serf_bucket_t *response;
644 response = accept_response(request, stream, acceptor_baton, pool);
646 /* We know we shouldn't get a response body. */
647 serf_bucket_response_set_head(response);
653 connection_closed(svn_ra_serf__connection_t *conn,
659 return svn_error_wrap_apr(why, NULL);
662 if (conn->session->using_ssl)
663 conn->ssl_context = NULL;
669 svn_ra_serf__conn_closed(serf_connection_t *conn,
674 svn_ra_serf__connection_t *ra_conn = closed_baton;
677 err = svn_error_trace(connection_closed(ra_conn, why, pool));
679 (void) save_error(ra_conn->session, err);
683 /* Implementation of svn_ra_serf__handle_client_cert */
685 handle_client_cert(void *data,
686 const char **cert_path,
689 svn_ra_serf__connection_t *conn = data;
690 svn_ra_serf__session_t *session = conn->session;
696 realm = construct_realm(session, session->pool);
698 if (!conn->ssl_client_auth_state)
700 SVN_ERR(svn_auth_first_credentials(&creds,
701 &conn->ssl_client_auth_state,
702 SVN_AUTH_CRED_SSL_CLIENT_CERT,
704 session->wc_callbacks->auth_baton,
709 SVN_ERR(svn_auth_next_credentials(&creds,
710 conn->ssl_client_auth_state,
716 svn_auth_cred_ssl_client_cert_t *client_creds;
717 client_creds = creds;
718 *cert_path = client_creds->cert_file;
724 /* Implements serf_ssl_need_client_cert_t for handle_client_cert */
725 apr_status_t svn_ra_serf__handle_client_cert(void *data,
726 const char **cert_path)
728 svn_ra_serf__connection_t *conn = data;
729 svn_ra_serf__session_t *session = conn->session;
732 err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
734 return save_error(session, err);
737 /* Implementation for svn_ra_serf__handle_client_cert_pw */
739 handle_client_cert_pw(void *data,
740 const char *cert_path,
741 const char **password,
744 svn_ra_serf__connection_t *conn = data;
745 svn_ra_serf__session_t *session = conn->session;
750 if (!conn->ssl_client_pw_auth_state)
752 SVN_ERR(svn_auth_first_credentials(&creds,
753 &conn->ssl_client_pw_auth_state,
754 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
756 session->wc_callbacks->auth_baton,
761 SVN_ERR(svn_auth_next_credentials(&creds,
762 conn->ssl_client_pw_auth_state,
768 svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
770 *password = pw_creds->password;
776 /* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
777 apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
778 const char *cert_path,
779 const char **password)
781 svn_ra_serf__connection_t *conn = data;
782 svn_ra_serf__session_t *session = conn->session;
785 err = svn_error_trace(handle_client_cert_pw(data,
790 return save_error(session, err);
795 * Given a REQUEST on connection CONN, construct a request bucket for it,
796 * returning the bucket in *REQ_BKT.
798 * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
799 * corresponds to the new request.
801 * The request will be METHOD at URL.
803 * If BODY_BKT is not-NULL, it will be sent as the request body.
805 * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
807 * REQUEST_POOL should live for the duration of the request. Serf will
808 * construct this and provide it to the request_setup callback, so we
809 * should just use that one.
812 setup_serf_req(serf_request_t *request,
813 serf_bucket_t **req_bkt,
814 serf_bucket_t **hdrs_bkt,
815 svn_ra_serf__session_t *session,
816 const char *method, const char *url,
817 serf_bucket_t *body_bkt, const char *content_type,
818 const char *accept_encoding,
819 apr_pool_t *request_pool,
820 apr_pool_t *scratch_pool)
822 serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
825 svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
827 if (set_CL && body_bkt != NULL)
829 /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
830 it speaks HTTP/1.1 (and thus, chunked requests), or because the
831 server actually responded as only supporting HTTP/1.0.
833 We'll take the existing body_bkt, spool it into a spillbuf, and
834 then wrap a bucket around that spillbuf. The spillbuf will give
835 us the Content-Length value. */
836 SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
839 /* Destroy original bucket since it content is already copied
841 serf_bucket_destroy(body_bkt);
843 body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
848 /* Create a request bucket. Note that this sucker is kind enough to
849 add a "Host" header for us. */
850 *req_bkt = serf_request_bucket_request_create(request, method, url,
851 body_bkt, allocator);
853 /* Set the Content-Length value. This will also trigger an HTTP/1.0
854 request (rather than the default chunked request). */
857 if (body_bkt == NULL)
858 serf_bucket_request_set_CL(*req_bkt, 0);
860 serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
863 *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
865 /* We use serf_bucket_headers_setn() because the USERAGENT has a
866 lifetime longer than this bucket. Thus, there is no need to copy
867 the header values. */
868 serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
872 serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
877 serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
882 serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
885 /* These headers need to be sent with every request; see issue #3255
886 ("mod_dav_svn does not pass client capabilities to start-commit
888 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
889 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
890 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
896 svn_ra_serf__context_run_wait(svn_boolean_t *done,
897 svn_ra_serf__session_t *sess,
898 apr_pool_t *scratch_pool)
900 apr_pool_t *iterpool;
901 apr_interval_time_t waittime_left = sess->timeout;
903 assert(sess->pending_error == SVN_NO_ERROR);
905 iterpool = svn_pool_create(scratch_pool);
912 svn_pool_clear(iterpool);
914 if (sess->cancel_func)
915 SVN_ERR((*sess->cancel_func)(sess->cancel_baton));
917 status = serf_context_run(sess->context,
918 SVN_RA_SERF__CONTEXT_RUN_DURATION,
921 err = sess->pending_error;
922 sess->pending_error = SVN_NO_ERROR;
924 /* If the context duration timeout is up, we'll subtract that
925 duration from the total time alloted for such things. If
926 there's no time left, we fail with a message indicating that
927 the connection timed out. */
928 if (APR_STATUS_IS_TIMEUP(status))
934 if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
936 waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
941 svn_error_compose_create(
943 svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
944 _("Connection timed out")));
950 waittime_left = sess->timeout;
956 if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
958 /* apr can't translate subversion errors to text */
959 SVN_ERR_W(svn_error_create(status, NULL, NULL),
960 _("Error running context"));
963 return svn_ra_serf__wrap_err(status, _("Error running context"));
966 /* Debugging purposes only! */
967 for (i = 0; i < sess->num_conns; i++)
969 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
972 svn_pool_destroy(iterpool);
979 svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
980 apr_pool_t *scratch_pool)
984 /* Create a serf request based on HANDLER. */
985 svn_ra_serf__request_create(handler);
987 /* Wait until the response logic marks its DONE status. */
988 err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
991 /* A callback invocation has been canceled. In this simple case of
992 context_run_one, we can keep the ra-session operational by resetting
995 If we don't do this, the next context run will notice that the connection
996 is still in the error state and will just return SVN_ERR_CEASE_INVOCATION
997 (=the last error for the connection) again */
998 if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1000 apr_status_t status = serf_connection_reset(handler->conn->conn);
1003 err = svn_error_compose_create(err,
1004 svn_ra_serf__wrap_err(status, NULL));
1007 if (handler->server_error)
1009 err = svn_error_compose_create(err, handler->server_error->error);
1010 handler->server_error = NULL;
1013 return svn_error_trace(err);
1018 * Expat callback invoked on a start element tag for an error response.
1020 static svn_error_t *
1021 start_error(svn_ra_serf__xml_parser_t *parser,
1022 svn_ra_serf__dav_props_t name,
1024 apr_pool_t *scratch_pool)
1026 svn_ra_serf__server_error_t *ctx = parser->user_data;
1028 if (!ctx->in_error &&
1029 strcmp(name.namespace, "DAV:") == 0 &&
1030 strcmp(name.name, "error") == 0)
1032 ctx->in_error = TRUE;
1034 else if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
1036 const char *err_code;
1038 err_code = svn_xml_get_attr_value("errcode", attrs);
1043 SVN_ERR(svn_cstring_atoi64(&val, err_code));
1044 ctx->error->apr_err = (apr_status_t)val;
1047 /* If there's no error code provided, or if the provided code is
1048 0 (which can happen sometimes depending on how the error is
1049 constructed on the server-side), just pick a generic error
1050 code to run with. */
1051 if (! ctx->error->apr_err)
1053 ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
1056 /* Start collecting cdata. */
1057 svn_stringbuf_setempty(ctx->cdata);
1058 ctx->collect_cdata = TRUE;
1061 return SVN_NO_ERROR;
1065 * Expat callback invoked on an end element tag for a PROPFIND response.
1067 static svn_error_t *
1068 end_error(svn_ra_serf__xml_parser_t *parser,
1069 svn_ra_serf__dav_props_t name,
1070 apr_pool_t *scratch_pool)
1072 svn_ra_serf__server_error_t *ctx = parser->user_data;
1074 if (ctx->in_error &&
1075 strcmp(name.namespace, "DAV:") == 0 &&
1076 strcmp(name.name, "error") == 0)
1078 ctx->in_error = FALSE;
1080 if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
1082 /* On the server dav_error_response_tag() will add a leading
1083 and trailing newline if DEBUG_CR is defined in mod_dav.h,
1084 so remove any such characters here. */
1085 svn_stringbuf_strip_whitespace(ctx->cdata);
1087 ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
1089 ctx->collect_cdata = FALSE;
1092 return SVN_NO_ERROR;
1096 * Expat callback invoked on CDATA elements in an error response.
1098 * This callback can be called multiple times.
1100 static svn_error_t *
1101 cdata_error(svn_ra_serf__xml_parser_t *parser,
1104 apr_pool_t *scratch_pool)
1106 svn_ra_serf__server_error_t *ctx = parser->user_data;
1108 if (ctx->collect_cdata)
1110 svn_stringbuf_appendbytes(ctx->cdata, data, len);
1113 return SVN_NO_ERROR;
1118 drain_bucket(serf_bucket_t *bucket)
1120 /* Read whatever is in the bucket, and just drop it. */
1123 apr_status_t status;
1127 status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
1134 static svn_ra_serf__server_error_t *
1135 begin_error_parsing(svn_ra_serf__xml_start_element_t start,
1136 svn_ra_serf__xml_end_element_t end,
1137 svn_ra_serf__xml_cdata_chunk_handler_t cdata,
1138 apr_pool_t *result_pool)
1140 svn_ra_serf__server_error_t *server_err;
1142 server_err = apr_pcalloc(result_pool, sizeof(*server_err));
1143 server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
1144 server_err->contains_precondition_error = FALSE;
1145 server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool);
1146 server_err->collect_cdata = FALSE;
1147 server_err->parser.pool = server_err->error->pool;
1148 server_err->parser.user_data = server_err;
1149 server_err->parser.start = start;
1150 server_err->parser.end = end;
1151 server_err->parser.cdata = cdata;
1152 server_err->parser.ignore_errors = TRUE;
1157 /* Implements svn_ra_serf__response_handler_t */
1159 svn_ra_serf__handle_discard_body(serf_request_t *request,
1160 serf_bucket_t *response,
1164 apr_status_t status;
1166 status = drain_bucket(response);
1168 return svn_ra_serf__wrap_err(status, NULL);
1170 return SVN_NO_ERROR;
1174 svn_ra_serf__response_discard_handler(serf_request_t *request,
1175 serf_bucket_t *response,
1179 return drain_bucket(response);
1183 /* Return the value of the RESPONSE's Location header if any, or NULL
1186 response_get_location(serf_bucket_t *response,
1187 const char *base_url,
1188 apr_pool_t *result_pool,
1189 apr_pool_t *scratch_pool)
1191 serf_bucket_t *headers;
1192 const char *location;
1194 headers = serf_bucket_response_get_headers(response);
1195 location = serf_bucket_headers_get(headers, "Location");
1196 if (location == NULL)
1199 /* The RFCs say we should have received a full url in LOCATION, but
1200 older apache versions and many custom web handlers just return a
1201 relative path here...
1203 And we can't trust anything because it is network data.
1205 if (*location == '/')
1208 apr_status_t status;
1210 status = apr_uri_parse(scratch_pool, base_url, &uri);
1212 if (status != APR_SUCCESS)
1215 /* Replace the path path with what we got */
1216 uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
1218 /* And make APR produce a proper full url for us */
1219 location = apr_uri_unparse(scratch_pool, &uri, 0);
1221 /* Fall through to ensure our canonicalization rules */
1223 else if (!svn_path_is_url(location))
1225 return NULL; /* Any other formats we should support? */
1228 return svn_uri_canonicalize(location, result_pool);
1232 /* Implements svn_ra_serf__response_handler_t */
1234 svn_ra_serf__expect_empty_body(serf_request_t *request,
1235 serf_bucket_t *response,
1237 apr_pool_t *scratch_pool)
1239 svn_ra_serf__handler_t *handler = baton;
1240 serf_bucket_t *hdrs;
1243 /* This function is just like handle_multistatus_only() except for the
1244 XML parsing callbacks. We want to look for the human-readable element. */
1246 /* We should see this just once, in order to initialize SERVER_ERROR.
1247 At that point, the core error processing will take over. If we choose
1248 not to parse an error, then we'll never return here (because we
1249 change the response handler). */
1250 SVN_ERR_ASSERT(handler->server_error == NULL);
1252 hdrs = serf_bucket_response_get_headers(response);
1253 val = serf_bucket_headers_get(hdrs, "Content-Type");
1254 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1256 svn_ra_serf__server_error_t *server_err;
1258 server_err = begin_error_parsing(start_error, end_error, cdata_error,
1259 handler->handler_pool);
1261 /* Get the parser to set our DONE flag. */
1262 server_err->parser.done = &handler->done;
1264 handler->server_error = server_err;
1268 /* The body was not text/xml, so we don't know what to do with it.
1269 Toss anything that arrives. */
1270 handler->discard_body = TRUE;
1273 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1274 to call the response handler again. That will start up the XML parsing,
1275 or it will be dropped on the floor (per the decision above). */
1276 return SVN_NO_ERROR;
1280 /* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric
1281 status code into *STATUS_CODE_OUT. Ignores leading whitespace. */
1282 static svn_error_t *
1283 parse_dav_status(int *status_code_out, svn_stringbuf_t *buf,
1284 apr_pool_t *scratch_pool)
1289 svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool);
1291 svn_stringbuf_strip_whitespace(temp_buf);
1292 token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status);
1294 token = apr_strtok(NULL, " \t\r\n", &tok_status);
1296 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1297 _("Malformed DAV:status CDATA '%s'"),
1299 err = svn_cstring_atoi(status_code_out, token);
1301 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
1302 _("Malformed DAV:status CDATA '%s'"),
1305 return SVN_NO_ERROR;
1309 * Expat callback invoked on a start element tag for a 207 response.
1311 static svn_error_t *
1312 start_207(svn_ra_serf__xml_parser_t *parser,
1313 svn_ra_serf__dav_props_t name,
1315 apr_pool_t *scratch_pool)
1317 svn_ra_serf__server_error_t *ctx = parser->user_data;
1319 if (!ctx->in_error &&
1320 strcmp(name.namespace, "DAV:") == 0 &&
1321 strcmp(name.name, "multistatus") == 0)
1323 ctx->in_error = TRUE;
1325 else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
1327 /* Start collecting cdata. */
1328 svn_stringbuf_setempty(ctx->cdata);
1329 ctx->collect_cdata = TRUE;
1331 else if (ctx->in_error &&
1332 strcmp(name.namespace, "DAV:") == 0 &&
1333 strcmp(name.name, "status") == 0)
1335 /* Start collecting cdata. */
1336 svn_stringbuf_setempty(ctx->cdata);
1337 ctx->collect_cdata = TRUE;
1340 return SVN_NO_ERROR;
1344 * Expat callback invoked on an end element tag for a 207 response.
1346 static svn_error_t *
1347 end_207(svn_ra_serf__xml_parser_t *parser,
1348 svn_ra_serf__dav_props_t name,
1349 apr_pool_t *scratch_pool)
1351 svn_ra_serf__server_error_t *ctx = parser->user_data;
1353 if (ctx->in_error &&
1354 strcmp(name.namespace, "DAV:") == 0 &&
1355 strcmp(name.name, "multistatus") == 0)
1357 ctx->in_error = FALSE;
1359 if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
1361 /* Remove leading newline added by DEBUG_CR on server */
1362 svn_stringbuf_strip_whitespace(ctx->cdata);
1364 ctx->collect_cdata = FALSE;
1365 ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
1367 if (ctx->contains_precondition_error)
1368 ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
1370 ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
1372 else if (ctx->in_error &&
1373 strcmp(name.namespace, "DAV:") == 0 &&
1374 strcmp(name.name, "status") == 0)
1378 ctx->collect_cdata = FALSE;
1380 SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool));
1381 if (status_code == 412)
1382 ctx->contains_precondition_error = TRUE;
1385 return SVN_NO_ERROR;
1389 * Expat callback invoked on CDATA elements in a 207 response.
1391 * This callback can be called multiple times.
1393 static svn_error_t *
1394 cdata_207(svn_ra_serf__xml_parser_t *parser,
1397 apr_pool_t *scratch_pool)
1399 svn_ra_serf__server_error_t *ctx = parser->user_data;
1401 if (ctx->collect_cdata)
1403 svn_stringbuf_appendbytes(ctx->cdata, data, len);
1406 return SVN_NO_ERROR;
1409 /* Implements svn_ra_serf__response_handler_t */
1411 svn_ra_serf__handle_multistatus_only(serf_request_t *request,
1412 serf_bucket_t *response,
1414 apr_pool_t *scratch_pool)
1416 svn_ra_serf__handler_t *handler = baton;
1418 /* This function is just like expect_empty_body() except for the
1419 XML parsing callbacks. We are looking for very limited pieces of
1420 the multistatus response. */
1422 /* We should see this just once, in order to initialize SERVER_ERROR.
1423 At that point, the core error processing will take over. If we choose
1424 not to parse an error, then we'll never return here (because we
1425 change the response handler). */
1426 SVN_ERR_ASSERT(handler->server_error == NULL);
1429 serf_bucket_t *hdrs;
1432 hdrs = serf_bucket_response_get_headers(response);
1433 val = serf_bucket_headers_get(hdrs, "Content-Type");
1434 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1436 svn_ra_serf__server_error_t *server_err;
1438 server_err = begin_error_parsing(start_207, end_207, cdata_207,
1439 handler->handler_pool);
1441 /* Get the parser to set our DONE flag. */
1442 server_err->parser.done = &handler->done;
1444 handler->server_error = server_err;
1448 /* The body was not text/xml, so we don't know what to do with it.
1449 Toss anything that arrives. */
1450 handler->discard_body = TRUE;
1454 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1455 to call the response handler again. That will start up the XML parsing,
1456 or it will be dropped on the floor (per the decision above). */
1457 return SVN_NO_ERROR;
1461 /* Conforms to Expat's XML_StartElementHandler */
1463 start_xml(void *userData, const char *raw_name, const char **attrs)
1465 svn_ra_serf__xml_parser_t *parser = userData;
1466 svn_ra_serf__dav_props_t name;
1467 apr_pool_t *scratch_pool;
1474 svn_ra_serf__xml_push_state(parser, 0);
1476 /* ### get a real scratch_pool */
1477 scratch_pool = parser->state->pool;
1479 svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool);
1481 svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
1483 err = parser->start(parser, name, attrs, scratch_pool);
1484 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1485 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1487 parser->error = err;
1491 /* Conforms to Expat's XML_EndElementHandler */
1493 end_xml(void *userData, const char *raw_name)
1495 svn_ra_serf__xml_parser_t *parser = userData;
1496 svn_ra_serf__dav_props_t name;
1498 apr_pool_t *scratch_pool;
1503 /* ### get a real scratch_pool */
1504 scratch_pool = parser->state->pool;
1506 svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
1508 err = parser->end(parser, name, scratch_pool);
1509 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1510 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1512 parser->error = err;
1516 /* Conforms to Expat's XML_CharacterDataHandler */
1518 cdata_xml(void *userData, const char *data, int len)
1520 svn_ra_serf__xml_parser_t *parser = userData;
1522 apr_pool_t *scratch_pool;
1528 svn_ra_serf__xml_push_state(parser, 0);
1530 /* ### get a real scratch_pool */
1531 scratch_pool = parser->state->pool;
1533 err = parser->cdata(parser, data, len, scratch_pool);
1534 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1535 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1537 parser->error = err;
1540 /* Flip the requisite bits in CTX to indicate that processing of the
1541 response is complete, adding the current "done item" to the list of
1544 add_done_item(svn_ra_serf__xml_parser_t *ctx)
1546 /* Make sure we don't add to DONE_LIST twice. */
1552 ctx->done_item->data = ctx->user_data;
1553 ctx->done_item->next = *ctx->done_list;
1554 *ctx->done_list = ctx->done_item;
1560 static svn_error_t *
1561 write_to_pending(svn_ra_serf__xml_parser_t *ctx,
1564 apr_pool_t *scratch_pool)
1566 if (ctx->pending == NULL)
1568 ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
1569 ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE,
1574 /* Copy the data into one or more chunks in the spill buffer. */
1575 return svn_error_trace(svn_spillbuf__write(ctx->pending->buf,
1581 static svn_error_t *
1582 inject_to_parser(svn_ra_serf__xml_parser_t *ctx,
1585 const serf_status_line *sl)
1589 xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0);
1591 if (! ctx->ignore_errors)
1593 SVN_ERR(ctx->error);
1595 if (xml_status != XML_STATUS_OK)
1598 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1599 _("XML parsing failed"));
1601 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1602 _("XML parsing failed: (%d %s)"),
1603 sl->code, sl->reason);
1607 return SVN_NO_ERROR;
1610 /* Apr pool cleanup handler to release an XML_Parser in success and error
1613 xml_parser_cleanup(void *baton)
1615 XML_Parser *xmlp = baton;
1619 (void) XML_ParserFree(*xmlp);
1626 /* Limit the amount of pending content to parse at once to < 100KB per
1627 iteration. This number is chosen somewhat arbitrarely. Making it lower
1628 will have a drastical negative impact on performance, whereas increasing it
1629 increases the risk for connection timeouts.
1631 #define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5
1634 svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
1635 svn_boolean_t *network_eof,
1636 apr_pool_t *scratch_pool)
1638 svn_boolean_t pending_empty = FALSE;
1639 apr_size_t cur_read = 0;
1641 /* Fast path exit: already paused, nothing to do, or already done. */
1642 if (parser->paused || parser->pending == NULL || *parser->done)
1644 *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
1645 return SVN_NO_ERROR;
1648 /* Parsing the pending conten in the spillbuf will result in many disc i/o
1649 operations. This can be so slow that we don't run the network event
1650 processing loop often enough, resulting in timed out connections.
1652 So we limit the amounts of bytes parsed per iteration.
1654 while (cur_read < PENDING_TO_PARSE)
1659 /* Get a block of content, stopping the loop when we run out. */
1660 SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf,
1664 /* Inject the content into the XML parser. */
1665 SVN_ERR(inject_to_parser(parser, data, len, NULL));
1667 /* If the XML parsing callbacks paused us, then we're done for now. */
1675 /* The buffer is empty. */
1676 pending_empty = TRUE;
1681 /* If the PENDING structures are empty *and* we consumed all content from
1682 the network, then we're completely done with the parsing. */
1683 if (pending_empty &&
1684 parser->pending->network_eof)
1687 SVN_ERR_ASSERT(parser->xmlp != NULL);
1689 /* Tell the parser that no more content will be parsed. */
1690 xml_status = XML_Parse(parser->xmlp, NULL, 0, 1);
1692 apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup);
1693 parser->xmlp = NULL;
1695 if (! parser->ignore_errors)
1697 SVN_ERR(parser->error);
1699 if (xml_status != XML_STATUS_OK)
1701 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1702 _("XML parsing failed"));
1706 add_done_item(parser);
1709 *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
1711 return SVN_NO_ERROR;
1713 #undef PENDING_TO_PARSE
1716 /* ### this is still broken conceptually. just shifting incrementally... */
1717 static svn_error_t *
1718 handle_server_error(serf_request_t *request,
1719 serf_bucket_t *response,
1720 apr_pool_t *scratch_pool)
1722 svn_ra_serf__server_error_t server_err = { 0 };
1723 serf_bucket_t *hdrs;
1727 hdrs = serf_bucket_response_get_headers(response);
1728 val = serf_bucket_headers_get(hdrs, "Content-Type");
1729 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1731 /* ### we should figure out how to reuse begin_error_parsing */
1733 server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL);
1734 server_err.contains_precondition_error = FALSE;
1735 server_err.cdata = svn_stringbuf_create_empty(scratch_pool);
1736 server_err.collect_cdata = FALSE;
1737 server_err.parser.pool = server_err.error->pool;
1738 server_err.parser.user_data = &server_err;
1739 server_err.parser.start = start_error;
1740 server_err.parser.end = end_error;
1741 server_err.parser.cdata = cdata_error;
1742 server_err.parser.done = &server_err.done;
1743 server_err.parser.ignore_errors = TRUE;
1745 /* We don't care about any errors except for SERVER_ERR.ERROR */
1746 svn_error_clear(svn_ra_serf__handle_xml_parser(request,
1751 /* ### checking DONE is silly. the above only parses whatever has
1752 ### been received at the network interface. totally wrong. but
1753 ### it is what we have for now (maintaining historical code),
1754 ### until we fully migrate. */
1755 if (server_err.done && server_err.error->apr_err == APR_SUCCESS)
1757 svn_error_clear(server_err.error);
1758 server_err.error = SVN_NO_ERROR;
1761 return svn_error_trace(server_err.error);
1764 /* The only error that we will return is from the XML response body.
1765 Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to
1767 err = drain_bucket(response);
1768 if (err && !SERF_BUCKET_READ_ERROR(err))
1769 return svn_ra_serf__wrap_err(err, NULL);
1771 return SVN_NO_ERROR;
1775 /* Implements svn_ra_serf__response_handler_t */
1777 svn_ra_serf__handle_xml_parser(serf_request_t *request,
1778 serf_bucket_t *response,
1782 serf_status_line sl;
1783 apr_status_t status;
1784 svn_ra_serf__xml_parser_t *ctx = baton;
1787 /* ### get the HANDLER rather than fetching this. */
1788 status = serf_bucket_response_status(response, &sl);
1789 if (SERF_BUCKET_READ_ERROR(status))
1791 return svn_ra_serf__wrap_err(status, NULL);
1794 /* Woo-hoo. Nothing here to see. */
1795 if (sl.code == 404 && !ctx->ignore_errors)
1797 err = handle_server_error(request, response, pool);
1799 if (err && APR_STATUS_IS_EOF(err->apr_err))
1802 return svn_error_trace(err);
1807 ctx->xmlp = XML_ParserCreate(NULL);
1808 apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup,
1809 apr_pool_cleanup_null);
1810 XML_SetUserData(ctx->xmlp, ctx);
1811 XML_SetElementHandler(ctx->xmlp, start_xml, end_xml);
1814 XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml);
1823 status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
1824 if (SERF_BUCKET_READ_ERROR(status))
1826 return svn_ra_serf__wrap_err(status, NULL);
1829 /* Note: once the callbacks invoked by inject_to_parser() sets the
1830 PAUSED flag, then it will not be cleared. write_to_pending() will
1831 only save the content. Logic outside of serf_context_run() will
1832 clear that flag, as appropriate, along with processing the
1833 content that we have placed into the PENDING buffer.
1835 We want to save arriving content into the PENDING structures if
1836 the parser has been paused, or we already have data in there (so
1837 the arriving data is appended, rather than injected out of order) */
1838 if (ctx->paused || HAS_PENDING_DATA(ctx->pending))
1840 err = write_to_pending(ctx, data, len, pool);
1844 err = inject_to_parser(ctx, data, len, &sl);
1847 /* Should have no errors if IGNORE_ERRORS is set. */
1848 SVN_ERR_ASSERT(!ctx->ignore_errors);
1853 SVN_ERR_ASSERT(ctx->xmlp != NULL);
1855 apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
1857 return svn_error_trace(err);
1860 if (APR_STATUS_IS_EAGAIN(status))
1862 return svn_ra_serf__wrap_err(status, NULL);
1865 if (APR_STATUS_IS_EOF(status))
1867 if (ctx->pending != NULL)
1868 ctx->pending->network_eof = TRUE;
1870 /* We just hit the end of the network content. If we have nothing
1871 in the PENDING structures, then we're completely done. */
1872 if (!HAS_PENDING_DATA(ctx->pending))
1875 SVN_ERR_ASSERT(ctx->xmlp != NULL);
1877 xml_status = XML_Parse(ctx->xmlp, NULL, 0, 1);
1879 apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
1881 if (! ctx->ignore_errors)
1883 SVN_ERR(ctx->error);
1885 if (xml_status != XML_STATUS_OK)
1887 return svn_error_create(
1888 SVN_ERR_XML_MALFORMED, NULL,
1889 _("The XML response contains invalid XML"));
1896 return svn_ra_serf__wrap_err(status, NULL);
1906 svn_ra_serf__credentials_callback(char **username, char **password,
1907 serf_request_t *request, void *baton,
1908 int code, const char *authn_type,
1912 svn_ra_serf__handler_t *handler = baton;
1913 svn_ra_serf__session_t *session = handler->session;
1915 svn_auth_cred_simple_t *simple_creds;
1920 /* Use svn_auth_first_credentials if this is the first time we ask for
1921 credentials during this session OR if the last time we asked
1922 session->auth_state wasn't set (eg. if the credentials provider was
1923 cancelled by the user). */
1924 if (!session->auth_state)
1926 err = svn_auth_first_credentials(&creds,
1927 &session->auth_state,
1928 SVN_AUTH_CRED_SIMPLE,
1930 session->wc_callbacks->auth_baton,
1935 err = svn_auth_next_credentials(&creds,
1936 session->auth_state,
1942 (void) save_error(session, err);
1943 return err->apr_err;
1946 session->auth_attempts++;
1948 if (!creds || session->auth_attempts > 4)
1950 /* No more credentials. */
1951 (void) save_error(session,
1953 SVN_ERR_AUTHN_FAILED, NULL,
1954 _("No more credentials or we tried too many "
1955 "times.\nAuthentication failed")));
1956 return SVN_ERR_AUTHN_FAILED;
1959 simple_creds = creds;
1960 *username = apr_pstrdup(pool, simple_creds->username);
1961 *password = apr_pstrdup(pool, simple_creds->password);
1965 *username = apr_pstrdup(pool, session->proxy_username);
1966 *password = apr_pstrdup(pool, session->proxy_password);
1968 session->proxy_auth_attempts++;
1970 if (!session->proxy_username || session->proxy_auth_attempts > 4)
1972 /* No more credentials. */
1973 (void) save_error(session,
1975 SVN_ERR_AUTHN_FAILED, NULL,
1976 _("Proxy authentication failed")));
1977 return SVN_ERR_AUTHN_FAILED;
1981 handler->conn->last_status_code = code;
1986 /* Wait for HTTP response status and headers, and invoke HANDLER->
1987 response_handler() to carry out operation-specific processing.
1988 Afterwards, check for connection close.
1990 SERF_STATUS allows returning errors to serf without creating a
1991 subversion error object.
1993 static svn_error_t *
1994 handle_response(serf_request_t *request,
1995 serf_bucket_t *response,
1996 svn_ra_serf__handler_t *handler,
1997 apr_status_t *serf_status,
1998 apr_pool_t *scratch_pool)
2000 apr_status_t status;
2003 /* ### need to verify whether this already gets init'd on every
2004 ### successful exit. for an error-exit, it will (properly) be
2005 ### ignored by the caller. */
2006 *serf_status = APR_SUCCESS;
2010 /* Uh-oh. Our connection died. */
2011 if (handler->response_error)
2013 /* Give a handler chance to prevent request requeue. */
2014 SVN_ERR(handler->response_error(request, response, 0,
2015 handler->response_error_baton));
2017 svn_ra_serf__request_create(handler);
2019 /* Response error callback is not configured. Requeue another request
2020 for this handler only if we didn't started to process body.
2021 Return error otherwise. */
2022 else if (!handler->reading_body)
2024 svn_ra_serf__request_create(handler);
2028 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2029 _("%s request on '%s' failed"),
2030 handler->method, handler->path);
2033 return SVN_NO_ERROR;
2036 /* If we're reading the body, then skip all this preparation. */
2037 if (handler->reading_body)
2040 /* Copy the Status-Line info into HANDLER, if we don't yet have it. */
2041 if (handler->sline.version == 0)
2043 serf_status_line sl;
2045 status = serf_bucket_response_status(response, &sl);
2046 if (status != APR_SUCCESS)
2048 /* The response line is not (yet) ready, or some other error. */
2049 *serf_status = status;
2050 return SVN_NO_ERROR; /* Handled by serf */
2053 /* If we got APR_SUCCESS, then we should have Status-Line info. */
2054 SVN_ERR_ASSERT(sl.version != 0);
2056 handler->sline = sl;
2057 handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
2059 /* HTTP/1.1? (or later) */
2060 if (sl.version != SERF_HTTP_10)
2061 handler->session->http10 = FALSE;
2064 /* Keep reading from the network until we've read all the headers. */
2065 status = serf_bucket_response_wait_for_headers(response);
2068 /* The typical "error" will be APR_EAGAIN, meaning that more input
2069 from the network is required to complete the reading of the
2071 if (!APR_STATUS_IS_EOF(status))
2073 /* Either the headers are not (yet) complete, or there really
2075 *serf_status = status;
2076 return SVN_NO_ERROR;
2079 /* wait_for_headers() will return EOF if there is no body in this
2080 response, or if we completely read the body. The latter is not
2081 true since we would have set READING_BODY to get the body read,
2082 and we would not be back to this code block.
2084 It can also return EOF if we truly hit EOF while (say) processing
2085 the headers. aka Badness. */
2087 /* Cases where a lack of a response body (via EOF) is okay:
2089 * - 204/304 response
2091 * Otherwise, if we get an EOF here, something went really wrong: either
2092 * the server closed on us early or we're reading too much. Either way,
2095 if (strcmp(handler->method, "HEAD") != 0
2096 && handler->sline.code != 204
2097 && handler->sline.code != 304)
2099 err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
2100 svn_ra_serf__wrap_err(status, NULL),
2101 _("Premature EOF seen from server"
2102 " (http status=%d)"),
2103 handler->sline.code);
2105 /* In case anything else arrives... discard it. */
2106 handler->discard_body = TRUE;
2112 /* ... and set up the header fields in HANDLER. */
2113 handler->location = response_get_location(response,
2114 handler->session->session_url_str,
2115 handler->handler_pool,
2118 /* On the last request, we failed authentication. We succeeded this time,
2119 so let's save away these credentials. */
2120 if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
2122 SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
2123 handler->session->pool));
2124 handler->session->auth_attempts = 0;
2125 handler->session->auth_state = NULL;
2127 handler->conn->last_status_code = handler->sline.code;
2129 if (handler->sline.code == 405
2130 || handler->sline.code == 408
2131 || handler->sline.code == 409
2132 || handler->sline.code >= 500)
2134 /* 405 Method Not allowed.
2136 409 Conflict: can indicate a hook error.
2137 5xx (Internal) Server error. */
2138 serf_bucket_t *hdrs;
2141 hdrs = serf_bucket_response_get_headers(response);
2142 val = serf_bucket_headers_get(hdrs, "Content-Type");
2143 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
2145 svn_ra_serf__server_error_t *server_err;
2147 server_err = begin_error_parsing(start_error, end_error, cdata_error,
2148 handler->handler_pool);
2149 /* Get the parser to set our DONE flag. */
2150 server_err->parser.done = &handler->done;
2152 handler->server_error = server_err;
2156 handler->discard_body = TRUE;
2158 if (!handler->session->pending_error)
2160 apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
2162 /* 405 == Method Not Allowed (Occurs when trying to lock a working
2163 copy path which no longer exists at HEAD in the repository. */
2164 if (handler->sline.code == 405
2165 && strcmp(handler->method, "LOCK") == 0)
2166 apr_err = SVN_ERR_FS_OUT_OF_DATE;
2168 handler->session->pending_error =
2169 svn_error_createf(apr_err, NULL,
2170 _("%s request on '%s' failed: %d %s"),
2171 handler->method, handler->path,
2172 handler->sline.code, handler->sline.reason);
2177 /* Stop processing the above, on every packet arrival. */
2178 handler->reading_body = TRUE;
2182 /* We've been instructed to ignore the body. Drain whatever is present. */
2183 if (handler->discard_body)
2185 *serf_status = drain_bucket(response);
2187 /* If the handler hasn't set done (which it shouldn't have) and
2188 we now have the EOF, go ahead and set it so that we can stop
2191 if (!handler->done && APR_STATUS_IS_EOF(*serf_status))
2192 handler->done = TRUE;
2194 return SVN_NO_ERROR;
2197 /* If we are supposed to parse the body as a server_error, then do
2199 if (handler->server_error != NULL)
2201 err = svn_ra_serf__handle_xml_parser(request, response,
2202 &handler->server_error->parser,
2205 /* If we do not receive an error or it is a non-transient error, return
2208 APR_EOF will be returned when parsing is complete.
2210 APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through
2211 parsing and the network has no more data right now. If we receive that,
2212 clear the error and return - allowing serf to wait for more data.
2214 if (!err || SERF_BUCKET_READ_ERROR(err->apr_err))
2215 return svn_error_trace(err);
2217 if (!APR_STATUS_IS_EOF(err->apr_err))
2219 *serf_status = err->apr_err;
2220 svn_error_clear(err);
2221 return SVN_NO_ERROR;
2224 /* Clear the EOF. We don't need it. */
2225 svn_error_clear(err);
2227 /* If the parsing is done, and we did not extract an error, then
2228 simply toss everything, and anything else that might arrive.
2229 The higher-level code will need to investigate HANDLER->SLINE,
2230 as we have no further information for them. */
2232 && handler->server_error->error->apr_err == APR_SUCCESS)
2234 svn_error_clear(handler->server_error->error);
2236 /* Stop parsing for a server error. */
2237 handler->server_error = NULL;
2239 /* If anything arrives after this, then just discard it. */
2240 handler->discard_body = TRUE;
2243 *serf_status = APR_EOF;
2244 return SVN_NO_ERROR;
2247 /* Pass the body along to the registered response handler. */
2248 err = handler->response_handler(request, response,
2249 handler->response_baton,
2253 && (!SERF_BUCKET_READ_ERROR(err->apr_err)
2254 || APR_STATUS_IS_ECONNRESET(err->apr_err)
2255 || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
2257 /* These errors are special cased in serf
2258 ### We hope no handler returns these by accident. */
2259 *serf_status = err->apr_err;
2260 svn_error_clear(err);
2261 return SVN_NO_ERROR;
2264 return svn_error_trace(err);
2268 /* Implements serf_response_handler_t for handle_response. Storing
2269 errors in handler->session->pending_error if appropriate. */
2271 handle_response_cb(serf_request_t *request,
2272 serf_bucket_t *response,
2274 apr_pool_t *scratch_pool)
2276 svn_ra_serf__handler_t *handler = baton;
2278 apr_status_t inner_status;
2279 apr_status_t outer_status;
2281 err = svn_error_trace(handle_response(request, response,
2282 handler, &inner_status,
2285 /* Select the right status value to return. */
2286 outer_status = save_error(handler->session, err);
2288 outer_status = inner_status;
2290 /* Make sure the DONE flag is set properly. */
2291 if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
2292 handler->done = TRUE;
2294 return outer_status;
2297 /* Perform basic request setup, with special handling for HEAD requests,
2298 and finer-grained callbacks invoked (if non-NULL) to produce the request
2299 headers and body. */
2300 static svn_error_t *
2301 setup_request(serf_request_t *request,
2302 svn_ra_serf__handler_t *handler,
2303 serf_bucket_t **req_bkt,
2304 apr_pool_t *request_pool,
2305 apr_pool_t *scratch_pool)
2307 serf_bucket_t *body_bkt;
2308 serf_bucket_t *headers_bkt;
2309 const char *accept_encoding;
2311 if (handler->body_delegate)
2313 serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
2315 /* ### should pass the scratch_pool */
2316 SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
2317 bkt_alloc, request_pool));
2324 if (handler->custom_accept_encoding)
2326 accept_encoding = NULL;
2328 else if (handler->session->using_compression)
2330 /* Accept gzip compression if enabled. */
2331 accept_encoding = "gzip";
2335 accept_encoding = NULL;
2338 SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
2339 handler->session, handler->method, handler->path,
2340 body_bkt, handler->body_type, accept_encoding,
2341 request_pool, scratch_pool));
2343 if (handler->header_delegate)
2345 /* ### should pass the scratch_pool */
2346 SVN_ERR(handler->header_delegate(headers_bkt,
2347 handler->header_delegate_baton,
2354 /* Implements the serf_request_setup_t interface (which sets up both a
2355 request and its response handler callback). Handles errors for
2358 setup_request_cb(serf_request_t *request,
2360 serf_bucket_t **req_bkt,
2361 serf_response_acceptor_t *acceptor,
2362 void **acceptor_baton,
2363 serf_response_handler_t *s_handler,
2364 void **s_handler_baton,
2367 svn_ra_serf__handler_t *handler = setup_baton;
2370 /* ### construct a scratch_pool? serf gives us a pool that will live for
2371 ### the duration of the request. */
2372 apr_pool_t *scratch_pool = pool;
2374 if (strcmp(handler->method, "HEAD") == 0)
2375 *acceptor = accept_head;
2377 *acceptor = accept_response;
2378 *acceptor_baton = handler->session;
2380 *s_handler = handle_response_cb;
2381 *s_handler_baton = handler;
2383 err = svn_error_trace(setup_request(request, handler, req_bkt,
2384 pool /* request_pool */, scratch_pool));
2386 return save_error(handler->session, err);
2390 svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
2392 SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL);
2394 /* In case HANDLER is re-queued, reset the various transient fields.
2396 ### prior to recent changes, HANDLER was constant. maybe we should
2397 ### break out these processing fields, apart from the request
2399 handler->done = FALSE;
2400 handler->server_error = NULL;
2401 handler->sline.version = 0;
2402 handler->location = NULL;
2403 handler->reading_body = FALSE;
2404 handler->discard_body = FALSE;
2406 /* ### do we ever alter the >response_handler? */
2408 /* ### do we need to hold onto the returned request object, or just
2409 ### not worry about it (the serf ctx will manage it). */
2410 (void) serf_connection_request_create(handler->conn->conn,
2411 setup_request_cb, handler);
2416 svn_ra_serf__discover_vcc(const char **vcc_url,
2417 svn_ra_serf__session_t *session,
2418 svn_ra_serf__connection_t *conn,
2422 const char *relative_path;
2425 /* If we've already got the information our caller seeks, just return it. */
2426 if (session->vcc_url && session->repos_root_str)
2428 *vcc_url = session->vcc_url;
2429 return SVN_NO_ERROR;
2432 /* If no connection is provided, use the default one. */
2435 conn = session->conns[0];
2438 path = session->session_url.path;
2447 err = svn_ra_serf__fetch_node_props(&props, conn,
2448 path, SVN_INVALID_REVNUM,
2449 base_props, pool, pool);
2452 apr_hash_t *ns_props;
2454 ns_props = apr_hash_get(props, "DAV:", 4);
2455 *vcc_url = svn_prop_get_value(ns_props,
2456 "version-controlled-configuration");
2458 ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
2459 relative_path = svn_prop_get_value(ns_props,
2460 "baseline-relative-path");
2461 uuid = svn_prop_get_value(ns_props, "repository-uuid");
2466 if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
2467 (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
2469 return svn_error_trace(err); /* found a _real_ error */
2473 /* This happens when the file is missing in HEAD. */
2474 svn_error_clear(err);
2476 /* Okay, strip off a component from PATH. */
2477 path = svn_urlpath__dirname(path, pool);
2479 /* An error occurred on conns. serf 0.4.0 remembers that
2480 the connection had a problem. We need to reset it, in
2481 order to use it again. */
2482 serf_connection_reset(conn->conn);
2486 while ((path[0] != '\0')
2487 && (! (path[0] == '/' && path[1] == '\0')));
2491 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
2492 _("The PROPFIND response did not include the "
2493 "requested version-controlled-configuration "
2497 /* Store our VCC in our cache. */
2498 if (!session->vcc_url)
2500 session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
2503 /* Update our cached repository root URL. */
2504 if (!session->repos_root_str)
2506 svn_stringbuf_t *url_buf;
2508 url_buf = svn_stringbuf_create(path, pool);
2510 svn_path_remove_components(url_buf,
2511 svn_path_component_count(relative_path));
2513 /* Now recreate the root_url. */
2514 session->repos_root = session->session_url;
2515 session->repos_root.path =
2516 (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
2517 session->repos_root_str =
2518 svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
2519 &session->repos_root, 0),
2523 /* Store the repository UUID in the cache. */
2526 session->uuid = apr_pstrdup(session->pool, uuid);
2529 return SVN_NO_ERROR;
2533 svn_ra_serf__get_relative_path(const char **rel_path,
2534 const char *orig_path,
2535 svn_ra_serf__session_t *session,
2536 svn_ra_serf__connection_t *conn,
2539 const char *decoded_root, *decoded_orig;
2541 if (! session->repos_root.path)
2543 const char *vcc_url;
2545 /* This should only happen if we haven't detected HTTP v2
2546 support from the server. */
2547 assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
2549 /* We don't actually care about the VCC_URL, but this API
2550 promises to populate the session's root-url cache, and that's
2551 what we really want. */
2552 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
2553 conn ? conn : session->conns[0],
2557 decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
2558 decoded_orig = svn_path_uri_decode(orig_path, pool);
2559 *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
2560 SVN_ERR_ASSERT(*rel_path != NULL);
2561 return SVN_NO_ERROR;
2565 svn_ra_serf__report_resource(const char **report_target,
2566 svn_ra_serf__session_t *session,
2567 svn_ra_serf__connection_t *conn,
2570 /* If we have HTTP v2 support, we want to report against the 'me'
2572 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2573 *report_target = apr_pstrdup(pool, session->me_resource);
2575 /* Otherwise, we'll use the default VCC. */
2577 SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool));
2579 return SVN_NO_ERROR;
2583 svn_ra_serf__error_on_status(serf_status_line sline,
2585 const char *location)
2592 return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
2594 ? _("Repository moved permanently to '%s';"
2596 : _("Repository moved temporarily to '%s';"
2597 " please relocate"), location);
2599 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
2600 _("Access to '%s' forbidden"), path);
2603 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
2604 _("'%s' path not found"), path);
2606 return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
2607 _("'%s': no lock token available"), path);
2610 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2611 _("DAV request failed: 411 Content length required. The "
2612 "server or an intermediate proxy does not accept "
2613 "chunked encoding. Try setting 'http-chunked-requests' "
2614 "to 'auto' or 'no' in your client configuration."));
2616 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2617 _("The requested feature is not supported by "
2621 if (sline.code >= 300)
2622 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2623 _("Unexpected HTTP status %d '%s' on '%s'\n"),
2624 sline.code, sline.reason, path);
2626 return SVN_NO_ERROR;
2630 svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
2631 svn_delta_shim_callbacks_t *callbacks)
2633 svn_ra_serf__session_t *session = ra_session->priv;
2635 session->shim_callbacks = callbacks;
2636 return SVN_NO_ERROR;
2640 /* Conforms to Expat's XML_StartElementHandler */
2642 expat_start(void *userData, const char *raw_name, const char **attrs)
2644 struct expat_ctx_t *ectx = userData;
2646 if (ectx->inner_error != NULL)
2649 ectx->inner_error = svn_error_trace(
2650 svn_ra_serf__xml_cb_start(ectx->xmlctx,
2653 #ifdef EXPAT_HAS_STOPPARSER
2654 if (ectx->inner_error)
2655 (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2660 /* Conforms to Expat's XML_EndElementHandler */
2662 expat_end(void *userData, const char *raw_name)
2664 struct expat_ctx_t *ectx = userData;
2666 if (ectx->inner_error != NULL)
2669 ectx->inner_error = svn_error_trace(
2670 svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name));
2672 #ifdef EXPAT_HAS_STOPPARSER
2673 if (ectx->inner_error)
2674 (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2679 /* Conforms to Expat's XML_CharacterDataHandler */
2681 expat_cdata(void *userData, const char *data, int len)
2683 struct expat_ctx_t *ectx = userData;
2685 if (ectx->inner_error != NULL)
2688 ectx->inner_error = svn_error_trace(
2689 svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len));
2691 #ifdef EXPAT_HAS_STOPPARSER
2692 if (ectx->inner_error)
2693 (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2698 /* Implements svn_ra_serf__response_handler_t */
2699 static svn_error_t *
2700 expat_response_handler(serf_request_t *request,
2701 serf_bucket_t *response,
2703 apr_pool_t *scratch_pool)
2705 struct expat_ctx_t *ectx = baton;
2709 ectx->parser = XML_ParserCreate(NULL);
2710 apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
2711 xml_parser_cleanup, apr_pool_cleanup_null);
2712 XML_SetUserData(ectx->parser, ectx);
2713 XML_SetElementHandler(ectx->parser, expat_start, expat_end);
2714 XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
2717 /* ### TODO: sline.code < 200 should really be handled by the core */
2718 if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300))
2720 /* By deferring to expect_empty_body(), it will make a choice on
2721 how to handle the body. Whatever the decision, the core handler
2722 will take over, and we will not be called again. */
2723 return svn_error_trace(svn_ra_serf__expect_empty_body(
2724 request, response, ectx->handler,
2730 apr_status_t status;
2735 status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
2736 if (SERF_BUCKET_READ_ERROR(status))
2737 return svn_ra_serf__wrap_err(status, NULL);
2740 /* ### move restart/skip into the core handler */
2741 ectx->handler->read_size += len;
2744 /* ### move PAUSED behavior to a new response handler that can feed
2745 ### an inner handler, or can pause for a while. */
2747 /* ### should we have an IGNORE_ERRORS flag like the v1 parser? */
2749 expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */);
2751 /* We need to check INNER_ERROR first. This is an error from the
2752 callbacks that has been "dropped off" for us to retrieve. On
2753 current Expat parsers, we stop the parser when an error occurs,
2754 so we want to ignore EXPAT_STATUS (which reports the stoppage).
2756 If an error is not present, THEN we go ahead and look for parsing
2758 if (ectx->inner_error)
2760 apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
2761 xml_parser_cleanup);
2762 return svn_error_trace(ectx->inner_error);
2764 if (expat_status == XML_STATUS_ERROR)
2765 return svn_error_createf(SVN_ERR_XML_MALFORMED,
2767 _("The %s response contains invalid XML"
2769 ectx->handler->method,
2770 ectx->handler->sline.code,
2771 ectx->handler->sline.reason);
2773 /* The parsing went fine. What has the bucket told us? */
2775 if (APR_STATUS_IS_EOF(status))
2777 /* Tell expat we've reached the end of the content. Ignore the
2778 return status. We just don't care. */
2779 (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */);
2781 svn_ra_serf__xml_context_destroy(ectx->xmlctx);
2782 apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
2783 xml_parser_cleanup);
2785 /* ### should check XMLCTX to see if it has returned to the
2786 ### INITIAL state. we may have ended early... */
2789 if (status && !SERF_BUCKET_READ_ERROR(status))
2791 return svn_ra_serf__wrap_err(status, NULL);
2799 svn_ra_serf__handler_t *
2800 svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
2801 apr_pool_t *result_pool)
2803 svn_ra_serf__handler_t *handler;
2804 struct expat_ctx_t *ectx;
2806 ectx = apr_pcalloc(result_pool, sizeof(*ectx));
2807 ectx->xmlctx = xmlctx;
2808 ectx->parser = NULL;
2809 ectx->cleanup_pool = result_pool;
2812 handler = apr_pcalloc(result_pool, sizeof(*handler));
2813 handler->handler_pool = result_pool;
2814 handler->response_handler = expat_response_handler;
2815 handler->response_baton = ectx;
2817 ectx->handler = handler;