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(¤t_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, <l);
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