summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_ra_serf/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_ra_serf/util.c')
-rw-r--r--subversion/libsvn_ra_serf/util.c2614
1 files changed, 2614 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c
new file mode 100644
index 0000000..c7a1716
--- /dev/null
+++ b/subversion/libsvn_ra_serf/util.c
@@ -0,0 +1,2614 @@
+/*
+ * util.c : serf utility routines for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <assert.h>
+
+#define APR_WANT_STRFUNC
+#include <apr.h>
+#include <apr_want.h>
+#include <apr_fnmatch.h>
+
+#include <serf.h>
+#include <serf_bucket_types.h>
+
+#include <expat.h>
+
+#include "svn_hash.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+#include "svn_string.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "svn_dirent_uri.h"
+
+#include "../libsvn_ra/ra_loader.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
+
+#include "ra_serf.h"
+
+
+/* Fix for older expat 1.95.x's that do not define
+ * XML_STATUS_OK/XML_STATUS_ERROR
+ */
+#ifndef XML_STATUS_OK
+#define XML_STATUS_OK 1
+#define XML_STATUS_ERROR 0
+#endif
+
+#ifndef XML_VERSION_AT_LEAST
+#define XML_VERSION_AT_LEAST(major,minor,patch) \
+(((major) < XML_MAJOR_VERSION) \
+ || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \
+ || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
+ (patch) <= XML_MICRO_VERSION))
+#endif /* APR_VERSION_AT_LEAST */
+
+#if XML_VERSION_AT_LEAST(1, 95, 8)
+#define EXPAT_HAS_STOPPARSER
+#endif
+
+/* Read/write chunks of this size into the spillbuf. */
+#define PARSE_CHUNK_SIZE 8000
+
+/* We will store one megabyte in memory, before switching to store content
+ into a temporary file. */
+#define SPILL_SIZE 1000000
+
+
+/* This structure records pending data for the parser in memory blocks,
+ and possibly into a temporary file if "too much" content arrives. */
+struct svn_ra_serf__pending_t {
+ /* The spillbuf where we record the pending data. */
+ svn_spillbuf_t *buf;
+
+ /* This flag is set when the network has reached EOF. The PENDING
+ processing can then properly detect when parsing has completed. */
+ svn_boolean_t network_eof;
+};
+
+#define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \
+ && svn_spillbuf__get_size((p)->buf) != 0)
+
+
+struct expat_ctx_t {
+ svn_ra_serf__xml_context_t *xmlctx;
+ XML_Parser parser;
+ svn_ra_serf__handler_t *handler;
+
+ svn_error_t *inner_error;
+
+ /* Do not use this pool for allocation. It is merely recorded for running
+ the cleanup handler. */
+ apr_pool_t *cleanup_pool;
+};
+
+
+static const apr_uint32_t serf_failure_map[][2] =
+{
+ { SERF_SSL_CERT_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID },
+ { SERF_SSL_CERT_EXPIRED, SVN_AUTH_SSL_EXPIRED },
+ { SERF_SSL_CERT_SELF_SIGNED, SVN_AUTH_SSL_UNKNOWNCA },
+ { SERF_SSL_CERT_UNKNOWNCA, SVN_AUTH_SSL_UNKNOWNCA }
+};
+
+/* Return a Subversion failure mask based on FAILURES, a serf SSL
+ failure mask. If anything in FAILURES is not directly mappable to
+ Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */
+static apr_uint32_t
+ssl_convert_serf_failures(int failures)
+{
+ apr_uint32_t svn_failures = 0;
+ apr_size_t i;
+
+ for (i = 0; i < sizeof(serf_failure_map) / (2 * sizeof(apr_uint32_t)); ++i)
+ {
+ if (failures & serf_failure_map[i][0])
+ {
+ svn_failures |= serf_failure_map[i][1];
+ failures &= ~serf_failure_map[i][0];
+ }
+ }
+
+ /* Map any remaining failure bits to our OTHER bit. */
+ if (failures)
+ {
+ svn_failures |= SVN_AUTH_SSL_OTHER;
+ }
+
+ return svn_failures;
+}
+
+
+static apr_status_t
+save_error(svn_ra_serf__session_t *session,
+ svn_error_t *err)
+{
+ if (err || session->pending_error)
+ {
+ session->pending_error = svn_error_compose_create(
+ session->pending_error,
+ err);
+ return session->pending_error->apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Construct the realmstring, e.g. https://svn.collab.net:443. */
+static const char *
+construct_realm(svn_ra_serf__session_t *session,
+ apr_pool_t *pool)
+{
+ const char *realm;
+ apr_port_t port;
+
+ if (session->session_url.port_str)
+ {
+ port = session->session_url.port;
+ }
+ else
+ {
+ port = apr_uri_port_of_scheme(session->session_url.scheme);
+ }
+
+ realm = apr_psprintf(pool, "%s://%s:%d",
+ session->session_url.scheme,
+ session->session_url.hostname,
+ port);
+
+ return realm;
+}
+
+/* Convert a hash table containing the fields (as documented in X.509) of an
+ organisation to a string ORG, allocated in POOL. ORG is as returned by
+ serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */
+static char *
+convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "%s, %s, %s, %s, %s (%s)",
+ (char*)svn_hash_gets(org, "OU"),
+ (char*)svn_hash_gets(org, "O"),
+ (char*)svn_hash_gets(org, "L"),
+ (char*)svn_hash_gets(org, "ST"),
+ (char*)svn_hash_gets(org, "C"),
+ (char*)svn_hash_gets(org, "E"));
+}
+
+/* This function is called on receiving a ssl certificate of a server when
+ opening a https connection. It allows Subversion to override the initial
+ validation done by serf.
+ Serf provides us the @a baton as provided in the call to
+ serf_ssl_server_cert_callback_set. The result of serf's initial validation
+ of the certificate @a CERT is returned as a bitmask in FAILURES. */
+static svn_error_t *
+ssl_server_cert(void *baton, int failures,
+ const serf_ssl_certificate_t *cert,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__connection_t *conn = baton;
+ svn_auth_ssl_server_cert_info_t cert_info;
+ svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
+ svn_auth_iterstate_t *state;
+ const char *realmstring;
+ apr_uint32_t svn_failures;
+ apr_hash_t *issuer, *subject, *serf_cert;
+ apr_array_header_t *san;
+ void *creds;
+ int found_matching_hostname = 0;
+
+ /* Implicitly approve any non-server certs. */
+ if (serf_ssl_cert_depth(cert) > 0)
+ {
+ if (failures)
+ conn->server_cert_failures |= ssl_convert_serf_failures(failures);
+ return APR_SUCCESS;
+ }
+
+ /* Extract the info from the certificate */
+ subject = serf_ssl_cert_subject(cert, scratch_pool);
+ issuer = serf_ssl_cert_issuer(cert, scratch_pool);
+ serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
+
+ cert_info.hostname = svn_hash_gets(subject, "CN");
+ san = svn_hash_gets(serf_cert, "subjectAltName");
+ cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
+ if (! cert_info.fingerprint)
+ cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
+ cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
+ if (! cert_info.valid_from)
+ cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
+ cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
+ if (! cert_info.valid_until)
+ cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
+ cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
+ cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
+
+ svn_failures = (ssl_convert_serf_failures(failures)
+ | conn->server_cert_failures);
+
+ /* Try to find matching server name via subjectAltName first... */
+ if (san) {
+ int i;
+ for (i = 0; i < san->nelts; i++) {
+ char *s = APR_ARRAY_IDX(san, i, char*);
+ if (apr_fnmatch(s, conn->session->session_url.hostname,
+ APR_FNM_PERIOD) == APR_SUCCESS) {
+ found_matching_hostname = 1;
+ cert_info.hostname = s;
+ break;
+ }
+ }
+ }
+
+ /* Match server certificate CN with the hostname of the server */
+ if (!found_matching_hostname && cert_info.hostname)
+ {
+ if (apr_fnmatch(cert_info.hostname, conn->session->session_url.hostname,
+ APR_FNM_PERIOD) == APR_FNM_NOMATCH)
+ {
+ svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
+ }
+ }
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
+ &svn_failures);
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
+ &cert_info);
+
+ realmstring = construct_realm(conn->session, conn->session->pool);
+
+ SVN_ERR(svn_auth_first_credentials(&creds, &state,
+ SVN_AUTH_CRED_SSL_SERVER_TRUST,
+ realmstring,
+ conn->session->wc_callbacks->auth_baton,
+ scratch_pool));
+ if (creds)
+ {
+ server_creds = creds;
+ SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
+ }
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
+
+ if (!server_creds)
+ return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
+static apr_status_t
+ssl_server_cert_cb(void *baton, int failures,
+ const serf_ssl_certificate_t *cert)
+{
+ svn_ra_serf__connection_t *conn = baton;
+ svn_ra_serf__session_t *session = conn->session;
+ apr_pool_t *subpool;
+ svn_error_t *err;
+
+ subpool = svn_pool_create(session->pool);
+ err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
+ svn_pool_destroy(subpool);
+
+ return save_error(session, err);
+}
+
+static svn_error_t *
+load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *files = svn_cstring_split(authorities, ";",
+ TRUE /* chop_whitespace */,
+ pool);
+ int i;
+
+ for (i = 0; i < files->nelts; ++i)
+ {
+ const char *file = APR_ARRAY_IDX(files, i, const char *);
+ serf_ssl_certificate_t *ca_cert;
+ apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
+
+ if (status == APR_SUCCESS)
+ status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
+
+ if (status != APR_SUCCESS)
+ {
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: unable to load certificate file '%s'"),
+ svn_dirent_local_style(file, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+conn_setup(apr_socket_t *sock,
+ serf_bucket_t **read_bkt,
+ serf_bucket_t **write_bkt,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = baton;
+
+ *read_bkt = serf_context_bucket_socket_create(conn->session->context,
+ sock, conn->bkt_alloc);
+
+ if (conn->session->using_ssl)
+ {
+ /* input stream */
+ *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
+ conn->bkt_alloc);
+ if (!conn->ssl_context)
+ {
+ conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
+
+#if SERF_VERSION_AT_LEAST(1,0,0)
+ serf_ssl_set_hostname(conn->ssl_context,
+ conn->session->session_url.hostname);
+#endif
+
+ serf_ssl_client_cert_provider_set(conn->ssl_context,
+ svn_ra_serf__handle_client_cert,
+ conn, conn->session->pool);
+ serf_ssl_client_cert_password_set(conn->ssl_context,
+ svn_ra_serf__handle_client_cert_pw,
+ conn, conn->session->pool);
+ serf_ssl_server_cert_callback_set(conn->ssl_context,
+ ssl_server_cert_cb,
+ conn);
+
+ /* See if the user wants us to trust "default" openssl CAs. */
+ if (conn->session->trust_default_ca)
+ {
+ serf_ssl_use_default_certificates(conn->ssl_context);
+ }
+ /* Are there custom CAs to load? */
+ if (conn->session->ssl_authorities)
+ {
+ SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
+ conn->session->pool));
+ }
+ }
+
+ if (write_bkt)
+ {
+ /* output stream */
+ *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
+ conn->ssl_context,
+ conn->bkt_alloc);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_ra_serf__conn_setup is a callback for serf. This function
+ creates a read bucket and will wrap the write bucket if SSL
+ is needed. */
+apr_status_t
+svn_ra_serf__conn_setup(apr_socket_t *sock,
+ serf_bucket_t **read_bkt,
+ serf_bucket_t **write_bkt,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = baton;
+ svn_ra_serf__session_t *session = conn->session;
+ svn_error_t *err;
+
+ err = svn_error_trace(conn_setup(sock,
+ read_bkt,
+ write_bkt,
+ baton,
+ pool));
+ return save_error(session, err);
+}
+
+
+/* Our default serf response acceptor. */
+static serf_bucket_t *
+accept_response(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ serf_bucket_alloc_t *bkt_alloc;
+
+ bkt_alloc = serf_request_get_alloc(request);
+ c = serf_bucket_barrier_create(stream, bkt_alloc);
+
+ return serf_bucket_response_create(c, bkt_alloc);
+}
+
+
+/* Custom response acceptor for HEAD requests. */
+static serf_bucket_t *
+accept_head(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *response;
+
+ response = accept_response(request, stream, acceptor_baton, pool);
+
+ /* We know we shouldn't get a response body. */
+ serf_bucket_response_set_head(response);
+
+ return response;
+}
+
+static svn_error_t *
+connection_closed(svn_ra_serf__connection_t *conn,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ if (why)
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (conn->session->using_ssl)
+ conn->ssl_context = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_ra_serf__conn_closed(serf_connection_t *conn,
+ void *closed_baton,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *ra_conn = closed_baton;
+ svn_error_t *err;
+
+ err = svn_error_trace(connection_closed(ra_conn, why, pool));
+
+ (void) save_error(ra_conn->session, err);
+}
+
+
+/* Implementation of svn_ra_serf__handle_client_cert */
+static svn_error_t *
+handle_client_cert(void *data,
+ const char **cert_path,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ const char *realm;
+ void *creds;
+
+ *cert_path = NULL;
+
+ realm = construct_realm(session, session->pool);
+
+ if (!conn->ssl_client_auth_state)
+ {
+ SVN_ERR(svn_auth_first_credentials(&creds,
+ &conn->ssl_client_auth_state,
+ SVN_AUTH_CRED_SSL_CLIENT_CERT,
+ realm,
+ session->wc_callbacks->auth_baton,
+ pool));
+ }
+ else
+ {
+ SVN_ERR(svn_auth_next_credentials(&creds,
+ conn->ssl_client_auth_state,
+ session->pool));
+ }
+
+ if (creds)
+ {
+ svn_auth_cred_ssl_client_cert_t *client_creds;
+ client_creds = creds;
+ *cert_path = client_creds->cert_file;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements serf_ssl_need_client_cert_t for handle_client_cert */
+apr_status_t svn_ra_serf__handle_client_cert(void *data,
+ const char **cert_path)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ svn_error_t *err;
+
+ err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
+
+ return save_error(session, err);
+}
+
+/* Implementation for svn_ra_serf__handle_client_cert_pw */
+static svn_error_t *
+handle_client_cert_pw(void *data,
+ const char *cert_path,
+ const char **password,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ void *creds;
+
+ *password = NULL;
+
+ if (!conn->ssl_client_pw_auth_state)
+ {
+ SVN_ERR(svn_auth_first_credentials(&creds,
+ &conn->ssl_client_pw_auth_state,
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ cert_path,
+ session->wc_callbacks->auth_baton,
+ pool));
+ }
+ else
+ {
+ SVN_ERR(svn_auth_next_credentials(&creds,
+ conn->ssl_client_pw_auth_state,
+ pool));
+ }
+
+ if (creds)
+ {
+ svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
+ pw_creds = creds;
+ *password = pw_creds->password;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
+apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
+ const char *cert_path,
+ const char **password)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ svn_error_t *err;
+
+ err = svn_error_trace(handle_client_cert_pw(data,
+ cert_path,
+ password,
+ session->pool));
+
+ return save_error(session, err);
+}
+
+
+/*
+ * Given a REQUEST on connection CONN, construct a request bucket for it,
+ * returning the bucket in *REQ_BKT.
+ *
+ * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
+ * corresponds to the new request.
+ *
+ * The request will be METHOD at URL.
+ *
+ * If BODY_BKT is not-NULL, it will be sent as the request body.
+ *
+ * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
+ *
+ * REQUEST_POOL should live for the duration of the request. Serf will
+ * construct this and provide it to the request_setup callback, so we
+ * should just use that one.
+ */
+static svn_error_t *
+setup_serf_req(serf_request_t *request,
+ serf_bucket_t **req_bkt,
+ serf_bucket_t **hdrs_bkt,
+ svn_ra_serf__session_t *session,
+ const char *method, const char *url,
+ serf_bucket_t *body_bkt, const char *content_type,
+ const char *accept_encoding,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
+
+#if SERF_VERSION_AT_LEAST(1, 1, 0)
+ svn_spillbuf_t *buf;
+
+ if (session->http10 && body_bkt != NULL)
+ {
+ /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
+ it speaks HTTP/1.1 (and thus, chunked requests), or because the
+ server actually responded as only supporting HTTP/1.0.
+
+ We'll take the existing body_bkt, spool it into a spillbuf, and
+ then wrap a bucket around that spillbuf. The spillbuf will give
+ us the Content-Length value. */
+ SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
+ request_pool,
+ scratch_pool));
+ /* Destroy original bucket since it content is already copied
+ to spillbuf. */
+ serf_bucket_destroy(body_bkt);
+
+ body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
+ request_pool,
+ scratch_pool);
+ }
+#endif
+
+ /* Create a request bucket. Note that this sucker is kind enough to
+ add a "Host" header for us. */
+ *req_bkt = serf_request_bucket_request_create(request, method, url,
+ body_bkt, allocator);
+
+ /* Set the Content-Length value. This will also trigger an HTTP/1.0
+ request (rather than the default chunked request). */
+#if SERF_VERSION_AT_LEAST(1, 1, 0)
+ if (session->http10)
+ {
+ if (body_bkt == NULL)
+ serf_bucket_request_set_CL(*req_bkt, 0);
+ else
+ serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
+ }
+#endif
+
+ *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
+
+ /* We use serf_bucket_headers_setn() because the USERAGENT has a
+ lifetime longer than this bucket. Thus, there is no need to copy
+ the header values. */
+ serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
+
+ if (content_type)
+ {
+ serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
+ }
+
+#if SERF_VERSION_AT_LEAST(1, 1, 0)
+ if (session->http10)
+ serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
+#endif
+
+ if (accept_encoding)
+ {
+ serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
+ }
+
+ /* These headers need to be sent with every request; see issue #3255
+ ("mod_dav_svn does not pass client capabilities to start-commit
+ hooks") for why. */
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__context_run_wait(svn_boolean_t *done,
+ svn_ra_serf__session_t *sess,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ apr_interval_time_t waittime_left = sess->timeout;
+
+ assert(sess->pending_error == SVN_NO_ERROR);
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (!*done)
+ {
+ apr_status_t status;
+ svn_error_t *err;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ if (sess->cancel_func)
+ SVN_ERR((*sess->cancel_func)(sess->cancel_baton));
+
+ status = serf_context_run(sess->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
+ iterpool);
+
+ err = sess->pending_error;
+ sess->pending_error = SVN_NO_ERROR;
+
+ /* If the context duration timeout is up, we'll subtract that
+ duration from the total time alloted for such things. If
+ there's no time left, we fail with a message indicating that
+ the connection timed out. */
+ if (APR_STATUS_IS_TIMEUP(status))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ status = 0;
+
+ if (sess->timeout)
+ {
+ if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
+ {
+ waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out"));
+ }
+ }
+ }
+ else
+ {
+ waittime_left = sess->timeout;
+ }
+
+ SVN_ERR(err);
+ if (status)
+ {
+ if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
+ {
+ /* apr can't translate subversion errors to text */
+ SVN_ERR_W(svn_error_create(status, NULL, NULL),
+ _("Error running context"));
+ }
+
+ return svn_ra_serf__wrap_err(status, _("Error running context"));
+ }
+
+ /* Debugging purposes only! */
+ for (i = 0; i < sess->num_conns; i++)
+ {
+ serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ /* Create a serf request based on HANDLER. */
+ svn_ra_serf__request_create(handler);
+
+ /* Wait until the response logic marks its DONE status. */
+ err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
+ scratch_pool);
+ if (handler->server_error)
+ {
+ err = svn_error_compose_create(err, handler->server_error->error);
+ handler->server_error = NULL;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+/*
+ * Expat callback invoked on a start element tag for an error response.
+ */
+static svn_error_t *
+start_error(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (!ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "error") == 0)
+ {
+ ctx->in_error = TRUE;
+ }
+ else if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
+ {
+ const char *err_code;
+
+ err_code = svn_xml_get_attr_value("errcode", attrs);
+ if (err_code)
+ {
+ apr_int64_t val;
+
+ SVN_ERR(svn_cstring_atoi64(&val, err_code));
+ ctx->error->apr_err = (apr_status_t)val;
+ }
+
+ /* If there's no error code provided, or if the provided code is
+ 0 (which can happen sometimes depending on how the error is
+ constructed on the server-side), just pick a generic error
+ code to run with. */
+ if (! ctx->error->apr_err)
+ {
+ ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+ }
+
+ /* Start collecting cdata. */
+ svn_stringbuf_setempty(ctx->cdata);
+ ctx->collect_cdata = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on an end element tag for a PROPFIND response.
+ */
+static svn_error_t *
+end_error(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "error") == 0)
+ {
+ ctx->in_error = FALSE;
+ }
+ if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
+ {
+ /* On the server dav_error_response_tag() will add a leading
+ and trailing newline if DEBUG_CR is defined in mod_dav.h,
+ so remove any such characters here. */
+ svn_stringbuf_strip_whitespace(ctx->cdata);
+
+ ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
+ ctx->cdata->len);
+ ctx->collect_cdata = FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on CDATA elements in an error response.
+ *
+ * This callback can be called multiple times.
+ */
+static svn_error_t *
+cdata_error(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->collect_cdata)
+ {
+ svn_stringbuf_appendbytes(ctx->cdata, data, len);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static apr_status_t
+drain_bucket(serf_bucket_t *bucket)
+{
+ /* Read whatever is in the bucket, and just drop it. */
+ while (1)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
+ if (status)
+ return status;
+ }
+}
+
+
+static svn_ra_serf__server_error_t *
+begin_error_parsing(svn_ra_serf__xml_start_element_t start,
+ svn_ra_serf__xml_end_element_t end,
+ svn_ra_serf__xml_cdata_chunk_handler_t cdata,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = apr_pcalloc(result_pool, sizeof(*server_err));
+ server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err->contains_precondition_error = FALSE;
+ server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool);
+ server_err->collect_cdata = FALSE;
+ server_err->parser.pool = server_err->error->pool;
+ server_err->parser.user_data = server_err;
+ server_err->parser.start = start;
+ server_err->parser.end = end;
+ server_err->parser.cdata = cdata;
+ server_err->parser.ignore_errors = TRUE;
+
+ return server_err;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_discard_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+
+ status = drain_bucket(response);
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+apr_status_t
+svn_ra_serf__response_discard_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ return drain_bucket(response);
+}
+
+
+/* Return the value of the RESPONSE's Location header if any, or NULL
+ otherwise. */
+static const char *
+response_get_location(serf_bucket_t *response,
+ const char *base_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_t *headers;
+ const char *location;
+
+ headers = serf_bucket_response_get_headers(response);
+ location = serf_bucket_headers_get(headers, "Location");
+ if (location == NULL)
+ return NULL;
+
+ /* The RFCs say we should have received a full url in LOCATION, but
+ older apache versions and many custom web handlers just return a
+ relative path here...
+
+ And we can't trust anything because it is network data.
+ */
+ if (*location == '/')
+ {
+ apr_uri_t uri;
+ apr_status_t status;
+
+ status = apr_uri_parse(scratch_pool, base_url, &uri);
+
+ if (status != APR_SUCCESS)
+ return NULL;
+
+ /* Replace the path path with what we got */
+ uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
+
+ /* And make APR produce a proper full url for us */
+ location = apr_uri_unparse(scratch_pool, &uri, 0);
+
+ /* Fall through to ensure our canonicalization rules */
+ }
+ else if (!svn_path_is_url(location))
+ {
+ return NULL; /* Any other formats we should support? */
+ }
+
+ return svn_uri_canonicalize(location, result_pool);
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__expect_empty_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ /* This function is just like handle_multistatus_only() except for the
+ XML parsing callbacks. We want to look for the human-readable element. */
+
+ /* We should see this just once, in order to initialize SERVER_ERROR.
+ At that point, the core error processing will take over. If we choose
+ not to parse an error, then we'll never return here (because we
+ change the response handler). */
+ SVN_ERR_ASSERT(handler->server_error == NULL);
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = begin_error_parsing(start_error, end_error, cdata_error,
+ handler->handler_pool);
+
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
+
+ handler->server_error = server_err;
+ }
+ else
+ {
+ /* The body was not text/xml, so we don't know what to do with it.
+ Toss anything that arrives. */
+ handler->discard_body = TRUE;
+ }
+
+ /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
+ to call the response handler again. That will start up the XML parsing,
+ or it will be dropped on the floor (per the decision above). */
+ return SVN_NO_ERROR;
+}
+
+
+/* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric
+ status code into *STATUS_CODE_OUT. Ignores leading whitespace. */
+static svn_error_t *
+parse_dav_status(int *status_code_out, svn_stringbuf_t *buf,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ const char *token;
+ char *tok_status;
+ svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool);
+
+ svn_stringbuf_strip_whitespace(temp_buf);
+ token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status);
+ if (token)
+ token = apr_strtok(NULL, " \t\r\n", &tok_status);
+ if (!token)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Malformed DAV:status CDATA '%s'"),
+ buf->data);
+ err = svn_cstring_atoi(status_code_out, token);
+ if (err)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
+ _("Malformed DAV:status CDATA '%s'"),
+ buf->data);
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on a start element tag for a 207 response.
+ */
+static svn_error_t *
+start_207(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (!ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "multistatus") == 0)
+ {
+ ctx->in_error = TRUE;
+ }
+ else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
+ {
+ /* Start collecting cdata. */
+ svn_stringbuf_setempty(ctx->cdata);
+ ctx->collect_cdata = TRUE;
+ }
+ else if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "status") == 0)
+ {
+ /* Start collecting cdata. */
+ svn_stringbuf_setempty(ctx->cdata);
+ ctx->collect_cdata = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on an end element tag for a 207 response.
+ */
+static svn_error_t *
+end_207(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "multistatus") == 0)
+ {
+ ctx->in_error = FALSE;
+ }
+ if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
+ {
+ /* Remove leading newline added by DEBUG_CR on server */
+ svn_stringbuf_strip_whitespace(ctx->cdata);
+
+ ctx->collect_cdata = FALSE;
+ ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
+ ctx->cdata->len);
+ if (ctx->contains_precondition_error)
+ ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
+ else
+ ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+ }
+ else if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "status") == 0)
+ {
+ int status_code;
+
+ ctx->collect_cdata = FALSE;
+
+ SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool));
+ if (status_code == 412)
+ ctx->contains_precondition_error = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on CDATA elements in a 207 response.
+ *
+ * This callback can be called multiple times.
+ */
+static svn_error_t *
+cdata_207(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->collect_cdata)
+ {
+ svn_stringbuf_appendbytes(ctx->cdata, data, len);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_multistatus_only(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+
+ /* This function is just like expect_empty_body() except for the
+ XML parsing callbacks. We are looking for very limited pieces of
+ the multistatus response. */
+
+ /* We should see this just once, in order to initialize SERVER_ERROR.
+ At that point, the core error processing will take over. If we choose
+ not to parse an error, then we'll never return here (because we
+ change the response handler). */
+ SVN_ERR_ASSERT(handler->server_error == NULL);
+
+ {
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = begin_error_parsing(start_207, end_207, cdata_207,
+ handler->handler_pool);
+
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
+
+ handler->server_error = server_err;
+ }
+ else
+ {
+ /* The body was not text/xml, so we don't know what to do with it.
+ Toss anything that arrives. */
+ handler->discard_body = TRUE;
+ }
+ }
+
+ /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
+ to call the response handler again. That will start up the XML parsing,
+ or it will be dropped on the floor (per the decision above). */
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to Expat's XML_StartElementHandler */
+static void
+start_xml(void *userData, const char *raw_name, const char **attrs)
+{
+ svn_ra_serf__xml_parser_t *parser = userData;
+ svn_ra_serf__dav_props_t name;
+ apr_pool_t *scratch_pool;
+ svn_error_t *err;
+
+ if (parser->error)
+ return;
+
+ if (!parser->state)
+ svn_ra_serf__xml_push_state(parser, 0);
+
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool);
+
+ svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
+
+ err = parser->start(parser, name, attrs, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
+}
+
+
+/* Conforms to Expat's XML_EndElementHandler */
+static void
+end_xml(void *userData, const char *raw_name)
+{
+ svn_ra_serf__xml_parser_t *parser = userData;
+ svn_ra_serf__dav_props_t name;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool;
+
+ if (parser->error)
+ return;
+
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
+
+ err = parser->end(parser, name, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
+}
+
+
+/* Conforms to Expat's XML_CharacterDataHandler */
+static void
+cdata_xml(void *userData, const char *data, int len)
+{
+ svn_ra_serf__xml_parser_t *parser = userData;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool;
+
+ if (parser->error)
+ return;
+
+ if (!parser->state)
+ svn_ra_serf__xml_push_state(parser, 0);
+
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ err = parser->cdata(parser, data, len, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
+}
+
+/* Flip the requisite bits in CTX to indicate that processing of the
+ response is complete, adding the current "done item" to the list of
+ completed items. */
+static void
+add_done_item(svn_ra_serf__xml_parser_t *ctx)
+{
+ /* Make sure we don't add to DONE_LIST twice. */
+ if (!*ctx->done)
+ {
+ *ctx->done = TRUE;
+ if (ctx->done_list)
+ {
+ ctx->done_item->data = ctx->user_data;
+ ctx->done_item->next = *ctx->done_list;
+ *ctx->done_list = ctx->done_item;
+ }
+ }
+}
+
+
+static svn_error_t *
+write_to_pending(svn_ra_serf__xml_parser_t *ctx,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ if (ctx->pending == NULL)
+ {
+ ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
+ ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE,
+ SPILL_SIZE,
+ ctx->pool);
+ }
+
+ /* Copy the data into one or more chunks in the spill buffer. */
+ return svn_error_trace(svn_spillbuf__write(ctx->pending->buf,
+ data, len,
+ scratch_pool));
+}
+
+
+static svn_error_t *
+inject_to_parser(svn_ra_serf__xml_parser_t *ctx,
+ const char *data,
+ apr_size_t len,
+ const serf_status_line *sl)
+{
+ int xml_status;
+
+ xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0);
+ if (xml_status == XML_STATUS_ERROR && !ctx->ignore_errors)
+ {
+ if (sl == NULL)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML parsing failed"));
+
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML parsing failed: (%d %s)"),
+ sl->code, sl->reason);
+ }
+
+ if (ctx->error && !ctx->ignore_errors)
+ return svn_error_trace(ctx->error);
+
+ return SVN_NO_ERROR;
+}
+
+/* Apr pool cleanup handler to release an XML_Parser in success and error
+ conditions */
+static apr_status_t
+xml_parser_cleanup(void *baton)
+{
+ XML_Parser *xmlp = baton;
+
+ if (*xmlp)
+ {
+ (void) XML_ParserFree(*xmlp);
+ *xmlp = NULL;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Limit the amount of pending content to parse at once to < 100KB per
+ iteration. This number is chosen somewhat arbitrarely. Making it lower
+ will have a drastical negative impact on performance, whereas increasing it
+ increases the risk for connection timeouts.
+ */
+#define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5
+
+svn_error_t *
+svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
+ svn_boolean_t *network_eof,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t pending_empty = FALSE;
+ apr_size_t cur_read = 0;
+
+ /* Fast path exit: already paused, nothing to do, or already done. */
+ if (parser->paused || parser->pending == NULL || *parser->done)
+ {
+ *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Parsing the pending conten in the spillbuf will result in many disc i/o
+ operations. This can be so slow that we don't run the network event
+ processing loop often enough, resulting in timed out connections.
+
+ So we limit the amounts of bytes parsed per iteration.
+ */
+ while (cur_read < PENDING_TO_PARSE)
+ {
+ const char *data;
+ apr_size_t len;
+
+ /* Get a block of content, stopping the loop when we run out. */
+ SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf,
+ scratch_pool));
+ if (data)
+ {
+ /* Inject the content into the XML parser. */
+ SVN_ERR(inject_to_parser(parser, data, len, NULL));
+
+ /* If the XML parsing callbacks paused us, then we're done for now. */
+ if (parser->paused)
+ break;
+
+ cur_read += len;
+ }
+ else
+ {
+ /* The buffer is empty. */
+ pending_empty = TRUE;
+ break;
+ }
+ }
+
+ /* If the PENDING structures are empty *and* we consumed all content from
+ the network, then we're completely done with the parsing. */
+ if (pending_empty &&
+ parser->pending->network_eof)
+ {
+ SVN_ERR_ASSERT(parser->xmlp != NULL);
+
+ /* Tell the parser that no more content will be parsed. Ignore the
+ return status. We just don't care. */
+ (void) XML_Parse(parser->xmlp, NULL, 0, 1);
+
+ apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup);
+ parser->xmlp = NULL;
+ add_done_item(parser);
+ }
+
+ *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
+
+ return SVN_NO_ERROR;
+}
+#undef PENDING_TO_PARSE
+
+
+/* ### this is still broken conceptually. just shifting incrementally... */
+static svn_error_t *
+handle_server_error(serf_request_t *request,
+ serf_bucket_t *response,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t server_err = { 0 };
+ serf_bucket_t *hdrs;
+ const char *val;
+ apr_status_t err;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ /* ### we should figure out how to reuse begin_error_parsing */
+
+ server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err.contains_precondition_error = FALSE;
+ server_err.cdata = svn_stringbuf_create_empty(scratch_pool);
+ server_err.collect_cdata = FALSE;
+ server_err.parser.pool = server_err.error->pool;
+ server_err.parser.user_data = &server_err;
+ server_err.parser.start = start_error;
+ server_err.parser.end = end_error;
+ server_err.parser.cdata = cdata_error;
+ server_err.parser.done = &server_err.done;
+ server_err.parser.ignore_errors = TRUE;
+
+ /* We don't care about any errors except for SERVER_ERR.ERROR */
+ svn_error_clear(svn_ra_serf__handle_xml_parser(request,
+ response,
+ &server_err.parser,
+ scratch_pool));
+
+ /* ### checking DONE is silly. the above only parses whatever has
+ ### been received at the network interface. totally wrong. but
+ ### it is what we have for now (maintaining historical code),
+ ### until we fully migrate. */
+ if (server_err.done && server_err.error->apr_err == APR_SUCCESS)
+ {
+ svn_error_clear(server_err.error);
+ server_err.error = SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(server_err.error);
+ }
+
+ /* The only error that we will return is from the XML response body.
+ Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to
+ surface. */
+ err = drain_bucket(response);
+ if (err && !SERF_BUCKET_READ_ERROR(err))
+ return svn_ra_serf__wrap_err(err, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_xml_parser(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ serf_status_line sl;
+ apr_status_t status;
+ svn_ra_serf__xml_parser_t *ctx = baton;
+ svn_error_t *err;
+
+ /* ### get the HANDLER rather than fetching this. */
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ /* Woo-hoo. Nothing here to see. */
+ if (sl.code == 404 && !ctx->ignore_errors)
+ {
+ err = handle_server_error(request, response, pool);
+
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ add_done_item(ctx);
+
+ return svn_error_trace(err);
+ }
+
+ if (ctx->headers_baton == NULL)
+ ctx->headers_baton = serf_bucket_response_get_headers(response);
+ else if (ctx->headers_baton != serf_bucket_response_get_headers(response))
+ {
+ /* We got a new response to an existing parser...
+ This tells us the connection has restarted and we should continue
+ where we stopped last time.
+ */
+
+ /* Is this a second attempt?? */
+ if (!ctx->skip_size)
+ ctx->skip_size = ctx->read_size;
+
+ ctx->read_size = 0; /* New request, nothing read */
+ }
+
+ if (!ctx->xmlp)
+ {
+ ctx->xmlp = XML_ParserCreate(NULL);
+ apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup,
+ apr_pool_cleanup_null);
+ XML_SetUserData(ctx->xmlp, ctx);
+ XML_SetElementHandler(ctx->xmlp, start_xml, end_xml);
+ if (ctx->cdata)
+ {
+ XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml);
+ }
+ }
+
+ while (1)
+ {
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
+
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ ctx->read_size += len;
+
+ if (ctx->skip_size)
+ {
+ /* Handle restarted requests correctly: Skip what we already read */
+ apr_size_t skip;
+
+ if (ctx->skip_size >= ctx->read_size)
+ {
+ /* Eek. What did the file shrink or something? */
+ if (APR_STATUS_IS_EOF(status))
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* Skip on to the next iteration of this loop. */
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ continue;
+ }
+
+ skip = (apr_size_t)(len - (ctx->read_size - ctx->skip_size));
+ data += skip;
+ len -= skip;
+ ctx->skip_size = 0;
+ }
+
+ /* Note: once the callbacks invoked by inject_to_parser() sets the
+ PAUSED flag, then it will not be cleared. write_to_pending() will
+ only save the content. Logic outside of serf_context_run() will
+ clear that flag, as appropriate, along with processing the
+ content that we have placed into the PENDING buffer.
+
+ We want to save arriving content into the PENDING structures if
+ the parser has been paused, or we already have data in there (so
+ the arriving data is appended, rather than injected out of order) */
+ if (ctx->paused || HAS_PENDING_DATA(ctx->pending))
+ {
+ err = write_to_pending(ctx, data, len, pool);
+ }
+ else
+ {
+ err = inject_to_parser(ctx, data, len, &sl);
+ if (err)
+ {
+ /* Should have no errors if IGNORE_ERRORS is set. */
+ SVN_ERR_ASSERT(!ctx->ignore_errors);
+ }
+ }
+ if (err)
+ {
+ SVN_ERR_ASSERT(ctx->xmlp != NULL);
+
+ apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
+ add_done_item(ctx);
+ return svn_error_trace(err);
+ }
+
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ if (ctx->pending != NULL)
+ ctx->pending->network_eof = TRUE;
+
+ /* We just hit the end of the network content. If we have nothing
+ in the PENDING structures, then we're completely done. */
+ if (!HAS_PENDING_DATA(ctx->pending))
+ {
+ SVN_ERR_ASSERT(ctx->xmlp != NULL);
+
+ /* Ignore the return status. We just don't care. */
+ (void) XML_Parse(ctx->xmlp, NULL, 0, 1);
+
+ apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
+ add_done_item(ctx);
+ }
+
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ /* feed me! */
+ }
+ /* not reached */
+}
+
+
+apr_status_t
+svn_ra_serf__credentials_callback(char **username, char **password,
+ serf_request_t *request, void *baton,
+ int code, const char *authn_type,
+ const char *realm,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+ svn_ra_serf__session_t *session = handler->session;
+ void *creds;
+ svn_auth_cred_simple_t *simple_creds;
+ svn_error_t *err;
+
+ if (code == 401)
+ {
+ /* Use svn_auth_first_credentials if this is the first time we ask for
+ credentials during this session OR if the last time we asked
+ session->auth_state wasn't set (eg. if the credentials provider was
+ cancelled by the user). */
+ if (!session->auth_state)
+ {
+ err = svn_auth_first_credentials(&creds,
+ &session->auth_state,
+ SVN_AUTH_CRED_SIMPLE,
+ realm,
+ session->wc_callbacks->auth_baton,
+ session->pool);
+ }
+ else
+ {
+ err = svn_auth_next_credentials(&creds,
+ session->auth_state,
+ session->pool);
+ }
+
+ if (err)
+ {
+ (void) save_error(session, err);
+ return err->apr_err;
+ }
+
+ session->auth_attempts++;
+
+ if (!creds || session->auth_attempts > 4)
+ {
+ /* No more credentials. */
+ (void) save_error(session,
+ svn_error_create(
+ SVN_ERR_AUTHN_FAILED, NULL,
+ _("No more credentials or we tried too many "
+ "times.\nAuthentication failed")));
+ return SVN_ERR_AUTHN_FAILED;
+ }
+
+ simple_creds = creds;
+ *username = apr_pstrdup(pool, simple_creds->username);
+ *password = apr_pstrdup(pool, simple_creds->password);
+ }
+ else
+ {
+ *username = apr_pstrdup(pool, session->proxy_username);
+ *password = apr_pstrdup(pool, session->proxy_password);
+
+ session->proxy_auth_attempts++;
+
+ if (!session->proxy_username || session->proxy_auth_attempts > 4)
+ {
+ /* No more credentials. */
+ (void) save_error(session,
+ svn_error_create(
+ SVN_ERR_AUTHN_FAILED, NULL,
+ _("Proxy authentication failed")));
+ return SVN_ERR_AUTHN_FAILED;
+ }
+ }
+
+ handler->conn->last_status_code = code;
+
+ return APR_SUCCESS;
+}
+
+/* Wait for HTTP response status and headers, and invoke HANDLER->
+ response_handler() to carry out operation-specific processing.
+ Afterwards, check for connection close.
+
+ SERF_STATUS allows returning errors to serf without creating a
+ subversion error object.
+ */
+static svn_error_t *
+handle_response(serf_request_t *request,
+ serf_bucket_t *response,
+ svn_ra_serf__handler_t *handler,
+ apr_status_t *serf_status,
+ apr_pool_t *scratch_pool)
+{
+ apr_status_t status;
+ svn_error_t *err;
+
+ /* ### need to verify whether this already gets init'd on every
+ ### successful exit. for an error-exit, it will (properly) be
+ ### ignored by the caller. */
+ *serf_status = APR_SUCCESS;
+
+ if (!response)
+ {
+ /* Uh-oh. Our connection died. */
+ if (handler->response_error)
+ SVN_ERR(handler->response_error(request, response, 0,
+ handler->response_error_baton));
+
+ /* Requeue another request for this handler.
+ ### how do we know if the handler can deal with this?! */
+ svn_ra_serf__request_create(handler);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* If we're reading the body, then skip all this preparation. */
+ if (handler->reading_body)
+ goto process_body;
+
+ /* Copy the Status-Line info into HANDLER, if we don't yet have it. */
+ if (handler->sline.version == 0)
+ {
+ serf_status_line sl;
+
+ status = serf_bucket_response_status(response, &sl);
+ if (status != APR_SUCCESS)
+ {
+ /* The response line is not (yet) ready, or some other error. */
+ *serf_status = status;
+ return SVN_NO_ERROR; /* Handled by serf */
+ }
+
+ /* If we got APR_SUCCESS, then we should have Status-Line info. */
+ SVN_ERR_ASSERT(sl.version != 0);
+
+ handler->sline = sl;
+ handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
+
+ /* HTTP/1.1? (or later) */
+ if (sl.version != SERF_HTTP_10)
+ handler->session->http10 = FALSE;
+ }
+
+ /* Keep reading from the network until we've read all the headers. */
+ status = serf_bucket_response_wait_for_headers(response);
+ if (status)
+ {
+ /* The typical "error" will be APR_EAGAIN, meaning that more input
+ from the network is required to complete the reading of the
+ headers. */
+ if (!APR_STATUS_IS_EOF(status))
+ {
+ /* Either the headers are not (yet) complete, or there really
+ was an error. */
+ *serf_status = status;
+ return SVN_NO_ERROR;
+ }
+
+ /* wait_for_headers() will return EOF if there is no body in this
+ response, or if we completely read the body. The latter is not
+ true since we would have set READING_BODY to get the body read,
+ and we would not be back to this code block.
+
+ It can also return EOF if we truly hit EOF while (say) processing
+ the headers. aka Badness. */
+
+ /* Cases where a lack of a response body (via EOF) is okay:
+ * - A HEAD request
+ * - 204/304 response
+ *
+ * Otherwise, if we get an EOF here, something went really wrong: either
+ * the server closed on us early or we're reading too much. Either way,
+ * scream loudly.
+ */
+ if (strcmp(handler->method, "HEAD") != 0
+ && handler->sline.code != 204
+ && handler->sline.code != 304)
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ svn_ra_serf__wrap_err(status, NULL),
+ _("Premature EOF seen from server"
+ " (http status=%d)"),
+ handler->sline.code);
+
+ /* In case anything else arrives... discard it. */
+ handler->discard_body = TRUE;
+
+ return err;
+ }
+ }
+
+ /* ... and set up the header fields in HANDLER. */
+ handler->location = response_get_location(response,
+ handler->session->session_url_str,
+ handler->handler_pool,
+ scratch_pool);
+
+ /* On the last request, we failed authentication. We succeeded this time,
+ so let's save away these credentials. */
+ if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
+ {
+ SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
+ handler->session->pool));
+ handler->session->auth_attempts = 0;
+ handler->session->auth_state = NULL;
+ }
+ handler->conn->last_status_code = handler->sline.code;
+
+ if (handler->sline.code == 405
+ || handler->sline.code == 408
+ || handler->sline.code == 409
+ || handler->sline.code >= 500)
+ {
+ /* 405 Method Not allowed.
+ 408 Request Timeout
+ 409 Conflict: can indicate a hook error.
+ 5xx (Internal) Server error. */
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = begin_error_parsing(start_error, end_error, cdata_error,
+ handler->handler_pool);
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
+
+ handler->server_error = server_err;
+ }
+ else
+ {
+ handler->discard_body = TRUE;
+
+ if (!handler->session->pending_error)
+ {
+ apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
+ /* 405 == Method Not Allowed (Occurs when trying to lock a working
+ copy path which no longer exists at HEAD in the repository. */
+ if (handler->sline.code == 405
+ && strcmp(handler->method, "LOCK") == 0)
+ apr_err = SVN_ERR_FS_OUT_OF_DATE;
+
+ handler->session->pending_error =
+ svn_error_createf(apr_err, NULL,
+ _("%s request on '%s' failed: %d %s"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason);
+ }
+ }
+ }
+
+ /* Stop processing the above, on every packet arrival. */
+ handler->reading_body = TRUE;
+
+ process_body:
+
+ /* We've been instructed to ignore the body. Drain whatever is present. */
+ if (handler->discard_body)
+ {
+ *serf_status = drain_bucket(response);
+
+ /* If the handler hasn't set done (which it shouldn't have) and
+ we now have the EOF, go ahead and set it so that we can stop
+ our context loops.
+ */
+ if (!handler->done && APR_STATUS_IS_EOF(*serf_status))
+ handler->done = TRUE;
+
+ return SVN_NO_ERROR;
+ }
+
+ /* If we are supposed to parse the body as a server_error, then do
+ that now. */
+ if (handler->server_error != NULL)
+ {
+ err = svn_ra_serf__handle_xml_parser(request, response,
+ &handler->server_error->parser,
+ scratch_pool);
+
+ /* If we do not receive an error or it is a non-transient error, return
+ immediately.
+
+ APR_EOF will be returned when parsing is complete.
+
+ APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through
+ parsing and the network has no more data right now. If we receive that,
+ clear the error and return - allowing serf to wait for more data.
+ */
+ if (!err || SERF_BUCKET_READ_ERROR(err->apr_err))
+ return svn_error_trace(err);
+
+ if (!APR_STATUS_IS_EOF(err->apr_err))
+ {
+ *serf_status = err->apr_err;
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ /* Clear the EOF. We don't need it. */
+ svn_error_clear(err);
+
+ /* If the parsing is done, and we did not extract an error, then
+ simply toss everything, and anything else that might arrive.
+ The higher-level code will need to investigate HANDLER->SLINE,
+ as we have no further information for them. */
+ if (handler->done
+ && handler->server_error->error->apr_err == APR_SUCCESS)
+ {
+ svn_error_clear(handler->server_error->error);
+
+ /* Stop parsing for a server error. */
+ handler->server_error = NULL;
+
+ /* If anything arrives after this, then just discard it. */
+ handler->discard_body = TRUE;
+ }
+
+ *serf_status = APR_EOF;
+ return SVN_NO_ERROR;
+ }
+
+ /* Pass the body along to the registered response handler. */
+ err = handler->response_handler(request, response,
+ handler->response_baton,
+ scratch_pool);
+
+ if (err
+ && (!SERF_BUCKET_READ_ERROR(err->apr_err)
+ || APR_STATUS_IS_ECONNRESET(err->apr_err)
+ || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
+ {
+ /* These errors are special cased in serf
+ ### We hope no handler returns these by accident. */
+ *serf_status = err->apr_err;
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+/* Implements serf_response_handler_t for handle_response. Storing
+ errors in handler->session->pending_error if appropriate. */
+static apr_status_t
+handle_response_cb(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+ svn_error_t *err;
+ apr_status_t inner_status;
+ apr_status_t outer_status;
+
+ err = svn_error_trace(handle_response(request, response,
+ handler, &inner_status,
+ scratch_pool));
+
+ /* Select the right status value to return. */
+ outer_status = save_error(handler->session, err);
+ if (!outer_status)
+ outer_status = inner_status;
+
+ /* Make sure the DONE flag is set properly. */
+ if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
+ handler->done = TRUE;
+
+ return outer_status;
+}
+
+/* Perform basic request setup, with special handling for HEAD requests,
+ and finer-grained callbacks invoked (if non-NULL) to produce the request
+ headers and body. */
+static svn_error_t *
+setup_request(serf_request_t *request,
+ svn_ra_serf__handler_t *handler,
+ serf_bucket_t **req_bkt,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_t *body_bkt;
+ serf_bucket_t *headers_bkt;
+ const char *accept_encoding;
+
+ if (handler->body_delegate)
+ {
+ serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
+
+ /* ### should pass the scratch_pool */
+ SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
+ bkt_alloc, request_pool));
+ }
+ else
+ {
+ body_bkt = NULL;
+ }
+
+ if (handler->custom_accept_encoding)
+ {
+ accept_encoding = NULL;
+ }
+ else if (handler->session->using_compression)
+ {
+ /* Accept gzip compression if enabled. */
+ accept_encoding = "gzip";
+ }
+ else
+ {
+ accept_encoding = NULL;
+ }
+
+ SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
+ handler->session, handler->method, handler->path,
+ body_bkt, handler->body_type, accept_encoding,
+ request_pool, scratch_pool));
+
+ if (handler->header_delegate)
+ {
+ /* ### should pass the scratch_pool */
+ SVN_ERR(handler->header_delegate(headers_bkt,
+ handler->header_delegate_baton,
+ request_pool));
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Implements the serf_request_setup_t interface (which sets up both a
+ request and its response handler callback). Handles errors for
+ setup_request_cb */
+static apr_status_t
+setup_request_cb(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *s_handler,
+ void **s_handler_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__handler_t *handler = setup_baton;
+ svn_error_t *err;
+
+ /* ### construct a scratch_pool? serf gives us a pool that will live for
+ ### the duration of the request. */
+ apr_pool_t *scratch_pool = pool;
+
+ if (strcmp(handler->method, "HEAD") == 0)
+ *acceptor = accept_head;
+ else
+ *acceptor = accept_response;
+ *acceptor_baton = handler->session;
+
+ *s_handler = handle_response_cb;
+ *s_handler_baton = handler;
+
+ err = svn_error_trace(setup_request(request, handler, req_bkt,
+ pool /* request_pool */, scratch_pool));
+
+ return save_error(handler->session, err);
+}
+
+void
+svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
+{
+ SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL);
+
+ /* In case HANDLER is re-queued, reset the various transient fields.
+
+ ### prior to recent changes, HANDLER was constant. maybe we should
+ ### break out these processing fields, apart from the request
+ ### definition. */
+ handler->done = FALSE;
+ handler->server_error = NULL;
+ handler->sline.version = 0;
+ handler->location = NULL;
+ handler->reading_body = FALSE;
+ handler->discard_body = FALSE;
+
+ /* ### do we ever alter the >response_handler? */
+
+ /* ### do we need to hold onto the returned request object, or just
+ ### not worry about it (the serf ctx will manage it). */
+ (void) serf_connection_request_create(handler->conn->conn,
+ setup_request_cb, handler);
+}
+
+
+svn_error_t *
+svn_ra_serf__discover_vcc(const char **vcc_url,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ const char *path;
+ const char *relative_path;
+ const char *uuid;
+
+ /* If we've already got the information our caller seeks, just return it. */
+ if (session->vcc_url && session->repos_root_str)
+ {
+ *vcc_url = session->vcc_url;
+ return SVN_NO_ERROR;
+ }
+
+ /* If no connection is provided, use the default one. */
+ if (! conn)
+ {
+ conn = session->conns[0];
+ }
+
+ path = session->session_url.path;
+ *vcc_url = NULL;
+ uuid = NULL;
+
+ do
+ {
+ apr_hash_t *props;
+ svn_error_t *err;
+
+ err = svn_ra_serf__fetch_node_props(&props, conn,
+ path, SVN_INVALID_REVNUM,
+ base_props, pool, pool);
+ if (! err)
+ {
+ apr_hash_t *ns_props;
+
+ ns_props = apr_hash_get(props, "DAV:", 4);
+ *vcc_url = svn_prop_get_value(ns_props,
+ "version-controlled-configuration");
+
+ ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
+ relative_path = svn_prop_get_value(ns_props,
+ "baseline-relative-path");
+ uuid = svn_prop_get_value(ns_props, "repository-uuid");
+ break;
+ }
+ else
+ {
+ if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
+ (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
+ {
+ return svn_error_trace(err); /* found a _real_ error */
+ }
+ else
+ {
+ /* This happens when the file is missing in HEAD. */
+ svn_error_clear(err);
+
+ /* Okay, strip off a component from PATH. */
+ path = svn_urlpath__dirname(path, pool);
+
+ /* An error occurred on conns. serf 0.4.0 remembers that
+ the connection had a problem. We need to reset it, in
+ order to use it again. */
+ serf_connection_reset(conn->conn);
+ }
+ }
+ }
+ while ((path[0] != '\0')
+ && (! (path[0] == '/' && path[1] == '\0')));
+
+ if (!*vcc_url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The PROPFIND response did not include the "
+ "requested version-controlled-configuration "
+ "value"));
+ }
+
+ /* Store our VCC in our cache. */
+ if (!session->vcc_url)
+ {
+ session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
+ }
+
+ /* Update our cached repository root URL. */
+ if (!session->repos_root_str)
+ {
+ svn_stringbuf_t *url_buf;
+
+ url_buf = svn_stringbuf_create(path, pool);
+
+ svn_path_remove_components(url_buf,
+ svn_path_component_count(relative_path));
+
+ /* Now recreate the root_url. */
+ session->repos_root = session->session_url;
+ session->repos_root.path =
+ (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
+ session->repos_root_str =
+ svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
+ &session->repos_root, 0),
+ session->pool);
+ }
+
+ /* Store the repository UUID in the cache. */
+ if (!session->uuid)
+ {
+ session->uuid = apr_pstrdup(session->pool, uuid);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_relative_path(const char **rel_path,
+ const char *orig_path,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ const char *decoded_root, *decoded_orig;
+
+ if (! session->repos_root.path)
+ {
+ const char *vcc_url;
+
+ /* This should only happen if we haven't detected HTTP v2
+ support from the server. */
+ assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ /* We don't actually care about the VCC_URL, but this API
+ promises to populate the session's root-url cache, and that's
+ what we really want. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
+ conn ? conn : session->conns[0],
+ pool));
+ }
+
+ decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
+ decoded_orig = svn_path_uri_decode(orig_path, pool);
+ *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
+ SVN_ERR_ASSERT(*rel_path != NULL);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__report_resource(const char **report_target,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ /* If we have HTTP v2 support, we want to report against the 'me'
+ resource. */
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ *report_target = apr_pstrdup(pool, session->me_resource);
+
+ /* Otherwise, we'll use the default VCC. */
+ else
+ SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__error_on_status(int status_code,
+ const char *path,
+ const char *location)
+{
+ switch(status_code)
+ {
+ case 301:
+ case 302:
+ case 307:
+ return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
+ (status_code == 301)
+ ? _("Repository moved permanently to '%s';"
+ " please relocate")
+ : _("Repository moved temporarily to '%s';"
+ " please relocate"), location);
+ case 403:
+ return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
+ _("Access to '%s' forbidden"), path);
+
+ case 404:
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("'%s' path not found"), path);
+ case 423:
+ return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
+ _("'%s': no lock token available"), path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
+ svn_delta_shim_callbacks_t *callbacks)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ session->shim_callbacks = callbacks;
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to Expat's XML_StartElementHandler */
+static void
+expat_start(void *userData, const char *raw_name, const char **attrs)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_start(ectx->xmlctx,
+ raw_name, attrs));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Conforms to Expat's XML_EndElementHandler */
+static void
+expat_end(void *userData, const char *raw_name)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Conforms to Expat's XML_CharacterDataHandler */
+static void
+expat_cdata(void *userData, const char *data, int len)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+expat_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct expat_ctx_t *ectx = baton;
+
+ if (!ectx->parser)
+ {
+ ectx->parser = XML_ParserCreate(NULL);
+ apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup, apr_pool_cleanup_null);
+ XML_SetUserData(ectx->parser, ectx);
+ XML_SetElementHandler(ectx->parser, expat_start, expat_end);
+ XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
+ }
+
+ /* ### TODO: sline.code < 200 should really be handled by the core */
+ if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300))
+ {
+ /* By deferring to expect_empty_body(), it will make a choice on
+ how to handle the body. Whatever the decision, the core handler
+ will take over, and we will not be called again. */
+ return svn_error_trace(svn_ra_serf__expect_empty_body(
+ request, response, ectx->handler,
+ scratch_pool));
+ }
+
+ while (1)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+ int expat_status;
+
+ status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return svn_ra_serf__wrap_err(status, NULL);
+
+#if 0
+ /* ### move restart/skip into the core handler */
+ ectx->handler->read_size += len;
+#endif
+
+ /* ### move PAUSED behavior to a new response handler that can feed
+ ### an inner handler, or can pause for a while. */
+
+ /* ### should we have an IGNORE_ERRORS flag like the v1 parser? */
+
+ expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */);
+
+ /* We need to check INNER_ERROR first. This is an error from the
+ callbacks that has been "dropped off" for us to retrieve. On
+ current Expat parsers, we stop the parser when an error occurs,
+ so we want to ignore EXPAT_STATUS (which reports the stoppage).
+
+ If an error is not present, THEN we go ahead and look for parsing
+ errors. */
+ if (ectx->inner_error)
+ {
+ apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup);
+ return svn_error_trace(ectx->inner_error);
+ }
+ if (expat_status == XML_STATUS_ERROR)
+ return svn_error_createf(SVN_ERR_XML_MALFORMED,
+ ectx->inner_error,
+ _("The %s response contains invalid XML"
+ " (%d %s)"),
+ ectx->handler->method,
+ ectx->handler->sline.code,
+ ectx->handler->sline.reason);
+
+ /* The parsing went fine. What has the bucket told us? */
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ /* Tell expat we've reached the end of the content. Ignore the
+ return status. We just don't care. */
+ (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */);
+
+ svn_ra_serf__xml_context_destroy(ectx->xmlctx);
+ apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup);
+
+ /* ### should check XMLCTX to see if it has returned to the
+ ### INITIAL state. we may have ended early... */
+ }
+
+ if (status && !SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ }
+
+ /* NOTREACHED */
+}
+
+
+svn_ra_serf__handler_t *
+svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__handler_t *handler;
+ struct expat_ctx_t *ectx;
+
+ ectx = apr_pcalloc(result_pool, sizeof(*ectx));
+ ectx->xmlctx = xmlctx;
+ ectx->parser = NULL;
+ ectx->cleanup_pool = result_pool;
+
+
+ handler = apr_pcalloc(result_pool, sizeof(*handler));
+ handler->handler_pool = result_pool;
+ handler->response_handler = expat_response_handler;
+ handler->response_baton = ectx;
+
+ ectx->handler = handler;
+
+ return handler;
+}
OpenPOWER on IntegriCloud