/*
 * util.c :  utility functions for the RA/DAV library
 *
 * ====================================================================
 * Copyright (c) 2000-2007 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 <apr_uri.h>

#include <ne_alloc.h>
#include <ne_compress.h>
#include <ne_basic.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_neon.h"
#include <assert.h>



static apr_status_t
xml_parser_cleanup(void *baton)
{
  ne_xml_destroy(baton);

  return APR_SUCCESS;
}

static ne_xml_parser *
xml_parser_create(svn_ra_neon__request_t *req)
{
  ne_xml_parser *p = ne_xml_create();

  /* ### 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(p, "");

  apr_pool_cleanup_register(req->pool, p,
                            xml_parser_cleanup,
                            apr_pool_cleanup_null);

  return p;
}



/* Simple multi-status parser
 *
 * For the purpose of 'simple' requests which - if it weren't
 * for our custom error parser - could use the ne_basic.h interfaces.
 */

static const svn_ra_neon__xml_elm_t multistatus_elements[] =
  { { "DAV:", "multistatus", ELEM_multistatus, 0 },
    { "DAV:", "response", ELEM_response, 0 },
    { "DAV:", "responsedescription", ELEM_responsedescription,
      SVN_RA_NEON__XML_CDATA },
    { "DAV:", "status", ELEM_status, SVN_RA_NEON__XML_CDATA },
    { "DAV:", "href", ELEM_href, SVN_RA_NEON__XML_CDATA },
    { "DAV:", "propstat", ELEM_propstat, SVN_RA_NEON__XML_CDATA },
    { "DAV:", "prop", ELEM_prop, SVN_RA_NEON__XML_CDATA },

    /* We start out basic and are not interested in other elements */
    { "", "", ELEM_unknown, 0 },

    { NULL }
  };


static const int multistatus_nesting_table[][5] =
  { { ELEM_root, ELEM_multistatus, SVN_RA_NEON__XML_INVALID },
    { ELEM_multistatus, ELEM_response, ELEM_responsedescription,
      SVN_RA_NEON__XML_DECLINE },
    { ELEM_responsedescription, SVN_RA_NEON__XML_INVALID },
    { ELEM_response, ELEM_href, ELEM_status, ELEM_propstat,
      SVN_RA_NEON__XML_DECLINE },
    { ELEM_status, SVN_RA_NEON__XML_INVALID },
    { ELEM_href, SVN_RA_NEON__XML_INVALID },
    { ELEM_propstat, ELEM_prop, ELEM_status, ELEM_responsedescription,
      SVN_RA_NEON__XML_INVALID },
    { ELEM_prop, SVN_RA_NEON__XML_DECLINE },
    { SVN_RA_NEON__XML_DECLINE },
  };


static int
validate_element(int parent, int child)
{
  int i = 0;
  int j = 0;

  while (parent != multistatus_nesting_table[i][0]
         && (multistatus_nesting_table[i][0] > 0 || i == 0))
    i++;

  if (parent == multistatus_nesting_table[i][0])
    while (multistatus_nesting_table[i][++j] != child
           && multistatus_nesting_table[i][j] > 0)
      ;

  return multistatus_nesting_table[i][j];
}

typedef struct
{
  svn_stringbuf_t *want_cdata;
  svn_stringbuf_t *cdata;

  svn_boolean_t in_propstat;
  svn_boolean_t propstat_has_error;
  svn_stringbuf_t *propname;
  svn_stringbuf_t *propstat_description;

  svn_ra_neon__request_t *req;
  svn_stringbuf_t *description;
  svn_boolean_t contains_error;
} multistatus_baton_t;

static svn_error_t *
start_207_element(int *elem, void *baton, int parent,
                  const char *nspace, const char *name, const char **atts)
{
  multistatus_baton_t *b = baton;
  const svn_ra_neon__xml_elm_t *elm =
    svn_ra_neon__lookup_xml_elem(multistatus_elements, nspace, name);
  *elem = elm ? validate_element(parent, elm->id) : SVN_RA_NEON__XML_DECLINE;


  if (parent == ELEM_prop)
    {
      svn_stringbuf_setempty(b->propname);
      if (strcmp(nspace, SVN_DAV_PROP_NS_DAV) == 0)
        svn_stringbuf_set(b->propname, SVN_PROP_PREFIX);
      else if (strcmp(nspace, "DAV:") == 0)
        svn_stringbuf_set(b->propname, "DAV:");

      svn_stringbuf_appendcstr(b->propname, name);
    }

  if (*elem < 1) /* ! > 0 */
    return SVN_NO_ERROR;

  switch (*elem)
    {
    case ELEM_propstat:
      b->in_propstat = TRUE;
      b->propstat_has_error = FALSE;
      break;

    default:
      break;
    }

  /* We're guaranteed to have ELM now: SVN_RA_NEON__XML_DECLINE < 1 */
  if (elm->flags & SVN_RA_NEON__XML_CDATA)
    {
      svn_stringbuf_setempty(b->cdata);
      b->want_cdata = b->cdata;
    }

  return SVN_NO_ERROR;
}

