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 * ====================================================================
25 #include "svn_pools.h"
27 #include "svn_base64.h"
28 #include "svn_props.h"
29 #include "private/svn_log.h"
35 const dav_resource
*resource
;
38 /* the resource's properties that we are sequencing over */
42 /* used for constructing repos-local names for properties */
43 svn_stringbuf_t
*work
;
45 /* passed to svn_repos_ funcs that fetch revprops. */
46 svn_repos_authz_func_t authz_read_func
;
47 void *authz_read_baton
;
51 struct dav_deadprop_rollback
{
57 /* retrieve the "right" string to use as a repos path */
59 get_repos_path(struct dav_resource_private
*info
)
61 return info
->repos_path
;
65 /* construct the repos-local name for the given DAV property name */
67 get_repos_propname(dav_db
*db
,
68 const dav_prop_name
*name
,
69 const char **repos_propname
)
71 if (strcmp(name
->ns
, SVN_DAV_PROP_NS_SVN
) == 0)
73 /* recombine the namespace ("svn:") and the name. */
74 svn_stringbuf_set(db
->work
, SVN_PROP_PREFIX
);
75 svn_stringbuf_appendcstr(db
->work
, name
->name
);
76 *repos_propname
= db
->work
->data
;
78 else if (strcmp(name
->ns
, SVN_DAV_PROP_NS_CUSTOM
) == 0)
80 /* the name of a custom prop is just the name -- no ns URI */
81 *repos_propname
= name
->name
;
85 *repos_propname
= NULL
;
91 get_value(dav_db
*db
, const dav_prop_name
*name
, svn_string_t
**pvalue
)
96 /* get the repos-local name */
97 get_repos_propname(db
, name
, &propname
);
101 /* we know these are not present. */
106 /* ### if db->props exists, then try in there first */
108 /* Working Baseline, Baseline, or (Working) Version resource */
109 if (db
->resource
->baselined
)
110 if (db
->resource
->type
== DAV_RESOURCE_TYPE_WORKING
)
111 serr
= svn_fs_txn_prop(pvalue
, db
->resource
->info
->root
.txn
,
114 serr
= svn_repos_fs_revision_prop(pvalue
,
115 db
->resource
->info
-> repos
->repos
,
116 db
->resource
->info
->root
.rev
, propname
,
118 db
->authz_read_baton
, db
->p
);
120 serr
= svn_fs_node_prop(pvalue
, db
->resource
->info
->root
.root
,
121 get_repos_path(db
->resource
->info
),
124 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
125 "could not fetch a property",
133 save_value(dav_db
*db
, const dav_prop_name
*name
, const svn_string_t
*value
)
135 const char *propname
;
138 /* get the repos-local name */
139 get_repos_propname(db
, name
, &propname
);
141 if (propname
== NULL
)
143 if (db
->resource
->info
->repos
->autoversioning
)
144 /* ignore the unknown namespace of the incoming prop. */
145 propname
= name
->name
;
147 return dav_new_error(db
->p
, HTTP_CONFLICT
, 0,
148 "Properties may only be defined in the "
149 SVN_DAV_PROP_NS_SVN
" and " SVN_DAV_PROP_NS_CUSTOM
153 /* Working Baseline or Working (Version) Resource */
154 if (db
->resource
->baselined
)
155 if (db
->resource
->working
)
156 serr
= svn_repos_fs_change_txn_prop(db
->resource
->info
->root
.txn
,
157 propname
, value
, db
->resource
->pool
);
160 /* ### VIOLATING deltaV: you can't proppatch a baseline, it's
161 not a working resource! But this is how we currently
162 (hackily) allow the svn client to change unversioned rev
163 props. See issue #916. */
164 serr
= svn_repos_fs_change_rev_prop3
165 (db
->resource
->info
->repos
->repos
,
166 db
->resource
->info
->root
.rev
,
167 db
->resource
->info
->repos
->username
,
168 propname
, value
, TRUE
, TRUE
,
170 db
->authz_read_baton
,
173 /* Tell the logging subsystem about the revprop change. */
174 dav_svn__operational_log(db
->resource
->info
,
175 svn_log__change_rev_prop(
176 db
->resource
->info
->root
.rev
,
178 db
->resource
->pool
));
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
);
185 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
189 /* a change to the props was made; make sure our cached copy is gone */
197 db_open(apr_pool_t
*p
,
198 const dav_resource
*resource
,
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
)
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 "
230 db
= apr_pcalloc(p
, sizeof(*db
));
232 db
->resource
= resource
;
233 db
->p
= svn_pool_create(p
);
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? */
256 svn_pool_destroy(db
->p
);
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. */
273 db_output_value(dav_db
*db
,
274 const dav_prop_name
*name
,
276 apr_text_header
*phdr
,
281 svn_string_t
*propval
;
283 apr_pool_t
*pool
= db
->resource
->pool
;
285 if ((err
= get_value(db
, name
, &propval
)) != NULL
)
288 /* return whether the prop was found, then punt or handle it. */
289 *found
= (propval
!= NULL
);
293 if (strcmp(name
->ns
, SVN_DAV_PROP_NS_CUSTOM
) == 0)
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
);
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
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
);
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
);
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 */
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. */
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
);
381 return dav_new_error(pool
, HTTP_INTERNAL_SERVER_ERROR
, 0,
382 "Unknown property encoding");
385 /* Next attribute, please. */
389 return save_value(db
, name
, propval
);
394 db_remove(dav_db
*db
, const dav_prop_name
*name
)
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
)
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
);
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
,
421 db
->authz_read_baton
,
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
);
428 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
429 "could not remove a property",
432 /* a change to the props was made; make sure our cached copy is gone */
440 db_exists(dav_db
*db
, const dav_prop_name
*name
)
442 const char *propname
;
443 svn_string_t
*propval
;
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
)
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
,
460 serr
= svn_repos_fs_revision_prop(&propval
,
461 db
->resource
->info
->repos
->repos
,
462 db
->resource
->info
->root
.rev
,
465 db
->authz_read_baton
, db
->p
);
467 serr
= svn_fs_node_prop(&propval
, db
->resource
->info
->root
.root
,
468 get_repos_path(db
->resource
->info
),
471 /* ### try and dispose of the value? */
473 retval
= (serr
== NULL
&& propval
!= NULL
);
474 svn_error_clear(serr
);
478 static void get_name(dav_db
*db
, dav_prop_name
*pname
)
482 pname
->ns
= pname
->name
= NULL
;
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)
494 pname
->ns
= SVN_DAV_PROP_NS_SVN
;
495 pname
->name
= (const char *)name
+ 4;
499 pname
->ns
= SVN_DAV_PROP_NS_CUSTOM
;
507 db_first_name(dav_db
*db
, dav_prop_name
*pname
)
509 /* for operational logging */
510 const char *action
= NULL
;
512 /* if we don't have a copy of the properties, then get one */
513 if (db
->props
== NULL
)
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
,
526 action
= svn_log__rev_proplist(db
->resource
->info
->root
.rev
,
528 serr
= svn_repos_fs_revision_proplist
530 db
->resource
->info
->repos
->repos
,
531 db
->resource
->info
->root
.rev
,
533 db
->authz_read_baton
,
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
),
545 serr
= svn_fs_check_path(&kind
, db
->resource
->info
->root
.root
,
546 get_repos_path(db
->resource
->info
),
551 if (kind
== svn_node_dir
)
552 action
= svn_log__get_dir(db
->resource
->info
->repos_path
,
553 db
->resource
->info
->root
.rev
,
554 FALSE
, TRUE
, 0, db
->resource
->pool
);
556 action
= svn_log__get_file(db
->resource
->info
->repos_path
,
557 db
->resource
->info
->root
.rev
,
558 FALSE
, TRUE
, db
->resource
->pool
);
562 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
563 "could not begin sequencing through "
568 /* begin the iteration over the hash */
569 db
->hi
= apr_hash_first(db
->p
, db
->props
);
571 /* fetch the first key */
574 /* If we have a high-level action to log, do so. */
576 dav_svn__operational_log(db
->resource
->info
, action
);
583 db_next_name(dav_db
*db
, dav_prop_name
*pname
)
585 /* skip to the next hash entry */
587 db
->hi
= apr_hash_next(db
->hi
);
597 db_get_rollback(dav_db
*db
,
598 const dav_prop_name
*name
,
599 dav_deadprop_rollback
**prollback
)
602 dav_deadprop_rollback
*ddp
;
603 svn_string_t
*propval
;
605 if ((err
= get_value(db
, name
, &propval
)) != NULL
)
608 ddp
= apr_palloc(db
->p
, sizeof(*ddp
));
610 ddp
->value
.data
= propval
? propval
->data
: NULL
;
611 ddp
->value
.len
= propval
? propval
->len
: 0;
619 db_apply_rollback(dav_db
*db
, dav_deadprop_rollback
*rollback
)
621 if (rollback
->value
.data
== NULL
)
623 return db_remove(db
, &rollback
->name
);
626 return save_value(db
, &rollback
->name
, &rollback
->value
);
630 const dav_hooks_propdb dav_svn__hooks_propdb
= {
633 db_define_namespaces
,