source: valtobtest/subversion-1.6.2/subversion/mod_dav_svn/deadprops.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 19.1 KB
Line 
1/*
2 * deadprops.c: mod_dav_svn dead property provider functions for Subversion
3 *
4 * ====================================================================
5 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
6 *
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.
12 *
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 * ====================================================================
17 */
18
19#include <apr_hash.h>
20
21#include <httpd.h>
22#include <mod_dav.h>
23
24#include "svn_xml.h"
25#include "svn_pools.h"
26#include "svn_dav.h"
27#include "svn_base64.h"
28#include "svn_props.h"
29#include "private/svn_log.h"
30
31#include "dav_svn.h"
32
33
34struct dav_db {
35  const dav_resource *resource;
36  apr_pool_t *p;
37
38  /* the resource's properties that we are sequencing over */
39  apr_hash_t *props;
40  apr_hash_index_t *hi;
41
42  /* used for constructing repos-local names for properties */
43  svn_stringbuf_t *work;
44
45  /* passed to svn_repos_ funcs that fetch revprops. */
46  svn_repos_authz_func_t authz_read_func;
47  void *authz_read_baton;
48};
49
50
51struct dav_deadprop_rollback {
52  dav_prop_name name;
53  svn_string_t value;
54};
55
56
57/* retrieve the "right" string to use as a repos path */
58static const char *
59get_repos_path(struct dav_resource_private *info)
60{
61  return info->repos_path;
62}
63
64
65/* construct the repos-local name for the given DAV property name */
66static void
67get_repos_propname(dav_db *db,
68                   const dav_prop_name *name,
69                   const char **repos_propname)
70{
71  if (strcmp(name->ns, SVN_DAV_PROP_NS_SVN) == 0)
72    {
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;
77    }
78  else if (strcmp(name->ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
79    {
80      /* the name of a custom prop is just the name -- no ns URI */
81      *repos_propname = name->name;
82    }
83  else
84    {
85      *repos_propname = NULL;
86    }
87}
88
89
90static dav_error *
91get_value(dav_db *db, const dav_prop_name *name, svn_string_t **pvalue)
92{
93  const char *propname;
94  svn_error_t *serr;
95
96  /* get the repos-local name */
97  get_repos_propname(db, name, &propname);
98
99  if (propname == NULL)
100    {
101      /* we know these are not present. */
102      *pvalue = NULL;
103      return NULL;
104    }
105
106  /* ### if db->props exists, then try in there first */
107
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,
112                             propname, db->p);
113    else
114      serr = svn_repos_fs_revision_prop(pvalue,
115                                        db->resource->info-> repos->repos,
116                                        db->resource->info->root.rev, propname,
117                                        db->authz_read_func,
118                                        db->authz_read_baton, db->p);
119  else
120    serr = svn_fs_node_prop(pvalue, db->resource->info->root.root,
121                            get_repos_path(db->resource->info),
122                            propname, db->p);
123  if (serr != NULL)
124    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
125                                "could not fetch a property",
126                                db->resource->pool);
127
128  return NULL;
129}
130
131
132static dav_error *
133save_value(dav_db *db, const dav_prop_name *name, const svn_string_t *value)
134{
135  const char *propname;
136  svn_error_t *serr;
137
138  /* get the repos-local name */
139  get_repos_propname(db, name, &propname);
140
141  if (propname == NULL)
142    {
143      if (db->resource->info->repos->autoversioning)
144        /* ignore the unknown namespace of the incoming prop. */
145        propname = name->name;
146      else
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
150                             " namespaces.");
151    }
152
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);
158    else
159      {
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,
169           db->authz_read_func,
170           db->authz_read_baton,
171           db->resource->pool);
172
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,
177                                              propname,
178                                              db->resource->pool));
179      }
180  else
181    serr = svn_repos_fs_change_node_prop(db->resource->info->root.root,
182                                         get_repos_path(db->resource->info),
183                                         propname, value, db->resource->pool);
184  if (serr != NULL)
185    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
186                                NULL,
187                                db->resource->pool);
188
189  /* a change to the props was made; make sure our cached copy is gone */
190  db->props = NULL;
191
192  return NULL;
193}
194
195
196static dav_error *
197db_open(apr_pool_t *p,
198        const dav_resource *resource,
199        int ro,
200        dav_db **pdb)
201{
202  dav_db *db;
203  dav_svn__authz_read_baton *arb;
204
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)
211    {
212      *pdb = NULL;
213      return NULL;
214    }
215
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)
219    {
220      /* ### Exception: in violation of deltaV, we *are* allowing a
221         baseline resource to receive a proppatch, as a way of
222         changing unversioned rev props.  Remove this someday: see IZ #916. */
223      if (! (resource->baselined
224             && resource->type == DAV_RESOURCE_TYPE_VERSION))
225        return dav_new_error(p, HTTP_CONFLICT, 0,
226                             "Properties may only be changed on working "
227                             "resources.");
228    }
229
230  db = apr_pcalloc(p, sizeof(*db));
231
232  db->resource = resource;
233  db->p = svn_pool_create(p);
234
235  /* ### temp hack */
236  db->work = svn_stringbuf_ncreate("", 0, db->p);
237
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);
244
245  /* ### use RO and node's mutable status to look for an error? */
246
247  *pdb = db;
248
249  return NULL;
250}
251
252
253static void
254db_close(dav_db *db)
255{
256  svn_pool_destroy(db->p);
257}
258
259
260static dav_error *
261db_define_namespaces(dav_db *db, dav_xmlns_info *xi)
262{
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);
266
267  /* ### we don't have any other possible namespaces right now. */
268
269  return NULL;
270}
271
272static dav_error *
273db_output_value(dav_db *db,
274                const dav_prop_name *name,
275                dav_xmlns_info *xi,
276                apr_text_header *phdr,
277                int *found)
278{
279  const char *prefix;
280  const char *s;
281  svn_string_t *propval;
282  dav_error *err;
283  apr_pool_t *pool = db->resource->pool;
284
285  if ((err = get_value(db, name, &propval)) != NULL)
286    return err;
287
288  /* return whether the prop was found, then punt or handle it. */
289  *found = (propval != NULL);
290  if (propval == NULL)
291    return NULL;
292
293  if (strcmp(name->ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
294    prefix = "C:";
295  else
296    prefix = "S:";
297
298  if (propval->len == 0)
299    {
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);
303    }
304  else
305    {
306      /* add <prefix:name [V:encoding="base64"]>value</prefix:name> */
307      const char *xml_safe;
308      const char *encoding = "";
309
310      /* Ensure XML-safety of our property values before sending them
311         across the wire. */
312      if (! svn_xml_is_xml_safe(propval->data, propval->len))
313        {
314          const svn_string_t *enc_propval
315            = svn_base64_encode_string2(propval, TRUE, pool);
316          xml_safe = enc_propval->data;
317          encoding = apr_pstrcat(pool, " V:encoding=\"base64\"", NULL);
318        }
319      else
320        {
321          svn_stringbuf_t *xmlval = NULL;
322          svn_xml_escape_cdata_string(&xmlval, propval, pool);
323          xml_safe = xmlval->data;
324        }
325
326      s = apr_psprintf(pool, "<%s%s%s>", prefix, name->name, encoding);
327      apr_text_append(pool, phdr, s);
328
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);
332
333      s = apr_psprintf(pool, "</%s%s>" DEBUG_CR, prefix, name->name);
334      apr_text_append(pool, phdr, s);
335    }
336
337  return NULL;
338}
339
340
341static dav_error *
342db_map_namespaces(dav_db *db,
343                  const apr_array_header_t *namespaces,
344                  dav_namespace_map **mapping)
345{
346  /* we don't need a namespace mapping right now. nothing to do */
347  return NULL;
348}
349
350
351static dav_error *
352db_store(dav_db *db,
353         const dav_prop_name *name,
354         const apr_xml_elem *elem,
355         dav_namespace_map *mapping)
356{
357  const svn_string_t *propval;
358  apr_pool_t *pool = db->p;
359  apr_xml_attr *attr = elem->attr;
360
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. */
366
367  propval = svn_string_create
368    (dav_xml_get_cdata(elem, pool, 0 /* strip_white */), pool);
369
370  /* Check for special encodings of the property value. */
371  while (attr)
372    {
373      if (strcmp(attr->name, "encoding") == 0) /* ### namespace check? */
374        {
375          const char *enc_type = attr->value;
376
377          /* Handle known encodings here. */
378          if (enc_type && (strcmp(enc_type, "base64") == 0))
379            propval = svn_base64_decode_string(propval, pool);
380          else
381            return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0,
382                                 "Unknown property encoding");
383          break;
384        }
385      /* Next attribute, please. */
386      attr = attr->next;
387    }
388
389  return save_value(db, name, propval);
390}
391
392
393static dav_error *
394db_remove(dav_db *db, const dav_prop_name *name)
395{
396  svn_error_t *serr;
397  const char *propname;
398
399  /* get the repos-local name */
400  get_repos_propname(db, name, &propname);
401
402  /* ### non-svn props aren't in our repos, so punt for now */
403  if (propname == NULL)
404    return NULL;
405
406  /* Working Baseline or Working (Version) Resource */
407  if (db->resource->baselined)
408    if (db->resource->working)
409      serr = svn_repos_fs_change_txn_prop(db->resource->info->root.txn,
410                                          propname, NULL, db->resource->pool);
411    else
412      /* ### VIOLATING deltaV: you can't proppatch a baseline, it's
413         not a working resource!  But this is how we currently
414         (hackily) allow the svn client to change unversioned rev
415         props.  See issue #916. */
416      serr = svn_repos_fs_change_rev_prop3(db->resource->info->repos->repos,
417                                           db->resource->info->root.rev,
418                                           db->resource->info->repos->username,
419                                           propname, NULL, TRUE, TRUE,
420                                           db->authz_read_func,
421                                           db->authz_read_baton,
422                                           db->resource->pool);
423  else
424    serr = svn_repos_fs_change_node_prop(db->resource->info->root.root,
425                                         get_repos_path(db->resource->info),
426                                         propname, NULL, db->resource->pool);
427  if (serr != NULL)
428    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
429                                "could not remove a property",
430                                db->resource->pool);
431
432  /* a change to the props was made; make sure our cached copy is gone */
433  db->props = NULL;
434
435  return NULL;
436}
437
438
439static int
440db_exists(dav_db *db, const dav_prop_name *name)
441{
442  const char *propname;
443  svn_string_t *propval;
444  svn_error_t *serr;
445  int retval;
446
447  /* get the repos-local name */
448  get_repos_propname(db, name, &propname);
449
450  /* ### non-svn props aren't in our repos */
451  if (propname == NULL)
452    return 0;
453
454  /* Working Baseline, Baseline, or (Working) Version resource */
455  if (db->resource->baselined)
456    if (db->resource->type == DAV_RESOURCE_TYPE_WORKING)
457      serr = svn_fs_txn_prop(&propval, db->resource->info->root.txn,
458                             propname, db->p);
459    else
460      serr = svn_repos_fs_revision_prop(&propval,
461                                        db->resource->info->repos->repos,
462                                        db->resource->info->root.rev,
463                                        propname,
464                                        db->authz_read_func,
465                                        db->authz_read_baton, db->p);
466  else
467    serr = svn_fs_node_prop(&propval, db->resource->info->root.root,
468                            get_repos_path(db->resource->info),
469                            propname, db->p);
470
471  /* ### try and dispose of the value? */
472
473  retval = (serr == NULL && propval != NULL);
474  svn_error_clear(serr);
475  return retval;
476}
477
478static void get_name(dav_db *db, dav_prop_name *pname)
479{
480  if (db->hi == NULL)
481    {
482      pname->ns = pname->name = NULL;
483    }
484  else
485    {
486      const void *name;
487
488      apr_hash_this(db->hi, &name, NULL, NULL);
489
490#define PREFIX_LEN (sizeof(SVN_PROP_PREFIX) - 1)
491      if (strncmp(name, SVN_PROP_PREFIX, PREFIX_LEN) == 0)
492#undef PREFIX_LEN
493        {
494          pname->ns = SVN_DAV_PROP_NS_SVN;
495          pname->name = (const char *)name + 4;
496        }
497      else
498        {
499          pname->ns = SVN_DAV_PROP_NS_CUSTOM;
500          pname->name = name;
501        }
502    }
503}
504
505
506static dav_error *
507db_first_name(dav_db *db, dav_prop_name *pname)
508{
509  /* for operational logging */
510  const char *action = NULL;
511
512  /* if we don't have a copy of the properties, then get one */
513  if (db->props == NULL)
514    {
515      svn_error_t *serr;
516
517      /* Working Baseline, Baseline, or (Working) Version resource */
518      if (db->resource->baselined)
519        {
520          if (db->resource->type == DAV_RESOURCE_TYPE_WORKING)
521            serr = svn_fs_txn_proplist(&db->props,
522                                       db->resource->info->root.txn,
523                                       db->p);
524          else
525            {
526              action = svn_log__rev_proplist(db->resource->info->root.rev,
527                                             db->resource->pool);
528              serr = svn_repos_fs_revision_proplist
529                (&db->props,
530                 db->resource->info->repos->repos,
531                 db->resource->info->root.rev,
532                 db->authz_read_func,
533                 db->authz_read_baton,
534                 db->p);
535            }
536        }
537      else
538        {
539          svn_node_kind_t kind;
540          serr = svn_fs_node_proplist(&db->props,
541                                      db->resource->info->root.root,
542                                      get_repos_path(db->resource->info),
543                                      db->p);
544          if (! serr)
545            serr = svn_fs_check_path(&kind, db->resource->info->root.root,
546                                     get_repos_path(db->resource->info),
547                                     db->p);
548
549          if (! serr)
550            {
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);
555              else
556                action = svn_log__get_file(db->resource->info->repos_path,
557                                           db->resource->info->root.rev,
558                                           FALSE, TRUE, db->resource->pool);
559            }
560        }
561      if (serr != NULL)
562        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
563                                    "could not begin sequencing through "
564                                    "properties",
565                                    db->resource->pool);
566    }
567
568  /* begin the iteration over the hash */
569  db->hi = apr_hash_first(db->p, db->props);
570
571  /* fetch the first key */
572  get_name(db, pname);
573
574  /* If we have a high-level action to log, do so. */
575  if (action != NULL)
576    dav_svn__operational_log(db->resource->info, action);
577
578  return NULL;
579}
580
581
582static dav_error *
583db_next_name(dav_db *db, dav_prop_name *pname)
584{
585  /* skip to the next hash entry */
586  if (db->hi != NULL)
587    db->hi = apr_hash_next(db->hi);
588
589  /* fetch the key */
590  get_name(db, pname);
591
592  return NULL;
593}
594
595
596static dav_error *
597db_get_rollback(dav_db *db,
598                const dav_prop_name *name,
599                dav_deadprop_rollback **prollback)
600{
601  dav_error *err;
602  dav_deadprop_rollback *ddp;
603  svn_string_t *propval;
604
605  if ((err = get_value(db, name, &propval)) != NULL)
606    return err;
607
608  ddp = apr_palloc(db->p, sizeof(*ddp));
609  ddp->name = *name;
610  ddp->value.data = propval ? propval->data : NULL;
611  ddp->value.len = propval ? propval->len : 0;
612
613  *prollback = ddp;
614  return NULL;
615}
616
617
618static dav_error *
619db_apply_rollback(dav_db *db, dav_deadprop_rollback *rollback)
620{
621  if (rollback->value.data == NULL)
622    {
623      return db_remove(db, &rollback->name);
624    }
625
626  return save_value(db, &rollback->name, &rollback->value);
627}
628
629
630const dav_hooks_propdb dav_svn__hooks_propdb = {
631  db_open,
632  db_close,
633  db_define_namespaces,
634  db_output_value,
635  db_map_namespaces,
636  db_store,
637  db_remove,
638  db_exists,
639  db_first_name,
640  db_next_name,
641  db_get_rollback,
642  db_apply_rollback,
643};
Note: See TracBrowser for help on using the repository browser.