static svn_error_t *
end_207_element(void *baton, int state,
                const char *nspace, const char *name)
{
  multistatus_baton_t *b = baton;

  switch (state)
    {
    case ELEM_multistatus:
      if (b->contains_error)
        {
          if (svn_stringbuf_isempty(b->description))
            return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
                                    _("The request response contained at least "
                                      "one error"));
          else
            return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
                                    b->description->data);
        }
      break;

    case ELEM_responsedescription:
      if (b->in_propstat)
        svn_stringbuf_set(b->propstat_description, b->cdata->data);
      else
        {
          if (! svn_stringbuf_isempty(b->description))
            svn_stringbuf_appendcstr(b->description, "\n");
          svn_stringbuf_appendstr(b->description, b->cdata);
        }
      break;

    case ELEM_status:
      {
        ne_status status;

        if (ne_parse_statusline(b->cdata->data, &status) == 0)
          {
            /*### I wanted ||=, but I guess the end result is the same */
            if (! b->in_propstat)
              b->contains_error |= (status.klass != 2);
            else
              b->propstat_has_error = (status.klass != 2);

            free(status.reason_phrase);
          }
        else
          return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
                                  _("The response contains a non-conforming "
                                    "HTTP status line"));
      }
      break;

    case ELEM_propstat:
      b->in_propstat = FALSE;
      b->contains_error |= b->propstat_has_error;
      svn_stringbuf_appendcstr(b->description,
                               apr_psprintf(b->req->pool,
                                            _("Error setting property '%s': "),
                                            b->propname->data));
      svn_stringbuf_appendstr(b->description,
                              b->propstat_description);

    default:
      /* do nothing */
      break;
    }

  /* When we have an element which wants cdata,
     we'll set it all up in start_207_element() again */
  b->want_cdata = NULL;

  return SVN_NO_ERROR;
}


static ne_xml_parser *
multistatus_parser_create(svn_ra_neon__request_t *req)
{
  multistatus_baton_t *b = apr_pcalloc(req->pool, sizeof(*b));
  ne_xml_parser *multistatus_parser =
    svn_ra_neon__xml_parser_create(req, ne_accept_207,
                                   start_207_element,
                                   svn_ra_neon__xml_collect_cdata,
                                   end_207_element, b);
  b->cdata = svn_stringbuf_create("", req->pool);
  b->description = svn_stringbuf_create("", req->pool);
  b->req = req;

  b->propname = svn_stringbuf_create("", req->pool);
  b->propstat_description = svn_stringbuf_create("", req->pool);

  return multistatus_parser;
}




/* Neon request management */

/* Forward declare */
static apr_status_t
dav_request_cleanup(void *baton);

static apr_status_t
dav_request_sess_cleanup(void *baton)
{
  svn_ra_neon__request_t *req = baton;

  /* Make sure we don't run the 'child' cleanup anymore:
     the pool it refers to probably doesn't exist anymore when it
     finally does get run if it hasn't by now. */
  apr_pool_cleanup_kill(req->pool, req, dav_request_cleanup);

  if (req->ne_req)
    ne_request_destroy(req->ne_req);

  return APR_SUCCESS;
}

static apr_status_t
dav_request_cleanup(void *baton)
{
  svn_ra_neon__request_t *req = baton;
  apr_pool_cleanup_run(req->sess->pool, req, dav_request_sess_cleanup);

  return APR_SUCCESS;
}


/* Return a path-absolute relative URL, given a URL reference (which may
   be absolute or relative). */
static const char *
path_from_url(const char *url)
{
  const char *p;

  /* Look for the scheme/authority separator.  Stop if we see a path
     separator - that indicates that this definitely isn't an absolute URL. */
  for (p = url; *p; p++)
    if (*p == ':' || *p == '/')
      break;

  /* Check whether we found the scheme/authority separator. */
  if (*p++ != ':' || *p++ != '/' || *p++ != '/')
    {
      /* No separator, so it must already be relative. */
      return url;
    }

  /* Find the end of the authority section, indicated by the start of
     a path, query, or fragment section. */
  for (; *p; p++)
    if (*p == '/' || *p == '?' || *p == '#')
      break;

  /* Return a pointer to the rest of the URL, or to "/" if there
     was no next section. */
  return *p == '\0' ? "/" : p;
}

