diff options
Diffstat (limited to 'subversion/libsvn_ra_serf/util.c')
| -rw-r--r-- | subversion/libsvn_ra_serf/util.c | 1523 | 
1 files changed, 327 insertions, 1196 deletions
diff --git a/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c index 8f6c1bb5d4fa..98f1f4a8cd63 100644 --- a/subversion/libsvn_ra_serf/util.c +++ b/subversion/libsvn_ra_serf/util.c @@ -32,83 +32,22 @@  #include <serf.h>  #include <serf_bucket_types.h> -#include <expat.h> -  #include "svn_hash.h"  #include "svn_dirent_uri.h"  #include "svn_path.h"  #include "svn_private_config.h"  #include "svn_string.h" -#include "svn_xml.h"  #include "svn_props.h"  #include "svn_dirent_uri.h"  #include "../libsvn_ra/ra_loader.h"  #include "private/svn_dep_compat.h"  #include "private/svn_fspath.h" -#include "private/svn_subr_private.h"  #include "private/svn_auth_private.h"  #include "private/svn_cert.h"  #include "ra_serf.h" - -/* Fix for older expat 1.95.x's that do not define - * XML_STATUS_OK/XML_STATUS_ERROR - */ -#ifndef XML_STATUS_OK -#define XML_STATUS_OK    1 -#define XML_STATUS_ERROR 0 -#endif - -#ifndef XML_VERSION_AT_LEAST -#define XML_VERSION_AT_LEAST(major,minor,patch)                  \ -(((major) < XML_MAJOR_VERSION)                                       \ - || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION)    \ - || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \ -     (patch) <= XML_MICRO_VERSION)) -#endif /* APR_VERSION_AT_LEAST */ - -#if XML_VERSION_AT_LEAST(1, 95, 8) -#define EXPAT_HAS_STOPPARSER -#endif - -/* Read/write chunks of this size into the spillbuf.  */ -#define PARSE_CHUNK_SIZE 8000 - -/* We will store one megabyte in memory, before switching to store content -   into a temporary file.  */ -#define SPILL_SIZE 1000000 - - -/* This structure records pending data for the parser in memory blocks, -   and possibly into a temporary file if "too much" content arrives.  */ -struct svn_ra_serf__pending_t { -  /* The spillbuf where we record the pending data.  */ -  svn_spillbuf_t *buf; - -  /* This flag is set when the network has reached EOF. The PENDING -     processing can then properly detect when parsing has completed.  */ -  svn_boolean_t network_eof; -}; - -#define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \ -                             && svn_spillbuf__get_size((p)->buf) != 0) - - -struct expat_ctx_t { -  svn_ra_serf__xml_context_t *xmlctx; -  XML_Parser parser; -  svn_ra_serf__handler_t *handler; - -  svn_error_t *inner_error; - -  /* Do not use this pool for allocation. It is merely recorded for running -     the cleanup handler.  */ -  apr_pool_t *cleanup_pool; -}; - -  static const apr_uint32_t serf_failure_map[][2] =  {    { SERF_SSL_CERT_NOTYETVALID,   SVN_AUTH_SSL_NOTYETVALID }, @@ -192,6 +131,7 @@ construct_realm(svn_ra_serf__session_t *session,  static char *  convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)  { +  const char *cn = svn_hash_gets(org, "CN");    const char *org_unit = svn_hash_gets(org, "OU");    const char *org_name = svn_hash_gets(org, "O");    const char *locality = svn_hash_gets(org, "L"); @@ -200,6 +140,12 @@ convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)    const char *email = svn_hash_gets(org, "E");    svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool); +  if (cn) +    { +      svn_stringbuf_appendcstr(buf, cn); +      svn_stringbuf_appendcstr(buf, ", "); +    } +    if (org_unit)      {        svn_stringbuf_appendcstr(buf, org_unit); @@ -285,7 +231,6 @@ ssl_server_cert(void *baton, int failures,        ### This should really be handled by serf, which should pass an error            for this case, but that has backwards compatibility issues. */        apr_array_header_t *san; -      svn_boolean_t found_san_entry = FALSE;        svn_boolean_t found_matching_hostname = FALSE;        svn_string_t *actual_hostname =            svn_string_create(conn->session->session_url.hostname, scratch_pool); @@ -293,11 +238,16 @@ ssl_server_cert(void *baton, int failures,        serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);        san = svn_hash_gets(serf_cert, "subjectAltName"); -      /* Try to find matching server name via subjectAltName first... */ -      if (san) +      /* Match server certificate CN with the hostname of the server iff +       * we didn't find any subjectAltName fields and try to match them. +       * Per RFC 2818 they are authoritative if present and CommonName +       * should be ignored.  NOTE: This isn't 100% correct since serf +       * only loads the subjectAltName hash with dNSNames, technically +       * we should ignore the CommonName if any subjectAltName entry +       * exists even if it is one we don't support. */ +      if (san && san->nelts > 0)          {            int i; -          found_san_entry = san->nelts > 0;            for (i = 0; i < san->nelts; i++)              {                const char *s = APR_ARRAY_IDX(san, i, const char*); @@ -310,12 +260,7 @@ ssl_server_cert(void *baton, int failures,                  }              }          } - -      /* Match server certificate CN with the hostname of the server iff -       * we didn't find any subjectAltName fields and try to match them. -       * Per RFC 2818 they are authoritative if present and CommonName -       * should be ignored. */ -      if (!found_matching_hostname && !found_san_entry) +      else          {            const char *hostname = NULL; @@ -368,11 +313,11 @@ ssl_server_cert(void *baton, int failures,      {        svn_error_t *err; -      svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, +      svn_auth_set_parameter(conn->session->auth_baton,                               SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,                               &cert_info); -      svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, +      svn_auth_set_parameter(conn->session->auth_baton,                               SVN_AUTH_PARAM_SSL_SERVER_FAILURES,                               &svn_failures); @@ -382,13 +327,13 @@ ssl_server_cert(void *baton, int failures,        err = svn_auth_first_credentials(&creds, &state,                                         SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,                                         realmstring, -                                       conn->session->wc_callbacks->auth_baton, +                                       conn->session->auth_baton,                                         scratch_pool); -      svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, +      svn_auth_set_parameter(conn->session->auth_baton,                               SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL); -      svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, +      svn_auth_set_parameter(conn->session->auth_baton,                               SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);        if (err) @@ -415,11 +360,11 @@ ssl_server_cert(void *baton, int failures,        return APR_SUCCESS;      } -  svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, +  svn_auth_set_parameter(conn->session->auth_baton,                           SVN_AUTH_PARAM_SSL_SERVER_FAILURES,                           &svn_failures); -  svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, +  svn_auth_set_parameter(conn->session->auth_baton,                           SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,                           &cert_info); @@ -428,7 +373,7 @@ ssl_server_cert(void *baton, int failures,    SVN_ERR(svn_auth_first_credentials(&creds, &state,                                       SVN_AUTH_CRED_SSL_SERVER_TRUST,                                       realmstring, -                                     conn->session->wc_callbacks->auth_baton, +                                     conn->session->auth_baton,                                       scratch_pool));    if (creds)      { @@ -449,7 +394,7 @@ ssl_server_cert(void *baton, int failures,          }      } -  svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, +  svn_auth_set_parameter(conn->session->auth_baton,                           SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);    /* Are there non accepted failures left? */ @@ -622,6 +567,7 @@ accept_response(serf_request_t *request,                  void *acceptor_baton,                  apr_pool_t *pool)  { +  /* svn_ra_serf__handler_t *handler = acceptor_baton; */    serf_bucket_t *c;    serf_bucket_alloc_t *bkt_alloc; @@ -639,6 +585,7 @@ accept_head(serf_request_t *request,              void *acceptor_baton,              apr_pool_t *pool)  { +  /* svn_ra_serf__handler_t *handler = acceptor_baton; */    serf_bucket_t *response;    response = accept_response(request, stream, acceptor_baton, pool); @@ -656,7 +603,7 @@ connection_closed(svn_ra_serf__connection_t *conn,  {    if (why)      { -      return svn_error_wrap_apr(why, NULL); +      return svn_ra_serf__wrap_err(why, NULL);      }    if (conn->session->using_ssl) @@ -701,7 +648,7 @@ handle_client_cert(void *data,                                             &conn->ssl_client_auth_state,                                             SVN_AUTH_CRED_SSL_CLIENT_CERT,                                             realm, -                                           session->wc_callbacks->auth_baton, +                                           session->auth_baton,                                             pool));        }      else @@ -753,7 +700,7 @@ handle_client_cert_pw(void *data,                                             &conn->ssl_client_pw_auth_state,                                             SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,                                             cert_path, -                                           session->wc_callbacks->auth_baton, +                                           session->auth_baton,                                             pool));        }      else @@ -804,6 +751,9 @@ apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,   *   * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.   * + * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers + * to request. + *   * REQUEST_POOL should live for the duration of the request. Serf will   * construct this and provide it to the request_setup callback, so we   * should just use that one. @@ -816,6 +766,7 @@ setup_serf_req(serf_request_t *request,                 const char *method, const char *url,                 serf_bucket_t *body_bkt, const char *content_type,                 const char *accept_encoding, +               svn_boolean_t dav_headers,                 apr_pool_t *request_pool,                 apr_pool_t *scratch_pool)  { @@ -882,12 +833,86 @@ setup_serf_req(serf_request_t *request,        serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);      } -  /* These headers need to be sent with every request; see issue #3255 -     ("mod_dav_svn does not pass client capabilities to start-commit -     hooks") for why. */ -  serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH); -  serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO); -  serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS); +  /* These headers need to be sent with every request that might need +     capability processing (e.g. during commit, reports, etc.), see +     issue #3255 ("mod_dav_svn does not pass client capabilities to +     start-commit hooks") for why. + +     Some request types like GET/HEAD/PROPFIND are unaware of capability +     handling; and in some cases the responses can even be cached by +     proxies, so we don't have to send these hearders there. */ +  if (dav_headers) +    { +      serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH); +      serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO); +      serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS); +    } + +  return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__context_run(svn_ra_serf__session_t *sess, +                         apr_interval_time_t *waittime_left, +                         apr_pool_t *scratch_pool) +{ +  apr_status_t status; +  svn_error_t *err; +  assert(sess->pending_error == SVN_NO_ERROR); + +  if (sess->cancel_func) +    SVN_ERR(sess->cancel_func(sess->cancel_baton)); + +  status = serf_context_run(sess->context, +                            SVN_RA_SERF__CONTEXT_RUN_DURATION, +                            scratch_pool); + +  err = sess->pending_error; +  sess->pending_error = SVN_NO_ERROR; + +   /* If the context duration timeout is up, we'll subtract that +      duration from the total time alloted for such things.  If +      there's no time left, we fail with a message indicating that +      the connection timed out.  */ +  if (APR_STATUS_IS_TIMEUP(status)) +    { +      status = 0; + +      if (sess->timeout) +        { +          if (*waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) +            { +              *waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; +            } +          else +            { +              return +                  svn_error_compose_create( +                        err, +                        svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, +                                         _("Connection timed out"))); +            } +        } +    } +  else +    { +      *waittime_left = sess->timeout; +    } + +  SVN_ERR(err); +  if (status) +    { +      /* ### This omits SVN_WARNING, and possibly relies on the fact that +         ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */ +      if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST) +        { +          /* apr can't translate subversion errors to text */ +          SVN_ERR_W(svn_error_create(status, NULL, NULL), +                    _("Error running context")); +        } + +      return svn_ra_serf__wrap_err(status, _("Error running context")); +    }    return SVN_NO_ERROR;  } @@ -905,63 +930,11 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done,    iterpool = svn_pool_create(scratch_pool);    while (!*done)      { -      apr_status_t status; -      svn_error_t *err;        int i;        svn_pool_clear(iterpool); -      if (sess->cancel_func) -        SVN_ERR((*sess->cancel_func)(sess->cancel_baton)); - -      status = serf_context_run(sess->context, -                                SVN_RA_SERF__CONTEXT_RUN_DURATION, -                                iterpool); - -      err = sess->pending_error; -      sess->pending_error = SVN_NO_ERROR; - -      /* If the context duration timeout is up, we'll subtract that -         duration from the total time alloted for such things.  If -         there's no time left, we fail with a message indicating that -         the connection timed out.  */ -      if (APR_STATUS_IS_TIMEUP(status)) -        { -          status = 0; - -          if (sess->timeout) -            { -              if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) -                { -                  waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; -                } -              else -                { -                  return -                      svn_error_compose_create( -                            err, -                            svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, -                                             _("Connection timed out"))); -                } -            } -        } -      else -        { -          waittime_left = sess->timeout; -        } - -      SVN_ERR(err); -      if (status) -        { -          if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST) -            { -              /* apr can't translate subversion errors to text */ -              SVN_ERR_W(svn_error_create(status, NULL, NULL), -                        _("Error running context")); -            } - -          return svn_ra_serf__wrap_err(status, _("Error running context")); -        } +      SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));        /* Debugging purposes only! */        for (i = 0; i < sess->num_conns; i++) @@ -974,6 +947,22 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done,    return SVN_NO_ERROR;  } +/* Ensure that a handler is no longer scheduled on the connection. + +   Eventually serf will have a reliable way to cancel existing requests, +   but currently it doesn't even have a way to relyable identify a request +   after rescheduling, for auth reasons. + +   So the only thing we can do today is reset the connection, which +   will cancel all outstanding requests and prepare the connection +   for re-use. +*/ +static void +svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler) +{ +  serf_connection_reset(handler->conn->conn); +  handler->scheduled = FALSE; +}  svn_error_t *  svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler, @@ -988,130 +977,18 @@ svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,    err = svn_ra_serf__context_run_wait(&handler->done, handler->session,                                        scratch_pool); -  /* A callback invocation has been canceled. In this simple case of -     context_run_one, we can keep the ra-session operational by resetting -     the connection. - -     If we don't do this, the next context run will notice that the connection -     is still in the error state and will just return SVN_ERR_CEASE_INVOCATION -     (=the last error for the connection) again  */ -  if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) +  if (handler->scheduled)      { -      apr_status_t status = serf_connection_reset(handler->conn->conn); - -      if (status) -        err = svn_error_compose_create(err, -                                       svn_ra_serf__wrap_err(status, NULL)); -    } - -  if (handler->server_error) -    { -      err = svn_error_compose_create(err, handler->server_error->error); -      handler->server_error = NULL; +      /* We reset the connection (breaking  pipelining, etc.), as +         if we didn't the next data would still be handled by this handler, +         which is done as far as our caller is concerned. */ +      svn_ra_serf__unschedule_handler(handler);      }    return svn_error_trace(err);  } -/* - * Expat callback invoked on a start element tag for an error response. - */ -static svn_error_t * -start_error(svn_ra_serf__xml_parser_t *parser, -            svn_ra_serf__dav_props_t name, -            const char **attrs, -            apr_pool_t *scratch_pool) -{ -  svn_ra_serf__server_error_t *ctx = parser->user_data; - -  if (!ctx->in_error && -      strcmp(name.namespace, "DAV:") == 0 && -      strcmp(name.name, "error") == 0) -    { -      ctx->in_error = TRUE; -    } -  else if (ctx->in_error && strcmp(name.name, "human-readable") == 0) -    { -      const char *err_code; - -      err_code = svn_xml_get_attr_value("errcode", attrs); -      if (err_code) -        { -          apr_int64_t val; - -          SVN_ERR(svn_cstring_atoi64(&val, err_code)); -          ctx->error->apr_err = (apr_status_t)val; -        } - -      /* If there's no error code provided, or if the provided code is -         0 (which can happen sometimes depending on how the error is -         constructed on the server-side), just pick a generic error -         code to run with. */ -      if (! ctx->error->apr_err) -        { -          ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; -        } - -      /* Start collecting cdata. */ -      svn_stringbuf_setempty(ctx->cdata); -      ctx->collect_cdata = TRUE; -    } - -  return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on an end element tag for a PROPFIND response. - */ -static svn_error_t * -end_error(svn_ra_serf__xml_parser_t *parser, -          svn_ra_serf__dav_props_t name, -          apr_pool_t *scratch_pool) -{ -  svn_ra_serf__server_error_t *ctx = parser->user_data; - -  if (ctx->in_error && -      strcmp(name.namespace, "DAV:") == 0 && -      strcmp(name.name, "error") == 0) -    { -      ctx->in_error = FALSE; -    } -  if (ctx->in_error && strcmp(name.name, "human-readable") == 0) -    { -      /* On the server dav_error_response_tag() will add a leading -         and trailing newline if DEBUG_CR is defined in mod_dav.h, -         so remove any such characters here. */ -      svn_stringbuf_strip_whitespace(ctx->cdata); - -      ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data, -                                           ctx->cdata->len); -      ctx->collect_cdata = FALSE; -    } - -  return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on CDATA elements in an error response. - * - * This callback can be called multiple times. - */ -static svn_error_t * -cdata_error(svn_ra_serf__xml_parser_t *parser, -            const char *data, -            apr_size_t len, -            apr_pool_t *scratch_pool) -{ -  svn_ra_serf__server_error_t *ctx = parser->user_data; - -  if (ctx->collect_cdata) -    { -      svn_stringbuf_appendbytes(ctx->cdata, data, len); -    } - -  return SVN_NO_ERROR; -}  static apr_status_t @@ -1131,28 +1008,7 @@ drain_bucket(serf_bucket_t *bucket)  } -static svn_ra_serf__server_error_t * -begin_error_parsing(svn_ra_serf__xml_start_element_t start, -                    svn_ra_serf__xml_end_element_t end, -                    svn_ra_serf__xml_cdata_chunk_handler_t cdata, -                    apr_pool_t *result_pool) -{ -  svn_ra_serf__server_error_t *server_err; - -  server_err = apr_pcalloc(result_pool, sizeof(*server_err)); -  server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL); -  server_err->contains_precondition_error = FALSE; -  server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool); -  server_err->collect_cdata = FALSE; -  server_err->parser.pool = server_err->error->pool; -  server_err->parser.user_data = server_err; -  server_err->parser.start = start; -  server_err->parser.end = end; -  server_err->parser.cdata = cdata; -  server_err->parser.ignore_errors = TRUE; - -  return server_err; -} +  /* Implements svn_ra_serf__response_handler_t */  svn_error_t * @@ -1241,7 +1097,7 @@ svn_ra_serf__expect_empty_body(serf_request_t *request,    const char *val;    /* This function is just like handle_multistatus_only() except for the -     XML parsing callbacks. We want to look for the human-readable element.  */ +     XML parsing callbacks. We want to look for the -readable element.  */    /* We should see this just once, in order to initialize SERVER_ERROR.       At that point, the core error processing will take over. If we choose @@ -1251,21 +1107,22 @@ svn_ra_serf__expect_empty_body(serf_request_t *request,    hdrs = serf_bucket_response_get_headers(response);    val = serf_bucket_headers_get(hdrs, "Content-Type"); -  if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) +  if (val +      && (handler->sline.code < 200 || handler->sline.code >= 300) +      && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)      {        svn_ra_serf__server_error_t *server_err; -      server_err = begin_error_parsing(start_error, end_error, cdata_error, -                                       handler->handler_pool); - -      /* Get the parser to set our DONE flag.  */ -      server_err->parser.done = &handler->done; +      SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler, +                                               FALSE, +                                               handler->handler_pool, +                                               handler->handler_pool));        handler->server_error = server_err;      }    else      { -      /* The body was not text/xml, so we don't know what to do with it. +      /* The body was not text/xml, or we got a success code.           Toss anything that arrives.  */        handler->discard_body = TRUE;      } @@ -1277,631 +1134,6 @@ svn_ra_serf__expect_empty_body(serf_request_t *request,  } -/* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric -   status code into *STATUS_CODE_OUT.  Ignores leading whitespace. */ -static svn_error_t * -parse_dav_status(int *status_code_out, svn_stringbuf_t *buf, -                 apr_pool_t *scratch_pool) -{ -  svn_error_t *err; -  const char *token; -  char *tok_status; -  svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool); - -  svn_stringbuf_strip_whitespace(temp_buf); -  token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status); -  if (token) -    token = apr_strtok(NULL, " \t\r\n", &tok_status); -  if (!token) -    return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, -                             _("Malformed DAV:status CDATA '%s'"), -                             buf->data); -  err = svn_cstring_atoi(status_code_out, token); -  if (err) -    return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err, -                             _("Malformed DAV:status CDATA '%s'"), -                             buf->data); - -  return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on a start element tag for a 207 response. - */ -static svn_error_t * -start_207(svn_ra_serf__xml_parser_t *parser, -          svn_ra_serf__dav_props_t name, -          const char **attrs, -          apr_pool_t *scratch_pool) -{ -  svn_ra_serf__server_error_t *ctx = parser->user_data; - -  if (!ctx->in_error && -      strcmp(name.namespace, "DAV:") == 0 && -      strcmp(name.name, "multistatus") == 0) -    { -      ctx->in_error = TRUE; -    } -  else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0) -    { -      /* Start collecting cdata. */ -      svn_stringbuf_setempty(ctx->cdata); -      ctx->collect_cdata = TRUE; -    } -  else if (ctx->in_error && -           strcmp(name.namespace, "DAV:") == 0 && -           strcmp(name.name, "status") == 0) -    { -      /* Start collecting cdata. */ -      svn_stringbuf_setempty(ctx->cdata); -      ctx->collect_cdata = TRUE; -    } - -  return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on an end element tag for a 207 response. - */ -static svn_error_t * -end_207(svn_ra_serf__xml_parser_t *parser, -        svn_ra_serf__dav_props_t name, -        apr_pool_t *scratch_pool) -{ -  svn_ra_serf__server_error_t *ctx = parser->user_data; - -  if (ctx->in_error && -      strcmp(name.namespace, "DAV:") == 0 && -      strcmp(name.name, "multistatus") == 0) -    { -      ctx->in_error = FALSE; -    } -  if (ctx->in_error && strcmp(name.name, "responsedescription") == 0) -    { -      /* Remove leading newline added by DEBUG_CR on server */ -      svn_stringbuf_strip_whitespace(ctx->cdata); - -      ctx->collect_cdata = FALSE; -      ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data, -                                           ctx->cdata->len); -      if (ctx->contains_precondition_error) -        ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH; -      else -        ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; -    } -  else if (ctx->in_error && -           strcmp(name.namespace, "DAV:") == 0 && -           strcmp(name.name, "status") == 0) -    { -      int status_code; - -      ctx->collect_cdata = FALSE; - -      SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool)); -      if (status_code == 412) -        ctx->contains_precondition_error = TRUE; -    } - -  return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on CDATA elements in a 207 response. - * - * This callback can be called multiple times. - */ -static svn_error_t * -cdata_207(svn_ra_serf__xml_parser_t *parser, -          const char *data, -          apr_size_t len, -          apr_pool_t *scratch_pool) -{ -  svn_ra_serf__server_error_t *ctx = parser->user_data; - -  if (ctx->collect_cdata) -    { -      svn_stringbuf_appendbytes(ctx->cdata, data, len); -    } - -  return SVN_NO_ERROR; -} - -/* Implements svn_ra_serf__response_handler_t */ -svn_error_t * -svn_ra_serf__handle_multistatus_only(serf_request_t *request, -                                     serf_bucket_t *response, -                                     void *baton, -                                     apr_pool_t *scratch_pool) -{ -  svn_ra_serf__handler_t *handler = baton; - -  /* This function is just like expect_empty_body() except for the -     XML parsing callbacks. We are looking for very limited pieces of -     the multistatus response.  */ - -  /* We should see this just once, in order to initialize SERVER_ERROR. -     At that point, the core error processing will take over. If we choose -     not to parse an error, then we'll never return here (because we -     change the response handler).  */ -  SVN_ERR_ASSERT(handler->server_error == NULL); - -    { -      serf_bucket_t *hdrs; -      const char *val; - -      hdrs = serf_bucket_response_get_headers(response); -      val = serf_bucket_headers_get(hdrs, "Content-Type"); -      if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) -        { -          svn_ra_serf__server_error_t *server_err; - -          server_err = begin_error_parsing(start_207, end_207, cdata_207, -                                           handler->handler_pool); - -          /* Get the parser to set our DONE flag.  */ -          server_err->parser.done = &handler->done; - -          handler->server_error = server_err; -        } -      else -        { -          /* The body was not text/xml, so we don't know what to do with it. -             Toss anything that arrives.  */ -          handler->discard_body = TRUE; -        } -    } - -  /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it -     to call the response handler again. That will start up the XML parsing, -     or it will be dropped on the floor (per the decision above).  */ -  return SVN_NO_ERROR; -} - - -/* Conforms to Expat's XML_StartElementHandler  */ -static void -start_xml(void *userData, const char *raw_name, const char **attrs) -{ -  svn_ra_serf__xml_parser_t *parser = userData; -  svn_ra_serf__dav_props_t name; -  apr_pool_t *scratch_pool; -  svn_error_t *err; - -  if (parser->error) -    return; - -  if (!parser->state) -    svn_ra_serf__xml_push_state(parser, 0); - -  /* ### get a real scratch_pool  */ -  scratch_pool = parser->state->pool; - -  svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool); - -  svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name); - -  err = parser->start(parser, name, attrs, scratch_pool); -  if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) -    err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); - -  parser->error = err; -} - - -/* Conforms to Expat's XML_EndElementHandler  */ -static void -end_xml(void *userData, const char *raw_name) -{ -  svn_ra_serf__xml_parser_t *parser = userData; -  svn_ra_serf__dav_props_t name; -  svn_error_t *err; -  apr_pool_t *scratch_pool; - -  if (parser->error) -    return; - -  /* ### get a real scratch_pool  */ -  scratch_pool = parser->state->pool; - -  svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name); - -  err = parser->end(parser, name, scratch_pool); -  if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) -    err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); - -  parser->error = err; -} - - -/* Conforms to Expat's XML_CharacterDataHandler  */ -static void -cdata_xml(void *userData, const char *data, int len) -{ -  svn_ra_serf__xml_parser_t *parser = userData; -  svn_error_t *err; -  apr_pool_t *scratch_pool; - -  if (parser->error) -    return; - -  if (!parser->state) -    svn_ra_serf__xml_push_state(parser, 0); - -  /* ### get a real scratch_pool  */ -  scratch_pool = parser->state->pool; - -  err = parser->cdata(parser, data, len, scratch_pool); -  if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) -    err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); - -  parser->error = err; -} - -/* Flip the requisite bits in CTX to indicate that processing of the -   response is complete, adding the current "done item" to the list of -   completed items. */ -static void -add_done_item(svn_ra_serf__xml_parser_t *ctx) -{ -  /* Make sure we don't add to DONE_LIST twice.  */ -  if (!*ctx->done) -    { -      *ctx->done = TRUE; -      if (ctx->done_list) -        { -          ctx->done_item->data = ctx->user_data; -          ctx->done_item->next = *ctx->done_list; -          *ctx->done_list = ctx->done_item; -        } -    } -} - - -static svn_error_t * -write_to_pending(svn_ra_serf__xml_parser_t *ctx, -                 const char *data, -                 apr_size_t len, -                 apr_pool_t *scratch_pool) -{ -  if (ctx->pending == NULL) -    { -      ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending)); -      ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE, -                                               SPILL_SIZE, -                                               ctx->pool); -    } - -  /* Copy the data into one or more chunks in the spill buffer.  */ -  return svn_error_trace(svn_spillbuf__write(ctx->pending->buf, -                                             data, len, -                                             scratch_pool)); -} - - -static svn_error_t * -inject_to_parser(svn_ra_serf__xml_parser_t *ctx, -                 const char *data, -                 apr_size_t len, -                 const serf_status_line *sl) -{ -  int xml_status; - -  xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0); - -  if (! ctx->ignore_errors) -    { -      SVN_ERR(ctx->error); - -      if (xml_status != XML_STATUS_OK) -        { -          if (sl == NULL) -            return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, -                                     _("XML parsing failed")); - -          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, -                                   _("XML parsing failed: (%d %s)"), -                                   sl->code, sl->reason); -        } -     } - -  return SVN_NO_ERROR; -} - -/* Apr pool cleanup handler to release an XML_Parser in success and error -   conditions */ -static apr_status_t -xml_parser_cleanup(void *baton) -{ -  XML_Parser *xmlp = baton; - -  if (*xmlp) -    { -      (void) XML_ParserFree(*xmlp); -      *xmlp = NULL; -    } - -  return APR_SUCCESS; -} - -/* Limit the amount of pending content to parse at once to < 100KB per -   iteration. This number is chosen somewhat arbitrarely. Making it lower -   will have a drastical negative impact on performance, whereas increasing it -   increases the risk for connection timeouts. - */ -#define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5 - -svn_error_t * -svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser, -                             svn_boolean_t *network_eof, -                             apr_pool_t *scratch_pool) -{ -  svn_boolean_t pending_empty = FALSE; -  apr_size_t cur_read = 0; - -  /* Fast path exit: already paused, nothing to do, or already done.  */ -  if (parser->paused || parser->pending == NULL || *parser->done) -    { -      *network_eof = parser->pending ? parser->pending->network_eof : FALSE; -      return SVN_NO_ERROR; -    } - -  /* Parsing the pending conten in the spillbuf will result in many disc i/o -     operations. This can be so slow that we don't run the network event -     processing loop often enough, resulting in timed out connections. - -     So we limit the amounts of bytes parsed per iteration. -   */ -  while (cur_read < PENDING_TO_PARSE) -    { -      const char *data; -      apr_size_t len; - -      /* Get a block of content, stopping the loop when we run out.  */ -      SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf, -                             scratch_pool)); -      if (data) -        { -          /* Inject the content into the XML parser.  */ -          SVN_ERR(inject_to_parser(parser, data, len, NULL)); - -          /* If the XML parsing callbacks paused us, then we're done for now.  */ -          if (parser->paused) -            break; - -          cur_read += len; -        } -      else -        { -          /* The buffer is empty. */ -          pending_empty = TRUE; -          break; -        } -    } - -  /* If the PENDING structures are empty *and* we consumed all content from -     the network, then we're completely done with the parsing.  */ -  if (pending_empty && -      parser->pending->network_eof) -    { -      int xml_status; -      SVN_ERR_ASSERT(parser->xmlp != NULL); - -      /* Tell the parser that no more content will be parsed. */ -      xml_status = XML_Parse(parser->xmlp, NULL, 0, 1); - -      apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup); -      parser->xmlp = NULL; - -      if (! parser->ignore_errors) -        { -          SVN_ERR(parser->error); - -          if (xml_status != XML_STATUS_OK) -            { -              return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, -                                       _("XML parsing failed")); -            } -        } - -      add_done_item(parser); -    } - -  *network_eof = parser->pending ? parser->pending->network_eof : FALSE; - -  return SVN_NO_ERROR; -} -#undef PENDING_TO_PARSE - - -/* ### this is still broken conceptually. just shifting incrementally... */ -static svn_error_t * -handle_server_error(serf_request_t *request, -                    serf_bucket_t *response, -                    apr_pool_t *scratch_pool) -{ -  svn_ra_serf__server_error_t server_err = { 0 }; -  serf_bucket_t *hdrs; -  const char *val; -  apr_status_t err; - -  hdrs = serf_bucket_response_get_headers(response); -  val = serf_bucket_headers_get(hdrs, "Content-Type"); -  if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) -    { -      /* ### we should figure out how to reuse begin_error_parsing  */ - -      server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL); -      server_err.contains_precondition_error = FALSE; -      server_err.cdata = svn_stringbuf_create_empty(scratch_pool); -      server_err.collect_cdata = FALSE; -      server_err.parser.pool = server_err.error->pool; -      server_err.parser.user_data = &server_err; -      server_err.parser.start = start_error; -      server_err.parser.end = end_error; -      server_err.parser.cdata = cdata_error; -      server_err.parser.done = &server_err.done; -      server_err.parser.ignore_errors = TRUE; - -      /* We don't care about any errors except for SERVER_ERR.ERROR  */ -      svn_error_clear(svn_ra_serf__handle_xml_parser(request, -                                                     response, -                                                     &server_err.parser, -                                                     scratch_pool)); - -      /* ### checking DONE is silly. the above only parses whatever has -         ### been received at the network interface. totally wrong. but -         ### it is what we have for now (maintaining historical code), -         ### until we fully migrate.  */ -      if (server_err.done && server_err.error->apr_err == APR_SUCCESS) -        { -          svn_error_clear(server_err.error); -          server_err.error = SVN_NO_ERROR; -        } - -      return svn_error_trace(server_err.error); -    } - -  /* The only error that we will return is from the XML response body. -     Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to -     surface. */ -  err = drain_bucket(response); -  if (err && !SERF_BUCKET_READ_ERROR(err)) -    return svn_ra_serf__wrap_err(err, NULL); - -  return SVN_NO_ERROR; -} - - -/* Implements svn_ra_serf__response_handler_t */ -svn_error_t * -svn_ra_serf__handle_xml_parser(serf_request_t *request, -                               serf_bucket_t *response, -                               void *baton, -                               apr_pool_t *pool) -{ -  serf_status_line sl; -  apr_status_t status; -  svn_ra_serf__xml_parser_t *ctx = baton; -  svn_error_t *err; - -  /* ### get the HANDLER rather than fetching this.  */ -  status = serf_bucket_response_status(response, &sl); -  if (SERF_BUCKET_READ_ERROR(status)) -    { -      return svn_ra_serf__wrap_err(status, NULL); -    } - -  /* Woo-hoo.  Nothing here to see.  */ -  if (sl.code == 404 && !ctx->ignore_errors) -    { -      err = handle_server_error(request, response, pool); - -      if (err && APR_STATUS_IS_EOF(err->apr_err)) -        add_done_item(ctx); - -      return svn_error_trace(err); -    } - -  if (!ctx->xmlp) -    { -      ctx->xmlp = XML_ParserCreate(NULL); -      apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup, -                                apr_pool_cleanup_null); -      XML_SetUserData(ctx->xmlp, ctx); -      XML_SetElementHandler(ctx->xmlp, start_xml, end_xml); -      if (ctx->cdata) -        { -          XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml); -        } -    } - -  while (1) -    { -      const char *data; -      apr_size_t len; - -      status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len); -      if (SERF_BUCKET_READ_ERROR(status)) -        { -          return svn_ra_serf__wrap_err(status, NULL); -        } - -      /* Note: once the callbacks invoked by inject_to_parser() sets the -         PAUSED flag, then it will not be cleared. write_to_pending() will -         only save the content. Logic outside of serf_context_run() will -         clear that flag, as appropriate, along with processing the -         content that we have placed into the PENDING buffer. - -         We want to save arriving content into the PENDING structures if -         the parser has been paused, or we already have data in there (so -         the arriving data is appended, rather than injected out of order)  */ -      if (ctx->paused || HAS_PENDING_DATA(ctx->pending)) -        { -          err = write_to_pending(ctx, data, len, pool); -        } -      else -        { -          err = inject_to_parser(ctx, data, len, &sl); -          if (err) -            { -              /* Should have no errors if IGNORE_ERRORS is set.  */ -              SVN_ERR_ASSERT(!ctx->ignore_errors); -            } -        } -      if (err) -        { -          SVN_ERR_ASSERT(ctx->xmlp != NULL); - -          apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup); -          add_done_item(ctx); -          return svn_error_trace(err); -        } - -      if (APR_STATUS_IS_EAGAIN(status)) -        { -          return svn_ra_serf__wrap_err(status, NULL); -        } - -      if (APR_STATUS_IS_EOF(status)) -        { -          if (ctx->pending != NULL) -            ctx->pending->network_eof = TRUE; - -          /* We just hit the end of the network content. If we have nothing -             in the PENDING structures, then we're completely done.  */ -          if (!HAS_PENDING_DATA(ctx->pending)) -            { -              int xml_status; -              SVN_ERR_ASSERT(ctx->xmlp != NULL); - -              xml_status = XML_Parse(ctx->xmlp, NULL, 0, 1); - -              apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup); - -              if (! ctx->ignore_errors) -                { -                  SVN_ERR(ctx->error); - -                  if (xml_status != XML_STATUS_OK) -                    { -                      return svn_error_create( -                                    SVN_ERR_XML_MALFORMED, NULL, -                                    _("The XML response contains invalid XML")); -                    } -                } - -              add_done_item(ctx); -            } - -          return svn_ra_serf__wrap_err(status, NULL); -        } - -      /* feed me! */ -    } -  /* not reached */ -} - -  apr_status_t  svn_ra_serf__credentials_callback(char **username, char **password,                                    serf_request_t *request, void *baton, @@ -1927,7 +1159,7 @@ svn_ra_serf__credentials_callback(char **username, char **password,                                             &session->auth_state,                                             SVN_AUTH_CRED_SIMPLE,                                             realm, -                                           session->wc_callbacks->auth_baton, +                                           session->auth_baton,                                             session->pool);          }        else @@ -2008,6 +1240,8 @@ handle_response(serf_request_t *request,    if (!response)      {        /* Uh-oh. Our connection died.  */ +      handler->scheduled = FALSE; +        if (handler->response_error)          {            /* Give a handler chance to prevent request requeue. */ @@ -2126,10 +1360,7 @@ handle_response(serf_request_t *request,      }    handler->conn->last_status_code = handler->sline.code; -  if (handler->sline.code == 405 -      || handler->sline.code == 408 -      || handler->sline.code == 409 -      || handler->sline.code >= 500) +  if (handler->sline.code >= 400)      {        /* 405 Method Not allowed.           408 Request Timeout @@ -2144,35 +1375,22 @@ handle_response(serf_request_t *request,          {            svn_ra_serf__server_error_t *server_err; -          server_err = begin_error_parsing(start_error, end_error, cdata_error, -                                           handler->handler_pool); -          /* Get the parser to set our DONE flag.  */ -          server_err->parser.done = &handler->done; +          SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler, +                                                   FALSE, +                                                   handler->handler_pool, +                                                   handler->handler_pool));            handler->server_error = server_err;          }        else          {            handler->discard_body = TRUE; - -          if (!handler->session->pending_error) -            { -              apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; - -              /* 405 == Method Not Allowed (Occurs when trying to lock a working -                copy path which no longer exists at HEAD in the repository. */ -              if (handler->sline.code == 405 -                  && strcmp(handler->method, "LOCK") == 0) -                apr_err = SVN_ERR_FS_OUT_OF_DATE; - -              handler->session->pending_error = -                  svn_error_createf(apr_err, NULL, -                                    _("%s request on '%s' failed: %d %s"), -                                   handler->method, handler->path, -                                   handler->sline.code, handler->sline.reason); -            }          }      } +  else if (handler->sline.code <= 199) +    { +      handler->discard_body = TRUE; +    }    /* Stop processing the above, on every packet arrival.  */    handler->reading_body = TRUE; @@ -2184,13 +1402,6 @@ handle_response(serf_request_t *request,      {        *serf_status = drain_bucket(response); -      /* If the handler hasn't set done (which it shouldn't have) and -         we now have the EOF, go ahead and set it so that we can stop -         our context loops. -       */ -      if (!handler->done && APR_STATUS_IS_EOF(*serf_status)) -          handler->done = TRUE; -        return SVN_NO_ERROR;      } @@ -2198,50 +1409,12 @@ handle_response(serf_request_t *request,       that now.  */    if (handler->server_error != NULL)      { -      err = svn_ra_serf__handle_xml_parser(request, response, -                                           &handler->server_error->parser, -                                           scratch_pool); - -      /* If we do not receive an error or it is a non-transient error, return -         immediately. - -         APR_EOF will be returned when parsing is complete. - -         APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through -         parsing and the network has no more data right now.  If we receive that, -         clear the error and return - allowing serf to wait for more data. -         */ -      if (!err || SERF_BUCKET_READ_ERROR(err->apr_err)) -        return svn_error_trace(err); - -      if (!APR_STATUS_IS_EOF(err->apr_err)) -        { -          *serf_status = err->apr_err; -          svn_error_clear(err); -          return SVN_NO_ERROR; -        } - -      /* Clear the EOF. We don't need it.  */ -      svn_error_clear(err); - -      /* If the parsing is done, and we did not extract an error, then -         simply toss everything, and anything else that might arrive. -         The higher-level code will need to investigate HANDLER->SLINE, -         as we have no further information for them.  */ -      if (handler->done -          && handler->server_error->error->apr_err == APR_SUCCESS) -        { -          svn_error_clear(handler->server_error->error); - -          /* Stop parsing for a server error.  */ -          handler->server_error = NULL; - -          /* If anything arrives after this, then just discard it.  */ -          handler->discard_body = TRUE; -        } - -      *serf_status = APR_EOF; -      return SVN_NO_ERROR; +      return svn_error_trace( +                svn_ra_serf__handle_server_error(handler->server_error, +                                                 handler, +                                                 request, response, +                                                 serf_status, +                                                 scratch_pool));      }    /* Pass the body along to the registered response handler.  */ @@ -2271,12 +1444,13 @@ static apr_status_t  handle_response_cb(serf_request_t *request,                     serf_bucket_t *response,                     void *baton, -                   apr_pool_t *scratch_pool) +                   apr_pool_t *response_pool)  {    svn_ra_serf__handler_t *handler = baton;    svn_error_t *err;    apr_status_t inner_status;    apr_status_t outer_status; +  apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */    err = svn_error_trace(handle_response(request, response,                                          handler, &inner_status, @@ -2287,9 +1461,34 @@ handle_response_cb(serf_request_t *request,    if (!outer_status)      outer_status = inner_status; -  /* Make sure the DONE flag is set properly.  */ +  /* Make sure the DONE flag is set properly and requests are cleaned up. */    if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status)) -    handler->done = TRUE; +    { +      svn_ra_serf__session_t *sess = handler->session; +      handler->done = TRUE; +      handler->scheduled = FALSE; +      outer_status = APR_EOF; + +      /* We use a cached handler->session here to allow handler to free the +         memory containing the handler */ +      save_error(sess, +                 handler->done_delegate(request, handler->done_delegate_baton, +                                        scratch_pool)); +    } +  else if (SERF_BUCKET_READ_ERROR(outer_status) +           && handler->session->pending_error) +    { +      handler->discard_body = TRUE; /* Discard further data */ +      handler->done = TRUE; /* Mark as done */ +      /* handler->scheduled is still TRUE, as we still expect data. +         If we would return an error outer-status the connection +         would have to be restarted. With scheduled still TRUE +         destroying the handler's pool will still reset the +         connection, avoiding the posibility of returning +         an error for this handler when a new request is +         scheduled. */ +      outer_status = APR_EAGAIN; /* Exit context loop */ +    }    return outer_status;  } @@ -2312,9 +1511,8 @@ setup_request(serf_request_t *request,      {        serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request); -      /* ### should pass the scratch_pool  */        SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton, -                                     bkt_alloc, request_pool)); +                                     bkt_alloc, request_pool, scratch_pool));      }    else      { @@ -2338,17 +1536,17 @@ setup_request(serf_request_t *request,    SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,                           handler->session, handler->method, handler->path,                           body_bkt, handler->body_type, accept_encoding, -                         request_pool, scratch_pool)); +                         !handler->no_dav_headers, request_pool, +                         scratch_pool));    if (handler->header_delegate)      { -      /* ### should pass the scratch_pool  */        SVN_ERR(handler->header_delegate(headers_bkt,                                         handler->header_delegate_baton, -                                       request_pool)); +                                       request_pool, scratch_pool));      } -  return APR_SUCCESS; +  return SVN_NO_ERROR;  }  /* Implements the serf_request_setup_t interface (which sets up both a @@ -2362,51 +1560,58 @@ setup_request_cb(serf_request_t *request,                void **acceptor_baton,                serf_response_handler_t *s_handler,                void **s_handler_baton, -              apr_pool_t *pool) +              apr_pool_t *request_pool)  {    svn_ra_serf__handler_t *handler = setup_baton; +  apr_pool_t *scratch_pool;    svn_error_t *err; -  /* ### construct a scratch_pool? serf gives us a pool that will live for -     ### the duration of the request.  */ -  apr_pool_t *scratch_pool = pool; +  /* Construct a scratch_pool? serf gives us a pool that will live for +     the duration of the request. But requests are retried in some cases */ +  scratch_pool = svn_pool_create(request_pool);    if (strcmp(handler->method, "HEAD") == 0)      *acceptor = accept_head;    else      *acceptor = accept_response; -  *acceptor_baton = handler->session; +  *acceptor_baton = handler;    *s_handler = handle_response_cb;    *s_handler_baton = handler;    err = svn_error_trace(setup_request(request, handler, req_bkt, -                                      pool /* request_pool */, scratch_pool)); +                                      request_pool, scratch_pool)); +  svn_pool_destroy(scratch_pool);    return save_error(handler->session, err);  }  void  svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)  { -  SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL); +  SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL +                           && !handler->scheduled); -  /* 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.  */ +  /* In case HANDLER is re-queued, reset the various transient fields. */    handler->done = FALSE;    handler->server_error = NULL;    handler->sline.version = 0;    handler->location = NULL;    handler->reading_body = FALSE;    handler->discard_body = FALSE; +  handler->scheduled = TRUE; + +  /* Keeping track of the returned request object would be nice, but doesn't +     work the way we would expect in ra_serf.. -  /* ### do we ever alter the >response_handler?  */ +     Serf sometimes creates a new request for us (and destroys the old one) +     without telling, like when authentication failed (401/407 response. -  /* ### do we need to hold onto the returned request object, or just -     ### not worry about it (the serf ctx will manage it).  */ +     We 'just' trust serf to do the right thing and expect it to tell us +     when the state of the request changes. + +     ### I fixed a request leak in serf in r2258 on auth failures. +   */    (void) serf_connection_request_create(handler->conn->conn,                                          setup_request_cb, handler);  } @@ -2415,8 +1620,7 @@ svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)  svn_error_t *  svn_ra_serf__discover_vcc(const char **vcc_url,                            svn_ra_serf__session_t *session, -                          svn_ra_serf__connection_t *conn, -                          apr_pool_t *pool) +                          apr_pool_t *scratch_pool)  {    const char *path;    const char *relative_path; @@ -2429,12 +1633,6 @@ svn_ra_serf__discover_vcc(const char **vcc_url,        return SVN_NO_ERROR;      } -  /* If no connection is provided, use the default one. */ -  if (! conn) -    { -      conn = session->conns[0]; -    } -    path = session->session_url.path;    *vcc_url = NULL;    uuid = NULL; @@ -2444,9 +1642,10 @@ svn_ra_serf__discover_vcc(const char **vcc_url,        apr_hash_t *props;        svn_error_t *err; -      err = svn_ra_serf__fetch_node_props(&props, conn, +      err = svn_ra_serf__fetch_node_props(&props, session,                                            path, SVN_INVALID_REVNUM, -                                          base_props, pool, pool); +                                          base_props, +                                          scratch_pool, scratch_pool);        if (! err)          {            apr_hash_t *ns_props; @@ -2474,12 +1673,7 @@ svn_ra_serf__discover_vcc(const char **vcc_url,                svn_error_clear(err);                /* Okay, strip off a component from PATH. */ -              path = svn_urlpath__dirname(path, pool); - -              /* An error occurred on conns. serf 0.4.0 remembers that -                 the connection had a problem. We need to reset it, in -                 order to use it again.  */ -              serf_connection_reset(conn->conn); +              path = svn_urlpath__dirname(path, scratch_pool);              }          }      } @@ -2505,7 +1699,7 @@ svn_ra_serf__discover_vcc(const char **vcc_url,      {        svn_stringbuf_t *url_buf; -      url_buf = svn_stringbuf_create(path, pool); +      url_buf = svn_stringbuf_create(path, scratch_pool);        svn_path_remove_components(url_buf,                                   svn_path_component_count(relative_path)); @@ -2533,7 +1727,6 @@ svn_error_t *  svn_ra_serf__get_relative_path(const char **rel_path,                                 const char *orig_path,                                 svn_ra_serf__session_t *session, -                               svn_ra_serf__connection_t *conn,                                 apr_pool_t *pool)  {    const char *decoded_root, *decoded_orig; @@ -2550,7 +1743,6 @@ svn_ra_serf__get_relative_path(const char **rel_path,           promises to populate the session's root-url cache, and that's           what we really want. */        SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, -                                        conn ? conn : session->conns[0],                                          pool));      } @@ -2564,7 +1756,6 @@ svn_ra_serf__get_relative_path(const char **rel_path,  svn_error_t *  svn_ra_serf__report_resource(const char **report_target,                               svn_ra_serf__session_t *session, -                             svn_ra_serf__connection_t *conn,                               apr_pool_t *pool)  {    /* If we have HTTP v2 support, we want to report against the 'me' @@ -2574,7 +1765,7 @@ svn_ra_serf__report_resource(const char **report_target,    /* Otherwise, we'll use the default VCC. */    else -    SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool)); +    SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, pool));    return SVN_NO_ERROR;  } @@ -2588,13 +1779,14 @@ svn_ra_serf__error_on_status(serf_status_line sline,      {        case 301:        case 302: +      case 303:        case 307: +      case 308:          return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,                                   (sline.code == 301) -                                 ? _("Repository moved permanently to '%s';" -                                     " please relocate") -                                 : _("Repository moved temporarily to '%s';" -                                     " please relocate"), location); +                                  ? _("Repository moved permanently to '%s'") +                                  : _("Repository moved temporarily to '%s'"), +                                 location);        case 403:          return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,                                   _("Access to '%s' forbidden"), path); @@ -2602,6 +1794,16 @@ svn_ra_serf__error_on_status(serf_status_line sline,        case 404:          return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,                                   _("'%s' path not found"), path); +      case 405: +        return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL, +                                 _("HTTP method is not allowed on '%s'"), +                                 path); +      case 409: +        return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, +                                 _("'%s' conflicts"), path); +      case 412: +        return svn_error_createf(SVN_ERR_RA_DAV_PRECONDITION_FAILED, NULL, +                                 _("Precondition on '%s' failed"), path);        case 423:          return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,                                   _("'%s': no lock token available"), path); @@ -2612,21 +1814,59 @@ svn_ra_serf__error_on_status(serf_status_line sline,                        "server or an intermediate proxy does not accept "                        "chunked encoding. Try setting 'http-chunked-requests' "                        "to 'auto' or 'no' in your client configuration.")); +      case 500: +        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, +                                 _("Unexpected server error %d '%s' on '%s'"), +                                 sline.code, sline.reason, path);        case 501:          return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,                                   _("The requested feature is not supported by "                                     "'%s'"), path);      } -  if (sline.code >= 300) +  if (sline.code >= 300 || sline.code <= 199)      return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, -                             _("Unexpected HTTP status %d '%s' on '%s'\n"), +                             _("Unexpected HTTP status %d '%s' on '%s'"),                               sline.code, sline.reason, path);    return SVN_NO_ERROR;  }  svn_error_t * +svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler) +{ +  /* Is it a standard error status? */ +  if (handler->sline.code != 405) +    SVN_ERR(svn_ra_serf__error_on_status(handler->sline, +                                         handler->path, +                                         handler->location)); + +  switch (handler->sline.code) +    { +      case 201: +        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, +                                 _("Path '%s' unexpectedly created"), +                                 handler->path); +      case 204: +        return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, +                                 _("Path '%s' already exists"), +                                 handler->path); + +      case 405: +        return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL, +                                 _("The HTTP method '%s' is not allowed" +                                   " on '%s'"), +                                 handler->method, handler->path); +      default: +        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, +                                 _("Unexpected HTTP status %d '%s' on '%s' " +                                   "request to '%s'"), +                                 handler->sline.code, handler->sline.reason, +                                 handler->method, handler->path); +    } +} + +svn_error_t *  svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,                                      svn_delta_shim_callbacks_t *callbacks)  { @@ -2636,185 +1876,76 @@ svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,    return SVN_NO_ERROR;  } - -/* Conforms to Expat's XML_StartElementHandler  */ -static void -expat_start(void *userData, const char *raw_name, const char **attrs) -{ -  struct expat_ctx_t *ectx = userData; - -  if (ectx->inner_error != NULL) -    return; - -  ectx->inner_error = svn_error_trace( -                        svn_ra_serf__xml_cb_start(ectx->xmlctx, -                                                  raw_name, attrs)); - -#ifdef EXPAT_HAS_STOPPARSER -  if (ectx->inner_error) -    (void) XML_StopParser(ectx->parser, 0 /* resumable */); -#endif -} - - -/* Conforms to Expat's XML_EndElementHandler  */ -static void -expat_end(void *userData, const char *raw_name) -{ -  struct expat_ctx_t *ectx = userData; - -  if (ectx->inner_error != NULL) -    return; - -  ectx->inner_error = svn_error_trace( -                        svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name)); - -#ifdef EXPAT_HAS_STOPPARSER -  if (ectx->inner_error) -    (void) XML_StopParser(ectx->parser, 0 /* resumable */); -#endif -} - - -/* Conforms to Expat's XML_CharacterDataHandler  */ -static void -expat_cdata(void *userData, const char *data, int len) +/* Shared/standard done_delegate handler */ +static svn_error_t * +response_done(serf_request_t *request, +              void *handler_baton, +              apr_pool_t *scratch_pool)  { -  struct expat_ctx_t *ectx = userData; - -  if (ectx->inner_error != NULL) -    return; +  svn_ra_serf__handler_t *handler = handler_baton; -  ectx->inner_error = svn_error_trace( -                        svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len)); - -#ifdef EXPAT_HAS_STOPPARSER -  if (ectx->inner_error) -    (void) XML_StopParser(ectx->parser, 0 /* resumable */); -#endif -} +  assert(handler->done); +  if (handler->no_fail_on_http_failure_status) +    return SVN_NO_ERROR; -/* Implements svn_ra_serf__response_handler_t */ -static svn_error_t * -expat_response_handler(serf_request_t *request, -                       serf_bucket_t *response, -                       void *baton, -                       apr_pool_t *scratch_pool) -{ -  struct expat_ctx_t *ectx = baton; +  if (handler->server_error) +    return svn_ra_serf__server_error_create(handler, scratch_pool); -  if (!ectx->parser) +  if (handler->sline.code >= 400 || handler->sline.code <= 199)      { -      ectx->parser = XML_ParserCreate(NULL); -      apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser, -                                xml_parser_cleanup, apr_pool_cleanup_null); -      XML_SetUserData(ectx->parser, ectx); -      XML_SetElementHandler(ectx->parser, expat_start, expat_end); -      XML_SetCharacterDataHandler(ectx->parser, expat_cdata); +      return svn_error_trace(svn_ra_serf__unexpected_status(handler));      } -  /* ### TODO: sline.code < 200 should really be handled by the core */ -  if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300)) +  if ((handler->sline.code >= 300 && handler->sline.code < 399) +      && !handler->no_fail_on_http_redirect_status)      { -      /* By deferring to expect_empty_body(), it will make a choice on -         how to handle the body. Whatever the decision, the core handler -         will take over, and we will not be called again.  */ -      return svn_error_trace(svn_ra_serf__expect_empty_body( -                               request, response, ectx->handler, -                               scratch_pool)); +      return svn_error_trace(svn_ra_serf__unexpected_status(handler));      } -  while (1) -    { -      apr_status_t status; -      const char *data; -      apr_size_t len; -      int expat_status; - -      status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len); -      if (SERF_BUCKET_READ_ERROR(status)) -        return svn_ra_serf__wrap_err(status, NULL); - -#if 0 -      /* ### move restart/skip into the core handler  */ -      ectx->handler->read_size += len; -#endif - -      /* ### move PAUSED behavior to a new response handler that can feed -         ### an inner handler, or can pause for a while.  */ - -      /* ### should we have an IGNORE_ERRORS flag like the v1 parser?  */ - -      expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */); - -      /* We need to check INNER_ERROR first. This is an error from the -         callbacks that has been "dropped off" for us to retrieve. On -         current Expat parsers, we stop the parser when an error occurs, -         so we want to ignore EXPAT_STATUS (which reports the stoppage). - -         If an error is not present, THEN we go ahead and look for parsing -         errors.  */ -      if (ectx->inner_error) -        { -          apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser, -                               xml_parser_cleanup); -          return svn_error_trace(ectx->inner_error); -        } -      if (expat_status == XML_STATUS_ERROR) -        return svn_error_createf(SVN_ERR_XML_MALFORMED, -                                 ectx->inner_error, -                                 _("The %s response contains invalid XML" -                                   " (%d %s)"), -                                 ectx->handler->method, -                                 ectx->handler->sline.code, -                                 ectx->handler->sline.reason); - -      /* The parsing went fine. What has the bucket told us?  */ - -      if (APR_STATUS_IS_EOF(status)) -        { -          /* Tell expat we've reached the end of the content. Ignore the -             return status. We just don't care.  */ -          (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */); +  return SVN_NO_ERROR; +} -          svn_ra_serf__xml_context_destroy(ectx->xmlctx); -          apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser, -                               xml_parser_cleanup); +/* Pool cleanup handler for request handlers. -          /* ### should check XMLCTX to see if it has returned to the -             ### INITIAL state. we may have ended early...  */ -        } +   If a serf context run stops for some outside error, like when the user +   cancels a request via ^C in the context loop, the handler is still +   registered in the serf context. With the pool cleanup there would be +   handlers registered in no freed memory. -      if (status && !SERF_BUCKET_READ_ERROR(status)) -        { -          return svn_ra_serf__wrap_err(status, NULL); -        } +   This fallback kills the connection for this case, which will make serf +   unregister any outstanding requests on it. */ +static apr_status_t +handler_cleanup(void *baton) +{ +  svn_ra_serf__handler_t *handler = baton; +  if (handler->scheduled) +    { +      svn_ra_serf__unschedule_handler(handler);      } -  /* NOTREACHED */ +  return APR_SUCCESS;  } -  svn_ra_serf__handler_t * -svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx, -                                  apr_pool_t *result_pool) +svn_ra_serf__create_handler(svn_ra_serf__session_t *session, +                            apr_pool_t *result_pool)  {    svn_ra_serf__handler_t *handler; -  struct expat_ctx_t *ectx; - -  ectx = apr_pcalloc(result_pool, sizeof(*ectx)); -  ectx->xmlctx = xmlctx; -  ectx->parser = NULL; -  ectx->cleanup_pool = result_pool; -    handler = apr_pcalloc(result_pool, sizeof(*handler));    handler->handler_pool = result_pool; -  handler->response_handler = expat_response_handler; -  handler->response_baton = ectx; -  ectx->handler = handler; +  apr_pool_cleanup_register(result_pool, handler, handler_cleanup, +                            apr_pool_cleanup_null); + +  handler->session = session; +  handler->conn = session->conns[0]; + +  /* Setup the default done handler, to handle server errors */ +  handler->done_delegate_baton = handler; +  handler->done_delegate = response_done;    return handler;  } +  | 
