/*
 * util.c :  utility functions for the RA/DAV library
 *
 * ====================================================================
 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://subversion.tigris.org/license-1.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 *
 * This software consists of voluntary contributions made by many
 * individuals.  For exact contribution history, see the revision
 * history and logs, available at http://subversion.tigris.org/.
 * ====================================================================
 */

#include <apr_pools.h>

#define APR_WANT_STRFUNC
#include <apr_want.h>

#include <ne_socket.h>
#include <ne_uri.h>
#include <ne_compress.h>

#include "svn_pools.h"
#include "svn_path.h"
#include "svn_string.h"
#include "svn_utf.h"
#include "svn_xml.h"

#include "svn_private_config.h"

#include "ra_dav.h"

#define WOOTWOOT 1



typedef struct {
  apr_pool_t *pool;                          /* pool on which this is alloc-d */
  void *original_userdata;                   /* userdata for callbacks */
  const svn_ra_dav__xml_elm_t *elements;     /* old-style elements table */
  svn_ra_dav__xml_validate_cb *validate_cb;  /* old-style validate callback */
  svn_ra_dav__xml_startelm_cb *startelm_cb;  /* old-style startelm callback */
  svn_ra_dav__xml_endelm_cb *endelm_cb;      /* old-style endelm callback */
  svn_stringbuf_t *cdata_accum;              /* stringbuffer for CDATA */
} neon_shim_baton_t;


const svn_ra_dav__xml_elm_t *
svn_ra_dav__lookup_xml_elem(const svn_ra_dav__xml_elm_t *table,
                            const char *nspace,
                            const char *name)
{
  /* placeholder for `unknown' element if it's present */
  const svn_ra_dav__xml_elm_t *elem_unknown = NULL;
  const svn_ra_dav__xml_elm_t *elem;

  for(elem = table; elem->nspace; ++elem)
    {
      if (strcmp(elem->nspace, nspace) == 0
          && strcmp(elem->name, name) == 0)
        return elem;

      /* Use a single loop to save CPU cycles.
       *
       * Maybe this element is defined as `unknown'? */
      if (elem->id == ELEM_unknown)
        elem_unknown = elem;
    }

  /* ELEM_unknown position in the table or NULL */
  return elem_unknown;
}

/** Fill in temporary structure for ELEM_unknown element.
 *
 * Call only for element ELEM_unknown!  For Neon 0.23 API
 * compatibility, we need to fill the XML element structure with real
 * namespace and element name, as "old-style" handler used to get that
 * from Neon parser. This is a hack, so don't expect it to be elegant.
 * The @a elem_pointer is a reference to element pointer which is
 * returned by svn_ra_dav__lookup_xml_elem, and supposedly points at
 * en entry in the XML elements table supplied by an "old-style"
 * handler. @a elem_unknown_temporary is a reference to XML element
 * structure allocated on the stack. There's no reason to allocate it
 * anywhere else because it's going to use @a nspace and @a name which
 * are passed into the "new-style" handler by the Neon parser, so the
 * structure pointed at by @a elem_unknown_temporary must die when the
 * calling function completes. This function is designed to be called
 * from "new-style" startelm and endelm callbacks. */
static void
handle_unknown(const svn_ra_dav__xml_elm_t **elem_pointer,
               svn_ra_dav__xml_elm_t *elem_unknown_temporary,
               const char *nspace, const char *name)
{
  elem_unknown_temporary->nspace = nspace;
  elem_unknown_temporary->name = name;
  elem_unknown_temporary->id = (*elem_pointer)->id;
  elem_unknown_temporary->flags = (*elem_pointer)->flags;

  /* The pointer will use temporary record instead of a table record */
  *elem_pointer = elem_unknown_temporary;
}

/** (Neon 0.24) Start element parsing.
 *
 * Calls "old-style" API callbacks validate_cb and startelm_cb to emulate
 * Neon 0.23 parser. @a userdata is a @c neon_shim_baton_t instance.
 * ---- ne_xml.h ----
 * The startelm callback may return:
 *   <0 =>  abort the parse (NE_XML_ABORT)
 *    0 =>  decline this element  (NE_XML_DECLINE)
 *   >0 =>  accept this element; value is state for this element.
 * The 'parent' integer is the state returned by the handler of the
 * parent element. */
