1 /*
2  * repos.c: mod_dav_svn repository provider functions for Subversion
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 #define APR_WANT_STRFUNC
25 #include <apr_want.h>
26 #include <apr_strings.h>
27 #include <apr_hash.h>
28 #include <apr_lib.h>
29 
30 #include <httpd.h>
31 #include <http_request.h>
32 #include <http_protocol.h>
33 #include <http_log.h>
34 #include <http_core.h>  /* for ap_construct_url */
35 #include <mod_dav.h>
36 
37 #define CORE_PRIVATE      /* To make ap_show_mpm public in 2.2 */
38 #include <http_config.h>
39 
40 #include "svn_hash.h"
41 #include "svn_types.h"
42 #include "svn_pools.h"
43 #include "svn_error.h"
44 #include "svn_time.h"
45 #include "svn_fs.h"
46 #include "svn_repos.h"
47 #include "svn_dav.h"
48 #include "svn_sorts.h"
49 #include "svn_version.h"
50 #include "svn_props.h"
51 #include "svn_ctype.h"
52 #include "svn_subst.h"
53 #include "mod_dav_svn.h"
54 #include "svn_ra.h"  /* for SVN_RA_CAPABILITY_* */
55 #include "svn_dirent_uri.h"
56 #include "private/svn_log.h"
57 #include "private/svn_fspath.h"
58 #include "private/svn_repos_private.h"
59 #include "private/svn_sorts_private.h"
60 
61 #include "dav_svn.h"
62 
63 
64 #define DEFAULT_ACTIVITY_DB "dav/activities.d"
65 
66 
67 struct dav_stream {
68   const dav_resource *res;
69 
70   /* for reading from the FS */
71   svn_stream_t *rstream;
72 
73   /* for writing to the FS. we use wstream OR the handler/baton. */
74   svn_stream_t *wstream;
75   svn_txdelta_window_handler_t delta_handler;
76   void *delta_baton;
77 };
78 
79 
80 /* Convenience structure that facilitates combined memory allocation of
81    a dav_resource and dav_resource_private pair. */
82 typedef struct dav_resource_combined {
83   dav_resource res;
84   dav_resource_private priv;
85 } dav_resource_combined;
86 
87 
88 /* Helper-wrapper around svn_fs_check_path(), which takes the same
89    arguments.  But: if we attempt to stat a path like "file1/file2",
90    then still return 'svn_node_none' to signal nonexistence, rather
91    than a full-blown filesystem error.  This allows mod_dav to throw
92    404 instead of 500. */
93 static dav_error *
fs_check_path(svn_node_kind_t * kind,svn_fs_root_t * root,const char * path,apr_pool_t * pool)94 fs_check_path(svn_node_kind_t *kind,
95               svn_fs_root_t *root,
96               const char *path,
97               apr_pool_t *pool)
98 {
99   svn_error_t *serr;
100   svn_node_kind_t my_kind;
101 
102   serr = svn_fs_check_path(&my_kind, root, path, pool);
103 
104   /* Possibly trap other fs-errors here someday -- errors which may
105      simply indicate the path's nonexistence, rather than a critical
106      problem. */
107   if (serr && serr->apr_err == SVN_ERR_FS_NOT_DIRECTORY)
108     {
109       svn_error_clear(serr);
110       *kind = svn_node_none;
111       return NULL;
112     }
113   else if (serr)
114     {
115       return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
116                                   apr_psprintf(pool, "Error checking kind of "
117                                                "path '%s' in repository",
118                                                path),
119                                   pool);
120     }
121 
122   *kind = my_kind;
123   return NULL;
124 }
125 
126 
127 static int
parse_version_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)128 parse_version_uri(dav_resource_combined *comb,
129                   const char *path,
130                   const char *label,
131                   int use_checked_in)
132 {
133   const char *slash;
134   const char *created_rev_str;
135 
136   /* format: CREATED_REV/REPOS_PATH */
137 
138   /* ### what to do with LABEL and USE_CHECKED_IN ?? */
139 
140   comb->res.type = DAV_RESOURCE_TYPE_VERSION;
141   comb->res.versioned = TRUE;
142 
143   slash = ap_strchr_c(path, '/');
144   if (slash == NULL)
145     {
146       /* http://host.name/repos/$svn/ver/0
147 
148          This URL form refers to the root path of the repository.
149       */
150       created_rev_str = apr_pstrndup(comb->res.pool, path, strlen(path));
151       comb->priv.root.rev = SVN_STR_TO_REV(created_rev_str);
152       comb->priv.repos_path = "/";
153     }
154   else if (slash == path)
155     {
156       /* the CREATED_REV was missing(?)
157 
158          ### not sure this can happen, though, because it would imply two
159          ### slashes, yet those are cleaned out within get_resource
160       */
161       return TRUE;
162     }
163   else
164     {
165       apr_size_t len = slash - path;
166 
167       created_rev_str = apr_pstrndup(comb->res.pool, path, len);
168       comb->priv.root.rev = SVN_STR_TO_REV(created_rev_str);
169       comb->priv.repos_path = slash;
170     }
171 
172   /* if the CREATED_REV parsing blew, then propagate it. */
173   if (comb->priv.root.rev == SVN_INVALID_REVNUM)
174     return TRUE;
175 
176   /* We have idempotent resource. */
177   comb->priv.idempotent = TRUE;
178 
179   return FALSE;
180 }
181 
182 
183 static int
parse_history_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)184 parse_history_uri(dav_resource_combined *comb,
185                   const char *path,
186                   const char *label,
187                   int use_checked_in)
188 {
189   /* format: ??? */
190 
191   /* ### what to do with LABEL and USE_CHECKED_IN ?? */
192 
193   comb->res.type = DAV_RESOURCE_TYPE_HISTORY;
194 
195   /* ### parse path */
196   comb->priv.repos_path = path;
197 
198   return FALSE;
199 }
200 
201 
202 static int
parse_working_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)203 parse_working_uri(dav_resource_combined *comb,
204                   const char *path,
205                   const char *label,
206                   int use_checked_in)
207 {
208   const char *slash;
209 
210   /* format: ACTIVITY_ID/REPOS_PATH */
211 
212   /* ### what to do with LABEL and USE_CHECKED_IN ?? */
213 
214   comb->res.type = DAV_RESOURCE_TYPE_WORKING;
215   comb->res.working = TRUE;
216   comb->res.versioned = TRUE;
217 
218   slash = ap_strchr_c(path, '/');
219 
220   /* This sucker starts with a slash.  That's bogus. */
221   if (slash == path)
222     return TRUE;
223 
224   if (slash == NULL)
225     {
226       /* There's no slash character in our path.  Assume it's just an
227          ACTIVITY_ID pointing to the root path.  That should be cool.
228          We'll just drop through to the normal case handling below. */
229       comb->priv.root.activity_id = apr_pstrdup(comb->res.pool, path);
230       comb->priv.repos_path = "/";
231     }
232   else
233     {
234       comb->priv.root.activity_id = apr_pstrndup(comb->res.pool, path,
235                                                  slash - path);
236       comb->priv.repos_path = slash;
237     }
238 
239   return FALSE;
240 }
241 
242 
243 static int
parse_activity_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)244 parse_activity_uri(dav_resource_combined *comb,
245                    const char *path,
246                    const char *label,
247                    int use_checked_in)
248 {
249   /* format: ACTIVITY_ID */
250 
251   /* ### what to do with LABEL and USE_CHECKED_IN ?? */
252 
253   comb->res.type = DAV_RESOURCE_TYPE_ACTIVITY;
254 
255   comb->priv.root.activity_id = path;
256 
257   return FALSE;
258 }
259 
260 
261 static int
parse_vcc_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)262 parse_vcc_uri(dav_resource_combined *comb,
263               const char *path,
264               const char *label,
265               int use_checked_in)
266 {
267   /* format: "default" (a singleton) */
268 
269   if (strcmp(path, DAV_SVN__DEFAULT_VCC_NAME) != 0)
270     return TRUE;
271 
272   if (label == NULL && !use_checked_in)
273     {
274       /* Version Controlled Configuration (baseline selector) */
275 
276       /* ### mod_dav has a proper model for these. technically, they are
277          ### version-controlled resources (REGULAR), but that just monkeys
278          ### up a lot of stuff for us. use a PRIVATE for now. */
279 
280       comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;   /* _REGULAR */
281       comb->priv.restype = DAV_SVN_RESTYPE_VCC;
282 
283       comb->res.exists = TRUE;
284       comb->res.versioned = TRUE;
285       comb->res.baselined = TRUE;
286 
287       /* NOTE: comb->priv.repos_path == NULL */
288     }
289   else
290     {
291       /* a specific Version Resource; in this case, a Baseline */
292 
293       svn_revnum_t revnum;
294 
295       if (label != NULL)
296         {
297           revnum = SVN_STR_TO_REV(label); /* assume slash terminates */
298           if (!SVN_IS_VALID_REVNUM(revnum))
299             return TRUE;        /* ### be nice to get better feedback */
300         }
301       else /* use_checked_in */
302         {
303           /* use the DAV:checked-in value of the VCC. this is always the
304              "latest" (or "youngest") revision. */
305 
306           /* signal prep_version to look it up */
307           revnum = SVN_INVALID_REVNUM;
308         }
309 
310       comb->res.type = DAV_RESOURCE_TYPE_VERSION;
311 
312       /* exists? need to wait for now */
313       comb->res.versioned = TRUE;
314       comb->res.baselined = TRUE;
315 
316       /* which baseline (revision tree) to access */
317       comb->priv.root.rev = revnum;
318 
319       /* NOTE: comb->priv.repos_path == NULL */
320       /* NOTE: comb->priv.created_rev == SVN_INVALID_REVNUM */
321     }
322 
323   return FALSE;
324 }
325 
326 
327 static int
parse_me_resource_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)328 parse_me_resource_uri(dav_resource_combined *comb,
329                       const char *path,
330                       const char *label,
331                       int use_checked_in)
332 {
333   /* In HTTP protocol v2, this uri represents the repository itself,
334      and is the place where custom REPORTs get sent to.  (It replaces
335      the older vcc uri form.)  It has no trailing components.  */
336 
337   if (path[0] != '\0')
338     return TRUE;
339 
340   comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
341   comb->priv.restype = DAV_SVN_RESTYPE_ME;
342 
343   /* We're keeping these the same as the VCC resource, to make things
344      smoother for our report requests. */
345   comb->res.exists = TRUE;
346   comb->res.versioned = TRUE;
347   comb->res.baselined = TRUE;
348   /* NOTE: comb->priv.repos_path == NULL */
349 
350   return FALSE;
351 }
352 
353 
354 static int
parse_baseline_coll_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)355 parse_baseline_coll_uri(dav_resource_combined *comb,
356                         const char *path,
357                         const char *label,
358                         int use_checked_in)
359 {
360   const char *slash;
361   svn_revnum_t revnum;
362 
363   /* format: REVISION/REPOS_PATH */
364 
365   /* ### what to do with LABEL and USE_CHECKED_IN ?? */
366 
367   slash = ap_strchr_c(path, '/');
368   if (slash == NULL)
369     slash = "/";        /* they are referring to the root of the BC */
370   else if (slash == path)
371     return TRUE;        /* the REVISION was missing(?)
372                            ### not sure this can happen, though, because
373                            ### it would imply two slashes, yet those are
374                            ### cleaned out within get_resource */
375 
376   revnum = SVN_STR_TO_REV(path);  /* assume slash terminates conversion */
377   if (!SVN_IS_VALID_REVNUM(revnum))
378     return TRUE;        /* ### be nice to get better feedback */
379 
380   /* ### mod_dav doesn't have a proper model for these. they are standard
381      ### VCRs, but we need some additional semantics attached to them.
382      ### need to figure out a way to label them as special. */
383 
384   comb->res.type = DAV_RESOURCE_TYPE_REGULAR;
385   comb->res.versioned = TRUE;
386   comb->priv.root.rev = revnum;
387   comb->priv.repos_path = slash;
388 
389   return FALSE;
390 }
391 
392 
393 static int
parse_baseline_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)394 parse_baseline_uri(dav_resource_combined *comb,
395                    const char *path,
396                    const char *label,
397                    int use_checked_in)
398 {
399   svn_revnum_t revnum;
400 
401   /* format: REVISION */
402 
403   /* ### what to do with LABEL and USE_CHECKED_IN ?? */
404 
405   revnum = SVN_STR_TO_REV(path);
406   if (!SVN_IS_VALID_REVNUM(revnum))
407     return TRUE;        /* ### be nice to get better feedback */
408 
409   /* create a Baseline resource (a special Version Resource) */
410 
411   comb->res.type = DAV_RESOURCE_TYPE_VERSION;
412 
413   /* exists? need to wait for now */
414   comb->res.versioned = TRUE;
415   comb->res.baselined = TRUE;
416 
417   /* which baseline (revision tree) to access */
418   comb->priv.root.rev = revnum;
419 
420   /* NOTE: comb->priv.repos_path == NULL */
421   /* NOTE: comb->priv.created_rev == SVN_INVALID_REVNUM */
422 
423   return FALSE;
424 }
425 
426 
427 static int
parse_revstub_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)428 parse_revstub_uri(dav_resource_combined *comb,
429                   const char *path,
430                   const char *label,
431                   int use_checked_in)
432 {
433   /* format: !svn/rev/REVISION
434 
435      In HTTP protocol v2, this represents a specific revision in the
436      repository.  Clients perform PROPFIND and PROPPATCH against it to
437      read and write revprops.  (This uri replaces baseline (bln) and
438      working baseline (wbl) forms.)
439    */
440 
441   svn_revnum_t revnum = SVN_STR_TO_REV(path);
442   if (!SVN_IS_VALID_REVNUM(revnum))
443     return TRUE;  /* fail */
444 
445   comb->res.type = DAV_RESOURCE_TYPE_VERSION;
446   comb->res.versioned = TRUE;
447   comb->res.baselined = TRUE;
448   /* exists? need to wait for now */
449 
450   /* which baseline (revision tree) to access */
451   comb->priv.root.rev = revnum;
452 
453   /* all resource parameters are fixed in URI. */
454   comb->priv.idempotent = TRUE;
455 
456   /* NOTE: comb->priv.repos_path == NULL */
457   /* NOTE: comb->priv.created_rev == SVN_INVALID_REVNUM */
458 
459   return FALSE;
460 }
461 
462 
463 static int
parse_revroot_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)464 parse_revroot_uri(dav_resource_combined *comb,
465                   const char *path,
466                   const char *label,
467                   int use_checked_in)
468 {
469   /* format: !svn/rvr/REVISION/[PATH]
470 
471      In HTTP protocol v2, this represents a path within a specific
472      revision.  Clients perform PROPFIND and GET against it to read
473      versioned file/dir properties and file contents.  (This uri
474      replaces baseline collection (bc) forms.)
475    */
476 
477   /* Right now, we treat 'rvr' URIs exactly the same as 'bc' ones.
478      Same expected format, same utility, etc.  */
479   return parse_baseline_coll_uri(comb, path, label, use_checked_in);
480 }
481 
482 
483 static int
parse_txnstub_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)484 parse_txnstub_uri(dav_resource_combined *comb,
485                   const char *path,
486                   const char *label,
487                   int use_checked_in)
488 {
489   /* format: !svn/txn/TXN_NAME
490 
491      In HTTP protocol v2, this represents a specific uncommitted
492      transaction.  Clients perform PROPFIND and PROPPATCH against it
493      to read and write txnprops during a commit.  They can also issue
494      a DELETE against it to abort the txn.
495    */
496 
497   if (path == NULL)
498     return TRUE;  /* fail, we need a txn_name. */
499 
500   comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
501   comb->priv.restype = DAV_SVN_RESTYPE_TXN_COLLECTION;
502   comb->priv.root.txn_name = apr_pstrdup(comb->res.pool, path);
503 
504   return FALSE;
505 }
506 
507 static int
parse_vtxnstub_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)508 parse_vtxnstub_uri(dav_resource_combined *comb,
509                    const char *path,
510                    const char *label,
511                    int use_checked_in)
512 {
513   /* format: !svn/vtxn/TXN_NAME */
514 
515   if (parse_txnstub_uri(comb, path, label, use_checked_in))
516     return TRUE;
517 
518   if (!comb->priv.root.txn_name)
519     return TRUE;
520 
521   comb->priv.root.vtxn_name = comb->priv.root.txn_name;
522   comb->priv.root.txn_name = dav_svn__get_txn(comb->priv.repos,
523                                               comb->priv.root.vtxn_name);
524 
525   return FALSE;
526 }
527 
528 
529 static int
parse_txnroot_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)530 parse_txnroot_uri(dav_resource_combined *comb,
531                   const char *path,
532                   const char *label,
533                   int use_checked_in)
534 {
535   /* format: !svn/txr/TXN_NAME/[PATH]
536 
537      In HTTP protocol v2, this represents a path within a specific
538      uncommitted transaction.  Clients perform PUT, COPY, DELETE, MOVE
539      against it to modify the path.
540    */
541   const char *slash;
542 
543   /* Note that we're calling this a WORKING resource, rather than
544      PRIVATE, so that we can let prep_working() do the same work for
545      us that it does on DeltaV 'working resources'.  */
546   comb->res.type = DAV_RESOURCE_TYPE_WORKING;
547 
548   /* ...but setting this restype can let parse_working() know whether
549      this is a !svn/wrk/ (DeltaV) or a !svn/txr (protocol v2) */
550   comb->priv.restype = DAV_SVN_RESTYPE_TXNROOT_COLLECTION;
551   comb->res.working = TRUE;
552   comb->res.versioned = TRUE;
553 
554   slash = ap_strchr_c(path, '/');
555 
556   /* This sucker starts with a slash.  That's bogus. */
557   if (slash == path)
558     return TRUE;
559 
560   if (slash == NULL)
561     {
562       /* There's no slash character in our path.  Assume it's just an
563          TXN_NAME pointing to the root path.  That should be cool.
564          We'll just drop through to the normal case handling below. */
565       comb->priv.root.txn_name = apr_pstrdup(comb->res.pool, path);
566       comb->priv.repos_path = "/";
567     }
568   else
569     {
570       comb->priv.root.txn_name = apr_pstrndup(comb->res.pool, path,
571                                               slash - path);
572       comb->priv.repos_path = slash;
573     }
574 
575   return FALSE;
576 }
577 
578 static int
parse_vtxnroot_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)579 parse_vtxnroot_uri(dav_resource_combined *comb,
580                    const char *path,
581                    const char *label,
582                    int use_checked_in)
583 {
584   /* format: !svn/vtxr/TXN_NAME/[PATH] */
585 
586   if (parse_txnroot_uri(comb, path, label, use_checked_in))
587     return TRUE;
588 
589   if (!comb->priv.root.txn_name)
590     return TRUE;
591 
592   comb->priv.root.vtxn_name = comb->priv.root.txn_name;
593   comb->priv.root.txn_name = dav_svn__get_txn(comb->priv.repos,
594                                               comb->priv.root.vtxn_name);
595 
596   return FALSE;
597 }
598 
599 
600 static int
parse_wrk_baseline_uri(dav_resource_combined * comb,const char * path,const char * label,int use_checked_in)601 parse_wrk_baseline_uri(dav_resource_combined *comb,
602                        const char *path,
603                        const char *label,
604                        int use_checked_in)
605 {
606   const char *slash;
607 
608   /* format: ACTIVITY_ID/REVISION */
609 
610   /* ### what to do with LABEL and USE_CHECKED_IN ?? */
611 
612   comb->res.type = DAV_RESOURCE_TYPE_WORKING;
613   comb->res.working = TRUE;
614   comb->res.versioned = TRUE;
615   comb->res.baselined = TRUE;
616 
617   if ((slash = ap_strchr_c(path, '/')) == NULL
618       || slash == path
619       || slash[1] == '\0')
620     return TRUE;
621 
622   comb->priv.root.activity_id = apr_pstrndup(comb->res.pool, path,
623                                              slash - path);
624   comb->priv.root.rev = SVN_STR_TO_REV(slash + 1);
625 
626   /* NOTE: comb->priv.repos_path == NULL */
627 
628   return FALSE;
629 }
630 
631 
632 static const struct special_defn
633 {
634   const char *name;
635 
636   /*
637    * COMB is the resource that we are constructing. Any elements that
638    * can be determined from the PATH may be set in COMB. However, further
639    * operations are not allowed (we don't want anything besides a parse
640    * error to occur).
641    *
642    * At a minimum, the parse function must set COMB->res.type and
643    * COMB->priv.repos_path.
644    *
645    * PATH does not contain a leading slash. Given "/root/$svn/xxx/the/path"
646    * as the request URI, the PATH variable will be "the/path"
647    */
648   int (*parse)(dav_resource_combined *comb, const char *path,
649                const char *label, int use_checked_in);
650 
651   /* The number of subcompenents after the !svn/xxx/... before we
652      reach the actual path within the repository. */
653   int numcomponents;
654 
655   /* Boolean:  are the subcomponents followed by a repos path? */
656   int has_repos_path;
657 
658   /* The private resource type for the /$svn/xxx/ collection. */
659   enum dav_svn_private_restype restype;
660 
661 } special_subdirs[] =
662 {
663   /* Our original delta-V-ish protocol uses all these: */
664   { "ver", parse_version_uri, 1, TRUE, DAV_SVN_RESTYPE_VER_COLLECTION },
665   { "his", parse_history_uri, 0, FALSE, DAV_SVN_RESTYPE_HIS_COLLECTION },
666   { "wrk", parse_working_uri, 1, TRUE,  DAV_SVN_RESTYPE_WRK_COLLECTION },
667   { "act", parse_activity_uri, 1, FALSE, DAV_SVN_RESTYPE_ACT_COLLECTION },
668   { "vcc", parse_vcc_uri, 1, FALSE, DAV_SVN_RESTYPE_VCC_COLLECTION },
669   { "bc", parse_baseline_coll_uri, 1, TRUE, DAV_SVN_RESTYPE_BC_COLLECTION },
670   { "bln", parse_baseline_uri, 1, FALSE, DAV_SVN_RESTYPE_BLN_COLLECTION },
671   { "wbl", parse_wrk_baseline_uri, 2, FALSE, DAV_SVN_RESTYPE_WBL_COLLECTION },
672 
673   /* The new v2 protocol uses these new 'stub' uris: */
674   { "me",  parse_me_resource_uri, 0, FALSE, DAV_SVN_RESTYPE_ME },
675   { "rev", parse_revstub_uri, 1, FALSE, DAV_SVN_RESTYPE_REV_COLLECTION },
676   { "rvr", parse_revroot_uri, 1, TRUE, DAV_SVN_RESTYPE_REVROOT_COLLECTION },
677   { "txn", parse_txnstub_uri, 1, FALSE, DAV_SVN_RESTYPE_TXN_COLLECTION},
678   { "txr", parse_txnroot_uri, 1, TRUE, DAV_SVN_RESTYPE_TXNROOT_COLLECTION},
679   { "vtxn", parse_vtxnstub_uri, 1, FALSE, DAV_SVN_RESTYPE_TXN_COLLECTION},
680   { "vtxr", parse_vtxnroot_uri, 1, TRUE, DAV_SVN_RESTYPE_TXNROOT_COLLECTION},
681 
682   { NULL } /* sentinel */
683 };
684 
685 
686 /*
687  * parse_uri: parse the provided URI into its various bits
688  *
689  * URI will contain a path relative to our configured root URI. It should
690  * not have a leading "/". The root is identified by "".
691  *
692  * On output: *COMB will contain all of the information parsed out of
693  * the URI -- the resource type, activity ID, path, etc.
694  *
695  * Note: this function will only parse the URI. Validation of the pieces,
696  * opening data stores, etc, are not part of this function.
697  *
698  * TRUE is returned if a parsing error occurred. FALSE for success.
699  */
700 static int
parse_uri(dav_resource_combined * comb,const char * uri,const char * label,int use_checked_in)701 parse_uri(dav_resource_combined *comb,
702           const char *uri,
703           const char *label,
704           int use_checked_in)
705 {
706   const char *special_uri = comb->priv.repos->special_uri;
707   apr_size_t len1;
708   apr_size_t len2;
709   char ch;
710 
711   len1 = strlen(uri);
712   len2 = strlen(special_uri);
713   if (len1 > len2
714       && ((ch = uri[len2]) == '/' || ch == '\0')
715       && memcmp(uri, special_uri, len2) == 0)
716     {
717       comb->priv.is_public_uri = FALSE;
718 
719       if (ch == '\0')
720         {
721           /* URI was "/root/!svn". It exists, but has restricted usage. */
722           comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
723           comb->priv.restype = DAV_SVN_RESTYPE_ROOT_COLLECTION;
724         }
725       else
726         {
727           const struct special_defn *defn;
728 
729           /* skip past the "!svn/" prefix */
730           uri += len2 + 1;
731           len1 -= len2 + 1;
732 
733           for (defn = special_subdirs ; defn->name != NULL; ++defn)
734             {
735               apr_size_t len3 = strlen(defn->name);
736 
737               if (len1 >= len3 && memcmp(uri, defn->name, len3) == 0)
738                 {
739                   /* If we find a slash after our special subdir, or
740                      if we don't and this subdir isn't *supposed* to have
741                      anything following it (such as the !svn/me
742                      resource), hand off the custom parser for this
743                      subdir type. */
744                   if (uri[len3] == '/')
745                     {
746                       if ((*defn->parse)(comb, uri + len3 + 1, label,
747                                          use_checked_in))
748                         return TRUE;
749                     }
750                   else if (uri[len3] == '\0')
751                     {
752                       if ((defn->numcomponents == 0)
753                           && (! defn->has_repos_path))
754                         {
755                           if ((*defn->parse)(comb, "", label, use_checked_in))
756                             return TRUE;
757                         }
758                       else
759                         {
760                           /* URI was "/root/!svn/XXX". The location
761                              exists, but has restricted usage. */
762                           comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
763 
764                           /* Store the resource type so that we can
765                              PROPFIND on this collection. */
766                           comb->priv.restype = defn->restype;
767                         }
768                     }
769                   else
770                     {
771                       /* e.g. "/root/!svn/activity" (we just know "act") */
772                       return TRUE;
773                     }
774 
775                   break;
776                 }
777             }
778 
779           /* if completed the loop, then it is an unrecognized subdir */
780           if (defn->name == NULL)
781             return TRUE;
782         }
783     }
784   else
785     {
786       /* Anything under the root, but not under "!svn". These are all
787          version-controlled resources. */
788       comb->res.type = DAV_RESOURCE_TYPE_REGULAR;
789       comb->res.versioned = TRUE;
790 
791       /* The location of these resources corresponds directly to the URI,
792          and we keep the leading "/". */
793       comb->priv.repos_path = comb->priv.uri_path->data;
794 
795       comb->priv.is_public_uri = TRUE;
796     }
797 
798   return FALSE;
799 }
800 
801 
802 static dav_error *
prep_regular(dav_resource_combined * comb)803 prep_regular(dav_resource_combined *comb)
804 {
805   apr_pool_t *pool = comb->res.pool;
806   dav_svn_repos *repos = comb->priv.repos;
807   svn_error_t *serr;
808   dav_error *derr;
809   svn_node_kind_t kind;
810 
811   /* A REGULAR resource might have a specific revision already (e.g. if it
812      is part of a baseline collection). However, if it doesn't, then we
813      will assume that we need the youngest revision.
814      ### other cases besides a BC? */
815   if (comb->priv.root.rev == SVN_INVALID_REVNUM)
816     {
817       serr = dav_svn__get_youngest_rev(&comb->priv.root.rev, repos, pool);
818       if (serr != NULL)
819         {
820           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
821                                       "Could not determine the proper "
822                                       "revision to access",
823                                       pool);
824         }
825     }
826   else
827     {
828       /* Did we have a query for this REGULAR resource? */
829       if (comb->priv.r->parsed_uri.query)
830         {
831           /* If yes, it's 'idempotent' only if peg revision is specified. */
832           comb->priv.idempotent = comb->priv.pegged;
833         }
834       else
835         {
836           /* Otherwise, we have the specific revision in URI, so the resource
837              is 'idempotent'. */
838           comb->priv.idempotent = TRUE;
839         }
840     }
841 
842   /* get the root of the tree */
843   serr = svn_fs_revision_root(&comb->priv.root.root, repos->fs,
844                               comb->priv.root.rev, pool);
845   if (serr != NULL)
846     {
847       return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
848                                   "Could not open the root of the "
849                                   "repository",
850                                   pool);
851     }
852 
853   derr = fs_check_path(&kind, comb->priv.root.root,
854                        comb->priv.repos_path, pool);
855   if (derr != NULL)
856     return derr;
857 
858   comb->res.exists = (kind != svn_node_none);
859   comb->res.collection = (kind == svn_node_dir);
860 
861   /* HACK:  dav_get_resource_state() is making shortcut assumptions
862      about how to distinguish a null resource from a lock-null
863      resource.  This is the only way to get around that problem.
864      Without it, it won't ever detect lock-nulls, and thus 'svn unlock
865      nonexistentURL' will always return 404's. */
866   if (! comb->res.exists)
867     comb->priv.r->path_info = (char *) "";
868 
869   return NULL;
870 }
871 
872 
873 static dav_error *
prep_version(dav_resource_combined * comb)874 prep_version(dav_resource_combined *comb)
875 {
876   svn_error_t *serr;
877   apr_pool_t *pool = comb->res.pool;
878 
879   /* we are accessing the Version Resource by REV/PATH */
880 
881   /* ### assert: .baselined = TRUE */
882 
883   /* if we don't have a revision, then assume the youngest */
884   if (!SVN_IS_VALID_REVNUM(comb->priv.root.rev))
885     {
886       serr = dav_svn__get_youngest_rev(&comb->priv.root.rev,
887                                        comb->priv.repos,
888                                        pool);
889       if (serr != NULL)
890         {
891           /* ### might not be a baseline */
892 
893           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
894                                       "Could not fetch 'youngest' revision "
895                                       "to enable accessing the latest "
896                                       "baseline resource.",
897                                       pool);
898         }
899     }
900 
901   /* ### baselines have no repos_path, and we don't need to open
902      ### a root (yet). we just needed to ensure that we have the proper
903      ### revision number. */
904 
905   if (!comb->priv.root.root)
906     {
907       serr = svn_fs_revision_root(&comb->priv.root.root,
908                                   comb->priv.repos->fs,
909                                   comb->priv.root.rev,
910                                   pool);
911       if (serr != NULL)
912         {
913           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
914                                       "Could not open a revision root.",
915                                       pool);
916         }
917     }
918 
919   /* ### we should probably check that the revision is valid */
920   comb->res.exists = TRUE;
921 
922   /* Set up the proper URI. Most likely, we arrived here via a VCC,
923      so the URI will be incorrect. Set the canonical form. */
924   /* ### assuming a baseline */
925   comb->res.uri = dav_svn__build_uri(comb->priv.repos,
926                                      DAV_SVN__BUILD_URI_BASELINE,
927                                      comb->priv.root.rev, NULL,
928                                      FALSE /* add_href */,
929                                      pool);
930 
931   return NULL;
932 }
933 
934 
935 static dav_error *
prep_history(dav_resource_combined * comb)936 prep_history(dav_resource_combined *comb)
937 {
938   return NULL;
939 }
940 
941 
942 static dav_error *
prep_working(dav_resource_combined * comb)943 prep_working(dav_resource_combined *comb)
944 {
945   apr_pool_t *pool = comb->res.pool;
946   svn_error_t *serr;
947   dav_error *derr;
948   svn_node_kind_t kind;
949   const char *txn_name = comb->priv.root.txn_name;
950 
951   /* A txnroot object will already have the txn_name filled in, but a
952      DeltaV 'working resource' will only have the activity_id at this
953      point. */
954   if (txn_name == NULL)
955     {
956       if (!comb->priv.root.activity_id)
957         return dav_svn__new_error(comb->res.pool, HTTP_BAD_REQUEST, 0, 0,
958                                   "The request did not specify an activity ID");
959 
960       txn_name = dav_svn__get_txn(comb->priv.repos,
961                                   comb->priv.root.activity_id);
962       if (txn_name == NULL)
963         {
964           return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0, 0,
965                                     "An unknown activity was specified in the "
966                                     "URL. This is generally caused by a "
967                                     "problem in the client software.");
968         }
969       comb->priv.root.txn_name = txn_name;
970     }
971 
972   /* get the FS transaction, given its name */
973   serr = svn_fs_open_txn(&comb->priv.root.txn, comb->priv.repos->fs, txn_name,
974                          pool);
975   if (serr != NULL)
976     {
977       if (serr->apr_err == SVN_ERR_FS_NO_SUCH_TRANSACTION)
978         {
979           svn_error_clear(serr);
980           return dav_svn__new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
981                                     "An activity was specified and found, but "
982                                     "the corresponding SVN FS transaction was "
983                                     "not found.");
984         }
985       return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
986                                   "Could not open the SVN FS transaction "
987                                   "corresponding to the specified activity.",
988                                   pool);
989     }
990 
991   if (comb->res.baselined)
992     {
993       /* a Working Baseline */
994 
995       /* if the transaction exists, then the working resource exists */
996       comb->res.exists = TRUE;
997 
998       return NULL;
999     }
1000 
1001   /* Set the txn author if not previously set.  Protect against multi-author
1002    * commits by verifying authenticated user associated with the current
1003    * request is the same as the txn author.
1004    * Note that anonymous requests are being excluded as being a change
1005    * in author, because the commit may touch areas of the repository
1006    * that are anonymous writeable as well as areas that are not.
1007    */
1008   if (comb->priv.repos->username)
1009     {
1010       svn_string_t *current_author;
1011       svn_string_t request_author;
1012 
1013       serr = svn_fs_txn_prop(&current_author, comb->priv.root.txn,
1014                              SVN_PROP_REVISION_AUTHOR, pool);
1015       if (serr != NULL)
1016         {
1017           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1018                    "Failed to retrieve author of the SVN FS transaction "
1019                    "corresponding to the specified activity.",
1020                    pool);
1021         }
1022 
1023       request_author.data = comb->priv.repos->username;
1024       request_author.len = strlen(request_author.data);
1025       if (!current_author)
1026         {
1027           serr = svn_fs_change_txn_prop(comb->priv.root.txn,
1028                                         SVN_PROP_REVISION_AUTHOR,
1029                                         &request_author, pool);
1030           if (serr != NULL)
1031             {
1032               return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1033                        "Failed to set the author of the SVN FS transaction "
1034                        "corresponding to the specified activity.",
1035                        pool);
1036             }
1037         }
1038       else if (!svn_string_compare(current_author, &request_author))
1039         {
1040           return dav_svn__new_error(pool, HTTP_NOT_IMPLEMENTED, 0, 0,
1041                                     "Multi-author commits not supported.");
1042         }
1043     }
1044 
1045   /* get the root of the tree */
1046   serr = svn_fs_txn_root(&comb->priv.root.root, comb->priv.root.txn, pool);
1047   if (serr != NULL)
1048     {
1049       return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1050                                   "Could not open the (transaction) root of "
1051                                   "the repository",
1052                                   pool);
1053     }
1054 
1055   derr = fs_check_path(&kind, comb->priv.root.root,
1056                        comb->priv.repos_path, pool);
1057   if (derr != NULL)
1058     return derr;
1059 
1060   comb->res.exists = (kind != svn_node_none);
1061   comb->res.collection = (kind == svn_node_dir);
1062 
1063   if (comb->res.exists
1064       && comb->priv.r->method_number == M_MKCOL
1065       && comb->priv.repos->is_svn_client)
1066     {
1067       /* mod_dav will now continue returning a generic HTTP_METHOD_NOT_ALLOWED
1068          error, which doesn't produce nice output on SVN, nor gives any details
1069          on why the operation failed.
1070 
1071          Let's error out a bit earlier and produce an error message that is
1072          easier to understand for both clients and users. */
1073 
1074       /* It would be nice if we could error out a bit later (see issue #2295),
1075          like in create_collection(), but mod_dav outsmarts us by just
1076          returning the error when the node exists. */
1077 
1078       return dav_svn__convert_err(
1079                   svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1080                                     "Path already exists, path '%s'",
1081                                     comb->priv.repos_path),
1082                   HTTP_METHOD_NOT_ALLOWED, NULL, pool);
1083     }
1084 
1085   return NULL;
1086 }
1087 
1088 
1089 static dav_error *
prep_activity(dav_resource_combined * comb)1090 prep_activity(dav_resource_combined *comb)
1091 {
1092   const char *txn_name;
1093 
1094   if (!comb->priv.root.activity_id)
1095     return dav_svn__new_error(comb->res.pool, HTTP_BAD_REQUEST, 0, 0,
1096                               "The request did not specify an activity ID");
1097 
1098   txn_name = dav_svn__get_txn(comb->priv.repos, comb->priv.root.activity_id);
1099 
1100   comb->priv.root.txn_name = txn_name;
1101   comb->res.exists = txn_name != NULL;
1102 
1103   return NULL;
1104 }
1105 
1106 
1107 static dav_error *
prep_private(dav_resource_combined * comb)1108 prep_private(dav_resource_combined *comb)
1109 {
1110   svn_error_t *serr;
1111   apr_pool_t *pool = comb->res.pool;
1112 
1113   if (comb->priv.restype == DAV_SVN_RESTYPE_VCC)
1114     {
1115       /* ### what to do */
1116     }
1117   else if (comb->priv.restype == DAV_SVN_RESTYPE_TXN_COLLECTION)
1118     {
1119       /* Open the named transaction. */
1120 
1121       if (comb->priv.root.txn_name == NULL)
1122         return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0, 0,
1123                                   "An unknown txn name was specified in the "
1124                                   "URL.");
1125 
1126       serr = svn_fs_open_txn(&comb->priv.root.txn,
1127                              comb->priv.repos->fs,
1128                              comb->priv.root.txn_name, pool);
1129       if (serr != NULL)
1130         {
1131           if (serr->apr_err == SVN_ERR_FS_NO_SUCH_TRANSACTION)
1132             {
1133               svn_error_clear(serr);
1134               comb->res.exists = FALSE;
1135               return dav_svn__new_error(pool, HTTP_NOT_FOUND, 0, 0,
1136                                         "Named transaction doesn't exist.");
1137             }
1138           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1139                                       "Could not open specified transaction.",
1140                                       pool);
1141         }
1142       comb->res.exists = TRUE;
1143     }
1144 
1145   return NULL;
1146 }
1147 
1148 
1149 static const struct res_type_handler
1150 {
1151   dav_resource_type type;
1152   dav_error * (*prep)(dav_resource_combined *comb);
1153 
1154 } res_type_handlers[] =
1155 {
1156   /* skip UNKNOWN */
1157   { DAV_RESOURCE_TYPE_REGULAR, prep_regular },
1158   { DAV_RESOURCE_TYPE_VERSION, prep_version },
1159   { DAV_RESOURCE_TYPE_HISTORY, prep_history },
1160   { DAV_RESOURCE_TYPE_WORKING, prep_working },
1161   /* skip WORKSPACE */
1162   { DAV_RESOURCE_TYPE_ACTIVITY, prep_activity },
1163   { DAV_RESOURCE_TYPE_PRIVATE, prep_private },
1164 
1165   { 0, NULL }   /* sentinel */
1166 };
1167 
1168 
1169 /*
1170 ** ### docco...
1171 **
1172 ** Set .exists and .collection
1173 ** open other, internal bits...
1174 */
1175 static dav_error *
prep_resource(dav_resource_combined * comb)1176 prep_resource(dav_resource_combined *comb)
1177 {
1178   const struct res_type_handler *scan;
1179 
1180   for (scan = res_type_handlers; scan->prep != NULL; ++scan)
1181     {
1182       if (comb->res.type == scan->type)
1183         return (*scan->prep)(comb);
1184     }
1185 
1186   return dav_svn__new_error(comb->res.pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
1187                             "DESIGN FAILURE: unknown resource type");
1188 }
1189 
1190 
1191 static dav_resource *
create_private_resource(const dav_resource * base,enum dav_svn_private_restype restype)1192 create_private_resource(const dav_resource *base,
1193                         enum dav_svn_private_restype restype)
1194 {
1195   dav_resource_combined *comb;
1196   svn_stringbuf_t *path;
1197   const struct special_defn *defn;
1198 
1199   for (defn = special_subdirs; defn->name != NULL; ++defn)
1200     if (defn->restype == restype)
1201       break;
1202   /* assert: defn->name != NULL */
1203 
1204   path = svn_stringbuf_createf(base->pool, "/%s/%s",
1205                             base->info->repos->special_uri, defn->name);
1206 
1207   comb = apr_pcalloc(base->pool, sizeof(*comb));
1208 
1209   /* ### can/should we leverage prep_resource */
1210 
1211   comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
1212 
1213   comb->res.exists = TRUE;
1214   comb->res.collection = TRUE;                  /* ### always true? */
1215   /* versioned = baselined = working = FALSE */
1216 
1217   if (base->info->repos->root_path[1])
1218     comb->res.uri = apr_pstrcat(base->pool, base->info->repos->root_path,
1219                                 path->data, SVN_VA_NULL);
1220   else
1221     comb->res.uri = path->data;
1222   comb->res.info = &comb->priv;
1223   comb->res.hooks = &dav_svn__hooks_repository;
1224   comb->res.pool = base->pool;
1225 
1226   comb->priv.uri_path = path;
1227   comb->priv.repos = base->info->repos;
1228   comb->priv.root.rev = SVN_INVALID_REVNUM;
1229   return &comb->res;
1230 }
1231 
log_warning_req(void * baton,svn_error_t * err)1232 static void log_warning_req(void *baton, svn_error_t *err)
1233 {
1234   request_rec *r = baton;
1235   const char *continuation = "";
1236 
1237   /* Not showing file/line so no point in tracing */
1238   err = svn_error_purge_tracing(err);
1239   while (err)
1240     {
1241       ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, "%s%s",
1242                     continuation, err->message);
1243       continuation = "-";
1244       err = err->child;
1245     }
1246 }
1247 
log_warning_conn(void * baton,svn_error_t * err)1248 static void log_warning_conn(void *baton, svn_error_t *err)
1249 {
1250   conn_rec *c = baton;
1251   const char *continuation = "";
1252 
1253   /* Not showing file/line so no point in tracing */
1254   err = svn_error_purge_tracing(err);
1255   while (err)
1256     {
1257       ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c, "%s%s",
1258                     continuation, err->message);
1259       continuation = "-";
1260       err = err->child;
1261     }
1262 }
1263 
1264 
1265 AP_MODULE_DECLARE(dav_error *)
dav_svn_split_uri2(request_rec * r,const char * uri_to_split,const char * root_path,const char ** cleaned_uri,int * trailing_slash,const char ** repos_basename,const char ** relative_path,const char ** repos_path,apr_pool_t * pool)1266 dav_svn_split_uri2(request_rec *r,
1267                    const char *uri_to_split,
1268                    const char *root_path,
1269                    const char **cleaned_uri,
1270                    int *trailing_slash,
1271                    const char **repos_basename,
1272                    const char **relative_path,
1273                    const char **repos_path,
1274                    apr_pool_t *pool)
1275 {
1276   apr_size_t len1;
1277   int had_slash;
1278   const char *fs_path;
1279   const char *fs_parent_path;
1280   const char *relative;
1281   char *uri;
1282 
1283   /* one of these is NULL, the other non-NULL. */
1284   fs_path = dav_svn__get_fs_path(r);
1285   fs_parent_path = dav_svn__get_fs_parent_path(r);
1286 
1287   if ((fs_path == NULL) && (fs_parent_path == NULL))
1288     {
1289       /* ### are SVN_ERR_APMOD codes within the right numeric space? */
1290       return dav_svn__new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
1291                                 SVN_ERR_APMOD_MISSING_PATH_TO_FS, 0,
1292                                 "The server is misconfigured: "
1293                                 "either an SVNPath or SVNParentPath "
1294                                 "directive is required to specify the location "
1295                                 "of this resource's repository.");
1296     }
1297 
1298   /* make a copy so that we can do some work on it */
1299   uri = apr_pstrdup(pool, uri_to_split);
1300 
1301   /* remove duplicate slashes, and make sure URI has no trailing '/' */
1302   ap_no2slash(uri);
1303   len1 = strlen(uri);
1304   had_slash = (len1 > 0 && uri[len1 - 1] == '/');
1305   if (len1 > 1 && had_slash)
1306     uri[len1 - 1] = '\0';
1307 
1308   if (had_slash)
1309     *trailing_slash = TRUE;
1310   else
1311     *trailing_slash = FALSE;
1312 
1313   /* return the first item.  */
1314   *cleaned_uri = apr_pstrdup(pool, uri);
1315 
1316   /* The URL space defined by the SVN provider is always a virtual
1317      space. Construct the path relative to the configured Location
1318      (root_path). So... the relative location is simply the URL used,
1319      skipping the root_path.
1320 
1321      Note: mod_dav has canonialized root_path. It will not have a trailing
1322      slash (unless it is "/").
1323 
1324      Note: given a URI of /something and a root of /some, then it is
1325            impossible to be here (and end up with "thing"). This is simply
1326            because we control /some and are dispatched to here for its
1327            URIs. We do not control /something, so we don't get here. Or,
1328            if we *do* control /something, then it is for THAT root.
1329   */
1330   relative = ap_stripprefix(uri, root_path);
1331 
1332   /* We want a leading slash on the path specified by <relative>. This
1333      will almost always be the case since root_path does not have a trailing
1334      slash. However, if the root is "/", then the slash will be removed
1335      from <relative>. Backing up a character will put the leading slash
1336      back.
1337 
1338      Watch out for the empty string! This can happen when URI == ROOT_PATH.
1339      We simply turn the path into "/" for this case. */
1340   if (*relative == '\0')
1341     relative = "/";
1342   else if (*relative != '/')
1343     --relative;
1344   /* ### need a better name... it isn't "relative" because of the leading
1345      ### slash. something about SVN-private-path */
1346 
1347   /* Depending on whether SVNPath or SVNParentPath was used, we need
1348      to compute 'relative' and 'repos_basename' differently.  */
1349 
1350   /* Normal case:  the SVNPath command was used to specify a
1351      particular repository.  */
1352   if (fs_path != NULL)
1353     {
1354       /* the repos_basename is the last component of root_path. */
1355       *repos_basename = svn_dirent_basename(root_path, pool);
1356 
1357       /* 'relative' is already correct for SVNPath; the root_path
1358          already contains the name of the repository, so relative is
1359          everything beyond that.  */
1360     }
1361 
1362   else
1363     {
1364       /* SVNParentPath was used instead: assume the first component of
1365          'relative' is the name of a repository. */
1366       const char *magic_component, *magic_end;
1367 
1368       /* A repository name is required here.
1369          Remember that 'relative' always starts with a "/". */
1370       if (relative[1] == '\0')
1371         {
1372           /* ### are SVN_ERR_APMOD codes within the right numeric space? */
1373           return dav_svn__new_error(pool, HTTP_FORBIDDEN,
1374                                     SVN_ERR_APMOD_MALFORMED_URI, 0,
1375                                     "The URI does not contain the name "
1376                                     "of a repository.");
1377         }
1378 
1379       magic_end = ap_strchr_c(relative + 1, '/');
1380       if (!magic_end)
1381         {
1382           /* ### Request was for parent directory with no trailing
1383              slash; we probably ought to just redirect to same with
1384              trailing slash appended. */
1385           magic_component = relative + 1;
1386           relative = "/";
1387         }
1388       else
1389         {
1390           magic_component = apr_pstrndup(pool, relative + 1,
1391                                          magic_end - relative - 1);
1392           relative = magic_end;
1393         }
1394 
1395       /* return answer */
1396       *repos_basename = magic_component;
1397     }
1398 
1399   /* We can return 'relative' at this point too. */
1400   *relative_path = apr_pstrdup(pool, relative);
1401 
1402   /* Code to remove the !svn junk from the front of the relative path,
1403      mainly stolen from parse_uri().  This code assumes that
1404      the 'relative' string being parsed doesn't start with '/'. */
1405   relative++;
1406 
1407   {
1408     const char *special_uri = dav_svn__get_special_uri(r);
1409     apr_size_t len2;
1410     char ch;
1411 
1412     len1 = strlen(relative);
1413     len2 = strlen(special_uri);
1414     if (len1 > len2
1415         && ((ch = relative[len2]) == '/' || ch == '\0')
1416         && memcmp(relative, special_uri, len2) == 0)
1417       {
1418         if (ch == '\0')
1419           {
1420             /* relative is just "!svn", which is malformed. */
1421             return dav_svn__new_error(pool, HTTP_NOT_FOUND,
1422                                       SVN_ERR_APMOD_MALFORMED_URI, 0,
1423                                       "Nothing follows the svn special_uri.");
1424           }
1425         else
1426           {
1427             const struct special_defn *defn;
1428 
1429             /* skip past the "!svn/" prefix */
1430             relative += len2 + 1;
1431             len1 -= len2 + 1;
1432 
1433             for (defn = special_subdirs ; defn->name != NULL; ++defn)
1434               {
1435                 apr_size_t len3 = strlen(defn->name);
1436 
1437                 if (len1 >= len3 && memcmp(relative, defn->name, len3) == 0)
1438                   {
1439                     /* Found a matching special dir. */
1440 
1441                     if (relative[len3] == '\0')
1442                       {
1443                         /* relative is "!svn/xxx"  */
1444                         if (defn->numcomponents == 0)
1445                           *repos_path = NULL;
1446                         else
1447                           return dav_svn__new_error(
1448                                      pool, HTTP_NOT_FOUND,
1449                                      SVN_ERR_APMOD_MALFORMED_URI, 0,
1450                                      "Missing info after special_uri.");
1451                       }
1452                     else if (relative[len3] == '/')
1453                       {
1454                         /* Skip past defn->numcomponents components,
1455                            return everything beyond that.*/
1456                         int j;
1457                         const char *end = NULL, *start = relative + len3 + 1;
1458 
1459                         for (j = 0; j < defn->numcomponents; j++)
1460                           {
1461                             end = ap_strchr_c(start, '/');
1462                             if (! end)
1463                               break;
1464                             start = end + 1;
1465                           }
1466 
1467                         if (! end)
1468                           {
1469                             /* Did we break from the loop prematurely? */
1470                             if (j != (defn->numcomponents - 1))
1471                               return dav_svn__new_error(
1472                                          pool, HTTP_NOT_FOUND,
1473                                          SVN_ERR_APMOD_MALFORMED_URI, 0,
1474                                          "Not enough components after "
1475                                          "special_uri.");
1476 
1477                             if (! defn->has_repos_path)
1478                               /* It's okay to not have found a slash. */
1479                               *repos_path = NULL;
1480                             else
1481                               *repos_path = "/";
1482                           }
1483                         else
1484                           {
1485                             /* Found a slash after the special components. */
1486                             *repos_path = apr_pstrdup(pool, start - 1);
1487                           }
1488                       }
1489                     else
1490                       {
1491                         return
1492                           dav_svn__new_error(pool, HTTP_NOT_FOUND,
1493                                         SVN_ERR_APMOD_MALFORMED_URI, 0,
1494                                         "Unknown data after special_uri.");
1495                       }
1496 
1497                   break;
1498                   }
1499               }
1500 
1501             if (defn->name == NULL)
1502               return
1503                 dav_svn__new_error(pool, HTTP_NOT_FOUND,
1504                                    SVN_ERR_APMOD_MALFORMED_URI, 0,
1505                                    "Couldn't match subdir after special_uri.");
1506           }
1507       }
1508     else
1509       {
1510         /* There's no "!svn/" at all, so the relative path is already
1511            a valid path within the repository.  */
1512         *repos_path = apr_pstrdup(pool, relative - 1);
1513       }
1514   }
1515 
1516   return NULL;
1517 }
1518 
1519 AP_MODULE_DECLARE(dav_error *)
dav_svn_split_uri(request_rec * r,const char * uri_to_split,const char * root_path,const char ** cleaned_uri,int * trailing_slash,const char ** repos_basename,const char ** relative_path,const char ** repos_path)1520 dav_svn_split_uri(request_rec *r,
1521                   const char *uri_to_split,
1522                   const char *root_path,
1523                   const char **cleaned_uri,
1524                   int *trailing_slash,
1525                   const char **repos_basename,
1526                   const char **relative_path,
1527                   const char **repos_path)
1528 {
1529   return dav_svn_split_uri2(r, uri_to_split, root_path, cleaned_uri,
1530                             trailing_slash, repos_basename, relative_path,
1531                             repos_path, r->pool);
1532 }
1533 
1534 /* Context for cleanup handler. */
1535 struct cleanup_fs_access_baton
1536 {
1537   svn_fs_t *fs;
1538   apr_pool_t *pool;
1539 };
1540 
1541 
1542 /* Pool cleanup handler.  Make sure fs's access ctx points to NULL
1543    when request pool is destroyed. */
1544 static apr_status_t
cleanup_fs_access(void * data)1545 cleanup_fs_access(void *data)
1546 {
1547   svn_error_t *serr;
1548   struct cleanup_fs_access_baton *baton = data;
1549 
1550   serr = svn_fs_set_access(baton->fs, NULL);
1551   if (serr)
1552     {
1553       ap_log_perror(APLOG_MARK, APLOG_ERR, serr->apr_err, baton->pool,
1554                     "cleanup_fs_access: error clearing fs access context");
1555       svn_error_clear(serr);
1556     }
1557 
1558   return APR_SUCCESS;
1559 }
1560 
1561 /* Context for cleanup handler. */
1562 struct cleanup_req_logging_baton
1563 {
1564   svn_fs_t *fs;
1565   conn_rec *connection;
1566 };
1567 
1568 static apr_status_t
cleanup_req_logging(void * data)1569 cleanup_req_logging(void *data)
1570 {
1571   struct cleanup_req_logging_baton *baton = data;
1572 
1573   /* The request about to be freed. Log future warnings with a connection
1574    * context instead of a request context. */
1575   svn_fs_set_warning_func(baton->fs, log_warning_conn, baton->connection);
1576 
1577   return APR_SUCCESS;
1578 }
1579 
1580 /* Helper func to construct a special 'parentpath' private resource. */
1581 static dav_error *
get_parentpath_resource(request_rec * r,dav_resource ** resource)1582 get_parentpath_resource(request_rec *r,
1583                         dav_resource **resource)
1584 {
1585   const char *new_uri;
1586   dav_svn_root *droot = apr_pcalloc(r->pool, sizeof(*droot));
1587   dav_svn_repos *repos = apr_pcalloc(r->pool, sizeof(*repos));
1588   dav_resource_combined *comb = apr_pcalloc(r->pool, sizeof(*comb));
1589   apr_size_t len = strlen(r->uri);
1590 
1591   comb->res.exists = TRUE;
1592   comb->res.collection = TRUE;
1593   comb->res.uri = apr_pstrdup(r->pool, r->uri);
1594   comb->res.info = &comb->priv;
1595   comb->res.hooks = &dav_svn__hooks_repository;
1596   comb->res.pool = r->pool;
1597   comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
1598 
1599   comb->priv.restype = DAV_SVN_RESTYPE_PARENTPATH_COLLECTION;
1600   comb->priv.r = r;
1601   comb->priv.repos_path = "Collection of Repositories";
1602   comb->priv.root = *droot;
1603   comb->priv.is_public_uri = TRUE;
1604   droot->rev = SVN_INVALID_REVNUM;
1605 
1606   comb->priv.repos = repos;
1607   repos->pool = r->pool;
1608   repos->xslt_uri = dav_svn__get_xslt_uri(r);
1609   repos->autoversioning = dav_svn__get_autoversioning_flag(r);
1610   repos->bulk_updates = dav_svn__get_bulk_updates_flag(r);
1611   repos->v2_protocol = dav_svn__check_httpv2_support(r);
1612   repos->base_url = ap_construct_url(r->pool, "", r);
1613   repos->special_uri = dav_svn__get_special_uri(r);
1614   repos->username = r->user;
1615   repos->client_capabilities = apr_hash_make(repos->pool);
1616   repos->youngest_rev = SVN_INVALID_REVNUM;
1617 
1618   /* Make sure this type of resource always has a trailing slash; if
1619      not, redirect to a URI that does. */
1620   if (r->uri[len-1] != '/')
1621     {
1622       new_uri = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri),
1623                             "/", SVN_VA_NULL);
1624       apr_table_setn(r->headers_out, "Location",
1625                      ap_construct_url(r->pool, new_uri, r));
1626       return dav_svn__new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0, 0,
1627                                 "Requests for a collection must have a "
1628                                 "trailing slash on the URI.");
1629     }
1630 
1631   /* No other "prepping" of resource needs to happen -- no opening
1632      of a repository or anything like that, because, well, there's
1633      no repository to open. */
1634   *resource = &comb->res;
1635   return NULL;
1636 }
1637 
1638 /* --------------- Borrowed from httpd's mod_negotiation.c -------------- */
1639 
1640 typedef struct accept_rec {
1641   char *name;                 /* MUST be lowercase */
1642   float quality;
1643 } accept_rec;
1644 
1645 /*
1646  * Get a single Accept-encoding line from ACCEPT_LINE, and place the
1647  * information we have parsed out of it into RESULT.
1648  */
1649 
get_entry(apr_pool_t * p,accept_rec * result,const char * accept_line)1650 static const char *get_entry(apr_pool_t *p, accept_rec *result,
1651                              const char *accept_line)
1652 {
1653     result->quality = 1.0f;
1654 
1655     /*
1656      * Note that this handles what I gather is the "old format",
1657      *
1658      *    Accept: text/html text/plain moo/zot
1659      *
1660      * without any compatibility kludges --- if the token after the
1661      * MIME type begins with a semicolon, we know we're looking at parms,
1662      * otherwise, we know we aren't.  (So why all the pissing and moaning
1663      * in the CERN server code?  I must be missing something).
1664      */
1665 
1666     result->name = ap_get_token(p, &accept_line, 0);
1667     ap_str_tolower(result->name);     /* You want case insensitive,
1668                                        * you'll *get* case insensitive.
1669                                        */
1670 
1671     while (*accept_line == ';')
1672       {
1673         /* Parameters ... */
1674 
1675         char *parm;
1676         char *cp;
1677         char *end;
1678 
1679         ++accept_line;
1680         parm = ap_get_token(p, &accept_line, 1);
1681 
1682         /* Look for 'var = value' --- and make sure the var is in lcase. */
1683 
1684         for (cp = parm; (*cp && !svn_ctype_isspace(*cp) && *cp != '='); ++cp)
1685           {
1686             *cp = (char)apr_tolower(*cp);
1687           }
1688 
1689         if (!*cp)
1690           {
1691             continue;           /* No '='; just ignore it. */
1692           }
1693 
1694         *cp++ = '\0';           /* Delimit var */
1695         while (*cp && (svn_ctype_isspace(*cp) || *cp == '='))
1696           {
1697             ++cp;
1698           }
1699 
1700         if (*cp == '"')
1701           {
1702             ++cp;
1703             for (end = cp;
1704                  (*end && *end != '\n' && *end != '\r' && *end != '\"');
1705                  end++);
1706           }
1707         else
1708           {
1709             for (end = cp; (*end && !svn_ctype_isspace(*end)); end++);
1710           }
1711         if (*end)
1712           {
1713             *end = '\0';        /* strip ending quote or return */
1714           }
1715         ap_str_tolower(cp);
1716 
1717         if (parm[0] == 'q'
1718             && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0')))
1719           {
1720             result->quality = (float) atof(cp);
1721           }
1722       }
1723 
1724     if (*accept_line == ',')
1725       {
1726         ++accept_line;
1727       }
1728 
1729     return accept_line;
1730 }
1731 
1732 /* @a accept_line is the Accept-Encoding header, which is of the
1733    format:
1734 
1735      Accept-Encoding: name; q=N;
1736 
1737    This function will return an array of accept_rec structures that
1738    contain the accepted encodings and the quality each one has
1739    associated with them.
1740 */
do_header_line(apr_pool_t * p,const char * accept_line)1741 static apr_array_header_t *do_header_line(apr_pool_t *p,
1742                                           const char *accept_line)
1743 {
1744     apr_array_header_t *accept_recs;
1745 
1746     if (!accept_line)
1747       return NULL;
1748 
1749     accept_recs = apr_array_make(p, 10, sizeof(accept_rec));
1750 
1751     while (*accept_line)
1752       {
1753         accept_rec *prefs = (accept_rec *) apr_array_push(accept_recs);
1754         accept_line = get_entry(p, prefs, accept_line);
1755       }
1756 
1757     return accept_recs;
1758 }
1759 
1760 /* ---------------------------------------------------------------------- */
1761 
1762 
1763 /* qsort comparison function for the quality field of the accept_rec
1764    structure */
sort_encoding_pref(const void * accept_rec1,const void * accept_rec2)1765 static int sort_encoding_pref(const void *accept_rec1, const void *accept_rec2)
1766 {
1767   float diff = ((const accept_rec *) accept_rec1)->quality -
1768       ((const accept_rec *) accept_rec2)->quality;
1769   return (diff == 0 ? 0 : (diff > 0 ? -1 : 1));
1770 }
1771 
get_svndiff_version(const struct accept_rec * rec)1772 static int get_svndiff_version(const struct accept_rec *rec)
1773 {
1774   if (strcmp(rec->name, "svndiff2") == 0)
1775     return 2;
1776   else if (strcmp(rec->name, "svndiff1") == 0)
1777     return 1;
1778   else if (strcmp(rec->name, "svndiff") == 0)
1779     return 0;
1780   else
1781     return -1;
1782 }
1783 
1784 /* Parse and handle any possible Accept-Encoding header that has been
1785    sent as part of the request.  */
1786 static void
negotiate_encoding_prefs(request_rec * r,int * svndiff_version)1787 negotiate_encoding_prefs(request_rec *r, int *svndiff_version)
1788 {
1789   /* It would be nice if mod_negotiation
1790      <http://httpd.apache.org/docs-2.1/mod/mod_negotiation.html> could
1791      handle the Accept-Encoding header parsing for us.  Sadly, it
1792      looks like its data structures and routines are private (see
1793      httpd/modules/mappers/mod_negotiation.c).  Thus, we duplicate the
1794      necessary ones in this file. */
1795   int i;
1796   apr_array_header_t *encoding_prefs;
1797   apr_array_header_t *svndiff_encodings;
1798   svn_boolean_t accepts_svndiff2 = FALSE;
1799 
1800   encoding_prefs = do_header_line(r->pool,
1801                                   apr_table_get(r->headers_in,
1802                                                 "Accept-Encoding"));
1803 
1804   if (!encoding_prefs || apr_is_empty_array(encoding_prefs))
1805     {
1806       *svndiff_version = 0;
1807       return;
1808     }
1809 
1810   svndiff_encodings = apr_array_make(r->pool, 3, sizeof(struct accept_rec));
1811   for (i = 0; i < encoding_prefs->nelts; i++)
1812     {
1813       const struct accept_rec *rec = &APR_ARRAY_IDX(encoding_prefs, i,
1814                                                     struct accept_rec);
1815       int version = get_svndiff_version(rec);
1816 
1817       if (version > 0)
1818         APR_ARRAY_PUSH(svndiff_encodings, struct accept_rec) = *rec;
1819 
1820       if (version == 2)
1821         accepts_svndiff2 = TRUE;
1822     }
1823 
1824   if (dav_svn__get_compression_level(r) == 0)
1825     {
1826       /* If the compression is disabled on the server, use the uncompressed
1827        * svndiff0 format, which we assume is always supported. */
1828       *svndiff_version = 0;
1829     }
1830   else if (accepts_svndiff2 && dav_svn__get_compression_level(r) == 1)
1831     {
1832       /* Enable svndiff2 if the client can read it, and if the server-side
1833        * compression level is set to 1.  Svndiff2 offers better speed and
1834        * compression ratio comparable to svndiff1 with compression level 1,
1835        * but not with other compression levels.
1836        */
1837       *svndiff_version = 2;
1838     }
1839   else if (svndiff_encodings->nelts > 0)
1840     {
1841       const struct accept_rec *rec;
1842 
1843       /* Otherwise, use what the client prefers to see. */
1844       svn_sort__array(svndiff_encodings, sort_encoding_pref);
1845       rec = &APR_ARRAY_IDX(svndiff_encodings, 0, struct accept_rec);
1846       *svndiff_version = get_svndiff_version(rec);
1847     }
1848   else
1849     {
1850       *svndiff_version = 0;
1851     }
1852 }
1853 
1854 
1855 /* The only two possible values for a capability. */
1856 static const char *capability_yes = "yes";
1857 static const char *capability_no = "no";
1858 
1859 /* Convert CAPABILITIES, a hash table mapping 'const char *' keys to
1860  * "yes" or "no" values, to a list of all keys whose value is "yes".
1861  * Return the list, allocated in POOL, and use POOL for all temporary
1862  * allocation.
1863  */
1864 static apr_array_header_t *
capabilities_as_list(apr_hash_t * capabilities,apr_pool_t * pool)1865 capabilities_as_list(apr_hash_t *capabilities, apr_pool_t *pool)
1866 {
1867   apr_array_header_t *list = apr_array_make(pool, apr_hash_count(capabilities),
1868                                             sizeof(char *));
1869   apr_hash_index_t *hi;
1870 
1871   for (hi = apr_hash_first(pool, capabilities); hi; hi = apr_hash_next(hi))
1872     {
1873       const void *key;
1874       void *val;
1875       apr_hash_this(hi, &key, NULL, &val);
1876       if (strcmp((const char *) val, "yes") == 0)
1877         APR_ARRAY_PUSH(list, const char *) = key;
1878     }
1879 
1880   return list;
1881 }
1882 
1883 
1884 /* Given a non-NULL QUERY string of the form "key1=val1&key2=val2&...",
1885  * parse the keys and values into an apr table.  Allocate the table in
1886  * POOL;  dup all keys and values into POOL as well.
1887  *
1888  * Note that repeating the same key will cause table overwrites
1889  * (e.g. "r=3&r=5"), and that a lack of value ("p=") is legal, but
1890  * equivalent to not specifying the key at all.
1891  */
1892 static apr_table_t *
querystring_to_table(const char * query,apr_pool_t * pool)1893 querystring_to_table(const char *query, apr_pool_t *pool)
1894 {
1895   apr_table_t *table = apr_table_make(pool, 2);
1896   apr_array_header_t *array = svn_cstring_split(query, "&", TRUE, pool);
1897   int i;
1898   for (i = 0; i < array->nelts; i++)
1899     {
1900       char *keyval = APR_ARRAY_IDX(array, i, char *);
1901       char *equals = strchr(keyval, '=');
1902       if (equals != NULL)
1903         {
1904           *equals = '\0';
1905           apr_table_set(table, keyval, equals + 1);
1906         }
1907     }
1908   return table;
1909 }
1910 
1911 
1912 /* Helper for get_resource(), called after COMB is fully parsed and prepped. */
1913 static dav_error *
do_out_of_date_check(dav_resource_combined * comb,request_rec * r)1914 do_out_of_date_check(dav_resource_combined *comb, request_rec *r)
1915 {
1916   svn_revnum_t created_rev;
1917   svn_error_t *serr;
1918 
1919   /* Do we have an X-SVN-Version-Name header? */
1920   if (! SVN_IS_VALID_REVNUM(comb->priv.version_name))
1921     return NULL;
1922 
1923   /* Note: LOCK and DELETE handlers already notice the header and do
1924      their own out-of-dateness checks.  MKCOL, COPY, MOVE don't supply
1925      the header at all, nor do MKACTIVITY, POST, or MERGE. */
1926   if (! ((r->method_number == M_PUT)
1927          || (r->method_number == M_PROPPATCH)))
1928     return NULL;
1929 
1930   /* Do an out-of-dateness check. */
1931   if ((serr = svn_fs_node_created_rev(&created_rev, comb->priv.root.root,
1932                                       comb->priv.repos_path, r->pool)))
1933     return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1934                                 "Could not get created rev of "
1935                                 "resource", r->pool);
1936 
1937   if (SVN_IS_VALID_REVNUM(created_rev))
1938     {
1939       if (comb->priv.version_name < created_rev)
1940         {
1941           serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL,
1942                                    comb->res.collection
1943                                     ? "Directory '%s' is out of date"
1944                                     : (comb->res.exists
1945                                         ? "File '%s' is out of date"
1946                                         : "'%s' is out of date"),
1947                                    comb->priv.repos_path);
1948           return dav_svn__convert_err(serr, HTTP_CONFLICT,
1949                                       "Attempting to modify out-of-date resource.",
1950                                       r->pool);
1951         }
1952       else if (comb->priv.version_name > created_rev)
1953         {
1954           svn_revnum_t txn_base_rev;
1955 
1956           txn_base_rev = svn_fs_txn_base_revision(comb->res.info->root.txn);
1957           if (comb->priv.version_name > txn_base_rev)
1958             {
1959               serr = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1960                                        "No such revision %ld",
1961                                        comb->priv.version_name);
1962 
1963               return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1964                                           "Unknown base revision",
1965                                           r->pool);
1966             }
1967         }
1968     }
1969   else if (comb->res.collection)
1970     {
1971       /* Issue #4480: With HTTPv2 we can receive the first change for a
1972          directory after it has been made mutable, because one of its
1973          descendants was changed before changing the directory.
1974 
1975          We have to check if whatever the node is in HEAD is equivalent
1976          to what it was in the provided BASE revision.
1977 
1978          If the node was copied, we would process it before its decendants
1979          and we already performed quite a few checks when making it mutable
1980          via its descendant, so what we should really check here is if the
1981          properties changed since the BASE version.
1982 
1983          ### I think svn_fs_node_relation() checks for more changes than we
1984              should check for here. Needs further review. But it looks like
1985              this check matches the checks in the libsvn_fs commit editor.
1986 
1987              For now I would say reporting out of date in a few too many
1988              cases is safer than not reporting out of date when we should.
1989        */
1990       svn_revnum_t txn_base_rev;
1991       svn_fs_root_t *txn_base_root;
1992       svn_fs_root_t *rev_root;
1993       svn_fs_node_relation_t node_relation;
1994 
1995       txn_base_rev = svn_fs_txn_base_revision(comb->res.info->root.txn);
1996 
1997       if (comb->priv.version_name == txn_base_rev)
1998         return NULL; /* Easy out: Nothing changed */
1999 
2000       serr = svn_fs_revision_root(&txn_base_root, comb->res.info->repos->fs,
2001                                   txn_base_rev, r->pool);
2002 
2003       if (serr != NULL)
2004         {
2005           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2006                                       "Could not open the transaction revision "
2007                                       "for verification against the base "
2008                                       "revision", r->pool);
2009         }
2010 
2011       serr = svn_fs_revision_root(&rev_root, comb->res.info->repos->fs,
2012                                   comb->priv.version_name, r->pool);
2013 
2014       if (serr != NULL)
2015         {
2016           svn_fs_close_root(txn_base_root);
2017           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2018                                       "Could not open the base revision "
2019                                       "for verification against the "
2020                                       "transaction revision", r->pool);
2021         }
2022 
2023       serr = svn_fs_node_relation(&node_relation, rev_root,
2024                                   comb->priv.repos_path,
2025                                   txn_base_root,
2026                                   comb->priv.repos_path,
2027                                   r->pool);
2028 
2029       svn_fs_close_root(rev_root);
2030       svn_fs_close_root(txn_base_root);
2031 
2032       if (serr != NULL)
2033         {
2034           /* ### correct HTTP error? */
2035           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2036                                       "Unable to fetch the node revision id "
2037                                       "of the version resource within the "
2038                                       "revision",
2039                                       r->pool);
2040         }
2041 
2042       if (node_relation != svn_fs_node_unchanged)
2043         {
2044           serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL,
2045                                    "Directory '%s' is out of date",
2046                                    comb->priv.repos_path);
2047           return dav_svn__convert_err(serr, HTTP_CONFLICT,
2048                                       "Attempting to modify out-of-date resource.",
2049                                       r->pool);
2050         }
2051     }
2052 
2053   return NULL;
2054 }
2055 
2056 
2057 /* Helper for get_resource().
2058  *
2059  * Given a fully fleshed out COMB object which has already been parsed
2060  * via parse_uri(), parse the querystring in QUERY.
2061  *
2062  * Specifically, look for optional 'p=PEGREV' and 'r=WORKINGREV'
2063  * values in the querystring, and modify COMB so that prep_regular()
2064  * opens the correct revision and path.
2065  */
2066 static dav_error *
parse_querystring(request_rec * r,const char * query,dav_resource_combined * comb,apr_pool_t * pool)2067 parse_querystring(request_rec *r, const char *query,
2068                   dav_resource_combined *comb, apr_pool_t *pool)
2069 {
2070   svn_error_t *serr;
2071   svn_revnum_t working_rev, peg_rev;
2072   apr_table_t *pairs = querystring_to_table(query, pool);
2073   const char *prevstr = apr_table_get(pairs, "p");
2074   const char *wrevstr;
2075   const char *keyword_subst;
2076 
2077   /* Will we be doing keyword substitution? */
2078   keyword_subst = apr_table_get(pairs, "kw");
2079   if (keyword_subst && (strcmp(keyword_subst, "1") == 0))
2080     comb->priv.keyword_subst = TRUE;
2081 
2082   if (prevstr)
2083     {
2084       while (*prevstr == 'r')
2085         prevstr++;
2086       peg_rev = SVN_STR_TO_REV(prevstr);
2087       if (!SVN_IS_VALID_REVNUM(peg_rev))
2088         return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0, 0,
2089                                   "invalid peg rev in query string");
2090     }
2091   else
2092     {
2093       /* No peg-rev?  Default to HEAD, just like the cmdline client. */
2094       serr = dav_svn__get_youngest_rev(&peg_rev, comb->priv.repos, pool);
2095       if (serr != NULL)
2096         return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2097                                     "Couldn't fetch youngest rev.", pool);
2098     }
2099 
2100   wrevstr = apr_table_get(pairs, "r");
2101   if (wrevstr)
2102     {
2103       while (*wrevstr == 'r')
2104         wrevstr++;
2105       working_rev = SVN_STR_TO_REV(wrevstr);
2106       if (!SVN_IS_VALID_REVNUM(working_rev))
2107         return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0, 0,
2108                                   "invalid working rev in query string");
2109     }
2110   else
2111     {
2112       /* No working-rev?  Assume it's equal to the peg-rev, just
2113          like the cmdline client does. */
2114       working_rev = peg_rev;
2115     }
2116 
2117   /* If WORKING_REV is younger than PEG_REV, we have a problem.
2118      Our node-tracing algorithms can't handle that scenario, so we'll
2119      disallow it here. */
2120   if (working_rev > peg_rev)
2121     return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0, 0,
2122                               "working rev greater than peg rev.");
2123 
2124   /* If WORKING_REV and PEG_REV are equivalent, we want to return the
2125      resource at the revision.  Otherwise, WORKING_REV is older than
2126      PEG_REV, so we need to crawl back through the history of
2127      REPOS_PATH@PEG_REV until we hit WORKING_REV.  We'll then redirect
2128      the client to the new location/revision pair found by that crawl. */
2129   if (working_rev == peg_rev)
2130     {
2131       comb->priv.root.rev = peg_rev;
2132 
2133       /* Did we have a peg revision?  Remember this little fact (in
2134          case deliver() needs to know it). */
2135       if (prevstr)
2136         comb->priv.pegged = TRUE;
2137     }
2138   else
2139     {
2140       const char *newpath, *location;
2141       apr_hash_t *locations;
2142       apr_array_header_t *loc_revs = apr_array_make(pool, 1,
2143                                                     sizeof(svn_revnum_t));
2144 
2145       dav_svn__authz_read_baton *arb = apr_pcalloc(pool, sizeof(*arb));
2146       arb->r = comb->priv.r;
2147       arb->repos = comb->priv.repos;
2148 
2149       APR_ARRAY_PUSH(loc_revs, svn_revnum_t) = working_rev;
2150       if ((serr = svn_repos_trace_node_locations(comb->priv.repos->fs,
2151                                                  &locations,
2152                                                  comb->priv.repos_path,
2153                                                  peg_rev,
2154                                                  loc_revs,
2155                                                  dav_svn__authz_read_func(arb),
2156                                                  arb,
2157                                                  pool)))
2158         return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2159                                     "Couldn't trace history.", pool);
2160 
2161       newpath = apr_hash_get(locations, &working_rev, sizeof(working_rev));
2162       if (! newpath)
2163         return dav_svn__new_error(pool, HTTP_NOT_FOUND, 0, 0,
2164                                   "path doesn't exist in that revision.");
2165 
2166       /* Redirect folks to a canonical, peg-revision-only location.
2167          If they used a peg revision in this request, we can use a
2168          permanent redirect.  If they didn't (peg-rev is HEAD), we can
2169          only use a temporary redirect.  In either case, preserve the
2170          "keyword_subst" state in the redirected location, too.  */
2171       location = ap_construct_url(r->pool,
2172                                   apr_psprintf(r->pool, "%s%s?p=%ld%s",
2173                                                (comb->priv.repos->root_path[1]
2174                                                 ? comb->priv.repos->root_path
2175                                                 : ""),
2176                                                newpath, working_rev,
2177                                                keyword_subst ? "&kw=1" : ""),
2178                                   r);
2179       apr_table_setn(r->headers_out, "Location", location);
2180       return dav_svn__new_error(r->pool,
2181                                 prevstr ? HTTP_MOVED_PERMANENTLY
2182                                         : HTTP_MOVED_TEMPORARILY,
2183                                 0, 0, "redirecting to canonical location");
2184     }
2185 
2186   return NULL;
2187 }
2188 
2189 static dav_error *
get_resource(request_rec * r,const char * root_path,const char * label,int use_checked_in,dav_resource ** resource)2190 get_resource(request_rec *r,
2191              const char *root_path,
2192              const char *label,
2193              int use_checked_in,
2194              dav_resource **resource)
2195 {
2196   const char *fs_path;
2197   const char *repo_name;
2198   const char *xslt_uri;
2199   const char *fs_parent_path;
2200   dav_resource_combined *comb;
2201   dav_svn_repos *repos;
2202   const char *cleaned_uri;
2203   const char *repo_basename;
2204   const char *relative;
2205   const char *repos_path;
2206   const char *repos_key;
2207   const char *version_name;
2208   svn_error_t *serr;
2209   dav_error *err;
2210   int had_slash;
2211   dav_locktoken_list *ltl;
2212   struct cleanup_fs_access_baton *cleanup_baton;
2213   struct cleanup_req_logging_baton *cleanup_req_logging_baton;
2214   void *userdata;
2215   apr_hash_t *fs_config;
2216 
2217   repo_name = dav_svn__get_repo_name(r);
2218   xslt_uri = dav_svn__get_xslt_uri(r);
2219   fs_parent_path = dav_svn__get_fs_parent_path(r);
2220 
2221   if (r->method_number == M_COPY)
2222     {
2223       /* Workaround for issue #4531: Avoid a depth-infinity walk on
2224          the copy source by overriding the Depth header here.
2225          mod_dav defaults to infinite depth if this header is not set
2226          which makes copies O(size of source) rather than the desired O(1).
2227          ### Should be fixed by an explicit provider API feature in mod_dav. */
2228       apr_table_setn(r->headers_in, "Depth", "0");
2229     }
2230 
2231   /* Special case: detect and build the SVNParentPath as a unique type
2232      of private resource, iff the SVNListParentPath directive is 'on'. */
2233   if (dav_svn__is_parentpath_list(r))
2234     {
2235       /* Only allow GET and HEAD on the parentpath resource
2236        * httpd uses the same method_number for HEAD as GET */
2237       if (r->method_number != M_GET)
2238         {
2239           int status;
2240 
2241           /* Marshall the error back to the client by generating by
2242            * way of the dav_svn__error_response_tag trick. */
2243           err = dav_svn__new_error(r->pool, HTTP_METHOD_NOT_ALLOWED,
2244                                    SVN_ERR_APMOD_MALFORMED_URI, 0,
2245                                    "The URI does not contain the name "
2246                                    "of a repository.");
2247           /* can't use r->allowed since the default handler isn't called */
2248           apr_table_setn(r->headers_out, "Allow", "GET,HEAD");
2249           status = dav_svn__error_response_tag(r, err);
2250 
2251           return dav_push_error(r->pool, status, err->error_id, NULL, err);
2252         }
2253 
2254       err = get_parentpath_resource(r, resource);
2255       if (err)
2256         return err;
2257       return NULL;
2258     }
2259 
2260   /* This does all the work of interpreting/splitting the request uri. */
2261   err = dav_svn_split_uri(r, r->uri, root_path,
2262                           &cleaned_uri, &had_slash,
2263                           &repo_basename, &relative, &repos_path);
2264   if (err)
2265     return err;
2266 
2267   /* The path that we will eventually try to open as an svn
2268      repository.  Normally defined by the SVNPath directive. */
2269   fs_path = dav_svn__get_fs_path(r);
2270 
2271   /* If the SVNParentPath directive was used instead... */
2272   if (fs_parent_path != NULL)
2273     {
2274       /* ...then the URL to the repository is actually one implicit
2275          component longer... */
2276       root_path = svn_urlpath__join(root_path, repo_basename, r->pool);
2277       /* ...and we need to specify exactly what repository to open. */
2278       fs_path = svn_dirent_join(fs_parent_path, repo_basename, r->pool);
2279     }
2280 
2281   /* Start building and filling a 'combination' object. */
2282   comb = apr_pcalloc(r->pool, sizeof(*comb));
2283   comb->res.info = &comb->priv;
2284   comb->res.hooks = &dav_svn__hooks_repository;
2285   comb->res.pool = r->pool;
2286   comb->res.uri = cleaned_uri;
2287 
2288   /* Original request, off which to generate subrequests later. */
2289   comb->priv.r = r;
2290 
2291   /* ### ugly hack to carry over Content-Type data to the open_stream, which
2292      ### does not have access to the request headers. */
2293   {
2294     const char *ct = apr_table_get(r->headers_in, "content-type");
2295 
2296     comb->priv.is_svndiff =
2297       ct != NULL
2298       && strcmp(ct, SVN_SVNDIFF_MIME_TYPE) == 0;
2299   }
2300 
2301   negotiate_encoding_prefs(r, &comb->priv.svndiff_version);
2302 
2303   /* ### and another hack for computing diffs to send to the client */
2304   comb->priv.delta_base = apr_table_get(r->headers_in,
2305                                         SVN_DAV_DELTA_BASE_HEADER);
2306 
2307   /* Gather any options requested by an svn client. */
2308   comb->priv.svn_client_options = apr_table_get(r->headers_in,
2309                                                 SVN_DAV_OPTIONS_HEADER);
2310 
2311   /* See if the client sent a custom 'version name' request header. */
2312   version_name = apr_table_get(r->headers_in, SVN_DAV_VERSION_NAME_HEADER);
2313   comb->priv.version_name
2314     = version_name ? SVN_STR_TO_REV(version_name): SVN_INVALID_REVNUM;
2315 
2316   /* Remember checksums, if any. */
2317   comb->priv.base_checksum =
2318     apr_table_get(r->headers_in, SVN_DAV_BASE_FULLTEXT_MD5_HEADER);
2319   comb->priv.result_checksum =
2320     apr_table_get(r->headers_in, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER);
2321 
2322   /* "relative" is part of the "uri" string, so it has the proper
2323      lifetime to store here. */
2324   /* ### that comment no longer applies. we're creating a string with its
2325      ### own lifetime now. so WHY are we using a string? hmm... */
2326   comb->priv.uri_path = svn_stringbuf_create(relative, r->pool);
2327 
2328   /* initialize this until we put something real here */
2329   comb->priv.root.rev = SVN_INVALID_REVNUM;
2330 
2331   /* create the repository structure and stash it away */
2332   repos = apr_pcalloc(r->pool, sizeof(*repos));
2333   repos->pool = r->pool;
2334   repos->youngest_rev = SVN_INVALID_REVNUM;
2335 
2336   comb->priv.repos = repos;
2337 
2338   /* We are assuming the root_path will live at least as long as this
2339      resource. Considering that it typically comes from the per-dir
2340      config in mod_dav, this is valid for now. */
2341   repos->root_path = svn_path_uri_encode(root_path, r->pool);
2342 
2343   /* where is the SVN FS for this resource? */
2344   repos->fs_path = fs_path;
2345 
2346   /* A name for the repository */
2347   repos->repo_name = repo_name;
2348 
2349   /* The repository filesystem basename */
2350   repos->repo_basename = repo_basename;
2351 
2352   /* An XSL transformation */
2353   repos->xslt_uri = xslt_uri;
2354 
2355   /* Is autoversioning active in this repos? */
2356   repos->autoversioning = dav_svn__get_autoversioning_flag(r);
2357 
2358   /* Are bulk updates allowed in this repos? */
2359   repos->bulk_updates = dav_svn__get_bulk_updates_flag(r);
2360 
2361   /* Are we advertising HTTP v2 protocol support? */
2362   repos->v2_protocol = dav_svn__check_httpv2_support(r);
2363 
2364   /* Path to activities database */
2365   repos->activities_db = dav_svn__get_activities_db(r);
2366   if (repos->activities_db == NULL)
2367     /* If not specified, use default ($repos/dav/activities.d). */
2368     repos->activities_db = svn_dirent_join(repos->fs_path,
2369                                          DEFAULT_ACTIVITY_DB,
2370                                          r->pool);
2371   else if (fs_parent_path != NULL)
2372     /* If this is a ParentPath-based repository, treat the specified
2373        path as a similar parent directory. */
2374     repos->activities_db = svn_dirent_join(repos->activities_db,
2375                                            svn_dirent_basename(repos->fs_path,
2376                                                                r->pool),
2377                                            r->pool);
2378 
2379   /* Remember various bits for later URL construction */
2380   repos->base_url = ap_construct_url(r->pool, "", r);
2381   repos->special_uri = dav_svn__get_special_uri(r);
2382 
2383   /* Remember who is making this request */
2384   repos->username = r->user;
2385 
2386   /* Allocate room for capabilities, but don't search for any until
2387      we know that this is a Subversion client. */
2388   repos->client_capabilities = apr_hash_make(repos->pool);
2389 
2390   /* Remember if the requesting client is a Subversion client, and if
2391      so, what its capabilities are. */
2392   {
2393     const char *val = apr_table_get(r->headers_in, "User-Agent");
2394 
2395     if (val && (ap_strstr_c(val, "SVN/") == val))
2396       {
2397         repos->is_svn_client = TRUE;
2398 
2399         /* Client capabilities are self-reported.  There is no
2400            guarantee the client actually has the capabilities it says
2401            it has, we just assume it is in the client's interests to
2402            report accurately.  Also, we only remember the capabilities
2403            the server cares about (even though the client may send
2404            more than that). */
2405 
2406         /* Start out assuming no capabilities. */
2407         svn_hash_sets(repos->client_capabilities,
2408                       SVN_RA_CAPABILITY_MERGEINFO,
2409                       capability_no);
2410 
2411         /* Then see what we can find. */
2412         val = apr_table_get(r->headers_in, "DAV");
2413         if (val)
2414           {
2415             apr_array_header_t *vals
2416               = svn_cstring_split(val, ",", TRUE, r->pool);
2417 
2418             if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
2419               {
2420                 svn_hash_sets(repos->client_capabilities,
2421                               SVN_RA_CAPABILITY_MERGEINFO, capability_yes);
2422               }
2423           }
2424       }
2425   }
2426 
2427   /* Retrieve/cache open repository */
2428   repos_key = apr_pstrcat(r->pool, "mod_dav_svn:", fs_path, SVN_VA_NULL);
2429   apr_pool_userdata_get(&userdata, repos_key, r->connection->pool);
2430   repos->repos = userdata;
2431   if (repos->repos == NULL)
2432     {
2433       const char *fs_type;
2434 
2435       /* construct FS configuration parameters */
2436       fs_config = apr_hash_make(r->connection->pool);
2437       svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
2438                     dav_svn__get_txdelta_cache_flag(r) ? "1" :"0");
2439       svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
2440                     dav_svn__get_fulltext_cache_flag(r) ? "1" :"0");
2441       svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
2442                     dav_svn__get_revprop_cache_flag(r) ? "2" :"0");
2443       svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NODEPROPS,
2444                     dav_svn__get_nodeprop_cache_flag(r) ? "1" :"0");
2445       svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ,
2446                     dav_svn__get_block_read_flag(r) ? "1" :"0");
2447 
2448       /* Disallow BDB/event until issue 4157 is fixed. */
2449       if (!strcmp(ap_show_mpm(), "event"))
2450         {
2451           serr = svn_repos__fs_type(&fs_type, fs_path, r->connection->pool);
2452           if (serr)
2453             {
2454               /* svn_repos_open2 is going to fail, use that error. */
2455               svn_error_clear(serr);
2456               serr = NULL;
2457             }
2458           else if (!strcmp(fs_type, "bdb"))
2459             serr = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2460                                      "BDB repository at '%s' is not compatible "
2461                                      "with event MPM",
2462                                      fs_path);
2463         }
2464       else
2465         serr = NULL;
2466 
2467       /* open the FS */
2468       if (!serr)
2469         serr = svn_repos_open3(&(repos->repos), fs_path, fs_config,
2470                                r->connection->pool, r->pool);
2471       if (serr != NULL)
2472         {
2473           /* The error returned by svn_repos_open2 might contain the
2474              actual path to the failed repository.  We don't want to
2475              leak that path back to the client, because that would be
2476              a security risk, but we do want to log the real error on
2477              the server side. */
2478 
2479           apr_status_t cause = svn_error_root_cause(serr)->apr_err;
2480           if (APR_STATUS_IS_ENOENT(cause) || APR_STATUS_IS_ENOTDIR(cause))
2481             return dav_svn__sanitize_error(
2482                 serr, "Could not find the requested SVN filesystem",
2483                 HTTP_NOT_FOUND, r);
2484           else
2485             return dav_svn__sanitize_error(
2486                 serr, "Could not open the requested SVN filesystem",
2487                 HTTP_INTERNAL_SERVER_ERROR, r);
2488         }
2489 
2490       /* Cache the open repos for the next request on this connection */
2491       apr_pool_userdata_set(repos->repos, repos_key,
2492                             NULL, r->connection->pool);
2493 
2494       /* Store the capabilities of the current connection, making sure
2495          to use the same pool repos->repos itself was created in. */
2496       serr = svn_repos_remember_client_capabilities
2497         (repos->repos, capabilities_as_list(repos->client_capabilities,
2498                                             r->connection->pool));
2499       if (serr != NULL)
2500         {
2501           return dav_svn__sanitize_error(serr,
2502                                          "Error storing client capabilities "
2503                                          "in repos object",
2504                                          HTTP_INTERNAL_SERVER_ERROR, r);
2505         }
2506 
2507       /* Configure hook script environment variables. */
2508       serr = svn_repos_hooks_setenv(repos->repos, dav_svn__get_hooks_env(r),
2509                                     r->pool);
2510       if (serr)
2511         return dav_svn__sanitize_error(serr,
2512                                        "Error settings hooks environment",
2513                                        HTTP_INTERNAL_SERVER_ERROR, r);
2514     }
2515 
2516   /* cache the filesystem object */
2517   repos->fs = svn_repos_fs(repos->repos);
2518 
2519   /* capture warnings during cleanup of the FS */
2520   svn_fs_set_warning_func(repos->fs, log_warning_req, r);
2521 
2522   /* We must degrade the logging context when the request is freed. */
2523   cleanup_req_logging_baton =
2524     apr_pcalloc(r->pool, sizeof(*cleanup_req_logging_baton));
2525   cleanup_req_logging_baton->fs = repos->fs;
2526   cleanup_req_logging_baton->connection = r->connection;
2527   apr_pool_pre_cleanup_register(r->pool, cleanup_req_logging_baton,
2528                                 cleanup_req_logging);
2529 
2530   /* if an authenticated username is present, attach it to the FS */
2531   if (r->user)
2532     {
2533       svn_fs_access_t *access_ctx;
2534 
2535       /* The fs is cached in connection->pool, but the fs access
2536          context lives in r->pool.  Because the username or token
2537          could change on each request, we need to make sure that the
2538          fs points to a NULL access context after the request is gone. */
2539       cleanup_baton = apr_pcalloc(r->pool, sizeof(*cleanup_baton));
2540       cleanup_baton->pool = r->pool;
2541       cleanup_baton->fs = repos->fs;
2542       apr_pool_cleanup_register(r->pool, cleanup_baton, cleanup_fs_access,
2543                                 apr_pool_cleanup_null);
2544 
2545       /* Create an access context based on the authenticated username. */
2546       serr = svn_fs_create_access(&access_ctx, r->user, r->pool);
2547       if (serr)
2548         {
2549           return dav_svn__sanitize_error(serr,
2550                                          "Could not create fs access context",
2551                                          HTTP_INTERNAL_SERVER_ERROR, r);
2552         }
2553 
2554       /* Attach the access context to the fs. */
2555       serr = svn_fs_set_access(repos->fs, access_ctx);
2556       if (serr)
2557         {
2558           return dav_svn__sanitize_error(serr, "Could not attach access "
2559                                          "context to fs",
2560                                          HTTP_INTERNAL_SERVER_ERROR, r);
2561         }
2562     }
2563 
2564   /* Look for locktokens in the "If:" request header. */
2565   err = dav_get_locktoken_list(r, &ltl);
2566 
2567   /* dav_get_locktoken_list claims to return a NULL list when no
2568      locktokens are present.  But it actually throws this error
2569      instead!  So we're deliberately trapping/ignoring it.
2570 
2571      This is a workaround for a bug in mod_dav.  Remove this when the
2572      bug is fixed in mod_dav.  See Subversion Issue #2248 */
2573   if (err && (err->error_id != DAV_ERR_IF_ABSENT))
2574     return err;
2575 
2576   /* If one or more locktokens are present in the header, push them
2577      into the filesystem access context. */
2578   if (ltl)
2579     {
2580       svn_fs_access_t *access_ctx;
2581       dav_locktoken_list *list = ltl;
2582 
2583       serr = svn_fs_get_access(&access_ctx, repos->fs);
2584       if (serr || !access_ctx)
2585         {
2586           if (serr == NULL)
2587             serr = svn_error_create(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL, NULL);
2588           return dav_svn__sanitize_error(serr, "Lock token is in request, "
2589                                          "but no user name",
2590                                          HTTP_BAD_REQUEST, r);
2591         }
2592 
2593       do {
2594         /* Note the path/lock pairs are only for lock token checking
2595            in access, and the relative path is not actually accurate
2596            as it contains the !svn bits.  However, we're using only
2597            the tokens anyway (for access control). */
2598 
2599         serr = svn_fs_access_add_lock_token2(access_ctx, relative,
2600                                              list->locktoken->uuid_str);
2601 
2602         if (serr)
2603           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2604                                       "Error pushing token into filesystem.",
2605                                       r->pool);
2606         list = list->next;
2607 
2608       } while (list);
2609     }
2610 
2611 
2612   /* Figure out the type of the resource. Note that we have a PARSE step
2613      which is separate from a PREP step. This is because the PARSE can
2614      map multiple URLs to the same resource type. The PREP operates on
2615      the type of the resource. */
2616 
2617   /* skip over the leading "/" in the relative URI */
2618   if (parse_uri(comb, relative + 1, label, use_checked_in))
2619     goto malformed_URI;
2620 
2621   /* Check for a query string on a regular-type resource; this allows
2622      us to discover and parse  a "universal" rev-path URI of the form
2623      "path?[r=REV][&p=PEGREV]" */
2624   if ((comb->res.type == DAV_RESOURCE_TYPE_REGULAR)
2625       && (r->parsed_uri.query != NULL)
2626       && ((err = parse_querystring(r, r->parsed_uri.query, comb, r->pool))))
2627     return err;
2628 
2629 #ifdef SVN_DEBUG
2630   if (comb->res.type == DAV_RESOURCE_TYPE_UNKNOWN)
2631     {
2632       /* Unknown URI. Return NULL to indicate "no resource" */
2633       DBG0("DESIGN FAILURE: should not be UNKNOWN at this point");
2634       *resource = NULL;
2635       return NULL;
2636     }
2637 #endif
2638 
2639   /* prepare the resource for operation */
2640   if ((err = prep_resource(comb)) != NULL)
2641     return err;
2642 
2643   /* a GET request for a REGULAR collection resource MUST have a trailing
2644      slash. Redirect to include one if it does not. */
2645   if (comb->res.collection && comb->res.type == DAV_RESOURCE_TYPE_REGULAR
2646       && !had_slash && r->method_number == M_GET)
2647     {
2648       const char *new_path = apr_pstrcat(r->pool,
2649                                          ap_escape_uri(r->pool, r->uri),
2650                                          "/",
2651                                          r->args ? "?" : "",
2652                                          r->args ? r->args : "",
2653                                          SVN_VA_NULL);
2654       apr_table_setn(r->headers_out, "Location",
2655                      ap_construct_url(r->pool, new_path, r));
2656       return dav_svn__new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0, 0,
2657                                 "Requests for a collection must have a "
2658                                 "trailing slash on the URI.");
2659     }
2660 
2661   /* HTTPv2: for write-requests, out-of-dateness checks happen via
2662      Base-Version header rather via CHECKOUT requests.
2663 
2664      If a Base-Version header is present on a write request, we need
2665      to do the out-of-dateness check *here*, rather than in other
2666      dav-provider vtable funcs.  That's because a number of mod_dav
2667      methods annoyingly trap and genericize our error messages.  */
2668   if ((err = do_out_of_date_check(comb, r)) != NULL)
2669     return err;
2670 
2671   *resource = &comb->res;
2672   return NULL;
2673 
2674  malformed_URI:
2675   /* A malformed URI error occurs when a URI indicates the "special" area,
2676      yet it has an improper construction. Generally, this is because some
2677      doofus typed it in manually or has a buggy client. */
2678   /* ### pick something other than HTTP_INTERNAL_SERVER_ERROR */
2679   /* ### are SVN_ERR_APMOD codes within the right numeric space? */
2680   return dav_svn__new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
2681                             SVN_ERR_APMOD_MALFORMED_URI, 0,
2682                             "The URI indicated a resource within Subversion's "
2683                             "special resource area, but does not exist. This "
2684                             "is generally caused by a problem in the client "
2685                             "software.");
2686 }
2687 
2688 
2689 /* Helper func:  return the parent of PATH, allocated in POOL.  If
2690    IS_URLPATH is set, PATH is a urlpath; otherwise, it's either a
2691    relpath or an fspath. */
2692 static const char *
get_parent_path(const char * path,svn_boolean_t is_urlpath,apr_pool_t * pool)2693 get_parent_path(const char *path,
2694                 svn_boolean_t is_urlpath,
2695                 apr_pool_t *pool)
2696 {
2697   if (*path != '\0') /* not an empty string */
2698     {
2699       if (is_urlpath)
2700         return svn_urlpath__dirname(path, pool);
2701       else
2702         return svn_fspath__dirname(path, pool);
2703     }
2704 
2705   return path;
2706 }
2707 
2708 
2709 static dav_error *
get_parent_resource(const dav_resource * resource,dav_resource ** parent_resource)2710 get_parent_resource(const dav_resource *resource,
2711                     dav_resource **parent_resource)
2712 {
2713   dav_resource *parent;
2714   dav_resource_private *parentinfo;
2715   svn_stringbuf_t *path = resource->info->uri_path;
2716 
2717   /* Initialize the return value. */
2718   *parent_resource = NULL;
2719 
2720   /* The root of the repository has no parent. */
2721   if (path->len == 1 && *path->data == '/')
2722     return NULL;
2723 
2724   /* If possible, create a parent based on the type of RESOURCE. */
2725   switch (resource->type)
2726     {
2727     case DAV_RESOURCE_TYPE_REGULAR:
2728 
2729       parent = apr_pcalloc(resource->pool, sizeof(*parent));
2730       parentinfo  = apr_pcalloc(resource->pool, sizeof(*parentinfo));
2731 
2732       parent->type = DAV_RESOURCE_TYPE_REGULAR;
2733       parent->exists = 1;
2734       parent->collection = 1;
2735       parent->versioned = 1;
2736       parent->hooks = resource->hooks;
2737       parent->pool = resource->pool;
2738       parent->uri = get_parent_path(svn_urlpath__canonicalize(resource->uri,
2739                                                               resource->pool),
2740                                     TRUE, resource->pool);
2741       parent->info = parentinfo;
2742 
2743       parentinfo->uri_path =
2744         svn_stringbuf_create(
2745                get_parent_path(
2746                    svn_urlpath__canonicalize(resource->info->uri_path->data,
2747                                             resource->pool),
2748                    TRUE, resource->pool),
2749                resource->pool);
2750       parentinfo->repos = resource->info->repos;
2751       parentinfo->root = resource->info->root;
2752       parentinfo->r = resource->info->r;
2753       parentinfo->svn_client_options = resource->info->svn_client_options;
2754       parentinfo->repos_path = get_parent_path(resource->info->repos_path,
2755                                                FALSE, resource->pool);
2756 
2757       *parent_resource = parent;
2758       break;
2759 
2760     case DAV_RESOURCE_TYPE_WORKING:
2761       /* The "/" occurring within the URL of working resources is part of
2762          its identifier; it does not establish parent resource relationships.
2763          All working resources have the same parent, which is:
2764          http://host.name/path2repos/$svn/wrk/
2765       */
2766       *parent_resource =
2767         create_private_resource(resource, DAV_SVN_RESTYPE_WRK_COLLECTION);
2768       break;
2769 
2770     case DAV_RESOURCE_TYPE_ACTIVITY:
2771       *parent_resource =
2772         create_private_resource(resource, DAV_SVN_RESTYPE_ACT_COLLECTION);
2773       break;
2774 
2775     case DAV_RESOURCE_TYPE_PRIVATE:
2776       if ((resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION)
2777           || (resource->info->restype == DAV_SVN_RESTYPE_REV_COLLECTION))
2778         *parent_resource =
2779           create_private_resource(resource, resource->info->restype);
2780       /* ### FIXME:  Need parents for other private resource types. */
2781       break;
2782 
2783     default:
2784       /* ### FIXME:  Need parents for other resource types. */
2785       break;
2786     }
2787 
2788   /* If we didn't create parent resource above, complain. */
2789   if (! *parent_resource)
2790     return dav_svn__new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
2791                               apr_psprintf(resource->pool,
2792                                            "get_parent_resource was called for "
2793                                            "%s (type %d)",
2794                                            resource->uri, resource->type));
2795 
2796   return NULL;
2797 }
2798 
2799 
2800 /* does RES2 live in the same repository as RES1? */
2801 static int
is_our_resource(const dav_resource * res1,const dav_resource * res2)2802 is_our_resource(const dav_resource *res1, const dav_resource *res2)
2803 {
2804   if (res1->hooks != res2->hooks
2805       || strcmp(res1->info->repos->fs_path, res2->info->repos->fs_path) != 0)
2806     {
2807       /* a different provider, or a different FS repository */
2808       return 0;
2809     }
2810 
2811   /* coalesce the repository */
2812   if (res1->info->repos != res2->info->repos)
2813     {
2814       /* ### might be nice to have a pool which we can clear to toss
2815          ### out the old, redundant repos/fs.  */
2816 
2817       /* have res2 point to res1's filesystem */
2818       res2->info->repos = res1->info->repos;
2819 
2820       /* res2's fs_root object is now invalid.  regenerate it using
2821          the now-shared filesystem. */
2822       if (res2->info->root.txn_name)
2823         {
2824           /* reopen the txn by name */
2825           svn_error_clear(svn_fs_open_txn(&(res2->info->root.txn),
2826                                           res2->info->repos->fs,
2827                                           res2->info->root.txn_name,
2828                                           res2->info->repos->pool));
2829 
2830           /* regenerate the txn "root" object */
2831           svn_error_clear(svn_fs_txn_root(&(res2->info->root.root),
2832                                           res2->info->root.txn,
2833                                           res2->info->repos->pool));
2834         }
2835       else if (res2->info->root.rev)
2836         {
2837           /* default:  regenerate the revision "root" object */
2838           svn_error_clear(svn_fs_revision_root(&(res2->info->root.root),
2839                                                res2->info->repos->fs,
2840                                                res2->info->root.rev,
2841                                                res2->info->repos->pool));
2842         }
2843     }
2844 
2845   return 1;
2846 }
2847 
2848 
2849 static int
is_same_resource(const dav_resource * res1,const dav_resource * res2)2850 is_same_resource(const dav_resource *res1, const dav_resource *res2)
2851 {
2852   if (!is_our_resource(res1, res2))
2853     return 0;
2854 
2855   /* ### what if the same resource were reached via two URIs? */
2856 
2857   return svn_stringbuf_compare(res1->info->uri_path, res2->info->uri_path);
2858 }
2859 
2860 
2861 static int
is_parent_resource(const dav_resource * res1,const dav_resource * res2)2862 is_parent_resource(const dav_resource *res1, const dav_resource *res2)
2863 {
2864   apr_size_t len1 = strlen(res1->info->uri_path->data);
2865   apr_size_t len2;
2866 
2867   if (!is_our_resource(res1, res2))
2868     return 0;
2869 
2870   /* ### what if a resource were reached via two URIs? we ought to define
2871      ### parent/child relations for resources independent of URIs.
2872      ### i.e. define a "canonical" location for each resource, then return
2873      ### the parent based on that location. */
2874 
2875   /* res2 is one of our resources, we can use its ->info ptr */
2876   len2 = strlen(res2->info->uri_path->data);
2877 
2878   return (len2 > len1
2879           && memcmp(res1->info->uri_path->data, res2->info->uri_path->data,
2880                     len1) == 0
2881           && res2->info->uri_path->data[len1] == '/');
2882 }
2883 
2884 
2885 static dav_error *
open_stream(const dav_resource * resource,dav_stream_mode mode,dav_stream ** stream)2886 open_stream(const dav_resource *resource,
2887             dav_stream_mode mode,
2888             dav_stream **stream)
2889 {
2890   svn_node_kind_t kind;
2891   dav_error *derr;
2892   svn_error_t *serr;
2893 
2894   if (mode == DAV_MODE_WRITE_TRUNC || mode == DAV_MODE_WRITE_SEEKABLE)
2895     {
2896       if (resource->type != DAV_RESOURCE_TYPE_WORKING)
2897         {
2898           return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED,
2899                                     0, 0,
2900                                     "Resource body changes may only be made to "
2901                                     "working resources (at this time).");
2902         }
2903       if (!resource->info->root.root)
2904         {
2905           return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED,
2906                                     0, 0,
2907                                     "Resource body changes may only be made to "
2908                                     "checked-out resources (at this time).");
2909         }
2910     }
2911 
2912   /* ### TODO:  Can we support range writes someday? */
2913   if (mode == DAV_MODE_WRITE_SEEKABLE)
2914     {
2915       return dav_svn__new_error(resource->pool, HTTP_NOT_IMPLEMENTED, 0, 0,
2916                                 "Resource body writes cannot use ranges "
2917                                 "(at this time).");
2918     }
2919 
2920   /* start building the stream structure */
2921   *stream = apr_pcalloc(resource->pool, sizeof(**stream));
2922   (*stream)->res = resource;
2923 
2924   derr = fs_check_path(&kind, resource->info->root.root,
2925                        resource->info->repos_path, resource->pool);
2926   if (derr != NULL)
2927     return derr;
2928 
2929   if (kind == svn_node_none) /* No existing file. */
2930     {
2931       serr = svn_fs_make_file(resource->info->root.root,
2932                               resource->info->repos_path,
2933                               resource->pool);
2934 
2935       if (serr != NULL)
2936         {
2937           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2938                                       "Could not create file within the "
2939                                       "repository.",
2940                                       resource->pool);
2941         }
2942     }
2943 
2944   /* if the working-resource was auto-checked-out (i.e. came into
2945      existence through the autoversioning feature), then possibly set
2946      the svn:mime-type property based on whatever value mod_mime has
2947      chosen.  If the path already has an svn:mime-type property
2948      set, do nothing. */
2949   if (resource->info->auto_checked_out
2950       && resource->info->r->content_type)
2951     {
2952       svn_string_t *mime_type;
2953 
2954       serr = svn_fs_node_prop(&mime_type,
2955                               resource->info->root.root,
2956                               resource->info->repos_path,
2957                               SVN_PROP_MIME_TYPE,
2958                               resource->pool);
2959 
2960       if (serr != NULL)
2961         {
2962           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2963                                       "Error fetching mime-type property.",
2964                                       resource->pool);
2965         }
2966 
2967       if (!mime_type)
2968         {
2969           serr = svn_fs_change_node_prop(resource->info->root.root,
2970                                          resource->info->repos_path,
2971                                          SVN_PROP_MIME_TYPE,
2972                                          svn_string_create
2973                                              (resource->info->r->content_type,
2974                                               resource->pool),
2975                                          resource->pool);
2976           if (serr != NULL)
2977             {
2978               return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2979                                           "Could not set mime-type property.",
2980                                           resource->pool);
2981             }
2982         }
2983     }
2984 
2985   serr = svn_fs_apply_textdelta(&(*stream)->delta_handler,
2986                                 &(*stream)->delta_baton,
2987                                 resource->info->root.root,
2988                                 resource->info->repos_path,
2989                                 resource->info->base_checksum,
2990                                 resource->info->result_checksum,
2991                                 resource->pool);
2992 
2993   if (serr != NULL)
2994     {
2995       return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2996                                   "Could not prepare to write the file",
2997                                   resource->pool);
2998     }
2999 
3000   /* if the incoming data is an SVNDIFF, then create a stream that
3001      will process the data into windows and invoke the FS window handler
3002      when a window is ready. */
3003   /* ### we need a better way to check the content-type! this is bogus
3004      ### because we're effectively looking at the request_rec. doubly
3005      ### bogus because this means you cannot open arbitrary streams and
3006      ### feed them content (the type is always tied to a request_rec).
3007      ### probably ought to pass the type to open_stream */
3008   if (resource->info->is_svndiff)
3009     {
3010       (*stream)->wstream =
3011         svn_txdelta_parse_svndiff((*stream)->delta_handler,
3012                                   (*stream)->delta_baton,
3013                                   TRUE,
3014                                   resource->pool);
3015     }
3016 
3017   return NULL;
3018 }
3019 
3020 
3021 static dav_error *
close_stream(dav_stream * stream,int commit)3022 close_stream(dav_stream *stream, int commit)
3023 {
3024   svn_error_t *serr;
3025   apr_pool_t *pool = stream->res->pool;
3026 
3027   if (stream->rstream != NULL)
3028     {
3029       serr = svn_stream_close(stream->rstream);
3030       if (serr)
3031         return dav_svn__convert_err
3032           (serr, HTTP_INTERNAL_SERVER_ERROR,
3033            "mod_dav_svn close_stream: error closing read stream",
3034            pool);
3035     }
3036 
3037   /* if we have a write-stream, then closing it also takes care of the
3038      handler (so make sure not to send a NULL to it, too) */
3039   if (stream->wstream != NULL)
3040     {
3041       serr = svn_stream_close(stream->wstream);
3042       if (serr)
3043         return dav_svn__convert_err
3044           (serr, HTTP_INTERNAL_SERVER_ERROR,
3045            "mod_dav_svn close_stream: error closing write stream",
3046            pool);
3047     }
3048   else if (stream->delta_handler != NULL)
3049     {
3050       serr = (*stream->delta_handler)(NULL, stream->delta_baton);
3051       if (serr)
3052         return dav_svn__convert_err
3053           (serr, HTTP_INTERNAL_SERVER_ERROR,
3054            "mod_dav_svn close_stream: error sending final (null) delta window",
3055            pool);
3056     }
3057 
3058   if (stream->wstream != NULL || stream->delta_handler != NULL)
3059     {
3060       request_rec *r = stream->res->info->r;
3061       svn_checksum_t *checksum;
3062 
3063       serr = svn_fs_file_checksum(&checksum, svn_checksum_md5,
3064                                   stream->res->info->root.root,
3065                                   stream->res->info->repos_path,
3066                                   FALSE, pool);
3067       if (serr)
3068         return dav_svn__convert_err
3069           (serr, HTTP_INTERNAL_SERVER_ERROR,
3070             "mod_dav_svn close_stream: error getting file checksum",
3071             pool);
3072 
3073       if (checksum)
3074         apr_table_set(r->headers_out, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
3075                       svn_checksum_to_cstring(checksum, pool));
3076     }
3077 
3078   return NULL;
3079 }
3080 
3081 
3082 static dav_error *
write_stream(dav_stream * stream,const void * buf,apr_size_t bufsize)3083 write_stream(dav_stream *stream, const void *buf, apr_size_t bufsize)
3084 {
3085   svn_error_t *serr;
3086   apr_pool_t *pool = stream->res->pool;
3087 
3088   if (stream->wstream != NULL)
3089     {
3090       serr = svn_stream_write(stream->wstream, buf, &bufsize);
3091       /* ### would the returned bufsize ever not match the requested amt? */
3092     }
3093   else
3094     {
3095       svn_txdelta_window_t window = { 0 };
3096       svn_txdelta_op_t op;
3097       svn_string_t data;
3098 
3099       data.data = buf;
3100       data.len = bufsize;
3101 
3102       op.action_code = svn_txdelta_new;
3103       op.offset = 0;
3104       op.length = bufsize;
3105 
3106       window.tview_len = bufsize;   /* result will be this long */
3107       window.num_ops = 1;
3108       window.ops = &op;
3109       window.new_data = &data;
3110 
3111       serr = (*stream->delta_handler)(&window, stream->delta_baton);
3112     }
3113 
3114   if (serr)
3115     {
3116       return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3117                                   "could not write the file contents",
3118                                   pool);
3119     }
3120   return NULL;
3121 }
3122 
3123 
3124 static dav_error *
seek_stream(dav_stream * stream,apr_off_t abs_position)3125 seek_stream(dav_stream *stream, apr_off_t abs_position)
3126 {
3127   /* ### fill this in */
3128 
3129   return dav_svn__new_error(stream->res->pool, HTTP_NOT_IMPLEMENTED, 0, 0,
3130                             "Resource body read/write cannot use ranges "
3131                             "(at this time)");
3132 }
3133 
3134 /* Returns whether the DAV resource lacks potential for generation of
3135    an ETag (defined as any of the following):
3136    - it doesn't exist
3137    - the resource type isn't REGULAR or VERSION
3138    - the resource is a Baseline */
3139 #define RESOURCE_LACKS_ETAG_POTENTIAL(resource) \
3140   (!resource->exists \
3141    || (resource->type != DAV_RESOURCE_TYPE_REGULAR \
3142        && resource->type != DAV_RESOURCE_TYPE_VERSION) \
3143    || (resource->type == DAV_RESOURCE_TYPE_VERSION \
3144        && resource->baselined))
3145 
3146 
3147 const char *
dav_svn__getetag(const dav_resource * resource,apr_pool_t * pool)3148 dav_svn__getetag(const dav_resource *resource, apr_pool_t *pool)
3149 {
3150   svn_error_t *serr;
3151   svn_revnum_t created_rev;
3152 
3153   if (RESOURCE_LACKS_ETAG_POTENTIAL(resource))
3154     return "";
3155 
3156   /* ### what kind of etag to return for activities, etc.? */
3157 
3158   if ((serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root,
3159                                       resource->info->repos_path,
3160                                       pool)))
3161     {
3162       /* ### what to do? */
3163       svn_error_clear(serr);
3164       return "";
3165     }
3166 
3167   /* Use the "weak" format of the etag for collections because our GET
3168      requests on collections include dynamic data (the HEAD revision,
3169      the build version of Subversion, etc.). */
3170   return apr_psprintf(pool, "%s\"%ld/%s\"",
3171                       resource->collection ? "W/" : "",
3172                       created_rev,
3173                       apr_xml_quote_string(pool,
3174                                            resource->info->repos_path, 1));
3175 }
3176 
3177 
3178 /* Since dav_svn__getetag() takes a pool argument, this wrapper is for
3179    the mod_dav hooks vtable entry, which does not. */
3180 static const char *
getetag_pathetic(const dav_resource * resource)3181 getetag_pathetic(const dav_resource *resource)
3182 {
3183   return dav_svn__getetag(resource, resource->pool);
3184 }
3185 
3186 /* Helper for set_headers(). Returns TRUE if request R to RESOURCE can be
3187  * cached. Returns FALSe otherwise. */
3188 static svn_boolean_t
is_cacheable(request_rec * r,const dav_resource * resource)3189 is_cacheable(request_rec *r, const dav_resource *resource)
3190 {
3191   /* Non-idempotent resource cannot be cached because actual
3192      target could change when youngest revision or transacation
3193      will change. */
3194   if (!resource->info->idempotent)
3195     return FALSE;
3196 
3197   /* Our GET requests on collections include dynamic data (the
3198      HEAD revision, the build version of Subversion, etc.).
3199      Directory content is also subject of authz filtering.*/
3200   if (resource->collection)
3201     return FALSE;
3202 
3203   if (resource->type == DAV_RESOURCE_TYPE_REGULAR ||
3204       resource->type == DAV_RESOURCE_TYPE_VERSION)
3205       return TRUE;
3206   else
3207       return FALSE;
3208 }
3209 
3210 static dav_error *
set_headers(request_rec * r,const dav_resource * resource)3211 set_headers(request_rec *r, const dav_resource *resource)
3212 {
3213   svn_error_t *serr;
3214   svn_filesize_t length;
3215   const char *mimetype = NULL;
3216 
3217   /* As version resources don't change, encourage caching. */
3218   if (is_cacheable(r, resource))
3219     /* Cache resource for one week (specified in seconds). */
3220     apr_table_setn(r->headers_out, "Cache-Control", "max-age=604800");
3221   else
3222     apr_table_setn(r->headers_out, "Cache-Control", "max-age=0");
3223 
3224   if (!resource->exists)
3225     return NULL;
3226 
3227   if ((resource->type == DAV_RESOURCE_TYPE_REGULAR)
3228       && resource->info->is_public_uri)
3229     {
3230       /* Include Last-Modified header for 'external' GET or HEAD requests
3231          (i.e. requests to URI's not under /!svn), to support usage of an
3232          SVN server as a file server, where the client needs timestamps
3233          for instance to use as "last modification time" of files on disk. */
3234 
3235       svn_revnum_t created_rev;
3236       svn_string_t *date_str = NULL;
3237 
3238       serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root,
3239                                      resource->info->repos_path,
3240                                      resource->pool);
3241 
3242       if (serr == NULL)
3243         {
3244           serr = svn_fs_revision_prop2(&date_str, resource->info->repos->fs,
3245                                        created_rev, SVN_PROP_REVISION_DATE,
3246                                        TRUE, resource->pool, resource->pool);
3247         }
3248 
3249       if ((serr == NULL) && date_str && date_str->data)
3250         {
3251           apr_time_t mtime;
3252           serr = svn_time_from_cstring(&mtime, date_str->data, resource->pool);
3253 
3254           if (serr == NULL)
3255             {
3256               /* Note the modification time for the requested resource, and
3257                  include the Last-Modified header in the response. */
3258               ap_update_mtime(r, mtime);
3259               ap_set_last_modified(r);
3260             }
3261         }
3262 
3263       svn_error_clear(serr);
3264     }
3265 
3266   /* generate our etag and place it into the output */
3267   apr_table_setn(r->headers_out, "ETag",
3268                  dav_svn__getetag(resource, resource->pool));
3269 
3270   /* we accept byte-ranges */
3271   apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
3272 
3273   /* For a directory, we will send text/html or text/xml. If we have a delta
3274      base, then we will always be generating an svndiff.  Otherwise,
3275      we need to fetch the appropriate MIME type from the resource's
3276      properties (and use text/plain if it isn't there). */
3277   if (resource->collection)
3278     {
3279       if (resource->info->repos->xslt_uri)
3280         mimetype = "text/xml";
3281       else
3282         mimetype = "text/html; charset=UTF-8";
3283     }
3284   else if (resource->info->delta_base != NULL)
3285     {
3286       dav_svn__uri_info info;
3287 
3288       /* First order of business is to parse it. */
3289       serr = dav_svn__simple_parse_uri(&info, resource,
3290                                        resource->info->delta_base,
3291                                        resource->pool);
3292 
3293       /* If we successfully parse the base URL, then send an svndiff. */
3294       if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM))
3295         {
3296           mimetype = SVN_SVNDIFF_MIME_TYPE;
3297 
3298           /* Note the base that this svndiff is based on, and tell any
3299              intermediate caching proxies that this header is
3300              significant.  */
3301           apr_table_setn(r->headers_out, "Vary", SVN_DAV_DELTA_BASE_HEADER);
3302           apr_table_setn(r->headers_out, SVN_DAV_DELTA_BASE_HEADER,
3303                          resource->info->delta_base);
3304         }
3305       svn_error_clear(serr);
3306     }
3307 
3308   if ((mimetype == NULL)
3309       && ((resource->type == DAV_RESOURCE_TYPE_VERSION)
3310           || (resource->type == DAV_RESOURCE_TYPE_REGULAR))
3311       && (resource->info->repos_path != NULL))
3312     {
3313       svn_string_t *value;
3314 
3315       serr = svn_fs_node_prop(&value,
3316                               resource->info->root.root,
3317                               resource->info->repos_path,
3318                               SVN_PROP_MIME_TYPE,
3319                               resource->pool);
3320       if (serr != NULL)
3321         return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3322                                     "could not fetch the resource's MIME type",
3323                                     resource->pool);
3324 
3325       if (value)
3326         mimetype = value->data;
3327       else if ((! resource->info->repos->is_svn_client)
3328                && r->content_type)
3329         mimetype = r->content_type;
3330 
3331       /* If we found a MIME type, we'll make sure it's Subversion-friendly. */
3332       if (mimetype)
3333         {
3334           if ((serr = svn_mime_type_validate(mimetype, resource->pool)))
3335             {
3336               /* Probably serr->apr == SVN_ERR_BAD_MIME_TYPE, but there's
3337                  no point even checking.  No matter what the error is, we
3338                  can't use this MIME type.  */
3339               svn_error_clear(serr);
3340               mimetype = NULL;
3341             }
3342         }
3343 
3344       /* We've found/calculated/validated no usable MIME type.  We
3345          could fall back to "application/octet-stream" (aka "bag o'
3346          bytes"), but many browsers have grown to expect "text/plain"
3347          to mean "*shrug*", and kick off their own MIME type detection
3348          routines when they see it.  So we'll use "text/plain".
3349 
3350          ### Why not just avoid sending a Content-type at all?  Is
3351          ### that just bad form for HTTP?  */
3352       if (! mimetype)
3353         mimetype = "text/plain";
3354 
3355 
3356       /* if we aren't sending a diff and aren't expanding keywords,
3357          then we know the exact length of the file, so set up the
3358          Content-Length header. */
3359       if (! resource->info->keyword_subst)
3360         {
3361           serr = svn_fs_file_length(&length,
3362                                     resource->info->root.root,
3363                                     resource->info->repos_path,
3364                                     resource->pool);
3365           if (serr != NULL)
3366             {
3367               return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3368                                           "could not fetch the resource length",
3369                                           resource->pool);
3370             }
3371           ap_set_content_length(r, (apr_off_t) length);
3372         }
3373     }
3374 
3375   /* set the discovered MIME type */
3376   /* ### it would be best to do this during the findct phase... */
3377   ap_set_content_type(r, mimetype);
3378 
3379   return NULL;
3380 }
3381 
3382 
3383 typedef struct diff_ctx_t {
3384   dav_svn__output *output;
3385   apr_bucket_brigade *bb;
3386 } diff_ctx_t;
3387 
3388 
3389 static svn_error_t *  __attribute__((warn_unused_result))
write_to_filter(void * baton,const char * buffer,apr_size_t * len)3390 write_to_filter(void *baton, const char *buffer, apr_size_t *len)
3391 {
3392   diff_ctx_t *dc = baton;
3393 
3394   /* take the current data and shove it into the filter */
3395   SVN_ERR(dav_svn__brigade_write(dc->bb, dc->output, buffer, *len));
3396 
3397   return SVN_NO_ERROR;
3398 }
3399 
3400 
3401 static svn_error_t *  __attribute__((warn_unused_result))
close_filter(void * baton)3402 close_filter(void *baton)
3403 {
3404   diff_ctx_t *dc = baton;
3405   apr_bucket *bkt;
3406 
3407   /* done with the file. write an EOS bucket now. */
3408   bkt = apr_bucket_eos_create(dav_svn__output_get_bucket_alloc(dc->output));
3409   APR_BRIGADE_INSERT_TAIL(dc->bb, bkt);
3410   SVN_ERR(dav_svn__output_pass_brigade(dc->output, dc->bb));
3411 
3412   return SVN_NO_ERROR;
3413 }
3414 
3415 
3416 static svn_error_t *
emit_collection_head(const dav_resource * resource,apr_bucket_brigade * bb,dav_svn__output * output,svn_boolean_t gen_html,apr_pool_t * pool)3417 emit_collection_head(const dav_resource *resource,
3418                      apr_bucket_brigade *bb,
3419                      dav_svn__output *output,
3420                      svn_boolean_t gen_html,
3421                      apr_pool_t *pool)
3422 {
3423   /* XML schema for the directory index if xslt_uri is set:
3424 
3425      <?xml version="1.0"?>
3426      <?xml-stylesheet type="text/xsl" href="[info->repos->xslt_uri]"?> */
3427   static const char xml_index_dtd[] =
3428     "<!DOCTYPE svn [\n"
3429     "  <!ELEMENT svn   (index)>\n"
3430     "  <!ATTLIST svn   version CDATA #REQUIRED\n"
3431     "                  href    CDATA #REQUIRED>\n"
3432     "  <!ELEMENT index (updir?, (file | dir)*)>\n"
3433     "  <!ATTLIST index name    CDATA #IMPLIED\n"
3434     "                  path    CDATA #IMPLIED\n"
3435     "                  rev     CDATA #IMPLIED\n"
3436     "                  base    CDATA #IMPLIED>\n"
3437     "  <!ELEMENT updir EMPTY>\n"
3438     "  <!ATTLIST updir href    CDATA #REQUIRED>\n"
3439     "  <!ELEMENT file  EMPTY>\n"
3440     "  <!ATTLIST file  name    CDATA #REQUIRED\n"
3441     "                  href    CDATA #REQUIRED>\n"
3442     "  <!ELEMENT dir   EMPTY>\n"
3443     "  <!ATTLIST dir   name    CDATA #REQUIRED\n"
3444     "                  href    CDATA #REQUIRED>\n"
3445     "]>\n";
3446 
3447   if (gen_html)
3448     {
3449       const char *title;
3450       if (resource->info->repos_path == NULL)
3451         title = "unknown location";
3452       else
3453         title = resource->info->repos_path;
3454 
3455       if (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
3456         {
3457           if (SVN_IS_VALID_REVNUM(resource->info->root.rev))
3458             title = apr_psprintf(pool,
3459                                  "Revision %ld: %s",
3460                                  resource->info->root.rev, title);
3461           if (resource->info->repos->repo_basename)
3462             title = apr_psprintf(pool, "%s - %s",
3463                                  resource->info->repos->repo_basename,
3464                                  title);
3465           if (resource->info->repos->repo_name)
3466             title = apr_psprintf(pool, "%s: %s",
3467                                  resource->info->repos->repo_name,
3468                                  title);
3469         }
3470 
3471       SVN_ERR(dav_svn__brigade_printf(bb, output,
3472                                       "<html><head><title>%s</title></head>\n"
3473                                       "<body>\n <h2>%s</h2>\n <ul>\n",
3474                                       title, title));
3475     }
3476   else
3477     {
3478       const char *name = resource->info->repos->repo_name;
3479       const char *href = resource->info->repos_path;
3480       const char *base = resource->info->repos->repo_basename;
3481 
3482       SVN_ERR(dav_svn__brigade_puts(bb, output, "<?xml version=\"1.0\"?>\n"));
3483       SVN_ERR(dav_svn__brigade_printf(bb, output,
3484                                       "<?xml-stylesheet type=\"text/xsl\" "
3485                                       "href=\"%s\"?>\n",
3486                                       resource->info->repos->xslt_uri));
3487       SVN_ERR(dav_svn__brigade_puts(bb, output, xml_index_dtd));
3488       SVN_ERR(dav_svn__brigade_puts(bb, output,
3489                          "<svn version=\"" SVN_VERSION "\"\n"
3490                          "     href=\"http://subversion.apache.org/\">\n"));
3491       SVN_ERR(dav_svn__brigade_puts(bb, output, "  <index"));
3492 
3493       if (name)
3494         SVN_ERR(dav_svn__brigade_printf(bb, output,
3495                                         " name=\"%s\"",
3496                                         apr_xml_quote_string(resource->pool,
3497                                                              name, 1)));
3498       if (SVN_IS_VALID_REVNUM(resource->info->root.rev))
3499         SVN_ERR(dav_svn__brigade_printf(bb, output, " rev=\"%ld\"",
3500                                         resource->info->root.rev));
3501       if (href)
3502         SVN_ERR(dav_svn__brigade_printf(bb, output, " path=\"%s\"",
3503                                         apr_xml_quote_string(resource->pool,
3504                                                              href, 1)));
3505       if (base)
3506         SVN_ERR(dav_svn__brigade_printf(bb, output, " base=\"%s\"", base));
3507 
3508       SVN_ERR(dav_svn__brigade_puts(bb, output, ">\n"));
3509     }
3510 
3511   if ((resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
3512       && resource->info->repos_path
3513       && ((resource->info->repos_path[1] != '\0')
3514           || dav_svn__get_list_parentpath_flag(resource->info->r)))
3515     {
3516       const char *href;
3517       if (resource->info->pegged)
3518         {
3519           href = apr_psprintf(pool, "../?p=%ld", resource->info->root.rev);
3520         }
3521       else
3522         {
3523           href = "../";
3524         }
3525 
3526       if (gen_html)
3527         {
3528           SVN_ERR(dav_svn__brigade_printf(bb, output,
3529                                           "  <li><a href=\"%s\">..</a></li>\n",
3530                                           href));
3531         }
3532       else
3533         {
3534           SVN_ERR(dav_svn__brigade_printf(bb, output,
3535                                           "    <updir href=\"%s\"/>\n",
3536                                           href));
3537         }
3538     }
3539 
3540   return SVN_NO_ERROR;
3541 }
3542 
3543 
3544 static svn_error_t *
emit_collection_entry(const dav_resource * resource,apr_bucket_brigade * bb,dav_svn__output * output,const svn_fs_dirent_t * entry,svn_boolean_t gen_html,apr_pool_t * pool)3545 emit_collection_entry(const dav_resource *resource,
3546                       apr_bucket_brigade *bb,
3547                       dav_svn__output *output,
3548                       const svn_fs_dirent_t *entry,
3549                       svn_boolean_t gen_html,
3550                       apr_pool_t *pool)
3551 {
3552   const char *name = entry->name;
3553   const char *href = name;
3554   svn_boolean_t is_dir = (entry->kind == svn_node_dir);
3555 
3556   /* append a trailing slash onto the name for directories. we NEED
3557      this for the href portion so that the relative reference will
3558      descend properly. for the visible portion, it is just nice. */
3559   /* ### The xml output doesn't like to see a trailing slash on
3560      ### the visible portion, so avoid that. */
3561   if (is_dir)
3562     href = apr_pstrcat(pool, href, "/", SVN_VA_NULL);
3563 
3564   if (gen_html)
3565     name = href;
3566 
3567   /* We quote special characters in both XML and HTML. */
3568   name = apr_xml_quote_string(pool, name, !gen_html);
3569 
3570   /* According to httpd-2.0.54/include/httpd.h, ap_os_escape_path()
3571      behaves differently on different platforms.  It claims to
3572      "convert an OS path to a URL in an OS dependant way".
3573      Nevertheless, there appears to be only one implementation
3574      of the function in httpd, and the code seems completely
3575      platform independent, so we'll assume it's appropriate for
3576      mod_dav_svn to use it to quote outbound paths. */
3577   href = ap_os_escape_path(pool, href, 0);
3578   href = apr_xml_quote_string(pool, href, 1);
3579 
3580   if (gen_html)
3581     {
3582       /* If our directory was access using the public peg-rev
3583          CGI query interface, we'll let its dirents carry that
3584          peg-rev, too. */
3585       if (resource->info->pegged)
3586         {
3587           SVN_ERR(dav_svn__brigade_printf(bb, output,
3588                      "  <li><a href=\"%s?p=%ld\">%s</a></li>\n",
3589                      href, resource->info->root.rev, name));
3590         }
3591       else
3592         {
3593           SVN_ERR(dav_svn__brigade_printf(bb, output,
3594                      "  <li><a href=\"%s\">%s</a></li>\n",
3595                      href, name));
3596         }
3597     }
3598   else
3599     {
3600       const char *const tag = (is_dir ? "dir" : "file");
3601 
3602       /* This is where we could search for props */
3603 
3604       /* If our directory was access using the public peg-rev
3605          CGI query interface, we'll let its dirents carry that
3606          peg-rev, too. */
3607       if (resource->info->pegged)
3608         {
3609           SVN_ERR(dav_svn__brigade_printf(bb, output,
3610                      "    <%s name=\"%s\" href=\"%s?p=%ld\" />\n",
3611                      tag, name, href, resource->info->root.rev));
3612         }
3613       else
3614         {
3615           SVN_ERR(dav_svn__brigade_printf(bb, output,
3616                      "    <%s name=\"%s\" href=\"%s\" />\n",
3617                      tag, name, href));
3618         }
3619     }
3620 
3621   return SVN_NO_ERROR;
3622 }
3623 
3624 
3625 static svn_error_t *
emit_collection_tail(const dav_resource * resource,apr_bucket_brigade * bb,dav_svn__output * output,svn_boolean_t gen_html,apr_pool_t * pool)3626 emit_collection_tail(const dav_resource *resource,
3627                      apr_bucket_brigade *bb,
3628                      dav_svn__output *output,
3629                      svn_boolean_t gen_html,
3630                      apr_pool_t *pool)
3631 {
3632   if (gen_html)
3633     {
3634       if (strcmp(ap_psignature("FOO", resource->info->r), "") != 0)
3635         {
3636           /* Apache's signature generation code didn't eat our prefix.
3637              ServerSignature must be enabled.  Print our version info.
3638 
3639              WARNING: This is a kludge!! ap_psignature() doesn't promise
3640              to return the empty string when ServerSignature is off.  We
3641              know it does by code inspection, but this behavior is subject
3642              to change. (Perhaps we should try to get the Apache folks to
3643              make this promise, though.  Seems harmless/useful enough...)
3644           */
3645           SVN_ERR(dav_svn__brigade_puts(bb, output,
3646                    " </ul>\n <hr noshade><em>Powered by "
3647                    "<a href=\"http://subversion.apache.org/\">"
3648                    "Apache Subversion"
3649                    "</a> version " SVN_VERSION "."
3650                    "</em>\n</body></html>"));
3651         }
3652       else
3653         SVN_ERR(dav_svn__brigade_puts(bb, output, " </ul>\n</body></html>"));
3654     }
3655   else
3656     SVN_ERR(dav_svn__brigade_puts(bb, output, "  </index>\n</svn>\n"));
3657 
3658   return SVN_NO_ERROR;
3659 }
3660 
3661 
3662 static dav_error *
deliver(const dav_resource * resource,ap_filter_t * unused)3663 deliver(const dav_resource *resource, ap_filter_t *unused)
3664 {
3665   svn_error_t *serr;
3666   apr_bucket_brigade *bb;
3667   apr_bucket *bkt;
3668   dav_svn__output *output;
3669 
3670   /* Check resource type */
3671   if (resource->baselined
3672       || (resource->type != DAV_RESOURCE_TYPE_REGULAR
3673           && resource->type != DAV_RESOURCE_TYPE_VERSION
3674           && resource->type != DAV_RESOURCE_TYPE_WORKING
3675           && resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION))
3676     {
3677       return dav_svn__new_error(resource->pool, HTTP_CONFLICT, 0, 0,
3678                                 "Cannot GET this type of resource.");
3679     }
3680 
3681   output = dav_svn__output_create(resource->info->r, resource->pool);
3682 
3683   if (resource->collection)
3684     {
3685       const int gen_html = !resource->info->repos->xslt_uri;
3686       apr_hash_t *entries;
3687       apr_pool_t *iterpool;
3688       apr_array_header_t *sorted;
3689       svn_revnum_t dir_rev = SVN_INVALID_REVNUM;
3690       int i;
3691 
3692       /* <svn version="1.3.0 (dev-build)"
3693               href="http://subversion.apache.org">
3694            <index name="[info->repos->repo_name]"
3695                   path="[info->repos_path]"
3696                   rev="[info->root.rev]">
3697              <file name="foo" href="foo" />
3698              <dir name="bar" href="bar/" />
3699            </index>
3700          </svn> */
3701 
3702 
3703       /* ### TO-DO:  check for a new mod_dav_svn directive here also. */
3704       if (resource->info->restype == DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
3705         {
3706           apr_hash_index_t *hi;
3707           apr_hash_t *dirents;
3708           const char *fs_parent_path =
3709             dav_svn__get_fs_parent_path(resource->info->r);
3710 
3711           serr = svn_io_get_dirents3(&dirents, fs_parent_path, TRUE,
3712                                      resource->pool, resource->pool);
3713           if (serr != NULL)
3714             return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3715                                         "could not fetch dirents of "
3716                                         "SVNParentPath", resource->pool);
3717 
3718           /* convert an io dirent hash to an fs dirent hash. */
3719           entries = apr_hash_make(resource->pool);
3720           for (hi = apr_hash_first(resource->pool, dirents);
3721                hi; hi = apr_hash_next(hi))
3722             {
3723               const void *key;
3724               void *val;
3725               svn_io_dirent_t *dirent;
3726               svn_fs_dirent_t *ent = apr_pcalloc(resource->pool, sizeof(*ent));
3727 
3728               apr_hash_this(hi, &key, NULL, &val);
3729               dirent = val;
3730 
3731               if (dirent->kind == svn_node_file && dirent->special)
3732                 {
3733                   svn_node_kind_t resolved_kind;
3734                   const char *link_path =
3735                     svn_dirent_join(fs_parent_path, key, resource->pool);
3736 
3737                   serr = svn_io_check_resolved_path(link_path, &resolved_kind,
3738                                                     resource->pool);
3739                   if (serr)
3740                     return dav_svn__convert_err(serr,
3741                                                 HTTP_INTERNAL_SERVER_ERROR,
3742                                                 "could not resolve symlink "
3743                                                 "dirent of SVNParentPath",
3744                                                 resource->pool);
3745                   if (resolved_kind != svn_node_dir)
3746                     continue;
3747 
3748                   dirent->kind = svn_node_dir;
3749                 }
3750               else if (dirent->kind != svn_node_dir)
3751                 continue;
3752 
3753               ent->name = key;
3754               ent->id = NULL;     /* ### does it matter? */
3755               ent->kind = dirent->kind;
3756 
3757               svn_hash_sets(entries, key, ent);
3758             }
3759 
3760         }
3761       else
3762         {
3763           dir_rev = svn_fs_revision_root_revision(resource->info->root.root);
3764           serr = svn_fs_dir_entries(&entries, resource->info->root.root,
3765                                     resource->info->repos_path, resource->pool);
3766           if (serr != NULL)
3767             return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3768                                         "could not fetch directory entries",
3769                                         resource->pool);
3770         }
3771 
3772       bb = apr_brigade_create(resource->pool,
3773                               dav_svn__output_get_bucket_alloc(output));
3774 
3775       serr = emit_collection_head(resource, bb, output, gen_html,
3776                                   resource->pool);
3777       if (serr != NULL)
3778         return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3779                                     "could not output collection",
3780                                     resource->pool);
3781 
3782       /* get a sorted list of the entries */
3783       sorted = svn_sort__hash(entries, svn_sort_compare_items_as_paths,
3784                               resource->pool);
3785 
3786       iterpool = svn_pool_create(resource->pool);
3787 
3788       for (i = 0; i < sorted->nelts; ++i)
3789         {
3790           const svn_sort__item_t *item = &APR_ARRAY_IDX(sorted, i,
3791                                                         const svn_sort__item_t);
3792           const svn_fs_dirent_t *entry = item->value;
3793           const char *name = item->key;
3794           const char *repos_relpath = NULL;
3795 
3796           svn_pool_clear(iterpool);
3797 
3798           /* DIR_REV is set to a valid revision if we're looking at
3799              the entries of a versioned directory.  Otherwise, we're
3800              looking at a parent-path listing. */
3801           if (SVN_IS_VALID_REVNUM(dir_rev))
3802             {
3803               repos_relpath = svn_fspath__join(resource->info->repos_path,
3804                                                name, iterpool);
3805               if (! dav_svn__allow_read(resource->info->r,
3806                                         resource->info->repos,
3807                                         repos_relpath,
3808                                         dir_rev,
3809                                         iterpool))
3810                 continue;
3811             }
3812           else
3813             {
3814                 if (! dav_svn__allow_list_repos(resource->info->r,
3815                                                 entry->name, iterpool))
3816                   continue;
3817             }
3818 
3819           serr = emit_collection_entry(resource, bb, output, entry, gen_html,
3820                                        iterpool);
3821           if (serr != NULL)
3822             return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3823                                         "could not output collection entry",
3824                                         resource->pool);
3825         }
3826 
3827       svn_pool_destroy(iterpool);
3828 
3829       serr = emit_collection_tail(resource, bb, output, gen_html,
3830                                   resource->pool);
3831       if (serr != NULL)
3832         return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3833                                     "could not output collection",
3834                                     resource->pool);
3835 
3836       bkt = apr_bucket_eos_create(dav_svn__output_get_bucket_alloc(output));
3837       APR_BRIGADE_INSERT_TAIL(bb, bkt);
3838       serr = dav_svn__output_pass_brigade(output, bb);
3839       if (serr != NULL)
3840         return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3841                                     "Could not write EOS to filter.",
3842                                     resource->pool);
3843 
3844       return NULL;
3845     }
3846 
3847 
3848   /* If we have a base for a delta, then we want to compute an svndiff
3849      between the provided base and the requested resource. For a simple
3850      request, then we just grab the file contents. */
3851   if (resource->info->delta_base != NULL)
3852     {
3853       dav_svn__uri_info info;
3854       svn_fs_root_t *root;
3855       svn_boolean_t is_file;
3856       svn_txdelta_stream_t *txd_stream;
3857       svn_stream_t *o_stream;
3858       svn_txdelta_window_handler_t handler;
3859       void * h_baton;
3860       diff_ctx_t dc = { 0 };
3861 
3862       /* First order of business is to parse it. */
3863       serr = dav_svn__simple_parse_uri(&info, resource,
3864                                        resource->info->delta_base,
3865                                        resource->pool);
3866 
3867       /* If we successfully parse the base URL, then send an svndiff. */
3868       if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM))
3869         {
3870           /* We are always accessing the base resource by ID, so open
3871              an ID root. */
3872           serr = svn_fs_revision_root(&root, resource->info->repos->fs,
3873                                       info.rev, resource->pool);
3874           if (serr != NULL)
3875             return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3876                                         "could not open a root for the base",
3877                                         resource->pool);
3878 
3879           /* verify that it is a file */
3880           serr = svn_fs_is_file(&is_file, root, info.repos_path,
3881                                 resource->pool);
3882           if (serr != NULL)
3883             return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3884                                         "could not determine if the base "
3885                                         "is really a file",
3886                                         resource->pool);
3887           if (!is_file)
3888             return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0, 0,
3889                                       apr_psprintf(resource->pool,
3890                                       "the delta base of '%s' does not refer "
3891                                       "to a file in revision %ld",
3892                                       info.repos_path, info.rev));
3893 
3894           /* Okay. Let's open up a delta stream for the client to read. */
3895           serr = svn_fs_get_file_delta_stream(&txd_stream,
3896                                               root, info.repos_path,
3897                                               resource->info->root.root,
3898                                               resource->info->repos_path,
3899                                               resource->pool);
3900           if (serr != NULL)
3901             return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3902                                         "could not prepare to read a delta",
3903                                         resource->pool);
3904 
3905           bb = apr_brigade_create(resource->pool,
3906                                   dav_svn__output_get_bucket_alloc(output));
3907 
3908           /* create a stream that svndiff data will be written to,
3909              which will copy it to the network */
3910           dc.output = output;
3911           dc.bb = bb;
3912           o_stream = svn_stream_create(&dc, resource->pool);
3913           svn_stream_set_write(o_stream, write_to_filter);
3914           svn_stream_set_close(o_stream, close_filter);
3915 
3916           /* get a handler/baton for writing into the output stream */
3917           svn_txdelta_to_svndiff3(&handler, &h_baton,
3918                                   o_stream, resource->info->svndiff_version,
3919                                   dav_svn__get_compression_level(resource->info->r),
3920                                   resource->pool);
3921 
3922           /* got everything set up. read in delta windows and shove them into
3923              the handler, which pushes data into the output stream, which goes
3924              to the network. */
3925           serr = svn_txdelta_send_txstream(txd_stream, handler, h_baton,
3926                                            resource->pool);
3927           apr_brigade_destroy(bb);
3928 
3929           if (serr != NULL)
3930             return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3931                                         "could not deliver the txdelta stream",
3932                                         resource->pool);
3933 
3934 
3935           return NULL;
3936         }
3937       else
3938         {
3939           svn_error_clear(serr);
3940         }
3941     }
3942 
3943   /* resource->info->delta_base is NULL, or we had an invalid base URL */
3944     {
3945       svn_stream_t *stream;
3946       char *block;
3947 
3948       serr = svn_fs_file_contents(&stream,
3949                                   resource->info->root.root,
3950                                   resource->info->repos_path,
3951                                   resource->pool);
3952       if (serr != NULL)
3953         {
3954           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3955                                       "could not prepare to read the file",
3956                                       resource->pool);
3957         }
3958 
3959       /* Perform keywords substitution if requested by client */
3960       if (resource->info->keyword_subst)
3961         {
3962           svn_string_t *keywords;
3963 
3964           serr = svn_fs_node_prop(&keywords,
3965                                   resource->info->root.root,
3966                                   resource->info->repos_path,
3967                                   SVN_PROP_KEYWORDS,
3968                                   resource->pool);
3969           if (serr != NULL)
3970             return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3971                                         "could not get fetch '"
3972                                         SVN_PROP_KEYWORDS "' property for "
3973                                         "for keywords substitution",
3974                                         resource->pool);
3975 
3976           if (keywords)
3977             {
3978               apr_hash_t *kw;
3979               svn_revnum_t cmt_rev;
3980               const char *str_cmt_rev, *str_uri, *str_root;
3981               const char *cmt_date, *cmt_author;
3982               apr_time_t when = 0;
3983 
3984               serr = svn_repos_get_committed_info(&cmt_rev,
3985                                                   &cmt_date,
3986                                                   &cmt_author,
3987                                                   resource->info->root.root,
3988                                                   resource->info->repos_path,
3989                                                   resource->pool);
3990               if (serr != NULL)
3991                 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3992                                             "could not fetch committed info "
3993                                             "for keywords substitution",
3994                                             resource->pool);
3995 
3996               serr = svn_time_from_cstring(&when, cmt_date, resource->pool);
3997               if (serr != NULL)
3998                 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3999                                             "could not parse committed date "
4000                                             "for keywords substitution",
4001                                             resource->pool);
4002               str_cmt_rev = apr_psprintf(resource->pool, "%ld", cmt_rev);
4003               str_uri = apr_pstrcat(resource->pool,
4004                                     resource->info->repos->base_url,
4005                                     ap_escape_uri(resource->pool,
4006                                                   resource->info->r->uri),
4007                                     SVN_VA_NULL);
4008               str_root = apr_pstrcat(resource->pool,
4009                                      resource->info->repos->base_url,
4010                                      resource->info->repos->root_path,
4011                                      SVN_VA_NULL);
4012 
4013               serr = svn_subst_build_keywords3(&kw, keywords->data,
4014                                                str_cmt_rev, str_uri, str_root,
4015                                                when, cmt_author,
4016                                                resource->pool);
4017               if (serr != NULL)
4018                 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4019                                             "could not perform keywords "
4020                                             "substitution", resource->pool);
4021 
4022               /* Replace the raw file STREAM with a wrapper that
4023                  handles keyword translation. */
4024               stream = svn_subst_stream_translated(
4025                            svn_stream_disown(stream, resource->pool),
4026                            NULL, FALSE, kw, TRUE, resource->pool);
4027             }
4028         }
4029 
4030       /* ### one day in the future, we can create a custom bucket type
4031          ### which will read from the FS stream on demand */
4032 
4033       block = apr_palloc(resource->pool, SVN__STREAM_CHUNK_SIZE);
4034       bb = apr_brigade_create(resource->pool,
4035                               dav_svn__output_get_bucket_alloc(output));
4036 
4037       while (1) {
4038         apr_size_t bufsize = SVN__STREAM_CHUNK_SIZE;
4039 
4040         /* read from the FS ... */
4041         serr = svn_stream_read_full(stream, block, &bufsize);
4042         if (serr != NULL)
4043           {
4044             apr_brigade_destroy(bb);
4045             return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4046                                         "could not read the file contents",
4047                                         resource->pool);
4048           }
4049         if (bufsize == 0)
4050           break;
4051 
4052         /* write to the filter ... */
4053         bkt = apr_bucket_transient_create(
4054           block, bufsize, dav_svn__output_get_bucket_alloc(output));
4055         APR_BRIGADE_INSERT_TAIL(bb, bkt);
4056         serr = dav_svn__output_pass_brigade(output, bb);
4057         if (serr != NULL)
4058           {
4059             apr_brigade_destroy(bb);
4060             /* ### that HTTP code... */
4061             return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4062                                         "Could not write data to filter.",
4063                                         resource->pool);
4064           }
4065       }
4066 
4067       /* done with the file. write an EOS bucket now. */
4068       bkt = apr_bucket_eos_create(dav_svn__output_get_bucket_alloc(output));
4069       APR_BRIGADE_INSERT_TAIL(bb, bkt);
4070       serr = dav_svn__output_pass_brigade(output, bb);
4071       if (serr != NULL)
4072         {
4073           apr_brigade_destroy(bb);
4074           /* ### that HTTP code... */
4075           return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4076                                       "Could not write EOS to filter.",
4077                                       resource->pool);
4078         }
4079 
4080       apr_brigade_destroy(bb);
4081       return NULL;
4082     }
4083 }
4084 
4085 
4086 static dav_error *
create_collection(dav_resource * resource)4087 create_collection(dav_resource *resource)
4088 {
4089   svn_error_t *serr;
4090   dav_error *err;
4091 
4092   if (resource->type != DAV_RESOURCE_TYPE_WORKING
4093       && resource->type != DAV_RESOURCE_TYPE_REGULAR)
4094     {
4095       return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0,
4096                                 "Collections can only be created within a "
4097                                 "working or regular collection (at this "
4098                                 "time).");
4099     }
4100 
4101   /* ...regular resources allowed only if autoversioning is turned on. */
4102   if (resource->type == DAV_RESOURCE_TYPE_REGULAR
4103       && ! (resource->info->repos->autoversioning))
4104     return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0,
4105                               "MKCOL called on regular resource, but "
4106                               "autoversioning is not active.");
4107 
4108   /* ### note that the parent was checked out at some point, and this
4109      ### is being preformed relative to the working rsrc for that parent */
4110 
4111   /* Auto-versioning mkcol of regular resource: */
4112   if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
4113     {
4114       /* Change the VCR into a WR, in place.  This creates a txn and
4115          changes resource->info->root from a rev-root into a txn-root. */
4116       err = dav_svn__checkout(resource,
4117                               1 /* auto-checkout */,
4118                               0, 0, 0, NULL, NULL);
4119       if (err)
4120         return err;
4121     }
4122 
4123   if ((serr = svn_fs_make_dir(resource->info->root.root,
4124                               resource->info->repos_path,
4125                               resource->pool)) != NULL)
4126     {
4127       /* ### need a better error */
4128       return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4129                                   "Could not create the collection.",
4130                                   resource->pool);
4131     }
4132 
4133   /* Auto-versioning commit of the txn. */
4134   if (resource->info->auto_checked_out)
4135     {
4136       /* This also changes the WR back into a VCR, in place. */
4137       err = dav_svn__checkin(resource, 0, NULL);
4138       if (err)
4139         return err;
4140     }
4141 
4142   return NULL;
4143 }
4144 
4145 
4146 static dav_error *
copy_resource(const dav_resource * src,dav_resource * dst,int depth,dav_response ** response)4147 copy_resource(const dav_resource *src,
4148               dav_resource *dst,
4149               int depth,
4150               dav_response **response)
4151 {
4152   svn_error_t *serr;
4153   dav_error *err;
4154   const char *src_repos_path, *dst_repos_path;
4155 
4156   /* ### source must be from a collection under baseline control. the
4157      ### baseline will (implicitly) indicate the source revision, and the
4158      ### path will be derived simply from the URL path */
4159 
4160   /* ### the destination's parent must be a working collection */
4161 
4162   /* ### ben goofing around: */
4163   /*  char *msg;
4164       apr_psprintf
4165       (src->pool, "Got a COPY request with src arg '%s' and dst arg '%s'",
4166       src->uri, dst->uri);
4167 
4168       return dav_svn__new_error(src->pool, HTTP_NOT_IMPLEMENTED, 0, msg);
4169   */
4170 
4171   /* ### Safeguard: see issue #916, whereby we're allowing an
4172      auto-checkout of a baseline for PROPPATCHing, *without* creating
4173      a new baseline afterwards.  We need to safeguard here that nobody
4174      is calling COPY with the baseline as a Destination! */
4175   if (dst->baselined && dst->type == DAV_RESOURCE_TYPE_VERSION)
4176     return dav_svn__new_error(src->pool, HTTP_PRECONDITION_FAILED, 0, 0,
4177                               "Illegal: COPY Destination is a baseline.");
4178 
4179   if (dst->type == DAV_RESOURCE_TYPE_REGULAR
4180       && !(dst->info->repos->autoversioning))
4181     return dav_svn__new_error(dst->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0,
4182                               "COPY called on regular resource, but "
4183                               "autoversioning is not active.");
4184 
4185   /* Auto-versioning copy of regular resource: */
4186   if (dst->type == DAV_RESOURCE_TYPE_REGULAR)
4187     {
4188       /* Change the VCR into a WR, in place.  This creates a txn and
4189          changes dst->info->root from a rev-root into a txn-root. */
4190       err = dav_svn__checkout(dst,
4191                               1 /* auto-checkout */,
4192                               0, 0, 0, NULL, NULL);
4193       if (err)
4194         return err;
4195     }
4196 
4197   src_repos_path = svn_repos_path(src->info->repos->repos, src->pool);
4198   dst_repos_path = svn_repos_path(dst->info->repos->repos, dst->pool);
4199 
4200   if (strcmp(src_repos_path, dst_repos_path) != 0)
4201     {
4202       /* Perhaps the source and dst repos use different path formats? */
4203       serr = svn_error_compose_create(
4204                 svn_dirent_get_absolute(&src_repos_path, src_repos_path,
4205                                         src->pool),
4206                 svn_dirent_get_absolute(&dst_repos_path, dst_repos_path,
4207                                         dst->pool));
4208 
4209       if (!serr && (strcmp(src_repos_path, dst_repos_path) != 0))
4210           return dav_svn__new_error_svn(
4211                 dst->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
4212                 "Copy source and destination are in different repositories");
4213     }
4214   else
4215       serr = SVN_NO_ERROR;
4216 
4217   if (!serr)
4218     {
4219       serr = svn_fs_copy(src->info->root.root,  /* root object of src rev*/
4220                          src->info->repos_path, /* relative path of src */
4221                          dst->info->root.root,  /* root object of dst txn*/
4222                          dst->info->repos_path, /* relative path of dst */
4223                          src->pool);
4224     }
4225   if (serr)
4226     return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4227                                 "Unable to make a filesystem copy.",
4228                                 dst->pool);
4229 
4230   /* Auto-versioning commit of the txn. */
4231   if (dst->info->auto_checked_out)
4232     {
4233       /* This also changes the WR back into a VCR, in place. */
4234       err = dav_svn__checkin(dst, 0, NULL);
4235       if (err)
4236         return err;
4237     }
4238 
4239   return NULL;
4240 }
4241 
4242 
4243 static dav_error *
remove_resource(dav_resource * resource,dav_response ** response)4244 remove_resource(dav_resource *resource, dav_response **response)
4245 {
4246   svn_error_t *serr;
4247   dav_error *err;
4248   apr_hash_t *locks;
4249 
4250   /* Only activities, working resources, regular resources, and
4251      certain private resources can be deleted... */
4252   if (! (resource->type == DAV_RESOURCE_TYPE_WORKING
4253          || resource->type == DAV_RESOURCE_TYPE_REGULAR
4254          || resource->type == DAV_RESOURCE_TYPE_ACTIVITY
4255          || (resource->type == DAV_RESOURCE_TYPE_PRIVATE
4256              && resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION)))
4257     return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0,
4258                               "DELETE called on invalid resource type.");
4259 
4260   /* ...and regular resources only if autoversioning is turned on. */
4261   if (resource->type == DAV_RESOURCE_TYPE_REGULAR
4262       && ! (resource->info->repos->autoversioning))
4263     return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0,
4264                               "DELETE called on regular resource, but "
4265                               "autoversioning is not active.");
4266 
4267   /* Handle activity deletions (early exit). */
4268   if (resource->type == DAV_RESOURCE_TYPE_ACTIVITY)
4269     {
4270       return dav_svn__delete_activity(resource->info->repos,
4271                                       resource->info->root.activity_id);
4272     }
4273 
4274   /* Handle deletions of transaction collections (early exit) */
4275   if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
4276       && resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION)
4277     {
4278       if (resource->info->root.vtxn_name)
4279         return dav_svn__delete_activity(resource->info->repos,
4280                                         resource->info->root.vtxn_name);
4281       else
4282         return dav_svn__abort_txn(resource->info->repos,
4283                                   resource->info->root.txn_name,
4284                                   resource->pool);
4285     }
4286 
4287   /* ### note that the parent was checked out at some point, and this
4288      ### is being preformed relative to the working rsrc for that parent */
4289 
4290   /* NOTE: strictly speaking, we cannot determine whether the parent was
4291      ever checked out, and that this working resource is relative to that
4292      checked out parent. It is entirely possible the client checked out
4293      the target resource and just deleted it. Subversion doesn't mind, but
4294      this does imply we are not enforcing the "checkout the parent, then
4295      delete from within" semantic. */
4296 
4297   /* Auto-versioning delete of regular resource: */
4298   if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
4299     {
4300       /* Change the VCR into a WR, in place.  This creates a txn and
4301          changes resource->info->root from a rev-root into a txn-root. */
4302       err = dav_svn__checkout(resource,
4303                               1 /* auto-checkout */,
4304                               0, 0, 0, NULL, NULL);
4305       if (err)
4306         return err;
4307     }
4308 
4309   /* Sanity check: an svn client may have sent a custom request header
4310      containing the revision of the item it thinks it's deleting.  In
4311      this case, we enforce the svn-specific semantic that the item
4312      must be up-to-date. */
4313   if (SVN_IS_VALID_REVNUM(resource->info->version_name))
4314     {
4315       svn_revnum_t created_rev;
4316       serr = svn_fs_node_created_rev(&created_rev,
4317                                      resource->info->root.root,
4318                                      resource->info->repos_path,
4319                                      resource->pool);
4320       if (serr)
4321         return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4322                                     "Could not get created rev of resource",
4323                                     resource->pool);
4324 
4325       if (resource->info->version_name < created_rev)
4326         {
4327           serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL,
4328                                    resource->collection
4329                                     ? "Directory '%s' is out of date"
4330                                     : (resource->exists
4331                                         ? "File '%s' is out of date"
4332                                         : "'%s' is out of date"),
4333                                    resource->info->repos_path);
4334           return dav_svn__convert_err(serr, HTTP_CONFLICT,
4335                                       "Can't DELETE out-of-date resource",
4336                                       resource->pool);
4337         }
4338       else if (resource->info->version_name > created_rev)
4339         {
4340           svn_revnum_t txn_base_rev;
4341 
4342           txn_base_rev = svn_fs_txn_base_revision(resource->info->root.txn);
4343           if (resource->info->version_name > txn_base_rev)
4344             {
4345               serr = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
4346                                        "No such revision %ld",
4347                                        resource->info->version_name);
4348 
4349               return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4350                                           "Unknown base revision",
4351                                           resource->pool);
4352             }
4353         }
4354     }
4355 
4356   /* Before attempting the filesystem delete, we need to push any
4357      incoming lock-tokens into the filesystem's access_t.  Normally
4358      they come in via 'If:' header, and get_resource()
4359      automatically notices them and does this work for us.  In the
4360      case of a directory deletion, however, older subversion clients
4361      are sending 'child' lock-tokens in the non-standard DELETE
4362      request body. */
4363 
4364   err = dav_svn__build_lock_hash(&locks, resource->info->r,
4365                                  resource->info->repos_path, resource->pool);
4366   if (err != NULL)
4367     return err;
4368 
4369   if (apr_hash_count(locks))
4370     {
4371       err = dav_svn__push_locks(resource, locks, resource->pool);
4372       if (err != NULL)
4373         return err;
4374     }
4375 
4376   if ((serr = svn_fs_delete(resource->info->root.root,
4377                             resource->info->repos_path,
4378                             resource->pool)) != NULL)
4379     {
4380       /* ### need a better error */
4381       return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4382                                   "Could not delete the resource",
4383                                   resource->pool);
4384     }
4385 
4386   /* Auto-versioning commit of the txn. */
4387   if (resource->info->auto_checked_out)
4388     {
4389       /* This also changes the WR back into a VCR, in place. */
4390       err = dav_svn__checkin(resource, 0, NULL);
4391       if (err)
4392         return err;
4393     }
4394 
4395   return NULL;
4396 }
4397 
4398 
4399 static dav_error *
move_resource(dav_resource * src,dav_resource * dst,dav_response ** response)4400 move_resource(dav_resource *src,
4401               dav_resource *dst,
4402               dav_response **response)
4403 {
4404   svn_error_t *serr;
4405   dav_error *err;
4406 
4407   /* NOTE: The svn client does not call the MOVE method yet. Strictly
4408      speaking, we do not need to implement this repository function.
4409      But we do so anyway, so non-deltaV clients can work against the
4410      repository when autoversioning is turned on.  Like the svn client,
4411      itself, we define a move to be a copy + delete within a single txn. */
4412 
4413   /* Because we have no 'atomic' move, we only allow this method on
4414      two regular resources with autoversioning active.  That way we
4415      can auto-checkout a single resource and do the copy + delete
4416      within a single txn.  (If we had two working resources, which txn
4417      would we use?) */
4418   if (src->type != DAV_RESOURCE_TYPE_REGULAR
4419       || dst->type != DAV_RESOURCE_TYPE_REGULAR
4420       || !(src->info->repos->autoversioning))
4421     return dav_svn__new_error(dst->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0,
4422                               "MOVE only allowed on two public URIs, and "
4423                               "autoversioning must be active.");
4424 
4425   /* Change the dst VCR into a WR, in place.  This creates a txn and
4426      changes dst->info->root from a rev-root into a txn-root. */
4427   err = dav_svn__checkout(dst,
4428                           1 /* auto-checkout */,
4429                           0, 0, 0, NULL, NULL);
4430   if (err)
4431     return err;
4432 
4433   /* Copy the src to the dst. */
4434   serr = svn_fs_copy(src->info->root.root,  /* the root object of src rev*/
4435                      src->info->repos_path, /* the relative path of src */
4436                      dst->info->root.root,  /* the root object of dst txn*/
4437                      dst->info->repos_path, /* the relative path of dst */
4438                      src->pool);
4439   if (serr)
4440     return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4441                                 "Unable to make a filesystem copy.",
4442                                 dst->pool);
4443 
4444   /* Notice: we're deleting the src repos path from the dst's txn_root. */
4445   if ((serr = svn_fs_delete(dst->info->root.root,
4446                             src->info->repos_path,
4447                             dst->pool)) != NULL)
4448     return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4449                                 "Could not delete the src resource.",
4450                                 dst->pool);
4451 
4452   /* Commit:  this also changes the WR back into a VCR, in place. */
4453   err = dav_svn__checkin(dst, 0, NULL);
4454   if (err)
4455     return err;
4456 
4457   return NULL;
4458 }
4459 
4460 
4461 typedef struct walker_ctx_t {
4462   /* the input walk parameters */
4463   const dav_walk_params *params;
4464 
4465   /* reused as we walk */
4466   dav_walk_resource wres;
4467 
4468   /* the current resource */
4469   dav_resource res;             /* wres.resource refers here */
4470   dav_resource_private info;    /* the info in res */
4471   svn_stringbuf_t *uri;            /* the uri within res */
4472   svn_stringbuf_t *repos_path;     /* the repos_path within res */
4473 
4474 } walker_ctx_t;
4475 
4476 /* Recursively walk a resource for walk().  When DEPTH != 0, recurse with
4477    DEPTH-1 on child nodes. WALK_ROOT should be TRUE for the root and will be
4478    FALSE for any descendants, to avoid unneeded work for every descendant
4479    node.
4480    */
4481 static dav_error *
do_walk(walker_ctx_t * ctx,int depth,svn_boolean_t walk_root,apr_pool_t * scratch_pool)4482 do_walk(walker_ctx_t *ctx,
4483         int depth,
4484         svn_boolean_t walk_root,
4485         apr_pool_t *scratch_pool)
4486 {
4487   const dav_walk_params *params = ctx->params;
4488   int isdir = ctx->res.collection;
4489   dav_error *err;
4490   svn_error_t *serr;
4491   apr_hash_index_t *hi;
4492   apr_size_t path_len;
4493   apr_size_t uri_len;
4494   apr_size_t repos_len;
4495   apr_hash_t *children;
4496   apr_pool_t *iterpool;
4497 
4498   /* The current resource is a collection (possibly here thru recursion)
4499      and this is the invocation for the collection. Alternatively, this is
4500      the first [and only] entry to do_walk() for a member resource, so
4501      this will be the invocation for the member. */
4502   err = (*params->func)(&ctx->wres,
4503                         isdir ? DAV_CALLTYPE_COLLECTION : DAV_CALLTYPE_MEMBER);
4504   if (err != NULL)
4505     return err;
4506 
4507   /* if we are not to recurse, or this is a member, then we're done */
4508   if (depth == 0 || !isdir)
4509     return NULL;
4510 
4511   /* ### for now, let's say that working resources have no children. of
4512      ### course, this isn't true (or "right") for working collections, but
4513      ### we don't actually need to do a walk right now. */
4514   if (params->root->type == DAV_RESOURCE_TYPE_WORKING)
4515     return NULL;
4516 
4517   /* ### need to allow more walking in the future */
4518   if (params->root->type != DAV_RESOURCE_TYPE_REGULAR)
4519     {
4520       return dav_svn__new_error(params->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0,
4521                                 "Walking the resource hierarchy can only be "
4522                                 "done on 'regular' resources [at this time].");
4523     }
4524 
4525   /* assert: collection resource. isdir == TRUE. repos_path != NULL. */
4526 
4527   /* append "/" to the paths, in preparation for appending child names.
4528      don't add "/" if the paths are simply "/" */
4529   if (ctx->info.uri_path->data[ctx->info.uri_path->len - 1] != '/')
4530     svn_stringbuf_appendcstr(ctx->info.uri_path, "/");
4531   if (ctx->repos_path->data[ctx->repos_path->len - 1] != '/')
4532     svn_stringbuf_appendcstr(ctx->repos_path, "/");
4533 
4534   /* NOTE: the URI should already have a trailing "/" */
4535 
4536   /* fix up the dependent pointers */
4537   ctx->info.repos_path = ctx->repos_path->data;
4538 
4539   /* all of the children exist. also initialize the collection flag. */
4540   ctx->res.exists = TRUE;
4541   ctx->res.collection = FALSE;
4542 
4543   /* remember these values so we can chop back to them after each time
4544      we append a child name to the path/uri/repos */
4545   path_len = ctx->info.uri_path->len;
4546   uri_len = ctx->uri->len;
4547   repos_len = ctx->repos_path->len;
4548 
4549   if (walk_root)
4550     {
4551       /* Tell our logging subsystem that we're listing a directory.
4552 
4553       Note: if we cared, we could look at the 'User-Agent:' request
4554          header and distinguish an svn client ('svn ls') from a generic
4555          DAV client.  */
4556       dav_svn__operational_log(&ctx->info,
4557                                svn_log__get_dir(ctx->info.repos_path,
4558                                                 ctx->info.root.rev,
4559                                                 TRUE, FALSE, SVN_DIRENT_ALL,
4560                                                 scratch_pool));
4561     }
4562 
4563   /* fetch this collection's children */
4564   serr = svn_fs_dir_entries(&children, ctx->info.root.root,
4565                             ctx->info.repos_path, scratch_pool);
4566   if (serr != NULL)
4567     return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4568                                 "could not fetch collection members",
4569                                 params->pool);
4570 
4571   /* iterate over the children in this collection */
4572   iterpool = svn_pool_create(scratch_pool);
4573   for (hi = apr_hash_first(scratch_pool, children); hi; hi = apr_hash_next(hi))
4574     {
4575       const void *key;
4576       apr_ssize_t klen;
4577       void *val;
4578       svn_fs_dirent_t *dirent;
4579 
4580       svn_pool_clear(iterpool);
4581 
4582       /* fetch one of the children */
4583       apr_hash_this(hi, &key, &klen, &val);
4584       dirent = val;
4585 
4586       /* authorize access to this resource, if applicable */
4587       if (params->walk_type & DAV_WALKTYPE_AUTH)
4588         {
4589           const char *repos_relpath =
4590             apr_pstrcat(iterpool,
4591                         apr_pstrmemdup(iterpool,
4592                                        ctx->repos_path->data,
4593                                        ctx->repos_path->len),
4594                         key, SVN_VA_NULL);
4595           if (! dav_svn__allow_read(ctx->info.r, ctx->info.repos,
4596                                     repos_relpath, ctx->info.root.rev,
4597                                     iterpool))
4598             continue;
4599         }
4600 
4601       /* append this child to our buffers */
4602       svn_stringbuf_appendbytes(ctx->info.uri_path, key, klen);
4603       svn_stringbuf_appendbytes(ctx->uri, key, klen);
4604       svn_stringbuf_appendbytes(ctx->repos_path, key, klen);
4605 
4606       /* reset the pointers since the above may have changed them */
4607       ctx->res.uri = ctx->uri->data;
4608       ctx->info.repos_path = ctx->repos_path->data;
4609 
4610       if (dirent->kind == svn_node_file)
4611         {
4612           err = (*params->func)(&ctx->wres, DAV_CALLTYPE_MEMBER);
4613           if (err != NULL)
4614             {
4615               svn_pool_destroy(iterpool);
4616               return err;
4617             }
4618         }
4619       else
4620         {
4621           /* this resource is a collection */
4622           ctx->res.collection = TRUE;
4623 
4624           /* append a slash to the URI (the path doesn't need it yet) */
4625           svn_stringbuf_appendcstr(ctx->uri, "/");
4626           ctx->res.uri = ctx->uri->data;
4627 
4628           /* recurse on this collection */
4629           err = do_walk(ctx, depth - 1, FALSE, iterpool);
4630           if (err != NULL)
4631             {
4632               svn_pool_destroy(iterpool);
4633               return err;
4634             }
4635 
4636           /* restore the data */
4637           ctx->res.collection = FALSE;
4638         }
4639 
4640       /* chop the child off the paths and uri. NOTE: no null-term. */
4641       ctx->info.uri_path->len = path_len;
4642       ctx->uri->len = uri_len;
4643       ctx->repos_path->len = repos_len;
4644     }
4645 
4646   svn_pool_destroy(iterpool);
4647 
4648   return NULL;
4649 }
4650 
4651 static dav_error *
walk(const dav_walk_params * params,int depth,dav_response ** response)4652 walk(const dav_walk_params *params, int depth, dav_response **response)
4653 {
4654   /* Thinking about adding support for LOCKNULL resources in this
4655      walker?  Check out the (working) code that was removed here:
4656           Author: cmpilato
4657           Date: Fri Mar 18 14:54:02 2005
4658           New Revision: 13475
4659      */
4660 
4661   walker_ctx_t ctx = { 0 };
4662   dav_error *err;
4663 
4664   if (params->root->info->restype == DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
4665     {
4666       /* Cannot walk an SVNParentPath collection, there is no repository. */
4667       return NULL;
4668     }
4669 
4670   ctx.params = params;
4671 
4672   ctx.wres.walk_ctx = params->walk_ctx;
4673   ctx.wres.pool = params->pool;
4674   ctx.wres.resource = &ctx.res;
4675 
4676   /* copy the resource over and adjust the "info" reference */
4677   ctx.res = *params->root;
4678   ctx.info = *ctx.res.info;
4679 
4680   ctx.res.info = &ctx.info;
4681 
4682   /* operate within the proper pool */
4683   ctx.res.pool = params->pool;
4684 
4685   /* Don't monkey with the path from params->root. Create a new one.
4686      This path will then be extended/shortened as necessary. */
4687   ctx.info.uri_path = svn_stringbuf_dup(ctx.info.uri_path, params->pool);
4688 
4689   /* prep the URI buffer */
4690   ctx.uri = svn_stringbuf_create(params->root->uri, params->pool);
4691 
4692   /* same for repos_path */
4693   if (ctx.info.repos_path == NULL)
4694     ctx.repos_path = NULL;
4695   else
4696     ctx.repos_path = svn_stringbuf_create(ctx.info.repos_path, params->pool);
4697 
4698   /* if we have a collection, then ensure the URI has a trailing "/" */
4699   /* ### get_resource always kills the trailing slash... */
4700   if (ctx.res.collection && ctx.uri->data[ctx.uri->len - 1] != '/') {
4701     svn_stringbuf_appendcstr(ctx.uri, "/");
4702   }
4703 
4704   /* the current resource's URI is stored in the (telescoping) ctx.uri */
4705   ctx.res.uri = ctx.uri->data;
4706 
4707   /* the current resource's repos_path is stored in ctx.repos_path */
4708   if (ctx.repos_path != NULL)
4709     ctx.info.repos_path = ctx.repos_path->data;
4710 
4711   /* ### is the root already/always open? need to verify */
4712 
4713   /* always return the error, and any/all multistatus responses */
4714   err = do_walk(&ctx, depth, TRUE, params->pool);
4715   *response = ctx.wres.response;
4716 
4717   return err;
4718 }
4719 
4720 
4721 
4722 /*** Utility functions for resource management ***/
4723 
4724 dav_resource *
dav_svn__create_working_resource(dav_resource * base,const char * activity_id,const char * txn_name,int tweak_in_place)4725 dav_svn__create_working_resource(dav_resource *base,
4726                                  const char *activity_id,
4727                                  const char *txn_name,
4728                                  int tweak_in_place)
4729 {
4730   const char *path;
4731   dav_resource *res;
4732 
4733   if (base->baselined)
4734     path = apr_psprintf(base->pool,
4735                         "/%s/wbl/%s/%ld",
4736                         base->info->repos->special_uri,
4737                         activity_id, base->info->root.rev);
4738   else
4739     path = apr_psprintf(base->pool, "/%s/wrk/%s%s",
4740                         base->info->repos->special_uri,
4741                         activity_id, base->info->repos_path);
4742   path = svn_path_uri_encode(path, base->pool);
4743 
4744   if (tweak_in_place)
4745     res = base;
4746   else
4747     {
4748       res = apr_pcalloc(base->pool, sizeof(*res));
4749       res->info = apr_pcalloc(base->pool, sizeof(*res->info));
4750     }
4751 
4752   res->type = DAV_RESOURCE_TYPE_WORKING;
4753   res->exists = TRUE;      /* ### not necessarily correct */
4754   res->versioned = TRUE;
4755   res->working = TRUE;
4756   res->baselined = base->baselined;
4757   /* collection = FALSE.   ### not necessarily correct */
4758 
4759   if (base->info->repos->root_path[1])
4760     res->uri = apr_pstrcat(base->pool, base->info->repos->root_path,
4761                            path, SVN_VA_NULL);
4762   else
4763     res->uri = path;
4764   res->hooks = &dav_svn__hooks_repository;
4765   res->pool = base->pool;
4766 
4767   res->info->uri_path = svn_stringbuf_create(path, base->pool);
4768   res->info->repos = base->info->repos;
4769   res->info->repos_path = base->info->repos_path;
4770   res->info->root.rev = base->info->root.rev;
4771   res->info->root.activity_id = activity_id;
4772   res->info->root.txn_name = txn_name;
4773 
4774   if (tweak_in_place)
4775     return NULL;
4776   else
4777     return res;
4778 }
4779 
4780 
4781 dav_error *
dav_svn__working_to_regular_resource(dav_resource * resource)4782 dav_svn__working_to_regular_resource(dav_resource *resource)
4783 {
4784   dav_resource_private *priv = resource->info;
4785   dav_svn_repos *repos = priv->repos;
4786   const char *path;
4787   svn_error_t *serr;
4788 
4789   /* no need to change the repos object or repos_path */
4790 
4791   /* set type back to REGULAR */
4792   resource->type = DAV_RESOURCE_TYPE_REGULAR;
4793 
4794   /* remove the working flag */
4795   resource->working = FALSE;
4796 
4797   /* Change the URL into either a baseline-collection or a public one. */
4798   if (priv->root.rev == SVN_INVALID_REVNUM)
4799     {
4800       serr = dav_svn__get_youngest_rev(&priv->root.rev, repos, resource->pool);
4801       if (serr != NULL)
4802         return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4803                                     "Could not determine youngest rev.",
4804                                     resource->pool);
4805 
4806       /* create public URL */
4807       path = apr_psprintf(resource->pool, "%s", priv->repos_path);
4808     }
4809   else
4810     {
4811       /* if rev was specific, create baseline-collection URL */
4812       path = dav_svn__build_uri(repos, DAV_SVN__BUILD_URI_BC,
4813                                 priv->root.rev, priv->repos_path,
4814                                 FALSE /* add_href */, resource->pool);
4815     }
4816   path = svn_path_uri_encode(path, resource->pool);
4817   priv->uri_path = svn_stringbuf_create(path, resource->pool);
4818 
4819   /* change root.root back into a revision root. */
4820   serr = svn_fs_revision_root(&priv->root.root, repos->fs,
4821                               priv->root.rev, resource->pool);
4822   if (serr != NULL)
4823     return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
4824                                 "Could not open revision root.",
4825                                 resource->pool);
4826 
4827   return NULL;
4828 }
4829 
4830 
4831 dav_error *
dav_svn__create_version_resource(dav_resource ** version_res,const char * uri,apr_pool_t * pool)4832 dav_svn__create_version_resource(dav_resource **version_res,
4833                                  const char *uri,
4834                                  apr_pool_t *pool)
4835 {
4836   int result;
4837   dav_error *err;
4838 
4839   dav_resource_combined *comb = apr_pcalloc(pool, sizeof(*comb));
4840 
4841   result = parse_version_uri(comb, uri, NULL, 0);
4842   if (result != 0)
4843     return dav_svn__new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
4844                               "Could not parse version resource uri.");
4845 
4846   err = prep_version(comb);
4847   if (err)
4848     return err;
4849 
4850   *version_res = &comb->res;
4851   return NULL;
4852 }
4853 
4854 
4855 
4856 static dav_error *
handle_post_request(request_rec * r,dav_resource * resource,dav_svn__output * output)4857 handle_post_request(request_rec *r,
4858                     dav_resource *resource,
4859                     dav_svn__output *output)
4860 {
4861   svn_skel_t *request_skel, *post_skel;
4862   int status;
4863   apr_pool_t *pool = resource->pool;
4864 
4865   /* Make sure our skel-based request parses okay, has an initial atom
4866      that identifies what kind of action is expected, and that that
4867      action is something we understand.  */
4868   status = dav_svn__parse_request_skel(&request_skel, r, pool);
4869 
4870   if (status != OK)
4871     return dav_svn__new_error(pool, status, 0, 0,
4872                               "Error parsing skel POST request body.");
4873 
4874   if (svn_skel__list_length(request_skel) < 1)
4875     return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0, 0,
4876                               "Unable to identify skel POST request flavor.");
4877 
4878   post_skel = request_skel->children;
4879 
4880   /* NOTE: If you add POST handlers here, you'll want to advertise
4881      that the server supports them, too.  See version.c:get_option(). */
4882 
4883   if (svn_skel__matches_atom(post_skel, "create-txn"))
4884     {
4885       return dav_svn__post_create_txn(resource, request_skel, output);
4886     }
4887   else if (svn_skel__matches_atom(post_skel, "create-txn-with-props"))
4888     {
4889       return dav_svn__post_create_txn_with_props(resource,
4890                                                  request_skel, output);
4891     }
4892 
4893   return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0, 0,
4894                             "Unsupported skel POST request flavor.");
4895 }
4896 
4897 
4898 /* A stripped down version of mod_dav's dav_handle_err so that POST
4899    errors, which are not passed via mod_dav, are handled in the same
4900    way as errors for requests that are passed via mod_dav. */
4901 static int
handle_err(request_rec * r,dav_error * err)4902 handle_err(request_rec *r, dav_error *err)
4903 {
4904   dav_error *stackerr = err;
4905 
4906   dav_svn__log_err(r, err, APLOG_ERR);
4907 
4908   /* our error messages are safe; tell Apache this */
4909   apr_table_setn(r->notes, "verbose-error-to", "*");
4910 
4911   /* We might be able to generate a standard <D:error> response.
4912      Search the error stack for an errortag. */
4913   while (stackerr != NULL && stackerr->tagname == NULL)
4914     stackerr = stackerr->prev;
4915 
4916   if (stackerr != NULL && stackerr->tagname != NULL)
4917     return dav_svn__error_response_tag(r, stackerr);
4918 
4919   return err->status;
4920 }
4921 
4922 
dav_svn__method_post(request_rec * r)4923 int dav_svn__method_post(request_rec *r)
4924 {
4925   dav_resource *resource;
4926   dav_error *derr;
4927   const char *content_type;
4928 
4929   /* We only allow POSTs against the "me resource" right now. */
4930   derr = get_resource(r, dav_svn__get_root_dir(r),
4931                       "ignored", 0, &resource);
4932   if (derr != NULL)
4933     return derr->status;
4934   if (resource->info->restype != DAV_SVN_RESTYPE_ME)
4935     return HTTP_BAD_REQUEST;
4936 
4937   /* Pass skel-type POST request handling off to a dispatcher; any
4938      other type of request is considered bogus. */
4939   content_type = apr_table_get(r->headers_in, "content-type");
4940   if (content_type && (strcmp(content_type, SVN_SKEL_MIME_TYPE) == 0))
4941     {
4942       dav_svn__output *output = dav_svn__output_create(resource->info->r,
4943                                                        resource->pool);
4944       derr = handle_post_request(r, resource, output);
4945     }
4946   else
4947     {
4948       derr = dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0, 0,
4949                                 "Unsupported POST request type.");
4950     }
4951 
4952   /* If something went wrong above, we'll generate a response back to
4953      the client with (hopefully) some helpful information. */
4954   if (derr)
4955     {
4956       /* POST is not a DAV method and so mod_dav isn't involved and
4957          won't handle this error.  Do it explicitly. */
4958       return handle_err(r, derr);
4959     }
4960 
4961   return OK;
4962 }
4963 
4964 
4965 
4966 const dav_hooks_repository dav_svn__hooks_repository =
4967 {
4968   1,                            /* special GET handling */
4969   get_resource,
4970   get_parent_resource,
4971   is_same_resource,
4972   is_parent_resource,
4973   open_stream,
4974   close_stream,
4975   write_stream,
4976   seek_stream,
4977   set_headers,
4978   deliver,
4979   create_collection,
4980   copy_resource,
4981   move_resource,
4982   remove_resource,
4983   walk,
4984   getetag_pathetic
4985 };
4986