svn_ra_neon__request_t *
svn_ra_neon__request_create(svn_ra_neon__session_t *sess,
                            const char *method, const char *url,
                            apr_pool_t *pool)
{
  apr_pool_t *reqpool = svn_pool_create(pool);
  svn_ra_neon__request_t *req = apr_pcalloc(reqpool, sizeof(*req));

  /* We never want to send Neon an absolute URL, since that can cause
     problems with some servers (for example, those that may be accessed
     using different server names from different locations, or those that
     want to rewrite the incoming URL).  If the URL passed in is absolute,
     convert it to a path-absolute relative URL. */
  const char *path = path_from_url(url);

  req->ne_sess = sess->main_session_busy ? sess->ne_sess2 : sess->ne_sess;
  req->ne_req = ne_request_create(req->ne_sess, method, path);
  req->sess = sess;
  req->pool = reqpool;
  req->iterpool = svn_pool_create(req->pool);
  req->method = apr_pstrdup(req->pool, method);
  req->url = apr_pstrdup(req->pool, url);
  req->rv = -1;

  /* Neon resources may be NULL on out-of-memory */
  assert(req->ne_req != NULL);
  apr_pool_cleanup_register(sess->pool, req,
                            dav_request_sess_cleanup,
                            apr_pool_cleanup_null);
  apr_pool_cleanup_register(reqpool, req,
                            dav_request_cleanup,
                            apr_pool_cleanup_null);

  return req;
}

static apr_status_t
compressed_body_reader_cleanup(void *baton)
{
  if (baton)
    ne_decompress_destroy(baton);

  return APR_SUCCESS;
}

static void
attach_ne_body_reader(svn_ra_neon__request_t *req,
                      ne_accept_response accpt,
                      ne_block_reader reader,
                      void *userdata)
{
  if (req->sess->compression)
    {
      ne_decompress *decompress =
        ne_decompress_reader(req->ne_req, accpt, reader, userdata);

      apr_pool_cleanup_register(req->pool,
                                decompress,
                                compressed_body_reader_cleanup,
                                apr_pool_cleanup_null);
    }
  else
    ne_add_response_body_reader(req->ne_req, accpt, reader, userdata);
}


typedef struct
{
  svn_ra_neon__request_t *req;
  svn_ra_neon__block_reader real_reader;
  void *real_baton;
} body_reader_wrapper_baton_t;

static int
body_reader_wrapper(void *userdata, const char *data, size_t len)
{
  body_reader_wrapper_baton_t *b = userdata;

  if (b->req->err)
    /* We already had an error? Bail out. */
    return 1;

  SVN_RA_NEON__REQ_ERR
    (b->req,
     b->real_reader(b->real_baton, data, len));

  if (b->req->err)
    return 1;

  return 0;
}

void
svn_ra_neon__add_response_body_reader(svn_ra_neon__request_t *req,
                                      ne_accept_response accpt,
                                      svn_ra_neon__block_reader reader,
                                      void *userdata)
{
  body_reader_wrapper_baton_t *b = apr_palloc(req->pool, sizeof(*b));

  b->req = req;
  b->real_baton = userdata;
  b->real_reader = reader;

  attach_ne_body_reader(req, accpt, body_reader_wrapper, b);
}



const svn_ra_neon__xml_elm_t *
svn_ra_neon__lookup_xml_elem(const svn_ra_neon__xml_elm_t *table,
                             const char *nspace,
                             const char *name)
{
  /* placeholder for `unknown' element if it's present */
  const svn_ra_neon__xml_elm_t *elem_unknown = NULL;
  const svn_ra_neon__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;
}

svn_error_t *
svn_ra_neon__xml_collect_cdata(void *baton, int state,
                               const char *cdata, size_t len)
{
  svn_stringbuf_t **b = baton;

  if (*b)
    svn_stringbuf_appendbytes(*b, cdata, len);

  return SVN_NO_ERROR;
}



svn_error_t *
svn_ra_neon__copy_href(svn_stringbuf_t *dst, const char *src,
                       apr_pool_t *pool)
{
  /* 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).
  */

  apr_uri_t uri;
  apr_status_t apr_status
    = apr_uri_parse(pool, src, &uri);

  if (apr_status != APR_SUCCESS)
    return svn_error_wrap_apr(apr_status,
                              _("Unable to parse URL '%s'"),
                              src);

  svn_stringbuf_setempty(dst);
  svn_stringbuf_appendcstr(dst, uri.path);

  return SVN_NO_ERROR;
}

