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;
+}