#define APR_WANT_STRFUNC
#include <apr_want.h>
#include <apr_tables.h>
#include <apr_strings.h>
#include <apr_xml.h>
#include <ne_socket.h>
#include <ne_utils.h>
#include <ne_xml.h>
#include "svn_error.h"
#include "svn_pools.h"
#include "svn_delta.h"
#include "svn_io.h"
#include "svn_path.h"
#include "svn_xml.h"
#include "svn_base64.h"
#include "svn_props.h"
#include "../libsvn_ra/ra_loader.h"
#include "svn_private_config.h"
#include "ra_dav.h"
struct report_baton {
svn_ra_file_rev_handler_t handler;
void *handler_baton;
const char *path;
svn_revnum_t revnum;
apr_hash_t *rev_props;
apr_array_header_t *prop_diffs;
const char *prop_name;
svn_boolean_t base64_prop;
svn_stringbuf_t *cdata_accum;
svn_stream_t *stream;
svn_boolean_t had_txdelta;
svn_error_t *err;
apr_pool_t *subpool;
};
static void
reset_file_rev(struct report_baton *rb)
{
svn_pool_clear(rb->subpool);
rb->path = NULL;
rb->revnum = SVN_INVALID_REVNUM;
rb->rev_props = apr_hash_make(rb->subpool);
rb->prop_diffs = apr_array_make(rb->subpool, 0, sizeof(svn_prop_t));
rb->had_txdelta = FALSE;
rb->stream = NULL;
}
static const svn_ra_dav__xml_elm_t report_elements[] =
{
{ SVN_XML_NAMESPACE, "file-revs-report", ELEM_file_revs_report, 0 },
{ SVN_XML_NAMESPACE, "file-rev", ELEM_file_rev, 0 },
{ SVN_XML_NAMESPACE, "rev-prop", ELEM_rev_prop, 0 },
{ SVN_XML_NAMESPACE, "set-prop", ELEM_set_prop, 0 },
{ SVN_XML_NAMESPACE, "remove-prop", ELEM_remove_prop, 0 },
{ SVN_XML_NAMESPACE, "txdelta", ELEM_txdelta, 0 },
{ NULL }
};
#define CHKERR(e) \
do { \
if ((rb->err = (e)) != NULL) \
return NE_XML_ABORT; \
} while(0)
static int
start_element(void *userdata, int parent_state, const char *ns,
const char *ln, const char **atts)
{
struct report_baton *rb = userdata;
const svn_ra_dav__xml_elm_t *elm;
const char *att;
elm = svn_ra_dav__lookup_xml_elem(report_elements, ns, ln);
if (!elm)
return NE_XML_DECLINE;
switch (parent_state)
{
case ELEM_root:
if (elm->id != ELEM_file_revs_report)
return NE_XML_ABORT;
break;
case ELEM_file_revs_report:
if (elm->id == ELEM_file_rev)
{
reset_file_rev(rb);
att = svn_xml_get_attr_value("rev", atts);
if (!att)
return NE_XML_ABORT;
rb->revnum = SVN_STR_TO_REV(att);
att = svn_xml_get_attr_value("path", atts);
if (!att)
return NE_XML_ABORT;
rb->path = apr_pstrdup(rb->subpool, att);
}
else
return NE_XML_ABORT;
break;
case ELEM_file_rev:
if (rb->had_txdelta)
return NE_XML_ABORT;
switch (elm->id)
{
case ELEM_rev_prop:
case ELEM_set_prop:
att = svn_xml_get_attr_value("name", atts);
if (!att)
return NE_XML_ABORT;
rb->prop_name = apr_pstrdup(rb->subpool, att);
att = svn_xml_get_attr_value("encoding", atts);
if (att && strcmp(att, "base64") == 0)
rb->base64_prop = TRUE;
else
rb->base64_prop = FALSE;
break;
case ELEM_remove_prop:
{
svn_prop_t *prop = apr_array_push(rb->prop_diffs);
att = svn_xml_get_attr_value("name", atts);
if (!att || *att == '\0')
return NE_XML_ABORT;
prop->name = apr_pstrdup(rb->subpool, att);
prop->value = NULL;
}
break;
case ELEM_txdelta:
{
svn_txdelta_window_handler_t whandler = NULL;
void *wbaton;
CHKERR(rb->handler(rb->handler_baton, rb->path, rb->revnum,
rb->rev_props, &whandler, &wbaton,
rb->prop_diffs, rb->subpool));
if (whandler)
rb->stream = svn_base64_decode
(svn_txdelta_parse_svndiff(whandler, wbaton, TRUE,
rb->subpool), rb->subpool);
}
break;
default:
return NE_XML_ABORT;
}
break;
default:
return NE_XML_ABORT;
}
return elm->id;
}
static const svn_string_t *
extract_propval(struct report_baton *rb)
{
const svn_string_t *v = svn_string_create_from_buf(rb->cdata_accum,
rb->subpool);
svn_stringbuf_setempty(rb->cdata_accum);
if (rb->base64_prop)
return svn_base64_decode_string(v, rb->subpool);
else
return v;
}
static int
end_element(void *userdata, int state,
const char *nspace, const char *elt_name)
{
struct report_baton *rb = userdata;
switch (state)
{
case ELEM_file_rev:
if (!rb->had_txdelta)
CHKERR(rb->handler(rb->handler_baton, rb->path, rb->revnum,
rb->rev_props, NULL, NULL, rb->prop_diffs,
rb->subpool));
break;
case ELEM_rev_prop:
apr_hash_set(rb->rev_props, rb->prop_name, APR_HASH_KEY_STRING,
extract_propval(rb));
break;
case ELEM_set_prop:
{
svn_prop_t *prop = apr_array_push(rb->prop_diffs);
prop->name = rb->prop_name;
prop->value = extract_propval(rb);
break;
}
case ELEM_txdelta:
if (rb->stream)
{
CHKERR(svn_stream_close(rb->stream));
rb->stream = NULL;
}
rb->had_txdelta = TRUE;
break;
}
return 0;
}
static int
cdata_handler(void *userdata, int state,
const char *cdata, size_t len)
{
struct report_baton *rb = userdata;
switch (state)
{
case ELEM_rev_prop:
case ELEM_set_prop:
svn_stringbuf_appendbytes(rb->cdata_accum, cdata, len);
break;
case ELEM_txdelta:
if (rb->stream)
{
apr_size_t l = len;
CHKERR(svn_stream_write(rb->stream, cdata, &l));
if (l != len)
return NE_XML_ABORT;
}
}
return 0;
}
#undef CHKERR
svn_error_t *
svn_ra_dav__get_file_revs(svn_ra_session_t *session,
const char *path,
svn_revnum_t start,
svn_revnum_t end,
svn_ra_file_rev_handler_t handler,
void *handler_baton,
apr_pool_t *pool)
{
svn_ra_dav__session_t *ras = session->priv;
svn_stringbuf_t *request_body = svn_stringbuf_create("", pool);
svn_string_t bc_url, bc_relative;
const char *final_bc_url;
int http_status = 0;
struct report_baton rb;
svn_error_t *err;
apr_hash_t *request_headers = apr_hash_make(pool);
static const char request_head[]
= "<S:file-revs-report xmlns:S=\"" SVN_XML_NAMESPACE "\">" DEBUG_CR;
static const char request_tail[]
= "</S:file-revs-report>";
apr_hash_set(request_headers, "Accept-Encoding", APR_HASH_KEY_STRING,
"svndiff1;q=0.9,svndiff;q=0.8");
svn_stringbuf_appendcstr(request_body, request_head);
svn_stringbuf_appendcstr(request_body,
apr_psprintf(pool,
"<S:start-revision>%ld"
"</S:start-revision>", start));
svn_stringbuf_appendcstr(request_body,
apr_psprintf(pool,
"<S:end-revision>%ld"
"</S:end-revision>", end));
svn_stringbuf_appendcstr(request_body, "<S:path>");
svn_stringbuf_appendcstr(request_body,
apr_xml_quote_string(pool, path, 0));
svn_stringbuf_appendcstr(request_body, "</S:path>");
svn_stringbuf_appendcstr(request_body, request_tail);
rb.handler = handler;
rb.handler_baton = handler_baton;
rb.cdata_accum = svn_stringbuf_create("", pool);
rb.err = NULL;
rb.subpool = svn_pool_create(pool);
reset_file_rev(&rb);
SVN_ERR(svn_ra_dav__get_baseline_info(NULL, &bc_url, &bc_relative, NULL,
ras->sess, ras->url->data, end,
pool));
final_bc_url = svn_path_url_add_component(bc_url.data, bc_relative.data,
pool);
err = svn_ra_dav__parsed_request(ras->sess, "REPORT", final_bc_url,
request_body->data, NULL, NULL,
start_element, cdata_handler, end_element,
&rb, request_headers, &http_status, FALSE,
pool);
if (http_status == 501)
return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
_("'get-file-revs' REPORT not implemented"));
if (rb.err != NULL)
{
if (err)
svn_error_clear(err);
return rb.err;
}
SVN_ERR(err);
if (!SVN_IS_VALID_REVNUM(rb.revnum))
return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("The file-revs report didn't contain any "
"revisions"));
svn_pool_destroy(rb.subpool);
return SVN_NO_ERROR;
}