static svn_error_t *
generate_error(svn_ra_neon__request_t *req, apr_pool_t *pool)
{
  int errcode = SVN_ERR_RA_DAV_REQUEST_FAILED;
  const char *context =
    apr_psprintf(req->pool, _("%s of '%s'"), req->method, req->url);
  const char *msg;
  const char *hostport;

  /* Convert the return codes. */
  switch (req->rv)
    {
    case NE_OK:
      switch (req->code)
        {
        case 404:
          return svn_error_create(SVN_ERR_RA_DAV_PATH_NOT_FOUND, NULL,
                                  apr_psprintf(pool, _("'%s' path not found"),
                                               req->url));

        case 301:
        case 302:
          return svn_error_create
            (SVN_ERR_RA_DAV_RELOCATED, NULL,
             apr_psprintf(pool,
                          (req->code == 301)
                          ? _("Repository moved permanently to '%s';"
                              " please relocate")
                          : _("Repository moved temporarily to '%s';"
                              " please relocate"),
                          svn_ra_neon__request_get_location(req, pool)));

        default:
          return svn_error_create
            (errcode, NULL,
             apr_psprintf(pool,
                          _("Server sent unexpected return value (%d %s) "
                            "in response to %s request for '%s'"), req->code,
                          req->code_desc, req->method, req->url));
        }
    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(req->ne_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(req->ne_sess), pool));

  /*### This is a translation nightmare. Make sure to compose full strings
    and mark those for translation. */
  return svn_error_createf(errcode, NULL, "%s: %s (%s://%s)",
                           context, msg, ne_get_scheme(req->ne_sess),
                           hostport);
}


/** Error parsing **/


/* Custom function of type ne_accept_response. */
static int ra_neon_error_accepter(void *userdata,
                                  ne_request *req,
                                  const ne_status *st)
{
  /* Before, this function was being run for *all* responses including
     the 401 auth challenge.  In neon 0.24.x that was harmless.  But
     in neon 0.25.0, trying to parse a 401 response as XML using
     ne_xml_parse_v aborts the response; so the auth hooks never got a
     chance. */
  ne_content_type ctype;

  /* Only accept non-2xx responses with text/xml content-type */
  if (st->klass != 2 && ne_get_content_type(req, &ctype) == 0)
    {
      int is_xml =
        (strcmp(ctype.type, "text") == 0 && strcmp(ctype.subtype, "xml") == 0);
      ne_free(ctype.value);
      return is_xml;
    }
  else
    return 0;
}


static const svn_ra_neon__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_NEON__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(svn_ra_neon__xml_elmid parent,
                                   svn_ra_neon__xml_elmid child)
{
  switch (parent)
    {
    case ELEM_root:
      if (child == ELEM_error)
        return child;
      else
        return SVN_RA_NEON__XML_INVALID;

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

    default:
      return SVN_RA_NEON__XML_DECLINE;
    }

  /* NOTREACHED */
}


static int
collect_error_cdata(void *baton, int state,
                    const char *cdata, size_t len)
{
  svn_stringbuf_t **b = baton;

  if (*b)
    svn_stringbuf_appendbytes(*b, cdata, len);

  return 0;
}

typedef struct error_parser_baton
{
  svn_stringbuf_t *want_cdata;
  svn_stringbuf_t *cdata;

  svn_error_t **dst_err;
  svn_error_t *tmp_err;
  svn_boolean_t *marshalled_error;
} error_parser_baton_t;


static int
start_err_element(void *baton, int parent,
                  const char *nspace, const char *name, const char **atts)
{
  const svn_ra_neon__xml_elm_t *elm
    = svn_ra_neon__lookup_xml_elem(error_elements, nspace, name);
  int acc = elm
    ? validate_error_elements(parent, elm->id) : SVN_RA_NEON__XML_DECLINE;
  error_parser_baton_t *b = baton;
  svn_error_t **err = &(b->tmp_err);

  if (acc < 1) /* ! > 0 */
    return acc;

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

  switch (elm->id)
    {
    case ELEM_human_readable:
      b->want_cdata = b->cdata;
      svn_stringbuf_setempty(b->want_cdata);
      break;

    default:
      b->want_cdata = NULL;
      break;
    }

  return elm->id;
}

