source: valtobtest/subversion-1.6.2/subversion/mod_dav_svn/repos.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: 128.7 KB
Line 
1/*
2 * repos.c: mod_dav_svn repository provider functions for Subversion
3 *
4 * ====================================================================
5 * Copyright (c) 2000-2007 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#define APR_WANT_STRFUNC
20#include <apr_want.h>
21#include <apr_strings.h>
22#include <apr_hash.h>
23#include <apr_lib.h>
24
25#include <httpd.h>
26#include <http_request.h>
27#include <http_protocol.h>
28#include <http_log.h>
29#include <http_core.h>  /* for ap_construct_url */
30#include <mod_dav.h>
31
32#include "svn_types.h"
33#include "svn_pools.h"
34#include "svn_error.h"
35#include "svn_time.h"
36#include "svn_fs.h"
37#include "svn_repos.h"
38#include "svn_dav.h"
39#include "svn_sorts.h"
40#include "svn_version.h"
41#include "svn_props.h"
42#include "mod_dav_svn.h"
43#include "svn_ra.h"  /* for SVN_RA_CAPABILITY_* */
44#include "private/svn_log.h"
45
46#include "dav_svn.h"
47
48
49#define DEFAULT_ACTIVITY_DB "dav/activities.d"
50
51
52struct dav_stream {
53  const dav_resource *res;
54
55  /* for reading from the FS */
56  svn_stream_t *rstream;
57
58  /* for writing to the FS. we use wstream OR the handler/baton. */
59  svn_stream_t *wstream;
60  svn_txdelta_window_handler_t delta_handler;
61  void *delta_baton;
62};
63
64
65/* Convenience structure that facilitates combined memory allocation of
66   a dav_resource and dav_resource_private pair. */
67typedef struct {
68  dav_resource res;
69  dav_resource_private priv;
70} dav_resource_combined;
71
72
73/* Helper-wrapper around svn_fs_check_path(), which takes the same
74   arguments.  But: if we attempt to stat a path like "file1/file2",
75   then still return 'svn_node_none' to signal nonexistence, rather
76   than a full-blown filesystem error.  This allows mod_dav to throw
77   404 instead of 500. */
78static dav_error *
79fs_check_path(svn_node_kind_t *kind,
80              svn_fs_root_t *root,
81              const char *path,
82              apr_pool_t *pool)
83{
84  svn_error_t *serr;
85  svn_node_kind_t my_kind;
86
87  serr = svn_fs_check_path(&my_kind, root, path, pool);
88
89  /* Possibly trap other fs-errors here someday -- errors which may
90     simply indicate the path's nonexistence, rather than a critical
91     problem. */
92  if (serr && serr->apr_err == SVN_ERR_FS_NOT_DIRECTORY)
93    {
94      svn_error_clear(serr);
95      *kind = svn_node_none;
96      return NULL;
97    }
98  else if (serr)
99    {
100      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
101                                  apr_psprintf(pool, "Error checking kind of "
102                                               "path '%s' in repository",
103                                               path),
104                                  pool);
105    }
106
107  *kind = my_kind;
108  return NULL;
109}
110
111
112static int
113parse_version_uri(dav_resource_combined *comb,
114                  const char *path,
115                  const char *label,
116                  int use_checked_in)
117{
118  const char *slash;
119  const char *created_rev_str;
120
121  /* format: CREATED_REV/REPOS_PATH */
122
123  /* ### what to do with LABEL and USE_CHECKED_IN ?? */
124
125  comb->res.type = DAV_RESOURCE_TYPE_VERSION;
126  comb->res.versioned = TRUE;
127
128  slash = ap_strchr_c(path, '/');
129  if (slash == NULL)
130    {
131      /* http://host.name/repos/$svn/ver/0
132
133         This URL form refers to the root path of the repository.
134      */
135      created_rev_str = apr_pstrndup(comb->res.pool, path, strlen(path));
136      comb->priv.root.rev = SVN_STR_TO_REV(created_rev_str);
137      comb->priv.repos_path = "/";
138    }
139  else if (slash == path)
140    {
141      /* the CREATED_REV was missing(?)
142
143         ### not sure this can happen, though, because it would imply two
144         ### slashes, yet those are cleaned out within get_resource
145      */
146      return TRUE;
147    }
148  else
149    {
150      apr_size_t len = slash - path;
151
152      created_rev_str = apr_pstrndup(comb->res.pool, path, len);
153      comb->priv.root.rev = SVN_STR_TO_REV(created_rev_str);
154      comb->priv.repos_path = slash;
155    }
156
157  /* if the CREATED_REV parsing blew, then propagate it. */
158  if (comb->priv.root.rev == SVN_INVALID_REVNUM)
159    return TRUE;
160
161  return FALSE;
162}
163
164
165static int
166parse_history_uri(dav_resource_combined *comb,
167                  const char *path,
168                  const char *label,
169                  int use_checked_in)
170{
171  /* format: ??? */
172
173  /* ### what to do with LABEL and USE_CHECKED_IN ?? */
174
175  comb->res.type = DAV_RESOURCE_TYPE_HISTORY;
176
177  /* ### parse path */
178  comb->priv.repos_path = path;
179
180  return FALSE;
181}
182
183
184static int
185parse_working_uri(dav_resource_combined *comb,
186                  const char *path,
187                  const char *label,
188                  int use_checked_in)
189{
190  const char *slash;
191
192  /* format: ACTIVITY_ID/REPOS_PATH */
193
194  /* ### what to do with LABEL and USE_CHECKED_IN ?? */
195
196  comb->res.type = DAV_RESOURCE_TYPE_WORKING;
197  comb->res.working = TRUE;
198  comb->res.versioned = TRUE;
199
200  slash = ap_strchr_c(path, '/');
201
202  /* This sucker starts with a slash.  That's bogus. */
203  if (slash == path)
204    return TRUE;
205
206  if (slash == NULL)
207    {
208      /* There's no slash character in our path.  Assume it's just an
209         ACTIVITY_ID pointing to the root path.  That should be cool.
210         We'll just drop through to the normal case handling below. */
211      comb->priv.root.activity_id = apr_pstrdup(comb->res.pool, path);
212      comb->priv.repos_path = "/";
213    }
214  else
215    {
216      comb->priv.root.activity_id = apr_pstrndup(comb->res.pool, path,
217                                                 slash - path);
218      comb->priv.repos_path = slash;
219    }
220
221  return FALSE;
222}
223
224
225static int
226parse_activity_uri(dav_resource_combined *comb,
227                   const char *path,
228                   const char *label,
229                   int use_checked_in)
230{
231  /* format: ACTIVITY_ID */
232
233  /* ### what to do with LABEL and USE_CHECKED_IN ?? */
234
235  comb->res.type = DAV_RESOURCE_TYPE_ACTIVITY;
236
237  comb->priv.root.activity_id = path;
238
239  return FALSE;
240}
241
242
243static int
244parse_vcc_uri(dav_resource_combined *comb,
245              const char *path,
246              const char *label,
247              int use_checked_in)
248{
249  /* format: "default" (a singleton) */
250
251  if (strcmp(path, DAV_SVN__DEFAULT_VCC_NAME) != 0)
252    return TRUE;
253
254  if (label == NULL && !use_checked_in)
255    {
256      /* Version Controlled Configuration (baseline selector) */
257
258      /* ### mod_dav has a proper model for these. technically, they are
259         ### version-controlled resources (REGULAR), but that just monkeys
260         ### up a lot of stuff for us. use a PRIVATE for now. */
261
262      comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;   /* _REGULAR */
263      comb->priv.restype = DAV_SVN_RESTYPE_VCC;
264
265      comb->res.exists = TRUE;
266      comb->res.versioned = TRUE;
267      comb->res.baselined = TRUE;
268
269      /* NOTE: comb->priv.repos_path == NULL */
270    }
271  else
272    {
273      /* a specific Version Resource; in this case, a Baseline */
274
275      int revnum;
276
277      if (label != NULL)
278        {
279          revnum = SVN_STR_TO_REV(label); /* assume slash terminates */
280          if (!SVN_IS_VALID_REVNUM(revnum))
281            return TRUE;        /* ### be nice to get better feedback */
282        }
283      else /* use_checked_in */
284        {
285          /* use the DAV:checked-in value of the VCC. this is always the
286             "latest" (or "youngest") revision. */
287
288          /* signal prep_version to look it up */
289          revnum = SVN_INVALID_REVNUM;
290        }
291
292      comb->res.type = DAV_RESOURCE_TYPE_VERSION;
293
294      /* exists? need to wait for now */
295      comb->res.versioned = TRUE;
296      comb->res.baselined = TRUE;
297
298      /* which baseline (revision tree) to access */
299      comb->priv.root.rev = revnum;
300
301      /* NOTE: comb->priv.repos_path == NULL */
302      /* NOTE: comb->priv.created_rev == SVN_INVALID_REVNUM */
303    }
304
305  return FALSE;
306}
307
308
309static int
310parse_baseline_coll_uri(dav_resource_combined *comb,
311                        const char *path,
312                        const char *label,
313                        int use_checked_in)
314{
315  const char *slash;
316  int revnum;
317
318  /* format: REVISION/REPOS_PATH */
319
320  /* ### what to do with LABEL and USE_CHECKED_IN ?? */
321
322  slash = ap_strchr_c(path, '/');
323  if (slash == NULL)
324    slash = "/";        /* they are referring to the root of the BC */
325  else if (slash == path)
326    return TRUE;        /* the REVISION was missing(?)
327                           ### not sure this can happen, though, because
328                           ### it would imply two slashes, yet those are
329                           ### cleaned out within get_resource */
330
331  revnum = SVN_STR_TO_REV(path);  /* assume slash terminates conversion */
332  if (!SVN_IS_VALID_REVNUM(revnum))
333    return TRUE;        /* ### be nice to get better feedback */
334
335  /* ### mod_dav doesn't have a proper model for these. they are standard
336     ### VCRs, but we need some additional semantics attached to them.
337     ### need to figure out a way to label them as special. */
338
339  comb->res.type = DAV_RESOURCE_TYPE_REGULAR;
340  comb->res.versioned = TRUE;
341  comb->priv.root.rev = revnum;
342  comb->priv.repos_path = slash;
343
344  return FALSE;
345}
346
347
348static int
349parse_baseline_uri(dav_resource_combined *comb,
350                   const char *path,
351                   const char *label,
352                   int use_checked_in)
353{
354  int revnum;
355
356  /* format: REVISION */
357
358  /* ### what to do with LABEL and USE_CHECKED_IN ?? */
359
360  revnum = SVN_STR_TO_REV(path);
361  if (!SVN_IS_VALID_REVNUM(revnum))
362    return TRUE;        /* ### be nice to get better feedback */
363
364  /* create a Baseline resource (a special Version Resource) */
365
366  comb->res.type = DAV_RESOURCE_TYPE_VERSION;
367
368  /* exists? need to wait for now */
369  comb->res.versioned = TRUE;
370  comb->res.baselined = TRUE;
371
372  /* which baseline (revision tree) to access */
373  comb->priv.root.rev = revnum;
374
375  /* NOTE: comb->priv.repos_path == NULL */
376  /* NOTE: comb->priv.created_rev == SVN_INVALID_REVNUM */
377
378  return FALSE;
379}
380
381
382static int
383parse_wrk_baseline_uri(dav_resource_combined *comb,
384                       const char *path,
385                       const char *label,
386                       int use_checked_in)
387{
388  const char *slash;
389
390  /* format: ACTIVITY_ID/REVISION */
391
392  /* ### what to do with LABEL and USE_CHECKED_IN ?? */
393
394  comb->res.type = DAV_RESOURCE_TYPE_WORKING;
395  comb->res.working = TRUE;
396  comb->res.versioned = TRUE;
397  comb->res.baselined = TRUE;
398
399  if ((slash = ap_strchr_c(path, '/')) == NULL
400      || slash == path
401      || slash[1] == '\0')
402    return TRUE;
403
404  comb->priv.root.activity_id = apr_pstrndup(comb->res.pool, path,
405                                             slash - path);
406  comb->priv.root.rev = SVN_STR_TO_REV(slash + 1);
407
408  /* NOTE: comb->priv.repos_path == NULL */
409
410  return FALSE;
411}
412
413
414static const struct special_defn
415{
416  const char *name;
417
418  /*
419   * COMB is the resource that we are constructing. Any elements that
420   * can be determined from the PATH may be set in COMB. However, further
421   * operations are not allowed (we don't want anything besides a parse
422   * error to occur).
423   *
424   * At a minimum, the parse function must set COMB->res.type and
425   * COMB->priv.repos_path.
426   *
427   * PATH does not contain a leading slash. Given "/root/$svn/xxx/the/path"
428   * as the request URI, the PATH variable will be "the/path"
429   */
430  int (*parse)(dav_resource_combined *comb, const char *path,
431               const char *label, int use_checked_in);
432
433  /* The number of subcompenents after the !svn/xxx/... before we
434     reach the actual path within the repository. */
435  int numcomponents;
436
437  /* Boolean:  are the subcomponents followed by a repos path? */
438  int has_repos_path;
439
440  /* The private resource type for the /$svn/xxx/ collection. */
441  enum dav_svn_private_restype restype;
442
443} special_subdirs[] =
444{
445  { "ver", parse_version_uri, 1, TRUE, DAV_SVN_RESTYPE_VER_COLLECTION },
446  { "his", parse_history_uri, 0, FALSE, DAV_SVN_RESTYPE_HIS_COLLECTION },
447  { "wrk", parse_working_uri, 1, TRUE,  DAV_SVN_RESTYPE_WRK_COLLECTION },
448  { "act", parse_activity_uri, 1, FALSE, DAV_SVN_RESTYPE_ACT_COLLECTION },
449  { "vcc", parse_vcc_uri, 1, FALSE, DAV_SVN_RESTYPE_VCC_COLLECTION },
450  { "bc", parse_baseline_coll_uri, 1, TRUE, DAV_SVN_RESTYPE_BC_COLLECTION },
451  { "bln", parse_baseline_uri, 1, FALSE, DAV_SVN_RESTYPE_BLN_COLLECTION },
452  { "wbl", parse_wrk_baseline_uri, 2, FALSE, DAV_SVN_RESTYPE_WBL_COLLECTION },
453  { NULL } /* sentinel */
454};
455
456
457/*
458 * parse_uri: parse the provided URI into its various bits
459 *
460 * URI will contain a path relative to our configured root URI. It should
461 * not have a leading "/". The root is identified by "".
462 *
463 * On output: *COMB will contain all of the information parsed out of
464 * the URI -- the resource type, activity ID, path, etc.
465 *
466 * Note: this function will only parse the URI. Validation of the pieces,
467 * opening data stores, etc, are not part of this function.
468 *
469 * TRUE is returned if a parsing error occurred. FALSE for success.
470 */
471static int
472parse_uri(dav_resource_combined *comb,
473          const char *uri,
474          const char *label,
475          int use_checked_in)
476{
477  const char *special_uri = comb->priv.repos->special_uri;
478  apr_size_t len1;
479  apr_size_t len2;
480  char ch;
481
482  len1 = strlen(uri);
483  len2 = strlen(special_uri);
484  if (len1 > len2
485      && ((ch = uri[len2]) == '/' || ch == '\0')
486      && memcmp(uri, special_uri, len2) == 0)
487    {
488      if (ch == '\0')
489        {
490          /* URI was "/root/!svn". It exists, but has restricted usage. */
491          comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
492          comb->priv.restype = DAV_SVN_RESTYPE_ROOT_COLLECTION;
493        }
494      else
495        {
496          const struct special_defn *defn;
497
498          /* skip past the "!svn/" prefix */
499          uri += len2 + 1;
500          len1 -= len2 + 1;
501
502          for (defn = special_subdirs ; defn->name != NULL; ++defn)
503            {
504              apr_size_t len3 = strlen(defn->name);
505
506              if (len1 >= len3 && memcmp(uri, defn->name, len3) == 0)
507                {
508                  if (uri[len3] == '\0')
509                    {
510                      /* URI was "/root/!svn/XXX". The location exists, but
511                         has restricted usage. */
512                      comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
513
514                      /* store the resource type so that we can PROPFIND
515                         on this collection. */
516                      comb->priv.restype = defn->restype;
517                    }
518                  else if (uri[len3] == '/')
519                    {
520                      if ((*defn->parse)(comb, uri + len3 + 1, label,
521                                         use_checked_in))
522                        return TRUE;
523                    }
524                  else
525                    {
526                      /* e.g. "/root/!svn/activity" (we just know "act") */
527                      return TRUE;
528                    }
529
530                  break;
531                }
532            }
533
534          /* if completed the loop, then it is an unrecognized subdir */
535          if (defn->name == NULL)
536            return TRUE;
537        }
538    }
539  else
540    {
541      /* Anything under the root, but not under "!svn". These are all
542         version-controlled resources. */
543      comb->res.type = DAV_RESOURCE_TYPE_REGULAR;
544      comb->res.versioned = TRUE;
545
546      /* The location of these resources corresponds directly to the URI,
547         and we keep the leading "/". */
548      comb->priv.repos_path = comb->priv.uri_path->data;
549    }
550
551  return FALSE;
552}
553
554
555static dav_error *
556prep_regular(dav_resource_combined *comb)
557{
558  apr_pool_t *pool = comb->res.pool;
559  dav_svn_repos *repos = comb->priv.repos;
560  svn_error_t *serr;
561  dav_error *derr;
562  svn_node_kind_t kind;
563
564  /* A REGULAR resource might have a specific revision already (e.g. if it
565     is part of a baseline collection). However, if it doesn't, then we
566     will assume that we need the youngest revision.
567     ### other cases besides a BC? */
568  if (comb->priv.root.rev == SVN_INVALID_REVNUM)
569    {
570      serr = svn_fs_youngest_rev(&comb->priv.root.rev, repos->fs, pool);
571      if (serr != NULL)
572        {
573          return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
574                                      "Could not determine the proper "
575                                      "revision to access",
576                                      pool);
577        }
578    }
579
580  /* get the root of the tree */
581  serr = svn_fs_revision_root(&comb->priv.root.root, repos->fs,
582                              comb->priv.root.rev, pool);
583  if (serr != NULL)
584    {
585      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
586                                  "Could not open the root of the "
587                                  "repository",
588                                  pool);
589    }
590
591  derr = fs_check_path(&kind, comb->priv.root.root,
592                       comb->priv.repos_path, pool);
593  if (derr != NULL)
594    return derr;
595
596  comb->res.exists = (kind != svn_node_none);
597  comb->res.collection = (kind == svn_node_dir);
598
599  /* HACK:  dav_get_resource_state() is making shortcut assumptions
600     about how to distinguish a null resource from a lock-null
601     resource.  This is the only way to get around that problem.
602     Without it, it won't ever detect lock-nulls, and thus 'svn unlock
603     nonexistentURL' will always return 404's. */
604  if (! comb->res.exists)
605    comb->priv.r->path_info = (char *) "";
606
607  return NULL;
608}
609
610
611static dav_error *
612prep_version(dav_resource_combined *comb)
613{
614  svn_error_t *serr;
615  apr_pool_t *pool = comb->res.pool;
616
617  /* we are accessing the Version Resource by REV/PATH */
618
619  /* ### assert: .baselined = TRUE */
620
621  /* if we don't have a revision, then assume the youngest */
622  if (!SVN_IS_VALID_REVNUM(comb->priv.root.rev))
623    {
624      serr = svn_fs_youngest_rev(&comb->priv.root.rev,
625                                 comb->priv.repos->fs,
626                                 pool);
627      if (serr != NULL)
628        {
629          /* ### might not be a baseline */
630
631          return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
632                                      "Could not fetch 'youngest' revision "
633                                      "to enable accessing the latest "
634                                      "baseline resource.",
635                                      pool);
636        }
637    }
638
639  /* ### baselines have no repos_path, and we don't need to open
640     ### a root (yet). we just needed to ensure that we have the proper
641     ### revision number. */
642
643  if (!comb->priv.root.root)
644    {
645      serr = svn_fs_revision_root(&comb->priv.root.root,
646                                  comb->priv.repos->fs,
647                                  comb->priv.root.rev,
648                                  pool);
649      if (serr != NULL)
650        {
651          return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
652                                      "Could not open a revision root.",
653                                      pool);
654        }
655    }
656
657  /* ### we should probably check that the revision is valid */
658  comb->res.exists = TRUE;
659
660  /* Set up the proper URI. Most likely, we arrived here via a VCC,
661     so the URI will be incorrect. Set the canonical form. */
662  /* ### assuming a baseline */
663  comb->res.uri = dav_svn__build_uri(comb->priv.repos,
664                                     DAV_SVN__BUILD_URI_BASELINE,
665                                     comb->priv.root.rev, NULL,
666                                     0 /* add_href */,
667                                     pool);
668
669  return NULL;
670}
671
672
673static dav_error *
674prep_history(dav_resource_combined *comb)
675{
676  return NULL;
677}
678
679
680static dav_error *
681prep_working(dav_resource_combined *comb)
682{
683  const char *txn_name = dav_svn__get_txn(comb->priv.repos,
684                                          comb->priv.root.activity_id);
685  apr_pool_t *pool = comb->res.pool;
686  svn_error_t *serr;
687  dav_error *derr;
688  svn_node_kind_t kind;
689
690  if (txn_name == NULL)
691    {
692      /* ### HTTP_BAD_REQUEST is probably wrong */
693      return dav_new_error(pool, HTTP_BAD_REQUEST, 0,
694                           "An unknown activity was specified in the URL. "
695                           "This is generally caused by a problem in the "
696                           "client software.");
697    }
698  comb->priv.root.txn_name = txn_name;
699
700  /* get the FS transaction, given its name */
701  serr = svn_fs_open_txn(&comb->priv.root.txn, comb->priv.repos->fs, txn_name,
702                         pool);
703  if (serr != NULL)
704    {
705      if (serr->apr_err == SVN_ERR_FS_NO_SUCH_TRANSACTION)
706        {
707          svn_error_clear(serr);
708          return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0,
709                               "An activity was specified and found, but the "
710                               "corresponding SVN FS transaction was not "
711                               "found.");
712        }
713      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
714                                  "Could not open the SVN FS transaction "
715                                  "corresponding to the specified activity.",
716                                  pool);
717    }
718
719  if (comb->res.baselined)
720    {
721      /* a Working Baseline */
722
723      /* if the transaction exists, then the working resource exists */
724      comb->res.exists = TRUE;
725
726      return NULL;
727    }
728
729  /* Set the txn author if not previously set.  Protect against multi-author
730   * commits by verifying authenticated user associated with the current
731   * request is the same as the txn author.
732   * Note that anonymous requests are being excluded as being a change
733   * in author, because the commit may touch areas of the repository
734   * that are anonymous writeable as well as areas that are not.
735   */
736  if (comb->priv.repos->username)
737    {
738      svn_string_t *current_author;
739      svn_string_t request_author;
740
741      serr = svn_fs_txn_prop(&current_author, comb->priv.root.txn,
742               SVN_PROP_REVISION_AUTHOR, pool);
743      if (serr != NULL)
744        {
745          return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
746                   "Failed to retrieve author of the SVN FS transaction "
747                   "corresponding to the specified activity.",
748                   pool);
749        }
750
751      request_author.data = comb->priv.repos->username;
752      request_author.len = strlen(request_author.data);
753      if (!current_author)
754        {
755          serr = svn_fs_change_txn_prop(comb->priv.root.txn,
756                   SVN_PROP_REVISION_AUTHOR, &request_author, pool);
757          if (serr != NULL)
758            {
759              return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
760                       "Failed to set the author of the SVN FS transaction "
761                       "corresponding to the specified activity.",
762                       pool);
763            }
764        }
765      else if (!svn_string_compare(current_author, &request_author))
766        {
767          return dav_new_error(pool, HTTP_NOT_IMPLEMENTED, 0,
768                   "Multi-author commits not supported.");
769        }
770    }
771
772  /* get the root of the tree */
773  serr = svn_fs_txn_root(&comb->priv.root.root, comb->priv.root.txn, pool);
774  if (serr != NULL)
775    {
776      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
777                                  "Could not open the (transaction) root of "
778                                  "the repository",
779                                  pool);
780    }
781
782  derr = fs_check_path(&kind, comb->priv.root.root,
783                       comb->priv.repos_path, pool);
784  if (derr != NULL)
785    return derr;
786
787  comb->res.exists = (kind != svn_node_none);
788  comb->res.collection = (kind == svn_node_dir);
789
790  return NULL;
791}
792
793
794static dav_error *
795prep_activity(dav_resource_combined *comb)
796{
797  const char *txn_name = dav_svn__get_txn(comb->priv.repos,
798                                          comb->priv.root.activity_id);
799
800  comb->priv.root.txn_name = txn_name;
801  comb->res.exists = txn_name != NULL;
802
803  return NULL;
804}
805
806
807static dav_error *
808prep_private(dav_resource_combined *comb)
809{
810  if (comb->priv.restype == DAV_SVN_RESTYPE_VCC)
811    {
812      /* ### what to do */
813    }
814  /* else nothing to do (### for now) */
815
816  return NULL;
817}
818
819
820static const struct res_type_handler
821{
822  dav_resource_type type;
823  dav_error * (*prep)(dav_resource_combined *comb);
824
825} res_type_handlers[] =
826{
827  /* skip UNKNOWN */
828  { DAV_RESOURCE_TYPE_REGULAR, prep_regular },
829  { DAV_RESOURCE_TYPE_VERSION, prep_version },
830  { DAV_RESOURCE_TYPE_HISTORY, prep_history },
831  { DAV_RESOURCE_TYPE_WORKING, prep_working },
832  /* skip WORKSPACE */
833  { DAV_RESOURCE_TYPE_ACTIVITY, prep_activity },
834  { DAV_RESOURCE_TYPE_PRIVATE, prep_private },
835
836  { 0, NULL }   /* sentinel */
837};
838
839
840/*
841** ### docco...
842**
843** Set .exists and .collection
844** open other, internal bits...
845*/
846static dav_error *
847prep_resource(dav_resource_combined *comb)
848{
849  const struct res_type_handler *scan;
850
851  for (scan = res_type_handlers; scan->prep != NULL; ++scan)
852    {
853      if (comb->res.type == scan->type)
854        return (*scan->prep)(comb);
855    }
856
857  return dav_new_error(comb->res.pool, HTTP_INTERNAL_SERVER_ERROR, 0,
858                       "DESIGN FAILURE: unknown resource type");
859}
860
861
862static dav_resource *
863create_private_resource(const dav_resource *base,
864                        enum dav_svn_private_restype restype)
865{
866  dav_resource_combined *comb;
867  svn_stringbuf_t *path;
868  const struct special_defn *defn;
869
870  for (defn = special_subdirs; defn->name != NULL; ++defn)
871    if (defn->restype == restype)
872      break;
873  /* assert: defn->name != NULL */
874
875  path = svn_stringbuf_createf(base->pool, "/%s/%s",
876                            base->info->repos->special_uri, defn->name);
877
878  comb = apr_pcalloc(base->pool, sizeof(*comb));
879
880  /* ### can/should we leverage prep_resource */
881
882  comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
883
884  comb->res.exists = TRUE;
885  comb->res.collection = TRUE;                  /* ### always true? */
886  /* versioned = baselined = working = FALSE */
887
888  comb->res.uri = apr_pstrcat(base->pool, base->info->repos->root_path,
889                              path->data, NULL);
890  comb->res.info = &comb->priv;
891  comb->res.hooks = &dav_svn__hooks_repository;
892  comb->res.pool = base->pool;
893
894  comb->priv.uri_path = path;
895  comb->priv.repos = base->info->repos;
896  comb->priv.root.rev = SVN_INVALID_REVNUM;
897  return &comb->res;
898}
899
900
901static void log_warning(void *baton, svn_error_t *err)
902{
903  request_rec *r = baton;
904
905  /* ### hmm. the FS is cleaned up at request cleanup time. "r" might
906     ### not really be valid. we should probably put the FS into a
907     ### subpool to ensure it gets cleaned before the request.
908
909     ### is there a good way to create and use a subpool for all
910     ### of our functions ... ??
911  */
912
913  ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, "%s", err->message);
914}
915
916
917AP_MODULE_DECLARE(dav_error *)
918dav_svn_split_uri(request_rec *r,
919                  const char *uri_to_split,
920                  const char *root_path,
921                  const char **cleaned_uri,
922                  int *trailing_slash,
923                  const char **repos_name,
924                  const char **relative_path,
925                  const char **repos_path)
926{
927  apr_size_t len1;
928  int had_slash;
929  const char *fs_path;
930  const char *fs_parent_path;
931  const char *relative;
932  char *uri;
933
934  /* one of these is NULL, the other non-NULL. */
935  fs_path = dav_svn__get_fs_path(r);
936  fs_parent_path = dav_svn__get_fs_parent_path(r);
937
938  if ((fs_path == NULL) && (fs_parent_path == NULL))
939    {
940      /* ### are SVN_ERR_APMOD codes within the right numeric space? */
941      return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
942                           SVN_ERR_APMOD_MISSING_PATH_TO_FS,
943                           "The server is misconfigured: "
944                           "either an SVNPath or SVNParentPath "
945                           "directive is required to specify the location "
946                           "of this resource's repository.");
947    }
948
949  /* make a copy so that we can do some work on it */
950  uri = apr_pstrdup(r->pool, uri_to_split);
951
952  /* remove duplicate slashes, and make sure URI has no trailing '/' */
953  ap_no2slash(uri);
954  len1 = strlen(uri);
955  had_slash = (len1 > 0 && uri[len1 - 1] == '/');
956  if (len1 > 1 && had_slash)
957    uri[len1 - 1] = '\0';
958
959  if (had_slash)
960    *trailing_slash = TRUE;
961  else
962    *trailing_slash = FALSE;
963
964  /* return the first item.  */
965  *cleaned_uri = apr_pstrdup(r->pool, uri);
966
967  /* The URL space defined by the SVN provider is always a virtual
968     space. Construct the path relative to the configured Location
969     (root_path). So... the relative location is simply the URL used,
970     skipping the root_path.
971
972     Note: mod_dav has canonialized root_path. It will not have a trailing
973     slash (unless it is "/").
974
975     Note: given a URI of /something and a root of /some, then it is
976           impossible to be here (and end up with "thing"). This is simply
977           because we control /some and are dispatched to here for its
978           URIs. We do not control /something, so we don't get here. Or,
979           if we *do* control /something, then it is for THAT root.
980  */
981  relative = ap_stripprefix(uri, root_path);
982
983  /* We want a leading slash on the path specified by <relative>. This
984     will almost always be the case since root_path does not have a trailing
985     slash. However, if the root is "/", then the slash will be removed
986     from <relative>. Backing up a character will put the leading slash
987     back.
988
989     Watch out for the empty string! This can happen when URI == ROOT_PATH.
990     We simply turn the path into "/" for this case. */
991  if (*relative == '\0')
992    relative = "/";
993  else if (*relative != '/')
994    --relative;
995  /* ### need a better name... it isn't "relative" because of the leading
996     ### slash. something about SVN-private-path */
997
998  /* Depending on whether SVNPath or SVNParentPath was used, we need
999     to compute 'relative' and 'repos_name' differently.  */
1000
1001  /* Normal case:  the SVNPath command was used to specify a
1002     particular repository.  */
1003  if (fs_path != NULL)
1004    {
1005      /* the repos_name is the last component of root_path. */
1006      *repos_name = svn_path_basename(root_path, r->pool);
1007
1008      /* 'relative' is already correct for SVNPath; the root_path
1009         already contains the name of the repository, so relative is
1010         everything beyond that.  */
1011    }
1012
1013  else
1014    {
1015      /* SVNParentPath was used instead: assume the first component of
1016         'relative' is the name of a repository. */
1017      const char *magic_component, *magic_end;
1018
1019      /* A repository name is required here.
1020         Remember that 'relative' always starts with a "/". */
1021      if (relative[1] == '\0')
1022        {
1023          /* ### are SVN_ERR_APMOD codes within the right numeric space? */
1024          return dav_new_error(r->pool, HTTP_FORBIDDEN,
1025                               SVN_ERR_APMOD_MALFORMED_URI,
1026                               "The URI does not contain the name "
1027                               "of a repository.");
1028        }
1029
1030      magic_end = ap_strchr_c(relative + 1, '/');
1031      if (!magic_end)
1032        {
1033          /* ### Request was for parent directory with no trailing
1034             slash; we probably ought to just redirect to same with
1035             trailing slash appended. */
1036          magic_component = relative + 1;
1037          relative = "/";
1038        }
1039      else
1040        {
1041          magic_component = apr_pstrndup(r->pool, relative + 1,
1042                                         magic_end - relative - 1);
1043          relative = magic_end;
1044        }
1045
1046      /* return answer */
1047      *repos_name = magic_component;
1048    }
1049
1050  /* We can return 'relative' at this point too. */
1051  *relative_path = apr_pstrdup(r->pool, relative);
1052
1053  /* Code to remove the !svn junk from the front of the relative path,
1054     mainly stolen from parse_uri().  This code assumes that
1055     the 'relative' string being parsed doesn't start with '/'. */
1056  relative++;
1057
1058  {
1059    const char *special_uri = dav_svn__get_special_uri(r);
1060    apr_size_t len2;
1061    char ch;
1062
1063    len1 = strlen(relative);
1064    len2 = strlen(special_uri);
1065    if (len1 > len2
1066        && ((ch = relative[len2]) == '/' || ch == '\0')
1067        && memcmp(relative, special_uri, len2) == 0)
1068      {
1069        if (ch == '\0')
1070          {
1071            /* relative is just "!svn", which is malformed. */
1072            return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1073                                 SVN_ERR_APMOD_MALFORMED_URI,
1074                                 "Nothing follows the svn special_uri.");
1075          }
1076        else
1077          {
1078            const struct special_defn *defn;
1079
1080            /* skip past the "!svn/" prefix */
1081            relative += len2 + 1;
1082            len1 -= len2 + 1;
1083
1084            for (defn = special_subdirs ; defn->name != NULL; ++defn)
1085              {
1086                apr_size_t len3 = strlen(defn->name);
1087
1088                if (len1 >= len3 && memcmp(relative, defn->name, len3) == 0)
1089                  {
1090                    /* Found a matching special dir. */
1091
1092                    if (relative[len3] == '\0')
1093                      {
1094                        /* relative is "!svn/xxx"  */
1095                        if (defn->numcomponents == 0)
1096                          *repos_path = NULL;
1097                        else
1098                          return
1099                            dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1100                                          SVN_ERR_APMOD_MALFORMED_URI,
1101                                          "Missing info after special_uri.");
1102                      }
1103                    else if (relative[len3] == '/')
1104                      {
1105                        /* Skip past defn->numcomponents components,
1106                           return everything beyond that.*/
1107                        int j;
1108                        const char *end = NULL, *start = relative + len3 + 1;
1109
1110                        for (j = 0; j < defn->numcomponents; j++)
1111                          {
1112                            end = ap_strchr_c(start, '/');
1113                            if (! end)
1114                              break;
1115                            start = end + 1;
1116                          }
1117
1118                        if (! end)
1119                          {
1120                            /* Did we break from the loop prematurely? */
1121                            if (j != (defn->numcomponents - 1))
1122                              return
1123                                dav_new_error(r->pool,
1124                                              HTTP_INTERNAL_SERVER_ERROR,
1125                                              SVN_ERR_APMOD_MALFORMED_URI,
1126                                              "Not enough components"
1127                                              " after special_uri.");
1128
1129                            if (! defn->has_repos_path)
1130                              /* It's okay to not have found a slash. */
1131                              *repos_path = NULL;
1132                            else
1133                              *repos_path = "/";
1134                          }
1135                        else
1136                          {
1137                            /* Found a slash after the special components. */
1138                            *repos_path = apr_pstrdup(r->pool, start);
1139                          }
1140                      }
1141                    else
1142                      {
1143                        return
1144                          dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1145                                        SVN_ERR_APMOD_MALFORMED_URI,
1146                                        "Unknown data after special_uri.");
1147                      }
1148
1149                  break;
1150                  }
1151              }
1152
1153            if (defn->name == NULL)
1154              return
1155                dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1156                              SVN_ERR_APMOD_MALFORMED_URI,
1157                              "Couldn't match subdir after special_uri.");
1158          }
1159      }
1160    else
1161      {
1162        /* There's no "!svn/" at all, so the relative path is already
1163           a valid path within the repository.  */
1164        *repos_path = apr_pstrdup(r->pool, relative);
1165      }
1166  }
1167
1168  return NULL;
1169}
1170
1171
1172/* Context for cleanup handler. */
1173struct cleanup_fs_access_baton
1174{
1175  svn_fs_t *fs;
1176  apr_pool_t *pool;
1177};
1178
1179
1180/* Pool cleanup handler.  Make sure fs's access ctx points to NULL
1181   when request pool is destroyed. */
1182static apr_status_t
1183cleanup_fs_access(void *data)
1184{
1185  svn_error_t *serr;
1186  struct cleanup_fs_access_baton *baton = data;
1187
1188  serr = svn_fs_set_access(baton->fs, NULL);
1189  if (serr)
1190    {
1191      ap_log_perror(APLOG_MARK, APLOG_ERR, serr->apr_err, baton->pool,
1192                    "cleanup_fs_access: error clearing fs access context");
1193      svn_error_clear(serr);
1194    }
1195
1196  return APR_SUCCESS;
1197}
1198
1199
1200/* Helper func to construct a special 'parentpath' private resource. */
1201static dav_error *
1202get_parentpath_resource(request_rec *r,
1203                        const char *root_path,
1204                        dav_resource **resource)
1205{
1206  const char *new_uri;
1207  dav_svn_root *droot = apr_pcalloc(r->pool, sizeof(*droot));
1208  dav_svn_repos *repos = apr_pcalloc(r->pool, sizeof(*repos));
1209  dav_resource_combined *comb = apr_pcalloc(r->pool, sizeof(*comb));
1210  apr_size_t len = strlen(r->uri);
1211
1212  comb->res.exists = TRUE;
1213  comb->res.collection = TRUE;
1214  comb->res.uri = apr_pstrdup(r->pool, r->uri);
1215  comb->res.info = &comb->priv;
1216  comb->res.hooks = &dav_svn__hooks_repository;
1217  comb->res.pool = r->pool;
1218  comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
1219
1220  comb->priv.restype = DAV_SVN_RESTYPE_PARENTPATH_COLLECTION;
1221  comb->priv.r = r;
1222  comb->priv.repos_path = "Collection of Repositories";
1223  comb->priv.root = *droot;
1224  droot->rev = SVN_INVALID_REVNUM;
1225
1226  comb->priv.repos = repos;
1227  repos->pool = r->pool;
1228  repos->xslt_uri = dav_svn__get_xslt_uri(r);
1229  repos->autoversioning = dav_svn__get_autoversioning_flag(r);
1230  repos->bulk_updates = dav_svn__get_bulk_updates_flag(r);
1231  repos->base_url = ap_construct_url(r->pool, "", r);
1232  repos->special_uri = dav_svn__get_special_uri(r);
1233  repos->username = r->user;
1234  repos->client_capabilities = apr_hash_make(repos->pool);
1235
1236  /* Make sure this type of resource always has a trailing slash; if
1237     not, redirect to a URI that does. */
1238  if (r->uri[len-1] != '/')
1239    {
1240      new_uri = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri),
1241                            "/", NULL);
1242      apr_table_setn(r->headers_out, "Location",
1243                     ap_construct_url(r->pool, new_uri, r));
1244      return dav_new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0,
1245                           "Requests for a collection must have a "
1246                           "trailing slash on the URI.");
1247    }
1248
1249  /* No other "prepping" of resource needs to happen -- no opening
1250     of a repository or anything like that, because, well, there's
1251     no repository to open. */
1252  *resource = &comb->res;
1253  return NULL;
1254}
1255
1256/* --------------- Borrowed from httpd's mod_negotiation.c -------------- */
1257
1258typedef struct accept_rec {
1259  char *name;                 /* MUST be lowercase */
1260  float quality;
1261} accept_rec;
1262
1263/*
1264 * Get a single Accept-encoding line from ACCEPT_LINE, and place the
1265 * information we have parsed out of it into RESULT.
1266 */
1267
1268static const char *get_entry(apr_pool_t *p, accept_rec *result,
1269                             const char *accept_line)
1270{
1271    result->quality = 1.0f;
1272
1273    /*
1274     * Note that this handles what I gather is the "old format",
1275     *
1276     *    Accept: text/html text/plain moo/zot
1277     *
1278     * without any compatibility kludges --- if the token after the
1279     * MIME type begins with a semicolon, we know we're looking at parms,
1280     * otherwise, we know we aren't.  (So why all the pissing and moaning
1281     * in the CERN server code?  I must be missing something).
1282     */
1283
1284    result->name = ap_get_token(p, &accept_line, 0);
1285    ap_str_tolower(result->name);     /* You want case insensitive,
1286                                       * you'll *get* case insensitive.
1287                                       */
1288
1289    while (*accept_line == ';')
1290      {
1291        /* Parameters ... */
1292
1293        char *parm;
1294        char *cp;
1295        char *end;
1296
1297        ++accept_line;
1298        parm = ap_get_token(p, &accept_line, 1);
1299
1300        /* Look for 'var = value' --- and make sure the var is in lcase. */
1301
1302        for (cp = parm; (*cp && !apr_isspace(*cp) && *cp != '='); ++cp)
1303          {
1304            *cp = apr_tolower(*cp);
1305          }
1306
1307        if (!*cp)
1308          {
1309            continue;           /* No '='; just ignore it. */
1310          }
1311
1312        *cp++ = '\0';           /* Delimit var */
1313        while (*cp && (apr_isspace(*cp) || *cp == '='))
1314          {
1315            ++cp;
1316          }
1317
1318        if (*cp == '"')
1319          {
1320            ++cp;
1321            for (end = cp;
1322                 (*end && *end != '\n' && *end != '\r' && *end != '\"');
1323                 end++);
1324          }
1325        else
1326          {
1327            for (end = cp; (*end && !apr_isspace(*end)); end++);
1328          }
1329        if (*end)
1330          {
1331            *end = '\0';        /* strip ending quote or return */
1332          }
1333        ap_str_tolower(cp);
1334
1335        if (parm[0] == 'q'
1336            && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0')))
1337          {
1338            result->quality = (float) atof(cp);
1339          }
1340      }
1341
1342    if (*accept_line == ',')
1343      {
1344        ++accept_line;
1345      }
1346
1347    return accept_line;
1348}
1349
1350/* @a accept_line is the Accept-Encoding header, which is of the
1351   format:
1352
1353     Accept-Encoding: name; q=N;
1354
1355   This function will return an array of accept_rec structures that
1356   contain the accepted encodings and the quality each one has
1357   associated with them.
1358*/
1359static apr_array_header_t *do_header_line(apr_pool_t *p,
1360                                          const char *accept_line)
1361{
1362    apr_array_header_t *accept_recs;
1363
1364    if (!accept_line)
1365      return NULL;
1366
1367    accept_recs = apr_array_make(p, 10, sizeof(accept_rec));
1368
1369    while (*accept_line)
1370      {
1371        accept_rec *prefs = (accept_rec *) apr_array_push(accept_recs);
1372        accept_line = get_entry(p, prefs, accept_line);
1373      }
1374
1375    return accept_recs;
1376}
1377
1378/* ---------------------------------------------------------------------- */
1379
1380
1381/* qsort comparison function for the quality field of the accept_rec
1382   structure */
1383static int sort_encoding_pref(const void *accept_rec1, const void *accept_rec2)
1384{
1385  float diff = ((const accept_rec *) accept_rec1)->quality -
1386      ((const accept_rec *) accept_rec2)->quality;
1387  return (diff == 0 ? 0 : (diff > 0 ? -1 : 1));
1388}
1389
1390/* Parse and handle any possible Accept-Encoding header that has been
1391   sent as part of the request.  */
1392static void
1393negotiate_encoding_prefs(request_rec *r, int *svndiff_version)
1394{
1395  /* It would be nice if mod_negotiation
1396     <http://httpd.apache.org/docs-2.1/mod/mod_negotiation.html> could
1397     handle the Accept-Encoding header parsing for us.  Sadly, it
1398     looks like its data structures and routines are private (see
1399     httpd/modules/mappers/mod_negotiation.c).  Thus, we duplicate the
1400     necessary ones in this file. */
1401  int i;
1402  const apr_array_header_t *encoding_prefs;
1403  encoding_prefs = do_header_line(r->pool,
1404                                  apr_table_get(r->headers_in,
1405                                                "Accept-Encoding"));
1406
1407  if (!encoding_prefs || apr_is_empty_array(encoding_prefs))
1408    {
1409      *svndiff_version = 0;
1410      return;
1411    }
1412
1413  *svndiff_version = 0;
1414  qsort(encoding_prefs->elts, (size_t) encoding_prefs->nelts,
1415        sizeof(accept_rec), sort_encoding_pref);
1416  for (i = 0; i < encoding_prefs->nelts; i++)
1417    {
1418      struct accept_rec rec = APR_ARRAY_IDX(encoding_prefs, i,
1419                                            struct accept_rec);
1420      if (strcmp(rec.name, "svndiff1") == 0)
1421        {
1422          *svndiff_version = 1;
1423          break;
1424        }
1425      else if (strcmp(rec.name, "svndiff") == 0)
1426        {
1427          *svndiff_version = 0;
1428          break;
1429        }
1430    }
1431}
1432
1433
1434/* The only two possible values for a capability. */
1435static const char *capability_yes = "yes";
1436static const char *capability_no = "no";
1437
1438/* Convert CAPABILITIES, a hash table mapping 'const char *' keys to
1439 * "yes" or "no" values, to a list of all keys whose value is "yes".
1440 * Return the list, allocated in POOL, and use POOL for all temporary
1441 * allocation.
1442 */
1443static apr_array_header_t *
1444capabilities_as_list(apr_hash_t *capabilities, apr_pool_t *pool)
1445{
1446  apr_array_header_t *list = apr_array_make(pool, apr_hash_count(capabilities),
1447                                            sizeof(char *));
1448  apr_hash_index_t *hi;
1449
1450  for (hi = apr_hash_first(pool, capabilities); hi; hi = apr_hash_next(hi))
1451    {
1452      const void *key;
1453      void *val;
1454      apr_hash_this(hi, &key, NULL, &val);
1455      if (strcmp((const char *) val, "yes") == 0)
1456        APR_ARRAY_PUSH(list, const char *) = key;
1457    }
1458
1459  return list;
1460}
1461
1462
1463/* Given a non-NULL QUERY string of the form "key1=val1&key2=val2&...",
1464 * parse the keys and values into an apr table.  Allocate the table in
1465 * POOL;  dup all keys and values into POOL as well.
1466 *
1467 * Note that repeating the same key will cause table overwrites
1468 * (e.g. "r=3&r=5"), and that a lack of value ("p=") is legal, but
1469 * equivalent to not specifying the key at all.
1470 */
1471static apr_table_t *
1472querystring_to_table(const char *query, apr_pool_t *pool)
1473{
1474  apr_table_t *table = apr_table_make(pool, 2);
1475  apr_array_header_t *array = svn_cstring_split(query, "&", TRUE, pool);
1476  int i;
1477  for (i = 0; i < array->nelts; i++)
1478    {
1479      char *keyval = APR_ARRAY_IDX(array, i, char *);
1480      char *equals = strchr(keyval, '=');
1481      if (equals != NULL)
1482        {
1483          *equals = '\0';
1484          apr_table_set(table, keyval, equals + 1);
1485        }
1486    }
1487  return table;
1488}
1489
1490
1491/* Helper for get_resource().
1492 *
1493 * Given a fully fleshed out COMB object which has already been parsed
1494 * via parse_uri(), parse the querystring in QUERY.
1495 *
1496 * Specifically, look for optional 'p=PEGREV' and 'r=WORKINGREV'
1497 * values in the querystring, and modify COMB so that prep_regular()
1498 * opens the correct revision and path.
1499 */
1500static dav_error *
1501parse_querystring(const char *query, dav_resource_combined *comb,
1502                  apr_pool_t *pool)
1503{
1504  svn_error_t *serr;
1505  svn_revnum_t working_rev, peg_rev;
1506  apr_table_t *pairs = querystring_to_table(query, pool);
1507  const char *prevstr = apr_table_get(pairs, "p");
1508  const char *wrevstr;
1509
1510  if (prevstr)
1511    {
1512      peg_rev = SVN_STR_TO_REV(prevstr);
1513      if (!SVN_IS_VALID_REVNUM(peg_rev))
1514        return dav_new_error(pool, HTTP_BAD_REQUEST, 0,
1515                             "invalid peg rev in query string");
1516    }
1517  else
1518    {
1519      /* No peg-rev?  Default to HEAD, just like the cmdline client. */
1520      serr = svn_fs_youngest_rev(&peg_rev, comb->priv.repos->fs, pool);
1521      if (serr != NULL)
1522        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1523                                    "Couldn't fetch youngest rev.", pool);
1524    }
1525
1526  wrevstr = apr_table_get(pairs, "r");
1527  if (wrevstr)
1528    {
1529      working_rev = SVN_STR_TO_REV(wrevstr);
1530      if (!SVN_IS_VALID_REVNUM(working_rev))
1531        return dav_new_error(pool, HTTP_BAD_REQUEST, 0,
1532                             "invalid working rev in query string");
1533    }
1534  else
1535    /* No working-rev?  Assume it's equal to the peg-rev, just
1536       like the cmdline client does. */
1537    working_rev = peg_rev;
1538
1539  if (working_rev == peg_rev)
1540    comb->priv.root.rev = peg_rev;
1541  else if (working_rev > peg_rev)
1542    return dav_new_error(pool, HTTP_CONFLICT, 0,
1543                         "working rev greater than peg rev.");
1544  else /* working_rev < peg_rev */
1545    {
1546      const char *newpath;
1547      apr_hash_t *locations;
1548      apr_array_header_t *loc_revs = apr_array_make(pool, 1,
1549                                                    sizeof(svn_revnum_t));
1550
1551      dav_svn__authz_read_baton *arb = apr_pcalloc(pool, sizeof(*arb));
1552      arb->r = comb->priv.r;
1553      arb->repos = comb->priv.repos;
1554
1555      APR_ARRAY_PUSH(loc_revs, svn_revnum_t) = working_rev;
1556      serr = svn_repos_trace_node_locations(comb->priv.repos->fs, &locations,
1557                                            comb->priv.repos_path, peg_rev,
1558                                            loc_revs,
1559                                            dav_svn__authz_read_func(arb),
1560                                            arb, pool);
1561      if (serr != NULL)
1562        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1563                                    "Couldn't trace history.", pool);
1564      newpath = apr_hash_get(locations, &working_rev, sizeof(svn_revnum_t));
1565      if (newpath != NULL)
1566        {
1567          comb->priv.root.rev = working_rev;
1568          comb->priv.repos_path = newpath;
1569        }
1570      else
1571        return dav_new_error(pool, HTTP_NOT_FOUND, 0,
1572                             "path doesn't exist in that revision.");
1573    }
1574
1575  return NULL;
1576}
1577
1578
1579
1580static dav_error *
1581get_resource(request_rec *r,
1582             const char *root_path,
1583             const char *label,
1584             int use_checked_in,
1585             dav_resource **resource)
1586{
1587  const char *fs_path;
1588  const char *repo_name;
1589  const char *xslt_uri;
1590  const char *fs_parent_path;
1591  dav_resource_combined *comb;
1592  dav_svn_repos *repos;
1593  const char *cleaned_uri;
1594  const char *repos_name;
1595  const char *relative;
1596  const char *repos_path;
1597  const char *repos_key;
1598  const char *version_name;
1599  svn_error_t *serr;
1600  dav_error *err;
1601  int had_slash;
1602  dav_locktoken_list *ltl;
1603  struct cleanup_fs_access_baton *cleanup_baton;
1604  void *userdata;
1605
1606  repo_name = dav_svn__get_repo_name(r);
1607  xslt_uri = dav_svn__get_xslt_uri(r);
1608  fs_parent_path = dav_svn__get_fs_parent_path(r);
1609
1610  /* Special case: detect and build the SVNParentPath as a unique type
1611     of private resource, iff the SVNListParentPath directive is 'on'. */
1612  if (fs_parent_path && dav_svn__get_list_parentpath_flag(r))
1613    {
1614      char *uri = apr_pstrdup(r->pool, r->uri);
1615      char *parentpath = apr_pstrdup(r->pool, root_path);
1616      apr_size_t uri_len = strlen(uri);
1617      apr_size_t parentpath_len = strlen(parentpath);
1618
1619      if (uri[uri_len-1] == '/')
1620        uri[uri_len-1] = '\0';
1621
1622      if (parentpath[parentpath_len-1] == '/')
1623        parentpath[parentpath_len-1] = '\0';
1624
1625      if (strcmp(parentpath, uri) == 0)
1626        {
1627          err = get_parentpath_resource(r, root_path, resource);
1628          if (err)
1629            return err;
1630          return NULL;
1631        }
1632    }
1633
1634  /* This does all the work of interpreting/splitting the request uri. */
1635  err = dav_svn_split_uri(r, r->uri, root_path,
1636                          &cleaned_uri, &had_slash,
1637                          &repos_name, &relative, &repos_path);
1638  if (err)
1639    return err;
1640
1641  /* The path that we will eventually try to open as an svn
1642     repository.  Normally defined by the SVNPath directive. */
1643  fs_path = dav_svn__get_fs_path(r);
1644
1645  /* If the SVNParentPath directive was used instead... */
1646  if (fs_parent_path != NULL)
1647    {
1648      /* ...then the URL to the repository is actually one implicit
1649         component longer... */
1650      root_path = svn_path_join(root_path, repos_name, r->pool);
1651      /* ...and we need to specify exactly what repository to open. */
1652      fs_path = svn_path_join(fs_parent_path, repos_name, r->pool);
1653    }
1654
1655  /* Start building and filling a 'combination' object. */
1656  comb = apr_pcalloc(r->pool, sizeof(*comb));
1657  comb->res.info = &comb->priv;
1658  comb->res.hooks = &dav_svn__hooks_repository;
1659  comb->res.pool = r->pool;
1660  comb->res.uri = cleaned_uri;
1661
1662  /* Original request, off which to generate subrequests later. */
1663  comb->priv.r = r;
1664
1665  /* ### ugly hack to carry over Content-Type data to the open_stream, which
1666     ### does not have access to the request headers. */
1667  {
1668    const char *ct = apr_table_get(r->headers_in, "content-type");
1669
1670    comb->priv.is_svndiff =
1671      ct != NULL
1672      && strcmp(ct, SVN_SVNDIFF_MIME_TYPE) == 0;
1673  }
1674
1675  negotiate_encoding_prefs(r, &comb->priv.svndiff_version);
1676
1677  /* ### and another hack for computing diffs to send to the client */
1678  comb->priv.delta_base = apr_table_get(r->headers_in,
1679                                        SVN_DAV_DELTA_BASE_HEADER);
1680
1681  /* Gather any options requested by an svn client. */
1682  comb->priv.svn_client_options = apr_table_get(r->headers_in,
1683                                                SVN_DAV_OPTIONS_HEADER);
1684
1685  /* See if the client sent a custom 'version name' request header. */
1686  version_name = apr_table_get(r->headers_in, SVN_DAV_VERSION_NAME_HEADER);
1687  comb->priv.version_name
1688    = version_name ? SVN_STR_TO_REV(version_name): SVN_INVALID_REVNUM;
1689
1690  /* Remember checksums, if any. */
1691  comb->priv.base_checksum =
1692    apr_table_get(r->headers_in, SVN_DAV_BASE_FULLTEXT_MD5_HEADER);
1693  comb->priv.result_checksum =
1694    apr_table_get(r->headers_in, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER);
1695
1696  /* "relative" is part of the "uri" string, so it has the proper
1697     lifetime to store here. */
1698  /* ### that comment no longer applies. we're creating a string with its
1699     ### own lifetime now. so WHY are we using a string? hmm... */
1700  comb->priv.uri_path = svn_stringbuf_create(relative, r->pool);
1701
1702  /* initialize this until we put something real here */
1703  comb->priv.root.rev = SVN_INVALID_REVNUM;
1704
1705  /* create the repository structure and stash it away */
1706  repos = apr_pcalloc(r->pool, sizeof(*repos));
1707  repos->pool = r->pool;
1708
1709  comb->priv.repos = repos;
1710
1711  /* We are assuming the root_path will live at least as long as this
1712     resource. Considering that it typically comes from the per-dir
1713     config in mod_dav, this is valid for now. */
1714  repos->root_path = svn_path_uri_encode(root_path, r->pool);
1715
1716  /* where is the SVN FS for this resource? */
1717  repos->fs_path = fs_path;
1718
1719  /* A name for the repository */
1720  repos->repo_name = repo_name;
1721
1722  /* The repository filesystem basename */
1723  repos->repo_basename = repos_name;
1724
1725  /* An XSL transformation */
1726  repos->xslt_uri = xslt_uri;
1727
1728  /* Is autoversioning active in this repos? */
1729  repos->autoversioning = dav_svn__get_autoversioning_flag(r);
1730
1731  /* Are bulk updates allowed in this repos? */
1732  repos->bulk_updates = dav_svn__get_bulk_updates_flag(r);
1733
1734  /* Path to activities database */
1735  repos->activities_db = dav_svn__get_activities_db(r);
1736  if (repos->activities_db == NULL)
1737    /* If not specified, use default ($repos/dav/activities.d). */
1738    repos->activities_db = svn_path_join(repos->fs_path,
1739                                         DEFAULT_ACTIVITY_DB,
1740                                         r->pool);
1741  else if (fs_parent_path != NULL)
1742    /* If this is a ParentPath-based repository, treat the specified
1743       path as a similar parent directory. */
1744    repos->activities_db = svn_path_join(repos->activities_db,
1745                                         svn_path_basename(repos->fs_path,
1746                                                           r->pool),
1747                                         r->pool);
1748
1749  /* Remember various bits for later URL construction */
1750  repos->base_url = ap_construct_url(r->pool, "", r);
1751  repos->special_uri = dav_svn__get_special_uri(r);
1752
1753  /* Remember who is making this request */
1754  repos->username = r->user;
1755
1756  /* Allocate room for capabilities, but don't search for any until
1757     we know that this is a Subversion client. */
1758  repos->client_capabilities = apr_hash_make(repos->pool);
1759
1760  /* Remember if the requesting client is a Subversion client, and if
1761     so, what its capabilities are. */
1762  {
1763    const char *val = apr_table_get(r->headers_in, "User-Agent");
1764
1765    if (val && (ap_strstr_c(val, "SVN/") == val))
1766      {
1767        repos->is_svn_client = TRUE;
1768
1769        /* Client capabilities are self-reported.  There is no
1770           guarantee the client actually has the capabilities it says
1771           it has, we just assume it is in the client's interests to
1772           report accurately.  Also, we only remember the capabilities
1773           the server cares about (even though the client may send
1774           more than that). */
1775
1776        /* Start out assuming no capabilities. */
1777        apr_hash_set(repos->client_capabilities, SVN_RA_CAPABILITY_MERGEINFO,
1778                     APR_HASH_KEY_STRING, capability_no);
1779
1780        /* Then see what we can find. */
1781        val = apr_table_get(r->headers_in, "DAV");
1782        if (val)
1783          {
1784            apr_array_header_t *vals
1785              = svn_cstring_split(val, ",", TRUE, r->pool);
1786
1787            if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_MERGEINFO,
1788                                            vals))
1789              {
1790                apr_hash_set(repos->client_capabilities,
1791                             SVN_RA_CAPABILITY_MERGEINFO,
1792                             APR_HASH_KEY_STRING, capability_yes);
1793              }
1794          }
1795      }
1796  }
1797
1798  /* Retrieve/cache open repository */
1799  repos_key = apr_pstrcat(r->pool, "mod_dav_svn:", fs_path, NULL);
1800  apr_pool_userdata_get(&userdata, repos_key, r->connection->pool);
1801  repos->repos = userdata;
1802  if (repos->repos == NULL)
1803    {
1804      serr = svn_repos_open(&(repos->repos), fs_path, r->connection->pool);
1805      if (serr != NULL)
1806        {
1807          /* The error returned by svn_repos_open might contain the
1808             actual path to the failed repository.  We don't want to
1809             leak that path back to the client, because that would be
1810             a security risk, but we do want to log the real error on
1811             the server side. */
1812          return dav_svn__sanitize_error(serr, "Could not open the requested "
1813                                         "SVN filesystem",
1814                                         HTTP_INTERNAL_SERVER_ERROR, r);
1815        }
1816
1817      /* Cache the open repos for the next request on this connection */
1818      apr_pool_userdata_set(repos->repos, repos_key,
1819                            NULL, r->connection->pool);
1820
1821      /* Store the capabilities of the current connection, making sure
1822         to use the same pool repos->repos itself was created in. */
1823      serr = svn_repos_remember_client_capabilities
1824        (repos->repos, capabilities_as_list(repos->client_capabilities,
1825                                            r->connection->pool));
1826      if (serr != NULL)
1827        {
1828          return dav_svn__sanitize_error(serr,
1829                                         "Error storing client capabilities "
1830                                         "in repos object",
1831                                         HTTP_INTERNAL_SERVER_ERROR, r);
1832        }
1833    }
1834
1835  /* cache the filesystem object */
1836  repos->fs = svn_repos_fs(repos->repos);
1837
1838  /* capture warnings during cleanup of the FS */
1839  svn_fs_set_warning_func(repos->fs, log_warning, r);
1840
1841  /* if an authenticated username is present, attach it to the FS */
1842  if (r->user)
1843    {
1844      svn_fs_access_t *access_ctx;
1845
1846      /* The fs is cached in connection->pool, but the fs access
1847         context lives in r->pool.  Because the username or token
1848         could change on each request, we need to make sure that the
1849         fs points to a NULL access context after the request is gone. */
1850      cleanup_baton = apr_pcalloc(r->pool, sizeof(*cleanup_baton));
1851      cleanup_baton->pool = r->pool;
1852      cleanup_baton->fs = repos->fs;
1853      apr_pool_cleanup_register(r->pool, cleanup_baton, cleanup_fs_access,
1854                                apr_pool_cleanup_null);
1855
1856      /* Create an access context based on the authenticated username. */
1857      serr = svn_fs_create_access(&access_ctx, r->user, r->pool);
1858      if (serr)
1859        {
1860          return dav_svn__sanitize_error(serr,
1861                                         "Could not create fs access context",
1862                                         HTTP_INTERNAL_SERVER_ERROR, r);
1863        }
1864
1865      /* Attach the access context to the fs. */
1866      serr = svn_fs_set_access(repos->fs, access_ctx);
1867      if (serr)
1868        {
1869          return dav_svn__sanitize_error(serr, "Could not attach access "
1870                                         "context to fs",
1871                                         HTTP_INTERNAL_SERVER_ERROR, r);
1872        }
1873    }
1874
1875  /* Look for locktokens in the "If:" request header. */
1876  err = dav_get_locktoken_list(r, &ltl);
1877
1878  /* dav_get_locktoken_list claims to return a NULL list when no
1879     locktokens are present.  But it actually throws this error
1880     instead!  So we're deliberately trapping/ignoring it.
1881
1882     This is a workaround for a bug in mod_dav.  Remove this when the
1883     bug is fixed in mod_dav.  See Subversion Issue #2248 */
1884  if (err && (err->error_id != DAV_ERR_IF_ABSENT))
1885    return err;
1886
1887  /* If one or more locktokens are present in the header, push them
1888     into the filesystem access context. */
1889  if (ltl)
1890    {
1891      svn_fs_access_t *access_ctx;
1892      dav_locktoken_list *list = ltl;
1893
1894      serr = svn_fs_get_access(&access_ctx, repos->fs);
1895      if (serr)
1896        {
1897          return dav_svn__sanitize_error(serr, "Lock token is in request, "
1898                                         "but no user name",
1899                                         HTTP_BAD_REQUEST, r);
1900        }
1901
1902      do {
1903        /* Note the path/lock pairs are only for lock token checking
1904           in access, and the relative path is not actually accurate
1905           as it contains the !svn bits.  However, we're using only
1906           the tokens anyway (for access control). */
1907
1908        serr = svn_fs_access_add_lock_token2(access_ctx, relative,
1909                                             list->locktoken->uuid_str);
1910
1911        if (serr)
1912          return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1913                                      "Error pushing token into filesystem.",
1914                                      r->pool);
1915        list = list->next;
1916
1917      } while (list);
1918    }
1919
1920
1921  /* Figure out the type of the resource. Note that we have a PARSE step
1922     which is separate from a PREP step. This is because the PARSE can
1923     map multiple URLs to the same resource type. The PREP operates on
1924     the type of the resource. */
1925
1926  /* skip over the leading "/" in the relative URI */
1927  if (parse_uri(comb, relative + 1, label, use_checked_in))
1928    goto malformed_URI;
1929
1930  /* Check for a query string on a regular-type resource; this allows
1931     us to discover and parse  a "universal" rev-path URI of the form
1932     "path?[r=REV][&p=PEGREV]" */
1933  if ((comb->res.type == DAV_RESOURCE_TYPE_REGULAR)
1934      && (r->parsed_uri.query != NULL))
1935    if ((err = parse_querystring(r->parsed_uri.query, comb, r->pool)) != NULL)
1936      return err;
1937
1938#ifdef SVN_DEBUG
1939  if (comb->res.type == DAV_RESOURCE_TYPE_UNKNOWN)
1940    {
1941      /* Unknown URI. Return NULL to indicate "no resource" */
1942      DBG0("DESIGN FAILURE: should not be UNKNOWN at this point");
1943      *resource = NULL;
1944      return NULL;
1945    }
1946#endif
1947
1948  /* prepare the resource for operation */
1949  if ((err = prep_resource(comb)) != NULL)
1950    return err;
1951
1952  /* a GET request for a REGULAR collection resource MUST have a trailing
1953     slash. Redirect to include one if it does not. */
1954  if (comb->res.collection && comb->res.type == DAV_RESOURCE_TYPE_REGULAR
1955      && !had_slash && r->method_number == M_GET)
1956    {
1957      /* note that we drop r->args. we don't deal with them anyways */
1958      const char *new_path = apr_pstrcat(r->pool,
1959                                         ap_escape_uri(r->pool, r->uri),
1960                                         "/",
1961                                         NULL);
1962      apr_table_setn(r->headers_out, "Location",
1963                     ap_construct_url(r->pool, new_path, r));
1964      return dav_new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0,
1965                           "Requests for a collection must have a "
1966                           "trailing slash on the URI.");
1967    }
1968
1969  *resource = &comb->res;
1970  return NULL;
1971
1972 malformed_URI:
1973  /* A malformed URI error occurs when a URI indicates the "special" area,
1974     yet it has an improper construction. Generally, this is because some
1975     doofus typed it in manually or has a buggy client. */
1976  /* ### pick something other than HTTP_INTERNAL_SERVER_ERROR */
1977  /* ### are SVN_ERR_APMOD codes within the right numeric space? */
1978  return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1979                       SVN_ERR_APMOD_MALFORMED_URI,
1980                       "The URI indicated a resource within Subversion's "
1981                       "special resource area, but does not exist. This is "
1982                       "generally caused by a problem in the client "
1983                       "software.");
1984}
1985
1986
1987/* Helper func:  return the parent of PATH, allocated in POOL. */
1988static const char *
1989get_parent_path(const char *path, apr_pool_t *pool)
1990{
1991  apr_size_t len;
1992  const char *parentpath, *base_name;
1993  char *tmp = apr_pstrdup(pool, path);
1994
1995  len = strlen(tmp);
1996
1997  if (len > 0)
1998    {
1999      /* Remove any trailing slash; else svn_path_split() asserts. */
2000      if (tmp[len-1] == '/')
2001        tmp[len-1] = '\0';
2002      svn_path_split(tmp, &parentpath, &base_name, pool);
2003
2004      return parentpath;
2005    }
2006
2007  return path;
2008}
2009
2010
2011static dav_error *
2012get_parent_resource(const dav_resource *resource,
2013                    dav_resource **parent_resource)
2014{
2015  dav_resource *parent;
2016  dav_resource_private *parentinfo;
2017  svn_stringbuf_t *path = resource->info->uri_path;
2018
2019  /* the root of the repository has no parent */
2020  if (path->len == 1 && *path->data == '/')
2021    {
2022      *parent_resource = NULL;
2023      return NULL;
2024    }
2025
2026  switch (resource->type)
2027    {
2028    case DAV_RESOURCE_TYPE_REGULAR:
2029
2030      parent = apr_pcalloc(resource->pool, sizeof(*parent));
2031      parentinfo  = apr_pcalloc(resource->pool, sizeof(*parentinfo));
2032
2033      parent->type = DAV_RESOURCE_TYPE_REGULAR;
2034      parent->exists = 1;
2035      parent->collection = 1;
2036      parent->versioned = 1;
2037      parent->hooks = resource->hooks;
2038      parent->pool = resource->pool;
2039      parent->uri = get_parent_path(resource->uri, resource->pool);
2040      parent->info = parentinfo;
2041
2042      parentinfo->pool = resource->info->pool;
2043      parentinfo->uri_path =
2044        svn_stringbuf_create(get_parent_path(resource->info->uri_path->data,
2045                                             resource->pool), resource->pool);
2046      parentinfo->repos = resource->info->repos;
2047      parentinfo->root = resource->info->root;
2048      parentinfo->r = resource->info->r;
2049      parentinfo->svn_client_options = resource->info->svn_client_options;
2050      parentinfo->repos_path = get_parent_path(resource->info->repos_path,
2051                                               resource->pool);
2052
2053      *parent_resource = parent;
2054      break;
2055
2056    case DAV_RESOURCE_TYPE_WORKING:
2057      /* The "/" occurring within the URL of working resources is part of
2058         its identifier; it does not establish parent resource relationships.
2059         All working resources have the same parent, which is:
2060         http://host.name/path2repos/$svn/wrk/
2061      */
2062      *parent_resource =
2063        create_private_resource(resource, DAV_SVN_RESTYPE_WRK_COLLECTION);
2064      break;
2065
2066    case DAV_RESOURCE_TYPE_ACTIVITY:
2067      *parent_resource =
2068        create_private_resource(resource, DAV_SVN_RESTYPE_ACT_COLLECTION);
2069      break;
2070
2071    default:
2072      /* ### needs more work. need parents for other resource types
2073         ###
2074         ### return an error so we can easily identify the cases where
2075         ### we've called this function unexpectedly. */
2076      return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2077                           apr_psprintf(resource->pool,
2078                                        "get_parent_resource was called for "
2079                                        "%s (type %d)",
2080                                        resource->uri, resource->type));
2081      break;
2082    }
2083
2084  return NULL;
2085}
2086
2087
2088/* does RES2 live in the same repository as RES1? */
2089static int
2090is_our_resource(const dav_resource *res1, const dav_resource *res2)
2091{
2092  if (res1->hooks != res2->hooks
2093      || strcmp(res1->info->repos->fs_path, res2->info->repos->fs_path) != 0)
2094    {
2095      /* a different provider, or a different FS repository */
2096      return 0;
2097    }
2098
2099  /* coalesce the repository */
2100  if (res1->info->repos != res2->info->repos)
2101    {
2102      /* ### might be nice to have a pool which we can clear to toss
2103         ### out the old, redundant repos/fs.  */
2104
2105      /* have res2 point to res1's filesystem */
2106      res2->info->repos = res1->info->repos;
2107
2108      /* res2's fs_root object is now invalid.  regenerate it using
2109         the now-shared filesystem. */
2110      if (res2->info->root.txn_name)
2111        {
2112          /* reopen the txn by name */
2113          svn_error_clear(svn_fs_open_txn(&(res2->info->root.txn),
2114                                          res2->info->repos->fs,
2115                                          res2->info->root.txn_name,
2116                                          res2->info->repos->pool));
2117
2118          /* regenerate the txn "root" object */
2119          svn_error_clear(svn_fs_txn_root(&(res2->info->root.root),
2120                                          res2->info->root.txn,
2121                                          res2->info->repos->pool));
2122        }
2123      else if (res2->info->root.rev)
2124        {
2125          /* default:  regenerate the revision "root" object */
2126          svn_error_clear(svn_fs_revision_root(&(res2->info->root.root),
2127                                               res2->info->repos->fs,
2128                                               res2->info->root.rev,
2129                                               res2->info->repos->pool));
2130        }
2131    }
2132
2133  return 1;
2134}
2135
2136
2137static int
2138is_same_resource(const dav_resource *res1, const dav_resource *res2)
2139{
2140  if (!is_our_resource(res1, res2))
2141    return 0;
2142
2143  /* ### what if the same resource were reached via two URIs? */
2144
2145  return svn_stringbuf_compare(res1->info->uri_path, res2->info->uri_path);
2146}
2147
2148
2149static int
2150is_parent_resource(const dav_resource *res1, const dav_resource *res2)
2151{
2152  apr_size_t len1 = strlen(res1->info->uri_path->data);
2153  apr_size_t len2;
2154
2155  if (!is_our_resource(res1, res2))
2156    return 0;
2157
2158  /* ### what if a resource were reached via two URIs? we ought to define
2159     ### parent/child relations for resources independent of URIs.
2160     ### i.e. define a "canonical" location for each resource, then return
2161     ### the parent based on that location. */
2162
2163  /* res2 is one of our resources, we can use its ->info ptr */
2164  len2 = strlen(res2->info->uri_path->data);
2165
2166  return (len2 > len1
2167          && memcmp(res1->info->uri_path->data, res2->info->uri_path->data,
2168                    len1) == 0
2169          && res2->info->uri_path->data[len1] == '/');
2170}
2171
2172
2173#if 0
2174/* Given an apache request R and a ROOT_PATH to the svn location
2175   block, set *KIND to the node-kind of the URI's associated
2176   (revision, path) pair, if possible.
2177
2178   Public uris, baseline collections, version resources, and working
2179   (non-baseline) resources all have associated (revision, path)
2180   pairs, and thus one of {svn_node_file, svn_node_dir, svn_node_none}
2181   will be returned.
2182
2183   If URI is something more abstract, then set *KIND to
2184   svn_node_unknown.  This is true for baselines, working baselines,
2185   version controled configurations, activities, histories, and other
2186   private resources.
2187*/
2188static dav_error *
2189resource_kind(request_rec *r,
2190              const char *uri,
2191              const char *root_path,
2192              svn_node_kind_t *kind)
2193{
2194  dav_error *derr;
2195  svn_error_t *serr;
2196  dav_resource *resource;
2197  svn_revnum_t base_rev;
2198  svn_fs_root_t *base_rev_root;
2199  char *saved_uri;
2200
2201  /* Temporarily insert the uri that the user actually wants us to
2202     convert into a resource.  Typically, this is already r->uri, so
2203     this is usually a no-op.  But sometimes the caller may pass in
2204     the Destination: header uri.
2205
2206     ### WHAT WE REALLY WANT here is to refactor get_resource,
2207     so that some alternate interface actually allows us to specify
2208     the URI to process, i.e. not always process r->uri.
2209  */
2210  saved_uri = r->uri;
2211  r->uri = apr_pstrdup(r->pool, uri);
2212
2213  /* parse the uri and prep the associated resource. */
2214  derr = get_resource(r, root_path,
2215                      /* ### I can't believe that every single
2216                         parser ignores the LABEL and USE_CHECKED_IN
2217                         args below!! */
2218                      "ignored_label", 1,
2219                      &resource);
2220  /* Restore r back to normal. */
2221  r->uri = saved_uri;
2222
2223  if (derr)
2224    return derr;
2225
2226  if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
2227    {
2228      /* Either a public URI or a bc.  In both cases, prep_regular()
2229         has already set the 'exists' and 'collection' flags by
2230         querying the appropriate revision root and path.  */
2231      if (! resource->exists)
2232        *kind = svn_node_none;
2233      else
2234        *kind = resource->collection ? svn_node_dir : svn_node_file;
2235    }
2236
2237  else if (resource->type == DAV_RESOURCE_TYPE_VERSION)
2238    {
2239      if (resource->baselined)  /* bln */
2240        *kind = svn_node_unknown;
2241
2242      else /* ver */
2243        {
2244          derr = fs_check_path(kind, resource->info->root.root,
2245                               resource->info->repos_path, r->pool);
2246          if (derr != NULL)
2247            return derr;
2248        }
2249    }
2250
2251  else if (resource->type == DAV_RESOURCE_TYPE_WORKING)
2252    {
2253      if (resource->baselined) /* wbl */
2254        *kind = svn_node_unknown;
2255
2256      else /* wrk */
2257        {
2258          /* don't call fs_check_path on the txn, but on the original
2259             revision that the txn is based on. */
2260          base_rev = svn_fs_txn_base_revision(resource->info->root.txn);
2261          serr = svn_fs_revision_root(&base_rev_root,
2262                                      resource->info->repos->fs,
2263                                      base_rev, r->pool);
2264          if (serr)
2265            return dav_svn__convert_err
2266              (serr, HTTP_INTERNAL_SERVER_ERROR,
2267               apr_psprintf(r->pool,
2268                            "Could not open root of revision %ld",
2269                            base_rev),
2270               r->pool);
2271
2272          derr = fs_check_path(kind, base_rev_root,
2273                               resource->info->repos_path, r->pool);
2274          if (derr != NULL)
2275            return derr;
2276        }
2277    }
2278
2279  else
2280    /* act, his, vcc, or some other private resource */
2281    *kind = svn_node_unknown;
2282
2283  return NULL;
2284}
2285#endif
2286
2287
2288static dav_error *
2289open_stream(const dav_resource *resource,
2290            dav_stream_mode mode,
2291            dav_stream **stream)
2292{
2293  svn_node_kind_t kind;
2294  dav_error *derr;
2295  svn_error_t *serr;
2296
2297  if (mode == DAV_MODE_WRITE_TRUNC || mode == DAV_MODE_WRITE_SEEKABLE)
2298    {
2299      if (resource->type != DAV_RESOURCE_TYPE_WORKING)
2300        {
2301          return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
2302                               "Resource body changes may only be made to "
2303                               "working resources [at this time].");
2304        }
2305    }
2306
2307#if 1
2308  if (mode == DAV_MODE_WRITE_SEEKABLE)
2309    {
2310      return dav_new_error(resource->pool, HTTP_NOT_IMPLEMENTED, 0,
2311                           "Resource body writes cannot use ranges "
2312                           "[at this time].");
2313    }
2314#endif
2315
2316  /* start building the stream structure */
2317  *stream = apr_pcalloc(resource->pool, sizeof(**stream));
2318  (*stream)->res = resource;
2319
2320  derr = fs_check_path(&kind, resource->info->root.root,
2321                       resource->info->repos_path, resource->pool);
2322  if (derr != NULL)
2323    return derr;
2324
2325  if (kind == svn_node_none) /* No existing file. */
2326    {
2327      serr = svn_fs_make_file(resource->info->root.root,
2328                              resource->info->repos_path,
2329                              resource->pool);
2330
2331      if (serr != NULL)
2332        {
2333          return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2334                                      "Could not create file within the "
2335                                      "repository.",
2336                                      resource->pool);
2337        }
2338    }
2339
2340  /* if the working-resource was auto-checked-out (i.e. came into
2341     existence through the autoversioning feature), then possibly set
2342     the svn:mime-type property based on whatever value mod_mime has
2343     chosen.  If the path already has an svn:mime-type property
2344     set, do nothing. */
2345  if (resource->info->auto_checked_out
2346      && resource->info->r->content_type)
2347    {
2348      svn_string_t *mime_type;
2349
2350      serr = svn_fs_node_prop(&mime_type,
2351                              resource->info->root.root,
2352                              resource->info->repos_path,
2353                              SVN_PROP_MIME_TYPE,
2354                              resource->pool);
2355
2356      if (serr != NULL)
2357        {
2358          return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2359                                      "Error fetching mime-type property.",
2360                                      resource->pool);
2361        }
2362
2363      if (!mime_type)
2364        {
2365          serr = svn_fs_change_node_prop(resource->info->root.root,
2366                                         resource->info->repos_path,
2367                                         SVN_PROP_MIME_TYPE,
2368                                         svn_string_create
2369                                             (resource->info->r->content_type,
2370                                              resource->pool),
2371                                         resource->pool);
2372          if (serr != NULL)
2373            {
2374              return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2375                                          "Could not set mime-type property.",
2376                                          resource->pool);
2377            }
2378        }
2379    }
2380
2381  serr = svn_fs_apply_textdelta(&(*stream)->delta_handler,
2382                                &(*stream)->delta_baton,
2383                                resource->info->root.root,
2384                                resource->info->repos_path,
2385                                resource->info->base_checksum,
2386                                resource->info->result_checksum,
2387                                resource->pool);
2388
2389  if (serr != NULL)
2390    {
2391      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2392                                  "Could not prepare to write the file",
2393                                  resource->pool);
2394    }
2395
2396  /* if the incoming data is an SVNDIFF, then create a stream that
2397     will process the data into windows and invoke the FS window handler
2398     when a window is ready. */
2399  /* ### we need a better way to check the content-type! this is bogus
2400     ### because we're effectively looking at the request_rec. doubly
2401     ### bogus because this means you cannot open arbitrary streams and
2402     ### feed them content (the type is always tied to a request_rec).
2403     ### probably ought to pass the type to open_stream */
2404  if (resource->info->is_svndiff)
2405    {
2406      (*stream)->wstream =
2407        svn_txdelta_parse_svndiff((*stream)->delta_handler,
2408                                  (*stream)->delta_baton,
2409                                  TRUE,
2410                                  resource->pool);
2411    }
2412
2413  return NULL;
2414}
2415
2416
2417static dav_error *
2418close_stream(dav_stream *stream, int commit)
2419{
2420  svn_error_t *serr;
2421  apr_pool_t *pool = stream->res->pool;
2422
2423  if (stream->rstream != NULL)
2424    {
2425      serr = svn_stream_close(stream->rstream);
2426      if (serr)
2427        return dav_svn__convert_err
2428          (serr, HTTP_INTERNAL_SERVER_ERROR,
2429           "mod_dav_svn close_stream: error closing read stream",
2430           pool);
2431    }
2432
2433  /* if we have a write-stream, then closing it also takes care of the
2434     handler (so make sure not to send a NULL to it, too) */
2435  if (stream->wstream != NULL)
2436    {
2437      serr = svn_stream_close(stream->wstream);
2438      if (serr)
2439        return dav_svn__convert_err
2440          (serr, HTTP_INTERNAL_SERVER_ERROR,
2441           "mod_dav_svn close_stream: error closing write stream",
2442           pool);
2443    }
2444  else if (stream->delta_handler != NULL)
2445    {
2446      serr = (*stream->delta_handler)(NULL, stream->delta_baton);
2447      if (serr)
2448        return dav_svn__convert_err
2449          (serr, HTTP_INTERNAL_SERVER_ERROR,
2450           "mod_dav_svn close_stream: error sending final (null) delta window",
2451           pool);
2452    }
2453
2454  return NULL;
2455}
2456
2457
2458static dav_error *
2459write_stream(dav_stream *stream, const void *buf, apr_size_t bufsize)
2460{
2461  svn_error_t *serr;
2462  apr_pool_t *pool = stream->res->pool;
2463
2464  if (stream->wstream != NULL)
2465    {
2466      serr = svn_stream_write(stream->wstream, buf, &bufsize);
2467      /* ### would the returned bufsize ever not match the requested amt? */
2468    }
2469  else
2470    {
2471      svn_txdelta_window_t window = { 0 };
2472      svn_txdelta_op_t op;
2473      svn_string_t data;
2474
2475      data.data = buf;
2476      data.len = bufsize;
2477
2478      op.action_code = svn_txdelta_new;
2479      op.offset = 0;
2480      op.length = bufsize;
2481
2482      window.tview_len = bufsize;   /* result will be this long */
2483      window.num_ops = 1;
2484      window.ops = &op;
2485      window.new_data = &data;
2486
2487      serr = (*stream->delta_handler)(&window, stream->delta_baton);
2488    }
2489
2490  if (serr)
2491    {
2492      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2493                                  "could not write the file contents",
2494                                  pool);
2495    }
2496  return NULL;
2497}
2498
2499
2500static dav_error *
2501seek_stream(dav_stream *stream, apr_off_t abs_position)
2502{
2503  /* ### fill this in */
2504
2505  return dav_new_error(stream->res->pool, HTTP_NOT_IMPLEMENTED, 0,
2506                       "Resource body read/write cannot use ranges "
2507                       "(at this time)");
2508}
2509
2510/* Returns whether the DAV resource lacks potential for generation of
2511   an ETag (defined as any of the following):
2512   - it doesn't exist
2513   - the resource type isn't REGULAR or VERSION
2514   - the resource is a Baseline */
2515#define RESOURCE_LACKS_ETAG_POTENTIAL(resource) \
2516  (!resource->exists \
2517   || (resource->type != DAV_RESOURCE_TYPE_REGULAR \
2518       && resource->type != DAV_RESOURCE_TYPE_VERSION) \
2519   || (resource->type == DAV_RESOURCE_TYPE_VERSION \
2520       && resource->baselined))
2521
2522
2523/* Return the last modification time of RESOURCE, or -1 if the DAV
2524   resource type is not handled, or if an error occurs.  Temporary
2525   allocations are made from RESOURCE->POOL. */
2526static apr_time_t
2527get_last_modified(const dav_resource *resource)
2528{
2529  apr_time_t last_modified;
2530  svn_error_t *serr;
2531  svn_revnum_t created_rev;
2532  svn_string_t *date_time;
2533
2534  if (RESOURCE_LACKS_ETAG_POTENTIAL(resource))
2535    return -1;
2536
2537  if ((serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root,
2538                                      resource->info->repos_path,
2539                                      resource->pool)))
2540    {
2541      svn_error_clear(serr);
2542      return -1;
2543    }
2544
2545  if ((serr = svn_fs_revision_prop(&date_time, resource->info->repos->fs,
2546                                   created_rev, "svn:date", resource->pool)))
2547    {
2548      svn_error_clear(serr);
2549      return -1;
2550    }
2551
2552  if (date_time == NULL || date_time->data == NULL)
2553    return -1;
2554
2555  if ((serr = svn_time_from_cstring(&last_modified, date_time->data,
2556                                    resource->pool)))
2557    {
2558      svn_error_clear(serr);
2559      return -1;
2560    }
2561
2562  return last_modified;
2563}
2564
2565
2566const char *
2567dav_svn__getetag(const dav_resource *resource, apr_pool_t *pool)
2568{
2569  svn_error_t *serr;
2570  svn_revnum_t created_rev;
2571
2572  if (RESOURCE_LACKS_ETAG_POTENTIAL(resource))
2573    return "";
2574
2575  /* ### what kind of etag to return for activities, etc.? */
2576
2577  if ((serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root,
2578                                      resource->info->repos_path,
2579                                      pool)))
2580    {
2581      /* ### what to do? */
2582      svn_error_clear(serr);
2583      return "";
2584    }
2585
2586  /* Use the "weak" format of the etag for collections because our GET
2587     requests on collections include dynamic data (the HEAD revision,
2588     the build version of Subversion, etc.). */
2589  return apr_psprintf(pool, "%s\"%ld/%s\"",
2590                      resource->collection ? "W/" : "",
2591                      created_rev,
2592                      apr_xml_quote_string(pool,
2593                                           resource->info->repos_path, 1));
2594}
2595
2596
2597/* Since dav_svn__getetag() takes a pool argument, this wrapper is for
2598   the mod_dav hooks vtable entry, which does not. */
2599static const char *
2600getetag_pathetic(const dav_resource *resource)
2601{
2602  return dav_svn__getetag(resource, resource->pool);
2603}
2604
2605
2606static dav_error *
2607set_headers(request_rec *r, const dav_resource *resource)
2608{
2609  svn_error_t *serr;
2610  svn_filesize_t length;
2611  const char *mimetype = NULL;
2612  apr_time_t last_modified;
2613
2614  if (!resource->exists)
2615    return NULL;
2616
2617  last_modified = get_last_modified(resource);
2618  if (last_modified != -1)
2619    {
2620      /* Note the modification time for the requested resource, and
2621         include the Last-Modified header in the response. */
2622      ap_update_mtime(r, last_modified);
2623      ap_set_last_modified(r);
2624    }
2625
2626  /* generate our etag and place it into the output */
2627  apr_table_setn(r->headers_out, "ETag",
2628                 dav_svn__getetag(resource, resource->pool));
2629
2630#if 0
2631  /* As version resources don't change, encourage caching. */
2632  /* ### FIXME: This conditional is wrong -- type is often REGULAR,
2633     ### and the resource doesn't seem to be baselined. */
2634  if (resource->type == DAV_RESOURCE_TYPE_VERSION)
2635    /* Cache resource for one week (specified in seconds). */
2636    apr_table_setn(r->headers_out, "Cache-Control", "max-age=604800");
2637#endif
2638
2639  /* we accept byte-ranges */
2640  apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
2641
2642  /* For a directory, we will send text/html or text/xml. If we have a delta
2643     base, then we will always be generating an svndiff.  Otherwise,
2644     we need to fetch the appropriate MIME type from the resource's
2645     properties (and use text/plain if it isn't there). */
2646  if (resource->collection)
2647    {
2648      if (resource->info->repos->xslt_uri)
2649        mimetype = "text/xml";
2650      else
2651        mimetype = "text/html; charset=UTF-8";
2652    }
2653  else if (resource->info->delta_base != NULL)
2654    {
2655      dav_svn__uri_info info;
2656
2657      /* First order of business is to parse it. */
2658      serr = dav_svn__simple_parse_uri(&info, resource,
2659                                       resource->info->delta_base,
2660                                       resource->pool);
2661
2662      /* If we successfully parse the base URL, then send an svndiff. */
2663      if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM))
2664        {
2665          mimetype = SVN_SVNDIFF_MIME_TYPE;
2666        }
2667      svn_error_clear(serr);
2668    }
2669
2670  if ((mimetype == NULL)
2671      && ((resource->type == DAV_RESOURCE_TYPE_VERSION)
2672          || (resource->type == DAV_RESOURCE_TYPE_REGULAR))
2673      && (resource->info->repos_path != NULL))
2674    {
2675      svn_string_t *value;
2676
2677      serr = svn_fs_node_prop(&value,
2678                              resource->info->root.root,
2679                              resource->info->repos_path,
2680                              SVN_PROP_MIME_TYPE,
2681                              resource->pool);
2682      if (serr != NULL)
2683        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2684                                    "could not fetch the resource's MIME type",
2685                                    resource->pool);
2686
2687      if (value)
2688        mimetype = value->data;
2689      else if ((! resource->info->repos->is_svn_client)
2690               && r->content_type)
2691        mimetype = r->content_type;
2692      else
2693        mimetype = ap_default_type(r);
2694
2695      serr = svn_mime_type_validate(mimetype, resource->pool);
2696      if (serr)
2697        {
2698          /* Probably serr->apr == SVN_ERR_BAD_MIME_TYPE, but
2699             there's no point even checking.  No matter what the
2700             error is, we can't derive the mime type from the
2701             svn:mime-type property.  So we resort to the infamous
2702             "mime type of last resort." */
2703          svn_error_clear(serr);
2704          mimetype = "application/octet-stream";
2705        }
2706
2707      /* if we aren't sending a diff, then we know the length of the file,
2708         so set up the Content-Length header */
2709      serr = svn_fs_file_length(&length,
2710                                resource->info->root.root,
2711                                resource->info->repos_path,
2712                                resource->pool);
2713      if (serr != NULL)
2714        {
2715          return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2716                                      "could not fetch the resource length",
2717                                      resource->pool);
2718        }
2719      ap_set_content_length(r, (apr_off_t) length);
2720    }
2721
2722  /* set the discovered MIME type */
2723  /* ### it would be best to do this during the findct phase... */
2724  ap_set_content_type(r, mimetype);
2725
2726  return NULL;
2727}
2728
2729
2730typedef struct {
2731  ap_filter_t *output;
2732  apr_pool_t *pool;
2733} diff_ctx_t;
2734
2735
2736static svn_error_t *
2737write_to_filter(void *baton, const char *buffer, apr_size_t *len)
2738{
2739  diff_ctx_t *dc = baton;
2740  apr_bucket_brigade *bb;
2741  apr_bucket *bkt;
2742  apr_status_t status;
2743
2744  /* take the current data and shove it into the filter */
2745  bb = apr_brigade_create(dc->pool, dc->output->c->bucket_alloc);
2746  bkt = apr_bucket_transient_create(buffer, *len, dc->output->c->bucket_alloc);
2747  APR_BRIGADE_INSERT_TAIL(bb, bkt);
2748  if ((status = ap_pass_brigade(dc->output, bb)) != APR_SUCCESS) {
2749    return svn_error_create(status, NULL,
2750                            "Could not write data to filter");
2751  }
2752
2753  return SVN_NO_ERROR;
2754}
2755
2756
2757static svn_error_t *
2758close_filter(void *baton)
2759{
2760  diff_ctx_t *dc = baton;
2761  apr_bucket_brigade *bb;
2762  apr_bucket *bkt;
2763  apr_status_t status;
2764
2765  /* done with the file. write an EOS bucket now. */
2766  bb = apr_brigade_create(dc->pool, dc->output->c->bucket_alloc);
2767  bkt = apr_bucket_eos_create(dc->output->c->bucket_alloc);
2768  APR_BRIGADE_INSERT_TAIL(bb, bkt);
2769  if ((status = ap_pass_brigade(dc->output, bb)) != APR_SUCCESS)
2770    return svn_error_create(status, NULL, "Could not write EOS to filter");
2771
2772  return SVN_NO_ERROR;
2773}
2774
2775
2776static dav_error *
2777deliver(const dav_resource *resource, ap_filter_t *output)
2778{
2779  svn_error_t *serr;
2780  apr_bucket_brigade *bb;
2781  apr_bucket *bkt;
2782  apr_status_t status;
2783
2784  /* Check resource type */
2785  if (resource->type != DAV_RESOURCE_TYPE_REGULAR
2786      && resource->type != DAV_RESOURCE_TYPE_VERSION
2787      && resource->type != DAV_RESOURCE_TYPE_WORKING
2788      && resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
2789    {
2790      return dav_new_error(resource->pool, HTTP_CONFLICT, 0,
2791                           "Cannot GET this type of resource.");
2792    }
2793
2794  if (resource->collection)
2795    {
2796      const int gen_html = !resource->info->repos->xslt_uri;
2797      apr_hash_t *entries;
2798      apr_pool_t *entry_pool;
2799      apr_array_header_t *sorted;
2800      int i;
2801
2802      /* XML schema for the directory index if xslt_uri is set:
2803
2804         <?xml version="1.0"?>
2805         <?xml-stylesheet type="text/xsl" href="[info->repos->xslt_uri]"?> */
2806      static const char xml_index_dtd[] =
2807        "<!DOCTYPE svn [\n"
2808        "  <!ELEMENT svn   (index)>\n"
2809        "  <!ATTLIST svn   version CDATA #REQUIRED\n"
2810        "                  href    CDATA #REQUIRED>\n"
2811        "  <!ELEMENT index (updir?, (file | dir)*)>\n"
2812        "  <!ATTLIST index name    CDATA #IMPLIED\n"
2813        "                  path    CDATA #IMPLIED\n"
2814        "                  rev     CDATA #IMPLIED\n"
2815        "                  base    CDATA #IMPLIED>\n"
2816        "  <!ELEMENT updir EMPTY>\n"
2817        "  <!ELEMENT file  EMPTY>\n"
2818        "  <!ATTLIST file  name    CDATA #REQUIRED\n"
2819        "                  href    CDATA #REQUIRED>\n"
2820        "  <!ELEMENT dir   EMPTY>\n"
2821        "  <!ATTLIST dir   name    CDATA #REQUIRED\n"
2822        "                  href    CDATA #REQUIRED>\n"
2823        "]>\n";
2824
2825      /* <svn version="1.3.0 (dev-build)"
2826              href="http://subversion.tigris.org">
2827           <index name="[info->repos->repo_name]"
2828                  path="[info->repos_path]"
2829                  rev="[info->root.rev]">
2830             <file name="foo" href="foo" />
2831             <dir name="bar" href="bar/" />
2832           </index>
2833         </svn> */
2834
2835
2836      /* ### TO-DO:  check for a new mod_dav_svn directive here also. */
2837      if (resource->info->restype == DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
2838        {
2839          apr_hash_index_t *hi;
2840          apr_hash_t *dirents;
2841          const char *fs_parent_path =
2842            dav_svn__get_fs_parent_path(resource->info->r);
2843
2844          serr = svn_io_get_dirents2(&dirents, fs_parent_path, resource->pool);
2845          if (serr != NULL)
2846            return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2847                                        "couldn't fetch dirents of SVNParentPath",
2848                                        resource->pool);
2849
2850          /* convert an io dirent hash to an fs dirent hash. */
2851          entries = apr_hash_make(resource->pool);
2852          for (hi = apr_hash_first(resource->pool, dirents);
2853               hi; hi = apr_hash_next(hi))
2854            {
2855              const void *key;
2856              void *val;
2857              svn_io_dirent_t *dirent;
2858              svn_fs_dirent_t *ent = apr_pcalloc(resource->pool, sizeof(*ent));
2859
2860              apr_hash_this(hi, &key, NULL, &val);
2861              dirent = val;
2862
2863              if (dirent->kind != svn_node_dir)
2864                continue;
2865
2866              ent->name = key;
2867              ent->id = NULL;     /* ### does it matter? */
2868              ent->kind = dirent->kind;
2869
2870              apr_hash_set(entries, key, APR_HASH_KEY_STRING, ent);
2871            }
2872
2873        }
2874      else
2875        {
2876          serr = svn_fs_dir_entries(&entries, resource->info->root.root,
2877                                    resource->info->repos_path, resource->pool);
2878          if (serr != NULL)
2879            return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2880                                        "could not fetch directory entries",
2881                                        resource->pool);
2882        }
2883
2884      bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
2885
2886      if (gen_html)
2887        {
2888          const char *title;
2889          if (resource->info->repos_path == NULL)
2890            title = "unknown location";
2891          else
2892            title = resource->info->repos_path;
2893
2894          if (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
2895            {
2896              if (SVN_IS_VALID_REVNUM(resource->info->root.rev))
2897                title = apr_psprintf(resource->pool,
2898                                     "Revision %ld: %s",
2899                                     resource->info->root.rev, title);
2900              if (resource->info->repos->repo_basename)
2901                title = apr_psprintf(resource->pool, "%s - %s",
2902                                     resource->info->repos->repo_basename,
2903                                     title);
2904              if (resource->info->repos->repo_name)
2905                title = apr_psprintf(resource->pool, "%s: %s",
2906                                     resource->info->repos->repo_name,
2907                                     title);
2908            }
2909
2910          ap_fprintf(output, bb, "<html><head><title>%s</title></head>\n"
2911                     "<body>\n <h2>%s</h2>\n <ul>\n", title, title);
2912        }
2913      else
2914        {
2915          const char *name = resource->info->repos->repo_name;
2916          const char *href = resource->info->repos_path;
2917          const char *base = resource->info->repos->repo_basename;
2918
2919          ap_fputs(output, bb, "<?xml version=\"1.0\"?>\n");
2920          ap_fprintf(output, bb,
2921                     "<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>\n",
2922                     resource->info->repos->xslt_uri);
2923          ap_fputs(output, bb, xml_index_dtd);
2924          ap_fputs(output, bb,
2925                   "<svn version=\"" SVN_VERSION "\"\n"
2926                   "     href=\"http://subversion.tigris.org/\">\n");
2927          ap_fputs(output, bb, "  <index");
2928          if (name)
2929            ap_fprintf(output, bb, " name=\"%s\"",
2930                       apr_xml_quote_string(resource->pool, name, 1));
2931          if (SVN_IS_VALID_REVNUM(resource->info->root.rev))
2932            ap_fprintf(output, bb, " rev=\"%ld\"",
2933                       resource->info->root.rev);
2934          if (href)
2935            ap_fprintf(output, bb, " path=\"%s\"",
2936                       apr_xml_quote_string(resource->pool,
2937                                            href,
2938                                            1));
2939          if (base)
2940            ap_fprintf(output, bb, " base=\"%s\"", base);
2941
2942          ap_fputs(output, bb, ">\n");
2943        }
2944
2945      if ((resource->info->repos_path && resource->info->repos_path[1] != '\0')
2946          && (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION))
2947        {
2948          if (gen_html)
2949            ap_fprintf(output, bb, "  <li><a href=\"../\">..</a></li>\n");
2950          else
2951            ap_fprintf(output, bb, "    <updir />\n");
2952        }
2953
2954      /* get a sorted list of the entries */
2955      sorted = svn_sort__hash(entries, svn_sort_compare_items_as_paths,
2956                              resource->pool);
2957
2958      entry_pool = svn_pool_create(resource->pool);
2959
2960      for (i = 0; i < sorted->nelts; ++i)
2961        {
2962          const svn_sort__item_t *item = &APR_ARRAY_IDX(sorted, i,
2963                                                        const svn_sort__item_t);
2964          const svn_fs_dirent_t *entry = item->value;
2965          const char *name = item->key;
2966          const char *href = name;
2967          svn_boolean_t is_dir = (entry->kind == svn_node_dir);
2968
2969          svn_pool_clear(entry_pool);
2970
2971          /* append a trailing slash onto the name for directories. we NEED
2972             this for the href portion so that the relative reference will
2973             descend properly. for the visible portion, it is just nice. */
2974          /* ### The xml output doesn't like to see a trailing slash on
2975             ### the visible portion, so avoid that. */
2976          if (is_dir)
2977            href = apr_pstrcat(entry_pool, href, "/", NULL);
2978
2979          if (gen_html)
2980            name = href;
2981
2982          /* We quote special characters in both XML and HTML. */
2983          name = apr_xml_quote_string(entry_pool, name, !gen_html);
2984
2985          /* According to httpd-2.0.54/include/httpd.h, ap_os_escape_path()
2986             behaves differently on different platforms.  It claims to
2987             "convert an OS path to a URL in an OS dependant way".
2988             Nevertheless, there appears to be only one implementation
2989             of the function in httpd, and the code seems completely
2990             platform independent, so we'll assume it's appropriate for
2991             mod_dav_svn to use it to quote outbound paths. */
2992          href = ap_os_escape_path(entry_pool, href, 0);
2993          href = apr_xml_quote_string(entry_pool, href, 1);
2994
2995          if (gen_html)
2996            {
2997              ap_fprintf(output, bb,
2998                         "  <li><a href=\"%s\">%s</a></li>\n",
2999                         href, name);
3000            }
3001          else
3002            {
3003              const char *const tag = (is_dir ? "dir" : "file");
3004
3005              /* This is where we could search for props */
3006
3007              ap_fprintf(output, bb,
3008                         "    <%s name=\"%s\" href=\"%s\" />\n",
3009                         tag, name, href);
3010            }
3011        }
3012
3013      svn_pool_destroy(entry_pool);
3014
3015      if (gen_html)
3016        ap_fputs(output, bb,
3017                 " </ul>\n <hr noshade><em>Powered by "
3018                 "<a href=\"http://subversion.tigris.org/\">Subversion</a> "
3019                 "version " SVN_VERSION "."
3020                 "</em>\n</body></html>");
3021      else
3022        ap_fputs(output, bb, "  </index>\n</svn>\n");
3023
3024      bkt = apr_bucket_eos_create(output->c->bucket_alloc);
3025      APR_BRIGADE_INSERT_TAIL(bb, bkt);
3026      if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS)
3027        return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3028                             "Could not write EOS to filter.");
3029
3030      return NULL;
3031    }
3032
3033
3034  /* If we have a base for a delta, then we want to compute an svndiff
3035     between the provided base and the requested resource. For a simple
3036     request, then we just grab the file contents. */
3037  if (resource->info->delta_base != NULL)
3038    {
3039      dav_svn__uri_info info;
3040      svn_fs_root_t *root;
3041      svn_boolean_t is_file;
3042      svn_txdelta_stream_t *txd_stream;
3043      svn_stream_t *o_stream;
3044      svn_txdelta_window_handler_t handler;
3045      void * h_baton;
3046      diff_ctx_t dc = { 0 };
3047
3048      /* First order of business is to parse it. */
3049      serr = dav_svn__simple_parse_uri(&info, resource,
3050                                       resource->info->delta_base,
3051                                       resource->pool);
3052
3053      /* If we successfully parse the base URL, then send an svndiff. */
3054      if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM))
3055        {
3056          /* We are always accessing the base resource by ID, so open
3057             an ID root. */
3058          serr = svn_fs_revision_root(&root, resource->info->repos->fs,
3059                                      info.rev, resource->pool);
3060          if (serr != NULL)
3061            return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3062                                        "could not open a root for the base",
3063                                        resource->pool);
3064
3065          /* verify that it is a file */
3066          serr = svn_fs_is_file(&is_file, root, info.repos_path,
3067                                resource->pool);
3068          if (serr != NULL)
3069            return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3070                                        "could not determine if the base "
3071                                        "is really a file",
3072                                        resource->pool);
3073          if (!is_file)
3074            return dav_new_error(resource->pool, HTTP_BAD_REQUEST, 0,
3075                                 "the delta base does not refer to a file");
3076
3077          /* Okay. Let's open up a delta stream for the client to read. */
3078          serr = svn_fs_get_file_delta_stream(&txd_stream,
3079                                              root, info.repos_path,
3080                                              resource->info->root.root,
3081                                              resource->info->repos_path,
3082                                              resource->pool);
3083          if (serr != NULL)
3084            return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3085                                        "could not prepare to read a delta",
3086                                        resource->pool);
3087
3088          /* create a stream that svndiff data will be written to,
3089             which will copy it to the network */
3090          dc.output = output;
3091          dc.pool = resource->pool;
3092          o_stream = svn_stream_create(&dc, resource->pool);
3093          svn_stream_set_write(o_stream, write_to_filter);
3094          svn_stream_set_close(o_stream, close_filter);
3095
3096          /* get a handler/baton for writing into the output stream */
3097          svn_txdelta_to_svndiff2(&handler, &h_baton,
3098                                  o_stream, resource->info->svndiff_version,
3099                                  resource->pool);
3100
3101          /* got everything set up. read in delta windows and shove them into
3102             the handler, which pushes data into the output stream, which goes
3103             to the network. */
3104          serr = svn_txdelta_send_txstream(txd_stream, handler, h_baton,
3105                                           resource->pool);
3106          if (serr != NULL)
3107            return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3108                                        "could not deliver the txdelta stream",
3109                                        resource->pool);
3110
3111
3112          return NULL;
3113        }
3114      else
3115        {
3116          svn_error_clear(serr);
3117        }
3118    }
3119
3120  /* resource->info->delta_base is NULL, or we had an invalid base URL */
3121    {
3122      svn_stream_t *stream;
3123      char *block;
3124
3125      serr = svn_fs_file_contents(&stream,
3126                                  resource->info->root.root,
3127                                  resource->info->repos_path,
3128                                  resource->pool);
3129      if (serr != NULL)
3130        {
3131          return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3132                                      "could not prepare to read the file",
3133                                      resource->pool);
3134        }
3135
3136      /* ### one day in the future, we can create a custom bucket type
3137         ### which will read from the FS stream on demand */
3138
3139      block = apr_palloc(resource->pool, SVN__STREAM_CHUNK_SIZE);
3140      while (1) {
3141        apr_size_t bufsize = SVN__STREAM_CHUNK_SIZE;
3142
3143        /* read from the FS ... */
3144        serr = svn_stream_read(stream, block, &bufsize);
3145        if (serr != NULL)
3146          {
3147            return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3148                                        "could not read the file contents",
3149                                        resource->pool);
3150          }
3151        if (bufsize == 0)
3152          break;
3153
3154        /* build a brigade and write to the filter ... */
3155        bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
3156        bkt = apr_bucket_transient_create(block, bufsize,
3157                                          output->c->bucket_alloc);
3158        APR_BRIGADE_INSERT_TAIL(bb, bkt);
3159        if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
3160          /* ### what to do with status; and that HTTP code... */
3161          return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3162                               "Could not write data to filter.");
3163        }
3164      }
3165
3166      /* done with the file. write an EOS bucket now. */
3167      bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
3168      bkt = apr_bucket_eos_create(output->c->bucket_alloc);
3169      APR_BRIGADE_INSERT_TAIL(bb, bkt);
3170      if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
3171        /* ### what to do with status; and that HTTP code... */
3172        return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3173                             "Could not write EOS to filter.");
3174      }
3175
3176      return NULL;
3177    }
3178}
3179
3180
3181static dav_error *
3182create_collection(dav_resource *resource)
3183{
3184  svn_error_t *serr;
3185  dav_error *err;
3186
3187  if (resource->type != DAV_RESOURCE_TYPE_WORKING
3188      && resource->type != DAV_RESOURCE_TYPE_REGULAR)
3189    {
3190      return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3191                           "Collections can only be created within a working "
3192                           "or regular collection [at this time].");
3193    }
3194
3195  /* ...regular resources allowed only if autoversioning is turned on. */
3196  if (resource->type == DAV_RESOURCE_TYPE_REGULAR
3197      && ! (resource->info->repos->autoversioning))
3198    return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3199                         "MKCOL called on regular resource, but "
3200                         "autoversioning is not active.");
3201
3202  /* ### note that the parent was checked out at some point, and this
3203     ### is being preformed relative to the working rsrc for that parent */
3204
3205  /* Auto-versioning mkcol of regular resource: */
3206  if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
3207    {
3208      /* Change the VCR into a WR, in place.  This creates a txn and
3209         changes resource->info->root from a rev-root into a txn-root. */
3210      err = dav_svn__checkout(resource,
3211                              1 /* auto-checkout */,
3212                              0, 0, 0, NULL, NULL);
3213      if (err)
3214        return err;
3215    }
3216
3217  if ((serr = svn_fs_make_dir(resource->info->root.root,
3218                              resource->info->repos_path,
3219                              resource->pool)) != NULL)
3220    {
3221      /* ### need a better error */
3222      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3223                                  "Could not create the collection.",
3224                                  resource->pool);
3225    }
3226
3227  /* Auto-versioning commit of the txn. */
3228  if (resource->info->auto_checked_out)
3229    {
3230      /* This also changes the WR back into a VCR, in place. */
3231      err = dav_svn__checkin(resource, 0, NULL);
3232      if (err)
3233        return err;
3234    }
3235
3236  return NULL;
3237}
3238
3239
3240static dav_error *
3241copy_resource(const dav_resource *src,
3242              dav_resource *dst,
3243              int depth,
3244              dav_response **response)
3245{
3246  svn_error_t *serr;
3247  dav_error *err;
3248  const char *src_repos_path, *dst_repos_path;
3249
3250  /* ### source must be from a collection under baseline control. the
3251     ### baseline will (implicitly) indicate the source revision, and the
3252     ### path will be derived simply from the URL path */
3253
3254  /* ### the destination's parent must be a working collection */
3255
3256  /* ### ben goofing around: */
3257  /*  char *msg;
3258      apr_psprintf
3259      (src->pool, "Got a COPY request with src arg '%s' and dst arg '%s'",
3260      src->uri, dst->uri);
3261
3262      return dav_new_error(src->pool, HTTP_NOT_IMPLEMENTED, 0, msg);
3263  */
3264
3265  /* ### Safeguard: see issue #916, whereby we're allowing an
3266     auto-checkout of a baseline for PROPPATCHing, *without* creating
3267     a new baseline afterwards.  We need to safeguard here that nobody
3268     is calling COPY with the baseline as a Destination! */
3269  if (dst->baselined && dst->type == DAV_RESOURCE_TYPE_VERSION)
3270    return dav_new_error(src->pool, HTTP_PRECONDITION_FAILED, 0,
3271                         "Illegal: COPY Destination is a baseline.");
3272
3273  if (dst->type == DAV_RESOURCE_TYPE_REGULAR
3274      && !(dst->info->repos->autoversioning))
3275    return dav_new_error(dst->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3276                         "COPY called on regular resource, but "
3277                         "autoversioning is not active.");
3278
3279  /* Auto-versioning copy of regular resource: */
3280  if (dst->type == DAV_RESOURCE_TYPE_REGULAR)
3281    {
3282      /* Change the VCR into a WR, in place.  This creates a txn and
3283         changes dst->info->root from a rev-root into a txn-root. */
3284      err = dav_svn__checkout(dst,
3285                              1 /* auto-checkout */,
3286                              0, 0, 0, NULL, NULL);
3287      if (err)
3288        return err;
3289    }
3290
3291  serr = svn_path_get_absolute(&src_repos_path,
3292                               svn_repos_path(src->info->repos->repos,
3293                                              src->pool),
3294                               src->pool);
3295  if (!serr)
3296    serr = svn_path_get_absolute(&dst_repos_path,
3297                                 svn_repos_path(dst->info->repos->repos,
3298                                                dst->pool),
3299                                 dst->pool);
3300
3301  if (!serr)
3302    {
3303      if (strcmp(src_repos_path, dst_repos_path) != 0)
3304        return dav_svn__new_error_tag
3305          (dst->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3306           "Copy source and destination are in different repositories.",
3307           SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG);
3308      serr = svn_fs_copy(src->info->root.root,  /* root object of src rev*/
3309                         src->info->repos_path, /* relative path of src */
3310                         dst->info->root.root,  /* root object of dst txn*/
3311                         dst->info->repos_path, /* relative path of dst */
3312                         src->pool);
3313    }
3314  if (serr)
3315    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3316                                "Unable to make a filesystem copy.",
3317                                dst->pool);
3318
3319  /* Auto-versioning commit of the txn. */
3320  if (dst->info->auto_checked_out)
3321    {
3322      /* This also changes the WR back into a VCR, in place. */
3323      err = dav_svn__checkin(dst, 0, NULL);
3324      if (err)
3325        return err;
3326    }
3327
3328  return NULL;
3329}
3330
3331
3332static dav_error *
3333remove_resource(dav_resource *resource, dav_response **response)
3334{
3335  svn_error_t *serr;
3336  dav_error *err;
3337  apr_hash_t *locks;
3338
3339  /* Only activities, and working or regular resources can be deleted... */
3340  if (resource->type != DAV_RESOURCE_TYPE_WORKING
3341      && resource->type != DAV_RESOURCE_TYPE_REGULAR
3342      && resource->type != DAV_RESOURCE_TYPE_ACTIVITY)
3343    return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3344                           "DELETE called on invalid resource type.");
3345
3346  /* ...and regular resources only if autoversioning is turned on. */
3347  if (resource->type == DAV_RESOURCE_TYPE_REGULAR
3348      && ! (resource->info->repos->autoversioning))
3349    return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3350                         "DELETE called on regular resource, but "
3351                         "autoversioning is not active.");
3352
3353  /* Handle activity deletions (early exit). */
3354  if (resource->type == DAV_RESOURCE_TYPE_ACTIVITY)
3355    {
3356      return dav_svn__delete_activity(resource->info->repos,
3357                                      resource->info->root.activity_id);
3358    }
3359
3360  /* ### note that the parent was checked out at some point, and this
3361     ### is being preformed relative to the working rsrc for that parent */
3362
3363  /* NOTE: strictly speaking, we cannot determine whether the parent was
3364     ever checked out, and that this working resource is relative to that
3365     checked out parent. It is entirely possible the client checked out
3366     the target resource and just deleted it. Subversion doesn't mind, but
3367     this does imply we are not enforcing the "checkout the parent, then
3368     delete from within" semantic. */
3369
3370  /* Auto-versioning delete of regular resource: */
3371  if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
3372    {
3373      /* Change the VCR into a WR, in place.  This creates a txn and
3374         changes resource->info->root from a rev-root into a txn-root. */
3375      err = dav_svn__checkout(resource,
3376                              1 /* auto-checkout */,
3377                              0, 0, 0, NULL, NULL);
3378      if (err)
3379        return err;
3380    }
3381
3382  /* Sanity check: an svn client may have sent a custom request header
3383     containing the revision of the item it thinks it's deleting.  In
3384     this case, we enforce the svn-specific semantic that the item
3385     must be up-to-date. */
3386  if (SVN_IS_VALID_REVNUM(resource->info->version_name))
3387    {
3388      svn_revnum_t created_rev;
3389      serr = svn_fs_node_created_rev(&created_rev,
3390                                     resource->info->root.root,
3391                                     resource->info->repos_path,
3392                                     resource->pool);
3393      if (serr)
3394        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3395                                    "Could not get created rev of resource",
3396                                    resource->pool);
3397
3398      if (resource->info->version_name < created_rev)
3399        {
3400          serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL,
3401                                   "Item '%s' is out of date",
3402                                   resource->info->repos_path);
3403          return dav_svn__convert_err(serr, HTTP_CONFLICT,
3404                                      "Can't DELETE out-of-date resource",
3405                                      resource->pool);
3406        }
3407    }
3408
3409  /* Before attempting the filesystem delete, we need to push any
3410     incoming lock-tokens into the filesystem's access_t.  Normally
3411     they come in via 'If:' header, and get_resource()
3412     automatically notices them and does this work for us.  In the
3413     case of a directory deletion, however, svn clients are sending
3414     'child' lock-tokens in the DELETE request body. */
3415
3416  err = dav_svn__build_lock_hash(&locks, resource->info->r,
3417                                 resource->info->repos_path, resource->pool);
3418  if (err != NULL)
3419    return err;
3420
3421  if (apr_hash_count(locks))
3422    {
3423      err = dav_svn__push_locks(resource, locks, resource->pool);
3424      if (err != NULL)
3425        return err;
3426    }
3427
3428  if ((serr = svn_fs_delete(resource->info->root.root,
3429                            resource->info->repos_path,
3430                            resource->pool)) != NULL)
3431    {
3432      /* ### need a better error */
3433      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3434                                  "Could not delete the resource",
3435                                  resource->pool);
3436    }
3437
3438  /* Auto-versioning commit of the txn. */
3439  if (resource->info->auto_checked_out)
3440    {
3441      /* This also changes the WR back into a VCR, in place. */
3442      err = dav_svn__checkin(resource, 0, NULL);
3443      if (err)
3444        return err;
3445    }
3446
3447  return NULL;
3448}
3449
3450
3451static dav_error *
3452move_resource(dav_resource *src,
3453              dav_resource *dst,
3454              dav_response **response)
3455{
3456  svn_error_t *serr;
3457  dav_error *err;
3458
3459  /* NOTE: The svn client does not call the MOVE method yet. Strictly
3460     speaking, we do not need to implement this repository function.
3461     But we do so anyway, so non-deltaV clients can work against the
3462     repository when autoversioning is turned on.  Like the svn client,
3463     itself, we define a move to be a copy + delete within a single txn. */
3464
3465  /* Because we have no 'atomic' move, we only allow this method on
3466     two regular resources with autoversioning active.  That way we
3467     can auto-checkout a single resource and do the copy + delete
3468     within a single txn.  (If we had two working resources, which txn
3469     would we use?) */
3470  if (src->type != DAV_RESOURCE_TYPE_REGULAR
3471      || dst->type != DAV_RESOURCE_TYPE_REGULAR
3472      || !(src->info->repos->autoversioning))
3473    return dav_new_error(dst->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3474                         "MOVE only allowed on two public URIs, and "
3475                         "autoversioning must be active.");
3476
3477  /* Change the dst VCR into a WR, in place.  This creates a txn and
3478     changes dst->info->root from a rev-root into a txn-root. */
3479  err = dav_svn__checkout(dst,
3480                          1 /* auto-checkout */,
3481                          0, 0, 0, NULL, NULL);
3482  if (err)
3483    return err;
3484
3485  /* Copy the src to the dst. */
3486  serr = svn_fs_copy(src->info->root.root,  /* the root object of src rev*/
3487                     src->info->repos_path, /* the relative path of src */
3488                     dst->info->root.root,  /* the root object of dst txn*/
3489                     dst->info->repos_path, /* the relative path of dst */
3490                     src->pool);
3491  if (serr)
3492    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3493                                "Unable to make a filesystem copy.",
3494                                dst->pool);
3495
3496  /* Notice: we're deleting the src repos path from the dst's txn_root. */
3497  if ((serr = svn_fs_delete(dst->info->root.root,
3498                            src->info->repos_path,
3499                            dst->pool)) != NULL)
3500    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3501                                "Could not delete the src resource.",
3502                                dst->pool);
3503
3504  /* Commit:  this also changes the WR back into a VCR, in place. */
3505  err = dav_svn__checkin(dst, 0, NULL);
3506  if (err)
3507    return err;
3508
3509  return NULL;
3510}
3511
3512
3513typedef struct {
3514  /* the input walk parameters */
3515  const dav_walk_params *params;
3516
3517  /* reused as we walk */
3518  dav_walk_resource wres;
3519
3520  /* the current resource */
3521  dav_resource res;             /* wres.resource refers here */
3522  dav_resource_private info;    /* the info in res */
3523  svn_stringbuf_t *uri;            /* the uri within res */
3524  svn_stringbuf_t *repos_path;     /* the repos_path within res */
3525
3526} walker_ctx_t;
3527
3528
3529static dav_error *
3530do_walk(walker_ctx_t *ctx, int depth)
3531{
3532  const dav_walk_params *params = ctx->params;
3533  int isdir = ctx->res.collection;
3534  dav_error *err;
3535  svn_error_t *serr;
3536  apr_hash_index_t *hi;
3537  apr_size_t path_len;
3538  apr_size_t uri_len;
3539  apr_size_t repos_len;
3540  apr_hash_t *children;
3541
3542  /* Clear the temporary pool. */
3543  svn_pool_clear(ctx->info.pool);
3544
3545  /* The current resource is a collection (possibly here thru recursion)
3546     and this is the invocation for the collection. Alternatively, this is
3547     the first [and only] entry to do_walk() for a member resource, so
3548     this will be the invocation for the member. */
3549  err = (*params->func)(&ctx->wres,
3550                        isdir ? DAV_CALLTYPE_COLLECTION : DAV_CALLTYPE_MEMBER);
3551  if (err != NULL)
3552    return err;
3553
3554  /* if we are not to recurse, or this is a member, then we're done */
3555  if (depth == 0 || !isdir)
3556    return NULL;
3557
3558  /* ### for now, let's say that working resources have no children. of
3559     ### course, this isn't true (or "right") for working collections, but
3560     ### we don't actually need to do a walk right now. */
3561  if (params->root->type == DAV_RESOURCE_TYPE_WORKING)
3562    return NULL;
3563
3564  /* ### need to allow more walking in the future */
3565  if (params->root->type != DAV_RESOURCE_TYPE_REGULAR)
3566    {
3567      return dav_new_error(params->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3568                           "Walking the resource hierarchy can only be done "
3569                           "on 'regular' resources [at this time].");
3570    }
3571
3572  /* assert: collection resource. isdir == TRUE. repos_path != NULL. */
3573
3574  /* append "/" to the paths, in preparation for appending child names.
3575     don't add "/" if the paths are simply "/" */
3576  if (ctx->info.uri_path->data[ctx->info.uri_path->len - 1] != '/')
3577    svn_stringbuf_appendcstr(ctx->info.uri_path, "/");
3578  if (ctx->repos_path->data[ctx->repos_path->len - 1] != '/')
3579    svn_stringbuf_appendcstr(ctx->repos_path, "/");
3580
3581  /* NOTE: the URI should already have a trailing "/" */
3582
3583  /* fix up the dependent pointers */
3584  ctx->info.repos_path = ctx->repos_path->data;
3585
3586  /* all of the children exist. also initialize the collection flag. */
3587  ctx->res.exists = TRUE;
3588  ctx->res.collection = FALSE;
3589
3590  /* remember these values so we can chop back to them after each time
3591     we append a child name to the path/uri/repos */
3592  path_len = ctx->info.uri_path->len;
3593  uri_len = ctx->uri->len;
3594  repos_len = ctx->repos_path->len;
3595
3596  /* Tell our logging subsystem that we're listing a directory.
3597
3598     Note: if we cared, we could look at the 'User-Agent:' request
3599     header and distinguish an svn client ('svn ls') from a generic
3600     DAV client.  */
3601  dav_svn__operational_log(&ctx->info,
3602                           svn_log__get_dir(ctx->info.repos_path,
3603                                            ctx->info.root.rev,
3604                                            TRUE, FALSE, SVN_DIRENT_ALL,
3605                                            params->pool));
3606
3607  /* fetch this collection's children */
3608  serr = svn_fs_dir_entries(&children, ctx->info.root.root,
3609                            ctx->info.repos_path, params->pool);
3610  if (serr != NULL)
3611    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3612                                "could not fetch collection members",
3613                                params->pool);
3614
3615  /* iterate over the children in this collection */
3616  for (hi = apr_hash_first(params->pool, children); hi; hi = apr_hash_next(hi))
3617    {
3618      const void *key;
3619      apr_ssize_t klen;
3620      void *val;
3621      svn_fs_dirent_t *dirent;
3622
3623      /* fetch one of the children */
3624      apr_hash_this(hi, &key, &klen, &val);
3625      dirent = val;
3626
3627      /* authorize access to this resource, if applicable */
3628      if (params->walk_type & DAV_WALKTYPE_AUTH)
3629        {
3630          /* ### how/what to do? */
3631        }
3632
3633      /* append this child to our buffers */
3634      svn_stringbuf_appendbytes(ctx->info.uri_path, key, klen);
3635      svn_stringbuf_appendbytes(ctx->uri, key, klen);
3636      svn_stringbuf_appendbytes(ctx->repos_path, key, klen);
3637
3638      /* reset the pointers since the above may have changed them */
3639      ctx->res.uri = ctx->uri->data;
3640      ctx->info.repos_path = ctx->repos_path->data;
3641
3642      if (dirent->kind == svn_node_file)
3643        {
3644          err = (*params->func)(&ctx->wres, DAV_CALLTYPE_MEMBER);
3645          if (err != NULL)
3646            return err;
3647        }
3648      else
3649        {
3650          /* this resource is a collection */
3651          ctx->res.collection = TRUE;
3652
3653          /* append a slash to the URI (the path doesn't need it yet) */
3654          svn_stringbuf_appendcstr(ctx->uri, "/");
3655          ctx->res.uri = ctx->uri->data;
3656
3657          /* recurse on this collection */
3658          err = do_walk(ctx, depth - 1);
3659          if (err != NULL)
3660            return err;
3661
3662          /* restore the data */
3663          ctx->res.collection = FALSE;
3664        }
3665
3666      /* chop the child off the paths and uri. NOTE: no null-term. */
3667      ctx->info.uri_path->len = path_len;
3668      ctx->uri->len = uri_len;
3669      ctx->repos_path->len = repos_len;
3670    }
3671  return NULL;
3672}
3673
3674static dav_error *
3675walk(const dav_walk_params *params, int depth, dav_response **response)
3676{
3677  /* Thinking about adding support for LOCKNULL resources in this
3678     walker?  Check out the (working) code that was removed here:
3679          Author: cmpilato
3680          Date: Fri Mar 18 14:54:02 2005
3681          New Revision: 13475
3682     */
3683
3684  walker_ctx_t ctx = { 0 };
3685  dav_error *err;
3686
3687  ctx.params = params;
3688
3689  ctx.wres.walk_ctx = params->walk_ctx;
3690  ctx.wres.pool = params->pool;
3691  ctx.wres.resource = &ctx.res;
3692
3693  /* copy the resource over and adjust the "info" reference */
3694  ctx.res = *params->root;
3695  ctx.info = *ctx.res.info;
3696
3697  ctx.res.info = &ctx.info;
3698
3699  /* operate within the proper pool */
3700  ctx.res.pool = params->pool;
3701
3702  /* Don't monkey with the path from params->root. Create a new one.
3703     This path will then be extended/shortened as necessary. */
3704  ctx.info.uri_path = svn_stringbuf_dup(ctx.info.uri_path, params->pool);
3705
3706  /* prep the URI buffer */
3707  ctx.uri = svn_stringbuf_create(params->root->uri, params->pool);
3708
3709  /* same for repos_path */
3710  if (ctx.info.repos_path == NULL)
3711    ctx.repos_path = NULL;
3712  else
3713    ctx.repos_path = svn_stringbuf_create(ctx.info.repos_path, params->pool);
3714
3715  /* if we have a collection, then ensure the URI has a trailing "/" */
3716  /* ### get_resource always kills the trailing slash... */
3717  if (ctx.res.collection && ctx.uri->data[ctx.uri->len - 1] != '/') {
3718    svn_stringbuf_appendcstr(ctx.uri, "/");
3719  }
3720
3721  /* the current resource's URI is stored in the (telescoping) ctx.uri */
3722  ctx.res.uri = ctx.uri->data;
3723
3724  /* the current resource's repos_path is stored in ctx.repos_path */
3725  if (ctx.repos_path != NULL)
3726    ctx.info.repos_path = ctx.repos_path->data;
3727
3728  /* Create a pool usable by the response. */
3729  ctx.info.pool = svn_pool_create(params->pool);
3730
3731  /* ### is the root already/always open? need to verify */
3732
3733  /* always return the error, and any/all multistatus responses */
3734  err = do_walk(&ctx, depth);
3735  *response = ctx.wres.response;
3736
3737  return err;
3738}
3739
3740
3741
3742/*** Utility functions for resource management ***/
3743
3744dav_resource *
3745dav_svn__create_working_resource(dav_resource *base,
3746                                 const char *activity_id,
3747                                 const char *txn_name,
3748                                 int tweak_in_place)
3749{
3750  const char *path;
3751  dav_resource *res;
3752
3753  if (base->baselined)
3754    path = apr_psprintf(base->pool,
3755                        "/%s/wbl/%s/%ld",
3756                        base->info->repos->special_uri,
3757                        activity_id, base->info->root.rev);
3758  else
3759    path = apr_psprintf(base->pool, "/%s/wrk/%s%s",
3760                        base->info->repos->special_uri,
3761                        activity_id, base->info->repos_path);
3762  path = svn_path_uri_encode(path, base->pool);
3763
3764  if (tweak_in_place)
3765    res = base;
3766  else
3767    {
3768      res = apr_pcalloc(base->pool, sizeof(*res));
3769      res->info = apr_pcalloc(base->pool, sizeof(*res->info));
3770    }
3771
3772  res->type = DAV_RESOURCE_TYPE_WORKING;
3773  res->exists = TRUE;      /* ### not necessarily correct */
3774  res->versioned = TRUE;
3775  res->working = TRUE;
3776  res->baselined = base->baselined;
3777  /* collection = FALSE.   ### not necessarily correct */
3778
3779  res->uri = apr_pstrcat(base->pool, base->info->repos->root_path,
3780                         path, NULL);
3781  res->hooks = &dav_svn__hooks_repository;
3782  res->pool = base->pool;
3783
3784  res->info->uri_path = svn_stringbuf_create(path, base->pool);
3785  res->info->repos = base->info->repos;
3786  res->info->repos_path = base->info->repos_path;
3787  res->info->root.rev = base->info->root.rev;
3788  res->info->root.activity_id = activity_id;
3789  res->info->root.txn_name = txn_name;
3790
3791  if (tweak_in_place)
3792    return NULL;
3793  else
3794    return res;
3795}
3796
3797
3798dav_error *
3799dav_svn__working_to_regular_resource(dav_resource *resource)
3800{
3801  dav_resource_private *priv = resource->info;
3802  dav_svn_repos *repos = priv->repos;
3803  const char *path;
3804  svn_error_t *serr;
3805
3806  /* no need to change the repos object or repos_path */
3807
3808  /* set type back to REGULAR */
3809  resource->type = DAV_RESOURCE_TYPE_REGULAR;
3810
3811  /* remove the working flag */
3812  resource->working = FALSE;
3813
3814  /* Change the URL into either a baseline-collection or a public one. */
3815  if (priv->root.rev == SVN_INVALID_REVNUM)
3816    {
3817      serr = svn_fs_youngest_rev(&priv->root.rev, repos->fs, resource->pool);
3818      if (serr != NULL)
3819        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3820                                    "Could not determine youngest rev.",
3821                                    resource->pool);
3822
3823      /* create public URL */
3824      path = apr_psprintf(resource->pool, "%s", priv->repos_path);
3825    }
3826  else
3827    {
3828      /* if rev was specific, create baseline-collection URL */
3829      path = dav_svn__build_uri(repos, DAV_SVN__BUILD_URI_BC,
3830                                priv->root.rev, priv->repos_path,
3831                                0, resource->pool);
3832    }
3833  path = svn_path_uri_encode(path, resource->pool);
3834  priv->uri_path = svn_stringbuf_create(path, resource->pool);
3835
3836  /* change root.root back into a revision root. */
3837  serr = svn_fs_revision_root(&priv->root.root, repos->fs,
3838                              priv->root.rev, resource->pool);
3839  if (serr != NULL)
3840    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3841                                "Could not open revision root.",
3842                                resource->pool);
3843
3844  return NULL;
3845}
3846
3847
3848dav_error *
3849dav_svn__create_version_resource(dav_resource **version_res,
3850                                 const char *uri,
3851                                 apr_pool_t *pool)
3852{
3853  int result;
3854  dav_error *err;
3855
3856  dav_resource_combined *comb = apr_pcalloc(pool, sizeof(*comb));
3857
3858  result = parse_version_uri(comb, uri, NULL, 0);
3859  if (result != 0)
3860    return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3861                         "Could not parse version resource uri.");
3862
3863  err = prep_version(comb);
3864  if (err)
3865    return err;
3866
3867  *version_res = &comb->res;
3868  return NULL;
3869}
3870
3871
3872const dav_hooks_repository dav_svn__hooks_repository =
3873{
3874  1,                            /* special GET handling */
3875  get_resource,
3876  get_parent_resource,
3877  is_same_resource,
3878  is_parent_resource,
3879  open_stream,
3880  close_stream,
3881  write_stream,
3882  seek_stream,
3883  set_headers,
3884  deliver,
3885  create_collection,
3886  copy_resource,
3887  move_resource,
3888  remove_resource,
3889  walk,
3890  getetag_pathetic
3891};
Note: See TracBrowser for help on using the repository browser.