static int
shim_startelm(void *userdata, int parent_state, const char *nspace,
              const char *name, const char **attrs)
{
  neon_shim_baton_t *baton = userdata;
  svn_ra_dav__xml_elm_t elem_unknown_temporary;
  const svn_ra_dav__xml_elm_t *elem =
    svn_ra_dav__lookup_xml_elem(baton->elements, nspace, name);
  int rc;

  if (!elem)
    return NE_XML_DECLINE; /* Let Neon handle this */

  /* TODO: explore an option of keeping element pointer in the baton
   * to cut one loop in endelm */

  /* 'parent' here actually means a parent element's id as opposed
   * to 'parent' parameter passed to the startelm() function */
  rc = baton->validate_cb(baton->original_userdata, parent_state, elem->id);
  if (rc != SVN_RA_DAV__XML_VALID) {
    return (rc == SVN_RA_DAV__XML_DECLINE) ? NE_XML_DECLINE : NE_XML_ABORT;
  }

  if (elem->id == ELEM_unknown)
    handle_unknown(&elem, &elem_unknown_temporary, nspace, name);

  rc = baton->startelm_cb(baton->original_userdata, elem, attrs);
  if (rc != SVN_RA_DAV__XML_VALID) {
    return (rc == SVN_RA_DAV__XML_DECLINE) ? NE_XML_DECLINE : NE_XML_ABORT;
  }

  if (baton->cdata_accum != NULL)
    svn_stringbuf_setempty(baton->cdata_accum);
  else
    baton->cdata_accum = svn_stringbuf_create("", baton->pool);

  /* @a parent in the pre-Neon 0.24 interface was a parent's element
   * id but now it's the status returned by parent's startelm(), so we need to
   * bridge this by returning this element's id as a status.
   * We also need to ensure that element ids start with 1, because
   * zero is `decline'. See ra_dav.h definition of ELEM_* values.
   */
  return elem->id;
}

/** (Neon 0.24) Collect element's contents.
 *
 * Collects element's contents into @a userdata string buffer. @a userdata is a
 * @c neon_shim_baton_t instance.
 * May return non-zero to abort the parse. */
static int shim_cdata(void *userdata, int state, const char *cdata, size_t len)
{
  const neon_shim_baton_t *baton = userdata;

  svn_stringbuf_appendbytes(baton->cdata_accum, cdata, len);
  return 0; /* no error */
}

/** (Neon 0.24) Finish parsing element.
 *
 * Calls "old-style" endelm_cb callback. @a userdata is a @c neon_shim_baton_t
 * instance.
 * May return non-zero to abort the parse. */
static int shim_endelm(void *userdata, int state, const char *nspace,
                       const char *name)
{
  const neon_shim_baton_t *baton = userdata;
  svn_ra_dav__xml_elm_t elem_unknown_temporary;
  const svn_ra_dav__xml_elm_t *elem =
    svn_ra_dav__lookup_xml_elem(baton->elements, nspace, name);
  int rc;

  if (!elem)
    return -1; /* shouldn't be here if startelm didn't abort the parse */

  if (elem->id == ELEM_unknown)
    handle_unknown(&elem, &elem_unknown_temporary, nspace, name);

  rc = baton->endelm_cb(baton->original_userdata,
                        elem,
                        baton->cdata_accum->data);
  if (rc != SVN_RA_DAV__XML_VALID)
    return -1; /* abort the parse */

  return 0; /* no error */
}

/** Push an XML handler onto Neon's handler stack.
 *
 * Parser @a p uses a stack of handlers to process XML. The handler is
 * composed of validation callback @a validate_cb, start-element
 * callback @a startelm_cb, and end-element callback @a endelm_cb, which
 * collectively handle elements supplied in an array @a elements. Parser
 * passes given user baton @a userdata to all callbacks.
 * This is a new function on top of ne_xml_push_handler, adds memory pool
 * @a pool as the last parameter. This parameter is not used with Neon
 * 0.23.9, but will be with Neon 0.24. When Neon 0.24 is used, ra_dav
 * receives calls from the new interface and performs functions described
 * above by itself, using @a elements and calling callbacks according to
 * 0.23 interface.
 */