static int
end_err_element(void *baton, int state, const char *nspace, const char *name)
{
  error_parser_baton_t *b = baton;
  svn_error_t **err = &(b->tmp_err);

  switch (state)
    {
    case ELEM_human_readable:
      {
        if (b->cdata->data && *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;
            const char *cd = b->cdata->data;
            if (*cd == '\n')
              ++cd;
            len = strlen(cd);
            if (len > 0 && cd[len-1] == '\n')
              --len;

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

    case ELEM_error:
      {
        if (*(b->dst_err))
          svn_error_clear(b->tmp_err);
        else if (b->tmp_err)
          {
            *(b->dst_err) = b->tmp_err;
            if (b->marshalled_error)
              *(b->marshalled_error) = TRUE;
          }
        b->tmp_err = NULL;
        break;
      }

    default:
      break;
    }

  return 0;
}

static apr_status_t
error_parser_baton_cleanup(void *baton)
{
  error_parser_baton_t *b = baton;

  if (b->tmp_err)
    svn_error_clear(b->tmp_err);

  return APR_SUCCESS;
}

static ne_xml_parser *
error_parser_create(svn_ra_neon__request_t *req)
{
  error_parser_baton_t *b = apr_palloc(req->pool, sizeof(*b));
  ne_xml_parser *error_parser;

  b->dst_err = &(req->err);
  b->marshalled_error = &(req->marshalled_error);
  b->tmp_err = NULL;

  b->want_cdata = NULL;
  b->cdata = svn_stringbuf_create("", req->pool);

  /* attach a standard <D:error> body parser to the request */
  error_parser = xml_parser_create(req);
  ne_xml_push_handler(error_parser,
                      start_err_element,
                      collect_error_cdata,
                      end_err_element, b);

  apr_pool_cleanup_register(req->pool, b,
                            error_parser_baton_cleanup,
                            apr_pool_cleanup_null);

  /* Register the "error" accepter and body-reader with the request --
     the one to use when HTTP status is *not* 2XX */
  attach_ne_body_reader(req, ra_neon_error_accepter,
                        ne_xml_parse_v, error_parser);

  return error_parser;
}


/* 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.
 */

typedef struct
{
  svn_ra_neon__request_t *req;
  apr_file_t *body_file;
} body_provider_baton_t;

static ssize_t ra_neon_body_provider(void *userdata,
                                     char *buffer,
                                     size_t buflen)
{
  body_provider_baton_t *b = userdata;
  svn_ra_neon__request_t *req = b->req;
  apr_file_t *body_file = b->body_file;

  if (req->sess->callbacks &&
      req->sess->callbacks->cancel_func)
    SVN_RA_NEON__REQ_ERR
      (req, (req->sess->callbacks->cancel_func)(req->sess->callback_baton));

  if (req->err)
    return -1;

  svn_pool_clear(req->iterpool);
  if (buflen == 0)
    {
      /* This is the beginning of a new body pull. Rewind the file. */
      apr_off_t offset = 0;
      SVN_RA_NEON__REQ_ERR
        (b->req,
         svn_io_file_seek(body_file, APR_SET, &offset, req->iterpool));
      return (req->err ? -1 : 0);
    }
  else
    {
      apr_size_t nbytes = buflen;
      svn_error_t *err = svn_io_file_read(body_file, buffer, &nbytes,
                                          req->iterpool);
      if (err)
        {
          if (APR_STATUS_IS_EOF(err->apr_err))
            {
              svn_error_clear(err);
              return 0;
            }

          SVN_RA_NEON__REQ_ERR(req, err);
          return -1;
        }
      else
        return nbytes;
    }
}


svn_error_t *svn_ra_neon__set_neon_body_provider(svn_ra_neon__request_t *req,
                                                 apr_file_t *body_file)
{
  apr_status_t status;
  apr_finfo_t finfo;
  body_provider_baton_t *b = apr_palloc(req->pool, sizeof(*b));

  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"));

  b->body_file = body_file;
  b->req = req;

  ne_set_request_body_provider(req->ne_req, (size_t) finfo.size,
                               ra_neon_body_provider, b);
  return SVN_NO_ERROR;
}


typedef struct spool_reader_baton_t
{
  const char *spool_file_name;
  apr_file_t *spool_file;
  svn_ra_neon__request_t *req;
} spool_reader_baton_t;


/* This implements the svn_ra_neon__block_reader() callback interface. */
static svn_error_t *
spool_reader(void *userdata,
             const char *buf,
             size_t len)
{
  spool_reader_baton_t *baton = userdata;

  SVN_ERR(svn_io_file_write_full(baton->spool_file, buf,
                                 len, NULL, baton->req->iterpool));
  svn_pool_clear(baton->req->iterpool);

  return SVN_NO_ERROR;
}


static svn_error_t *
parse_spool_file(svn_ra_neon__session_t *ras,
                 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)
    {
      if (ras->callbacks &&
          ras->callbacks->cancel_func)
        SVN_ERR((ras->callbacks->cancel_func)(ras->callback_baton));

      len = SVN__STREAM_CHUNK_SIZE;
      SVN_ERR(svn_stream_read(spool_stream, buf, &len));
      if (len > 0)
        if (ne_xml_parse(success_parser, buf, len) != 0)
          /* The parse encountered an error or
             was aborted by a user defined callback */
          break;

      if (len != SVN__STREAM_CHUNK_SIZE)
        break;
    }
  return SVN_NO_ERROR;
}


/* A baton that is used along with a set of Neon ne_startelm_cb,
 * ne_cdata_cb, and ne_endelm_cb callbacks to handle conversion
 * from Subversion style errors to Neon style errors.
 *
 * The underlying Subversion callbacks are called, and if errors
 * are returned they are stored in this baton and a Neon level
 * error code is returned to the parser.
 */
typedef struct {
  svn_ra_neon__request_t *req;
  ne_xml_parser *parser;

  void *baton;
  svn_ra_neon__startelm_cb_t startelm_cb;
  svn_ra_neon__cdata_cb_t cdata_cb;
  svn_ra_neon__endelm_cb_t endelm_cb;
} parser_wrapper_baton_t;

static int
wrapper_startelm_cb(void *baton,
                    int parent,
                    const char *nspace,
                    const char *name,
                    const char **atts)
{
  parser_wrapper_baton_t *pwb = baton;
  int elem = SVN_RA_NEON__XML_DECLINE;

  if (pwb->startelm_cb)
    SVN_RA_NEON__REQ_ERR
      (pwb->req,
       pwb->startelm_cb(&elem, pwb->baton, parent, nspace, name, atts));

  if (elem == SVN_RA_NEON__XML_INVALID)
    SVN_RA_NEON__REQ_ERR
      (pwb->req,
       svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL));

  if (pwb->req->err)
    return NE_XML_ABORT;

  return elem;
}

