blob: bfbc018db2e3a7523c46bfb4f80c4cf8092710df [file] [log] [blame]
/*
* deadprops.c: mod_dav_svn dead property provider functions for Subversion
*
* ====================================================================
* 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/.
* ====================================================================
*/
#include <httpd.h>
#include <mod_dav.h>
#include <apr_hash.h>
#include "dav_svn.h"
#include "svn_xml.h"
struct dav_db {
const dav_resource *resource;
apr_pool_t *p;
/* the resource's properties that we are sequencing over */
apr_hash_t *props;
apr_hash_index_t *hi;
/* used for constructing repos-local names for properties */
svn_stringbuf_t *work;
};
struct dav_deadprop_rollback {
dav_prop_name name;
svn_string_t value;
};
/* retrieve the "right" string to use as a repos path */
static const char *get_repos_path (struct dav_resource_private *info)
{
return info->node_id_str ? info->node_id_str : info->repos_path;
}
/* construct the repos-local name for the given DAV property name */
static void get_repos_propname(dav_db *db, const dav_prop_name *name,
char **repos_propname)
{
if (strcmp(name->ns, SVN_PROP_PREFIX) == 0)
{
/* recombine the namespace ("svn:") and the name. */
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_PROP_CUSTOM_PREFIX) == 0)
{
/* the name of a custom prop is just the name -- no ns URI */
*repos_propname = (char *)name->name;
}
else
{
*repos_propname = NULL;
}
}
static dav_error *get_value(dav_db *db, const dav_prop_name *name,
svn_string_t **pvalue)
{
char *propname;
svn_error_t *serr;
/* get the repos-local name */
get_repos_propname(db, name, &propname);
/* ### disallow arbitrary, non-SVN properties. this effectively shuts
### off arbitrary DeltaV clients for now. */
if (propname == NULL)
{
/* we know these are not present. */
*pvalue = NULL;
return NULL;
}
/* ### if db->props exists, then try in there first */
/* Working Baseline, Baseline, or (Working) Version resource */
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_fs_revision_prop(pvalue, db->resource->info->repos->fs,
db->resource->info->root.rev,
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");
return NULL;
}
static dav_error *save_value(dav_db *db, const dav_prop_name *name,
const svn_string_t *value)
{
char *propname;
svn_error_t *serr;
/* get the repos-local name */
get_repos_propname(db, name, &propname);
/* ### disallow arbitrary, non-SVN properties. this effectively shuts
### off arbitrary DeltaV clients for now. */
if (propname == NULL)
return dav_new_error(db->p, HTTP_CONFLICT, 0,
"Properties may only be defined in the "
SVN_PROP_PREFIX " and " SVN_PROP_CUSTOM_PREFIX
" namespaces.");
/* Working Baseline or Working (Version) Resource */
if (db->resource->baselined)
serr = svn_fs_change_txn_prop(db->resource->info->root.txn,
propname, value, db->resource->pool);
else
serr = svn_fs_change_node_prop(db->resource->info->root.root,
get_repos_path(db->resource->info),
propname, value, db->resource->pool);
if (serr != NULL)
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not change a property");
/* a change to the props was made; make sure our cached copy is gone */
db->props = NULL;
return NULL;
}
static dav_error *dav_svn_db_open(apr_pool_t *p, const dav_resource *resource,
int ro, dav_db **pdb)
{
dav_db *db;
/* Some resource types do not have deadprop databases. Specifically:
REGULAR, VERSION, and WORKING resources have them. (SVN does not
have WORKSPACE resources, and isn't covered here) */
if (resource->type == DAV_RESOURCE_TYPE_HISTORY
|| resource->type == DAV_RESOURCE_TYPE_ACTIVITY
|| resource->type == DAV_RESOURCE_TYPE_PRIVATE)
{
*pdb = NULL;
return NULL;
}
/* If the DB is being opened R/W, and this isn't a working resource, then
we have a problem! */
if (!ro && resource->type != DAV_RESOURCE_TYPE_WORKING)
{
return dav_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 = p;
/* ### temp hack */
db->work = svn_stringbuf_ncreate("", 0, p);
/* ### use RO and node's mutable status to look for an error? */
*pdb = db;
return NULL;
}
static void dav_svn_db_close(dav_db *db)
{
/* nothing to do */
}
static dav_error *dav_svn_db_define_namespaces(dav_db *db, dav_xmlns_info *xi)
{
dav_xmlns_add(xi, "S", SVN_PROP_PREFIX);
dav_xmlns_add(xi, "C", SVN_PROP_CUSTOM_PREFIX);
/* ### we don't have any other possible namespaces right now. */
return NULL;
}
static dav_error *dav_svn_db_output_value(dav_db *db,
const dav_prop_name *name,
dav_xmlns_info *xi,
apr_text_header *phdr, int *found)
{
svn_string_t *propval;
svn_stringbuf_t *xmlsafe = NULL;
const char *prefix;
const char *s;
dav_error *err;
if ((err = get_value(db, name, &propval)) != NULL)
return err;
/* return whether the prop was found, then punt or handle it. */
*found = propval != NULL;
if (propval == NULL)
return NULL;
/* XML-escape our properties before sending them across the wire. */
svn_xml_escape_string(&xmlsafe, propval, db->p);
if (strcmp(name->ns, SVN_PROP_CUSTOM_PREFIX) == 0)
prefix = "C:";
else
prefix = "S:";
if (xmlsafe->len == 0)
{
/* empty value. add an empty elem. */
s = apr_psprintf(db->p, "<%s%s/>" DEBUG_CR, prefix, name->name);
apr_text_append(db->p, phdr, s);
}
else
{
/* add <prefix:name>value</prefix:name> */
s = apr_psprintf(db->p, "<%s%s>", prefix, name->name);
apr_text_append(db->p, phdr, s);
/* the value is in our pool which means it has the right lifetime. */
/* ### at least, per the current mod_dav architecture/API */
/* ### oops. apr_text is not binary-safe */
apr_text_append(db->p, phdr, xmlsafe->data);
s = apr_psprintf(db->p, "</%s%s>" DEBUG_CR, prefix, name->name);
apr_text_append(db->p, phdr, s);
}
return NULL;
}
static dav_error *dav_svn_db_map_namespaces(
dav_db *db,
const apr_array_header_t *namespaces,
dav_namespace_map **mapping)
{
/* we don't need a namespace mapping right now. nothing to do */
return NULL;
}
static dav_error *dav_svn_db_store(dav_db *db, const dav_prop_name *name,
const apr_xml_elem *elem,
dav_namespace_map *mapping)
{
svn_string_t propval;
/* ### oops. apr_xml is busted: it doesn't allow for binary data at the
### moment. thankfully, we aren't using binary props yet. */
/* SVN sends property values as a big blob of bytes. Thus, there should be
no child elements of the property-name element. That also means that
the entire contents of the blob is located in elem->first_cdata. The
dav_xml_get_cdata() will figure it all out for us, but (normally) it
should be awfully fast and not need to copy any data. */
propval.data = dav_xml_get_cdata(elem, db->p, 0 /* strip_white */);
propval.len = strlen(propval.data);
return save_value(db, name, &propval);
}
static dav_error *dav_svn_db_remove(dav_db *db, const dav_prop_name *name)
{
svn_error_t *serr;
char *propname;
/* get the repos-local name */
get_repos_propname(db, name, &propname);
/* ### non-svn props aren't in our repos, so punt for now */
if (propname == NULL)
return NULL;
/* Working Baseline or Working (Version) Resource */
if (db->resource->baselined)
serr = svn_fs_change_txn_prop(db->resource->info->root.txn,
propname, NULL, db->resource->pool);
else
serr = svn_fs_change_node_prop(db->resource->info->root.root,
get_repos_path(db->resource->info),
propname, NULL, db->resource->pool);
if (serr != NULL)
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not remove a property");
/* a change to the props was made; make sure our cached copy is gone */
db->props = NULL;
return NULL;
}
static int dav_svn_db_exists(dav_db *db, const dav_prop_name *name)
{
char *propname;
svn_string_t *propval;
svn_error_t *serr;
/* get the repos-local name */
get_repos_propname(db, name, &propname);
/* ### non-svn props aren't in our repos */
if (propname == NULL)
return 0;
/* Working Baseline, Baseline, or (Working) Version resource */
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_fs_revision_prop(&propval, db->resource->info->repos->fs,
db->resource->info->root.rev,
propname, db->p);
else
serr = svn_fs_node_prop(&propval, db->resource->info->root.root,
get_repos_path(db->resource->info),
propname, db->p);
/* ### try and dispose of the value? */
return serr == NULL && propval != NULL;
}
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)
{
pname->ns = SVN_PROP_PREFIX;
pname->name = (const char *)name + 4;
}
else
{
pname->ns = SVN_PROP_CUSTOM_PREFIX;
pname->name = name;
}
}
}
static dav_error *dav_svn_db_first_name(dav_db *db, dav_prop_name *pname)
{
/* if we don't have a copy of the properties, then get one */
if (db->props == NULL)
{
svn_error_t *serr;
/* Working Baseline, Baseline, or (Working) Version resource */
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
serr = svn_fs_revision_proplist(&db->props,
db->resource->info->repos->fs,
db->resource->info->root.rev, db->p);
else
serr = svn_fs_node_proplist(&db->props, db->resource->info->root.root,
get_repos_path(db->resource->info), db->p);
if (serr != NULL)
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not begin sequencing through "
"properties");
}
/* begin the iteration over the hash */
db->hi = apr_hash_first(db->p, db->props);
/* fetch the first key */
get_name(db, pname);
return NULL;
}
static dav_error *dav_svn_db_next_name(dav_db *db, dav_prop_name *pname)
{
/* skip to the next hash entry */
if (db->hi != NULL)
db->hi = apr_hash_next(db->hi);
/* fetch the key */
get_name(db, pname);
return NULL;
}
static dav_error *dav_svn_db_get_rollback(dav_db *db, const dav_prop_name *name,
dav_deadprop_rollback **prollback)
{
dav_error *err;
dav_deadprop_rollback *ddp;
svn_string_t *propval;
if ((err = get_value(db, name, &propval)) != NULL)
return err;
ddp = apr_palloc(db->p, sizeof(*ddp));
ddp->name = *name;
ddp->value.data = propval ? propval->data : NULL;
ddp->value.len = propval ? propval->len : 0;
*prollback = ddp;
return NULL;
}
static dav_error *dav_svn_db_apply_rollback(dav_db *db,
dav_deadprop_rollback *rollback)
{
if (rollback->value.data == NULL)
{
return dav_svn_db_remove(db, &rollback->name);
}
return save_value(db, &rollback->name, &rollback->value);
}
const dav_hooks_propdb dav_svn_hooks_propdb = {
dav_svn_db_open,
dav_svn_db_close,
dav_svn_db_define_namespaces,
dav_svn_db_output_value,
dav_svn_db_map_namespaces,
dav_svn_db_store,
dav_svn_db_remove,
dav_svn_db_exists,
dav_svn_db_first_name,
dav_svn_db_next_name,
dav_svn_db_get_rollback,
dav_svn_db_apply_rollback,
};
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/