static void shim_xml_push_handler(ne_xml_parser *p,
                                  const svn_ra_dav__xml_elm_t *elements,
                                  svn_ra_dav__xml_validate_cb validate_cb,
                                  svn_ra_dav__xml_startelm_cb startelm_cb,
                                  svn_ra_dav__xml_endelm_cb endelm_cb, 
                                  void *userdata,
                                  apr_pool_t *pool)
{
  neon_shim_baton_t *baton = apr_pcalloc(pool, sizeof(neon_shim_baton_t));
  baton->pool = pool;
  baton->original_userdata = userdata;
  baton->elements = elements;
  baton->validate_cb = validate_cb;
  baton->startelm_cb = startelm_cb;
  baton->endelm_cb = endelm_cb;
  baton->cdata_accum = NULL; /* don't create until startelm is called */

  ne_xml_push_handler(p, shim_startelm, shim_cdata, shim_endelm, baton);
}




void svn_ra_dav__copy_href(svn_stringbuf_t *dst, const char *src)
{
  ne_uri parsed_url;

  /* parse the PATH element out of the URL and store it.

     ### do we want to verify the rest matches the current session?

     Note: mod_dav does not (currently) use an absolute URL, but simply a
     server-relative path (i.e. this uri_parse is effectively a no-op).
  */
  (void) ne_uri_parse(src, &parsed_url);
  svn_stringbuf_set(dst, parsed_url.path);
  ne_uri_free(&parsed_url);
}

svn_error_t *svn_ra_dav__convert_error(ne_session *sess,
                                       const char *context,
                                       int retcode,
                                       apr_pool_t *pool)
{
  int errcode = SVN_ERR_RA_DAV_REQUEST_FAILED;
  const char *msg;
  const char *hostport;

  /* Convert the return codes. */
  switch (retcode) 
    {
    case NE_AUTH:
      errcode = SVN_ERR_RA_NOT_AUTHORIZED;
      msg = _("authorization failed");
      break;
      
    case NE_CONNECT:
      msg = _("could not connect to server");
      break;

    case NE_TIMEOUT:
      msg = _("timed out waiting for server");
      break;

    default:
      /* Get the error string from neon and convert to UTF-8. */
      SVN_ERR (svn_utf_cstring_to_utf8 (&msg, ne_get_error (sess), pool));
      break;
    }

  /* The hostname may contain non-ASCII characters, so convert it to UTF-8. */
  SVN_ERR (svn_utf_cstring_to_utf8 (&hostport, ne_get_server_hostport(sess),
                                    pool));

  return svn_error_createf (errcode, NULL, "%s: %s (%s://%s)", 
                            context, msg, ne_get_scheme(sess), 
                            hostport);
}


/** Error parsing **/


/* Custom function of type ne_accept_response. */
static int ra_dav_error_accepter(void *userdata,
                                 ne_request *req,
                                 const ne_status *st)
{
  /* Only accept the body-response if the HTTP status code is *not* 2XX. */
  return (st->klass != 2);
}


static const svn_ra_dav__xml_elm_t error_elements[] =
{
  { "DAV:", "error", ELEM_error, 0 },
  { "svn:", "error", ELEM_svn_error, 0 },
  { "http://apache.org/dav/xmlns", "human-readable", 
    ELEM_human_readable, SVN_RA_DAV__XML_CDATA },

  /* ### our validator doesn't yet recognize the rich, specific
         <D:some-condition-failed/> objects as defined by DeltaV.*/

  { NULL }
};


static int validate_error_elements(void *userdata,
                                   svn_ra_dav__xml_elmid parent,
                                   svn_ra_dav__xml_elmid child)
{
  switch (parent)
    {
    case ELEM_root:
      if (child == ELEM_error)
        return SVN_RA_DAV__XML_VALID;
      else
        return SVN_RA_DAV__XML_INVALID;

    case ELEM_error:
      if (child == ELEM_svn_error
          || child == ELEM_human_readable)
        return SVN_RA_DAV__XML_VALID;
      else
        return SVN_RA_DAV__XML_DECLINE;  /* ignore if something else
                                            was in there */

    default:
      return SVN_RA_DAV__XML_DECLINE;
    }

  /* NOTREACHED */
}