static int
wrapper_cdata_cb(void *baton, int state, const char *cdata, size_t len)
{
  parser_wrapper_baton_t *pwb = baton;

  if (pwb->cdata_cb)
    SVN_RA_NEON__REQ_ERR
      (pwb->req,
       pwb->cdata_cb(pwb->baton, state, cdata, len));

  if (pwb->req->err)
    return NE_XML_ABORT;

  return 0;
}

static int
wrapper_endelm_cb(void *baton,
                  int state,
                  const char *nspace,
                  const char *name)
{
  parser_wrapper_baton_t *pwb = baton;

  if (pwb->endelm_cb)
    SVN_RA_NEON__REQ_ERR
      (pwb->req,
       pwb->endelm_cb(pwb->baton, state, nspace, name));

  if (pwb->req->err)
    return NE_XML_ABORT;

  return 0;
}

static int
wrapper_reader_cb(void *baton, const char *data, size_t len)
{
  parser_wrapper_baton_t *pwb = baton;
  svn_ra_neon__session_t *sess = pwb->req->sess;

  if (pwb->req->err)
    return 1;

  if (sess->callbacks->cancel_func)
    SVN_RA_NEON__REQ_ERR
      (pwb->req,
       (sess->callbacks->cancel_func)(sess->callback_baton));

  if (pwb->req->err)
    return 1;

  return ne_xml_parse(pwb->parser, data, len);
}

ne_xml_parser *
svn_ra_neon__xml_parser_create(svn_ra_neon__request_t *req,
                               ne_accept_response accpt,
                               svn_ra_neon__startelm_cb_t startelm_cb,
                               svn_ra_neon__cdata_cb_t cdata_cb,
                               svn_ra_neon__endelm_cb_t endelm_cb,
                               void *baton)
{
  ne_xml_parser *p = xml_parser_create(req);
  parser_wrapper_baton_t *pwb = apr_palloc(req->pool, sizeof(*pwb));

  pwb->req = req;
  pwb->parser = p;
  pwb->baton = baton;
  pwb->startelm_cb = startelm_cb;
  pwb->cdata_cb = cdata_cb;
  pwb->endelm_cb = endelm_cb;

  ne_xml_push_handler(p,
                      wrapper_startelm_cb,
                      wrapper_cdata_cb,
                      wrapper_endelm_cb, pwb);

  if (accpt)
    attach_ne_body_reader(req, accpt, wrapper_reader_cb, pwb);

  return p;
}


typedef struct cancellation_baton_t
{
  ne_block_reader real_cb;
  void *real_userdata;
  svn_ra_neon__request_t *req;
} cancellation_baton_t;

static int
cancellation_callback(void *userdata, const char *block, size_t len)
{
  cancellation_baton_t *b = userdata;
  svn_ra_neon__session_t *ras = b->req->sess;

  if (ras->callbacks->cancel_func)
    SVN_RA_NEON__REQ_ERR
      (b->req,
       (ras->callbacks->cancel_func)(ras->callback_baton));

  if (b->req->err)
    return 1;
  else
    return (b->real_cb)(b->real_userdata, block, len);
}


