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