static int start_err_element(void *userdata, const svn_ra_dav__xml_elm_t *elm,
                             const char **atts)
{
  svn_error_t **err = userdata;

  switch (elm->id)
    {
    case ELEM_svn_error:
      {
        /* allocate the svn_error_t.  Hopefully the value will be
           overwritten by the <human-readable> tag, or even someday by
           a <D:failed-precondition/> tag. */
        *err = svn_error_create(APR_EGENERAL, NULL,
                                "General svn error from server");
        break;
      }
    case ELEM_human_readable:
      {
        /* get the errorcode attribute if present */
        const char *errcode_str = 
          svn_xml_get_attr_value("errcode", /* ### make constant in
                                               some mod_dav header? */
                                 atts);

        if (errcode_str && *err) 
          (*err)->apr_err = atoi(errcode_str);

        break;
      }

    default:
      break;
    }

  return SVN_RA_DAV__XML_VALID;
}

static int end_err_element(void *userdata, const svn_ra_dav__xml_elm_t *elm,
                           const char *cdata)
{
  svn_error_t **err = userdata;

  switch (elm->id)
    {
    case ELEM_human_readable:
      {
        if (cdata && *err)
          {
            /* 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. */
            apr_size_t len;
            if (*cdata == '\n')
              ++cdata;
            len = strlen(cdata);
            if (len > 0 && cdata[len-1] == '\n')
              --len;

            (*err)->message = apr_pstrmemdup((*err)->pool, cdata, len);
          }
        break;
      }

    default:
      break;
    }

  return SVN_RA_DAV__XML_VALID;
}


/* A body provider for ne_set_request_body_provider that pulls data
 * from an APR file. See ne_request.h for a description of the
 * interface.
 */
static ssize_t ra_dav_body_provider (void *userdata,
                                     char *buffer,
                                     size_t buflen)
{
  apr_file_t *body_file = userdata;
  apr_status_t status;

  if (buflen == 0)
    {
      /* This is the beginning of a new body pull. Rewind the file. */
      apr_off_t offset = 0;
      status = apr_file_seek(body_file, APR_SET, &offset);
      return (status ? -1 : 0);
    }
  else
    {
      apr_size_t nbytes = buflen;
      status = apr_file_read(body_file, buffer, &nbytes);
      if (status)
        return (APR_STATUS_IS_EOF(status) ? 0 : -1);
      else
        return nbytes;
    }
}


svn_error_t *svn_ra_dav__set_neon_body_provider(ne_request *req,
                                                apr_file_t *body_file)
{
  apr_status_t status;
  apr_finfo_t finfo;

  /* ### APR bug? apr_file_info_get won't always return the correct
         size for buffered files. */
  status = apr_file_info_get(&finfo, APR_FINFO_SIZE, body_file);
  if (status)
    return svn_error_wrap_apr(status,
                              _("Can't calculate the request body size"));

  ne_set_request_body_provider(req, (size_t) finfo.size,
                               ra_dav_body_provider, body_file);
  return SVN_NO_ERROR;
}


typedef struct spool_reader_baton_t
{
  const char *spool_file_name;
  apr_file_t *spool_file;
  apr_pool_t *pool;
  svn_error_t *error;

} spool_reader_baton_t;


static void 
spool_reader(void *userdata, 
             const char *buf, 
             size_t len)
{
  spool_reader_baton_t *baton = userdata;
  if (! baton->error)
    baton->error = svn_io_file_write_full(baton->spool_file, buf, 
                                          len, NULL, baton->pool);
}


static svn_error_t *
parse_spool_file(const char *spool_file_name,
                 ne_xml_parser *success_parser,
                 apr_pool_t *pool)
{
  apr_file_t *spool_file;
  svn_stream_t *spool_stream;
  char *buf = apr_palloc(pool, SVN_STREAM_CHUNK_SIZE);
  apr_size_t len;
  
  SVN_ERR( svn_io_file_open(&spool_file, spool_file_name,
                            (APR_READ | APR_BUFFERED), APR_OS_DEFAULT, pool));
  spool_stream = svn_stream_from_aprfile(spool_file, pool);
  while (1)
    {
      len = SVN_STREAM_CHUNK_SIZE;
      SVN_ERR (svn_stream_read (spool_stream, buf, &len));
      if (len > 0)
        ne_xml_parse(success_parser, buf, len);
      if (len != SVN_STREAM_CHUNK_SIZE)
        break;
    }
  return SVN_NO_ERROR;
}



/* See doc string for svn_ra_dav__parsed_request.  The only new
   parameter here is use_neon_shim, which if true, means that
   VALIDATE_CB, STARTELM_CB, and ENDELM_CB are expecting the old,
   pre-0.24 Neon api, so use a shim layer to translate for them. */