static cancellation_baton_t *
get_cancellation_baton(svn_ra_neon__request_t *req,
                       ne_block_reader real_cb,
                       void *real_userdata,
                       apr_pool_t *pool)
{
  cancellation_baton_t *b = apr_palloc(pool, sizeof(*b));

  b->real_cb = real_cb;
  b->real_userdata = real_userdata;
  b->req = req;

  return b;
}

/* See doc string for svn_ra_neon__parsed_request. */
static svn_error_t *
parsed_request(svn_ra_neon__request_t *req,
               svn_ra_neon__session_t *ras,
               const char *method,
               const char *url,
               const char *body,
               apr_file_t *body_file,
               void set_parser(ne_xml_parser *parser,
                               void *baton),
               svn_ra_neon__startelm_cb_t startelm_cb,
               svn_ra_neon__cdata_cb_t cdata_cb,
               svn_ra_neon__endelm_cb_t endelm_cb,
               void *baton,
               apr_hash_t *extra_headers,
               int *status_code,
               svn_boolean_t spool_response,
               apr_pool_t *pool)
{
  ne_xml_parser *success_parser = NULL;
  const char *msg;
  spool_reader_baton_t spool_reader_baton;

  if (body == NULL)
    SVN_ERR(svn_ra_neon__set_neon_body_provider(req, body_file));

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

  /* create a parser to read the normal response body */
  success_parser = svn_ra_neon__xml_parser_create(req, NULL,
                                                  startelm_cb, cdata_cb,
                                                  endelm_cb, baton);

  /* 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);

  /* 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;
      SVN_ERR(svn_io_temp_dir(&tmpfile_path, pool));

      tmpfile_path = svn_path_join(tmpfile_path, "dav-spool", pool);
      /* Blow the temp-file away as soon as we eliminate the entire request */
      SVN_ERR(svn_io_open_unique_file2(&spool_reader_baton.spool_file,
                                       &spool_reader_baton.spool_file_name,
                                       tmpfile_path, "",
                                       svn_io_file_del_on_pool_cleanup,
                                       req->pool));
      spool_reader_baton.req = req;

      svn_ra_neon__add_response_body_reader(req, ne_accept_2xx, spool_reader,
                                            &spool_reader_baton);
    }
  else
    attach_ne_body_reader(req, ne_accept_2xx, cancellation_callback,
                          get_cancellation_baton(req, ne_xml_parse_v,
                                                 success_parser, pool));

  /* run the request and get the resulting status code. */
  SVN_ERR(svn_ra_neon__request_dispatch(status_code,
                                        req, extra_headers, body,
                                        (strcmp(method, "PROPFIND") == 0)
                                        ? 207 : 200,
                                        0, /* not used */
                                        pool));

  if (spool_response)
    {
      /* All done with the temporary file we spooled the response into. */
      (void) apr_file_close(spool_reader_baton.spool_file);

      /* The success parser may set an error value in req->err */
      SVN_RA_NEON__REQ_ERR
        (req, parse_spool_file(ras, spool_reader_baton.spool_file_name,
                               success_parser, req->pool));
      if (req->err)
        {
          svn_error_compose(req->err, svn_error_createf
                            (SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
                             _("Error reading spooled %s request response"),
                             method));
          return req->err;
        }
    }

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

  return SVN_NO_ERROR;
}


svn_error_t *
svn_ra_neon__parsed_request(svn_ra_neon__session_t *sess,
                            const char *method,
                            const char *url,
                            const char *body,
                            apr_file_t *body_file,
                            void set_parser(ne_xml_parser *parser,
                                            void *baton),
                            svn_ra_neon__startelm_cb_t startelm_cb,
                            svn_ra_neon__cdata_cb_t cdata_cb,
                            svn_ra_neon__endelm_cb_t endelm_cb,
                            void *baton,
                            apr_hash_t *extra_headers,
                            int *status_code,
                            svn_boolean_t spool_response,
                            apr_pool_t *pool)
{
  /* create/prep the request */
  svn_ra_neon__request_t* req = svn_ra_neon__request_create(sess, method, url,
                                                           pool);
  svn_error_t *err = parsed_request(req,
                                    sess, method, url, body, body_file,
                                    set_parser,
                                    startelm_cb, cdata_cb, endelm_cb,
                                    baton, extra_headers, status_code,
                                    spool_response, pool);
  svn_ra_neon__request_destroy(req);
  return err;
}


