Change property listing action in the SVN-ACTION DAV logging names to
[svn.git] / subversion / mod_dav_svn / deadprops.c
blob4f367e0554ed7b3042260670943eccf319afcacc
1 /*
2 * deadprops.c: mod_dav_svn dead property provider functions for Subversion
4 * ====================================================================
5 * Copyright (c) 2000-2004 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 #include <apr_hash.h>
21 #include <httpd.h>
22 #include <mod_dav.h>
24 #include "svn_xml.h"
25 #include "svn_pools.h"
26 #include "svn_dav.h"
27 #include "svn_base64.h"
28 #include "svn_props.h"
30 #include "dav_svn.h"
33 struct dav_db {
34 const dav_resource *resource;
35 apr_pool_t *p;
37 /* the resource's properties that we are sequencing over */
38 apr_hash_t *props;
39 apr_hash_index_t *hi;
41 /* used for constructing repos-local names for properties */
42 svn_stringbuf_t *work;
44 /* passed to svn_repos_ funcs that fetch revprops. */
45 svn_repos_authz_func_t authz_read_func;
46 void *authz_read_baton;
50 struct dav_deadprop_rollback {
51 dav_prop_name name;
52 svn_string_t value;
56 /* retrieve the "right" string to use as a repos path */
57 static const char *
58 get_repos_path(struct dav_resource_private *info)
60 return info->repos_path;
64 /* construct the repos-local name for the given DAV property name */
65 static void
66 get_repos_propname(dav_db *db,
67 const dav_prop_name *name,
68 const char **repos_propname)
70 if (strcmp(name->ns, SVN_DAV_PROP_NS_SVN) == 0)
72 /* recombine the namespace ("svn:") and the name. */
73 svn_stringbuf_set(db->work, SVN_PROP_PREFIX);
74 svn_stringbuf_appendcstr(db->work, name->name);
75 *repos_propname = db->work->data;
77 else if (strcmp(name->ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
79 /* the name of a custom prop is just the name -- no ns URI */
80 *repos_propname = name->name;
82 else
84 *repos_propname = NULL;
89 static dav_error *
90 get_value(dav_db *db, const dav_prop_name *name, svn_string_t **pvalue)
92 const char *propname;
93 svn_error_t *serr;
95 /* get the repos-local name */
96 get_repos_propname(db, name, &propname);
98 if (propname == NULL)
100 /* we know these are not present. */
101 *pvalue = NULL;
102 return NULL;
105 /* ### if db->props exists, then try in there first */
107 /* Working Baseline, Baseline, or (Working) Version resource */
108 if (db->resource->baselined)
109 if (db->resource->type == DAV_RESOURCE_TYPE_WORKING)
110 serr = svn_fs_txn_prop(pvalue, db->resource->info->root.txn,
111 propname, db->p);
112 else
113 serr = svn_repos_fs_revision_prop(pvalue,
114 db->resource->info-> repos->repos,
115 db->resource->info->root.rev, propname,
116 db->authz_read_func,
117 db->authz_read_baton, db->p);
118 else
119 serr = svn_fs_node_prop(pvalue, db->resource->info->root.root,
120 get_repos_path(db->resource->info),
121 propname, db->p);
122 if (serr != NULL)
123 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
124 "could not fetch a property",
125 db->resource->pool);
127 return NULL;
131 static dav_error *
132 save_value(dav_db *db, const dav_prop_name *name, const svn_string_t *value)
134 const char *propname;
135 svn_error_t *serr;
137 /* get the repos-local name */
138 get_repos_propname(db, name, &propname);
140 if (propname == NULL)
142 if (db->resource->info->repos->autoversioning)
143 /* ignore the unknown namespace of the incoming prop. */
144 propname = name->name;
145 else
146 return dav_new_error(db->p, HTTP_CONFLICT, 0,
147 "Properties may only be defined in the "
148 SVN_DAV_PROP_NS_SVN " and " SVN_DAV_PROP_NS_CUSTOM
149 " namespaces.");
152 /* Working Baseline or Working (Version) Resource */
153 if (db->resource->baselined)
154 if (db->resource->working)
155 serr = svn_repos_fs_change_txn_prop(db->resource->info->root.txn,
156 propname, value, db->resource->pool);
157 else
159 /* ### VIOLATING deltaV: you can't proppatch a baseline, it's
160 not a working resource! But this is how we currently
161 (hackily) allow the svn client to change unversioned rev
162 props. See issue #916. */
163 serr = svn_repos_fs_change_rev_prop3
164 (db->resource->info->repos->repos,
165 db->resource->info->root.rev,
166 db->resource->info->repos->username,
167 propname, value, TRUE, TRUE,
168 db->authz_read_func,
169 db->authz_read_baton,
170 db->resource->pool);
172 /* Tell the logging subsystem about the revprop change. */
173 dav_svn__operational_log(db->resource->info,
174 apr_psprintf(db->resource->pool,
175 "change-rev-prop r%ld %s",
176 db->resource->info->root.rev,
177 svn_path_uri_encode(propname,
178 db->resource->pool)));
180 else
181 serr = svn_repos_fs_change_node_prop(db->resource->info->root.root,
182 get_repos_path(db->resource->info),
183 propname, value, db->resource->pool);
184 if (serr != NULL)
185 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
186 NULL,
187 db->resource->pool);
189 /* a change to the props was made; make sure our cached copy is gone */
190 db->props = NULL;
192 return NULL;
196 static dav_error *
197 db_open(apr_pool_t *p,
198 const dav_resource *resource,
199 int ro,
200 dav_db **pdb)
202 dav_db *db;
203 dav_svn__authz_read_baton *arb;
205 /* Some resource types do not have deadprop databases. Specifically:
206 REGULAR, VERSION, and WORKING resources have them. (SVN does not
207 have WORKSPACE resources, and isn't covered here) */
208 if (resource->type == DAV_RESOURCE_TYPE_HISTORY
209 || resource->type == DAV_RESOURCE_TYPE_ACTIVITY
210 || resource->type == DAV_RESOURCE_TYPE_PRIVATE)
212 *pdb = NULL;
213 return NULL;
216 /* If the DB is being opened R/W, and this isn't a working resource, then
217 we have a problem! */
218 if (!ro && resource->type != DAV_RESOURCE_TYPE_WORKING)
220 /* ### Exception: in violation of deltaV, we *are* allowing a
221 baseline resource to receive a proppatch, as a way of
222 changing unversioned rev props. Remove this someday: see IZ #916. */
223 if (! (resource->baselined
224 && resource->type == DAV_RESOURCE_TYPE_VERSION))
225 return dav_new_error(p, HTTP_CONFLICT, 0,
226 "Properties may only be changed on working "
227 "resources.");
230 db = apr_pcalloc(p, sizeof(*db));
232 db->resource = resource;
233 db->p = svn_pool_create(p);
235 /* ### temp hack */
236 db->work = svn_stringbuf_ncreate("", 0, db->p);
238 /* make our path-based authz callback available to svn_repos_* funcs. */
239 arb = apr_pcalloc(p, sizeof(*arb));
240 arb->r = resource->info->r;
241 arb->repos = resource->info->repos;
242 db->authz_read_baton = arb;
243 db->authz_read_func = dav_svn__authz_read_func(arb);
245 /* ### use RO and node's mutable status to look for an error? */
247 *pdb = db;
249 return NULL;
253 static void
254 db_close(dav_db *db)
256 svn_pool_destroy(db->p);
260 static dav_error *
261 db_define_namespaces(dav_db *db, dav_xmlns_info *xi)
263 dav_xmlns_add(xi, "S", SVN_DAV_PROP_NS_SVN);
264 dav_xmlns_add(xi, "C", SVN_DAV_PROP_NS_CUSTOM);
265 dav_xmlns_add(xi, "V", SVN_DAV_PROP_NS_DAV);
267 /* ### we don't have any other possible namespaces right now. */
269 return NULL;
272 static dav_error *
273 db_output_value(dav_db *db,
274 const dav_prop_name *name,
275 dav_xmlns_info *xi,
276 apr_text_header *phdr,
277 int *found)
279 const char *prefix;
280 const char *s;
281 svn_string_t *propval;
282 dav_error *err;
283 apr_pool_t *pool = db->resource->pool;
285 if ((err = get_value(db, name, &propval)) != NULL)
286 return err;
288 /* return whether the prop was found, then punt or handle it. */
289 *found = (propval != NULL);
290 if (propval == NULL)
291 return NULL;
293 if (strcmp(name->ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
294 prefix = "C:";
295 else
296 prefix = "S:";
298 if (propval->len == 0)
300 /* empty value. add an empty elem. */
301 s = apr_psprintf(pool, "<%s%s/>" DEBUG_CR, prefix, name->name);
302 apr_text_append(pool, phdr, s);
304 else
306 /* add <prefix:name [V:encoding="base64"]>value</prefix:name> */
307 const char *xml_safe;
308 const char *encoding = "";
310 /* Ensure XML-safety of our property values before sending them
311 across the wire. */
312 if (! svn_xml_is_xml_safe(propval->data, propval->len))
314 const svn_string_t *enc_propval
315 = svn_base64_encode_string(propval, pool);
316 xml_safe = enc_propval->data;
317 encoding = apr_pstrcat(pool, " V:encoding=\"base64\"", NULL);
319 else
321 svn_stringbuf_t *xmlval = NULL;
322 svn_xml_escape_cdata_string(&xmlval, propval, pool);
323 xml_safe = xmlval->data;
326 s = apr_psprintf(pool, "<%s%s%s>", prefix, name->name, encoding);
327 apr_text_append(pool, phdr, s);
329 /* the value is in our pool which means it has the right lifetime. */
330 /* ### at least, per the current mod_dav architecture/API */
331 apr_text_append(pool, phdr, xml_safe);
333 s = apr_psprintf(pool, "</%s%s>" DEBUG_CR, prefix, name->name);
334 apr_text_append(pool, phdr, s);
337 return NULL;
341 static dav_error *
342 db_map_namespaces(dav_db *db,
343 const apr_array_header_t *namespaces,
344 dav_namespace_map **mapping)
346 /* we don't need a namespace mapping right now. nothing to do */
347 return NULL;
351 static dav_error *
352 db_store(dav_db *db,
353 const dav_prop_name *name,
354 const apr_xml_elem *elem,
355 dav_namespace_map *mapping)
357 const svn_string_t *propval;
358 apr_pool_t *pool = db->p;
359 apr_xml_attr *attr = elem->attr;
361 /* SVN sends property values as a big blob of bytes. Thus, there should be
362 no child elements of the property-name element. That also means that
363 the entire contents of the blob is located in elem->first_cdata. The
364 dav_xml_get_cdata() will figure it all out for us, but (normally) it
365 should be awfully fast and not need to copy any data. */
367 propval = svn_string_create
368 (dav_xml_get_cdata(elem, pool, 0 /* strip_white */), pool);
370 /* Check for special encodings of the property value. */
371 while (attr)
373 if (strcmp(attr->name, "encoding") == 0) /* ### namespace check? */
375 const char *enc_type = attr->value;
377 /* Handle known encodings here. */
378 if (enc_type && (strcmp(enc_type, "base64") == 0))
379 propval = svn_base64_decode_string(propval, pool);
380 else
381 return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0,
382 "Unknown property encoding");
383 break;
385 /* Next attribute, please. */
386 attr = attr->next;
389 return save_value(db, name, propval);
393 static dav_error *
394 db_remove(dav_db *db, const dav_prop_name *name)
396 svn_error_t *serr;
397 const char *propname;
399 /* get the repos-local name */
400 get_repos_propname(db, name, &propname);
402 /* ### non-svn props aren't in our repos, so punt for now */
403 if (propname == NULL)
404 return NULL;
406 /* Working Baseline or Working (Version) Resource */
407 if (db->resource->baselined)
408 if (db->resource->working)
409 serr = svn_repos_fs_change_txn_prop(db->resource->info->root.txn,
410 propname, NULL, db->resource->pool);
411 else
412 /* ### VIOLATING deltaV: you can't proppatch a baseline, it's
413 not a working resource! But this is how we currently
414 (hackily) allow the svn client to change unversioned rev
415 props. See issue #916. */
416 serr = svn_repos_fs_change_rev_prop3(db->resource->info->repos->repos,
417 db->resource->info->root.rev,
418 db->resource->info->repos->username,
419 propname, NULL, TRUE, TRUE,
420 db->authz_read_func,
421 db->authz_read_baton,
422 db->resource->pool);
423 else
424 serr = svn_repos_fs_change_node_prop(db->resource->info->root.root,
425 get_repos_path(db->resource->info),
426 propname, NULL, db->resource->pool);
427 if (serr != NULL)
428 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
429 "could not remove a property",
430 db->resource->pool);
432 /* a change to the props was made; make sure our cached copy is gone */
433 db->props = NULL;
435 return NULL;
439 static int
440 db_exists(dav_db *db, const dav_prop_name *name)
442 const char *propname;
443 svn_string_t *propval;
444 svn_error_t *serr;
445 int retval;
447 /* get the repos-local name */
448 get_repos_propname(db, name, &propname);
450 /* ### non-svn props aren't in our repos */
451 if (propname == NULL)
452 return 0;
454 /* Working Baseline, Baseline, or (Working) Version resource */
455 if (db->resource->baselined)
456 if (db->resource->type == DAV_RESOURCE_TYPE_WORKING)
457 serr = svn_fs_txn_prop(&propval, db->resource->info->root.txn,
458 propname, db->p);
459 else
460 serr = svn_repos_fs_revision_prop(&propval,
461 db->resource->info->repos->repos,
462 db->resource->info->root.rev,
463 propname,
464 db->authz_read_func,
465 db->authz_read_baton, db->p);
466 else
467 serr = svn_fs_node_prop(&propval, db->resource->info->root.root,
468 get_repos_path(db->resource->info),
469 propname, db->p);
471 /* ### try and dispose of the value? */
473 retval = (serr == NULL && propval != NULL);
474 svn_error_clear(serr);
475 return retval;
478 static void get_name(dav_db *db, dav_prop_name *pname)
480 if (db->hi == NULL)
482 pname->ns = pname->name = NULL;
484 else
486 const void *name;
488 apr_hash_this(db->hi, &name, NULL, NULL);
490 #define PREFIX_LEN (sizeof(SVN_PROP_PREFIX) - 1)
491 if (strncmp(name, SVN_PROP_PREFIX, PREFIX_LEN) == 0)
492 #undef PREFIX_LEN
494 pname->ns = SVN_DAV_PROP_NS_SVN;
495 pname->name = (const char *)name + 4;
497 else
499 pname->ns = SVN_DAV_PROP_NS_CUSTOM;
500 pname->name = name;
506 static dav_error *
507 db_first_name(dav_db *db, dav_prop_name *pname)
509 /* for operational logging */
510 char *action = NULL;
512 /* if we don't have a copy of the properties, then get one */
513 if (db->props == NULL)
515 svn_error_t *serr;
517 /* Working Baseline, Baseline, or (Working) Version resource */
518 if (db->resource->baselined)
520 if (db->resource->type == DAV_RESOURCE_TYPE_WORKING)
521 serr = svn_fs_txn_proplist(&db->props,
522 db->resource->info->root.txn,
523 db->p);
524 else
526 action = apr_psprintf(db->resource->pool, "rev-proplist r%ld",
527 db->resource->info->root.rev);
528 serr = svn_repos_fs_revision_proplist
529 (&db->props,
530 db->resource->info->repos->repos,
531 db->resource->info->root.rev,
532 db->authz_read_func,
533 db->authz_read_baton,
534 db->p);
537 else
539 svn_node_kind_t kind;
540 serr = svn_fs_node_proplist(&db->props,
541 db->resource->info->root.root,
542 get_repos_path(db->resource->info),
543 db->p);
544 if (! serr)
545 serr = svn_fs_check_path(&kind, db->resource->info->root.root,
546 get_repos_path(db->resource->info),
547 db->p);
549 if (! serr)
550 action = apr_psprintf(db->resource->pool, "get-%s %s r%ld props",
551 (kind == svn_node_dir ? "dir" : "file"),
552 svn_path_uri_encode(db->resource->info->repos_path,
553 db->resource->pool),
554 db->resource->info->root.rev);
556 if (serr != NULL)
557 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
558 "could not begin sequencing through "
559 "properties",
560 db->resource->pool);
563 /* begin the iteration over the hash */
564 db->hi = apr_hash_first(db->p, db->props);
566 /* fetch the first key */
567 get_name(db, pname);
569 /* If we have a high-level action to log, do so. */
570 if (action != NULL)
571 dav_svn__operational_log(db->resource->info, action);
573 return NULL;
577 static dav_error *
578 db_next_name(dav_db *db, dav_prop_name *pname)
580 /* skip to the next hash entry */
581 if (db->hi != NULL)
582 db->hi = apr_hash_next(db->hi);
584 /* fetch the key */
585 get_name(db, pname);
587 return NULL;
591 static dav_error *
592 db_get_rollback(dav_db *db,
593 const dav_prop_name *name,
594 dav_deadprop_rollback **prollback)
596 dav_error *err;
597 dav_deadprop_rollback *ddp;
598 svn_string_t *propval;
600 if ((err = get_value(db, name, &propval)) != NULL)
601 return err;
603 ddp = apr_palloc(db->p, sizeof(*ddp));
604 ddp->name = *name;
605 ddp->value.data = propval ? propval->data : NULL;
606 ddp->value.len = propval ? propval->len : 0;
608 *prollback = ddp;
609 return NULL;
613 static dav_error *
614 db_apply_rollback(dav_db *db, dav_deadprop_rollback *rollback)
616 if (rollback->value.data == NULL)
618 return db_remove(db, &rollback->name);
621 return save_value(db, &rollback->name, &rollback->value);
625 const dav_hooks_propdb dav_svn__hooks_propdb = {
626 db_open,
627 db_close,
628 db_define_namespaces,
629 db_output_value,
630 db_map_namespaces,
631 db_store,
632 db_remove,
633 db_exists,
634 db_first_name,
635 db_next_name,
636 db_get_rollback,
637 db_apply_rollback,