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"
55 /* Fix for older expat 1.95.x's that do not define
56 * XML_STATUS_OK/XML_STATUS_ERROR
59 #define XML_STATUS_OK 1
60 #define XML_STATUS_ERROR 0
63 #ifndef XML_VERSION_AT_LEAST
64 #define XML_VERSION_AT_LEAST(major,minor,patch) \
65 (((major) < XML_MAJOR_VERSION) \
66 || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \
67 || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
68 (patch) <= XML_MICRO_VERSION))
69 #endif /* APR_VERSION_AT_LEAST */
71 #if XML_VERSION_AT_LEAST(1, 95, 8)
72 #define EXPAT_HAS_STOPPARSER
75 /* Read/write chunks of this size into the spillbuf. */
76 #define PARSE_CHUNK_SIZE 8000
78 /* We will store one megabyte in memory, before switching to store content
79 into a temporary file. */
80 #define SPILL_SIZE 1000000
83 /* This structure records pending data for the parser in memory blocks,
84 and possibly into a temporary file if "too much" content arrives. */
85 struct svn_ra_serf__pending_t {
86 /* The spillbuf where we record the pending data. */
89 /* This flag is set when the network has reached EOF. The PENDING
90 processing can then properly detect when parsing has completed. */
91 svn_boolean_t network_eof;
94 #define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \
95 && svn_spillbuf__get_size((p)->buf) != 0)
99 svn_ra_serf__xml_context_t *xmlctx;
101 svn_ra_serf__handler_t *handler;
103 svn_error_t *inner_error;
105 /* Do not use this pool for allocation. It is merely recorded for running
106 the cleanup handler. */
107 apr_pool_t *cleanup_pool;
111 static const apr_uint32_t serf_failure_map[][2] =
113 { SERF_SSL_CERT_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID },
114 { SERF_SSL_CERT_EXPIRED, SVN_AUTH_SSL_EXPIRED },
115 { SERF_SSL_CERT_SELF_SIGNED, SVN_AUTH_SSL_UNKNOWNCA },
116 { SERF_SSL_CERT_UNKNOWNCA, SVN_AUTH_SSL_UNKNOWNCA }
119 /* Return a Subversion failure mask based on FAILURES, a serf SSL
120 failure mask. If anything in FAILURES is not directly mappable to
121 Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */
123 ssl_convert_serf_failures(int failures)
125 apr_uint32_t svn_failures = 0;
128 for (i = 0; i < sizeof(serf_failure_map) / (2 * sizeof(apr_uint32_t)); ++i)
130 if (failures & serf_failure_map[i][0])
132 svn_failures |= serf_failure_map[i][1];
133 failures &= ~serf_failure_map[i][0];
137 /* Map any remaining failure bits to our OTHER bit. */
140 svn_failures |= SVN_AUTH_SSL_OTHER;
148 save_error(svn_ra_serf__session_t *session,
151 if (err || session->pending_error)
153 session->pending_error = svn_error_compose_create(
154 session->pending_error,
156 return session->pending_error->apr_err;
163 /* Construct the realmstring, e.g. https://svn.collab.net:443. */
165 construct_realm(svn_ra_serf__session_t *session,
171 if (session->session_url.port_str)
173 port = session->session_url.port;
177 port = apr_uri_port_of_scheme(session->session_url.scheme);
180 realm = apr_psprintf(pool, "%s://%s:%d",
181 session->session_url.scheme,
182 session->session_url.hostname,
188 /* Convert a hash table containing the fields (as documented in X.509) of an
189 organisation to a string ORG, allocated in POOL. ORG is as returned by
190 serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */
192 convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
194 return apr_psprintf(pool, "%s, %s, %s, %s, %s (%s)",
195 (char*)svn_hash_gets(org, "OU"),
196 (char*)svn_hash_gets(org, "O"),
197 (char*)svn_hash_gets(org, "L"),
198 (char*)svn_hash_gets(org, "ST"),
199 (char*)svn_hash_gets(org, "C"),
200 (char*)svn_hash_gets(org, "E"));
203 /* This function is called on receiving a ssl certificate of a server when
204 opening a https connection. It allows Subversion to override the initial
205 validation done by serf.
206 Serf provides us the @a baton as provided in the call to
207 serf_ssl_server_cert_callback_set. The result of serf's initial validation
208 of the certificate @a CERT is returned as a bitmask in FAILURES. */
210 ssl_server_cert(void *baton, int failures,
211 const serf_ssl_certificate_t *cert,
212 apr_pool_t *scratch_pool)
214 svn_ra_serf__connection_t *conn = baton;
215 svn_auth_ssl_server_cert_info_t cert_info;
216 svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
217 svn_auth_iterstate_t *state;
218 const char *realmstring;
219 apr_uint32_t svn_failures;
220 apr_hash_t *issuer, *subject, *serf_cert;
221 apr_array_header_t *san;
223 int found_matching_hostname = 0;
225 /* Implicitly approve any non-server certs. */
226 if (serf_ssl_cert_depth(cert) > 0)
229 conn->server_cert_failures |= ssl_convert_serf_failures(failures);
233 /* Extract the info from the certificate */
234 subject = serf_ssl_cert_subject(cert, scratch_pool);
235 issuer = serf_ssl_cert_issuer(cert, scratch_pool);
236 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
238 cert_info.hostname = svn_hash_gets(subject, "CN");
239 san = svn_hash_gets(serf_cert, "subjectAltName");
240 cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
241 if (! cert_info.fingerprint)
242 cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
243 cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
244 if (! cert_info.valid_from)
245 cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
246 cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
247 if (! cert_info.valid_until)
248 cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
249 cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
250 cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
252 svn_failures = (ssl_convert_serf_failures(failures)
253 | conn->server_cert_failures);
255 /* Try to find matching server name via subjectAltName first... */
258 for (i = 0; i < san->nelts; i++) {
259 char *s = APR_ARRAY_IDX(san, i, char*);
260 if (apr_fnmatch(s, conn->session->session_url.hostname,
261 APR_FNM_PERIOD) == APR_SUCCESS) {
262 found_matching_hostname = 1;
263 cert_info.hostname = s;
269 /* Match server certificate CN with the hostname of the server */
270 if (!found_matching_hostname && cert_info.hostname)
272 if (apr_fnmatch(cert_info.hostname, conn->session->session_url.hostname,
273 APR_FNM_PERIOD) == APR_FNM_NOMATCH)
275 svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
279 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
280 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
283 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
284 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
287 realmstring = construct_realm(conn->session, conn->session->pool);
289 SVN_ERR(svn_auth_first_credentials(&creds, &state,
290 SVN_AUTH_CRED_SSL_SERVER_TRUST,
292 conn->session->wc_callbacks->auth_baton,
296 server_creds = creds;
297 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
300 svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
301 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
304 return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL, NULL);
309 /* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
311 ssl_server_cert_cb(void *baton, int failures,
312 const serf_ssl_certificate_t *cert)
314 svn_ra_serf__connection_t *conn = baton;
315 svn_ra_serf__session_t *session = conn->session;
319 subpool = svn_pool_create(session->pool);
320 err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
321 svn_pool_destroy(subpool);
323 return save_error(session, err);
327 load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
330 apr_array_header_t *files = svn_cstring_split(authorities, ";",
331 TRUE /* chop_whitespace */,
335 for (i = 0; i < files->nelts; ++i)
337 const char *file = APR_ARRAY_IDX(files, i, const char *);
338 serf_ssl_certificate_t *ca_cert;
339 apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
341 if (status == APR_SUCCESS)
342 status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
344 if (status != APR_SUCCESS)
346 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
347 _("Invalid config: unable to load certificate file '%s'"),
348 svn_dirent_local_style(file, pool));
356 conn_setup(apr_socket_t *sock,
357 serf_bucket_t **read_bkt,
358 serf_bucket_t **write_bkt,
362 svn_ra_serf__connection_t *conn = baton;
364 *read_bkt = serf_context_bucket_socket_create(conn->session->context,
365 sock, conn->bkt_alloc);
367 if (conn->session->using_ssl)
370 *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
372 if (!conn->ssl_context)
374 conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
376 serf_ssl_set_hostname(conn->ssl_context,
377 conn->session->session_url.hostname);
379 serf_ssl_client_cert_provider_set(conn->ssl_context,
380 svn_ra_serf__handle_client_cert,
381 conn, conn->session->pool);
382 serf_ssl_client_cert_password_set(conn->ssl_context,
383 svn_ra_serf__handle_client_cert_pw,
384 conn, conn->session->pool);
385 serf_ssl_server_cert_callback_set(conn->ssl_context,
389 /* See if the user wants us to trust "default" openssl CAs. */
390 if (conn->session->trust_default_ca)
392 serf_ssl_use_default_certificates(conn->ssl_context);
394 /* Are there custom CAs to load? */
395 if (conn->session->ssl_authorities)
397 SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
398 conn->session->pool));
405 *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
414 /* svn_ra_serf__conn_setup is a callback for serf. This function
415 creates a read bucket and will wrap the write bucket if SSL
418 svn_ra_serf__conn_setup(apr_socket_t *sock,
419 serf_bucket_t **read_bkt,
420 serf_bucket_t **write_bkt,
424 svn_ra_serf__connection_t *conn = baton;
425 svn_ra_serf__session_t *session = conn->session;
428 err = svn_error_trace(conn_setup(sock,
433 return save_error(session, err);
437 /* Our default serf response acceptor. */
438 static serf_bucket_t *
439 accept_response(serf_request_t *request,
440 serf_bucket_t *stream,
441 void *acceptor_baton,
445 serf_bucket_alloc_t *bkt_alloc;
447 bkt_alloc = serf_request_get_alloc(request);
448 c = serf_bucket_barrier_create(stream, bkt_alloc);
450 return serf_bucket_response_create(c, bkt_alloc);
454 /* Custom response acceptor for HEAD requests. */
455 static serf_bucket_t *
456 accept_head(serf_request_t *request,
457 serf_bucket_t *stream,
458 void *acceptor_baton,
461 serf_bucket_t *response;
463 response = accept_response(request, stream, acceptor_baton, pool);
465 /* We know we shouldn't get a response body. */
466 serf_bucket_response_set_head(response);
472 connection_closed(svn_ra_serf__connection_t *conn,
478 return svn_error_wrap_apr(why, NULL);
481 if (conn->session->using_ssl)
482 conn->ssl_context = NULL;
488 svn_ra_serf__conn_closed(serf_connection_t *conn,
493 svn_ra_serf__connection_t *ra_conn = closed_baton;
496 err = svn_error_trace(connection_closed(ra_conn, why, pool));
498 (void) save_error(ra_conn->session, err);
502 /* Implementation of svn_ra_serf__handle_client_cert */
504 handle_client_cert(void *data,
505 const char **cert_path,
508 svn_ra_serf__connection_t *conn = data;
509 svn_ra_serf__session_t *session = conn->session;
515 realm = construct_realm(session, session->pool);
517 if (!conn->ssl_client_auth_state)
519 SVN_ERR(svn_auth_first_credentials(&creds,
520 &conn->ssl_client_auth_state,
521 SVN_AUTH_CRED_SSL_CLIENT_CERT,
523 session->wc_callbacks->auth_baton,
528 SVN_ERR(svn_auth_next_credentials(&creds,
529 conn->ssl_client_auth_state,
535 svn_auth_cred_ssl_client_cert_t *client_creds;
536 client_creds = creds;
537 *cert_path = client_creds->cert_file;
543 /* Implements serf_ssl_need_client_cert_t for handle_client_cert */
544 apr_status_t svn_ra_serf__handle_client_cert(void *data,
545 const char **cert_path)
547 svn_ra_serf__connection_t *conn = data;
548 svn_ra_serf__session_t *session = conn->session;
551 err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
553 return save_error(session, err);
556 /* Implementation for svn_ra_serf__handle_client_cert_pw */
558 handle_client_cert_pw(void *data,
559 const char *cert_path,
560 const char **password,
563 svn_ra_serf__connection_t *conn = data;
564 svn_ra_serf__session_t *session = conn->session;
569 if (!conn->ssl_client_pw_auth_state)
571 SVN_ERR(svn_auth_first_credentials(&creds,
572 &conn->ssl_client_pw_auth_state,
573 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
575 session->wc_callbacks->auth_baton,
580 SVN_ERR(svn_auth_next_credentials(&creds,
581 conn->ssl_client_pw_auth_state,
587 svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
589 *password = pw_creds->password;
595 /* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
596 apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
597 const char *cert_path,
598 const char **password)
600 svn_ra_serf__connection_t *conn = data;
601 svn_ra_serf__session_t *session = conn->session;
604 err = svn_error_trace(handle_client_cert_pw(data,
609 return save_error(session, err);
614 * Given a REQUEST on connection CONN, construct a request bucket for it,
615 * returning the bucket in *REQ_BKT.
617 * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
618 * corresponds to the new request.
620 * The request will be METHOD at URL.
622 * If BODY_BKT is not-NULL, it will be sent as the request body.
624 * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
626 * REQUEST_POOL should live for the duration of the request. Serf will
627 * construct this and provide it to the request_setup callback, so we
628 * should just use that one.
631 setup_serf_req(serf_request_t *request,
632 serf_bucket_t **req_bkt,
633 serf_bucket_t **hdrs_bkt,
634 svn_ra_serf__session_t *session,
635 const char *method, const char *url,
636 serf_bucket_t *body_bkt, const char *content_type,
637 const char *accept_encoding,
638 apr_pool_t *request_pool,
639 apr_pool_t *scratch_pool)
641 serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
644 svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
646 if (set_CL && body_bkt != NULL)
648 /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
649 it speaks HTTP/1.1 (and thus, chunked requests), or because the
650 server actually responded as only supporting HTTP/1.0.
652 We'll take the existing body_bkt, spool it into a spillbuf, and
653 then wrap a bucket around that spillbuf. The spillbuf will give
654 us the Content-Length value. */
655 SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
658 /* Destroy original bucket since it content is already copied
660 serf_bucket_destroy(body_bkt);
662 body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
667 /* Create a request bucket. Note that this sucker is kind enough to
668 add a "Host" header for us. */
669 *req_bkt = serf_request_bucket_request_create(request, method, url,
670 body_bkt, allocator);
672 /* Set the Content-Length value. This will also trigger an HTTP/1.0
673 request (rather than the default chunked request). */
676 if (body_bkt == NULL)
677 serf_bucket_request_set_CL(*req_bkt, 0);
679 serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
682 *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
684 /* We use serf_bucket_headers_setn() because the USERAGENT has a
685 lifetime longer than this bucket. Thus, there is no need to copy
686 the header values. */
687 serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
691 serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
696 serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
701 serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
704 /* These headers need to be sent with every request; see issue #3255
705 ("mod_dav_svn does not pass client capabilities to start-commit
707 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
708 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
709 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
715 svn_ra_serf__context_run_wait(svn_boolean_t *done,
716 svn_ra_serf__session_t *sess,
717 apr_pool_t *scratch_pool)
719 apr_pool_t *iterpool;
720 apr_interval_time_t waittime_left = sess->timeout;
722 assert(sess->pending_error == SVN_NO_ERROR);
724 iterpool = svn_pool_create(scratch_pool);
731 svn_pool_clear(iterpool);
733 if (sess->cancel_func)
734 SVN_ERR((*sess->cancel_func)(sess->cancel_baton));
736 status = serf_context_run(sess->context,
737 SVN_RA_SERF__CONTEXT_RUN_DURATION,
740 err = sess->pending_error;
741 sess->pending_error = SVN_NO_ERROR;
743 /* If the context duration timeout is up, we'll subtract that
744 duration from the total time alloted for such things. If
745 there's no time left, we fail with a message indicating that
746 the connection timed out. */
747 if (APR_STATUS_IS_TIMEUP(status))
749 svn_error_clear(err);
755 if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
757 waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
761 return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
762 _("Connection timed out"));
768 waittime_left = sess->timeout;
774 if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
776 /* apr can't translate subversion errors to text */
777 SVN_ERR_W(svn_error_create(status, NULL, NULL),
778 _("Error running context"));
781 return svn_ra_serf__wrap_err(status, _("Error running context"));
784 /* Debugging purposes only! */
785 for (i = 0; i < sess->num_conns; i++)
787 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
790 svn_pool_destroy(iterpool);
797 svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
798 apr_pool_t *scratch_pool)
802 /* Create a serf request based on HANDLER. */
803 svn_ra_serf__request_create(handler);
805 /* Wait until the response logic marks its DONE status. */
806 err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
808 if (handler->server_error)
810 err = svn_error_compose_create(err, handler->server_error->error);
811 handler->server_error = NULL;
814 return svn_error_trace(err);
819 * Expat callback invoked on a start element tag for an error response.
822 start_error(svn_ra_serf__xml_parser_t *parser,
823 svn_ra_serf__dav_props_t name,
825 apr_pool_t *scratch_pool)
827 svn_ra_serf__server_error_t *ctx = parser->user_data;
829 if (!ctx->in_error &&
830 strcmp(name.namespace, "DAV:") == 0 &&
831 strcmp(name.name, "error") == 0)
833 ctx->in_error = TRUE;
835 else if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
837 const char *err_code;
839 err_code = svn_xml_get_attr_value("errcode", attrs);
844 SVN_ERR(svn_cstring_atoi64(&val, err_code));
845 ctx->error->apr_err = (apr_status_t)val;
848 /* If there's no error code provided, or if the provided code is
849 0 (which can happen sometimes depending on how the error is
850 constructed on the server-side), just pick a generic error
852 if (! ctx->error->apr_err)
854 ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
857 /* Start collecting cdata. */
858 svn_stringbuf_setempty(ctx->cdata);
859 ctx->collect_cdata = TRUE;
866 * Expat callback invoked on an end element tag for a PROPFIND response.
869 end_error(svn_ra_serf__xml_parser_t *parser,
870 svn_ra_serf__dav_props_t name,
871 apr_pool_t *scratch_pool)
873 svn_ra_serf__server_error_t *ctx = parser->user_data;
876 strcmp(name.namespace, "DAV:") == 0 &&
877 strcmp(name.name, "error") == 0)
879 ctx->in_error = FALSE;
881 if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
883 /* On the server dav_error_response_tag() will add a leading
884 and trailing newline if DEBUG_CR is defined in mod_dav.h,
885 so remove any such characters here. */
886 svn_stringbuf_strip_whitespace(ctx->cdata);
888 ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
890 ctx->collect_cdata = FALSE;
897 * Expat callback invoked on CDATA elements in an error response.
899 * This callback can be called multiple times.
902 cdata_error(svn_ra_serf__xml_parser_t *parser,
905 apr_pool_t *scratch_pool)
907 svn_ra_serf__server_error_t *ctx = parser->user_data;
909 if (ctx->collect_cdata)
911 svn_stringbuf_appendbytes(ctx->cdata, data, len);
919 drain_bucket(serf_bucket_t *bucket)
921 /* Read whatever is in the bucket, and just drop it. */
928 status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
935 static svn_ra_serf__server_error_t *
936 begin_error_parsing(svn_ra_serf__xml_start_element_t start,
937 svn_ra_serf__xml_end_element_t end,
938 svn_ra_serf__xml_cdata_chunk_handler_t cdata,
939 apr_pool_t *result_pool)
941 svn_ra_serf__server_error_t *server_err;
943 server_err = apr_pcalloc(result_pool, sizeof(*server_err));
944 server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
945 server_err->contains_precondition_error = FALSE;
946 server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool);
947 server_err->collect_cdata = FALSE;
948 server_err->parser.pool = server_err->error->pool;
949 server_err->parser.user_data = server_err;
950 server_err->parser.start = start;
951 server_err->parser.end = end;
952 server_err->parser.cdata = cdata;
953 server_err->parser.ignore_errors = TRUE;
958 /* Implements svn_ra_serf__response_handler_t */
960 svn_ra_serf__handle_discard_body(serf_request_t *request,
961 serf_bucket_t *response,
967 status = drain_bucket(response);
969 return svn_ra_serf__wrap_err(status, NULL);
975 svn_ra_serf__response_discard_handler(serf_request_t *request,
976 serf_bucket_t *response,
980 return drain_bucket(response);
984 /* Return the value of the RESPONSE's Location header if any, or NULL
987 response_get_location(serf_bucket_t *response,
988 const char *base_url,
989 apr_pool_t *result_pool,
990 apr_pool_t *scratch_pool)
992 serf_bucket_t *headers;
993 const char *location;
995 headers = serf_bucket_response_get_headers(response);
996 location = serf_bucket_headers_get(headers, "Location");
997 if (location == NULL)
1000 /* The RFCs say we should have received a full url in LOCATION, but
1001 older apache versions and many custom web handlers just return a
1002 relative path here...
1004 And we can't trust anything because it is network data.
1006 if (*location == '/')
1009 apr_status_t status;
1011 status = apr_uri_parse(scratch_pool, base_url, &uri);
1013 if (status != APR_SUCCESS)
1016 /* Replace the path path with what we got */
1017 uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
1019 /* And make APR produce a proper full url for us */
1020 location = apr_uri_unparse(scratch_pool, &uri, 0);
1022 /* Fall through to ensure our canonicalization rules */
1024 else if (!svn_path_is_url(location))
1026 return NULL; /* Any other formats we should support? */
1029 return svn_uri_canonicalize(location, result_pool);
1033 /* Implements svn_ra_serf__response_handler_t */
1035 svn_ra_serf__expect_empty_body(serf_request_t *request,
1036 serf_bucket_t *response,
1038 apr_pool_t *scratch_pool)
1040 svn_ra_serf__handler_t *handler = baton;
1041 serf_bucket_t *hdrs;
1044 /* This function is just like handle_multistatus_only() except for the
1045 XML parsing callbacks. We want to look for the human-readable element. */
1047 /* We should see this just once, in order to initialize SERVER_ERROR.
1048 At that point, the core error processing will take over. If we choose
1049 not to parse an error, then we'll never return here (because we
1050 change the response handler). */
1051 SVN_ERR_ASSERT(handler->server_error == NULL);
1053 hdrs = serf_bucket_response_get_headers(response);
1054 val = serf_bucket_headers_get(hdrs, "Content-Type");
1055 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1057 svn_ra_serf__server_error_t *server_err;
1059 server_err = begin_error_parsing(start_error, end_error, cdata_error,
1060 handler->handler_pool);
1062 /* Get the parser to set our DONE flag. */
1063 server_err->parser.done = &handler->done;
1065 handler->server_error = server_err;
1069 /* The body was not text/xml, so we don't know what to do with it.
1070 Toss anything that arrives. */
1071 handler->discard_body = TRUE;
1074 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1075 to call the response handler again. That will start up the XML parsing,
1076 or it will be dropped on the floor (per the decision above). */
1077 return SVN_NO_ERROR;
1081 /* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric
1082 status code into *STATUS_CODE_OUT. Ignores leading whitespace. */
1083 static svn_error_t *
1084 parse_dav_status(int *status_code_out, svn_stringbuf_t *buf,
1085 apr_pool_t *scratch_pool)
1090 svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool);
1092 svn_stringbuf_strip_whitespace(temp_buf);
1093 token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status);
1095 token = apr_strtok(NULL, " \t\r\n", &tok_status);
1097 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1098 _("Malformed DAV:status CDATA '%s'"),
1100 err = svn_cstring_atoi(status_code_out, token);
1102 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
1103 _("Malformed DAV:status CDATA '%s'"),
1106 return SVN_NO_ERROR;
1110 * Expat callback invoked on a start element tag for a 207 response.
1112 static svn_error_t *
1113 start_207(svn_ra_serf__xml_parser_t *parser,
1114 svn_ra_serf__dav_props_t name,
1116 apr_pool_t *scratch_pool)
1118 svn_ra_serf__server_error_t *ctx = parser->user_data;
1120 if (!ctx->in_error &&
1121 strcmp(name.namespace, "DAV:") == 0 &&
1122 strcmp(name.name, "multistatus") == 0)
1124 ctx->in_error = TRUE;
1126 else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
1128 /* Start collecting cdata. */
1129 svn_stringbuf_setempty(ctx->cdata);
1130 ctx->collect_cdata = TRUE;
1132 else if (ctx->in_error &&
1133 strcmp(name.namespace, "DAV:") == 0 &&
1134 strcmp(name.name, "status") == 0)
1136 /* Start collecting cdata. */
1137 svn_stringbuf_setempty(ctx->cdata);
1138 ctx->collect_cdata = TRUE;
1141 return SVN_NO_ERROR;
1145 * Expat callback invoked on an end element tag for a 207 response.
1147 static svn_error_t *
1148 end_207(svn_ra_serf__xml_parser_t *parser,
1149 svn_ra_serf__dav_props_t name,
1150 apr_pool_t *scratch_pool)
1152 svn_ra_serf__server_error_t *ctx = parser->user_data;
1154 if (ctx->in_error &&
1155 strcmp(name.namespace, "DAV:") == 0 &&
1156 strcmp(name.name, "multistatus") == 0)
1158 ctx->in_error = FALSE;
1160 if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
1162 /* Remove leading newline added by DEBUG_CR on server */
1163 svn_stringbuf_strip_whitespace(ctx->cdata);
1165 ctx->collect_cdata = FALSE;
1166 ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
1168 if (ctx->contains_precondition_error)
1169 ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
1171 ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
1173 else if (ctx->in_error &&
1174 strcmp(name.namespace, "DAV:") == 0 &&
1175 strcmp(name.name, "status") == 0)
1179 ctx->collect_cdata = FALSE;
1181 SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool));
1182 if (status_code == 412)
1183 ctx->contains_precondition_error = TRUE;
1186 return SVN_NO_ERROR;
1190 * Expat callback invoked on CDATA elements in a 207 response.
1192 * This callback can be called multiple times.
1194 static svn_error_t *
1195 cdata_207(svn_ra_serf__xml_parser_t *parser,
1198 apr_pool_t *scratch_pool)
1200 svn_ra_serf__server_error_t *ctx = parser->user_data;
1202 if (ctx->collect_cdata)
1204 svn_stringbuf_appendbytes(ctx->cdata, data, len);
1207 return SVN_NO_ERROR;
1210 /* Implements svn_ra_serf__response_handler_t */
1212 svn_ra_serf__handle_multistatus_only(serf_request_t *request,
1213 serf_bucket_t *response,
1215 apr_pool_t *scratch_pool)
1217 svn_ra_serf__handler_t *handler = baton;
1219 /* This function is just like expect_empty_body() except for the
1220 XML parsing callbacks. We are looking for very limited pieces of
1221 the multistatus response. */
1223 /* We should see this just once, in order to initialize SERVER_ERROR.
1224 At that point, the core error processing will take over. If we choose
1225 not to parse an error, then we'll never return here (because we
1226 change the response handler). */
1227 SVN_ERR_ASSERT(handler->server_error == NULL);
1230 serf_bucket_t *hdrs;
1233 hdrs = serf_bucket_response_get_headers(response);
1234 val = serf_bucket_headers_get(hdrs, "Content-Type");
1235 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1237 svn_ra_serf__server_error_t *server_err;
1239 server_err = begin_error_parsing(start_207, end_207, cdata_207,
1240 handler->handler_pool);
1242 /* Get the parser to set our DONE flag. */
1243 server_err->parser.done = &handler->done;
1245 handler->server_error = server_err;
1249 /* The body was not text/xml, so we don't know what to do with it.
1250 Toss anything that arrives. */
1251 handler->discard_body = TRUE;
1255 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1256 to call the response handler again. That will start up the XML parsing,
1257 or it will be dropped on the floor (per the decision above). */
1258 return SVN_NO_ERROR;
1262 /* Conforms to Expat's XML_StartElementHandler */
1264 start_xml(void *userData, const char *raw_name, const char **attrs)
1266 svn_ra_serf__xml_parser_t *parser = userData;
1267 svn_ra_serf__dav_props_t name;
1268 apr_pool_t *scratch_pool;
1275 svn_ra_serf__xml_push_state(parser, 0);
1277 /* ### get a real scratch_pool */
1278 scratch_pool = parser->state->pool;
1280 svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool);
1282 svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
1284 err = parser->start(parser, name, attrs, scratch_pool);
1285 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1286 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1288 parser->error = err;
1292 /* Conforms to Expat's XML_EndElementHandler */
1294 end_xml(void *userData, const char *raw_name)
1296 svn_ra_serf__xml_parser_t *parser = userData;
1297 svn_ra_serf__dav_props_t name;
1299 apr_pool_t *scratch_pool;
1304 /* ### get a real scratch_pool */
1305 scratch_pool = parser->state->pool;
1307 svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
1309 err = parser->end(parser, name, scratch_pool);
1310 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1311 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1313 parser->error = err;
1317 /* Conforms to Expat's XML_CharacterDataHandler */
1319 cdata_xml(void *userData, const char *data, int len)
1321 svn_ra_serf__xml_parser_t *parser = userData;
1323 apr_pool_t *scratch_pool;
1329 svn_ra_serf__xml_push_state(parser, 0);
1331 /* ### get a real scratch_pool */
1332 scratch_pool = parser->state->pool;
1334 err = parser->cdata(parser, data, len, scratch_pool);
1335 if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1336 err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1338 parser->error = err;
1341 /* Flip the requisite bits in CTX to indicate that processing of the
1342 response is complete, adding the current "done item" to the list of
1345 add_done_item(svn_ra_serf__xml_parser_t *ctx)
1347 /* Make sure we don't add to DONE_LIST twice. */
1353 ctx->done_item->data = ctx->user_data;
1354 ctx->done_item->next = *ctx->done_list;
1355 *ctx->done_list = ctx->done_item;
1361 static svn_error_t *
1362 write_to_pending(svn_ra_serf__xml_parser_t *ctx,
1365 apr_pool_t *scratch_pool)
1367 if (ctx->pending == NULL)
1369 ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
1370 ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE,
1375 /* Copy the data into one or more chunks in the spill buffer. */
1376 return svn_error_trace(svn_spillbuf__write(ctx->pending->buf,
1382 static svn_error_t *
1383 inject_to_parser(svn_ra_serf__xml_parser_t *ctx,
1386 const serf_status_line *sl)
1390 xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0);
1391 if (xml_status == XML_STATUS_ERROR && !ctx->ignore_errors)
1394 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1395 _("XML parsing failed"));
1397 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1398 _("XML parsing failed: (%d %s)"),
1399 sl->code, sl->reason);
1402 if (ctx->error && !ctx->ignore_errors)
1403 return svn_error_trace(ctx->error);
1405 return SVN_NO_ERROR;
1408 /* Apr pool cleanup handler to release an XML_Parser in success and error
1411 xml_parser_cleanup(void *baton)
1413 XML_Parser *xmlp = baton;
1417 (void) XML_ParserFree(*xmlp);
1424 /* Limit the amount of pending content to parse at once to < 100KB per
1425 iteration. This number is chosen somewhat arbitrarely. Making it lower
1426 will have a drastical negative impact on performance, whereas increasing it
1427 increases the risk for connection timeouts.
1429 #define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5
1432 svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
1433 svn_boolean_t *network_eof,
1434 apr_pool_t *scratch_pool)
1436 svn_boolean_t pending_empty = FALSE;
1437 apr_size_t cur_read = 0;
1439 /* Fast path exit: already paused, nothing to do, or already done. */
1440 if (parser->paused || parser->pending == NULL || *parser->done)
1442 *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
1443 return SVN_NO_ERROR;
1446 /* Parsing the pending conten in the spillbuf will result in many disc i/o
1447 operations. This can be so slow that we don't run the network event
1448 processing loop often enough, resulting in timed out connections.
1450 So we limit the amounts of bytes parsed per iteration.
1452 while (cur_read < PENDING_TO_PARSE)
1457 /* Get a block of content, stopping the loop when we run out. */
1458 SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf,
1462 /* Inject the content into the XML parser. */
1463 SVN_ERR(inject_to_parser(parser, data, len, NULL));
1465 /* If the XML parsing callbacks paused us, then we're done for now. */
1473 /* The buffer is empty. */
1474 pending_empty = TRUE;
1479 /* If the PENDING structures are empty *and* we consumed all content from
1480 the network, then we're completely done with the parsing. */
1481 if (pending_empty &&
1482 parser->pending->network_eof)
1484 SVN_ERR_ASSERT(parser->xmlp != NULL);
1486 /* Tell the parser that no more content will be parsed. Ignore the
1487 return status. We just don't care. */
1488 (void) XML_Parse(parser->xmlp, NULL, 0, 1);
1490 apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup);
1491 parser->xmlp = NULL;
1492 add_done_item(parser);
1495 *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
1497 return SVN_NO_ERROR;
1499 #undef PENDING_TO_PARSE
1502 /* ### this is still broken conceptually. just shifting incrementally... */
1503 static svn_error_t *
1504 handle_server_error(serf_request_t *request,
1505 serf_bucket_t *response,
1506 apr_pool_t *scratch_pool)
1508 svn_ra_serf__server_error_t server_err = { 0 };
1509 serf_bucket_t *hdrs;
1513 hdrs = serf_bucket_response_get_headers(response);
1514 val = serf_bucket_headers_get(hdrs, "Content-Type");
1515 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1517 /* ### we should figure out how to reuse begin_error_parsing */
1519 server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL);
1520 server_err.contains_precondition_error = FALSE;
1521 server_err.cdata = svn_stringbuf_create_empty(scratch_pool);
1522 server_err.collect_cdata = FALSE;
1523 server_err.parser.pool = server_err.error->pool;
1524 server_err.parser.user_data = &server_err;
1525 server_err.parser.start = start_error;
1526 server_err.parser.end = end_error;
1527 server_err.parser.cdata = cdata_error;
1528 server_err.parser.done = &server_err.done;
1529 server_err.parser.ignore_errors = TRUE;
1531 /* We don't care about any errors except for SERVER_ERR.ERROR */
1532 svn_error_clear(svn_ra_serf__handle_xml_parser(request,
1537 /* ### checking DONE is silly. the above only parses whatever has
1538 ### been received at the network interface. totally wrong. but
1539 ### it is what we have for now (maintaining historical code),
1540 ### until we fully migrate. */
1541 if (server_err.done && server_err.error->apr_err == APR_SUCCESS)
1543 svn_error_clear(server_err.error);
1544 server_err.error = SVN_NO_ERROR;
1547 return svn_error_trace(server_err.error);
1550 /* The only error that we will return is from the XML response body.
1551 Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to
1553 err = drain_bucket(response);
1554 if (err && !SERF_BUCKET_READ_ERROR(err))
1555 return svn_ra_serf__wrap_err(err, NULL);
1557 return SVN_NO_ERROR;
1561 /* Implements svn_ra_serf__response_handler_t */
1563 svn_ra_serf__handle_xml_parser(serf_request_t *request,
1564 serf_bucket_t *response,
1568 serf_status_line sl;
1569 apr_status_t status;
1570 svn_ra_serf__xml_parser_t *ctx = baton;
1573 /* ### get the HANDLER rather than fetching this. */
1574 status = serf_bucket_response_status(response, &sl);
1575 if (SERF_BUCKET_READ_ERROR(status))
1577 return svn_ra_serf__wrap_err(status, NULL);
1580 /* Woo-hoo. Nothing here to see. */
1581 if (sl.code == 404 && !ctx->ignore_errors)
1583 err = handle_server_error(request, response, pool);
1585 if (err && APR_STATUS_IS_EOF(err->apr_err))
1588 return svn_error_trace(err);
1591 if (ctx->headers_baton == NULL)
1592 ctx->headers_baton = serf_bucket_response_get_headers(response);
1593 else if (ctx->headers_baton != serf_bucket_response_get_headers(response))
1595 /* We got a new response to an existing parser...
1596 This tells us the connection has restarted and we should continue
1597 where we stopped last time.
1600 /* Is this a second attempt?? */
1601 if (!ctx->skip_size)
1602 ctx->skip_size = ctx->read_size;
1604 ctx->read_size = 0; /* New request, nothing read */
1609 ctx->xmlp = XML_ParserCreate(NULL);
1610 apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup,
1611 apr_pool_cleanup_null);
1612 XML_SetUserData(ctx->xmlp, ctx);
1613 XML_SetElementHandler(ctx->xmlp, start_xml, end_xml);
1616 XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml);
1625 status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
1627 if (SERF_BUCKET_READ_ERROR(status))
1629 return svn_ra_serf__wrap_err(status, NULL);
1632 ctx->read_size += len;
1636 /* Handle restarted requests correctly: Skip what we already read */
1639 if (ctx->skip_size >= ctx->read_size)
1641 /* Eek. What did the file shrink or something? */
1642 if (APR_STATUS_IS_EOF(status))
1644 SVN_ERR_MALFUNCTION();
1647 /* Skip on to the next iteration of this loop. */
1648 if (APR_STATUS_IS_EAGAIN(status))
1650 return svn_ra_serf__wrap_err(status, NULL);
1655 skip = (apr_size_t)(len - (ctx->read_size - ctx->skip_size));
1661 /* Note: once the callbacks invoked by inject_to_parser() sets the
1662 PAUSED flag, then it will not be cleared. write_to_pending() will
1663 only save the content. Logic outside of serf_context_run() will
1664 clear that flag, as appropriate, along with processing the
1665 content that we have placed into the PENDING buffer.
1667 We want to save arriving content into the PENDING structures if
1668 the parser has been paused, or we already have data in there (so
1669 the arriving data is appended, rather than injected out of order) */
1670 if (ctx->paused || HAS_PENDING_DATA(ctx->pending))
1672 err = write_to_pending(ctx, data, len, pool);
1676 err = inject_to_parser(ctx, data, len, &sl);
1679 /* Should have no errors if IGNORE_ERRORS is set. */
1680 SVN_ERR_ASSERT(!ctx->ignore_errors);
1685 SVN_ERR_ASSERT(ctx->xmlp != NULL);
1687 apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
1689 return svn_error_trace(err);
1692 if (APR_STATUS_IS_EAGAIN(status))
1694 return svn_ra_serf__wrap_err(status, NULL);
1697 if (APR_STATUS_IS_EOF(status))
1699 if (ctx->pending != NULL)
1700 ctx->pending->network_eof = TRUE;
1702 /* We just hit the end of the network content. If we have nothing
1703 in the PENDING structures, then we're completely done. */
1704 if (!HAS_PENDING_DATA(ctx->pending))
1706 SVN_ERR_ASSERT(ctx->xmlp != NULL);
1708 /* Ignore the return status. We just don't care. */
1709 (void) XML_Parse(ctx->xmlp, NULL, 0, 1);
1711 apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
1715 return svn_ra_serf__wrap_err(status, NULL);
1725 svn_ra_serf__credentials_callback(char **username, char **password,
1726 serf_request_t *request, void *baton,
1727 int code, const char *authn_type,
1731 svn_ra_serf__handler_t *handler = baton;
1732 svn_ra_serf__session_t *session = handler->session;
1734 svn_auth_cred_simple_t *simple_creds;
1739 /* Use svn_auth_first_credentials if this is the first time we ask for
1740 credentials during this session OR if the last time we asked
1741 session->auth_state wasn't set (eg. if the credentials provider was
1742 cancelled by the user). */
1743 if (!session->auth_state)
1745 err = svn_auth_first_credentials(&creds,
1746 &session->auth_state,
1747 SVN_AUTH_CRED_SIMPLE,
1749 session->wc_callbacks->auth_baton,
1754 err = svn_auth_next_credentials(&creds,
1755 session->auth_state,
1761 (void) save_error(session, err);
1762 return err->apr_err;
1765 session->auth_attempts++;
1767 if (!creds || session->auth_attempts > 4)
1769 /* No more credentials. */
1770 (void) save_error(session,
1772 SVN_ERR_AUTHN_FAILED, NULL,
1773 _("No more credentials or we tried too many "
1774 "times.\nAuthentication failed")));
1775 return SVN_ERR_AUTHN_FAILED;
1778 simple_creds = creds;
1779 *username = apr_pstrdup(pool, simple_creds->username);
1780 *password = apr_pstrdup(pool, simple_creds->password);
1784 *username = apr_pstrdup(pool, session->proxy_username);
1785 *password = apr_pstrdup(pool, session->proxy_password);
1787 session->proxy_auth_attempts++;
1789 if (!session->proxy_username || session->proxy_auth_attempts > 4)
1791 /* No more credentials. */
1792 (void) save_error(session,
1794 SVN_ERR_AUTHN_FAILED, NULL,
1795 _("Proxy authentication failed")));
1796 return SVN_ERR_AUTHN_FAILED;
1800 handler->conn->last_status_code = code;
1805 /* Wait for HTTP response status and headers, and invoke HANDLER->
1806 response_handler() to carry out operation-specific processing.
1807 Afterwards, check for connection close.
1809 SERF_STATUS allows returning errors to serf without creating a
1810 subversion error object.
1812 static svn_error_t *
1813 handle_response(serf_request_t *request,
1814 serf_bucket_t *response,
1815 svn_ra_serf__handler_t *handler,
1816 apr_status_t *serf_status,
1817 apr_pool_t *scratch_pool)
1819 apr_status_t status;
1822 /* ### need to verify whether this already gets init'd on every
1823 ### successful exit. for an error-exit, it will (properly) be
1824 ### ignored by the caller. */
1825 *serf_status = APR_SUCCESS;
1829 /* Uh-oh. Our connection died. */
1830 if (handler->response_error)
1831 SVN_ERR(handler->response_error(request, response, 0,
1832 handler->response_error_baton));
1834 /* Requeue another request for this handler.
1835 ### how do we know if the handler can deal with this?! */
1836 svn_ra_serf__request_create(handler);
1838 return SVN_NO_ERROR;
1841 /* If we're reading the body, then skip all this preparation. */
1842 if (handler->reading_body)
1845 /* Copy the Status-Line info into HANDLER, if we don't yet have it. */
1846 if (handler->sline.version == 0)
1848 serf_status_line sl;
1850 status = serf_bucket_response_status(response, &sl);
1851 if (status != APR_SUCCESS)
1853 /* The response line is not (yet) ready, or some other error. */
1854 *serf_status = status;
1855 return SVN_NO_ERROR; /* Handled by serf */
1858 /* If we got APR_SUCCESS, then we should have Status-Line info. */
1859 SVN_ERR_ASSERT(sl.version != 0);
1861 handler->sline = sl;
1862 handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
1864 /* HTTP/1.1? (or later) */
1865 if (sl.version != SERF_HTTP_10)
1866 handler->session->http10 = FALSE;
1869 /* Keep reading from the network until we've read all the headers. */
1870 status = serf_bucket_response_wait_for_headers(response);
1873 /* The typical "error" will be APR_EAGAIN, meaning that more input
1874 from the network is required to complete the reading of the
1876 if (!APR_STATUS_IS_EOF(status))
1878 /* Either the headers are not (yet) complete, or there really
1880 *serf_status = status;
1881 return SVN_NO_ERROR;
1884 /* wait_for_headers() will return EOF if there is no body in this
1885 response, or if we completely read the body. The latter is not
1886 true since we would have set READING_BODY to get the body read,
1887 and we would not be back to this code block.
1889 It can also return EOF if we truly hit EOF while (say) processing
1890 the headers. aka Badness. */
1892 /* Cases where a lack of a response body (via EOF) is okay:
1894 * - 204/304 response
1896 * Otherwise, if we get an EOF here, something went really wrong: either
1897 * the server closed on us early or we're reading too much. Either way,
1900 if (strcmp(handler->method, "HEAD") != 0
1901 && handler->sline.code != 204
1902 && handler->sline.code != 304)
1904 err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
1905 svn_ra_serf__wrap_err(status, NULL),
1906 _("Premature EOF seen from server"
1907 " (http status=%d)"),
1908 handler->sline.code);
1910 /* In case anything else arrives... discard it. */
1911 handler->discard_body = TRUE;
1917 /* ... and set up the header fields in HANDLER. */
1918 handler->location = response_get_location(response,
1919 handler->session->session_url_str,
1920 handler->handler_pool,
1923 /* On the last request, we failed authentication. We succeeded this time,
1924 so let's save away these credentials. */
1925 if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
1927 SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
1928 handler->session->pool));
1929 handler->session->auth_attempts = 0;
1930 handler->session->auth_state = NULL;
1932 handler->conn->last_status_code = handler->sline.code;
1934 if (handler->sline.code == 405
1935 || handler->sline.code == 408
1936 || handler->sline.code == 409
1937 || handler->sline.code >= 500)
1939 /* 405 Method Not allowed.
1941 409 Conflict: can indicate a hook error.
1942 5xx (Internal) Server error. */
1943 serf_bucket_t *hdrs;
1946 hdrs = serf_bucket_response_get_headers(response);
1947 val = serf_bucket_headers_get(hdrs, "Content-Type");
1948 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1950 svn_ra_serf__server_error_t *server_err;
1952 server_err = begin_error_parsing(start_error, end_error, cdata_error,
1953 handler->handler_pool);
1954 /* Get the parser to set our DONE flag. */
1955 server_err->parser.done = &handler->done;
1957 handler->server_error = server_err;
1961 handler->discard_body = TRUE;
1963 if (!handler->session->pending_error)
1965 apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
1967 /* 405 == Method Not Allowed (Occurs when trying to lock a working
1968 copy path which no longer exists at HEAD in the repository. */
1969 if (handler->sline.code == 405
1970 && strcmp(handler->method, "LOCK") == 0)
1971 apr_err = SVN_ERR_FS_OUT_OF_DATE;
1973 handler->session->pending_error =
1974 svn_error_createf(apr_err, NULL,
1975 _("%s request on '%s' failed: %d %s"),
1976 handler->method, handler->path,
1977 handler->sline.code, handler->sline.reason);
1982 /* Stop processing the above, on every packet arrival. */
1983 handler->reading_body = TRUE;
1987 /* We've been instructed to ignore the body. Drain whatever is present. */
1988 if (handler->discard_body)
1990 *serf_status = drain_bucket(response);
1992 /* If the handler hasn't set done (which it shouldn't have) and
1993 we now have the EOF, go ahead and set it so that we can stop
1996 if (!handler->done && APR_STATUS_IS_EOF(*serf_status))
1997 handler->done = TRUE;
1999 return SVN_NO_ERROR;
2002 /* If we are supposed to parse the body as a server_error, then do
2004 if (handler->server_error != NULL)
2006 err = svn_ra_serf__handle_xml_parser(request, response,
2007 &handler->server_error->parser,
2010 /* If we do not receive an error or it is a non-transient error, return
2013 APR_EOF will be returned when parsing is complete.
2015 APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through
2016 parsing and the network has no more data right now. If we receive that,
2017 clear the error and return - allowing serf to wait for more data.
2019 if (!err || SERF_BUCKET_READ_ERROR(err->apr_err))
2020 return svn_error_trace(err);
2022 if (!APR_STATUS_IS_EOF(err->apr_err))
2024 *serf_status = err->apr_err;
2025 svn_error_clear(err);
2026 return SVN_NO_ERROR;
2029 /* Clear the EOF. We don't need it. */
2030 svn_error_clear(err);
2032 /* If the parsing is done, and we did not extract an error, then
2033 simply toss everything, and anything else that might arrive.
2034 The higher-level code will need to investigate HANDLER->SLINE,
2035 as we have no further information for them. */
2037 && handler->server_error->error->apr_err == APR_SUCCESS)
2039 svn_error_clear(handler->server_error->error);
2041 /* Stop parsing for a server error. */
2042 handler->server_error = NULL;
2044 /* If anything arrives after this, then just discard it. */
2045 handler->discard_body = TRUE;
2048 *serf_status = APR_EOF;
2049 return SVN_NO_ERROR;
2052 /* Pass the body along to the registered response handler. */
2053 err = handler->response_handler(request, response,
2054 handler->response_baton,
2058 && (!SERF_BUCKET_READ_ERROR(err->apr_err)
2059 || APR_STATUS_IS_ECONNRESET(err->apr_err)
2060 || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
2062 /* These errors are special cased in serf
2063 ### We hope no handler returns these by accident. */
2064 *serf_status = err->apr_err;
2065 svn_error_clear(err);
2066 return SVN_NO_ERROR;
2069 return svn_error_trace(err);
2073 /* Implements serf_response_handler_t for handle_response. Storing
2074 errors in handler->session->pending_error if appropriate. */
2076 handle_response_cb(serf_request_t *request,
2077 serf_bucket_t *response,
2079 apr_pool_t *scratch_pool)
2081 svn_ra_serf__handler_t *handler = baton;
2083 apr_status_t inner_status;
2084 apr_status_t outer_status;
2086 err = svn_error_trace(handle_response(request, response,
2087 handler, &inner_status,
2090 /* Select the right status value to return. */
2091 outer_status = save_error(handler->session, err);
2093 outer_status = inner_status;
2095 /* Make sure the DONE flag is set properly. */
2096 if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
2097 handler->done = TRUE;
2099 return outer_status;
2102 /* Perform basic request setup, with special handling for HEAD requests,
2103 and finer-grained callbacks invoked (if non-NULL) to produce the request
2104 headers and body. */
2105 static svn_error_t *
2106 setup_request(serf_request_t *request,
2107 svn_ra_serf__handler_t *handler,
2108 serf_bucket_t **req_bkt,
2109 apr_pool_t *request_pool,
2110 apr_pool_t *scratch_pool)
2112 serf_bucket_t *body_bkt;
2113 serf_bucket_t *headers_bkt;
2114 const char *accept_encoding;
2116 if (handler->body_delegate)
2118 serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
2120 /* ### should pass the scratch_pool */
2121 SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
2122 bkt_alloc, request_pool));
2129 if (handler->custom_accept_encoding)
2131 accept_encoding = NULL;
2133 else if (handler->session->using_compression)
2135 /* Accept gzip compression if enabled. */
2136 accept_encoding = "gzip";
2140 accept_encoding = NULL;
2143 SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
2144 handler->session, handler->method, handler->path,
2145 body_bkt, handler->body_type, accept_encoding,
2146 request_pool, scratch_pool));
2148 if (handler->header_delegate)
2150 /* ### should pass the scratch_pool */
2151 SVN_ERR(handler->header_delegate(headers_bkt,
2152 handler->header_delegate_baton,
2159 /* Implements the serf_request_setup_t interface (which sets up both a
2160 request and its response handler callback). Handles errors for
2163 setup_request_cb(serf_request_t *request,
2165 serf_bucket_t **req_bkt,
2166 serf_response_acceptor_t *acceptor,
2167 void **acceptor_baton,
2168 serf_response_handler_t *s_handler,
2169 void **s_handler_baton,
2172 svn_ra_serf__handler_t *handler = setup_baton;
2175 /* ### construct a scratch_pool? serf gives us a pool that will live for
2176 ### the duration of the request. */
2177 apr_pool_t *scratch_pool = pool;
2179 if (strcmp(handler->method, "HEAD") == 0)
2180 *acceptor = accept_head;
2182 *acceptor = accept_response;
2183 *acceptor_baton = handler->session;
2185 *s_handler = handle_response_cb;
2186 *s_handler_baton = handler;
2188 err = svn_error_trace(setup_request(request, handler, req_bkt,
2189 pool /* request_pool */, scratch_pool));
2191 return save_error(handler->session, err);
2195 svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
2197 SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL);
2199 /* In case HANDLER is re-queued, reset the various transient fields.
2201 ### prior to recent changes, HANDLER was constant. maybe we should
2202 ### break out these processing fields, apart from the request
2204 handler->done = FALSE;
2205 handler->server_error = NULL;
2206 handler->sline.version = 0;
2207 handler->location = NULL;
2208 handler->reading_body = FALSE;
2209 handler->discard_body = FALSE;
2211 /* ### do we ever alter the >response_handler? */
2213 /* ### do we need to hold onto the returned request object, or just
2214 ### not worry about it (the serf ctx will manage it). */
2215 (void) serf_connection_request_create(handler->conn->conn,
2216 setup_request_cb, handler);
2221 svn_ra_serf__discover_vcc(const char **vcc_url,
2222 svn_ra_serf__session_t *session,
2223 svn_ra_serf__connection_t *conn,
2227 const char *relative_path;
2230 /* If we've already got the information our caller seeks, just return it. */
2231 if (session->vcc_url && session->repos_root_str)
2233 *vcc_url = session->vcc_url;
2234 return SVN_NO_ERROR;
2237 /* If no connection is provided, use the default one. */
2240 conn = session->conns[0];
2243 path = session->session_url.path;
2252 err = svn_ra_serf__fetch_node_props(&props, conn,
2253 path, SVN_INVALID_REVNUM,
2254 base_props, pool, pool);
2257 apr_hash_t *ns_props;
2259 ns_props = apr_hash_get(props, "DAV:", 4);
2260 *vcc_url = svn_prop_get_value(ns_props,
2261 "version-controlled-configuration");
2263 ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
2264 relative_path = svn_prop_get_value(ns_props,
2265 "baseline-relative-path");
2266 uuid = svn_prop_get_value(ns_props, "repository-uuid");
2271 if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
2272 (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
2274 return svn_error_trace(err); /* found a _real_ error */
2278 /* This happens when the file is missing in HEAD. */
2279 svn_error_clear(err);
2281 /* Okay, strip off a component from PATH. */
2282 path = svn_urlpath__dirname(path, pool);
2284 /* An error occurred on conns. serf 0.4.0 remembers that
2285 the connection had a problem. We need to reset it, in
2286 order to use it again. */
2287 serf_connection_reset(conn->conn);
2291 while ((path[0] != '\0')
2292 && (! (path[0] == '/' && path[1] == '\0')));
2296 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
2297 _("The PROPFIND response did not include the "
2298 "requested version-controlled-configuration "
2302 /* Store our VCC in our cache. */
2303 if (!session->vcc_url)
2305 session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
2308 /* Update our cached repository root URL. */
2309 if (!session->repos_root_str)
2311 svn_stringbuf_t *url_buf;
2313 url_buf = svn_stringbuf_create(path, pool);
2315 svn_path_remove_components(url_buf,
2316 svn_path_component_count(relative_path));
2318 /* Now recreate the root_url. */
2319 session->repos_root = session->session_url;
2320 session->repos_root.path =
2321 (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
2322 session->repos_root_str =
2323 svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
2324 &session->repos_root, 0),
2328 /* Store the repository UUID in the cache. */
2331 session->uuid = apr_pstrdup(session->pool, uuid);
2334 return SVN_NO_ERROR;
2338 svn_ra_serf__get_relative_path(const char **rel_path,
2339 const char *orig_path,
2340 svn_ra_serf__session_t *session,
2341 svn_ra_serf__connection_t *conn,
2344 const char *decoded_root, *decoded_orig;
2346 if (! session->repos_root.path)
2348 const char *vcc_url;
2350 /* This should only happen if we haven't detected HTTP v2
2351 support from the server. */
2352 assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
2354 /* We don't actually care about the VCC_URL, but this API
2355 promises to populate the session's root-url cache, and that's
2356 what we really want. */
2357 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
2358 conn ? conn : session->conns[0],
2362 decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
2363 decoded_orig = svn_path_uri_decode(orig_path, pool);
2364 *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
2365 SVN_ERR_ASSERT(*rel_path != NULL);
2366 return SVN_NO_ERROR;
2370 svn_ra_serf__report_resource(const char **report_target,
2371 svn_ra_serf__session_t *session,
2372 svn_ra_serf__connection_t *conn,
2375 /* If we have HTTP v2 support, we want to report against the 'me'
2377 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2378 *report_target = apr_pstrdup(pool, session->me_resource);
2380 /* Otherwise, we'll use the default VCC. */
2382 SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool));
2384 return SVN_NO_ERROR;
2388 svn_ra_serf__error_on_status(serf_status_line sline,
2390 const char *location)
2397 return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
2399 ? _("Repository moved permanently to '%s';"
2401 : _("Repository moved temporarily to '%s';"
2402 " please relocate"), location);
2404 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
2405 _("Access to '%s' forbidden"), path);
2408 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
2409 _("'%s' path not found"), path);
2411 return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
2412 _("'%s': no lock token available"), path);
2415 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2416 _("DAV request failed: 411 Content length required. The "
2417 "server or an intermediate proxy does not accept "
2418 "chunked encoding. Try setting 'http-chunked-requests' "
2419 "to 'auto' or 'no' in your client configuration."));
2422 if (sline.code >= 300)
2423 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2424 _("Unexpected HTTP status %d '%s' on '%s'\n"),
2425 sline.code, sline.reason, path);
2427 return SVN_NO_ERROR;
2431 svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
2432 svn_delta_shim_callbacks_t *callbacks)
2434 svn_ra_serf__session_t *session = ra_session->priv;
2436 session->shim_callbacks = callbacks;
2437 return SVN_NO_ERROR;
2441 /* Conforms to Expat's XML_StartElementHandler */
2443 expat_start(void *userData, const char *raw_name, const char **attrs)
2445 struct expat_ctx_t *ectx = userData;
2447 if (ectx->inner_error != NULL)
2450 ectx->inner_error = svn_error_trace(
2451 svn_ra_serf__xml_cb_start(ectx->xmlctx,
2454 #ifdef EXPAT_HAS_STOPPARSER
2455 if (ectx->inner_error)
2456 (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2461 /* Conforms to Expat's XML_EndElementHandler */
2463 expat_end(void *userData, const char *raw_name)
2465 struct expat_ctx_t *ectx = userData;
2467 if (ectx->inner_error != NULL)
2470 ectx->inner_error = svn_error_trace(
2471 svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name));
2473 #ifdef EXPAT_HAS_STOPPARSER
2474 if (ectx->inner_error)
2475 (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2480 /* Conforms to Expat's XML_CharacterDataHandler */
2482 expat_cdata(void *userData, const char *data, int len)
2484 struct expat_ctx_t *ectx = userData;
2486 if (ectx->inner_error != NULL)
2489 ectx->inner_error = svn_error_trace(
2490 svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len));
2492 #ifdef EXPAT_HAS_STOPPARSER
2493 if (ectx->inner_error)
2494 (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2499 /* Implements svn_ra_serf__response_handler_t */
2500 static svn_error_t *
2501 expat_response_handler(serf_request_t *request,
2502 serf_bucket_t *response,
2504 apr_pool_t *scratch_pool)
2506 struct expat_ctx_t *ectx = baton;
2510 ectx->parser = XML_ParserCreate(NULL);
2511 apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
2512 xml_parser_cleanup, apr_pool_cleanup_null);
2513 XML_SetUserData(ectx->parser, ectx);
2514 XML_SetElementHandler(ectx->parser, expat_start, expat_end);
2515 XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
2518 /* ### TODO: sline.code < 200 should really be handled by the core */
2519 if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300))
2521 /* By deferring to expect_empty_body(), it will make a choice on
2522 how to handle the body. Whatever the decision, the core handler
2523 will take over, and we will not be called again. */
2524 return svn_error_trace(svn_ra_serf__expect_empty_body(
2525 request, response, ectx->handler,
2531 apr_status_t status;
2536 status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
2537 if (SERF_BUCKET_READ_ERROR(status))
2538 return svn_ra_serf__wrap_err(status, NULL);
2541 /* ### move restart/skip into the core handler */
2542 ectx->handler->read_size += len;
2545 /* ### move PAUSED behavior to a new response handler that can feed
2546 ### an inner handler, or can pause for a while. */
2548 /* ### should we have an IGNORE_ERRORS flag like the v1 parser? */
2550 expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */);
2552 /* We need to check INNER_ERROR first. This is an error from the
2553 callbacks that has been "dropped off" for us to retrieve. On
2554 current Expat parsers, we stop the parser when an error occurs,
2555 so we want to ignore EXPAT_STATUS (which reports the stoppage).
2557 If an error is not present, THEN we go ahead and look for parsing
2559 if (ectx->inner_error)
2561 apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
2562 xml_parser_cleanup);
2563 return svn_error_trace(ectx->inner_error);
2565 if (expat_status == XML_STATUS_ERROR)
2566 return svn_error_createf(SVN_ERR_XML_MALFORMED,
2568 _("The %s response contains invalid XML"
2570 ectx->handler->method,
2571 ectx->handler->sline.code,
2572 ectx->handler->sline.reason);
2574 /* The parsing went fine. What has the bucket told us? */
2576 if (APR_STATUS_IS_EOF(status))
2578 /* Tell expat we've reached the end of the content. Ignore the
2579 return status. We just don't care. */
2580 (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */);
2582 svn_ra_serf__xml_context_destroy(ectx->xmlctx);
2583 apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
2584 xml_parser_cleanup);
2586 /* ### should check XMLCTX to see if it has returned to the
2587 ### INITIAL state. we may have ended early... */
2590 if (status && !SERF_BUCKET_READ_ERROR(status))
2592 return svn_ra_serf__wrap_err(status, NULL);
2600 svn_ra_serf__handler_t *
2601 svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
2602 apr_pool_t *result_pool)
2604 svn_ra_serf__handler_t *handler;
2605 struct expat_ctx_t *ectx;
2607 ectx = apr_pcalloc(result_pool, sizeof(*ectx));
2608 ectx->xmlctx = xmlctx;
2609 ectx->parser = NULL;
2610 ectx->cleanup_pool = result_pool;
2613 handler = apr_pcalloc(result_pool, sizeof(*handler));
2614 handler->handler_pool = result_pool;
2615 handler->response_handler = expat_response_handler;
2616 handler->response_baton = ectx;
2618 ectx->handler = handler;