svn_error_t *
svn_ra_neon__simple_request(int *code,
                            svn_ra_neon__session_t *ras,
                            const char *method,
                            const char *url,
                            apr_hash_t *extra_headers,
                            const char *body,
                            int okay_1, int okay_2, apr_pool_t *pool)
{
  svn_ra_neon__request_t *req =
    svn_ra_neon__request_create(ras, method, url, pool);
  svn_error_t *err;

  /* we don't need the status parser: it's attached to the request
     and detected errors will be returned there... */
  (void) multistatus_parser_create(req);

  /* svn_ra_neon__request_dispatch() adds the custom error response
     reader.  Neon will take care of the Content-Length calculation */
  err = svn_ra_neon__request_dispatch(code, req, extra_headers,
                                      body ? body : "",
                                      okay_1, okay_2, pool);
  svn_ra_neon__request_destroy(req);

  return err;
}

void
svn_ra_neon__add_depth_header(apr_hash_t *extra_headers, int depth)
{
  /*  assert(extra_headers != NULL);
  assert(depth == SVN_RA_NEON__DEPTH_ZERO
         || depth == SVN_RA_NEON__DEPTH_ONE
         || depth == SVN_RA_NEON__DEPTH_INFINITE); */
  apr_hash_set(extra_headers, "Depth", APR_HASH_KEY_STRING,
               (depth == SVN_RA_NEON__DEPTH_INFINITE)
               ? "infinity" : (depth == SVN_RA_NEON__DEPTH_ZERO) ? "0" : "1");

  return;
}


svn_error_t *
svn_ra_neon__copy(svn_ra_neon__session_t *ras,
                  svn_boolean_t overwrite,
                  int depth,
                  const char *src,
                  const char *dst,
                  apr_pool_t *pool)
{
  const char *abs_dst;
  apr_hash_t *extra_headers = apr_hash_make(pool);

  abs_dst = apr_psprintf(pool, "%s://%s%s", ne_get_scheme(ras->ne_sess),
                         ne_get_server_hostport(ras->ne_sess), dst);
  apr_hash_set(extra_headers, "Destination", APR_HASH_KEY_STRING, abs_dst);
  apr_hash_set(extra_headers, "Overwrite", APR_HASH_KEY_STRING,
               overwrite ? "T" : "F");
  svn_ra_neon__add_depth_header(extra_headers, depth);

  return svn_ra_neon__simple_request(NULL, ras, "COPY", src, extra_headers,
                                     NULL, 201, 204, pool);
}



svn_error_t *
svn_ra_neon__maybe_store_auth_info(svn_ra_neon__session_t *ras,
                                   apr_pool_t *pool)
{
  /* 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,
                                    pool));

  return SVN_NO_ERROR;
}


svn_error_t *
svn_ra_neon__maybe_store_auth_info_after_result(svn_error_t *err,
                                                svn_ra_neon__session_t *ras,
                                                apr_pool_t *pool)
{
  if (! err || (err->apr_err != SVN_ERR_RA_NOT_AUTHORIZED))
    {
      svn_error_t *err2 = svn_ra_neon__maybe_store_auth_info(ras, pool);
      if (err2 && ! err)
        return err2;
      else if (err)
        {
          svn_error_clear(err2);
          return err;
        }
    }

  return err;
}


svn_error_t *
svn_ra_neon__request_dispatch(int *code_p,
                              svn_ra_neon__request_t *req,
                              apr_hash_t *extra_headers,
                              const char *body,
                              int okay_1,
                              int okay_2,
                              apr_pool_t *pool)
{
  ne_xml_parser *error_parser;
  const ne_status *statstruct;

  /* 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->ne_req,
                                (const char *) key, (const char *) val);
        }
    }

  if (body)
    ne_set_request_body_buffer(req->ne_req, body, strlen(body));

  /* attach a standard <D:error> body parser to the request */
  error_parser = error_parser_create(req);

  if (req->ne_sess == req->sess->ne_sess) /* We're consuming 'session 1' */
    req->sess->main_session_busy = TRUE;
  /* run the request, see what comes back. */
  req->rv = ne_request_dispatch(req->ne_req);
  if (req->ne_sess == req->sess->ne_sess) /* We're done consuming 'session 1' */
    req->sess->main_session_busy = FALSE;

  /* Save values from the request */
  statstruct = ne_get_status(req->ne_req);
  req->code_desc = apr_pstrdup(pool, statstruct->reason_phrase);
  req->code = statstruct->code;

  if (code_p)
     *code_p = req->code;

  if (!req->marshalled_error)
    SVN_ERR(req->err);

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

  /* Any other errors? Report them */
  SVN_ERR(req->err);

  /* We either have a neon error, or some other error
     that we didn't expect. */
  return generate_error(req, pool);
}


const char *
svn_ra_neon__request_get_location(svn_ra_neon__request_t *request,
                                  apr_pool_t *pool)
{
  const char *val = ne_get_response_header(request->ne_req, "Location");
  return val ? apr_pstrdup(pool, val) : NULL;
}
