2
* liveprops.c: mod_dav_svn live 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
* ====================================================================
23
#include <apr_tables.h>
29
#include "svn_pools.h"
33
#include "svn_props.h"
37
** The namespace URIs that we use. This list and the enumeration must
40
static const char * const dav_svn_namespace_uris[] =
48
DAV_SVN_NAMESPACE_URI_DAV, /* the DAV: namespace URI */
49
DAV_SVN_NAMESPACE_URI /* the dav<->ra_dav namespace URI */
52
#define SVN_RO_DAV_PROP(name) \
53
{ DAV_SVN_NAMESPACE_URI_DAV, #name, DAV_PROPID_##name, 0 }
54
#define SVN_RW_DAV_PROP(name) \
55
{ DAV_SVN_NAMESPACE_URI_DAV, #name, DAV_PROPID_##name, 1 }
56
#define SVN_RO_DAV_PROP2(sym,name) \
57
{ DAV_SVN_NAMESPACE_URI_DAV, #name, DAV_PROPID_##sym, 0 }
58
#define SVN_RW_DAV_PROP2(sym,name) \
59
{ DAV_SVN_NAMESPACE_URI_DAV, #name, DAV_PROPID_##sym, 1 }
61
#define SVN_RO_SVN_PROP(sym,name) \
62
{ DAV_SVN_NAMESPACE_URI, #name, SVN_PROPID_##sym, 0 }
63
#define SVN_RW_SVN_PROP(sym,name) \
64
{ DAV_SVN_NAMESPACE_URI, #name, SVN_PROPID_##sym, 1 }
68
SVN_PROPID_baseline_relative_path = 1,
69
SVN_PROPID_md5_checksum,
70
SVN_PROPID_repository_uuid
73
static const dav_liveprop_spec dav_svn_props[] =
75
/* ### don't worry about these for a bit */
77
/* WebDAV properties */
78
SVN_RO_DAV_PROP(getcontentlanguage), /* ### make this r/w? */
80
SVN_RO_DAV_PROP(getcontentlength),
81
SVN_RO_DAV_PROP(getcontenttype), /* ### make this r/w? */
82
SVN_RO_DAV_PROP(getetag),
83
SVN_RO_DAV_PROP(creationdate),
84
SVN_RO_DAV_PROP(getlastmodified),
86
/* DeltaV properties */
87
SVN_RO_DAV_PROP2(baseline_collection, baseline-collection),
88
SVN_RO_DAV_PROP2(checked_in, checked-in),
89
SVN_RO_DAV_PROP2(version_controlled_configuration,
90
version-controlled-configuration),
91
SVN_RO_DAV_PROP2(version_name, version-name),
92
SVN_RO_DAV_PROP2(creator_displayname, creator-displayname),
93
SVN_RO_DAV_PROP2(auto_version, auto-version),
96
SVN_RO_SVN_PROP(baseline_relative_path, baseline-relative-path),
97
SVN_RO_SVN_PROP(md5_checksum, md5-checksum),
98
SVN_RO_SVN_PROP(repository_uuid, repository-uuid),
103
static const dav_liveprop_group dav_svn_liveprop_group =
106
dav_svn_namespace_uris,
107
&dav_svn_hooks_liveprop
110
/* Set *PROPVAL to the value for the revision property PROPNAME on
111
COMMITTED_REV, in the repository identified by RESOURCE, if
112
RESOURCE's path is readable. If it is not readable, set *PROPVAL
113
to NULL and return SVN_NO_ERROR. Use POOL for temporary
114
allocations and the allocation of *PROPVAL.
116
Note that this function does not check the readability of the
117
revision property, but the readability of a path. The true
118
readability of a revision property is determined by investigating
119
the readability of all changed paths in the revision. For certain
120
revision properties (e.g. svn:author and svn:date) to be readable,
121
it is enough if at least one changed path is readable. When we
122
already have a changed path, we can skip the check for the other
123
changed paths in the revision and save a lot of work. This means
124
that we will make a mistake when our path is unreadable and another
125
changed path is readable, but we will at least only hide too much
126
and not leak any protected properties.
128
WARNING: This method of only checking the readability of a path is
129
only valid to get revision properties for which it is enough if at
130
least one changed path is readable. Using this function to get
131
revision properties for which all changed paths must be readable
132
might leak protected information because we will only test the
133
readability of a single changed path.
135
static svn_error_t *dav_svn_get_path_revprop(svn_string_t **propval,
136
const dav_resource *resource,
137
svn_revnum_t committed_rev,
138
const char *propname,
141
dav_svn_authz_read_baton arb;
142
svn_boolean_t allowed;
147
arb.r = resource->info->r;
148
arb.repos = resource->info->repos;
149
SVN_ERR(svn_fs_revision_root(&root,
150
resource->info->repos->fs,
151
committed_rev, pool));
152
SVN_ERR(dav_svn_authz_read(&allowed,
154
resource->info->repos_path,
160
/* Get the property of the created revision. The authz is already
161
performed, so we don't need to do it here too. */
162
return svn_repos_fs_revision_prop(propval,
163
resource->info->repos->repos,
169
static dav_prop_insert dav_svn_insert_prop(const dav_resource *resource,
170
int propid, dav_prop_insert what,
171
apr_text_header *phdr)
173
const char *value = NULL;
175
apr_pool_t *response_pool = resource->pool;
176
apr_pool_t *p = resource->info->pool;
177
const dav_liveprop_spec *info;
182
** Almost none of the SVN provider properties are defined if the
183
** resource does not exist. We do need to return the one VCC
184
** property and baseline-relative-path on lock-null resources,
185
** however, so that svn clients can run 'svn unlock' and 'svn info'
188
** Even though we state that the SVN properties are not defined, the
189
** client cannot store dead values -- we deny that thru the is_writable
192
if ((! resource->exists)
193
&& (propid != DAV_PROPID_version_controlled_configuration)
194
&& (propid != SVN_PROPID_baseline_relative_path))
195
return DAV_PROP_INSERT_NOTSUPP;
197
/* ### we may want to respond to DAV_PROPID_resourcetype for PRIVATE
198
### resources. need to think on "proper" interaction with mod_dav */
202
case DAV_PROPID_getlastmodified:
203
case DAV_PROPID_creationdate:
205
/* In subversion terms, the date attached to a file's CR is
206
the true "last modified" time. However, we're defining
207
creationdate in the same way. IMO, the "creationdate" is
208
really the date attached to the revision in which the item
209
*first* came into existence; this would found by tracing
210
back through the log of the file -- probably via
211
svn_fs_revisions_changed. gstein, is it a bad thing that
212
we're currently using 'creationdate' to mean the same thing
213
as 'last modified date'? */
214
const char *datestring;
216
enum dav_svn_time_format format;
218
/* ### for now, our global VCC has no such property. */
219
if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
220
&& resource->info->restype == DAV_SVN_RESTYPE_VCC)
222
return DAV_PROP_INSERT_NOTSUPP;
225
if (propid == DAV_PROPID_creationdate)
227
/* Return an ISO8601 date; this is what the svn client
228
expects, and rfc2518 demands it. */
229
format = dav_svn_time_format_iso8601;
231
else /* propid == DAV_PROPID_getlastmodified */
233
format = dav_svn_time_format_rfc1123;
236
if (0 != dav_svn_get_last_modified_time (&datestring, &timeval,
237
resource, format, p))
239
return DAV_PROP_INSERT_NOTDEF;
242
value = apr_xml_quote_string(p, datestring, 1);
246
case DAV_PROPID_creator_displayname:
248
svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
249
svn_string_t *last_author = NULL;
251
/* ### for now, our global VCC has no such property. */
252
if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
253
&& resource->info->restype == DAV_SVN_RESTYPE_VCC)
255
return DAV_PROP_INSERT_NOTSUPP;
258
if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION)
260
/* A baseline URI. */
261
committed_rev = resource->info->root.rev;
263
else if (resource->type == DAV_RESOURCE_TYPE_REGULAR
264
|| resource->type == DAV_RESOURCE_TYPE_WORKING
265
|| resource->type == DAV_RESOURCE_TYPE_VERSION)
267
/* Get the CR field out of the node's skel. Notice that the
268
root object might be an ID root -or- a revision root. */
269
serr = svn_fs_node_created_rev(&committed_rev,
270
resource->info->root.root,
271
resource->info->repos_path, p);
274
/* ### what to do? */
275
svn_error_clear(serr);
276
value = "###error###";
282
return DAV_PROP_INSERT_NOTSUPP;
285
serr = dav_svn_get_path_revprop(&last_author,
288
SVN_PROP_REVISION_AUTHOR,
292
/* ### what to do? */
293
svn_error_clear(serr);
294
value = "###error###";
298
if (last_author == NULL)
299
return DAV_PROP_INSERT_NOTDEF;
301
value = apr_xml_quote_string(p, last_author->data, 1);
305
case DAV_PROPID_getcontentlanguage:
306
/* ### need something here */
307
return DAV_PROP_INSERT_NOTSUPP;
310
case DAV_PROPID_getcontentlength:
312
svn_filesize_t len = 0;
314
/* our property, but not defined on collection resources */
315
if (resource->collection || resource->baselined)
316
return DAV_PROP_INSERT_NOTSUPP;
318
serr = svn_fs_file_length(&len, resource->info->root.root,
319
resource->info->repos_path, p);
322
svn_error_clear(serr);
323
value = "0"; /* ### what to do? */
327
value = apr_psprintf(p, "%" SVN_FILESIZE_T_FMT, len);
331
case DAV_PROPID_getcontenttype:
333
/* The subversion client assumes that any file without an
334
svn:mime-type property is of type text/plain. So it seems
335
safe (and consistent) to assume the same on the server. */
337
const char *mime_type = NULL;
339
if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION)
340
return DAV_PROP_INSERT_NOTSUPP;
342
if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
343
&& resource->info->restype == DAV_SVN_RESTYPE_VCC)
345
return DAV_PROP_INSERT_NOTSUPP;
348
if (resource->collection) /* defaults for directories */
350
if (resource->info->repos->xslt_uri)
351
mime_type = "text/xml";
353
mime_type = "text/html; charset=UTF-8";
357
if ((serr = svn_fs_node_prop (&pval, resource->info->root.root,
358
resource->info->repos_path,
359
SVN_PROP_MIME_TYPE, p)))
361
svn_error_clear(serr);
366
mime_type = pval->data;
367
else if ((! resource->info->repos->is_svn_client)
368
&& resource->info->r->content_type)
369
mime_type = resource->info->r->content_type;
371
mime_type = "text/plain"; /* default for file */
373
if ((serr = svn_mime_type_validate (mime_type, p)))
375
/* Probably serr->apr == SVN_ERR_BAD_MIME_TYPE, but
376
there's no point even checking. No matter what the
377
error is, we can't claim to have a mime type for
379
svn_error_clear(serr);
380
return DAV_PROP_INSERT_NOTDEF;
388
case DAV_PROPID_getetag:
389
if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
390
&& resource->info->restype == DAV_SVN_RESTYPE_VCC)
392
return DAV_PROP_INSERT_NOTSUPP;
395
value = dav_svn_getetag(resource, p);
398
case DAV_PROPID_auto_version:
399
/* we only support one autoversioning behavior, and thus only
400
return this one static value; someday when we support
401
locking, there are other possible values/behaviors for this. */
402
if (resource->info->repos->autoversioning)
403
value = "DAV:checkout-checkin";
405
return DAV_PROP_INSERT_NOTDEF;
408
case DAV_PROPID_baseline_collection:
409
/* only defined for Baselines */
410
/* ### whoops. also defined for a VCC. deal with it later. */
411
if (resource->type != DAV_RESOURCE_TYPE_VERSION || !resource->baselined)
412
return DAV_PROP_INSERT_NOTSUPP;
413
value = dav_svn_build_uri(resource->info->repos, DAV_SVN_BUILD_URI_BC,
414
resource->info->root.rev, NULL,
415
1 /* add_href */, p);
418
case DAV_PROPID_checked_in:
419
/* only defined for VCRs (in the public space and in a BC space) */
420
/* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */
421
if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
422
&& resource->info->restype == DAV_SVN_RESTYPE_VCC)
426
serr = svn_fs_youngest_rev(&revnum, resource->info->repos->fs, p);
429
/* ### what to do? */
430
svn_error_clear(serr);
431
value = "###error###";
434
s = dav_svn_build_uri(resource->info->repos,
435
DAV_SVN_BUILD_URI_BASELINE,
436
revnum, NULL, 0 /* add_href */, p);
437
value = apr_psprintf(p, "<D:href>%s</D:href>",
438
apr_xml_quote_string(p, s, 1));
440
else if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
442
/* not defined for this resource type */
443
return DAV_PROP_INSERT_NOTSUPP;
447
svn_revnum_t rev_to_use =
448
dav_svn_get_safe_cr(resource->info->root.root,
449
resource->info->repos_path, p);
451
s = dav_svn_build_uri(resource->info->repos,
452
DAV_SVN_BUILD_URI_VERSION,
453
rev_to_use, resource->info->repos_path,
454
0 /* add_href */, p);
455
value = apr_psprintf(p, "<D:href>%s</D:href>",
456
apr_xml_quote_string(p, s, 1));
460
case DAV_PROPID_version_controlled_configuration:
461
/* only defined for VCRs */
462
/* ### VCRs within the BC should not have this property! */
463
/* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */
464
if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
465
return DAV_PROP_INSERT_NOTSUPP;
466
value = dav_svn_build_uri(resource->info->repos, DAV_SVN_BUILD_URI_VCC,
467
SVN_IGNORED_REVNUM, NULL,
468
1 /* add_href */, p);
471
case DAV_PROPID_version_name:
472
/* only defined for Version Resources and Baselines */
473
/* ### whoops. also defined for VCRs. deal with it later. */
474
if ((resource->type != DAV_RESOURCE_TYPE_VERSION)
475
&& (! resource->versioned))
476
return DAV_PROP_INSERT_NOTSUPP;
478
if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
479
&& resource->info->restype == DAV_SVN_RESTYPE_VCC)
481
return DAV_PROP_INSERT_NOTSUPP;
484
if (resource->baselined)
486
/* just the revision number for baselines */
487
value = apr_psprintf(p, "%ld",
488
resource->info->root.rev);
492
svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
494
/* Get the CR field out of the node's skel. Notice that the
495
root object might be an ID root -or- a revision root. */
496
serr = svn_fs_node_created_rev(&committed_rev,
497
resource->info->root.root,
498
resource->info->repos_path, p);
501
/* ### what to do? */
502
svn_error_clear(serr);
503
value = "###error###";
507
/* Convert the revision into a quoted string */
508
s = apr_psprintf(p, "%ld", committed_rev);
509
value = apr_xml_quote_string(p, s, 1);
513
case SVN_PROPID_baseline_relative_path:
514
/* only defined for VCRs */
515
/* ### VCRs within the BC should not have this property! */
516
/* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */
517
if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
518
return DAV_PROP_INSERT_NOTSUPP;
520
/* drop the leading slash, so it is relative */
521
s = resource->info->repos_path + 1;
522
value = apr_xml_quote_string(p, s, 1);
525
case SVN_PROPID_md5_checksum:
526
if ((! resource->collection)
527
&& (! resource->baselined)
528
&& (resource->type == DAV_RESOURCE_TYPE_REGULAR
529
|| resource->type == DAV_RESOURCE_TYPE_WORKING
530
|| resource->type == DAV_RESOURCE_TYPE_VERSION))
532
unsigned char digest[APR_MD5_DIGESTSIZE];
534
serr = svn_fs_file_md5_checksum(digest,
535
resource->info->root.root,
536
resource->info->repos_path, p);
539
/* ### what to do? */
540
svn_error_clear(serr);
541
value = "###error###";
545
value = svn_md5_digest_to_cstring (digest, p);
548
return DAV_PROP_INSERT_NOTSUPP;
551
return DAV_PROP_INSERT_NOTSUPP;
555
case SVN_PROPID_repository_uuid:
556
serr = svn_fs_get_uuid(resource->info->repos->fs, &value, p);
559
/* ### what to do? */
560
svn_error_clear(serr);
561
value = "###error###";
567
/* ### what the heck was this property? */
568
return DAV_PROP_INSERT_NOTDEF;
571
/* assert: value != NULL */
573
/* get the information and global NS index for the property */
574
global_ns = dav_get_liveprop_info(propid, &dav_svn_liveprop_group, &info);
576
/* assert: info != NULL && info->name != NULL */
578
if (what == DAV_PROP_INSERT_NAME
579
|| (what == DAV_PROP_INSERT_VALUE && *value == '\0')) {
580
s = apr_psprintf(response_pool, "<lp%d:%s/>" DEBUG_CR, global_ns,
583
else if (what == DAV_PROP_INSERT_VALUE) {
584
s = apr_psprintf(response_pool, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
585
global_ns, info->name, value, global_ns, info->name);
588
/* assert: what == DAV_PROP_INSERT_SUPPORTED */
589
s = apr_psprintf(response_pool,
590
"<D:supported-live-property D:name=\"%s\" "
591
"D:namespace=\"%s\"/>" DEBUG_CR,
592
info->name, dav_svn_namespace_uris[info->ns]);
594
apr_text_append(response_pool, phdr, s);
596
/* we inserted whatever was asked for */
600
static int dav_svn_is_writable(const dav_resource *resource, int propid)
602
const dav_liveprop_spec *info;
604
(void) dav_get_liveprop_info(propid, &dav_svn_liveprop_group, &info);
605
return info->is_writable;
608
static dav_error * dav_svn_patch_validate(const dav_resource *resource,
609
const apr_xml_elem *elem,
610
int operation, void **context,
613
/* NOTE: this function will not be called unless/until we have
614
modifiable (writable) live properties. */
618
static dav_error * dav_svn_patch_exec(const dav_resource *resource,
619
const apr_xml_elem *elem,
620
int operation, void *context,
621
dav_liveprop_rollback **rollback_ctx)
623
/* NOTE: this function will not be called unless/until we have
624
modifiable (writable) live properties. */
628
static void dav_svn_patch_commit(const dav_resource *resource,
629
int operation, void *context,
630
dav_liveprop_rollback *rollback_ctx)
632
/* NOTE: this function will not be called unless/until we have
633
modifiable (writable) live properties. */
636
static dav_error * dav_svn_patch_rollback(const dav_resource *resource,
637
int operation, void *context,
638
dav_liveprop_rollback *rollback_ctx)
640
/* NOTE: this function will not be called unless/until we have
641
modifiable (writable) live properties. */
645
const dav_hooks_liveprop dav_svn_hooks_liveprop = {
648
dav_svn_namespace_uris,
649
dav_svn_patch_validate,
651
dav_svn_patch_commit,
652
dav_svn_patch_rollback,
655
void dav_svn_gather_propsets(apr_array_header_t *uris)
657
/* ### what should we use for a URL to describe the available prop set? */
658
/* ### for now... nothing. we will *only* have DAV properties */
660
*(const char **)apr_array_push(uris) =
661
"<http://subversion.tigris.org/dav/propset/svn/1>";
665
int dav_svn_find_liveprop(const dav_resource *resource,
666
const char *ns_uri, const char *name,
667
const dav_hooks_liveprop **hooks)
669
/* don't try to find any liveprops if this isn't "our" resource */
670
if (resource->hooks != &dav_svn_hooks_repos)
673
return dav_do_find_liveprop(ns_uri, name, &dav_svn_liveprop_group, hooks);
676
void dav_svn_insert_all_liveprops(request_rec *r, const dav_resource *resource,
677
dav_prop_insert what, apr_text_header *phdr)
679
const dav_liveprop_spec *spec;
683
/* don't insert any liveprops if this isn't "our" resource */
684
if (resource->hooks != &dav_svn_hooks_repos)
687
if (!resource->exists) {
688
/* a lock-null resource */
690
** ### technically, we should insert empty properties. dunno offhand
691
** ### what part of the spec said this, but it was essentially thus:
692
** ### "the properties should be defined, but may have no value".
697
pool = resource->info->pool;
698
subpool = svn_pool_create(pool);
699
resource->info->pool = subpool;
701
for (spec = dav_svn_props; spec->name != NULL; ++spec)
703
svn_pool_clear(subpool);
704
(void) dav_svn_insert_prop(resource, spec->propid, what, phdr);
707
resource->info->pool = pool;
708
svn_pool_destroy(subpool);
710
/* ### we know the others aren't defined as liveprops */
713
void dav_svn_register_uris(apr_pool_t *p)
715
/* register the namespace URIs */
716
dav_register_liveprop_group(p, &dav_svn_liveprop_group);
720
int dav_svn_get_last_modified_time (const char **datestring,
722
const dav_resource *resource,
723
enum dav_svn_time_format format,
726
svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
727
svn_string_t *committed_date = NULL;
729
apr_time_t timeval_tmp;
731
if ((datestring == NULL) && (timeval == NULL))
734
if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION)
736
/* A baseline URI. */
737
committed_rev = resource->info->root.rev;
739
else if (resource->type == DAV_RESOURCE_TYPE_REGULAR
740
|| resource->type == DAV_RESOURCE_TYPE_WORKING
741
|| resource->type == DAV_RESOURCE_TYPE_VERSION)
743
serr = svn_fs_node_created_rev(&committed_rev,
744
resource->info->root.root,
745
resource->info->repos_path, pool);
748
svn_error_clear(serr);
754
/* unsupported resource kind -- has no mod-time */
758
serr = dav_svn_get_path_revprop(&committed_date,
761
SVN_PROP_REVISION_DATE,
765
svn_error_clear(serr);
769
if (committed_date == NULL)
772
/* return the ISO8601 date as an apr_time_t */
773
serr = svn_time_from_cstring(&timeval_tmp, committed_date->data, pool);
776
svn_error_clear(serr);
781
memcpy(timeval, &timeval_tmp, sizeof(*timeval));
786
if (format == dav_svn_time_format_iso8601)
788
*datestring = committed_date->data;
790
else if (format == dav_svn_time_format_rfc1123)
795
/* convert the apr_time_t into an apr_time_exp_t */
796
status = apr_time_exp_gmt(&tms, timeval_tmp);
797
if (status != APR_SUCCESS)
800
/* stolen from dav/fs/repos.c :-) */
801
*datestring = apr_psprintf(pool, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
802
apr_day_snames[tms.tm_wday],
803
tms.tm_mday, apr_month_snames[tms.tm_mon],
805
tms.tm_hour, tms.tm_min, tms.tm_sec);
807
else /* unknown time format */