You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2012/05/16 22:32:54 UTC
svn commit: r1339349 [19/37] - in /subversion/branches/fix-rdump-editor: ./
build/ build/ac-macros/ build/generator/ build/generator/templates/
build/win32/ contrib/client-side/emacs/ contrib/client-side/vim/
contrib/server-side/ notes/ notes/api-errat...
Modified: subversion/branches/fix-rdump-editor/subversion/libsvn_ra_serf/util.c
URL: http://svn.apache.org/viewvc/subversion/branches/fix-rdump-editor/subversion/libsvn_ra_serf/util.c?rev=1339349&r1=1339348&r2=1339349&view=diff
==============================================================================
--- subversion/branches/fix-rdump-editor/subversion/libsvn_ra_serf/util.c (original)
+++ subversion/branches/fix-rdump-editor/subversion/libsvn_ra_serf/util.c Wed May 16 20:32:43 2012
@@ -33,14 +33,19 @@
#include <serf.h>
#include <serf_bucket_types.h>
+#include <expat.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 "../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"
@@ -53,125 +58,54 @@
#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 */
-#define PARSE_CHUNK_SIZE 8000
+#if XML_VERSION_AT_LEAST(1, 95, 8)
+#define EXPAT_HAS_STOPPARSER
+#endif
-/* As chunks of content arrive from the server, and we need to hold them
- in memory (because the XML parser is paused), they are copied into
- these buffers. The buffers are arranged into a linked list. */
-struct pending_buffer_t {
- apr_size_t size;
- char data[PARSE_CHUNK_SIZE];
+/* Read/write chunks of this size into the spillbuf. */
+#define PARSE_CHUNK_SIZE 8000
- struct pending_buffer_t *next;
-};
+/* 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 amount of content in memory. */
- apr_size_t memory_size;
-
- /* HEAD points to the first block of the linked list of buffers.
- TAIL points to the last block, for quickly appending more blocks
- to the overall list. */
- struct pending_buffer_t *head;
- struct pending_buffer_t *tail;
-
- /* Available blocks for storing pending data. These were allocated
- previously, then the data consumed and returned to this list. */
- struct pending_buffer_t *avail;
-
- /* Once MEMORY_SIZE exceeds SPILL_SIZE, then arriving content will be
- appended to the (temporary) file indicated by SPILL. */
- apr_file_t *spill;
-
- /* As we consume content from SPILL, this value indicates where we
- will begin reading. */
- apr_off_t spill_start;
+ /* 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)->head != NULL || (p)->spill != NULL))
-
-/* We will store one megabyte in memory, before switching to store content
- into a temporary file. */
-#define SPILL_SIZE 1000000
-
-/* See notes/ra-serf-testing.txt for some information on testing this
- new "paused" feature (aka network pushback).
-
- Define PBTEST_ACTIVE, if you would like to run the pushback tests. */
-#undef PBTEST_ACTIVE
-#ifdef PBTEST_ACTIVE
-
-static int pbtest_step = 0;
-
-/* Note: this cannot resolve states 5 and 7. */
-#define PBTEST_STATE(p) \
- ((p) == NULL ? 1 \
- : ((p)->spill == NULL ? ((p)->head == NULL ? 2 : 3) \
- : ((p)->head == NULL ? 6 : 4)))
-
-/* Note: INJECT and COMPLETED are only used for debug output. */
-typedef struct {
- svn_boolean_t paused; /* pause the parser on this step? */
- svn_boolean_t inject; /* inject pending content on this step? */
- int when_next; /* when to move to the next step? */
- const char *completed; /* what test was completed? */
-} pbtest_desc_t;
-
-static const pbtest_desc_t pbtest_description[] = {
- { 0 }, /* unused */
- { TRUE, FALSE, 3, "1.1" },
- { TRUE, FALSE, 3, "1.3" },
- { FALSE, FALSE, 3, "2.3" },
- { FALSE, TRUE, 2, "3.3" }, /* WHEN_NEXT is ignored due to INJECT */
- { TRUE, FALSE, 3, "1.2" },
- { TRUE, FALSE, 4, NULL },
- { TRUE, FALSE, 4, "1.4" },
- { FALSE, FALSE, 4, "2.4" },
- { FALSE, TRUE, 6, "3.4" }, /* WHEN_NEXT is ignored due to INJECT */
- { TRUE, FALSE, 6, "1.6" },
- { FALSE, FALSE, 6, "2.6" },
- { FALSE, TRUE, 7, "3.6" }, /* WHEN_NEXT is ignored due to INJECT */
- { TRUE, FALSE, 6, "1.7" },
- { 0 } /* unused */
-};
-
-#define PBTEST_SET_PAUSED(ctx) \
- (PBTEST_THIS_REQ(ctx) && pbtest_step < 14 \
- ? (ctx)->paused = pbtest_description[pbtest_step].paused \
- : FALSE)
-
-#define PBTEST_MAYBE_STEP(ctx, force) maybe_next_step(ctx, force)
-
-#define PBTEST_FORCE_SPILL(ctx) (PBTEST_THIS_REQ(ctx) && pbtest_step == 6)
+#define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \
+ && svn_spillbuf__get_size((p)->buf) != 0)
-#define PBTEST_THIS_REQ(ctx) \
- ((ctx)->response_type != NULL \
- && strcmp((ctx)->response_type, "update-report") == 0)
-#else /* PBTEST_ACTIVE */
+struct expat_ctx_t {
+ svn_ra_serf__xml_context_t *xmlctx;
+ XML_Parser parser;
+ svn_ra_serf__handler_t *handler;
-/* Be wary with the definition of these macros so that we don't
- end up with "statement with no effect" warnings. Obviously, this
- depends upon particular usage, which is easy to verify. */
-#define PBTEST_SET_PAUSED(ctx) /* empty */
-#define PBTEST_MAYBE_STEP(ctx, force) /* empty */
+ svn_error_t *inner_error;
-#define PBTEST_FORCE_SPILL(ctx) FALSE
-#define PBTEST_THIS_REQ(ctx) FALSE
-
-#endif /* PBTEST_ACTIVE */
+ /* 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 },
@@ -207,6 +141,23 @@ ssl_convert_serf_failures(int failures)
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,
@@ -366,21 +317,10 @@ ssl_server_cert_cb(void *baton, int fail
svn_error_t *err;
subpool = svn_pool_create(session->pool);
- err = ssl_server_cert(baton, failures, cert, subpool);
-
+ err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
svn_pool_destroy(subpool);
- 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;
-
+ return save_error(session, err);
}
static svn_error_t *
@@ -484,31 +424,23 @@ svn_ra_serf__conn_setup(apr_socket_t *so
{
svn_ra_serf__connection_t *conn = baton;
svn_ra_serf__session_t *session = conn->session;
- apr_status_t status = APR_SUCCESS;
-
- svn_error_t *err = conn_setup(sock,
- read_bkt,
- write_bkt,
- baton,
- pool);
-
- if (err || session->pending_error)
- {
- session->pending_error = svn_error_compose_create(
- session->pending_error,
- err);
-
- status = session->pending_error->apr_err;
- }
+ svn_error_t *err;
- return status;
+ err = svn_error_trace(conn_setup(sock,
+ read_bkt,
+ write_bkt,
+ baton,
+ pool));
+ return save_error(session, err);
}
-serf_bucket_t*
-svn_ra_serf__accept_response(serf_request_t *request,
- serf_bucket_t *stream,
- void *acceptor_baton,
- apr_pool_t *pool)
+
+/* 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;
@@ -519,7 +451,9 @@ svn_ra_serf__accept_response(serf_reques
return serf_bucket_response_create(c, bkt_alloc);
}
-static serf_bucket_t*
+
+/* Custom response acceptor for HEAD requests. */
+static serf_bucket_t *
accept_head(serf_request_t *request,
serf_bucket_t *stream,
void *acceptor_baton,
@@ -527,8 +461,7 @@ accept_head(serf_request_t *request,
{
serf_bucket_t *response;
- response = svn_ra_serf__accept_response(request, stream, acceptor_baton,
- pool);
+ response = accept_response(request, stream, acceptor_baton, pool);
/* We know we shouldn't get a response body. */
serf_bucket_response_set_head(response);
@@ -537,8 +470,7 @@ accept_head(serf_request_t *request,
}
static svn_error_t *
-connection_closed(serf_connection_t *conn,
- svn_ra_serf__connection_t *sc,
+connection_closed(svn_ra_serf__connection_t *conn,
apr_status_t why,
apr_pool_t *pool)
{
@@ -547,8 +479,8 @@ connection_closed(serf_connection_t *con
SVN_ERR_MALFUNCTION();
}
- if (sc->using_ssl)
- sc->ssl_context = NULL;
+ if (conn->using_ssl)
+ conn->ssl_context = NULL;
return SVN_NO_ERROR;
}
@@ -559,15 +491,12 @@ svn_ra_serf__conn_closed(serf_connection
apr_status_t why,
apr_pool_t *pool)
{
- svn_ra_serf__connection_t *sc = closed_baton;
+ svn_ra_serf__connection_t *ra_conn = closed_baton;
svn_error_t *err;
- err = connection_closed(conn, sc, why, pool);
+ err = svn_error_trace(connection_closed(ra_conn, why, pool));
- if (err)
- sc->session->pending_error = svn_error_compose_create(
- sc->session->pending_error,
- err);
+ (void) save_error(ra_conn->session, err);
}
@@ -618,20 +547,11 @@ apr_status_t svn_ra_serf__handle_client_
{
svn_ra_serf__connection_t *conn = data;
svn_ra_serf__session_t *session = conn->session;
- svn_error_t *err = handle_client_cert(data,
- cert_path,
- session->pool);
-
- if (err || session->pending_error)
- {
- session->pending_error = svn_error_compose_create(
- session->pending_error,
- err);
+ svn_error_t *err;
- return session->pending_error->apr_err;
- }
+ err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
- return APR_SUCCESS;
+ return save_error(session, err);
}
/* Implementation for svn_ra_serf__handle_client_cert_pw */
@@ -680,59 +600,102 @@ apr_status_t svn_ra_serf__handle_client_
{
svn_ra_serf__connection_t *conn = data;
svn_ra_serf__session_t *session = conn->session;
- svn_error_t *err = handle_client_cert_pw(data,
- cert_path,
- password,
- session->pool);
-
- if (err || session->pending_error)
- {
- session->pending_error = svn_error_compose_create(
- session->pending_error,
- err);
+ svn_error_t *err;
- return session->pending_error->apr_err;
- }
+ err = svn_error_trace(handle_client_cert_pw(data,
+ cert_path,
+ password,
+ session->pool));
- return APR_SUCCESS;
+ return save_error(session, err);
}
-svn_error_t *
-svn_ra_serf__setup_serf_req(serf_request_t *request,
- serf_bucket_t **req_bkt,
- serf_bucket_t **ret_hdrs_bkt,
- svn_ra_serf__connection_t *conn,
- const char *method, const char *url,
- serf_bucket_t *body_bkt, const char *content_type)
-{
- serf_bucket_t *hdrs_bkt;
+/*
+ * 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__connection_t *conn,
+ const char *method, const char *url,
+ serf_bucket_t *body_bkt, const char *content_type,
+ 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 (conn->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));
+ 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,
- serf_request_get_alloc(request));
+ *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 (conn->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);
- hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
- serf_bucket_headers_setn(hdrs_bkt, "User-Agent", conn->useragent);
+ /* We use serf_bucket_headers_setn() because the string below have a
+ lifetime longer than this bucket. Thus, there is no need to copy
+ the header values. */
+ serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", conn->useragent);
if (content_type)
{
- serf_bucket_headers_setn(hdrs_bkt, "Content-Type", content_type);
+ serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
}
/* 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_set(hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
- serf_bucket_headers_set(hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
- serf_bucket_headers_set(hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
-
- if (ret_hdrs_bkt)
- {
- *ret_hdrs_bkt = hdrs_bkt;
- }
+ 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;
}
@@ -796,16 +759,38 @@ svn_ra_serf__context_run_wait(svn_boolea
}
+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,
- void *userData,
svn_ra_serf__dav_props_t name,
- const char **attrs)
+ const char **attrs,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (!ctx->in_error &&
strcmp(name.namespace, "DAV:") == 0 &&
@@ -843,10 +828,10 @@ start_error(svn_ra_serf__xml_parser_t *p
*/
static svn_error_t *
end_error(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (ctx->in_error &&
strcmp(name.namespace, "DAV:") == 0 &&
@@ -881,11 +866,11 @@ end_error(svn_ra_serf__xml_parser_t *par
*/
static svn_error_t *
cdata_error(svn_ra_serf__xml_parser_t *parser,
- void *userData,
const char *data,
- apr_size_t len)
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (ctx->collect_cdata)
{
@@ -895,6 +880,49 @@ cdata_error(svn_ra_serf__xml_parser_t *p
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->init = TRUE;
+ server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err->has_xml_response = TRUE;
+ 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,
@@ -917,6 +945,8 @@ svn_ra_serf__handle_discard_body(serf_re
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->has_xml_response = TRUE;
server_err->contains_precondition_error = FALSE;
@@ -955,9 +985,7 @@ svn_ra_serf__handle_discard_body(serf_re
}
- status = svn_ra_serf__response_discard_handler(request, response,
- NULL, pool);
-
+ status = drain_bucket(response);
if (status)
return svn_error_wrap_apr(status, NULL);
@@ -970,22 +998,7 @@ svn_ra_serf__response_discard_handler(se
void *baton,
apr_pool_t *pool)
{
- /* Just loop through and discard the body. */
- while (1)
- {
- apr_status_t status;
- const char *data;
- apr_size_t len;
-
- status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
-
- if (status)
- {
- return status;
- }
-
- /* feed me */
- }
+ return drain_bucket(response);
}
const char *
@@ -1000,41 +1013,60 @@ svn_ra_serf__response_get_location(serf_
return val ? svn_urlpath__canonicalize(val, pool) : NULL;
}
+
/* Implements svn_ra_serf__response_handler_t */
svn_error_t *
-svn_ra_serf__handle_status_only(serf_request_t *request,
- serf_bucket_t *response,
- void *baton,
- apr_pool_t *pool)
+svn_ra_serf__expect_empty_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
{
- svn_error_t *err;
- svn_ra_serf__simple_request_context_t *ctx = baton;
+ svn_ra_serf__handler_t *handler = baton;
+ serf_bucket_t *hdrs;
+ const char *val;
- SVN_ERR_ASSERT(ctx->pool);
+ /* This function is just like handle_multistatus_only() except for the
+ XML parsing callbacks. We want to look for the human-readable element. */
- err = svn_ra_serf__handle_discard_body(request, response,
- &ctx->server_error, pool);
+ /* 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);
- if (err && APR_STATUS_IS_EOF(err->apr_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)
{
- serf_status_line sl;
- apr_status_t status;
+ svn_ra_serf__server_error_t *server_err;
- status = serf_bucket_response_status(response, &sl);
- if (SERF_BUCKET_READ_ERROR(status))
- {
- return svn_error_wrap_apr(status, NULL);
- }
+ 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;
- ctx->status = sl.code;
- ctx->reason = sl.reason ? apr_pstrdup(ctx->pool, sl.reason) : NULL;
- ctx->location = svn_ra_serf__response_get_location(response, ctx->pool);
- ctx->done = TRUE;
+ handler->server_error = server_err;
+ }
+ else
+ {
+ /* ### hmm. this is a bit early. we have not seen EOF. if the
+ ### caller thinks we are "done", then it may never call into
+ ### serf_context_run() again to flush the response. */
+ handler->done = TRUE;
+
+ /* The body was not text/xml, so we don't know what to do with it.
+ Toss anything that arrives. */
+ handler->discard_body = TRUE;
}
- return svn_error_trace(err);
+ /* 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 *
@@ -1068,11 +1100,11 @@ parse_dav_status(int *status_code_out, s
*/
static svn_error_t *
start_207(svn_ra_serf__xml_parser_t *parser,
- void *userData,
svn_ra_serf__dav_props_t name,
- const char **attrs)
+ const char **attrs,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (!ctx->in_error &&
strcmp(name.namespace, "DAV:") == 0 &&
@@ -1103,10 +1135,10 @@ start_207(svn_ra_serf__xml_parser_t *par
*/
static svn_error_t *
end_207(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (ctx->in_error &&
strcmp(name.namespace, "DAV:") == 0 &&
@@ -1116,9 +1148,18 @@ end_207(svn_ra_serf__xml_parser_t *parse
}
if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
{
+ apr_size_t len = ctx->cdata->len;
+ const char *data = ctx->cdata->data;
+
+ /* Remove leading newline added by DEBUG_CR on server */
+ if (*data == '\n')
+ {
+ ++data;
+ --len;
+ }
+
ctx->collect_cdata = FALSE;
- ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
- ctx->cdata->len);
+ ctx->error->message = apr_pstrmemdup(ctx->error->pool, data, len);
if (ctx->contains_precondition_error)
ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
else
@@ -1147,11 +1188,11 @@ end_207(svn_ra_serf__xml_parser_t *parse
*/
static svn_error_t *
cdata_207(svn_ra_serf__xml_parser_t *parser,
- void *userData,
const char *data,
- apr_size_t len)
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (ctx->collect_cdata)
{
@@ -1166,97 +1207,65 @@ svn_error_t *
svn_ra_serf__handle_multistatus_only(serf_request_t *request,
serf_bucket_t *response,
void *baton,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
- svn_error_t *err;
- svn_ra_serf__simple_request_context_t *ctx = baton;
- svn_ra_serf__server_error_t *server_err = &ctx->server_error;
+ svn_ra_serf__handler_t *handler = baton;
- SVN_ERR_ASSERT(ctx->pool);
+ /* 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);
- /* If necessary, initialize our XML parser. */
- if (server_err && !server_err->init)
{
serf_bucket_t *hdrs;
const char *val;
- server_err->init = TRUE;
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)
{
- server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
- server_err->has_xml_response = TRUE;
- 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_207;
- server_err->parser.end = end_207;
- server_err->parser.cdata = cdata_207;
- server_err->parser.done = &ctx->done;
- server_err->parser.ignore_errors = TRUE;
- }
- else
- {
- ctx->done = TRUE;
- server_err->error = SVN_NO_ERROR;
- }
- }
+ svn_ra_serf__server_error_t *server_err;
- /* If server_err->error still contains APR_SUCCESS, it means that we
- have not successfully parsed the XML yet. */
- if (server_err && server_err->error
- && server_err->error->apr_err == APR_SUCCESS)
- {
- err = svn_ra_serf__handle_xml_parser(request, response,
- &server_err->parser, pool);
+ server_err = begin_error_parsing(start_207, end_207, cdata_207,
+ handler->handler_pool);
- /* APR_EOF will be returned when parsing is complete. If we see
- any other error, return it immediately. In practice the only
- other error we expect to see is APR_EAGAIN, which indicates that
- we could not parse the XML because the contents are not yet
- available to be read. */
- if (!err || !APR_STATUS_IS_EOF(err->apr_err))
- {
- return svn_error_trace(err);
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
+
+ handler->server_error = server_err;
}
- else if (ctx->done && server_err->error->apr_err == APR_SUCCESS)
+ else
{
- svn_error_clear(server_err->error);
- server_err->error = SVN_NO_ERROR;
- }
-
- svn_error_clear(err);
- }
-
- err = svn_ra_serf__handle_discard_body(request, response, NULL, pool);
-
- if (err && APR_STATUS_IS_EOF(err->apr_err))
- {
- serf_status_line sl;
- apr_status_t status;
+ /* ### hmm. this is a bit early. we have not seen EOF. if the
+ ### caller thinks we are "done", then it may never call into
+ ### serf_context_run() again to flush the response. */
+ handler->done = TRUE;
- status = serf_bucket_response_status(response, &sl);
- if (SERF_BUCKET_READ_ERROR(status))
- {
- return svn_error_wrap_apr(status, NULL);
+ /* The body was not text/xml, so we don't know what to do with it.
+ Toss anything that arrives. */
+ handler->discard_body = TRUE;
}
-
- ctx->status = sl.code;
- ctx->reason = sl.reason ? apr_pstrdup(ctx->pool, sl.reason) : NULL;
- ctx->location = svn_ra_serf__response_get_location(response, ctx->pool);
}
- return svn_error_trace(err);
+ /* 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;
if (parser->error)
return;
@@ -1264,31 +1273,43 @@ start_xml(void *userData, const char *ra
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);
- parser->error = parser->start(parser, parser->user_data, name, attrs);
+ parser->error = parser->start(parser, name, attrs, scratch_pool);
}
+
+/* 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;
+ 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);
- parser->error = parser->end(parser, parser->user_data, name);
+ parser->error = parser->end(parser, name, scratch_pool);
}
+
+/* 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;
+ apr_pool_t *scratch_pool;
if (parser->error)
return;
@@ -1296,7 +1317,10 @@ cdata_xml(void *userData, const char *da
if (!parser->state)
svn_ra_serf__xml_push_state(parser, 0);
- parser->error = parser->cdata(parser, parser->user_data, data, len);
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ parser->error = parser->cdata(parser, data, len, scratch_pool);
}
/* Flip the requisite bits in CTX to indicate that processing of the
@@ -1319,157 +1343,24 @@ add_done_item(svn_ra_serf__xml_parser_t
}
-#ifdef PBTEST_ACTIVE
-
-/* Determine whether we should move to the next step. Print out the
- transition for debugging purposes. If FORCE is TRUE, then we
- definitely make a step (injection has completed). */
-static void
-maybe_next_step(svn_ra_serf__xml_parser_t *parser, svn_boolean_t force)
-{
- const pbtest_desc_t *desc;
- int state;
-
- /* This would fail the state transition, but for clarity... just return
- when the testing has completed. */
- if (pbtest_step == 14)
- return;
-
- /* If this is not the request running the test, then exit. */
- if (!PBTEST_THIS_REQ(parser))
- return;
-
- desc = &pbtest_description[pbtest_step];
- state = PBTEST_STATE(parser->pending);
-
- /* Forced? ... or reached target state? */
- if (force || state == desc->when_next)
- {
- ++pbtest_step;
-
- if (desc->completed != NULL)
- SVN_DBG(("PBTEST: completed TEST %s\n", desc->completed));
-
- /* Pause the parser based on the new step's config. */
- ++desc;
- parser->paused = desc->paused;
-
- SVN_DBG(("PBTEST: advanced: step=%d paused=%d inject=%d state=%d\n",
- pbtest_step, desc->paused, desc->inject, state));
- }
- else
- {
- SVN_DBG(("PBTEST: step[%d]: state=%d waiting_for=%d\n",
- pbtest_step, state, desc->when_next));
- }
-}
-
-#endif /* PBTEST_ACTIVE */
-
-
-/* Get a buffer from the parsing context. It will come from the free list,
- or allocated as necessary. */
-static struct pending_buffer_t *
-get_buffer(svn_ra_serf__xml_parser_t *parser)
-{
- struct pending_buffer_t *pb;
-
- if (parser->pending->avail == NULL)
- return apr_palloc(parser->pool, sizeof(*pb));
-
- pb = parser->pending->avail;
- parser->pending->avail = pb->next;
- return pb;
-}
-
-
-/* Return PB to the list of available buffers in PARSER. */
-static void
-return_buffer(svn_ra_serf__xml_parser_t *parser,
- struct pending_buffer_t *pb)
-{
- pb->next = parser->pending->avail;
- parser->pending->avail = pb;
-}
-
-
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)
{
- struct pending_buffer_t *pb;
-
- /* The caller should not have provided us more than we can store into
- a single memory block. */
- SVN_ERR_ASSERT(len <= PARSE_CHUNK_SIZE);
-
if (ctx->pending == NULL)
- ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
-
- /* We do not (yet) have a spill file, but the amount stored in memory
- has grown too large. Create the file and place the pending data into
- the temporary file.
-
- For testing purposes, there are points when we may want to
- create the spill file, regardless. */
- if (PBTEST_FORCE_SPILL(ctx)
- || (ctx->pending->spill == NULL
- && ctx->pending->memory_size > SPILL_SIZE))
- {
-#ifdef PBTEST_ACTIVE
- /* Only allow a spill file for steps 6 or later. */
- if (!PBTEST_THIS_REQ(ctx) || pbtest_step >= 6)
-#endif
- SVN_ERR(svn_io_open_unique_file3(&ctx->pending->spill,
- NULL /* temp_path */,
- NULL /* dirpath */,
- svn_io_file_del_on_pool_cleanup,
- ctx->pool, scratch_pool));
- }
-
- /* Once a spill file has been constructed, then we need to put all
- arriving data into the file. We will no longer attempt to hold it
- in memory. */
- if (ctx->pending->spill != NULL)
- {
- /* NOTE: we assume the file position is at the END. The caller should
- ensure this, so that we will append. */
- SVN_ERR(svn_io_file_write_full(ctx->pending->spill, data, len,
- NULL, scratch_pool));
- return SVN_NO_ERROR;
- }
-
- /* We're still within bounds of holding the pending information in
- memory. Get a buffer, copy the data there, and link it into our
- pending data. */
- pb = get_buffer(ctx);
- /* NOTE: *pb is uninitialized. All fields must be stored. */
-
- pb->size = len;
- memcpy(pb->data, data, len);
- pb->next = NULL;
-
- /* Start a list of buffers, or append to the end of the linked list
- of buffers. */
- if (ctx->pending->tail == NULL)
{
- ctx->pending->head = pb;
- ctx->pending->tail = pb;
+ ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
+ ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE,
+ SPILL_SIZE,
+ ctx->pool);
}
- else
- {
- ctx->pending->tail->next = pb;
- ctx->pending->tail = pb;
- }
-
- /* We need to record how much is buffered in memory. Once we reach
- SPILL_SIZE (or thereabouts, it doesn't have to be precise), then
- we'll switch to putting the content into a file. */
- ctx->pending->memory_size += len;
- return SVN_NO_ERROR;
+ /* 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));
}
@@ -1481,7 +1372,7 @@ inject_to_parser(svn_ra_serf__xml_parser
{
int xml_status;
- xml_status = XML_Parse(ctx->xmlp, data, len, 0);
+ xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0);
if (xml_status == XML_STATUS_ERROR && !ctx->ignore_errors)
{
if (sl == NULL)
@@ -1496,10 +1387,6 @@ inject_to_parser(svn_ra_serf__xml_parser
if (ctx->error && !ctx->ignore_errors)
return svn_error_trace(ctx->error);
- /* We may want to ignore the callbacks choice for the PAUSED flag.
- Set this value, as appropriate. */
- PBTEST_SET_PAUSED(ctx);
-
return SVN_NO_ERROR;
}
@@ -1523,23 +1410,6 @@ svn_error_t *
svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
apr_pool_t *scratch_pool)
{
- struct pending_buffer_t *pb;
- svn_error_t *err;
- apr_off_t output_unused;
-
- /* We may need to repair the PAUSED state when testing. */
- PBTEST_SET_PAUSED(parser);
-
-#ifdef PBTEST_ACTIVE
- /* If this step should not inject content, then fast-path exit. */
- if (PBTEST_THIS_REQ(parser)
- && pbtest_step < 14 && !pbtest_description[pbtest_step].inject)
- {
- SVN_DBG(("PBTEST: process: injection disabled\n"));
- return SVN_NO_ERROR;
- }
-#endif
-
/* Fast path exit: already paused, nothing to do, or already done. */
if (parser->paused || parser->pending == NULL || *parser->done)
return SVN_NO_ERROR;
@@ -1547,107 +1417,35 @@ svn_ra_serf__process_pending(svn_ra_serf
/* ### it is possible that the XML parsing of the pending content is
### so slow, and that we don't return to reading the connection
### fast enough... that the server will disconnect us. right now,
- ### that is highly improbably, but is noted for future's sake.
+ ### that is highly improbable, but is noted for future's sake.
### should that ever happen, the loops in this function can simply
### terminate after N seconds. */
- /* Empty out memory buffers until we run out, or we get paused again. */
- while (parser->pending->head != NULL)
- {
- /* Pull the HEAD buffer out of the list. */
- pb = parser->pending->head;
- if (parser->pending->tail == pb)
- parser->pending->head = parser->pending->tail = NULL;
- else
- parser->pending->head = pb->next;
-
- /* We're using less memory now. If we haven't hit the spill file,
- then we may be able to keep using memory. */
- parser->pending->memory_size -= pb->size;
-
- err = inject_to_parser(parser, pb->data, pb->size, NULL);
-
- return_buffer(parser, pb);
-
- if (err)
- return svn_error_trace(err);
-
- /* If the callbacks paused us, then we're done for now. */
- if (parser->paused)
- return SVN_NO_ERROR;
- }
-
-#ifdef PBTEST_ACTIVE
- /* For steps 4 and 9, we wait until all of the memory content has been
- injected. At that point, we can take another step which will pause
- the parser, and we'll need to exit. */
- if (PBTEST_THIS_REQ(parser)
- && (pbtest_step == 4 || pbtest_step == 9))
- {
- PBTEST_MAYBE_STEP(parser, TRUE);
- return SVN_NO_ERROR;
- }
-#endif
-
- /* If we don't have a spill file, then we've exhausted all
- pending content. */
- if (parser->pending->spill == NULL)
- goto pending_empty;
-
- /* Seek once to where we left off reading. */
- output_unused = parser->pending->spill_start; /* ### stupid API */
- SVN_ERR(svn_io_file_seek(parser->pending->spill,
- APR_SET, &output_unused,
- scratch_pool));
-
- /* We need a buffer for reading out of the file. One of these will always
- exist by the time we start reading from the spill file. */
- pb = get_buffer(parser);
-
- /* Keep reading until we hit EOF, or get paused again. */
+ /* Try to read everything from the spillbuf. */
while (TRUE)
{
- apr_size_t len = sizeof(pb->data);
- apr_status_t status;
-
- /* Read some data and remember where we left off. */
- status = apr_file_read(parser->pending->spill, pb->data, &len);
- if (status && !APR_STATUS_IS_EOF(status))
- {
- err = svn_error_wrap_apr(status, NULL);
- break;
- }
- parser->pending->spill_start += len;
+ const char *data;
+ apr_size_t len;
- err = inject_to_parser(parser, pb->data, len, NULL);
- if (err)
+ /* 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 == NULL)
break;
- /* If we just consumed everything in the spill file, then we may
- be done with the parsing. */
- /* ### future change: when we hit EOF, then remove the spill file.
- ### we could go back to using memory for a while. */
- if (APR_STATUS_IS_EOF(status))
- goto pending_empty;
+ /* Inject the content into the XML parser. */
+ SVN_ERR(inject_to_parser(parser, data, len, NULL));
- /* If the callbacks paused the parsing, then we're done for now. */
+ /* If the XML parsing callbacks paused us, then we're done for now. */
if (parser->paused)
- break;
+ return SVN_NO_ERROR;
}
+ /* All stored content (memory and file) has now been exhausted. */
- return_buffer(parser, pb);
- return svn_error_trace(err); /* may be SVN_NO_ERROR */
-
- pending_empty:
/* If the PENDING structures are empty *and* we consumed all content from
the network, then we're completely done with the parsing. */
if (parser->pending->network_eof)
{
-#ifdef PBTEST_ACTIVE
- if (PBTEST_THIS_REQ(parser))
- SVN_DBG(("process: terminating parse.\n"));
-#endif
-
SVN_ERR_ASSERT(parser->xmlp != NULL);
/* Tell the parser that no more content will be parsed. Ignore the
@@ -1659,14 +1457,25 @@ svn_ra_serf__process_pending(svn_ra_serf
add_done_item(parser);
}
- /* For testing step 12, we have written all of the disk content. This
- will advance to step 13 and pause the parser again. */
- PBTEST_MAYBE_STEP(parser, TRUE);
-
return SVN_NO_ERROR;
}
+/* ### 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 };
+
+ svn_error_clear(svn_ra_serf__handle_discard_body(request, response,
+ &server_err, scratch_pool));
+
+ return server_err.error;
+}
+
+
/* Implements svn_ra_serf__response_handler_t */
svn_error_t *
svn_ra_serf__handle_xml_parser(serf_request_t *request,
@@ -1679,31 +1488,19 @@ svn_ra_serf__handle_xml_parser(serf_requ
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_error_wrap_apr(status, NULL);
}
- if (ctx->status_code)
- {
- *ctx->status_code = sl.code;
- }
-
- if (sl.code == 301 || sl.code == 302 || sl.code == 307)
- {
- ctx->location = svn_ra_serf__response_get_location(response, ctx->pool);
- }
-
/* Woo-hoo. Nothing here to see. */
if (sl.code == 404 && ctx->ignore_errors == FALSE)
{
- /* If our caller won't know about the 404, abort() for now. */
- SVN_ERR_ASSERT(ctx->status_code);
-
add_done_item(ctx);
- err = svn_ra_serf__handle_server_error(request, response, pool);
+ err = handle_server_error(request, response, pool);
SVN_ERR(svn_error_compose_create(
svn_ra_serf__handle_discard_body(request, response, NULL, pool),
@@ -1738,25 +1535,6 @@ svn_ra_serf__handle_xml_parser(serf_requ
{
XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml);
}
-
- /* This is the first invocation. If we're looking at an update
- report, then move to step 1 of the testing sequence. */
-#ifdef PBTEST_ACTIVE
- if (PBTEST_THIS_REQ(ctx))
- PBTEST_MAYBE_STEP(ctx, TRUE);
-#endif
- }
-
- /* If we are storing content into a spill file, then move to the end of
- the file. We need to pre-position the file so that write_to_pending()
- will always append the content. */
- if (ctx->pending != NULL && ctx->pending->spill != NULL)
- {
- apr_off_t output_unused = 0; /* ### stupid API */
-
- SVN_ERR(svn_io_file_seek(ctx->pending->spill,
- APR_END, &output_unused,
- pool));
}
while (1)
@@ -1800,24 +1578,6 @@ svn_ra_serf__handle_xml_parser(serf_requ
ctx->skip_size = 0;
}
- /* Ensure that the parser's PAUSED state is correct before we test
- the flag. */
- PBTEST_SET_PAUSED(ctx);
-
-#ifdef PBTEST_ACTIVE
- if (PBTEST_THIS_REQ(ctx))
- {
- SVN_DBG(("response: len=%d paused=%d status=%08x\n",
- (int)len, ctx->paused, status));
-#if 0
- /* ### DATA is not necessarily NUL-terminated, but this
- ### generally works. so if you want to see content... */
- if (len > 0)
- SVN_DBG(("content=%s\n", data));
-#endif
- }
-#endif
-
/* 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
@@ -1830,13 +1590,6 @@ svn_ra_serf__handle_xml_parser(serf_requ
if (ctx->paused || HAS_PENDING_DATA(ctx->pending))
{
err = write_to_pending(ctx, data, len, pool);
-
- /* We may have a transition to a next step.
-
- Note: this only happens on writing to PENDING. If the
- parser is unpaused, then we will never change state within
- this network-reading loop. */
- PBTEST_MAYBE_STEP(ctx, FALSE);
}
else
{
@@ -1866,20 +1619,10 @@ svn_ra_serf__handle_xml_parser(serf_requ
if (ctx->pending != NULL)
ctx->pending->network_eof = TRUE;
-#ifdef PBTEST_ACTIVE
- if (PBTEST_THIS_REQ(ctx))
- SVN_DBG(("network: reached EOF.\n"));
-#endif
-
/* 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))
{
-#ifdef PBTEST_ACTIVE
- if (PBTEST_THIS_REQ(ctx))
- SVN_DBG(("network: terminating parse.\n"));
-#endif
-
SVN_ERR_ASSERT(ctx->xmlp != NULL);
/* Ignore the return status. We just don't care. */
@@ -1897,19 +1640,6 @@ svn_ra_serf__handle_xml_parser(serf_requ
/* not reached */
}
-/* Implements svn_ra_serf__response_handler_t */
-svn_error_t *
-svn_ra_serf__handle_server_error(serf_request_t *request,
- serf_bucket_t *response,
- apr_pool_t *pool)
-{
- svn_ra_serf__server_error_t server_err = { 0 };
-
- svn_error_clear(svn_ra_serf__handle_discard_body(request, response,
- &server_err, pool));
-
- return server_err.error;
-}
apr_status_t
svn_ra_serf__credentials_callback(char **username, char **password,
@@ -1918,8 +1648,8 @@ svn_ra_serf__credentials_callback(char *
const char *realm,
apr_pool_t *pool)
{
- svn_ra_serf__handler_t *ctx = baton;
- svn_ra_serf__session_t *session = ctx->session;
+ 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;
@@ -1948,8 +1678,7 @@ svn_ra_serf__credentials_callback(char *
if (err)
{
- session->pending_error
- = svn_error_compose_create(session->pending_error, err);
+ (void) save_error(session, err);
return err->apr_err;
}
@@ -1958,13 +1687,11 @@ svn_ra_serf__credentials_callback(char *
if (!creds || session->auth_attempts > 4)
{
/* No more credentials. */
- session->pending_error
- = svn_error_compose_create(
- session->pending_error,
- svn_error_create(
- SVN_ERR_AUTHN_FAILED, NULL,
- _("No more credentials or we tried too many times.\n"
- "Authentication failed")));
+ (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;
}
@@ -1982,21 +1709,20 @@ svn_ra_serf__credentials_callback(char *
if (!session->proxy_username || session->proxy_auth_attempts > 4)
{
/* No more credentials. */
- session->pending_error
- = svn_error_compose_create(
- ctx->session->pending_error,
- svn_error_create(SVN_ERR_AUTHN_FAILED, NULL,
- _("Proxy authentication failed")));
+ (void) save_error(session,
+ svn_error_create(
+ SVN_ERR_AUTHN_FAILED, NULL,
+ _("Proxy authentication failed")));
return SVN_ERR_AUTHN_FAILED;
}
}
- ctx->conn->last_status_code = code;
+ handler->conn->last_status_code = code;
return APR_SUCCESS;
}
-/* Wait for HTTP response status and headers, and invoke CTX->
+/* Wait for HTTP response status and headers, and invoke HANDLER->
response_handler() to carry out operation-specific processing.
Afterwards, check for connection close.
@@ -2006,47 +1732,79 @@ svn_ra_serf__credentials_callback(char *
static svn_error_t *
handle_response(serf_request_t *request,
serf_bucket_t *response,
- svn_ra_serf__handler_t *ctx,
+ svn_ra_serf__handler_t *handler,
apr_status_t *serf_status,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
- serf_status_line sl;
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. Requeue. */
- if (ctx->response_error)
- SVN_ERR(ctx->response_error(request, response, 0,
- ctx->response_error_baton));
-
- svn_ra_serf__request_create(ctx);
+ /* 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 APR_SUCCESS;
+ return SVN_NO_ERROR;
}
- status = serf_bucket_response_status(response, &sl);
- if (SERF_BUCKET_READ_ERROR(status))
- {
- *serf_status = status;
- return SVN_NO_ERROR; /* Handled by serf */
- }
- if (!sl.version && (APR_STATUS_IS_EOF(status) ||
- APR_STATUS_IS_EAGAIN(status)))
+ /* 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 = status;
- return SVN_NO_ERROR; /* Handled by serf */
+ 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);
}
+ /* 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
@@ -2055,171 +1813,211 @@ handle_response(serf_request_t *request,
* the server closed on us early or we're reading too much. Either way,
* scream loudly.
*/
- if (strcmp(ctx->method, "HEAD") != 0 && sl.code != 204 && sl.code != 304)
- {
- svn_error_t *err =
- svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
- svn_error_wrap_apr(status, NULL),
- _("Premature EOF seen from server "
- "(http status=%d)"), sl.code);
- /* This discard may be no-op, but let's preserve the algorithm
- used elsewhere in this function for clarity's sake. */
- svn_ra_serf__response_discard_handler(request, response, NULL, pool);
+ 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_error_wrap_apr(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;
}
}
- if (ctx->conn->last_status_code == 401 && sl.code < 400)
- {
- SVN_ERR(svn_auth_save_credentials(ctx->session->auth_state,
- ctx->session->pool));
- ctx->session->auth_attempts = 0;
- ctx->session->auth_state = NULL;
- }
-
- ctx->conn->last_status_code = sl.code;
-
- if (sl.code == 405 || sl.code == 409 || sl.code >= 500)
+ /* ... and set up the header fields in HANDLER. */
+ handler->location = svn_ra_serf__response_get_location(
+ response, handler->handler_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 == 409
+ || handler->sline.code >= 500)
{
/* 405 Method Not allowed.
409 Conflict: can indicate a hook error.
5xx (Internal) Server error. */
- SVN_ERR(svn_ra_serf__handle_server_error(request, response, pool));
+ /* ### this is completely wrong. it only catches the current network
+ ### packet. we need ongoing parsing. see SERVER_ERROR down below
+ ### in the process_body: area. we'll eventually move to that. */
+ SVN_ERR(handle_server_error(request, response, scratch_pool));
- if (!ctx->session->pending_error)
+ 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 (sl.code == 405 && !strcmp(ctx->method, "LOCK"))
+ if (handler->sline.code == 405
+ && strcmp(handler->method, "LOCK") == 0)
apr_err = SVN_ERR_FS_OUT_OF_DATE;
- return
- svn_error_createf(apr_err, NULL,
- _("%s request on '%s' failed: %d %s"),
- ctx->method, ctx->path, sl.code, sl.reason);
+ return svn_error_createf(apr_err, NULL,
+ _("%s request on '%s' failed: %d %s"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason);
}
return SVN_NO_ERROR; /* Error is set in caller */
}
- else
+
+ /* 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)
{
- svn_error_t *err;
+ *serf_status = drain_bucket(response);
+ return SVN_NO_ERROR;
+ }
- err = ctx->response_handler(request,response, ctx->response_baton, pool);
+ /* 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 (err
- && (!SERF_BUCKET_READ_ERROR(err->apr_err)
- || APR_STATUS_IS_ECONNRESET(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;
+ /* APR_EOF will be returned when parsing is complete. If we see
+ any other error, return it immediately.
+
+ In practice the only other error we expect to see is APR_EAGAIN,
+ which indicates the network has no more data right now. This
+ svn_error_t will get unwrapped, and that APR_EAGAIN will be
+ returned to serf. We'll get called later, when more network data
+ is available. */
+ if (!err || !APR_STATUS_IS_EOF(err->apr_err))
+ return svn_error_trace(err);
+
+ /* 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;
}
- return svn_error_trace(err);
+ *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)))
+ {
+ /* 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 ctx->session->pending_error if appropriate. */
+ 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 *pool)
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__handler_t *ctx = baton;
- svn_ra_serf__session_t *session = ctx->session;
+ svn_ra_serf__handler_t *handler = baton;
svn_error_t *err;
- apr_status_t serf_status = APR_SUCCESS;
+ apr_status_t inner_status;
+ apr_status_t outer_status;
- err = svn_error_trace(
- handle_response(request, response, ctx, &serf_status, pool));
+ err = svn_error_trace(handle_response(request, response,
+ handler, &inner_status,
+ scratch_pool));
- if (err || session->pending_error)
- {
- session->pending_error = svn_error_compose_create(session->pending_error,
- err);
+ /* Select the right status value to return. */
+ outer_status = save_error(handler->session, err);
+ if (!outer_status)
+ outer_status = inner_status;
- serf_status = session->pending_error->apr_err;
- }
+ /* 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 serf_status;
+ return outer_status;
}
-/* If the CTX->setup() callback is non-NULL, invoke it to carry out the
- majority of the serf_request_setup_t implementation. Otherwise, perform
- default setup, with special handling for HEAD requests, and finer-grained
- callbacks invoked (if non-NULL) to produce the request headers and
- body. */
+/* 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 *ctx,
- serf_bucket_t **req_bkt,
- serf_response_acceptor_t *acceptor,
- void **acceptor_baton,
- serf_response_handler_t *handler,
- void **handler_baton,
- apr_pool_t *pool)
+ 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;
- *acceptor = svn_ra_serf__accept_response;
- *acceptor_baton = ctx->session;
-
- if (ctx->setup)
+ if (handler->body_delegate)
{
- svn_ra_serf__response_handler_t response_handler;
- void *response_baton;
-
- SVN_ERR(ctx->setup(request, ctx->setup_baton, req_bkt,
- acceptor, acceptor_baton,
- &response_handler, &response_baton,
- pool));
+ serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
- ctx->response_handler = response_handler;
- ctx->response_baton = response_baton;
+ /* ### should pass the scratch_pool */
+ SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
+ bkt_alloc, request_pool));
}
else
{
- serf_bucket_t *body_bkt;
- serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
-
- if (strcmp(ctx->method, "HEAD") == 0)
- {
- *acceptor = accept_head;
- }
-
- if (ctx->body_delegate)
- {
- SVN_ERR(ctx->body_delegate(&body_bkt, ctx->body_delegate_baton,
- bkt_alloc, pool));
- }
- else
- {
- body_bkt = NULL;
- }
+ body_bkt = NULL;
+ }
- SVN_ERR(svn_ra_serf__setup_serf_req(request, req_bkt, &headers_bkt,
- ctx->conn, ctx->method, ctx->path,
- body_bkt, ctx->body_type));
+ SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
+ handler->conn, handler->method, handler->path,
+ body_bkt, handler->body_type,
+ request_pool, scratch_pool));
- if (ctx->header_delegate)
- {
- SVN_ERR(ctx->header_delegate(headers_bkt, ctx->header_delegate_baton,
- pool));
- }
+ if (handler->header_delegate)
+ {
+ /* ### should pass the scratch_pool */
+ SVN_ERR(handler->header_delegate(headers_bkt,
+ handler->header_delegate_baton,
+ request_pool));
}
- *handler = handle_response_cb;
- *handler_baton = ctx;
-
return APR_SUCCESS;
}
@@ -2232,40 +2030,58 @@ setup_request_cb(serf_request_t *request
serf_bucket_t **req_bkt,
serf_response_acceptor_t *acceptor,
void **acceptor_baton,
- serf_response_handler_t *handler,
- void **handler_baton,
+ serf_response_handler_t *s_handler,
+ void **s_handler_baton,
apr_pool_t *pool)
{
- svn_ra_serf__handler_t *ctx = setup_baton;
+ svn_ra_serf__handler_t *handler = setup_baton;
svn_error_t *err;
- err = setup_request(request, ctx,
- req_bkt,
- acceptor, acceptor_baton,
- handler, handler_baton,
- pool);
+ /* ### 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 (err)
- {
- ctx->session->pending_error
- = svn_error_compose_create(ctx->session->pending_error,
- err);
+ if (strcmp(handler->method, "HEAD") == 0)
+ *acceptor = accept_head;
+ else
+ *acceptor = accept_response;
+ *acceptor_baton = handler->session;
- return err->apr_err;
- }
+ *s_handler = handle_response_cb;
+ *s_handler_baton = handler;
- return APR_SUCCESS;
+ 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,
@@ -2298,26 +2114,22 @@ svn_ra_serf__discover_vcc(const char **v
apr_hash_t *props;
svn_error_t *err;
- err = svn_ra_serf__retrieve_props(&props, session, conn,
- path, SVN_INVALID_REVNUM,
- "0", base_props, pool, pool);
+ err = svn_ra_serf__fetch_node_props(&props, conn,
+ path, SVN_INVALID_REVNUM,
+ base_props, pool, pool);
if (! err)
{
- *vcc_url =
- svn_ra_serf__get_ver_prop(props, path,
- SVN_INVALID_REVNUM,
- "DAV:",
+ apr_hash_t *ns_props;
+
+ ns_props = apr_hash_get(props, "DAV:", 4);
+ *vcc_url = svn_prop_get_value(ns_props,
"version-controlled-configuration");
- relative_path = svn_ra_serf__get_ver_prop(props, path,
- SVN_INVALID_REVNUM,
- SVN_DAV_PROP_NS_DAV,
- "baseline-relative-path");
-
- uuid = svn_ra_serf__get_ver_prop(props, path,
- SVN_INVALID_REVNUM,
- SVN_DAV_PROP_NS_DAV,
- "repository-uuid");
+ ns_props = apr_hash_get(props,
+ SVN_DAV_PROP_NS_DAV, APR_HASH_KEY_STRING);
+ relative_path = svn_prop_get_value(ns_props,
+ "baseline-relative-path");
+ uuid = svn_prop_get_value(ns_props, "repository-uuid");
break;
}
else
@@ -2477,3 +2289,175 @@ svn_ra_serf__register_editor_shim_callba
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;
+
+ SVN_ERR_ASSERT(ectx->parser != NULL);
+
+ /* ### should we bail on anything < 200 or >= 300 ??
+ ### actually: < 200 should really be handled by the core. */
+ if (ectx->handler->sline.code == 404)
+ {
+ /* 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_error_wrap_apr(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 */);
+ if (expat_status == XML_STATUS_ERROR)
+ return svn_error_createf(SVN_ERR_XML_MALFORMED, NULL,
+ _("The %s response contains invalid XML"
+ " (%d %s)"),
+ ectx->handler->method,
+ ectx->handler->sline.code,
+ ectx->handler->sline.reason);
+
+ /* Was an error dropped off for us? */
+ if (ectx->inner_error)
+ {
+ apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup);
+ return svn_error_trace(ectx->inner_error);
+ }
+
+ /* The parsing went fine. What has the bucket told us? */
+
+ if (APR_STATUS_IS_EAGAIN(status))
+ return svn_error_wrap_apr(status, NULL);
+
+ 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 */);
+
+ 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... */
+
+ return svn_error_wrap_apr(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 = XML_ParserCreate(NULL);
+ apr_pool_cleanup_register(result_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);
+
+
+ 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;
+}