static svn_error_t *
parsed_request(ne_session *sess,
               const char *method,
               const char *url,
               const char *body,
               apr_file_t *body_file,
               void set_parser (ne_xml_parser *parser,
                                void *baton),
               const svn_ra_dav__xml_elm_t *elements,
               svn_boolean_t use_neon_shim,
               /* These three are defined iff use_neon_shim is defined. */
               svn_ra_dav__xml_validate_cb validate_compat_cb,
               svn_ra_dav__xml_startelm_cb startelm_compat_cb, 
               svn_ra_dav__xml_endelm_cb endelm_compat_cb,
               /* These three are defined iff use_neon_shim is NOT defined. */
               ne_xml_startelm_cb *startelm_cb,
               ne_xml_cdata_cb *cdata_cb,
               ne_xml_endelm_cb *endelm_cb,
               void *baton,
               apr_hash_t *extra_headers,
               int *status_code,
               svn_boolean_t spool_response,
               apr_pool_t *pool)
{
  ne_request *req = NULL;
  ne_decompress *decompress_main = NULL;
  ne_decompress *decompress_err = NULL;
  ne_xml_parser *success_parser = NULL;
  ne_xml_parser *error_parser = NULL;
  int rv;
  int decompress_rv;
  int code;
  int expected_code;
  const char *msg;
  spool_reader_baton_t spool_reader_baton;
  svn_error_t *err = SVN_NO_ERROR;
  svn_ra_dav__session_t *ras = ne_get_session_private(sess, 
                                                      SVN_RA_NE_SESSION_ID);

  /* create/prep the request */
  req = ne_request_create(sess, method, url);

  if (body != NULL)
    ne_set_request_body_buffer(req, body, strlen(body));
  else if ((err = svn_ra_dav__set_neon_body_provider(req, body_file)))
    goto cleanup;

  /* ### use a symbolic name somewhere for this MIME type? */
  ne_add_request_header(req, "Content-Type", "text/xml");

  /* add any extra headers passed in by caller. */
  if (extra_headers != NULL)
    {
      apr_hash_index_t *hi;
      for (hi = apr_hash_first (pool, extra_headers);
           hi; hi = apr_hash_next (hi))
        {
          const void *key;
          void *val;
          apr_hash_this (hi, &key, NULL, &val);
          ne_add_request_header(req, (const char *) key, (const char *) val); 
        }
    }

  /* create a parser to read the normal response body */
  success_parser = ne_xml_create();

  if (use_neon_shim)
    {
      shim_xml_push_handler(success_parser, elements,
                            validate_compat_cb, startelm_compat_cb,
                            endelm_compat_cb, baton, pool);
    }
  else
    {
      ne_xml_push_handler(success_parser, startelm_cb, cdata_cb,
                          endelm_cb, baton);
    }

  /* ### HACK: Set the parser's error to the empty string.  Someday we
     hope neon will let us have an easy way to tell the difference
     between XML parsing errors, and errors that occur while handling
     the XML tags that we get.  Until then, trust that whenever neon
     has an error somewhere below the API, it sets its own error to
     something non-empty (the API promises non-NULL, at least). */
  ne_xml_set_error(success_parser, "");

  /* if our caller is interested in having access to this parser, call
     the SET_PARSER callback with BATON. */
  if (set_parser != NULL)
    set_parser(success_parser, baton);

  /* create a parser to read the <D:error> response body */
  error_parser = ne_xml_create();

  /* ### The error callbacks are local to this file and are still
     ### using the Neon <= 0.23 API.  They need to be upgraded.  In
     ### the meantime, we ignore the value of use_neon_shim here. */
  shim_xml_push_handler(error_parser, error_elements,
                        validate_error_elements, start_err_element,
                        end_err_element, &err, pool);

  /* Register the "main" accepter and body-reader with the request --
     the one to use when the HTTP status is 2XX.  If we are spooling
     the response to disk first, we use our custom spool reader.  */
  if (spool_response)
    {
      const char *tmpfile_path;
      err = svn_io_temp_dir(&tmpfile_path, pool);
      if (err)
        goto cleanup;

      tmpfile_path = svn_path_join(tmpfile_path, "dav-spool", pool);
      SVN_ERR (svn_io_open_unique_file (&spool_reader_baton.spool_file, 
                                        &spool_reader_baton.spool_file_name,
                                        tmpfile_path, "", FALSE, pool));
      spool_reader_baton.pool = pool;
      spool_reader_baton.error = SVN_NO_ERROR;
      if (ras->compression)
        decompress_main = ne_decompress_reader(req, ne_accept_2xx,
                                               spool_reader, 
                                               &spool_reader_baton);
      else
        ne_add_response_body_reader(req, ne_accept_2xx, 
                                    spool_reader, &spool_reader_baton);
    }
  else
    {
      if (ras->compression)
        decompress_main = ne_decompress_reader(req, ne_accept_2xx,
                                               ne_xml_parse_v, 
                                               success_parser);
      else
        ne_add_response_body_reader(req, ne_accept_2xx, 
                                    ne_xml_parse_v, success_parser);
    }

  /* Register the "error" accepter and body-reader with the request --
     the one to use when HTTP status is *not* 2XX */
  if (ras->compression)
    decompress_err = ne_decompress_reader(req, ra_dav_error_accepter,
                                          ne_xml_parse_v, error_parser);
  else
    ne_add_response_body_reader(req, ra_dav_error_accepter, 
                                ne_xml_parse_v, error_parser);

  /* run the request and get the resulting status code. */
  rv = ne_request_dispatch(req);

  if (spool_response)
    {
      /* All done with the temporary file we spooled the response
         into. */
      (void) apr_file_close(spool_reader_baton.spool_file);
      if (spool_reader_baton.error)
        {
          err = svn_error_createf
            (SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
             _("Error spooling the %s request response to disk"), method);
          goto cleanup;
        }
    }

  if (decompress_main)
    {
      decompress_rv = ne_decompress_destroy(decompress_main);
      if (decompress_rv != 0)
        {
          rv = decompress_rv;
        }
    }

  if (decompress_err)
    {
      decompress_rv = ne_decompress_destroy(decompress_err);
      if (decompress_rv != 0)
        {
          rv = decompress_rv;
        }
    }
  
  code = ne_get_status(req)->code;
  if (status_code)
    *status_code = code;

  if (err) /* If the error parser had a problem */
    goto cleanup;

  /* Set the expected code based on the method. */
  expected_code = 200;
  if (strcmp(method, "PROPFIND") == 0)
    expected_code = 207;

  if ((code != expected_code) 
      || (rv != NE_OK))
    {
      if (code == 404)
        {
          msg = apr_psprintf(pool, _("'%s' path not found"), url);
          err = svn_error_create(SVN_ERR_RA_DAV_PATH_NOT_FOUND, NULL, msg);
        }
      else
        {
          msg = apr_psprintf(pool, _("%s of '%s'"), method, url);
          err = svn_ra_dav__convert_error(sess, msg, rv, pool);
        }
      goto cleanup;
    }

  /* If we spooled the response to disk instead of parsing on the fly,
     we now need to go read that sucker back and parse it. */
  if (spool_response)
    {
      apr_pool_t *subpool = svn_pool_create(pool);
      err = parse_spool_file(spool_reader_baton.spool_file_name, 
                             success_parser, subpool);
      svn_pool_destroy(subpool);
      if (err)
        {
          svn_error_compose(err, svn_error_createf
                            (SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
                             _("Error reading spooled %s request response"),
                             method));
          goto cleanup;
        }
    }

  /* was there an XML parse error somewhere? */
  msg = ne_xml_get_error(success_parser);
  if (msg != NULL && *msg != '\0')
    {
      err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
                              _("The %s request returned invalid XML "
                                "in the response: %s (%s)"),
                              method, msg, url);
      goto cleanup;
    }

  /* ### necessary?  be paranoid for now. */
  err = SVN_NO_ERROR;

 cleanup:
  if (req)
    ne_request_destroy(req);
  if (success_parser)
    ne_xml_destroy(success_parser);
  if (error_parser)
    ne_xml_destroy(error_parser);
  if (spool_response && spool_reader_baton.spool_file_name)
    (void) apr_file_remove(spool_reader_baton.spool_file_name, pool);
  if (err)
    return svn_error_createf(err->apr_err, err,
                             _("%s request failed on '%s'"), method, url );
  return SVN_NO_ERROR;
}


