summaryrefslogtreecommitdiff
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.c1549
1 files changed, 353 insertions, 1196 deletions
diff --git a/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c
index 8f6c1bb..5490dde 100644
--- a/subversion/libsvn_ra_serf/util.c
+++ b/subversion/libsvn_ra_serf/util.c
@@ -32,83 +32,22 @@
#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 "private/svn_auth_private.h"
#include "private/svn_cert.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 },
@@ -192,6 +131,7 @@ construct_realm(svn_ra_serf__session_t *session,
static char *
convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
{
+ const char *cn = svn_hash_gets(org, "CN");
const char *org_unit = svn_hash_gets(org, "OU");
const char *org_name = svn_hash_gets(org, "O");
const char *locality = svn_hash_gets(org, "L");
@@ -200,6 +140,12 @@ convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
const char *email = svn_hash_gets(org, "E");
svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
+ if (cn)
+ {
+ svn_stringbuf_appendcstr(buf, cn);
+ svn_stringbuf_appendcstr(buf, ", ");
+ }
+
if (org_unit)
{
svn_stringbuf_appendcstr(buf, org_unit);
@@ -285,7 +231,6 @@ ssl_server_cert(void *baton, int failures,
### This should really be handled by serf, which should pass an error
for this case, but that has backwards compatibility issues. */
apr_array_header_t *san;
- svn_boolean_t found_san_entry = FALSE;
svn_boolean_t found_matching_hostname = FALSE;
svn_string_t *actual_hostname =
svn_string_create(conn->session->session_url.hostname, scratch_pool);
@@ -293,11 +238,16 @@ ssl_server_cert(void *baton, int failures,
serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
san = svn_hash_gets(serf_cert, "subjectAltName");
- /* Try to find matching server name via subjectAltName first... */
- if (san)
+ /* Match server certificate CN with the hostname of the server iff
+ * we didn't find any subjectAltName fields and try to match them.
+ * Per RFC 2818 they are authoritative if present and CommonName
+ * should be ignored. NOTE: This isn't 100% correct since serf
+ * only loads the subjectAltName hash with dNSNames, technically
+ * we should ignore the CommonName if any subjectAltName entry
+ * exists even if it is one we don't support. */
+ if (san && san->nelts > 0)
{
int i;
- found_san_entry = san->nelts > 0;
for (i = 0; i < san->nelts; i++)
{
const char *s = APR_ARRAY_IDX(san, i, const char*);
@@ -310,12 +260,7 @@ ssl_server_cert(void *baton, int failures,
}
}
}
-
- /* Match server certificate CN with the hostname of the server iff
- * we didn't find any subjectAltName fields and try to match them.
- * Per RFC 2818 they are authoritative if present and CommonName
- * should be ignored. */
- if (!found_matching_hostname && !found_san_entry)
+ else
{
const char *hostname = NULL;
@@ -368,11 +313,11 @@ ssl_server_cert(void *baton, int failures,
{
svn_error_t *err;
- svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ svn_auth_set_parameter(conn->session->auth_baton,
SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
&cert_info);
- svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ svn_auth_set_parameter(conn->session->auth_baton,
SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
&svn_failures);
@@ -382,13 +327,13 @@ ssl_server_cert(void *baton, int failures,
err = svn_auth_first_credentials(&creds, &state,
SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
realmstring,
- conn->session->wc_callbacks->auth_baton,
+ conn->session->auth_baton,
scratch_pool);
- svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ svn_auth_set_parameter(conn->session->auth_baton,
SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
- svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ svn_auth_set_parameter(conn->session->auth_baton,
SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);
if (err)
@@ -415,11 +360,11 @@ ssl_server_cert(void *baton, int failures,
return APR_SUCCESS;
}
- svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ svn_auth_set_parameter(conn->session->auth_baton,
SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
&svn_failures);
- svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ svn_auth_set_parameter(conn->session->auth_baton,
SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
&cert_info);
@@ -428,7 +373,7 @@ ssl_server_cert(void *baton, int failures,
SVN_ERR(svn_auth_first_credentials(&creds, &state,
SVN_AUTH_CRED_SSL_SERVER_TRUST,
realmstring,
- conn->session->wc_callbacks->auth_baton,
+ conn->session->auth_baton,
scratch_pool));
if (creds)
{
@@ -449,7 +394,7 @@ ssl_server_cert(void *baton, int failures,
}
}
- svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ svn_auth_set_parameter(conn->session->auth_baton,
SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
/* Are there non accepted failures left? */
@@ -622,6 +567,7 @@ accept_response(serf_request_t *request,
void *acceptor_baton,
apr_pool_t *pool)
{
+ /* svn_ra_serf__handler_t *handler = acceptor_baton; */
serf_bucket_t *c;
serf_bucket_alloc_t *bkt_alloc;
@@ -639,6 +585,7 @@ accept_head(serf_request_t *request,
void *acceptor_baton,
apr_pool_t *pool)
{
+ /* svn_ra_serf__handler_t *handler = acceptor_baton; */
serf_bucket_t *response;
response = accept_response(request, stream, acceptor_baton, pool);
@@ -656,7 +603,7 @@ connection_closed(svn_ra_serf__connection_t *conn,
{
if (why)
{
- return svn_error_wrap_apr(why, NULL);
+ return svn_ra_serf__wrap_err(why, NULL);
}
if (conn->session->using_ssl)
@@ -701,7 +648,7 @@ handle_client_cert(void *data,
&conn->ssl_client_auth_state,
SVN_AUTH_CRED_SSL_CLIENT_CERT,
realm,
- session->wc_callbacks->auth_baton,
+ session->auth_baton,
pool));
}
else
@@ -753,7 +700,7 @@ handle_client_cert_pw(void *data,
&conn->ssl_client_pw_auth_state,
SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
cert_path,
- session->wc_callbacks->auth_baton,
+ session->auth_baton,
pool));
}
else
@@ -804,6 +751,9 @@ apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
*
* If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
*
+ * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers
+ * to request.
+ *
* 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.
@@ -816,6 +766,7 @@ setup_serf_req(serf_request_t *request,
const char *method, const char *url,
serf_bucket_t *body_bkt, const char *content_type,
const char *accept_encoding,
+ svn_boolean_t dav_headers,
apr_pool_t *request_pool,
apr_pool_t *scratch_pool)
{
@@ -882,12 +833,86 @@ setup_serf_req(serf_request_t *request,
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);
+ /* These headers need to be sent with every request that might need
+ capability processing (e.g. during commit, reports, etc.), see
+ issue #3255 ("mod_dav_svn does not pass client capabilities to
+ start-commit hooks") for why.
+
+ Some request types like GET/HEAD/PROPFIND are unaware of capability
+ handling; and in some cases the responses can even be cached by
+ proxies, so we don't have to send these hearders there. */
+ if (dav_headers)
+ {
+ 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(svn_ra_serf__session_t *sess,
+ apr_interval_time_t *waittime_left,
+ apr_pool_t *scratch_pool)
+{
+ apr_status_t status;
+ svn_error_t *err;
+ assert(sess->pending_error == SVN_NO_ERROR);
+
+ if (sess->cancel_func)
+ SVN_ERR(sess->cancel_func(sess->cancel_baton));
+
+ status = serf_context_run(sess->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
+ scratch_pool);
+
+ 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))
+ {
+ 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_compose_create(
+ err,
+ svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out")));
+ }
+ }
+ }
+ else
+ {
+ *waittime_left = sess->timeout;
+ }
+
+ SVN_ERR(err);
+ if (status)
+ {
+ /* ### This omits SVN_WARNING, and possibly relies on the fact that
+ ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */
+ 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"));
+ }
return SVN_NO_ERROR;
}
@@ -905,63 +930,11 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done,
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))
- {
- 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_compose_create(
- err,
- 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"));
- }
+ SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));
/* Debugging purposes only! */
for (i = 0; i < sess->num_conns; i++)
@@ -974,6 +947,22 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done,
return SVN_NO_ERROR;
}
+/* Ensure that a handler is no longer scheduled on the connection.
+
+ Eventually serf will have a reliable way to cancel existing requests,
+ but currently it doesn't even have a way to relyable identify a request
+ after rescheduling, for auth reasons.
+
+ So the only thing we can do today is reset the connection, which
+ will cancel all outstanding requests and prepare the connection
+ for re-use.
+*/
+static void
+svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler)
+{
+ serf_connection_reset(handler->conn->conn);
+ handler->scheduled = FALSE;
+}
svn_error_t *
svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
@@ -988,130 +977,18 @@ svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
scratch_pool);
- /* A callback invocation has been canceled. In this simple case of
- context_run_one, we can keep the ra-session operational by resetting
- the connection.
-
- If we don't do this, the next context run will notice that the connection
- is still in the error state and will just return SVN_ERR_CEASE_INVOCATION
- (=the last error for the connection) again */
- if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
- {
- apr_status_t status = serf_connection_reset(handler->conn->conn);
-
- if (status)
- err = svn_error_compose_create(err,
- svn_ra_serf__wrap_err(status, NULL));
- }
-
- if (handler->server_error)
+ if (handler->scheduled)
{
- err = svn_error_compose_create(err, handler->server_error->error);
- handler->server_error = NULL;
+ /* We reset the connection (breaking pipelining, etc.), as
+ if we didn't the next data would still be handled by this handler,
+ which is done as far as our caller is concerned. */
+ svn_ra_serf__unschedule_handler(handler);
}
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
@@ -1131,28 +1008,7 @@ drain_bucket(serf_bucket_t *bucket)
}
-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 *
@@ -1241,7 +1097,7 @@ svn_ra_serf__expect_empty_body(serf_request_t *request,
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. */
+ XML parsing callbacks. We want to look for the -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
@@ -1251,21 +1107,22 @@ svn_ra_serf__expect_empty_body(serf_request_t *request,
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)
+ if (val
+ && (handler->sline.code < 200 || handler->sline.code >= 300)
+ && 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;
+ SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
+ FALSE,
+ handler->handler_pool,
+ handler->handler_pool));
handler->server_error = server_err;
}
else
{
- /* The body was not text/xml, so we don't know what to do with it.
+ /* The body was not text/xml, or we got a success code.
Toss anything that arrives. */
handler->discard_body = TRUE;
}
@@ -1277,631 +1134,6 @@ svn_ra_serf__expect_empty_body(serf_request_t *request,
}
-/* 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 (! ctx->ignore_errors)
- {
- SVN_ERR(ctx->error);
-
- if (xml_status != XML_STATUS_OK)
- {
- 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);
- }
- }
-
- 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)
- {
- int xml_status;
- SVN_ERR_ASSERT(parser->xmlp != NULL);
-
- /* Tell the parser that no more content will be parsed. */
- xml_status = XML_Parse(parser->xmlp, NULL, 0, 1);
-
- apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup);
- parser->xmlp = NULL;
-
- if (! parser->ignore_errors)
- {
- SVN_ERR(parser->error);
-
- if (xml_status != XML_STATUS_OK)
- {
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("XML parsing failed"));
- }
- }
-
- 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->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);
- }
-
- /* 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))
- {
- int xml_status;
- SVN_ERR_ASSERT(ctx->xmlp != NULL);
-
- xml_status = XML_Parse(ctx->xmlp, NULL, 0, 1);
-
- apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
-
- if (! ctx->ignore_errors)
- {
- SVN_ERR(ctx->error);
-
- if (xml_status != XML_STATUS_OK)
- {
- return svn_error_create(
- SVN_ERR_XML_MALFORMED, NULL,
- _("The XML response contains invalid XML"));
- }
- }
-
- 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,
@@ -1927,7 +1159,7 @@ svn_ra_serf__credentials_callback(char **username, char **password,
&session->auth_state,
SVN_AUTH_CRED_SIMPLE,
realm,
- session->wc_callbacks->auth_baton,
+ session->auth_baton,
session->pool);
}
else
@@ -2008,6 +1240,8 @@ handle_response(serf_request_t *request,
if (!response)
{
/* Uh-oh. Our connection died. */
+ handler->scheduled = FALSE;
+
if (handler->response_error)
{
/* Give a handler chance to prevent request requeue. */
@@ -2126,10 +1360,7 @@ handle_response(serf_request_t *request,
}
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)
+ if (handler->sline.code >= 400)
{
/* 405 Method Not allowed.
408 Request Timeout
@@ -2144,35 +1375,22 @@ handle_response(serf_request_t *request,
{
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;
+ SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
+ FALSE,
+ handler->handler_pool,
+ handler->handler_pool));
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);
- }
}
}
+ else if (handler->sline.code <= 199)
+ {
+ handler->discard_body = TRUE;
+ }
/* Stop processing the above, on every packet arrival. */
handler->reading_body = TRUE;
@@ -2184,13 +1402,6 @@ handle_response(serf_request_t *request,
{
*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;
}
@@ -2198,50 +1409,12 @@ handle_response(serf_request_t *request,
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;
+ return svn_error_trace(
+ svn_ra_serf__handle_server_error(handler->server_error,
+ handler,
+ request, response,
+ serf_status,
+ scratch_pool));
}
/* Pass the body along to the registered response handler. */
@@ -2271,12 +1444,13 @@ static apr_status_t
handle_response_cb(serf_request_t *request,
serf_bucket_t *response,
void *baton,
- apr_pool_t *scratch_pool)
+ apr_pool_t *response_pool)
{
svn_ra_serf__handler_t *handler = baton;
svn_error_t *err;
apr_status_t inner_status;
apr_status_t outer_status;
+ apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */
err = svn_error_trace(handle_response(request, response,
handler, &inner_status,
@@ -2287,9 +1461,34 @@ handle_response_cb(serf_request_t *request,
if (!outer_status)
outer_status = inner_status;
- /* Make sure the DONE flag is set properly. */
+ /* Make sure the DONE flag is set properly and requests are cleaned up. */
if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
- handler->done = TRUE;
+ {
+ svn_ra_serf__session_t *sess = handler->session;
+ handler->done = TRUE;
+ handler->scheduled = FALSE;
+ outer_status = APR_EOF;
+
+ /* We use a cached handler->session here to allow handler to free the
+ memory containing the handler */
+ save_error(sess,
+ handler->done_delegate(request, handler->done_delegate_baton,
+ scratch_pool));
+ }
+ else if (SERF_BUCKET_READ_ERROR(outer_status)
+ && handler->session->pending_error)
+ {
+ handler->discard_body = TRUE; /* Discard further data */
+ handler->done = TRUE; /* Mark as done */
+ /* handler->scheduled is still TRUE, as we still expect data.
+ If we would return an error outer-status the connection
+ would have to be restarted. With scheduled still TRUE
+ destroying the handler's pool will still reset the
+ connection, avoiding the posibility of returning
+ an error for this handler when a new request is
+ scheduled. */
+ outer_status = APR_EAGAIN; /* Exit context loop */
+ }
return outer_status;
}
@@ -2312,9 +1511,8 @@ setup_request(serf_request_t *request,
{
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));
+ bkt_alloc, request_pool, scratch_pool));
}
else
{
@@ -2338,17 +1536,17 @@ setup_request(serf_request_t *request,
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));
+ !handler->no_dav_headers, 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));
+ request_pool, scratch_pool));
}
- return APR_SUCCESS;
+ return SVN_NO_ERROR;
}
/* Implements the serf_request_setup_t interface (which sets up both a
@@ -2362,51 +1560,58 @@ setup_request_cb(serf_request_t *request,
void **acceptor_baton,
serf_response_handler_t *s_handler,
void **s_handler_baton,
- apr_pool_t *pool)
+ apr_pool_t *request_pool)
{
svn_ra_serf__handler_t *handler = setup_baton;
+ apr_pool_t *scratch_pool;
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;
+ /* Construct a scratch_pool? serf gives us a pool that will live for
+ the duration of the request. But requests are retried in some cases */
+ scratch_pool = svn_pool_create(request_pool);
if (strcmp(handler->method, "HEAD") == 0)
*acceptor = accept_head;
else
*acceptor = accept_response;
- *acceptor_baton = handler->session;
+ *acceptor_baton = handler;
*s_handler = handle_response_cb;
*s_handler_baton = handler;
err = svn_error_trace(setup_request(request, handler, req_bkt,
- pool /* request_pool */, scratch_pool));
+ request_pool, scratch_pool));
+ svn_pool_destroy(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.
+ SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL
+ && !handler->scheduled);
- ### prior to recent changes, HANDLER was constant. maybe we should
- ### break out these processing fields, apart from the request
- ### definition. */
+ /* In case HANDLER is re-queued, reset the various transient fields. */
handler->done = FALSE;
handler->server_error = NULL;
handler->sline.version = 0;
handler->location = NULL;
handler->reading_body = FALSE;
handler->discard_body = FALSE;
+ handler->scheduled = TRUE;
+
+ /* Keeping track of the returned request object would be nice, but doesn't
+ work the way we would expect in ra_serf..
+
+ Serf sometimes creates a new request for us (and destroys the old one)
+ without telling, like when authentication failed (401/407 response.
- /* ### do we ever alter the >response_handler? */
+ We 'just' trust serf to do the right thing and expect it to tell us
+ when the state of the request changes.
- /* ### do we need to hold onto the returned request object, or just
- ### not worry about it (the serf ctx will manage it). */
+ ### I fixed a request leak in serf in r2258 on auth failures.
+ */
(void) serf_connection_request_create(handler->conn->conn,
setup_request_cb, handler);
}
@@ -2415,8 +1620,7 @@ svn_ra_serf__request_create(svn_ra_serf__handler_t *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)
+ apr_pool_t *scratch_pool)
{
const char *path;
const char *relative_path;
@@ -2429,12 +1633,6 @@ svn_ra_serf__discover_vcc(const char **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;
@@ -2444,9 +1642,10 @@ svn_ra_serf__discover_vcc(const char **vcc_url,
apr_hash_t *props;
svn_error_t *err;
- err = svn_ra_serf__fetch_node_props(&props, conn,
+ err = svn_ra_serf__fetch_node_props(&props, session,
path, SVN_INVALID_REVNUM,
- base_props, pool, pool);
+ base_props,
+ scratch_pool, scratch_pool);
if (! err)
{
apr_hash_t *ns_props;
@@ -2474,12 +1673,7 @@ svn_ra_serf__discover_vcc(const char **vcc_url,
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);
+ path = svn_urlpath__dirname(path, scratch_pool);
}
}
}
@@ -2505,7 +1699,7 @@ svn_ra_serf__discover_vcc(const char **vcc_url,
{
svn_stringbuf_t *url_buf;
- url_buf = svn_stringbuf_create(path, pool);
+ url_buf = svn_stringbuf_create(path, scratch_pool);
svn_path_remove_components(url_buf,
svn_path_component_count(relative_path));
@@ -2533,7 +1727,6 @@ 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;
@@ -2550,7 +1743,6 @@ svn_ra_serf__get_relative_path(const char **rel_path,
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));
}
@@ -2564,7 +1756,6 @@ svn_ra_serf__get_relative_path(const char **rel_path,
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'
@@ -2574,7 +1765,7 @@ svn_ra_serf__report_resource(const char **report_target,
/* Otherwise, we'll use the default VCC. */
else
- SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool));
+ SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, pool));
return SVN_NO_ERROR;
}
@@ -2588,13 +1779,14 @@ svn_ra_serf__error_on_status(serf_status_line sline,
{
case 301:
case 302:
+ case 303:
case 307:
+ case 308:
return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
(sline.code == 301)
- ? _("Repository moved permanently to '%s';"
- " please relocate")
- : _("Repository moved temporarily to '%s';"
- " please relocate"), location);
+ ? _("Repository moved permanently to '%s'")
+ : _("Repository moved temporarily to '%s'"),
+ location);
case 403:
return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
_("Access to '%s' forbidden"), path);
@@ -2602,6 +1794,16 @@ svn_ra_serf__error_on_status(serf_status_line sline,
case 404:
return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
_("'%s' path not found"), path);
+ case 405:
+ return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
+ _("HTTP method is not allowed on '%s'"),
+ path);
+ case 409:
+ return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
+ _("'%s' conflicts"), path);
+ case 412:
+ return svn_error_createf(SVN_ERR_RA_DAV_PRECONDITION_FAILED, NULL,
+ _("Precondition on '%s' failed"), path);
case 423:
return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
_("'%s': no lock token available"), path);
@@ -2612,21 +1814,59 @@ svn_ra_serf__error_on_status(serf_status_line sline,
"server or an intermediate proxy does not accept "
"chunked encoding. Try setting 'http-chunked-requests' "
"to 'auto' or 'no' in your client configuration."));
+ case 500:
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Unexpected server error %d '%s' on '%s'"),
+ sline.code, sline.reason, path);
case 501:
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("The requested feature is not supported by "
"'%s'"), path);
}
- if (sline.code >= 300)
+ if (sline.code >= 300 || sline.code <= 199)
return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
- _("Unexpected HTTP status %d '%s' on '%s'\n"),
+ _("Unexpected HTTP status %d '%s' on '%s'"),
sline.code, sline.reason, path);
return SVN_NO_ERROR;
}
svn_error_t *
+svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler)
+{
+ /* Is it a standard error status? */
+ if (handler->sline.code != 405)
+ SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
+ handler->path,
+ handler->location));
+
+ switch (handler->sline.code)
+ {
+ case 201:
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Path '%s' unexpectedly created"),
+ handler->path);
+ case 204:
+ return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Path '%s' already exists"),
+ handler->path);
+
+ case 405:
+ return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
+ _("The HTTP method '%s' is not allowed"
+ " on '%s'"),
+ handler->method, handler->path);
+ default:
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Unexpected HTTP status %d '%s' on '%s' "
+ "request to '%s'"),
+ handler->sline.code, handler->sline.reason,
+ handler->method, handler->path);
+ }
+}
+
+svn_error_t *
svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
svn_delta_shim_callbacks_t *callbacks)
{
@@ -2636,185 +1876,102 @@ svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
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)
+/* Shared/standard done_delegate handler */
+static svn_error_t *
+response_done(serf_request_t *request,
+ void *handler_baton,
+ apr_pool_t *scratch_pool)
{
- struct expat_ctx_t *ectx = userData;
+ svn_ra_serf__handler_t *handler = handler_baton;
- 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
-}
+ assert(handler->done);
+ if (handler->no_fail_on_http_failure_status)
+ return SVN_NO_ERROR;
-/* 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 (handler->server_error)
+ return svn_ra_serf__server_error_create(handler, scratch_pool);
- if (!ectx->parser)
+ if (handler->sline.code >= 400 || handler->sline.code <= 199)
{
- 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);
+ return svn_error_trace(svn_ra_serf__unexpected_status(handler));
}
- /* ### TODO: sline.code < 200 should really be handled by the core */
- if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300))
+ if ((handler->sline.code >= 300 && handler->sline.code < 399)
+ && !handler->no_fail_on_http_redirect_status)
{
- /* 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));
+ return svn_error_trace(svn_ra_serf__unexpected_status(handler));
}
- 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 */);
+ return SVN_NO_ERROR;
+}
- svn_ra_serf__xml_context_destroy(ectx->xmlctx);
- apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
- xml_parser_cleanup);
+/* Pool cleanup handler for request handlers.
- /* ### should check XMLCTX to see if it has returned to the
- ### INITIAL state. we may have ended early... */
- }
+ If a serf context run stops for some outside error, like when the user
+ cancels a request via ^C in the context loop, the handler is still
+ registered in the serf context. With the pool cleanup there would be
+ handlers registered in no freed memory.
- if (status && !SERF_BUCKET_READ_ERROR(status))
- {
- return svn_ra_serf__wrap_err(status, NULL);
- }
+ This fallback kills the connection for this case, which will make serf
+ unregister any outstanding requests on it. */
+static apr_status_t
+handler_cleanup(void *baton)
+{
+ svn_ra_serf__handler_t *handler = baton;
+ if (handler->scheduled)
+ {
+ svn_ra_serf__unschedule_handler(handler);
}
- /* NOTREACHED */
+ return APR_SUCCESS;
}
-
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__create_handler(svn_ra_serf__session_t *session,
+ 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;
+ apr_pool_cleanup_register(result_pool, handler, handler_cleanup,
+ apr_pool_cleanup_null);
+
+ handler->session = session;
+ handler->conn = session->conns[0];
+
+ /* Setup the default done handler, to handle server errors */
+ handler->done_delegate_baton = handler;
+ handler->done_delegate = response_done;
return handler;
}
+
+svn_error_t *
+svn_ra_serf__uri_parse(apr_uri_t *uri,
+ const char *url_str,
+ apr_pool_t *result_pool)
+{
+ apr_status_t status;
+
+ status = apr_uri_parse(result_pool, url_str, uri);
+ if (status)
+ {
+ /* Do not use returned error status in error message because currently
+ apr_uri_parse() returns APR_EGENERAL for all parsing errors. */
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal URL '%s'"),
+ url_str);
+ }
+
+ /* Depending the version of apr-util in use, for root paths uri.path
+ will be NULL or "", where serf requires "/". */
+ if (uri->path == NULL || uri->path[0] == '\0')
+ {
+ uri->path = apr_pstrdup(result_pool, "/");
+ }
+
+ return SVN_NO_ERROR;
+}