#include <apr_pools.h>
#include <apr_hash.h>
#define APR_WANT_STRFUNC
#include <apr_want.h>
#include "svn_string.h"
#include "svn_error.h"
#include "svn_path.h"
#include "svn_ra.h"
#include "svn_pools.h"
#include "svn_props.h"
#include "svn_xml.h"
#include "private/svn_dav_protocol.h"
#include "svn_private_config.h"
#include "ra_neon.h"
static const svn_ra_neon__xml_elm_t merge_elements[] =
{
{ "DAV:", "updated-set", ELEM_updated_set, 0 },
{ "DAV:", "merged-set", ELEM_merged_set, 0 },
{ "DAV:", "ignored-set", ELEM_ignored_set, 0 },
{ "DAV:", "href", ELEM_href, SVN_RA_NEON__XML_CDATA },
{ "DAV:", "merge-response", ELEM_merge_response, 0 },
{ "DAV:", "checked-in", ELEM_checked_in, 0 },
{ "DAV:", "response", ELEM_response, 0 },
{ "DAV:", "propstat", ELEM_propstat, 0 },
{ "DAV:", "status", ELEM_status, SVN_RA_NEON__XML_CDATA },
{ "DAV:", "responsedescription", ELEM_responsedescription,
SVN_RA_NEON__XML_CDATA },
{ "DAV:", "prop", ELEM_prop, 0 },
{ "DAV:", "resourcetype", ELEM_resourcetype, 0 },
{ "DAV:", "collection", ELEM_collection, 0 },
{ "DAV:", "baseline", ELEM_baseline, 0 },
{ "DAV:", SVN_DAV__VERSION_NAME, ELEM_version_name, SVN_RA_NEON__XML_CDATA },
{ SVN_XML_NAMESPACE, "post-commit-err",
ELEM_post_commit_err, SVN_RA_NEON__XML_CDATA },
{ "DAV:", SVN_DAV__CREATIONDATE, ELEM_creationdate, SVN_RA_NEON__XML_CDATA },
{ "DAV:", "creator-displayname", ELEM_creator_displayname,
SVN_RA_NEON__XML_CDATA },
{ NULL }
};
enum merge_rtype {
RTYPE_UNKNOWN,
RTYPE_REGULAR,
RTYPE_COLLECTION,
RTYPE_BASELINE
};
typedef struct {
svn_stringbuf_t *want_cdata;
svn_stringbuf_t *cdata;
apr_pool_t *pool;
apr_pool_t *scratchpool;
const char *base_href;
svn_revnum_t rev;
svn_boolean_t response_has_error;
int response_parent;
int href_parent;
svn_stringbuf_t *href;
int status;
enum merge_rtype rtype;
svn_stringbuf_t *vsn_name;
svn_stringbuf_t *vsn_url;
svn_stringbuf_t *committed_date;
svn_stringbuf_t *last_author;
svn_stringbuf_t *post_commit_err;
apr_hash_t *valid_targets;
svn_ra_push_wc_prop_func_t push_prop;
void *cb_baton;
} merge_ctx_t;
static void add_ignored(merge_ctx_t *mc, const char *cdata)
{
}
static svn_boolean_t okay_to_bump_path(const char *path,
apr_hash_t *valid_targets,
apr_pool_t *pool)
{
svn_stringbuf_t *parent_path;
enum svn_recurse_kind r;
if (apr_hash_get(valid_targets, path, APR_HASH_KEY_STRING))
return TRUE;
parent_path = svn_stringbuf_create(path, pool);
do {
apr_size_t len = parent_path->len;
svn_path_remove_component(parent_path);
if (len == parent_path->len)
break;
r = (enum svn_recurse_kind) apr_hash_get(valid_targets,
parent_path->data,
APR_HASH_KEY_STRING);
if (r == svn_recursive)
return TRUE;
} while (! svn_path_is_empty(parent_path->data));
return FALSE;
}
static svn_error_t *bump_resource(merge_ctx_t *mc,
const char *path,
char *vsn_url,
apr_pool_t *pool)
{
if (mc->push_prop == NULL)
return SVN_NO_ERROR;
if (! okay_to_bump_path(path, mc->valid_targets, pool))
return SVN_NO_ERROR;
{
svn_string_t vsn_url_str;
vsn_url_str.data = vsn_url;
vsn_url_str.len = strlen(vsn_url);
return (*mc->push_prop)(mc->cb_baton, path,
SVN_RA_NEON__LP_VSN_URL, &vsn_url_str,
pool);
}
}
static svn_error_t * handle_resource(merge_ctx_t *mc,
apr_pool_t *pool)
{
const char *relative;
if (mc->response_has_error)
{
return SVN_NO_ERROR;
}
if (mc->response_parent == ELEM_merged_set)
{
return svn_error_createf(APR_EGENERAL, NULL,
_("Protocol error: we told the server not to "
"auto-merge any resources, but it said that "
"'%s' was merged"), mc->href->data);
}
if (mc->response_parent != ELEM_updated_set)
{
return svn_error_createf(APR_EGENERAL, NULL,
_("Internal error: there is an unknown parent "
"(%d) for the 'DAV:response' element within"
" the MERGE response"), mc->response_parent);
}
#if 0
if (mc->href->len == 0
|| mc->vsn_name->len == 0
|| mc->vsn_url->len == 0
|| mc->rtype == RTYPE_UNKNOWN)
{
return svn_error_createf(APR_EGENERAL, NULL,
_("Protocol error: the MERGE response for the "
"'%s' resource did not return all of the "
"properties that we asked for (and need to "
"complete the commit)"), mc->href->data);
}
#endif
if (mc->rtype == RTYPE_BASELINE)
{
mc->rev = SVN_STR_TO_REV(mc->vsn_name->data);
return SVN_NO_ERROR;
}
if (! svn_path_is_ancestor(mc->base_href, mc->href->data))
{
return svn_error_createf(APR_EGENERAL, NULL,
_("A MERGE response for '%s' is not a child "
"of the destination ('%s')"),
mc->href->data, mc->base_href);
}
relative = svn_path_is_child(mc->base_href, mc->href->data, NULL);
if (! relative)
relative = "";
relative = svn_path_uri_decode(relative, pool);
return bump_resource(mc, relative, mc->vsn_url->data, pool);
}
static int validate_element(svn_ra_neon__xml_elmid parent,
svn_ra_neon__xml_elmid child)
{
if ((child == ELEM_collection || child == ELEM_baseline)
&& parent != ELEM_resourcetype) {
return SVN_RA_NEON__XML_INVALID;
}
switch (parent)
{
case ELEM_root:
if (child == ELEM_merge_response)
return child;
else
return SVN_RA_NEON__XML_INVALID;
case ELEM_merge_response:
if (child == ELEM_updated_set
|| child == ELEM_merged_set
|| child == ELEM_ignored_set)
return child;
else
return SVN_RA_NEON__XML_DECLINE;
case ELEM_updated_set:
case ELEM_merged_set:
if (child == ELEM_response)
return child;
else
return SVN_RA_NEON__XML_DECLINE;
case ELEM_ignored_set:
if (child == ELEM_href)
return child;
else
return SVN_RA_NEON__XML_DECLINE;
case ELEM_response:
if (child == ELEM_href
|| child == ELEM_status
|| child == ELEM_propstat)
return child;
else if (child == ELEM_responsedescription)
return SVN_RA_NEON__XML_DECLINE;
else
return SVN_RA_NEON__XML_DECLINE;
case ELEM_propstat:
if (child == ELEM_prop || child == ELEM_status)
return child;
else if (child == ELEM_responsedescription)
return SVN_RA_NEON__XML_DECLINE;
else
return SVN_RA_NEON__XML_DECLINE;
case ELEM_prop:
if (child == ELEM_checked_in
|| child == ELEM_resourcetype
|| child == ELEM_version_name
|| child == ELEM_creationdate
|| child == ELEM_creator_displayname
|| child == ELEM_post_commit_err
)
return child;
else
return SVN_RA_NEON__XML_DECLINE;
case ELEM_checked_in:
if (child == ELEM_href)
return child;
else
return SVN_RA_NEON__XML_DECLINE;
case ELEM_resourcetype:
if (child == ELEM_collection || child == ELEM_baseline)
return child;
else
return SVN_RA_NEON__XML_DECLINE;
default:
return SVN_RA_NEON__XML_DECLINE;
}
}
static svn_error_t *
start_element(int *elem, 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(merge_elements, nspace, name);
merge_ctx_t *mc = baton;
*elem = elm ? validate_element(parent, elm->id) : SVN_RA_NEON__XML_DECLINE;
if (*elem < 1)
return SVN_NO_ERROR;
switch (elm->id)
{
case ELEM_response:
mc->response_has_error = FALSE;
mc->rtype = RTYPE_UNKNOWN;
mc->href->len = 0;
mc->vsn_name->len = 0;
mc->vsn_url->len = 0;
case ELEM_ignored_set:
case ELEM_checked_in:
mc->href_parent = elm->id;
break;
case ELEM_updated_set:
case ELEM_merged_set:
mc->response_parent = elm->id;
break;
case ELEM_propstat:
mc->status = 0;
break;
case ELEM_resourcetype:
mc->rtype = RTYPE_REGULAR;
break;
case ELEM_collection:
mc->rtype = RTYPE_COLLECTION;
break;
case ELEM_baseline:
mc->rtype = RTYPE_BASELINE;
break;
default:
break;
}
switch (elm->id)
{
case ELEM_href:
case ELEM_status:
case ELEM_version_name:
case ELEM_post_commit_err:
case ELEM_creationdate:
case ELEM_creator_displayname:
mc->want_cdata = mc->cdata;
svn_stringbuf_setempty(mc->cdata);
break;
default:
mc->want_cdata = NULL;
break;
}
return SVN_NO_ERROR;
}
static svn_error_t *
end_element(void *baton, int state,
const char *nspace, const char *name)
{
merge_ctx_t *mc = baton;
switch (state)
{
case ELEM_href:
switch (mc->href_parent)
{
case ELEM_ignored_set:
add_ignored(mc, mc->cdata->data);
break;
case ELEM_response:
SVN_ERR(svn_ra_neon__copy_href(mc->href, mc->cdata->data,
mc->scratchpool));
break;
case ELEM_checked_in:
SVN_ERR(svn_ra_neon__copy_href(mc->vsn_url, mc->cdata->data,
mc->scratchpool));
break;
}
break;
case ELEM_responsedescription:
break;
case ELEM_status:
{
ne_status hs;
if (ne_parse_statusline(mc->cdata->data, &hs) != 0)
mc->response_has_error = TRUE;
else
{
mc->status = hs.code;
if (hs.code != 200)
{
mc->response_has_error = TRUE;
}
free(hs.reason_phrase);
}
if (mc->response_has_error)
{
return svn_error_create(APR_EGENERAL, NULL,
_("The MERGE property response had an "
"error status"));
}
}
break;
case ELEM_propstat:
if (mc->status == 200 )
{
}
break;
case ELEM_response:
{
SVN_ERR(handle_resource(mc, mc->scratchpool));
svn_pool_clear(mc->scratchpool);
}
break;
case ELEM_checked_in:
mc->href_parent = ELEM_response;
break;
case ELEM_version_name:
svn_stringbuf_set(mc->vsn_name, mc->cdata->data);
break;
case ELEM_post_commit_err:
svn_stringbuf_set(mc->post_commit_err, mc->cdata->data);
break;
case ELEM_creationdate:
svn_stringbuf_set(mc->committed_date, mc->cdata->data);
break;
case ELEM_creator_displayname:
svn_stringbuf_set(mc->last_author, mc->cdata->data);
break;
default:
break;
}
return SVN_NO_ERROR;
}
svn_error_t * svn_ra_neon__assemble_locktoken_body(svn_stringbuf_t **body,
apr_hash_t *lock_tokens,
apr_pool_t *pool)
{
apr_hash_index_t *hi;
apr_size_t buf_size;
const char *closing_tag = "</S:lock-token-list>";
apr_size_t closing_tag_size = strlen(closing_tag);
apr_pool_t *tmppool = svn_pool_create(pool);
apr_hash_t *xml_locks = apr_hash_make(tmppool);
svn_stringbuf_t *lockbuf = svn_stringbuf_create
("<S:lock-token-list xmlns:S=\"" SVN_XML_NAMESPACE "\">" DEBUG_CR, pool);
buf_size = lockbuf->len;
#define SVN_LOCK "<S:lock>" DEBUG_CR
#define SVN_LOCK_LEN sizeof(SVN_LOCK)-1
#define SVN_LOCK_CLOSE "</S:lock>" DEBUG_CR
#define SVN_LOCK_CLOSE_LEN sizeof(SVN_LOCK_CLOSE)-1
#define SVN_LOCK_PATH "<S:lock-path>"
#define SVN_LOCK_PATH_LEN sizeof(SVN_LOCK_PATH)-1
#define SVN_LOCK_PATH_CLOSE "</S:lock-path>" DEBUG_CR
#define SVN_LOCK_PATH_CLOSE_LEN sizeof(SVN_LOCK_CLOSE)-1
#define SVN_LOCK_TOKEN "<S:lock-token>"
#define SVN_LOCK_TOKEN_LEN sizeof(SVN_LOCK_TOKEN)-1
#define SVN_LOCK_TOKEN_CLOSE "</S:lock-token>" DEBUG_CR
#define SVN_LOCK_TOKEN_CLOSE_LEN sizeof(SVN_LOCK_TOKEN_CLOSE)-1
for (hi = apr_hash_first(tmppool, lock_tokens); hi; hi = apr_hash_next(hi))
{
const void *key;
void *val;
apr_ssize_t klen;
svn_string_t lock_path;
svn_stringbuf_t *lock_path_xml = NULL;
apr_hash_this(hi, &key, &klen, &val);
lock_path.data = key;
lock_path.len = klen;
svn_xml_escape_cdata_string(&lock_path_xml, &lock_path, tmppool);
apr_hash_set(xml_locks, lock_path_xml->data, lock_path_xml->len, val);
buf_size += SVN_LOCK_LEN;
buf_size += SVN_LOCK_PATH_LEN;
buf_size += lock_path_xml->len;
buf_size += SVN_LOCK_PATH_CLOSE_LEN;
buf_size += SVN_LOCK_TOKEN_LEN;
buf_size += strlen(val);
buf_size += SVN_LOCK_TOKEN_CLOSE_LEN;
buf_size += SVN_LOCK_CLOSE_LEN;
}
buf_size += closing_tag_size;
svn_stringbuf_ensure(lockbuf, buf_size + 1);
for (hi = apr_hash_first(tmppool, xml_locks); hi; hi = apr_hash_next(hi))
{
const void *key;
apr_ssize_t klen;
void *val;
apr_hash_this(hi, &key, &klen, &val);
svn_stringbuf_appendcstr(lockbuf, SVN_LOCK);
svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_PATH);
svn_stringbuf_appendbytes(lockbuf, key, klen);
svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_PATH_CLOSE);
svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_TOKEN);
svn_stringbuf_appendcstr(lockbuf, val);
svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_TOKEN_CLOSE);
svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_CLOSE);
}
svn_stringbuf_appendcstr(lockbuf, closing_tag);
#undef SVN_LOCK
#undef SVN_LOCK_LEN
#undef SVN_LOCK_CLOSE
#undef SVN_LOCK_CLOSE_LEN
#undef SVN_LOCK_PATH
#undef SVN_LOCK_PATH_LEN
#undef SVN_LOCK_PATH_CLOSE
#undef SVN_LOCK_PATH_CLOSE_LEN
#undef SVN_LOCK_TOKEN
#undef SVN_LOCK_TOKEN_LEN
#undef SVN_LOCK_TOKEN_CLOSE
#undef SVN_LOCK_TOKEN_CLOSE_LEN
*body = lockbuf;
svn_pool_destroy(tmppool);
return SVN_NO_ERROR;
}
svn_error_t * svn_ra_neon__merge_activity(svn_revnum_t *new_rev,
const char **committed_date,
const char **committed_author,
const char **post_commit_err,
svn_ra_neon__session_t *ras,
const char *repos_url,
const char *activity_url,
apr_hash_t *valid_targets,
apr_hash_t *lock_tokens,
svn_boolean_t keep_locks,
svn_boolean_t disable_merge_response,
apr_pool_t *pool)
{
merge_ctx_t mc = { 0 };
const char *body;
apr_hash_t *extra_headers = NULL;
svn_stringbuf_t *lockbuf = svn_stringbuf_create("", pool);
mc.cdata = svn_stringbuf_create("", pool);
mc.pool = pool;
mc.scratchpool = svn_pool_create(pool);
mc.base_href = repos_url;
mc.rev = SVN_INVALID_REVNUM;
mc.valid_targets = valid_targets;
mc.push_prop = ras->callbacks->push_wc_prop;
mc.cb_baton = ras->callback_baton;
mc.href = MAKE_BUFFER(pool);
mc.vsn_name = MAKE_BUFFER(pool);
mc.vsn_url = MAKE_BUFFER(pool);
mc.committed_date = MAKE_BUFFER(pool);
mc.last_author = MAKE_BUFFER(pool);
if (post_commit_err)
mc.post_commit_err = MAKE_BUFFER(pool);
if (disable_merge_response
|| (! keep_locks))
{
const char *value;
value = apr_psprintf(pool, "%s %s",
disable_merge_response ?
SVN_DAV_OPTION_NO_MERGE_RESPONSE : "",
keep_locks ?
"" : SVN_DAV_OPTION_RELEASE_LOCKS);
if (! extra_headers)
extra_headers = apr_hash_make(pool);
apr_hash_set(extra_headers, SVN_DAV_OPTIONS_HEADER, APR_HASH_KEY_STRING,
value);
}
if ((lock_tokens != NULL)
&& (apr_hash_count(lock_tokens) > 0))
SVN_ERR(svn_ra_neon__assemble_locktoken_body(&lockbuf, lock_tokens, pool));
body = apr_psprintf(pool,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<D:merge xmlns:D=\"DAV:\">"
"<D:source><D:href>%s</D:href></D:source>"
"<D:no-auto-merge/><D:no-checkout/>"
"<D:prop><D:checked-in/>"
"<D:" SVN_DAV__VERSION_NAME "/><D:resourcetype/>"
"<D:" SVN_DAV__CREATIONDATE "/><D:creator-displayname/>"
"</D:prop>"
"%s"
"</D:merge>",
activity_url, lockbuf->data);
SVN_ERR(svn_ra_neon__parsed_request(ras, "MERGE", repos_url,
body, 0, NULL,
start_element,
svn_ra_neon__xml_collect_cdata,
end_element, &mc, extra_headers,
NULL, FALSE, pool));
if (new_rev)
*new_rev = mc.rev;
if (committed_date)
*committed_date = mc.committed_date->len
? apr_pstrdup(pool, mc.committed_date->data) : NULL;
if (committed_author)
*committed_author = mc.last_author->len
? apr_pstrdup(pool, mc.last_author->data) : NULL;
if (post_commit_err)
*post_commit_err = mc.post_commit_err->len
? apr_pstrdup(pool, mc.post_commit_err->data) : NULL;
svn_pool_destroy(mc.scratchpool);
return NULL;
}