svn_error_t *
svn_ra_dav__parsed_request(ne_session *sess,
                           const char *method,
                           const char *url,
                           const char *body,
                           apr_file_t *body_file,
                           void set_parser (ne_xml_parser *parser,
                                            void *baton),
                           ne_xml_startelm_cb *startelm_cb,
                           ne_xml_cdata_cb *cdata_cb,
                           ne_xml_endelm_cb *endelm_cb,
                           void *baton,
                           apr_hash_t *extra_headers,
                           int *status_code,
                           svn_boolean_t spool_response,
                           apr_pool_t *pool)
{
  return parsed_request(sess, method, url, body, body_file, 
                        set_parser, NULL, FALSE, NULL, NULL, NULL, 
                        startelm_cb, cdata_cb, endelm_cb,
                        baton, extra_headers, status_code, 
                        spool_response, pool);
}


svn_error_t *
svn_ra_dav__parsed_request_compat(ne_session *sess,
                                  const char *method,
                                  const char *url,
                                  const char *body,
                                  apr_file_t *body_file,
                                  void set_parser (ne_xml_parser *parser,
                                                   void *baton),
                                  const svn_ra_dav__xml_elm_t *elements, 
                                  svn_ra_dav__xml_validate_cb validate_cb,
                                  svn_ra_dav__xml_startelm_cb startelm_cb, 
                                  svn_ra_dav__xml_endelm_cb endelm_cb,
                                  void *baton,
                                  apr_hash_t *extra_headers,
                                  int *status_code,
                                  svn_boolean_t spool_response,
                                  apr_pool_t *pool)
{
  return parsed_request(sess, method, url, body, body_file, 
                        set_parser, elements, TRUE, 
                        validate_cb, startelm_cb, endelm_cb,
                        NULL, NULL, NULL, baton, extra_headers, 
                        status_code, spool_response, pool);
}



