1 /*
2 * liveprops.c: mod_dav_svn provider functions for "live properties"
3 * (properties implemented by the WebDAV specification
4 * itself, not unique to Subversion or its users).
5 *
6 * ====================================================================
7 * Licensed to the Apache Software Foundation (ASF) under one
8 * or more contributor license agreements. See the NOTICE file
9 * distributed with this work for additional information
10 * regarding copyright ownership. The ASF licenses this file
11 * to you under the Apache License, Version 2.0 (the
12 * "License"); you may not use this file except in compliance
13 * with the License. You may obtain a copy of the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing,
18 * software distributed under the License is distributed on an
19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 * KIND, either express or implied. See the License for the
21 * specific language governing permissions and limitations
22 * under the License.
23 * ====================================================================
24 */
25
26 #include <apr_tables.h>
27
28 #include <httpd.h>
29 #include <http_core.h>
30 #include <http_log.h>
31 #include <util_xml.h>
32 #include <mod_dav.h>
33
34 #include "svn_checksum.h"
35 #include "svn_pools.h"
36 #include "svn_time.h"
37 #include "svn_dav.h"
38 #include "svn_props.h"
39 #include "svn_ctype.h"
40
41 #include "private/svn_dav_protocol.h"
42
43 #include "dav_svn.h"
44
45
46 /*
47 ** The namespace URIs that we use. This list and the enumeration must
48 ** stay in sync.
49 */
50 static const char * const namespace_uris[] =
51 {
52 "DAV:",
53 SVN_DAV_PROP_NS_DAV,
54
55 NULL /* sentinel */
56 };
57 enum {
58 NAMESPACE_URI_DAV, /* the DAV: namespace URI */
59 NAMESPACE_URI /* the dav<->ra_dav namespace URI */
60 };
61
62 #define SVN_RO_DAV_PROP(name) \
63 { NAMESPACE_URI_DAV, #name, DAV_PROPID_##name, 0 }
64 #define SVN_RW_DAV_PROP(name) \
65 { NAMESPACE_URI_DAV, #name, DAV_PROPID_##name, 1 }
66 #define SVN_RO_DAV_PROP2(sym,name) \
67 { NAMESPACE_URI_DAV, #name, DAV_PROPID_##sym, 0 }
68 #define SVN_RW_DAV_PROP2(sym,name) \
69 { NAMESPACE_URI_DAV, #name, DAV_PROPID_##sym, 1 }
70
71 #define SVN_RO_SVN_PROP(sym,name) \
72 { NAMESPACE_URI, #name, SVN_PROPID_##sym, 0 }
73 #define SVN_RW_SVN_PROP(sym,name) \
74 { NAMESPACE_URI, #name, SVN_PROPID_##sym, 1 }
75
76
77 enum {
78 SVN_PROPID_baseline_relative_path = 1,
79 SVN_PROPID_md5_checksum,
80 SVN_PROPID_repository_uuid,
81 SVN_PROPID_deadprop_count,
82 SVN_PROPID_sha1_checksum
83 };
84
85
86 static const dav_liveprop_spec props[] =
87 {
88 /* ### don't worry about these for a bit */
89 #if 0
90 /* WebDAV properties */
91 SVN_RO_DAV_PROP(getcontentlanguage), /* ### make this r/w? */
92 #endif
93 SVN_RO_DAV_PROP(getcontentlength),
94 SVN_RO_DAV_PROP(getcontenttype), /* ### make this r/w? */
95 SVN_RO_DAV_PROP(getetag),
96 SVN_RO_DAV_PROP(creationdate),
97 SVN_RO_DAV_PROP(getlastmodified),
98
99 /* DeltaV properties */
100 SVN_RO_DAV_PROP2(baseline_collection, baseline-collection),
101 SVN_RO_DAV_PROP2(checked_in, checked-in),
102 SVN_RO_DAV_PROP2(version_controlled_configuration,
103 version-controlled-configuration),
104 SVN_RO_DAV_PROP2(version_name, version-name),
105 SVN_RO_DAV_PROP2(creator_displayname, creator-displayname),
106 SVN_RO_DAV_PROP2(auto_version, auto-version),
107
108 /* SVN properties */
109 SVN_RO_SVN_PROP(baseline_relative_path, baseline-relative-path),
110 SVN_RO_SVN_PROP(md5_checksum, md5-checksum),
111 SVN_RO_SVN_PROP(repository_uuid, repository-uuid),
112 SVN_RO_SVN_PROP(deadprop_count, deadprop-count),
113 SVN_RO_SVN_PROP(sha1_checksum, sha1-checksum),
114
115 { 0 } /* sentinel */
116 };
117
118
119 /* Set *PROPVAL to the value for the revision property PROPNAME on
120 COMMITTED_REV, in the repository identified by RESOURCE, if
121 RESOURCE's path is readable. If it is not readable, set *PROPVAL
122 to NULL and return SVN_NO_ERROR. Use POOL for temporary
123 allocations and the allocation of *PROPVAL.
124
125 Note that this function does not check the readability of the
126 revision property, but the readability of a path. The true
127 readability of a revision property is determined by investigating
128 the readability of all changed paths in the revision. For certain
129 revision properties (e.g. svn:author and svn:date) to be readable,
130 it is enough if at least one changed path is readable. When we
131 already have a changed path, we can skip the check for the other
132 changed paths in the revision and save a lot of work. This means
133 that we will make a mistake when our path is unreadable and another
134 changed path is readable, but we will at least only hide too much
135 and not leak any protected properties.
136
137 WARNING: This method of only checking the readability of a path is
138 only valid to get revision properties for which it is enough if at
139 least one changed path is readable. Using this function to get
140 revision properties for which all changed paths must be readable
141 might leak protected information because we will only test the
142 readability of a single changed path.
143 */
144 static svn_error_t *
get_path_revprop(svn_string_t ** propval,const dav_resource * resource,svn_revnum_t committed_rev,const char * propname,apr_pool_t * pool)145 get_path_revprop(svn_string_t **propval,
146 const dav_resource *resource,
147 svn_revnum_t committed_rev,
148 const char *propname,
149 apr_pool_t *pool)
150 {
151 *propval = NULL;
152
153 if (! dav_svn__allow_read_resource(resource, committed_rev, pool))
154 return SVN_NO_ERROR;
155
156 /* Get the property of the created revision. The authz is already
157 performed, so we don't need to do it here too. */
158 return svn_repos_fs_revision_prop(propval,
159 resource->info->repos->repos,
160 committed_rev,
161 propname,
162 NULL, NULL, pool);
163 }
164
165
166 enum time_format {
167 time_format_iso8601,
168 time_format_rfc1123
169 };
170
171
172 /* Given a mod_dav_svn @a resource, set @a *timeval and @a *datestring
173 to the last-modified-time of the resource. The datestring will be
174 formatted according to @a format. Use @a pool for both
175 scratchwork, and to allocate @a *datestring.
176
177 If @a timeval or @a datestring is NULL, don't touch it.
178
179 Return zero on success, non-zero if an error occurs. */
180 static int
get_last_modified_time(const char ** datestring,apr_time_t * timeval,const dav_resource * resource,enum time_format format,apr_pool_t * pool)181 get_last_modified_time(const char **datestring,
182 apr_time_t *timeval,
183 const dav_resource *resource,
184 enum time_format format,
185 apr_pool_t *pool)
186 {
187 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
188 svn_string_t *committed_date = NULL;
189 svn_error_t *serr;
190 apr_time_t timeval_tmp;
191
192 if ((datestring == NULL) && (timeval == NULL))
193 return 0;
194
195 if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION)
196 {
197 /* A baseline URI. */
198 committed_rev = resource->info->root.rev;
199 }
200 else if (resource->type == DAV_RESOURCE_TYPE_REGULAR
201 || resource->type == DAV_RESOURCE_TYPE_WORKING
202 || resource->type == DAV_RESOURCE_TYPE_VERSION)
203 {
204 serr = svn_fs_node_created_rev(&committed_rev,
205 resource->info->root.root,
206 resource->info->repos_path, pool);
207 if (serr != NULL)
208 {
209 svn_error_clear(serr);
210 return 1;
211 }
212 }
213 else
214 {
215 /* unsupported resource kind -- has no mod-time */
216 return 1;
217 }
218
219 serr = get_path_revprop(&committed_date,
220 resource,
221 committed_rev,
222 SVN_PROP_REVISION_DATE,
223 pool);
224 if (serr)
225 {
226 svn_error_clear(serr);
227 return 1;
228 }
229
230 if (committed_date == NULL)
231 return 1;
232
233 /* return the ISO8601 date as an apr_time_t */
234 serr = svn_time_from_cstring(&timeval_tmp, committed_date->data, pool);
235 if (serr != NULL)
236 {
237 svn_error_clear(serr);
238 return 1;
239 }
240
241 if (timeval)
242 *timeval = timeval_tmp;
243
244 if (! datestring)
245 return 0;
246
247 if (format == time_format_iso8601)
248 {
249 *datestring = committed_date->data;
250 }
251 else if (format == time_format_rfc1123)
252 {
253 apr_time_exp_t tms;
254 apr_status_t status;
255
256 /* convert the apr_time_t into an apr_time_exp_t */
257 status = apr_time_exp_gmt(&tms, timeval_tmp);
258 if (status != APR_SUCCESS)
259 return 1;
260
261 /* stolen from dav/fs/repos.c :-) */
262 *datestring = apr_psprintf(pool, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
263 apr_day_snames[tms.tm_wday],
264 tms.tm_mday, apr_month_snames[tms.tm_mon],
265 tms.tm_year + 1900,
266 tms.tm_hour, tms.tm_min, tms.tm_sec);
267 }
268 else /* unknown time format */
269 {
270 return 1;
271 }
272
273 return 0;
274 }
275
276 static dav_prop_insert
insert_prop_internal(const dav_resource * resource,int propid,dav_prop_insert what,apr_text_header * phdr,apr_pool_t * result_pool,apr_pool_t * scratch_pool)277 insert_prop_internal(const dav_resource *resource,
278 int propid,
279 dav_prop_insert what,
280 apr_text_header *phdr,
281 apr_pool_t *result_pool,
282 apr_pool_t *scratch_pool)
283 {
284 const char *value = NULL;
285 const char *s;
286 const dav_liveprop_spec *info;
287 long global_ns;
288 svn_error_t *serr;
289
290 /* ### TODO proper errors */
291 static const char *const error_value = "###error###";
292
293 /*
294 ** Almost none of the SVN provider properties are defined if the
295 ** resource does not exist. We do need to return the one VCC
296 ** property and baseline-relative-path on lock-null resources,
297 ** however, so that svn clients can run 'svn unlock' and 'svn info'
298 ** on these things.
299 **
300 ** Even though we state that the SVN properties are not defined, the
301 ** client cannot store dead values -- we deny that thru the is_writable
302 ** hook function.
303 */
304 if ((! resource->exists)
305 && (propid != DAV_PROPID_version_controlled_configuration)
306 && (propid != SVN_PROPID_baseline_relative_path))
307 return DAV_PROP_INSERT_NOTSUPP;
308
309 /* ### we may want to respond to DAV_PROPID_resourcetype for PRIVATE
310 ### resources. need to think on "proper" interaction with mod_dav */
311
312 switch (propid)
313 {
314 case DAV_PROPID_getlastmodified:
315 case DAV_PROPID_creationdate:
316 {
317 /* In subversion terms, the date attached to a file's CR is
318 the true "last modified" time. However, we're defining
319 creationdate in the same way. IMO, the "creationdate" is
320 really the date attached to the revision in which the item
321 *first* came into existence; this would found by tracing
322 back through the log of the file -- probably via
323 svn_fs_revisions_changed. gstein, is it a bad thing that
324 we're currently using 'creationdate' to mean the same thing
325 as 'last modified date'? */
326 const char *datestring;
327 apr_time_t timeval;
328 enum time_format format;
329
330 /* ### for now, our global VCC has no such property. */
331 if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
332 && (resource->info->restype == DAV_SVN_RESTYPE_VCC
333 || resource->info->restype == DAV_SVN_RESTYPE_ME))
334 {
335 return DAV_PROP_INSERT_NOTSUPP;
336 }
337
338 if (propid == DAV_PROPID_creationdate)
339 {
340 /* Return an ISO8601 date; this is what the svn client
341 expects, and rfc2518 demands it. */
342 format = time_format_iso8601;
343 }
344 else /* propid == DAV_PROPID_getlastmodified */
345 {
346 format = time_format_rfc1123;
347 }
348
349 if (0 != get_last_modified_time(&datestring, &timeval,
350 resource, format, scratch_pool))
351 {
352 return DAV_PROP_INSERT_NOTDEF;
353 }
354
355 value = apr_xml_quote_string(scratch_pool, datestring, 1);
356 break;
357 }
358
359 case DAV_PROPID_creator_displayname:
360 {
361 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
362 svn_string_t *last_author = NULL;
363
364 /* ### for now, our global VCC has no such property. */
365 if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
366 && (resource->info->restype == DAV_SVN_RESTYPE_VCC
367 || resource->info->restype == DAV_SVN_RESTYPE_ME))
368 {
369 return DAV_PROP_INSERT_NOTSUPP;
370 }
371
372 if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION)
373 {
374 /* A baseline URI. */
375 committed_rev = resource->info->root.rev;
376 }
377 else if (resource->type == DAV_RESOURCE_TYPE_REGULAR
378 || resource->type == DAV_RESOURCE_TYPE_WORKING
379 || resource->type == DAV_RESOURCE_TYPE_VERSION)
380 {
381 /* Get the CR field out of the node's skel. Notice that the
382 root object might be an ID root -or- a revision root. */
383 serr = svn_fs_node_created_rev(&committed_rev,
384 resource->info->root.root,
385 resource->info->repos_path,
386 scratch_pool);
387 if (serr != NULL)
388 {
389 ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
390 resource->info->r,
391 "Can't get created-rev of '%s': "
392 "%s",
393 resource->info->repos_path,
394 serr->message);
395 svn_error_clear(serr);
396 value = error_value;
397 break;
398 }
399 }
400 else
401 {
402 return DAV_PROP_INSERT_NOTSUPP;
403 }
404
405 serr = get_path_revprop(&last_author,
406 resource,
407 committed_rev,
408 SVN_PROP_REVISION_AUTHOR,
409 scratch_pool);
410 if (serr)
411 {
412 ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
413 resource->info->r,
414 "Can't get author of r%ld: "
415 "%s",
416 committed_rev,
417 serr->message);
418 svn_error_clear(serr);
419 value = error_value;
420 break;
421 }
422
423 if (last_author == NULL)
424 return DAV_PROP_INSERT_NOTDEF;
425
426 /* We need to sanitize the LAST_AUTHOR. */
427 value = dav_svn__fuzzy_escape_author(last_author->data,
428 resource->info->repos->is_svn_client,
429 scratch_pool, scratch_pool);
430 break;
431 }
432
433 case DAV_PROPID_getcontentlanguage:
434 /* ### need something here */
435 return DAV_PROP_INSERT_NOTSUPP;
436 break;
437
438 case DAV_PROPID_getcontentlength:
439 {
440 svn_filesize_t len = 0;
441
442 /* our property, but not defined on collection resources */
443 if (resource->type == DAV_RESOURCE_TYPE_ACTIVITY
444 || resource->collection || resource->baselined)
445 return DAV_PROP_INSERT_NOTSUPP;
446
447 serr = svn_fs_file_length(&len, resource->info->root.root,
448 resource->info->repos_path, scratch_pool);
449 if (serr != NULL)
450 {
451 ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
452 resource->info->r,
453 "Can't get filesize of '%s': "
454 "%s",
455 resource->info->repos_path,
456 serr->message);
457 svn_error_clear(serr);
458 value = error_value;
459 break;
460 }
461
462 value = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, len);
463 break;
464 }
465
466 case DAV_PROPID_getcontenttype:
467 {
468 /* The subversion client assumes that any file without an
469 svn:mime-type property is of type text/plain. So it seems
470 safe (and consistent) to assume the same on the server. */
471 svn_string_t *pval;
472 const char *mime_type = NULL;
473
474 if (resource->type == DAV_RESOURCE_TYPE_ACTIVITY
475 || (resource->baselined
476 && resource->type == DAV_RESOURCE_TYPE_VERSION))
477 return DAV_PROP_INSERT_NOTSUPP;
478
479 if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
480 && (resource->info->restype == DAV_SVN_RESTYPE_VCC
481 || resource->info->restype == DAV_SVN_RESTYPE_ME))
482 {
483 return DAV_PROP_INSERT_NOTSUPP;
484 }
485
486 if (resource->collection) /* defaults for directories */
487 {
488 if (resource->info->repos->xslt_uri)
489 mime_type = "text/xml";
490 else
491 mime_type = "text/html; charset=UTF-8";
492 }
493 else
494 {
495 if ((serr = svn_fs_node_prop(&pval, resource->info->root.root,
496 resource->info->repos_path,
497 SVN_PROP_MIME_TYPE, scratch_pool)))
498 {
499 svn_error_clear(serr);
500 pval = NULL;
501 }
502
503 if (pval)
504 mime_type = pval->data;
505 else if ((! resource->info->repos->is_svn_client)
506 && resource->info->r->content_type)
507 mime_type = resource->info->r->content_type;
508 else
509 mime_type = "text/plain";
510
511 if ((serr = svn_mime_type_validate(mime_type, scratch_pool)))
512 {
513 /* Probably serr->apr == SVN_ERR_BAD_MIME_TYPE, but
514 there's no point even checking. No matter what the
515 error is, we can't claim to have a mime type for
516 this resource. */
517 ap_log_rerror(APLOG_MARK, APLOG_WARNING, serr->apr_err,
518 resource->info->r, "%s", serr->message);
519 svn_error_clear(serr);
520 return DAV_PROP_INSERT_NOTDEF;
521 }
522 }
523
524 value = mime_type;
525 break;
526 }
527
528 case DAV_PROPID_getetag:
529 if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
530 && (resource->info->restype == DAV_SVN_RESTYPE_VCC
531 || resource->info->restype == DAV_SVN_RESTYPE_ME))
532 {
533 return DAV_PROP_INSERT_NOTSUPP;
534 }
535
536 value = dav_svn__getetag(resource, scratch_pool);
537 break;
538
539 case DAV_PROPID_auto_version:
540 /* we only support one autoversioning behavior, and thus only
541 return this one static value; someday when we support
542 locking, there are other possible values/behaviors for this. */
543 if (resource->info->repos->autoversioning)
544 value = "DAV:checkout-checkin";
545 else
546 return DAV_PROP_INSERT_NOTDEF;
547 break;
548
549 case DAV_PROPID_baseline_collection:
550 /* only defined for Baselines */
551 /* ### whoops. also defined for a VCC. deal with it later. */
552 if (resource->type != DAV_RESOURCE_TYPE_VERSION || !resource->baselined)
553 return DAV_PROP_INSERT_NOTSUPP;
554 value = dav_svn__build_uri(resource->info->repos, DAV_SVN__BUILD_URI_BC,
555 resource->info->root.rev, NULL,
556 TRUE /* add_href */, scratch_pool);
557 break;
558
559 case DAV_PROPID_checked_in:
560 /* only defined for VCRs (in the public space and in a BC space) */
561 /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */
562 if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
563 && (resource->info->restype == DAV_SVN_RESTYPE_VCC
564 || resource->info->restype == DAV_SVN_RESTYPE_ME))
565 {
566 svn_revnum_t revnum;
567
568 serr = dav_svn__get_youngest_rev(&revnum, resource->info->repos,
569 scratch_pool);
570 if (serr != NULL)
571 {
572 ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
573 resource->info->r,
574 "Can't get youngest revision in '%s': "
575 "%s",
576 svn_fs_path(resource->info->repos->fs,
577 scratch_pool),
578 serr->message);
579 svn_error_clear(serr);
580 value = error_value;
581 break;
582 }
583 s = dav_svn__build_uri(resource->info->repos,
584 DAV_SVN__BUILD_URI_BASELINE,
585 revnum, NULL, FALSE /* add_href */,
586 scratch_pool);
587 value = apr_psprintf(scratch_pool, "<D:href>%s</D:href>",
588 apr_xml_quote_string(scratch_pool, s, 1));
589 }
590 else if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
591 {
592 /* not defined for this resource type */
593 return DAV_PROP_INSERT_NOTSUPP;
594 }
595 else
596 {
597 svn_revnum_t rev_to_use =
598 dav_svn__get_safe_cr(resource->info->root.root,
599 resource->info->repos_path, scratch_pool);
600
601 s = dav_svn__build_uri(resource->info->repos,
602 DAV_SVN__BUILD_URI_VERSION,
603 rev_to_use, resource->info->repos_path,
604 FALSE /* add_href */, scratch_pool);
605 value = apr_psprintf(scratch_pool, "<D:href>%s</D:href>",
606 apr_xml_quote_string(scratch_pool, s, 1));
607 }
608 break;
609
610 case DAV_PROPID_version_controlled_configuration:
611 /* only defined for VCRs */
612 /* ### VCRs within the BC should not have this property! */
613 /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */
614 if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
615 return DAV_PROP_INSERT_NOTSUPP;
616 value = dav_svn__build_uri(resource->info->repos, DAV_SVN__BUILD_URI_VCC,
617 SVN_IGNORED_REVNUM, NULL,
618 TRUE /* add_href */, scratch_pool);
619 break;
620
621 case DAV_PROPID_version_name:
622 /* only defined for Version Resources and Baselines */
623 /* ### whoops. also defined for VCRs. deal with it later. */
624 if ((resource->type != DAV_RESOURCE_TYPE_VERSION)
625 && (! resource->versioned))
626 return DAV_PROP_INSERT_NOTSUPP;
627
628 if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
629 && (resource->info->restype == DAV_SVN_RESTYPE_VCC
630 || resource->info->restype == DAV_SVN_RESTYPE_ME))
631 {
632 return DAV_PROP_INSERT_NOTSUPP;
633 }
634
635 if (resource->baselined)
636 {
637 /* just the revision number for baselines */
638 value = apr_psprintf(scratch_pool, "%ld",
639 resource->info->root.rev);
640 }
641 else
642 {
643 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
644
645 /* Get the CR field out of the node's skel. Notice that the
646 root object might be an ID root -or- a revision root. */
647 serr = svn_fs_node_created_rev(&committed_rev,
648 resource->info->root.root,
649 resource->info->repos_path,
650 scratch_pool);
651 if (serr != NULL)
652 {
653 ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
654 resource->info->r,
655 "Can't get created-rev of '%s': "
656 "%s",
657 resource->info->repos_path,
658 serr->message);
659 svn_error_clear(serr);
660 value = error_value;
661 break;
662 }
663
664 /* Convert the revision into a quoted string */
665 s = apr_psprintf(scratch_pool, "%ld", committed_rev);
666 value = apr_xml_quote_string(scratch_pool, s, 1);
667 }
668 break;
669
670 case SVN_PROPID_baseline_relative_path:
671 /* only defined for VCRs */
672 /* ### VCRs within the BC should not have this property! */
673 /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */
674 if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
675 return DAV_PROP_INSERT_NOTSUPP;
676
677 /* drop the leading slash, so it is relative */
678 s = resource->info->repos_path + 1;
679 value = apr_xml_quote_string(scratch_pool, s, 1);
680 break;
681
682 case SVN_PROPID_md5_checksum:
683 case SVN_PROPID_sha1_checksum:
684 if ((! resource->collection)
685 && (! resource->baselined)
686 && (resource->type == DAV_RESOURCE_TYPE_REGULAR
687 || resource->type == DAV_RESOURCE_TYPE_WORKING
688 || resource->type == DAV_RESOURCE_TYPE_VERSION))
689 {
690 svn_checksum_t *checksum;
691 svn_checksum_kind_t checksum_kind;
692
693 if (propid == SVN_PROPID_md5_checksum)
694 {
695 checksum_kind = svn_checksum_md5;
696 }
697 else
698 {
699 checksum_kind = svn_checksum_sha1;
700 }
701
702 serr = svn_fs_file_checksum(&checksum, checksum_kind,
703 resource->info->root.root,
704 resource->info->repos_path, TRUE,
705 scratch_pool);
706 if (serr && serr->apr_err == SVN_ERR_FS_NOT_FILE)
707 {
708 /* It should not happen since we're already checked
709 RESOURCE->COLLECTION, but svn_fs_check_path() call
710 was added in r1239596 for some reason. Keep it for
711 now. */
712 svn_error_clear(serr);
713 return DAV_PROP_INSERT_NOTSUPP;
714 }
715 else if (serr)
716 {
717 ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
718 resource->info->r,
719 "Can't fetch or compute %s checksum of '%s': "
720 "%s",
721 checksum_kind == svn_checksum_md5
722 ? "MD5"
723 : "SHA1",
724 resource->info->repos_path,
725 serr->message);
726 svn_error_clear(serr);
727 value = error_value;
728 break;
729 }
730
731 value = svn_checksum_to_cstring(checksum, scratch_pool);
732
733 if (! value)
734 return DAV_PROP_INSERT_NOTSUPP;
735 }
736 else
737 return DAV_PROP_INSERT_NOTSUPP;
738
739 break;
740
741 case SVN_PROPID_repository_uuid:
742 serr = svn_fs_get_uuid(resource->info->repos->fs, &value, scratch_pool);
743 if (serr != NULL)
744 {
745 ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
746 resource->info->r,
747 "Can't fetch UUID of '%s': "
748 "%s",
749 svn_fs_path(resource->info->repos->fs, scratch_pool),
750 serr->message);
751 svn_error_clear(serr);
752 value = error_value;
753 break;
754 }
755 break;
756
757 case SVN_PROPID_deadprop_count:
758 {
759 svn_boolean_t has_props;
760
761 if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
762 return DAV_PROP_INSERT_NOTSUPP;
763
764 /* Retrieving the actual properties is quite expensive while
765 svn clients only want to know if there are properties, by
766 using this svn defined property.
767
768 Our and and SvnKit's implementation of the ra layer check
769 for '> 0' to provide the boolean if the node has custom
770 properties or not, so starting with 1.9 we just provide
771 "1" or "0".
772 */
773 serr = svn_fs_node_has_props(&has_props,
774 resource->info->root.root,
775 resource->info->repos_path,
776 scratch_pool);
777
778 if (serr != NULL)
779 {
780 ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
781 resource->info->r,
782 "Can't fetch has properties on '%s': "
783 "%s",
784 resource->info->repos_path,
785 serr->message);
786 svn_error_clear(serr);
787 value = error_value;
788 break;
789 }
790
791 value = has_props ? "1" : "0";
792 break;
793 }
794
795 default:
796 /* ### what the heck was this property? */
797 return DAV_PROP_INSERT_NOTDEF;
798 }
799
800 /* assert: value != NULL */
801
802 /* get the information and global NS index for the property */
803 global_ns = dav_get_liveprop_info(propid, &dav_svn__liveprop_group, &info);
804
805 /* assert: info != NULL && info->name != NULL */
806
807 if (what == DAV_PROP_INSERT_NAME
808 || (what == DAV_PROP_INSERT_VALUE && *value == '\0')) {
809 s = apr_psprintf(result_pool, "<lp%ld:%s/>" DEBUG_CR, global_ns,
810 info->name);
811 }
812 else if (what == DAV_PROP_INSERT_VALUE) {
813 s = apr_psprintf(result_pool, "<lp%ld:%s>%s</lp%ld:%s>" DEBUG_CR,
814 global_ns, info->name, value, global_ns, info->name);
815 }
816 else {
817 /* assert: what == DAV_PROP_INSERT_SUPPORTED */
818 s = apr_psprintf(result_pool,
819 "<D:supported-live-property D:name=\"%s\" "
820 "D:namespace=\"%s\"/>" DEBUG_CR,
821 info->name, namespace_uris[info->ns]);
822 }
823 apr_text_append(result_pool, phdr, s);
824
825 /* we inserted whatever was asked for */
826 return what;
827 }
828
829 static dav_prop_insert
insert_prop(const dav_resource * resource,int propid,dav_prop_insert what,apr_text_header * phdr)830 insert_prop(const dav_resource *resource,
831 int propid,
832 dav_prop_insert what,
833 apr_text_header *phdr)
834 {
835 apr_pool_t *result_pool = resource->pool;
836 apr_pool_t *scratch_pool;
837 dav_prop_insert rv;
838
839 /* Create subpool and destroy on return, because mod_dav doesn't provide
840 scratch pool for insert_prop() callback. */
841 scratch_pool = svn_pool_create(result_pool);
842
843 rv = insert_prop_internal(resource, propid, what, phdr,
844 result_pool, scratch_pool);
845
846 svn_pool_destroy(scratch_pool);
847 return rv;
848 }
849
850 static int
is_writable(const dav_resource * resource,int propid)851 is_writable(const dav_resource *resource, int propid)
852 {
853 const dav_liveprop_spec *info = NULL;
854
855 (void) dav_get_liveprop_info(propid, &dav_svn__liveprop_group, &info);
856 return info ? info->is_writable : FALSE;
857 }
858
859
860 static dav_error *
patch_validate(const dav_resource * resource,const apr_xml_elem * elem,int operation,void ** context,int * defer_to_dead)861 patch_validate(const dav_resource *resource,
862 const apr_xml_elem *elem,
863 int operation,
864 void **context,
865 int *defer_to_dead)
866 {
867 /* NOTE: this function will not be called unless/until we have
868 modifiable (writable) live properties. */
869 return NULL;
870 }
871
872
873 static dav_error *
patch_exec(const dav_resource * resource,const apr_xml_elem * elem,int operation,void * context,dav_liveprop_rollback ** rollback_ctx)874 patch_exec(const dav_resource *resource,
875 const apr_xml_elem *elem,
876 int operation,
877 void *context,
878 dav_liveprop_rollback **rollback_ctx)
879 {
880 /* NOTE: this function will not be called unless/until we have
881 modifiable (writable) live properties. */
882 return NULL;
883 }
884
885
886 static void
patch_commit(const dav_resource * resource,int operation,void * context,dav_liveprop_rollback * rollback_ctx)887 patch_commit(const dav_resource *resource,
888 int operation,
889 void *context,
890 dav_liveprop_rollback *rollback_ctx)
891 {
892 /* NOTE: this function will not be called unless/until we have
893 modifiable (writable) live properties. */
894 }
895
896 static dav_error *
patch_rollback(const dav_resource * resource,int operation,void * context,dav_liveprop_rollback * rollback_ctx)897 patch_rollback(const dav_resource *resource,
898 int operation,
899 void *context,
900 dav_liveprop_rollback *rollback_ctx)
901 {
902 /* NOTE: this function will not be called unless/until we have
903 modifiable (writable) live properties. */
904 return NULL;
905 }
906
907
908 static const dav_hooks_liveprop hooks_liveprop = {
909 insert_prop,
910 is_writable,
911 namespace_uris,
912 patch_validate,
913 patch_exec,
914 patch_commit,
915 patch_rollback,
916 };
917
918
919 const dav_liveprop_group dav_svn__liveprop_group =
920 {
921 props,
922 namespace_uris,
923 &hooks_liveprop
924 };
925
926
927 void
dav_svn__gather_propsets(apr_array_header_t * uris)928 dav_svn__gather_propsets(apr_array_header_t *uris)
929 {
930 /* ### what should we use for a URL to describe the available prop set? */
931 /* ### for now... nothing. we will *only* have DAV properties */
932 #if 0
933 *(const char **)apr_array_push(uris) =
934 "<http://subversion.tigris.org/dav/propset/svn/1>";
935 #endif
936 }
937
938
939 int
dav_svn__find_liveprop(const dav_resource * resource,const char * ns_uri,const char * name,const dav_hooks_liveprop ** hooks)940 dav_svn__find_liveprop(const dav_resource *resource,
941 const char *ns_uri,
942 const char *name,
943 const dav_hooks_liveprop **hooks)
944 {
945 /* don't try to find any liveprops if this isn't "our" resource */
946 if (resource->hooks != &dav_svn__hooks_repository)
947 return 0;
948
949 return dav_do_find_liveprop(ns_uri, name, &dav_svn__liveprop_group, hooks);
950 }
951
952
953 void
dav_svn__insert_all_liveprops(request_rec * r,const dav_resource * resource,dav_prop_insert what,apr_text_header * phdr)954 dav_svn__insert_all_liveprops(request_rec *r,
955 const dav_resource *resource,
956 dav_prop_insert what,
957 apr_text_header *phdr)
958 {
959 const dav_liveprop_spec *spec;
960 apr_pool_t *iterpool;
961
962 /* don't insert any liveprops if this isn't "our" resource */
963 if (resource->hooks != &dav_svn__hooks_repository)
964 return;
965
966 if (!resource->exists)
967 {
968 /* a lock-null resource */
969 /*
970 ** ### technically, we should insert empty properties. dunno offhand
971 ** ### what part of the spec said this, but it was essentially thus:
972 ** ### "the properties should be defined, but may have no value".
973 */
974 return;
975 }
976
977 iterpool = svn_pool_create(resource->pool);
978 for (spec = props; spec->name != NULL; ++spec)
979 {
980 svn_pool_clear(iterpool);
981 (void) insert_prop_internal(resource, spec->propid, what, phdr,
982 resource->pool, iterpool);
983 }
984 svn_pool_destroy(iterpool);
985
986 /* ### we know the others aren't defined as liveprops */
987 }
988