#include <apr_hash.h>
#include <httpd.h>
#include <mod_dav.h>
#include "svn_xml.h"
#include "svn_pools.h"
#include "svn_dav.h"
#include "svn_base64.h"
#include "svn_props.h"
#include "private/svn_log.h"
#include "dav_svn.h"
struct dav_db {
const dav_resource *resource;
apr_pool_t *p;
apr_hash_t *props;
apr_hash_index_t *hi;
svn_stringbuf_t *work;
svn_repos_authz_func_t authz_read_func;
void *authz_read_baton;
};
struct dav_deadprop_rollback {
int dummy;
};
static const char *
get_repos_path(struct dav_resource_private *info)
{
return info->repos_path;
}
static void
get_repos_propname(dav_db *db,
const dav_prop_name *name,
const char **repos_propname)
{
if (strcmp(name->ns, SVN_DAV_PROP_NS_SVN) == 0)
{
svn_stringbuf_set(db->work, SVN_PROP_PREFIX);
svn_stringbuf_appendcstr(db->work, name->name);
*repos_propname = db->work->data;
}
else if (strcmp(name->ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
{
*repos_propname = name->name;
}
else
{
*repos_propname = NULL;
}
}
static dav_error *
get_value(dav_db *db, const dav_prop_name *name, svn_string_t **pvalue)
{
const char *propname;
svn_error_t *serr;
get_repos_propname(db, name, &propname);
if (propname == NULL)
{
*pvalue = NULL;
return NULL;
}
if (db->resource->baselined)
{
if (db->resource->type == DAV_RESOURCE_TYPE_WORKING)
serr = svn_fs_txn_prop(pvalue, db->resource->info->root.txn,
propname, db->p);
else
serr = svn_repos_fs_revision_prop(pvalue,
db->resource->info-> repos->repos,
db->resource->info->root.rev,
propname, db->authz_read_func,
db->authz_read_baton, db->p);
}
else if (db->resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION)
{
serr = svn_fs_txn_prop(pvalue, db->resource->info->root.txn,
propname, db->p);
}
else
{
serr = svn_fs_node_prop(pvalue, db->resource->info->root.root,
get_repos_path(db->resource->info),
propname, db->p);
}
if (serr != NULL)
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not fetch a property",
db->resource->pool);
return NULL;
}
static dav_error *
save_value(dav_db *db, const dav_prop_name *name,
const svn_string_t *const *old_value_p,
const svn_string_t *value)
{
const char *propname;
svn_error_t *serr;
const dav_resource *resource = db->resource;
apr_pool_t *subpool;
get_repos_propname(db, name, &propname);
if (propname == NULL)
{
if (db->resource->info->repos->autoversioning)
propname = name->name;
else
return dav_svn__new_error(db->p, HTTP_CONFLICT, 0,
"Properties may only be defined in the "
SVN_DAV_PROP_NS_SVN " and "
SVN_DAV_PROP_NS_CUSTOM " namespaces.");
}
subpool = svn_pool_create(db->resource->pool);
if (db->resource->baselined)
{
if (db->resource->working)
{
serr = svn_repos_fs_change_txn_prop(resource->info->root.txn,
propname, value,
subpool);
}
else
{
serr = svn_repos_fs_change_rev_prop4(resource->info->repos->repos,
resource->info->root.rev,
resource->info->repos->username,
propname, old_value_p, value,
TRUE, TRUE,
db->authz_read_func,
db->authz_read_baton,
subpool);
if (serr)
{
svn_error_t *purged_serr = svn_error_purge_tracing(serr);
if (purged_serr->apr_err == SVN_ERR_REPOS_HOOK_FAILURE)
purged_serr->message = apr_xml_quote_string
(purged_serr->pool,
purged_serr->message, 1);
resource->info->revprop_error = svn_error_dup(purged_serr);
}
dav_svn__operational_log(resource->info,
svn_log__change_rev_prop(
resource->info->root.rev,
propname, subpool));
}
}
else if (resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION)
{
serr = svn_repos_fs_change_txn_prop(resource->info->root.txn,
propname, value, subpool);
}
else
{
serr = svn_repos_fs_change_node_prop(resource->info->root.root,
get_repos_path(resource->info),
propname, value, subpool);
}
svn_pool_destroy(subpool);
if (serr != NULL)
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
NULL, resource->pool);
db->props = NULL;
return NULL;
}
static dav_error *
db_open(apr_pool_t *p,
const dav_resource *resource,
int ro,
dav_db **pdb)
{
dav_db *db;
dav_svn__authz_read_baton *arb;
if (resource->type == DAV_RESOURCE_TYPE_HISTORY
|| resource->type == DAV_RESOURCE_TYPE_ACTIVITY
|| (resource->type == DAV_RESOURCE_TYPE_PRIVATE
&& resource->info->restype != DAV_SVN_RESTYPE_TXN_COLLECTION
&& resource->info->restype != DAV_SVN_RESTYPE_TXNROOT_COLLECTION))
{
*pdb = NULL;
return NULL;
}
if ((! ro)
&& resource->type != DAV_RESOURCE_TYPE_WORKING
&& resource->type != DAV_RESOURCE_TYPE_PRIVATE
&& resource->info->restype != DAV_SVN_RESTYPE_TXN_COLLECTION)
{
if (! (resource->baselined
&& resource->type == DAV_RESOURCE_TYPE_VERSION))
return dav_svn__new_error(p, HTTP_CONFLICT, 0,
"Properties may only be changed on working "
"resources.");
}
db = apr_pcalloc(p, sizeof(*db));
db->resource = resource;
db->p = svn_pool_create(p);
db->work = svn_stringbuf_ncreate("", 0, db->p);
arb = apr_pcalloc(p, sizeof(*arb));
arb->r = resource->info->r;
arb->repos = resource->info->repos;
db->authz_read_baton = arb;
db->authz_read_func = dav_svn__authz_read_func(arb);
*pdb = db;
return NULL;
}
static void
db_close(dav_db *db)
{
svn_pool_destroy(db->p);
}
static dav_error *
db_define_namespaces(dav_db *db, dav_xmlns_info *xi)
{
dav_xmlns_add(xi, "S", SVN_DAV_PROP_NS_SVN);
dav_xmlns_add(xi, "C", SVN_DAV_PROP_NS_CUSTOM);
dav_xmlns_add(xi, "V", SVN_DAV_PROP_NS_DAV);
return NULL;
}
static dav_error *
db_output_value(dav_db *db,
const dav_prop_name *name,
dav_xmlns_info *xi,
apr_text_header *phdr,
int *found)
{
const char *prefix;
const char *s;
svn_string_t *propval;
dav_error *err;
apr_pool_t *pool = db->resource->pool;
if ((err = get_value(db, name, &propval)) != NULL)
return err;
*found = (propval != NULL);
if (propval == NULL)
return NULL;
if (strcmp(name->ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
prefix = "C:";
else
prefix = "S:";
if (propval->len == 0)
{
s = apr_psprintf(pool, "<%s%s/>" DEBUG_CR, prefix, name->name);
apr_text_append(pool, phdr, s);
}
else
{
const char *xml_safe;
const char *encoding = "";
if (! svn_xml_is_xml_safe(propval->data, propval->len))
{
const svn_string_t *enc_propval
= svn_base64_encode_string2(propval, TRUE, pool);
xml_safe = enc_propval->data;
encoding = " V:encoding=\"base64\"";
}
else
{
svn_stringbuf_t *xmlval = NULL;
svn_xml_escape_cdata_string(&xmlval, propval, pool);
xml_safe = xmlval->data;
}
s = apr_psprintf(pool, "<%s%s%s>", prefix, name->name, encoding);
apr_text_append(pool, phdr, s);
apr_text_append(pool, phdr, xml_safe);
s = apr_psprintf(pool, "</%s%s>" DEBUG_CR, prefix, name->name);
apr_text_append(pool, phdr, s);
}
return NULL;
}
static dav_error *
db_map_namespaces(dav_db *db,
const apr_array_header_t *namespaces,
dav_namespace_map **mapping)
{
return NULL;
}
static dav_error *
decode_property_value(const svn_string_t **out_propval_p,
svn_boolean_t *absent,
const svn_string_t *maybe_encoded_propval,
const apr_xml_elem *elem,
apr_pool_t *pool)
{
apr_xml_attr *attr = elem->attr;
*absent = FALSE;
*out_propval_p = maybe_encoded_propval;
while (attr)
{
if (strcmp(attr->name, "encoding") == 0)
{
const char *enc_type = attr->value;
if (enc_type && (strcmp(enc_type, "base64") == 0))
*out_propval_p = svn_base64_decode_string(maybe_encoded_propval,
pool);
else
return dav_svn__new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0,
"Unknown property encoding");
break;
}
if (strcmp(attr->name, SVN_DAV__OLD_VALUE__ABSENT) == 0)
{
*absent = TRUE;
*out_propval_p = NULL;
}
attr = attr->next;
}
return NULL;
}
static dav_error *
db_store(dav_db *db,
const dav_prop_name *name,
const apr_xml_elem *elem,
dav_namespace_map *mapping)
{
const svn_string_t *const *old_propval_p;
const svn_string_t *old_propval;
const svn_string_t *propval;
svn_boolean_t absent;
apr_pool_t *pool = db->p;
dav_error *derr;
propval = svn_string_create
(dav_xml_get_cdata(elem, pool, 0 ), pool);
derr = decode_property_value(&propval, &absent, propval, elem, pool);
if (derr)
return derr;
if (absent && ! elem->first_child)
return dav_svn__new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0,
apr_psprintf(pool,
"'%s' cannot be specified on the "
"value without specifying an "
"expectation",
SVN_DAV__OLD_VALUE__ABSENT));
if (elem->first_child && !strcmp(elem->first_child->name, SVN_DAV__OLD_VALUE))
{
const char *propname;
get_repos_propname(db, name, &propname);
old_propval = svn_string_create(dav_xml_get_cdata(elem->first_child, pool,
0 ),
pool);
derr = decode_property_value(&old_propval, &absent,
old_propval, elem->first_child, pool);
if (derr)
return derr;
old_propval_p = (const svn_string_t *const *) &old_propval;
}
else
old_propval_p = NULL;
return save_value(db, name, old_propval_p, propval);
}
static dav_error *
db_remove(dav_db *db, const dav_prop_name *name)
{
svn_error_t *serr;
const char *propname;
apr_pool_t *subpool;
get_repos_propname(db, name, &propname);
if (propname == NULL)
return NULL;
subpool = svn_pool_create(db->resource->pool);
if (db->resource->baselined)
if (db->resource->working)
serr = svn_repos_fs_change_txn_prop(db->resource->info->root.txn,
propname, NULL, subpool);
else
serr = svn_repos_fs_change_rev_prop4(db->resource->info->repos->repos,
db->resource->info->root.rev,
db->resource->info->repos->username,
propname, NULL, NULL, TRUE, TRUE,
db->authz_read_func,
db->authz_read_baton,
subpool);
else
serr = svn_repos_fs_change_node_prop(db->resource->info->root.root,
get_repos_path(db->resource->info),
propname, NULL, subpool);
svn_pool_destroy(subpool);
if (serr != NULL)
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not remove a property",
db->resource->pool);
db->props = NULL;
return NULL;
}
static int
db_exists(dav_db *db, const dav_prop_name *name)
{
const char *propname;
svn_string_t *propval;
svn_error_t *serr;
int retval;
get_repos_propname(db, name, &propname);
if (propname == NULL)
return 0;
if (db->resource->baselined)
if (db->resource->type == DAV_RESOURCE_TYPE_WORKING)
serr = svn_fs_txn_prop(&propval, db->resource->info->root.txn,
propname, db->p);
else
serr = svn_repos_fs_revision_prop(&propval,
db->resource->info->repos->repos,
db->resource->info->root.rev,
propname,
db->authz_read_func,
db->authz_read_baton, db->p);
else
serr = svn_fs_node_prop(&propval, db->resource->info->root.root,
get_repos_path(db->resource->info),
propname, db->p);
retval = (serr == NULL && propval != NULL);
svn_error_clear(serr);
return retval;
}
static void get_name(dav_db *db, dav_prop_name *pname)
{
if (db->hi == NULL)
{
pname->ns = pname->name = NULL;
}
else
{
const void *name;
apr_hash_this(db->hi, &name, NULL, NULL);
#define PREFIX_LEN (sizeof(SVN_PROP_PREFIX) - 1)
if (strncmp(name, SVN_PROP_PREFIX, PREFIX_LEN) == 0)
#undef PREFIX_LEN
{
pname->ns = SVN_DAV_PROP_NS_SVN;
pname->name = (const char *)name + 4;
}
else
{
pname->ns = SVN_DAV_PROP_NS_CUSTOM;
pname->name = name;
}
}
}
static dav_error *
db_first_name(dav_db *db, dav_prop_name *pname)
{
const char *action = NULL;
if (db->props == NULL)
{
svn_error_t *serr;
if (db->resource->baselined)
{
if (db->resource->type == DAV_RESOURCE_TYPE_WORKING)
serr = svn_fs_txn_proplist(&db->props,
db->resource->info->root.txn,
db->p);
else
{
action = svn_log__rev_proplist(db->resource->info->root.rev,
db->resource->pool);
serr = svn_repos_fs_revision_proplist
(&db->props,
db->resource->info->repos->repos,
db->resource->info->root.rev,
db->authz_read_func,
db->authz_read_baton,
db->p);
}
}
else
{
svn_node_kind_t kind;
serr = svn_fs_node_proplist(&db->props,
db->resource->info->root.root,
get_repos_path(db->resource->info),
db->p);
if (! serr)
serr = svn_fs_check_path(&kind, db->resource->info->root.root,
get_repos_path(db->resource->info),
db->p);
if (! serr)
{
if (kind == svn_node_dir)
action = svn_log__get_dir(db->resource->info->repos_path,
db->resource->info->root.rev,
FALSE, TRUE, 0, db->resource->pool);
else
action = svn_log__get_file(db->resource->info->repos_path,
db->resource->info->root.rev,
FALSE, TRUE, db->resource->pool);
}
}
if (serr != NULL)
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not begin sequencing through "
"properties",
db->resource->pool);
}
db->hi = apr_hash_first(db->p, db->props);
get_name(db, pname);
if (action != NULL)
dav_svn__operational_log(db->resource->info, action);
return NULL;
}
static dav_error *
db_next_name(dav_db *db, dav_prop_name *pname)
{
if (db->hi != NULL)
db->hi = apr_hash_next(db->hi);
get_name(db, pname);
return NULL;
}
static dav_error *
db_get_rollback(dav_db *db,
const dav_prop_name *name,
dav_deadprop_rollback **prollback)
{
*prollback = apr_palloc(db->p, sizeof(dav_deadprop_rollback));
return NULL;
}
static dav_error *
db_apply_rollback(dav_db *db, dav_deadprop_rollback *rollback)
{
dav_error *derr;
if (! db->resource->info->revprop_error)
return NULL;
derr = dav_svn__convert_err(db->resource->info->revprop_error,
HTTP_INTERNAL_SERVER_ERROR, NULL,
db->resource->pool);
db->resource->info->revprop_error = NULL;
return derr;
}
const dav_hooks_propdb dav_svn__hooks_propdb = {
db_open,
db_close,
db_define_namespaces,
db_output_value,
db_map_namespaces,
db_store,
db_remove,
db_exists,
db_first_name,
db_next_name,
db_get_rollback,
db_apply_rollback,
};