svn_error_t *
svn_ra_dav__maybe_store_auth_info(svn_ra_dav__session_t *ras)
{
  /* No auth_baton?  Never mind. */
  if (! ras->callbacks->auth_baton)
    return SVN_NO_ERROR;

  /* If we ever got credentials, ask the iter_baton to save them.  */
  SVN_ERR (svn_auth_save_credentials(ras->auth_iterstate,
                                     ras->pool));
  
  return SVN_NO_ERROR;
}



void
svn_ra_dav__add_error_handler(ne_request *request,
                              ne_xml_parser *parser,
                              svn_error_t **err,
                              apr_pool_t *pool)
{
  shim_xml_push_handler(parser,
                        error_elements,
                        validate_error_elements,
                        start_err_element,
                        end_err_element,
                        err,
                        pool);
  
  ne_add_response_body_reader(request,
                              ra_dav_error_accepter,
                              ne_xml_parse_v,
                              parser);  
}



svn_error_t *
svn_ra_dav__request_dispatch(int *code_p,
                             ne_request *request,
                             ne_session *session,
                             const char *method,
                             const char *url,
                             int okay_1,
                             int okay_2,
                             apr_pool_t *pool)
{
  ne_xml_parser *error_parser;
  int rv;
  const ne_status *statstruct;
  const char *code_desc;
  int code;
  const char *msg;
  svn_error_t *err = SVN_NO_ERROR;

  /* attach a standard <D:error> body parser to the request */
  error_parser = ne_xml_create();
  shim_xml_push_handler(error_parser, error_elements,
                        validate_error_elements, start_err_element,
                        end_err_element, &err, pool);
  ne_add_response_body_reader(request, ra_dav_error_accepter,
                              ne_xml_parse_v, error_parser);

  /* run the request, see what comes back. */
  rv = ne_request_dispatch(request);

  statstruct = ne_get_status(request);
  code_desc = apr_pstrdup(pool, statstruct->reason_phrase);
  code = statstruct->code;
  if (code_p)
     *code_p = code;

  ne_request_destroy(request);
  ne_xml_destroy(error_parser);

  /* If the status code was one of the two that we expected, then go
     ahead and return now. IGNORE any marshalled error. */
  if (rv == NE_OK && (code == okay_1 || code == okay_2))
    return SVN_NO_ERROR;

  /* next, check to see if a <D:error> was discovered */
  if (err)
    return err;

  /* We either have a neon error, or some other error
     that we didn't expect. */
  msg = apr_psprintf(pool, _("%s of '%s'"), method, url);
  return svn_ra_dav__convert_error(session, msg, rv, pool);
}
