#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 <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_dav.h"
#define WOOTWOOT 1
typedef struct {
apr_pool_t *pool;
void *original_userdata;
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;
svn_stringbuf_t *cdata_accum;
} 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)
{
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;
if (elem->id == ELEM_unknown)
elem_unknown = elem;
}
return elem_unknown;
}
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;
*elem_pointer = elem_unknown_temporary;
}
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;
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);
return elem->id;
}
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;
}
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;
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;
return 0;
}
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;
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;
(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;
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:
SVN_ERR(svn_utf_cstring_to_utf8(&msg, ne_get_error(sess), pool));
break;
}
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);
}
static int ra_dav_error_accepter(void *userdata,
ne_request *req,
const ne_status *st)
{
#ifdef SVN_NEON_0_25
ne_content_type ctype;
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;
#else
return (st->klass != 2);
#endif
}
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 },
{ 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;
default:
return SVN_RA_DAV__XML_DECLINE;
}
}
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:
{
*err = svn_error_create(APR_EGENERAL, NULL,
"General svn error from server");
break;
}
case ELEM_human_readable:
{
const char *errcode_str =
svn_xml_get_attr_value("errcode",
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)
{
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;
}
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)
{
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;
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;
#ifdef SVN_NEON_0_25
static int
#else
static void
#endif
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);
#ifdef SVN_NEON_0_25
if (baton->error)
return 1;
else
return 0;
#endif
}
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;
}
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,
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,
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;
#ifndef SVN_NEON_0_25
int decompress_rv;
#endif
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);
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;
ne_add_request_header(req, "Content-Type", "text/xml");
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);
}
}
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);
}
ne_xml_set_error(success_parser, "");
if (set_parser != NULL)
set_parser(success_parser, baton);
error_parser = ne_xml_create();
shim_xml_push_handler(error_parser, error_elements,
validate_error_elements, start_err_element,
end_err_element, &err, pool);
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);
err = svn_io_open_unique_file2(&spool_reader_baton.spool_file,
&spool_reader_baton.spool_file_name,
tmpfile_path, "",
svn_io_file_del_none, pool);
if (err)
goto cleanup;
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);
}
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);
rv = ne_request_dispatch(req);
if (spool_response)
{
(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;
}
}
#ifdef SVN_NEON_0_25
if (decompress_main)
ne_decompress_destroy(decompress_main);
if (decompress_err)
ne_decompress_destroy(decompress_err);
#else
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;
}
}
#endif
code = ne_get_status(req)->code;
if (status_code)
*status_code = code;
if (err)
goto cleanup;
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 (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;
}
}
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;
}
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,
apr_pool_t *pool)
{
if (! ras->callbacks->auth_baton)
return SVN_NO_ERROR;
SVN_ERR(svn_auth_save_credentials(ras->auth_iterstate,
pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_dav__maybe_store_auth_info_after_result(svn_error_t *err,
svn_ra_dav__session_t *ras,
apr_pool_t *pool)
{
if (! err || (err->apr_err != SVN_ERR_RA_NOT_AUTHORIZED))
{
svn_error_t *err2 = svn_ra_dav__maybe_store_auth_info(ras, pool);
if (err2 && ! err)
return err2;
else if (err)
{
svn_error_clear(err2);
return err;
}
}
return err;
}
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,
#ifdef SVN_NEON_0_25
svn_ra_dav__request_interrogator interrogator,
void *interrogator_baton,
#endif
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;
#ifdef SVN_NEON_0_25
svn_error_t *err2 = SVN_NO_ERROR;
#endif
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);
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;
#ifdef SVN_NEON_0_25
if (interrogator)
err2 = (*interrogator)(request, rv, interrogator_baton);
#endif
ne_request_destroy(request);
ne_xml_destroy(error_parser);
#ifdef SVN_NEON_0_25
if (err2)
{
svn_error_clear(err);
return err2;
}
#endif
if (rv == NE_OK && (code == okay_1 || code == okay_2))
return SVN_NO_ERROR;
if (err)
return err;
msg = apr_psprintf(pool, _("%s of '%s'"), method, url);
return svn_ra_dav__convert_error(session, msg, rv, pool);
}