diff options
author | James McCoy <jamessan@debian.org> | 2018-07-31 22:26:52 -0400 |
---|---|---|
committer | James McCoy <jamessan@debian.org> | 2018-07-31 22:26:52 -0400 |
commit | e20a507113ff1126aeb4a97b806390ea377fe292 (patch) | |
tree | 0260b3a40387d7f994fbadaf22f1e9d3c080b09f /subversion/mod_dav_svn/deadprops.c | |
parent | c64debffb81d2fa17e9a72af7199ccf88b3cc556 (diff) |
New upstream version 1.10.2
Diffstat (limited to 'subversion/mod_dav_svn/deadprops.c')
-rw-r--r-- | subversion/mod_dav_svn/deadprops.c | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/subversion/mod_dav_svn/deadprops.c b/subversion/mod_dav_svn/deadprops.c new file mode 100644 index 0000000..3d79e86 --- /dev/null +++ b/subversion/mod_dav_svn/deadprops.c @@ -0,0 +1,815 @@ +/* + * deadprops.c: mod_dav_svn provider functions for "dead properties" + * (properties implemented by Subversion or its users, + * not as part of the WebDAV specification). + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_hash.h> + +#include <httpd.h> +#include <mod_dav.h> + +#include "svn_hash.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; + + /* 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; + + /* passed to svn_repos_ funcs that fetch revprops. */ + svn_repos_authz_func_t authz_read_func; + void *authz_read_baton; +}; + + +struct dav_deadprop_rollback { + int dummy; +}; + + +/* retrieve the "right" string to use as a repos path */ +static const char * +get_repos_path(struct dav_resource_private *info) +{ + return 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, + const char **repos_propname) +{ + if (strcmp(name->ns, SVN_DAV_PROP_NS_SVN) == 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_DAV_PROP_NS_CUSTOM) == 0) + { + /* the name of a custom prop is just the name -- no ns URI */ + *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 the repos-local name */ + get_repos_propname(db, name, &propname); + + if (propname == NULL) + { + /* we know these are not present. */ + *pvalue = NULL; + return NULL; + } + + /* If db->props exists, then use it to obtain property value. */ + if (db->props) + { + *pvalue = svn_hash_gets(db->props, propname); + return NULL; + } + + /* We've got three different types of properties (node, txn, and + revision), and we've got two different protocol versions to deal + with. Let's try to make some sense of this, shall we? + + HTTP v1: + working baseline ('wbl') resource -> txn prop change + non-working, baselined resource ('bln') -> rev prop change [*] + working, non-baselined resource ('wrk') -> node prop change + + HTTP v2: + transaction resource ('txn') -> txn prop change + revision resource ('rev') -> rev prop change + transaction root resource ('txr') -> node prop change + + [*] This is a violation of the DeltaV spec (### see issue #916). + + */ + + 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 svn_error_t * +change_txn_prop(svn_fs_txn_t *txn, + const char *propname, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + if (strcmp(propname, SVN_PROP_REVISION_AUTHOR) == 0) + return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + "Attempted to modify 'svn:author' property " + "on a transaction"); + + SVN_ERR(svn_repos_fs_change_txn_prop(txn, propname, value, scratch_pool)); + + return SVN_NO_ERROR; +} + + +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 the repos-local name */ + get_repos_propname(db, name, &propname); + + if (propname == NULL) + { + if (resource->info->repos->autoversioning) + /* ignore the unknown namespace of the incoming prop. */ + propname = name->name; + else + return dav_svn__new_error(db->p, HTTP_CONFLICT, 0, 0, + "Properties may only be defined in the " + SVN_DAV_PROP_NS_SVN " and " + SVN_DAV_PROP_NS_CUSTOM " namespaces."); + } + + /* We've got three different types of properties (node, txn, and + revision), and we've got two different protocol versions to deal + with. Let's try to make some sense of this, shall we? + + HTTP v1: + working baseline ('wbl') resource -> txn prop change + non-working, baselined resource ('bln') -> rev prop change [*] + working, non-baselined resource ('wrk') -> node prop change + + HTTP v2: + transaction resource ('txn') -> txn prop change + revision resource ('rev') -> rev prop change + transaction root resource ('txr') -> node prop change + + [*] This is a violation of the DeltaV spec (### see issue #916). + + */ + + /* A subpool to cope with mod_dav making multiple calls, e.g. during + PROPPATCH with multiple values. */ + subpool = svn_pool_create(resource->pool); + if (resource->baselined) + { + if (resource->working) + { + serr = 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); + + /* Prepare any hook failure message to get sent over the wire */ + 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); + + /* mod_dav doesn't handle the returned error very well, it + generates its own generic error that will be returned to + the client. Cache the detailed error here so that it can + be returned a second time when the rollback mechanism + triggers. */ + resource->info->revprop_error = svn_error_dup(purged_serr); + } + + /* Tell the logging subsystem about the revprop change. */ + 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 = 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); + + /* a change to the props was made; make sure our cached copy is gone */ + 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; + + /* Some resource types do not have deadprop databases. + Specifically: REGULAR, VERSION, WORKING, and our custom + transaction and transaction root 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 + && resource->info->restype != DAV_SVN_RESTYPE_TXN_COLLECTION + && resource->info->restype != DAV_SVN_RESTYPE_TXNROOT_COLLECTION)) + { + *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 + && resource->type != DAV_RESOURCE_TYPE_PRIVATE + && resource->info->restype != DAV_SVN_RESTYPE_TXN_COLLECTION) + { + /* ### Exception: in violation of deltaV, we *are* allowing a + baseline resource to receive a proppatch, as a way of + changing unversioned rev props. Remove this someday: see IZ #916. */ + if (! (resource->baselined + && resource->type == DAV_RESOURCE_TYPE_VERSION)) + return dav_svn__new_error(p, HTTP_CONFLICT, 0, 0, + "Properties may only be changed on working " + "resources."); + } + + db = apr_pcalloc(p, sizeof(*db)); + + db->resource = resource; + db->p = svn_pool_create(p); + + /* ### temp hack */ + db->work = svn_stringbuf_create_empty(db->p); + + /* make our path-based authz callback available to svn_repos_* funcs. */ + 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); + + /* ### use RO and node's mutable status to look for an error? */ + + *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); + + /* ### we don't have any other possible namespaces right now. */ + + 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; + + /* return whether the prop was found, then punt or handle it. */ + *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) + { + /* empty value. add an empty elem. */ + s = apr_psprintf(pool, "<%s%s/>" DEBUG_CR, prefix, name->name); + apr_text_append(pool, phdr, s); + } + else + { + /* add <prefix:name [V:encoding="base64"]>value</prefix:name> */ + const char *xml_safe; + const char *encoding = ""; + + /* Ensure XML-safety of our property values before sending them + across the wire. */ + 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); + + /* the value is in our pool which means it has the right lifetime. */ + /* ### at least, per the current mod_dav architecture/API */ + 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) +{ + /* we don't need a namespace mapping right now. nothing to do */ + 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; + + /* Default: no "encoding" attribute. */ + *absent = FALSE; + *out_propval_p = maybe_encoded_propval; + + /* Check for special encodings of the property value. */ + while (attr) + { + if (strcmp(attr->name, "encoding") == 0) /* ### namespace check? */ + { + const char *enc_type = attr->value; + + /* Handle known encodings here. */ + 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, 0, + "Unknown property encoding"); + break; + } + + if (strcmp(attr->name, SVN_DAV__OLD_VALUE__ABSENT) == 0) + { + /* ### parse attr->value */ + *absent = TRUE; + *out_propval_p = NULL; + } + + /* Next attribute, please. */ + 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; + + /* 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 = svn_string_create + (dav_xml_get_cdata(elem, pool, 0 /* strip_white */), pool); + + derr = decode_property_value(&propval, &absent, propval, elem, pool); + if (derr) + return derr; + + if (absent && ! elem->first_child) + /* ### better error check */ + return dav_svn__new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, + apr_psprintf(pool, + "'%s' cannot be specified on the " + "value without specifying an " + "expectation", + SVN_DAV__OLD_VALUE__ABSENT)); + + /* ### namespace check? */ + if (elem->first_child && !strcmp(elem->first_child->name, SVN_DAV__OLD_VALUE)) + { + /* Parse OLD_PROPVAL. */ + old_propval = svn_string_create(dav_xml_get_cdata(elem->first_child, pool, + 0 /* strip_white */), + 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 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; + + /* A subpool to cope with mod_dav making multiple calls, e.g. during + PROPPATCH with multiple values. */ + subpool = svn_pool_create(db->resource->pool); + + /* Working Baseline or Working (Version) Resource */ + if (db->resource->baselined) + if (db->resource->working) + serr = change_txn_prop(db->resource->info->root.txn, propname, + NULL, subpool); + else + /* ### VIOLATING deltaV: you can't proppatch a baseline, it's + not a working resource! But this is how we currently + (hackily) allow the svn client to change unversioned rev + props. See issue #916. */ + 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); + + /* a change to the props was made; make sure our cached copy is gone */ + 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 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_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); + + /* ### try and dispose of the value? */ + + 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 char *name = apr_hash_this_key(db->hi); + +#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 = 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) +{ + /* for operational logging */ + const char *action = NULL; + + /* 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 + { + 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 + { + serr = svn_fs_node_proplist(&db->props, + db->resource->info->root.root, + get_repos_path(db->resource->info), + db->p); + + if (! serr) + { + if (db->resource->collection) + 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); + } + + /* begin the iteration over the hash */ + db->hi = apr_hash_first(db->p, db->props); + + /* fetch the first key */ + get_name(db, pname); + + /* If we have a high-level action to log, do so. */ + 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) +{ + /* 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 * +db_get_rollback(dav_db *db, + const dav_prop_name *name, + dav_deadprop_rollback **prollback) +{ + /* This gets called by mod_dav in preparation for a revprop change. + mod_dav_svn doesn't need to make any changes during rollback, but + we want the rollback mechanism to trigger. Making changes in + response to post-revprop-change hook errors would be positively + wrong. */ + + *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; + + /* Returning the original revprop change error here will cause this + detailed error to get returned to the client in preference to the + more generic error created by mod_dav. */ + 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, +}; |