blob: d3ccefb0fd00bbffad17d3e0d3f761b0f9834cb2 [file] [log] [blame]
/*
* util.c : utility functions for the RA/DAV library
*
* ====================================================================
* Copyright (c) 2000-2002 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/.
* ====================================================================
*/
#define APR_WANT_STRFUNC
#include <apr_want.h>
#include <ne_uri.h>
#include "svn_string.h"
#include "svn_xml.h"
#include "ra_dav.h"
void svn_ra_dav__copy_href(svn_stringbuf_t *dst, const char *src)
{
struct 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) uri_parse(src, &parsed_url, NULL);
svn_stringbuf_set(dst, parsed_url.path);
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_REQUEST_FAILED;
const char *msg;
/* 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. */
msg = ne_get_error (sess);
break;
}
return svn_error_createf (errcode, 0, NULL, pool,
"%s: %s", context, msg);
}
/** Error parsing **/
/* Context baton for the error parser. Obviously, it just builds an
ERR from POOL. */
typedef struct {
svn_error_t *err;
apr_pool_t *pool;
} parser_cxt_t;
/* Custom function of type ne_accept_response. */
static int ra_dav_error_accepter(void *userdata,
ne_request *req,
ne_status *st)
{
/* Only accept the body-response if the HTTP status code is *not* 2XX. */
return (st->klass != 2);
}
static const struct ne_xml_elm error_elements[] =
{
{ "DAV:", "error", ELEM_error, 0 },
{ "svn:", "error", ELEM_svn_error, 0 },
{ "http://apache.org/dav/xmlns", "human-readable",
ELEM_human_readable, NE_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,
ne_xml_elmid parent,
ne_xml_elmid child)
{
switch (parent)
{
case NE_ELM_root:
if (child == ELEM_error)
return NE_XML_VALID;
else
return NE_XML_INVALID;
case ELEM_error:
if (child == ELEM_svn_error
|| child == ELEM_human_readable)
return NE_XML_VALID;
else
return NE_XML_DECLINE; /* ignore if something else was in there */
default:
return NE_XML_DECLINE;
}
/* NOTREACHED */
}
static int start_err_element(void *userdata, const struct ne_xml_elm *elm,
const char **atts)
{
parser_cxt_t *pc = (parser_cxt_t *) 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. */
pc->err = svn_error_create(APR_EGENERAL, 0, NULL, pc->pool,
"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 && pc->err)
pc->err->apr_err = atoi(errcode_str);
break;
}
default:
break;
}
return 0;
}
static int end_err_element(void *userdata, const struct ne_xml_elm *elm,
const char *cdata)
{
parser_cxt_t *pc = (parser_cxt_t *) userdata;
switch (elm->id)
{
case ELEM_human_readable:
{
if (cdata && pc->err)
pc->err->message = apr_pstrdup(pc->err->pool, cdata);
break;
}
default:
break;
}
return 0;
}
svn_error_t *svn_ra_dav__parsed_request(svn_ra_session_t *ras,
const char *method,
const char *url,
const char *body,
int fd,
const struct ne_xml_elm *elements,
ne_xml_validate_cb validate_cb,
ne_xml_startelm_cb startelm_cb,
ne_xml_endelm_cb endelm_cb,
void *baton,
apr_pool_t *pool)
{
ne_request *req;
ne_xml_parser *success_parser;
ne_xml_parser *error_parser;
int rv;
int code;
const char *msg;
svn_error_t *err;
parser_cxt_t *pc = apr_pcalloc (pool, sizeof(*pc));
pc->pool = pool;
/* create/prep the request */
req = ne_request_create(ras->sess, method, url);
if (body != NULL)
ne_set_request_body_buffer(req, body, strlen(body));
else
ne_set_request_body_fd(req, fd);
/* ### use a symbolic name somewhere for this MIME type? */
ne_add_request_header(req, "Content-Type", "text/xml");
/* create a parser to read the normal response body */
success_parser = ne_xml_create();
ne_xml_push_handler(success_parser, elements,
validate_cb, startelm_cb, endelm_cb, baton);
/* create a parser to read the <D:error> response body */
error_parser = ne_xml_create();
ne_xml_push_handler(error_parser, error_elements, validate_error_elements,
start_err_element, end_err_element, pc);
/* Register the "main" accepter and body-reader with the request --
the one to use when the HTTP status is 2XX */
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 */
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);
code = ne_get_status(req)->code;
ne_request_destroy(req);
if (rv != NE_OK)
{
msg = apr_psprintf(pool, "%s of %s", method, url);
err = svn_ra_dav__convert_error(ras->sess, msg, rv, pool);
goto error;
}
if (pc->err != NULL)
{
/* The HTTP status code wasn't 2XX, so the error-parser built an
error for us. */
err = pc->err;
goto error;
}
if (code != 200)
{
/* Bad status, but error-parser didn't build an error. Return a
generic error instead.*/
err = svn_error_createf(APR_EGENERAL, 0, NULL, pool,
"The %s status was %d, but expected 200.",
method, code);
goto error;
}
/* 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_REQUEST_FAILED, 0, NULL,
pool,
"The %s request returned invalid XML "
"in the response: %s. (%s)",
method, msg, url);
goto error;
}
/* ### maybe hook this to a pool? */
ne_xml_destroy(success_parser);
ne_xml_destroy(error_parser);
return NULL;
error:
ne_xml_destroy(success_parser);
ne_xml_destroy(error_parser);
return svn_error_createf(err->apr_err, err->src_err, err, NULL,
"%s request failed on %s", method, url );
}
svn_error_t *
svn_ra_dav__maybe_store_auth_info(svn_ra_session_t *ras)
{
void *a, *auth_baton;
svn_ra_simple_password_authenticator_t *authenticator;
SVN_ERR (ras->callbacks->get_authenticator (&a, &auth_baton,
svn_ra_auth_simple_password,
ras->callback_baton,
ras->pool));
authenticator = (svn_ra_simple_password_authenticator_t *) a;
/* If we have a auth-info storage callback, use it. */
if (authenticator->store_user_and_pass)
/* Storage will only happen if AUTH_BATON is already caching auth info. */
SVN_ERR (authenticator->store_user_and_pass (auth_baton));
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_dav__request_dispatch(int *code,
ne_request *request,
ne_session *session,
const char *method,
const char *url,
apr_pool_t *pool)
{
ne_xml_parser *error_parser;
int rv;
const ne_status *statstruct;
const char *code_desc;
parser_cxt_t *pc = apr_pcalloc (pool, sizeof(*pc));
pc->pool = pool;
/* attach a standard <D:error> body parser to the request */
error_parser = ne_xml_create();
ne_xml_push_handler(error_parser, error_elements, validate_error_elements,
start_err_element, end_err_element, pc);
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 = statstruct->code;
code_desc = apr_pstrdup(pool, statstruct->reason_phrase);
ne_request_destroy(request);
ne_xml_destroy(error_parser);
/* first, check to see if neon itself got an error */
if (rv != NE_OK)
{
const char *msg = apr_psprintf(pool, "%s of %s", method, url);
return svn_ra_dav__convert_error(session, msg, rv, pool);
}
/* next, check to see if a <D:error> was discovered */
if (pc->err != NULL)
return pc->err;
if ((*code < 200) || (*code >= 300))
/* Bad http status, but error-parser didn't build an svn_error_t
for some reason. Return a generic error instead.*/
return svn_error_createf(APR_EGENERAL, 0, NULL, pool,
"%s of %s returned status code %d (%s)",
method, url, *code, code_desc);
return SVN_NO_ERROR;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/