* www/release-process.html
[svn.git] / subversion / mod_dav_svn / repos.c
blobc11114f2a9ada019b18a53ff960a017649565f1c
1 /*
2 * repos.c: mod_dav_svn repository provider functions for Subversion
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 #define APR_WANT_STRFUNC
20 #include <apr_want.h>
21 #include <apr_strings.h>
22 #include <apr_hash.h>
23 #include <apr_lib.h>
25 #include <httpd.h>
26 #include <http_request.h>
27 #include <http_protocol.h>
28 #include <http_log.h>
29 #include <http_core.h> /* for ap_construct_url */
30 #include <mod_dav.h>
32 #include "svn_types.h"
33 #include "svn_pools.h"
34 #include "svn_error.h"
35 #include "svn_time.h"
36 #include "svn_fs.h"
37 #include "svn_repos.h"
38 #include "svn_dav.h"
39 #include "svn_sorts.h"
40 #include "svn_version.h"
41 #include "svn_props.h"
42 #include "mod_dav_svn.h"
43 #include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
45 #include "dav_svn.h"
48 #define DEFAULT_ACTIVITY_DB "dav/activities.d"
51 struct dav_stream {
52 const dav_resource *res;
54 /* for reading from the FS */
55 svn_stream_t *rstream;
57 /* for writing to the FS. we use wstream OR the handler/baton. */
58 svn_stream_t *wstream;
59 svn_txdelta_window_handler_t delta_handler;
60 void *delta_baton;
64 /* Convenience structure that facilitates combined memory allocation of
65 a dav_resource and dav_resource_private pair. */
66 typedef struct {
67 dav_resource res;
68 dav_resource_private priv;
69 } dav_resource_combined;
72 /* Helper-wrapper around svn_fs_check_path(), which takes the same
73 arguments. But: if we attempt to stat a path like "file1/file2",
74 then still return 'svn_node_none' to signal nonexistence, rather
75 than a full-blown filesystem error. This allows mod_dav to throw
76 404 instead of 500. */
77 static dav_error *
78 fs_check_path(svn_node_kind_t *kind,
79 svn_fs_root_t *root,
80 const char *path,
81 apr_pool_t *pool)
83 svn_error_t *serr;
84 svn_node_kind_t my_kind;
86 serr = svn_fs_check_path(&my_kind, root, path, pool);
88 /* Possibly trap other fs-errors here someday -- errors which may
89 simply indicate the path's nonexistence, rather than a critical
90 problem. */
91 if (serr && serr->apr_err == SVN_ERR_FS_NOT_DIRECTORY)
93 svn_error_clear(serr);
94 *kind = svn_node_none;
95 return NULL;
97 else if (serr)
99 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
100 apr_psprintf(pool, "Error checking kind of "
101 "path '%s' in repository",
102 path),
103 pool);
106 *kind = my_kind;
107 return NULL;
111 static int
112 parse_version_uri(dav_resource_combined *comb,
113 const char *path,
114 const char *label,
115 int use_checked_in)
117 const char *slash;
118 const char *created_rev_str;
120 /* format: CREATED_REV/REPOS_PATH */
122 /* ### what to do with LABEL and USE_CHECKED_IN ?? */
124 comb->res.type = DAV_RESOURCE_TYPE_VERSION;
125 comb->res.versioned = TRUE;
127 slash = ap_strchr_c(path, '/');
128 if (slash == NULL)
130 /* http://host.name/repos/$svn/ver/0
132 This URL form refers to the root path of the repository.
134 created_rev_str = apr_pstrndup(comb->res.pool, path, strlen(path));
135 comb->priv.root.rev = SVN_STR_TO_REV(created_rev_str);
136 comb->priv.repos_path = "/";
138 else if (slash == path)
140 /* the CREATED_REV was missing(?)
142 ### not sure this can happen, though, because it would imply two
143 ### slashes, yet those are cleaned out within get_resource
145 return TRUE;
147 else
149 apr_size_t len = slash - path;
151 created_rev_str = apr_pstrndup(comb->res.pool, path, len);
152 comb->priv.root.rev = SVN_STR_TO_REV(created_rev_str);
153 comb->priv.repos_path = slash;
156 /* if the CREATED_REV parsing blew, then propagate it. */
157 if (comb->priv.root.rev == SVN_INVALID_REVNUM)
158 return TRUE;
160 return FALSE;
164 static int
165 parse_history_uri(dav_resource_combined *comb,
166 const char *path,
167 const char *label,
168 int use_checked_in)
170 /* format: ??? */
172 /* ### what to do with LABEL and USE_CHECKED_IN ?? */
174 comb->res.type = DAV_RESOURCE_TYPE_HISTORY;
176 /* ### parse path */
177 comb->priv.repos_path = path;
179 return FALSE;
183 static int
184 parse_working_uri(dav_resource_combined *comb,
185 const char *path,
186 const char *label,
187 int use_checked_in)
189 const char *slash;
191 /* format: ACTIVITY_ID/REPOS_PATH */
193 /* ### what to do with LABEL and USE_CHECKED_IN ?? */
195 comb->res.type = DAV_RESOURCE_TYPE_WORKING;
196 comb->res.working = TRUE;
197 comb->res.versioned = TRUE;
199 slash = ap_strchr_c(path, '/');
201 /* This sucker starts with a slash. That's bogus. */
202 if (slash == path)
203 return TRUE;
205 if (slash == NULL)
207 /* There's no slash character in our path. Assume it's just an
208 ACTIVITY_ID pointing to the root path. That should be cool.
209 We'll just drop through to the normal case handling below. */
210 comb->priv.root.activity_id = apr_pstrdup(comb->res.pool, path);
211 comb->priv.repos_path = "/";
213 else
215 comb->priv.root.activity_id = apr_pstrndup(comb->res.pool, path,
216 slash - path);
217 comb->priv.repos_path = slash;
220 return FALSE;
224 static int
225 parse_activity_uri(dav_resource_combined *comb,
226 const char *path,
227 const char *label,
228 int use_checked_in)
230 /* format: ACTIVITY_ID */
232 /* ### what to do with LABEL and USE_CHECKED_IN ?? */
234 comb->res.type = DAV_RESOURCE_TYPE_ACTIVITY;
236 comb->priv.root.activity_id = path;
238 return FALSE;
242 static int
243 parse_vcc_uri(dav_resource_combined *comb,
244 const char *path,
245 const char *label,
246 int use_checked_in)
248 /* format: "default" (a singleton) */
250 if (strcmp(path, DAV_SVN__DEFAULT_VCC_NAME) != 0)
251 return TRUE;
253 if (label == NULL && !use_checked_in)
255 /* Version Controlled Configuration (baseline selector) */
257 /* ### mod_dav has a proper model for these. technically, they are
258 ### version-controlled resources (REGULAR), but that just monkeys
259 ### up a lot of stuff for us. use a PRIVATE for now. */
261 comb->res.type = DAV_RESOURCE_TYPE_PRIVATE; /* _REGULAR */
262 comb->priv.restype = DAV_SVN_RESTYPE_VCC;
264 comb->res.exists = TRUE;
265 comb->res.versioned = TRUE;
266 comb->res.baselined = TRUE;
268 /* NOTE: comb->priv.repos_path == NULL */
270 else
272 /* a specific Version Resource; in this case, a Baseline */
274 int revnum;
276 if (label != NULL)
278 revnum = SVN_STR_TO_REV(label); /* assume slash terminates */
279 if (!SVN_IS_VALID_REVNUM(revnum))
280 return TRUE; /* ### be nice to get better feedback */
282 else /* use_checked_in */
284 /* use the DAV:checked-in value of the VCC. this is always the
285 "latest" (or "youngest") revision. */
287 /* signal prep_version to look it up */
288 revnum = SVN_INVALID_REVNUM;
291 comb->res.type = DAV_RESOURCE_TYPE_VERSION;
293 /* exists? need to wait for now */
294 comb->res.versioned = TRUE;
295 comb->res.baselined = TRUE;
297 /* which baseline (revision tree) to access */
298 comb->priv.root.rev = revnum;
300 /* NOTE: comb->priv.repos_path == NULL */
301 /* NOTE: comb->priv.created_rev == SVN_INVALID_REVNUM */
304 return FALSE;
308 static int
309 parse_baseline_coll_uri(dav_resource_combined *comb,
310 const char *path,
311 const char *label,
312 int use_checked_in)
314 const char *slash;
315 int revnum;
317 /* format: REVISION/REPOS_PATH */
319 /* ### what to do with LABEL and USE_CHECKED_IN ?? */
321 slash = ap_strchr_c(path, '/');
322 if (slash == NULL)
323 slash = "/"; /* they are referring to the root of the BC */
324 else if (slash == path)
325 return TRUE; /* the REVISION was missing(?)
326 ### not sure this can happen, though, because
327 ### it would imply two slashes, yet those are
328 ### cleaned out within get_resource */
330 revnum = SVN_STR_TO_REV(path); /* assume slash terminates conversion */
331 if (!SVN_IS_VALID_REVNUM(revnum))
332 return TRUE; /* ### be nice to get better feedback */
334 /* ### mod_dav doesn't have a proper model for these. they are standard
335 ### VCRs, but we need some additional semantics attached to them.
336 ### need to figure out a way to label them as special. */
338 comb->res.type = DAV_RESOURCE_TYPE_REGULAR;
339 comb->res.versioned = TRUE;
340 comb->priv.root.rev = revnum;
341 comb->priv.repos_path = slash;
343 return FALSE;
347 static int
348 parse_baseline_uri(dav_resource_combined *comb,
349 const char *path,
350 const char *label,
351 int use_checked_in)
353 int revnum;
355 /* format: REVISION */
357 /* ### what to do with LABEL and USE_CHECKED_IN ?? */
359 revnum = SVN_STR_TO_REV(path);
360 if (!SVN_IS_VALID_REVNUM(revnum))
361 return TRUE; /* ### be nice to get better feedback */
363 /* create a Baseline resource (a special Version Resource) */
365 comb->res.type = DAV_RESOURCE_TYPE_VERSION;
367 /* exists? need to wait for now */
368 comb->res.versioned = TRUE;
369 comb->res.baselined = TRUE;
371 /* which baseline (revision tree) to access */
372 comb->priv.root.rev = revnum;
374 /* NOTE: comb->priv.repos_path == NULL */
375 /* NOTE: comb->priv.created_rev == SVN_INVALID_REVNUM */
377 return FALSE;
381 static int
382 parse_wrk_baseline_uri(dav_resource_combined *comb,
383 const char *path,
384 const char *label,
385 int use_checked_in)
387 const char *slash;
389 /* format: ACTIVITY_ID/REVISION */
391 /* ### what to do with LABEL and USE_CHECKED_IN ?? */
393 comb->res.type = DAV_RESOURCE_TYPE_WORKING;
394 comb->res.working = TRUE;
395 comb->res.versioned = TRUE;
396 comb->res.baselined = TRUE;
398 if ((slash = ap_strchr_c(path, '/')) == NULL
399 || slash == path
400 || slash[1] == '\0')
401 return TRUE;
403 comb->priv.root.activity_id = apr_pstrndup(comb->res.pool, path,
404 slash - path);
405 comb->priv.root.rev = SVN_STR_TO_REV(slash + 1);
407 /* NOTE: comb->priv.repos_path == NULL */
409 return FALSE;
413 static const struct special_defn
415 const char *name;
418 * COMB is the resource that we are constructing. Any elements that
419 * can be determined from the PATH may be set in COMB. However, further
420 * operations are not allowed (we don't want anything besides a parse
421 * error to occur).
423 * At a minimum, the parse function must set COMB->res.type and
424 * COMB->priv.repos_path.
426 * PATH does not contain a leading slash. Given "/root/$svn/xxx/the/path"
427 * as the request URI, the PATH variable will be "the/path"
429 int (*parse)(dav_resource_combined *comb, const char *path,
430 const char *label, int use_checked_in);
432 /* The number of subcompenents after the !svn/xxx/... before we
433 reach the actual path within the repository. */
434 int numcomponents;
436 /* Boolean: are the subcomponents followed by a repos path? */
437 int has_repos_path;
439 /* The private resource type for the /$svn/xxx/ collection. */
440 enum dav_svn_private_restype restype;
442 } special_subdirs[] =
444 { "ver", parse_version_uri, 1, TRUE, DAV_SVN_RESTYPE_VER_COLLECTION },
445 { "his", parse_history_uri, 0, FALSE, DAV_SVN_RESTYPE_HIS_COLLECTION },
446 { "wrk", parse_working_uri, 1, TRUE, DAV_SVN_RESTYPE_WRK_COLLECTION },
447 { "act", parse_activity_uri, 1, FALSE, DAV_SVN_RESTYPE_ACT_COLLECTION },
448 { "vcc", parse_vcc_uri, 1, FALSE, DAV_SVN_RESTYPE_VCC_COLLECTION },
449 { "bc", parse_baseline_coll_uri, 1, TRUE, DAV_SVN_RESTYPE_BC_COLLECTION },
450 { "bln", parse_baseline_uri, 1, FALSE, DAV_SVN_RESTYPE_BLN_COLLECTION },
451 { "wbl", parse_wrk_baseline_uri, 2, FALSE, DAV_SVN_RESTYPE_WBL_COLLECTION },
452 { NULL } /* sentinel */
457 * parse_uri: parse the provided URI into its various bits
459 * URI will contain a path relative to our configured root URI. It should
460 * not have a leading "/". The root is identified by "".
462 * On output: *COMB will contain all of the information parsed out of
463 * the URI -- the resource type, activity ID, path, etc.
465 * Note: this function will only parse the URI. Validation of the pieces,
466 * opening data stores, etc, are not part of this function.
468 * TRUE is returned if a parsing error occurred. FALSE for success.
470 static int
471 parse_uri(dav_resource_combined *comb,
472 const char *uri,
473 const char *label,
474 int use_checked_in)
476 const char *special_uri = comb->priv.repos->special_uri;
477 apr_size_t len1;
478 apr_size_t len2;
479 char ch;
481 len1 = strlen(uri);
482 len2 = strlen(special_uri);
483 if (len1 > len2
484 && ((ch = uri[len2]) == '/' || ch == '\0')
485 && memcmp(uri, special_uri, len2) == 0)
487 if (ch == '\0')
489 /* URI was "/root/!svn". It exists, but has restricted usage. */
490 comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
491 comb->priv.restype = DAV_SVN_RESTYPE_ROOT_COLLECTION;
493 else
495 const struct special_defn *defn;
497 /* skip past the "!svn/" prefix */
498 uri += len2 + 1;
499 len1 -= len2 + 1;
501 for (defn = special_subdirs ; defn->name != NULL; ++defn)
503 apr_size_t len3 = strlen(defn->name);
505 if (len1 >= len3 && memcmp(uri, defn->name, len3) == 0)
507 if (uri[len3] == '\0')
509 /* URI was "/root/!svn/XXX". The location exists, but
510 has restricted usage. */
511 comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
513 /* store the resource type so that we can PROPFIND
514 on this collection. */
515 comb->priv.restype = defn->restype;
517 else if (uri[len3] == '/')
519 if ((*defn->parse)(comb, uri + len3 + 1, label,
520 use_checked_in))
521 return TRUE;
523 else
525 /* e.g. "/root/!svn/activity" (we just know "act") */
526 return TRUE;
529 break;
533 /* if completed the loop, then it is an unrecognized subdir */
534 if (defn->name == NULL)
535 return TRUE;
538 else
540 /* Anything under the root, but not under "!svn". These are all
541 version-controlled resources. */
542 comb->res.type = DAV_RESOURCE_TYPE_REGULAR;
543 comb->res.versioned = TRUE;
545 /* The location of these resources corresponds directly to the URI,
546 and we keep the leading "/". */
547 comb->priv.repos_path = comb->priv.uri_path->data;
550 return FALSE;
554 static dav_error *
555 prep_regular(dav_resource_combined *comb)
557 apr_pool_t *pool = comb->res.pool;
558 dav_svn_repos *repos = comb->priv.repos;
559 svn_error_t *serr;
560 dav_error *derr;
561 svn_node_kind_t kind;
563 /* A REGULAR resource might have a specific revision already (e.g. if it
564 is part of a baseline collection). However, if it doesn't, then we
565 will assume that we need the youngest revision.
566 ### other cases besides a BC? */
567 if (comb->priv.root.rev == SVN_INVALID_REVNUM)
569 serr = svn_fs_youngest_rev(&comb->priv.root.rev, repos->fs, pool);
570 if (serr != NULL)
572 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
573 "Could not determine the proper "
574 "revision to access",
575 pool);
579 /* get the root of the tree */
580 serr = svn_fs_revision_root(&comb->priv.root.root, repos->fs,
581 comb->priv.root.rev, pool);
582 if (serr != NULL)
584 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
585 "Could not open the root of the "
586 "repository",
587 pool);
590 derr = fs_check_path(&kind, comb->priv.root.root,
591 comb->priv.repos_path, pool);
592 if (derr != NULL)
593 return derr;
595 comb->res.exists = (kind == svn_node_none) ? FALSE : TRUE;
596 comb->res.collection = (kind == svn_node_dir) ? TRUE : FALSE;
598 /* HACK: dav_get_resource_state() is making shortcut assumptions
599 about how to distinguish a null resource from a lock-null
600 resource. This is the only way to get around that problem.
601 Without it, it won't ever detect lock-nulls, and thus 'svn unlock
602 nonexistentURL' will always return 404's. */
603 if (! comb->res.exists)
604 comb->priv.r->path_info = (char *) "";
606 return NULL;
610 static dav_error *
611 prep_version(dav_resource_combined *comb)
613 svn_error_t *serr;
614 apr_pool_t *pool = comb->res.pool;
616 /* we are accessing the Version Resource by REV/PATH */
618 /* ### assert: .baselined = TRUE */
620 /* if we don't have a revision, then assume the youngest */
621 if (!SVN_IS_VALID_REVNUM(comb->priv.root.rev))
623 serr = svn_fs_youngest_rev(&comb->priv.root.rev,
624 comb->priv.repos->fs,
625 pool);
626 if (serr != NULL)
628 /* ### might not be a baseline */
630 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
631 "Could not fetch 'youngest' revision "
632 "to enable accessing the latest "
633 "baseline resource.",
634 pool);
638 /* ### baselines have no repos_path, and we don't need to open
639 ### a root (yet). we just needed to ensure that we have the proper
640 ### revision number. */
642 if (!comb->priv.root.root)
644 serr = svn_fs_revision_root(&comb->priv.root.root,
645 comb->priv.repos->fs,
646 comb->priv.root.rev,
647 pool);
648 if (serr != NULL)
650 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
651 "Could not open a revision root.",
652 pool);
656 /* ### we should probably check that the revision is valid */
657 comb->res.exists = TRUE;
659 /* Set up the proper URI. Most likely, we arrived here via a VCC,
660 so the URI will be incorrect. Set the canonical form. */
661 /* ### assuming a baseline */
662 comb->res.uri = dav_svn__build_uri(comb->priv.repos,
663 DAV_SVN__BUILD_URI_BASELINE,
664 comb->priv.root.rev, NULL,
665 0 /* add_href */,
666 pool);
668 return NULL;
672 static dav_error *
673 prep_history(dav_resource_combined *comb)
675 return NULL;
679 static dav_error *
680 prep_working(dav_resource_combined *comb)
682 const char *txn_name = dav_svn__get_txn(comb->priv.repos,
683 comb->priv.root.activity_id);
684 apr_pool_t *pool = comb->res.pool;
685 svn_error_t *serr;
686 dav_error *derr;
687 svn_node_kind_t kind;
689 if (txn_name == NULL)
691 /* ### HTTP_BAD_REQUEST is probably wrong */
692 return dav_new_error(pool, HTTP_BAD_REQUEST, 0,
693 "An unknown activity was specified in the URL. "
694 "This is generally caused by a problem in the "
695 "client software.");
697 comb->priv.root.txn_name = txn_name;
699 /* get the FS transaction, given its name */
700 serr = svn_fs_open_txn(&comb->priv.root.txn, comb->priv.repos->fs, txn_name,
701 pool);
702 if (serr != NULL)
704 if (serr->apr_err == SVN_ERR_FS_NO_SUCH_TRANSACTION)
706 svn_error_clear(serr);
707 return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0,
708 "An activity was specified and found, but the "
709 "corresponding SVN FS transaction was not "
710 "found.");
712 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
713 "Could not open the SVN FS transaction "
714 "corresponding to the specified activity.",
715 pool);
718 if (comb->res.baselined)
720 /* a Working Baseline */
722 /* if the transaction exists, then the working resource exists */
723 comb->res.exists = TRUE;
725 return NULL;
728 /* Set the txn author if not previously set. Protect against multi-author
729 * commits by verifying authenticated user associated with the current
730 * request is the same as the txn author.
731 * Note that anonymous requests are being excluded as being a change
732 * in author, because the commit may touch areas of the repository
733 * that are anonymous writeable as well as areas that are not.
735 if (comb->priv.repos->username)
737 svn_string_t *current_author;
738 svn_string_t request_author;
740 serr = svn_fs_txn_prop(&current_author, comb->priv.root.txn,
741 SVN_PROP_REVISION_AUTHOR, pool);
742 if (serr != NULL)
744 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
745 "Failed to retrieve author of the SVN FS transaction "
746 "corresponding to the specified activity.",
747 pool);
750 request_author.data = comb->priv.repos->username;
751 request_author.len = strlen(request_author.data);
752 if (!current_author)
754 serr = svn_fs_change_txn_prop(comb->priv.root.txn,
755 SVN_PROP_REVISION_AUTHOR, &request_author, pool);
756 if (serr != NULL)
758 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
759 "Failed to set the author of the SVN FS transaction "
760 "corresponding to the specified activity.",
761 pool);
764 else if (!svn_string_compare(current_author, &request_author))
766 return dav_new_error(pool, HTTP_NOT_IMPLEMENTED, 0,
767 "Multi-author commits not supported.");
771 /* get the root of the tree */
772 serr = svn_fs_txn_root(&comb->priv.root.root, comb->priv.root.txn, pool);
773 if (serr != NULL)
775 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
776 "Could not open the (transaction) root of "
777 "the repository",
778 pool);
781 derr = fs_check_path(&kind, comb->priv.root.root,
782 comb->priv.repos_path, pool);
783 if (derr != NULL)
784 return derr;
786 comb->res.exists = (kind == svn_node_none) ? FALSE : TRUE;
787 comb->res.collection = (kind == svn_node_dir) ? TRUE : FALSE;
789 return NULL;
793 static dav_error *
794 prep_activity(dav_resource_combined *comb)
796 const char *txn_name = dav_svn__get_txn(comb->priv.repos,
797 comb->priv.root.activity_id);
799 comb->priv.root.txn_name = txn_name;
800 comb->res.exists = txn_name != NULL;
802 return NULL;
806 static dav_error *
807 prep_private(dav_resource_combined *comb)
809 if (comb->priv.restype == DAV_SVN_RESTYPE_VCC)
811 /* ### what to do */
813 /* else nothing to do (### for now) */
815 return NULL;
819 static const struct res_type_handler
821 dav_resource_type type;
822 dav_error * (*prep)(dav_resource_combined *comb);
824 } res_type_handlers[] =
826 /* skip UNKNOWN */
827 { DAV_RESOURCE_TYPE_REGULAR, prep_regular },
828 { DAV_RESOURCE_TYPE_VERSION, prep_version },
829 { DAV_RESOURCE_TYPE_HISTORY, prep_history },
830 { DAV_RESOURCE_TYPE_WORKING, prep_working },
831 /* skip WORKSPACE */
832 { DAV_RESOURCE_TYPE_ACTIVITY, prep_activity },
833 { DAV_RESOURCE_TYPE_PRIVATE, prep_private },
835 { 0, NULL } /* sentinel */
840 ** ### docco...
842 ** Set .exists and .collection
843 ** open other, internal bits...
845 static dav_error *
846 prep_resource(dav_resource_combined *comb)
848 const struct res_type_handler *scan;
850 for (scan = res_type_handlers; scan->prep != NULL; ++scan)
852 if (comb->res.type == scan->type)
853 return (*scan->prep)(comb);
856 return dav_new_error(comb->res.pool, HTTP_INTERNAL_SERVER_ERROR, 0,
857 "DESIGN FAILURE: unknown resource type");
861 static dav_resource *
862 create_private_resource(const dav_resource *base,
863 enum dav_svn_private_restype restype)
865 dav_resource_combined *comb;
866 svn_stringbuf_t *path;
867 const struct special_defn *defn;
869 for (defn = special_subdirs; defn->name != NULL; ++defn)
870 if (defn->restype == restype)
871 break;
872 /* assert: defn->name != NULL */
874 path = svn_stringbuf_createf(base->pool, "/%s/%s",
875 base->info->repos->special_uri, defn->name);
877 comb = apr_pcalloc(base->pool, sizeof(*comb));
879 /* ### can/should we leverage prep_resource */
881 comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
883 comb->res.exists = TRUE;
884 comb->res.collection = TRUE; /* ### always true? */
885 /* versioned = baselined = working = FALSE */
887 comb->res.uri = apr_pstrcat(base->pool, base->info->repos->root_path,
888 path->data, NULL);
889 comb->res.info = &comb->priv;
890 comb->res.hooks = &dav_svn__hooks_repository;
891 comb->res.pool = base->pool;
893 comb->priv.uri_path = path;
894 comb->priv.repos = base->info->repos;
895 comb->priv.root.rev = SVN_INVALID_REVNUM;
896 return &comb->res;
900 static void log_warning(void *baton, svn_error_t *err)
902 request_rec *r = baton;
904 /* ### hmm. the FS is cleaned up at request cleanup time. "r" might
905 ### not really be valid. we should probably put the FS into a
906 ### subpool to ensure it gets cleaned before the request.
908 ### is there a good way to create and use a subpool for all
909 ### of our functions ... ??
912 ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, "%s", err->message);
916 AP_MODULE_DECLARE(dav_error *)
917 dav_svn_split_uri(request_rec *r,
918 const char *uri_to_split,
919 const char *root_path,
920 const char **cleaned_uri,
921 int *trailing_slash,
922 const char **repos_name,
923 const char **relative_path,
924 const char **repos_path)
926 apr_size_t len1;
927 int had_slash;
928 const char *fs_path;
929 const char *fs_parent_path;
930 const char *relative;
931 char *uri;
933 /* one of these is NULL, the other non-NULL. */
934 fs_path = dav_svn__get_fs_path(r);
935 fs_parent_path = dav_svn__get_fs_parent_path(r);
937 if ((fs_path == NULL) && (fs_parent_path == NULL))
939 /* ### are SVN_ERR_APMOD codes within the right numeric space? */
940 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
941 SVN_ERR_APMOD_MISSING_PATH_TO_FS,
942 "The server is misconfigured: "
943 "either an SVNPath or SVNParentPath "
944 "directive is required to specify the location "
945 "of this resource's repository.");
948 /* make a copy so that we can do some work on it */
949 uri = apr_pstrdup(r->pool, uri_to_split);
951 /* remove duplicate slashes, and make sure URI has no trailing '/' */
952 ap_no2slash(uri);
953 len1 = strlen(uri);
954 had_slash = (len1 > 0 && uri[len1 - 1] == '/');
955 if (len1 > 1 && had_slash)
956 uri[len1 - 1] = '\0';
958 if (had_slash)
959 *trailing_slash = TRUE;
960 else
961 *trailing_slash = FALSE;
963 /* return the first item. */
964 *cleaned_uri = apr_pstrdup(r->pool, uri);
966 /* The URL space defined by the SVN provider is always a virtual
967 space. Construct the path relative to the configured Location
968 (root_path). So... the relative location is simply the URL used,
969 skipping the root_path.
971 Note: mod_dav has canonialized root_path. It will not have a trailing
972 slash (unless it is "/").
974 Note: given a URI of /something and a root of /some, then it is
975 impossible to be here (and end up with "thing"). This is simply
976 because we control /some and are dispatched to here for its
977 URIs. We do not control /something, so we don't get here. Or,
978 if we *do* control /something, then it is for THAT root.
980 relative = ap_stripprefix(uri, root_path);
982 /* We want a leading slash on the path specified by <relative>. This
983 will almost always be the case since root_path does not have a trailing
984 slash. However, if the root is "/", then the slash will be removed
985 from <relative>. Backing up a character will put the leading slash
986 back.
988 Watch out for the empty string! This can happen when URI == ROOT_PATH.
989 We simply turn the path into "/" for this case. */
990 if (*relative == '\0')
991 relative = "/";
992 else if (*relative != '/')
993 --relative;
994 /* ### need a better name... it isn't "relative" because of the leading
995 ### slash. something about SVN-private-path */
997 /* Depending on whether SVNPath or SVNParentPath was used, we need
998 to compute 'relative' and 'repos_name' differently. */
1000 /* Normal case: the SVNPath command was used to specify a
1001 particular repository. */
1002 if (fs_path != NULL)
1004 /* the repos_name is the last component of root_path. */
1005 *repos_name = svn_path_basename(root_path, r->pool);
1007 /* 'relative' is already correct for SVNPath; the root_path
1008 already contains the name of the repository, so relative is
1009 everything beyond that. */
1012 else
1014 /* SVNParentPath was used instead: assume the first component of
1015 'relative' is the name of a repository. */
1016 const char *magic_component, *magic_end;
1018 /* A repository name is required here.
1019 Remember that 'relative' always starts with a "/". */
1020 if (relative[1] == '\0')
1022 /* ### are SVN_ERR_APMOD codes within the right numeric space? */
1023 return dav_new_error(r->pool, HTTP_FORBIDDEN,
1024 SVN_ERR_APMOD_MALFORMED_URI,
1025 "The URI does not contain the name "
1026 "of a repository.");
1029 magic_end = ap_strchr_c(relative + 1, '/');
1030 if (!magic_end)
1032 /* ### Request was for parent directory with no trailing
1033 slash; we probably ought to just redirect to same with
1034 trailing slash appended. */
1035 magic_component = relative + 1;
1036 relative = "/";
1038 else
1040 magic_component = apr_pstrndup(r->pool, relative + 1,
1041 magic_end - relative - 1);
1042 relative = magic_end;
1045 /* return answer */
1046 *repos_name = magic_component;
1049 /* We can return 'relative' at this point too. */
1050 *relative_path = apr_pstrdup(r->pool, relative);
1052 /* Code to remove the !svn junk from the front of the relative path,
1053 mainly stolen from parse_uri(). This code assumes that
1054 the 'relative' string being parsed doesn't start with '/'. */
1055 relative++;
1058 const char *special_uri = dav_svn__get_special_uri(r);
1059 apr_size_t len2;
1060 char ch;
1062 len1 = strlen(relative);
1063 len2 = strlen(special_uri);
1064 if (len1 > len2
1065 && ((ch = relative[len2]) == '/' || ch == '\0')
1066 && memcmp(relative, special_uri, len2) == 0)
1068 if (ch == '\0')
1070 /* relative is just "!svn", which is malformed. */
1071 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1072 SVN_ERR_APMOD_MALFORMED_URI,
1073 "Nothing follows the svn special_uri.");
1075 else
1077 const struct special_defn *defn;
1079 /* skip past the "!svn/" prefix */
1080 relative += len2 + 1;
1081 len1 -= len2 + 1;
1083 for (defn = special_subdirs ; defn->name != NULL; ++defn)
1085 apr_size_t len3 = strlen(defn->name);
1087 if (len1 >= len3 && memcmp(relative, defn->name, len3) == 0)
1089 /* Found a matching special dir. */
1091 if (relative[len3] == '\0')
1093 /* relative is "!svn/xxx" */
1094 if (defn->numcomponents == 0)
1095 *repos_path = NULL;
1096 else
1097 return
1098 dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1099 SVN_ERR_APMOD_MALFORMED_URI,
1100 "Missing info after special_uri.");
1102 else if (relative[len3] == '/')
1104 /* Skip past defn->numcomponents components,
1105 return everything beyond that.*/
1106 int j;
1107 const char *end = NULL, *start = relative + len3 + 1;
1109 for (j = 0; j < defn->numcomponents; j++)
1111 end = ap_strchr_c(start, '/');
1112 if (! end)
1113 break;
1114 start = end + 1;
1117 if (! end)
1119 /* Did we break from the loop prematurely? */
1120 if (j != (defn->numcomponents - 1))
1121 return
1122 dav_new_error(r->pool,
1123 HTTP_INTERNAL_SERVER_ERROR,
1124 SVN_ERR_APMOD_MALFORMED_URI,
1125 "Not enough components"
1126 " after special_uri.");
1128 if (! defn->has_repos_path)
1129 /* It's okay to not have found a slash. */
1130 *repos_path = NULL;
1131 else
1132 *repos_path = "/";
1134 else
1136 /* Found a slash after the special components. */
1137 *repos_path = apr_pstrdup(r->pool, start);
1140 else
1142 return
1143 dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1144 SVN_ERR_APMOD_MALFORMED_URI,
1145 "Unknown data after special_uri.");
1148 break;
1152 if (defn->name == NULL)
1153 return
1154 dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1155 SVN_ERR_APMOD_MALFORMED_URI,
1156 "Couldn't match subdir after special_uri.");
1159 else
1161 /* There's no "!svn/" at all, so the relative path is already
1162 a valid path within the repository. */
1163 *repos_path = apr_pstrdup(r->pool, relative);
1167 return NULL;
1171 /* Context for cleanup handler. */
1172 struct cleanup_fs_access_baton
1174 svn_fs_t *fs;
1175 apr_pool_t *pool;
1179 /* Pool cleanup handler. Make sure fs's access ctx points to NULL
1180 when request pool is destroyed. */
1181 static apr_status_t
1182 cleanup_fs_access(void *data)
1184 svn_error_t *serr;
1185 struct cleanup_fs_access_baton *baton = data;
1187 serr = svn_fs_set_access(baton->fs, NULL);
1188 if (serr)
1190 ap_log_perror(APLOG_MARK, APLOG_ERR, serr->apr_err, baton->pool,
1191 "cleanup_fs_access: error clearing fs access context");
1192 svn_error_clear(serr);
1195 return APR_SUCCESS;
1199 /* Helper func to construct a special 'parentpath' private resource. */
1200 static dav_error *
1201 get_parentpath_resource(request_rec *r,
1202 const char *root_path,
1203 dav_resource **resource)
1205 const char *new_uri;
1206 dav_svn_root *droot = apr_pcalloc(r->pool, sizeof(*droot));
1207 dav_svn_repos *repos = apr_pcalloc(r->pool, sizeof(*repos));
1208 dav_resource_combined *comb = apr_pcalloc(r->pool, sizeof(*comb));
1209 apr_size_t len = strlen(r->uri);
1211 comb->res.exists = TRUE;
1212 comb->res.collection = TRUE;
1213 comb->res.uri = apr_pstrdup(r->pool, r->uri);
1214 comb->res.info = &comb->priv;
1215 comb->res.hooks = &dav_svn__hooks_repository;
1216 comb->res.pool = r->pool;
1217 comb->res.type = DAV_RESOURCE_TYPE_PRIVATE;
1219 comb->priv.restype = DAV_SVN_RESTYPE_PARENTPATH_COLLECTION;
1220 comb->priv.r = r;
1221 comb->priv.repos_path = "Collection of Repositories";
1222 comb->priv.root = *droot;
1223 droot->rev = SVN_INVALID_REVNUM;
1225 comb->priv.repos = repos;
1226 repos->pool = r->pool;
1227 repos->xslt_uri = dav_svn__get_xslt_uri(r);
1228 repos->autoversioning = dav_svn__get_autoversioning_flag(r);
1229 repos->bulk_updates = dav_svn__get_bulk_updates_flag(r);
1230 repos->base_url = ap_construct_url(r->pool, "", r);
1231 repos->special_uri = dav_svn__get_special_uri(r);
1232 repos->username = r->user;
1233 repos->client_capabilities = apr_hash_make(repos->pool);
1235 /* Make sure this type of resource always has a trailing slash; if
1236 not, redirect to a URI that does. */
1237 if (r->uri[len-1] != '/')
1239 new_uri = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri),
1240 "/", NULL);
1241 apr_table_setn(r->headers_out, "Location",
1242 ap_construct_url(r->pool, new_uri, r));
1243 return dav_new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0,
1244 "Requests for a collection must have a "
1245 "trailing slash on the URI.");
1248 /* No other "prepping" of resource needs to happen -- no opening
1249 of a repository or anything like that, because, well, there's
1250 no repository to open. */
1251 *resource = &comb->res;
1252 return NULL;
1255 /* --------------- Borrowed from httpd's mod_negotiation.c -------------- */
1257 typedef struct accept_rec {
1258 char *name; /* MUST be lowercase */
1259 float quality;
1260 } accept_rec;
1263 * Get a single Accept-encoding line from ACCEPT_LINE, and place the
1264 * information we have parsed out of it into RESULT.
1267 static const char *get_entry(apr_pool_t *p, accept_rec *result,
1268 const char *accept_line)
1270 result->quality = 1.0f;
1273 * Note that this handles what I gather is the "old format",
1275 * Accept: text/html text/plain moo/zot
1277 * without any compatibility kludges --- if the token after the
1278 * MIME type begins with a semicolon, we know we're looking at parms,
1279 * otherwise, we know we aren't. (So why all the pissing and moaning
1280 * in the CERN server code? I must be missing something).
1283 result->name = ap_get_token(p, &accept_line, 0);
1284 ap_str_tolower(result->name); /* You want case insensitive,
1285 * you'll *get* case insensitive.
1288 while (*accept_line == ';')
1290 /* Parameters ... */
1292 char *parm;
1293 char *cp;
1294 char *end;
1296 ++accept_line;
1297 parm = ap_get_token(p, &accept_line, 1);
1299 /* Look for 'var = value' --- and make sure the var is in lcase. */
1301 for (cp = parm; (*cp && !apr_isspace(*cp) && *cp != '='); ++cp)
1303 *cp = apr_tolower(*cp);
1306 if (!*cp)
1308 continue; /* No '='; just ignore it. */
1311 *cp++ = '\0'; /* Delimit var */
1312 while (*cp && (apr_isspace(*cp) || *cp == '='))
1314 ++cp;
1317 if (*cp == '"')
1319 ++cp;
1320 for (end = cp;
1321 (*end && *end != '\n' && *end != '\r' && *end != '\"');
1322 end++);
1324 else
1326 for (end = cp; (*end && !apr_isspace(*end)); end++);
1328 if (*end)
1330 *end = '\0'; /* strip ending quote or return */
1332 ap_str_tolower(cp);
1334 if (parm[0] == 'q'
1335 && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0')))
1337 result->quality = (float) atof(cp);
1341 if (*accept_line == ',')
1343 ++accept_line;
1346 return accept_line;
1349 /* @a accept_line is the Accept-Encoding header, which is of the
1350 format:
1352 Accept-Encoding: name; q=N;
1354 This function will return an array of accept_rec structures that
1355 contain the accepted encodings and the quality each one has
1356 associated with them.
1358 static apr_array_header_t *do_header_line(apr_pool_t *p,
1359 const char *accept_line)
1361 apr_array_header_t *accept_recs;
1363 if (!accept_line)
1364 return NULL;
1366 accept_recs = apr_array_make(p, 10, sizeof(accept_rec));
1368 while (*accept_line)
1370 accept_rec *prefs = (accept_rec *) apr_array_push(accept_recs);
1371 accept_line = get_entry(p, prefs, accept_line);
1374 return accept_recs;
1377 /* ---------------------------------------------------------------------- */
1380 /* qsort comparison function for the quality field of the accept_rec
1381 structure */
1382 static int sort_encoding_pref(const void *accept_rec1, const void *accept_rec2)
1384 float diff = ((const accept_rec *) accept_rec1)->quality -
1385 ((const accept_rec *) accept_rec2)->quality;
1386 return (diff == 0 ? 0 : (diff > 0 ? -1 : 1));
1389 /* Parse and handle any possible Accept-Encoding header that has been
1390 sent as part of the request. */
1391 static void
1392 negotiate_encoding_prefs(request_rec *r, int *svndiff_version)
1394 /* It would be nice if mod_negotiation
1395 <http://httpd.apache.org/docs-2.1/mod/mod_negotiation.html> could
1396 handle the Accept-Encoding header parsing for us. Sadly, it
1397 looks like its data structures and routines are private (see
1398 httpd/modules/mappers/mod_negotiation.c). Thus, we duplicate the
1399 necessary ones in this file. */
1400 int i;
1401 const apr_array_header_t *encoding_prefs;
1402 encoding_prefs = do_header_line(r->pool,
1403 apr_table_get(r->headers_in,
1404 "Accept-Encoding"));
1406 if (!encoding_prefs || apr_is_empty_array(encoding_prefs))
1408 *svndiff_version = 0;
1409 return;
1412 *svndiff_version = 0;
1413 qsort(encoding_prefs->elts, (size_t) encoding_prefs->nelts,
1414 sizeof(accept_rec), sort_encoding_pref);
1415 for (i = 0; i < encoding_prefs->nelts; i++)
1417 struct accept_rec rec = APR_ARRAY_IDX(encoding_prefs, i,
1418 struct accept_rec);
1419 if (strcmp(rec.name, "svndiff1") == 0)
1421 *svndiff_version = 1;
1422 break;
1424 else if (strcmp(rec.name, "svndiff") == 0)
1426 *svndiff_version = 0;
1427 break;
1433 /* The only two possible values for a capability. */
1434 static const char *capability_yes = "yes";
1435 static const char *capability_no = "no";
1437 /* Convert CAPABILITIES, a hash table mapping 'const char *' keys to
1438 * "yes" or "no" values, to a list of all keys whose value is "yes".
1439 * Return the list, allocated in POOL, and use POOL for all temporary
1440 * allocation.
1442 static apr_array_header_t *
1443 capabilities_as_list(apr_hash_t *capabilities, apr_pool_t *pool)
1445 apr_array_header_t *list = apr_array_make(pool, apr_hash_count(capabilities),
1446 sizeof(char *));
1447 apr_hash_index_t *hi;
1449 for (hi = apr_hash_first(pool, capabilities); hi; hi = apr_hash_next(hi))
1451 const void *key;
1452 void *val;
1453 apr_hash_this(hi, &key, NULL, &val);
1454 if (strcmp((const char *) val, "yes") == 0)
1455 APR_ARRAY_PUSH(list, const char *) = key;
1458 return list;
1462 static dav_error *
1463 get_resource(request_rec *r,
1464 const char *root_path,
1465 const char *label,
1466 int use_checked_in,
1467 dav_resource **resource)
1469 const char *fs_path;
1470 const char *repo_name;
1471 const char *xslt_uri;
1472 const char *fs_parent_path;
1473 dav_resource_combined *comb;
1474 dav_svn_repos *repos;
1475 const char *cleaned_uri;
1476 const char *repos_name;
1477 const char *relative;
1478 const char *repos_path;
1479 const char *repos_key;
1480 const char *version_name;
1481 svn_error_t *serr;
1482 dav_error *err;
1483 int had_slash;
1484 dav_locktoken_list *ltl;
1485 struct cleanup_fs_access_baton *cleanup_baton;
1486 void *userdata;
1488 repo_name = dav_svn__get_repo_name(r);
1489 xslt_uri = dav_svn__get_xslt_uri(r);
1490 fs_parent_path = dav_svn__get_fs_parent_path(r);
1492 /* Special case: detect and build the SVNParentPath as a unique type
1493 of private resource, iff the SVNListParentPath directive is 'on'. */
1494 if (fs_parent_path && dav_svn__get_list_parentpath_flag(r))
1496 char *uri = apr_pstrdup(r->pool, r->uri);
1497 char *parentpath = apr_pstrdup(r->pool, root_path);
1498 apr_size_t uri_len = strlen(uri);
1499 apr_size_t parentpath_len = strlen(parentpath);
1501 if (uri[uri_len-1] == '/')
1502 uri[uri_len-1] = '\0';
1504 if (parentpath[parentpath_len-1] == '/')
1505 parentpath[parentpath_len-1] = '\0';
1507 if (strcmp(parentpath, uri) == 0)
1509 err = get_parentpath_resource(r, root_path, resource);
1510 if (err)
1511 return err;
1512 return NULL;
1516 /* This does all the work of interpreting/splitting the request uri. */
1517 err = dav_svn_split_uri(r, r->uri, root_path,
1518 &cleaned_uri, &had_slash,
1519 &repos_name, &relative, &repos_path);
1520 if (err)
1521 return err;
1523 /* The path that we will eventually try to open as an svn
1524 repository. Normally defined by the SVNPath directive. */
1525 fs_path = dav_svn__get_fs_path(r);
1527 /* If the SVNParentPath directive was used instead... */
1528 if (fs_parent_path != NULL)
1530 /* ...then the URL to the repository is actually one implicit
1531 component longer... */
1532 root_path = svn_path_join(root_path, repos_name, r->pool);
1533 /* ...and we need to specify exactly what repository to open. */
1534 fs_path = svn_path_join(fs_parent_path, repos_name, r->pool);
1537 /* Start building and filling a 'combination' object. */
1538 comb = apr_pcalloc(r->pool, sizeof(*comb));
1539 comb->res.info = &comb->priv;
1540 comb->res.hooks = &dav_svn__hooks_repository;
1541 comb->res.pool = r->pool;
1542 comb->res.uri = cleaned_uri;
1544 /* Original request, off which to generate subrequests later. */
1545 comb->priv.r = r;
1547 /* ### ugly hack to carry over Content-Type data to the open_stream, which
1548 ### does not have access to the request headers. */
1550 const char *ct = apr_table_get(r->headers_in, "content-type");
1552 comb->priv.is_svndiff =
1553 ct != NULL
1554 && strcmp(ct, SVN_SVNDIFF_MIME_TYPE) == 0;
1557 negotiate_encoding_prefs(r, &comb->priv.svndiff_version);
1559 /* ### and another hack for computing diffs to send to the client */
1560 comb->priv.delta_base = apr_table_get(r->headers_in,
1561 SVN_DAV_DELTA_BASE_HEADER);
1563 /* Gather any options requested by an svn client. */
1564 comb->priv.svn_client_options = apr_table_get(r->headers_in,
1565 SVN_DAV_OPTIONS_HEADER);
1567 /* See if the client sent a custom 'version name' request header. */
1568 version_name = apr_table_get(r->headers_in, SVN_DAV_VERSION_NAME_HEADER);
1569 comb->priv.version_name
1570 = version_name ? SVN_STR_TO_REV(version_name): SVN_INVALID_REVNUM;
1572 /* Remember checksums, if any. */
1573 comb->priv.base_checksum =
1574 apr_table_get(r->headers_in, SVN_DAV_BASE_FULLTEXT_MD5_HEADER);
1575 comb->priv.result_checksum =
1576 apr_table_get(r->headers_in, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER);
1578 /* "relative" is part of the "uri" string, so it has the proper
1579 lifetime to store here. */
1580 /* ### that comment no longer applies. we're creating a string with its
1581 ### own lifetime now. so WHY are we using a string? hmm... */
1582 comb->priv.uri_path = svn_stringbuf_create(relative, r->pool);
1584 /* initialize this until we put something real here */
1585 comb->priv.root.rev = SVN_INVALID_REVNUM;
1587 /* create the repository structure and stash it away */
1588 repos = apr_pcalloc(r->pool, sizeof(*repos));
1589 repos->pool = r->pool;
1591 comb->priv.repos = repos;
1593 /* We are assuming the root_path will live at least as long as this
1594 resource. Considering that it typically comes from the per-dir
1595 config in mod_dav, this is valid for now. */
1596 repos->root_path = svn_path_uri_encode(root_path, r->pool);
1598 /* where is the SVN FS for this resource? */
1599 repos->fs_path = fs_path;
1601 /* A name for the repository */
1602 repos->repo_name = repo_name;
1604 /* The repository filesystem basename */
1605 repos->repo_basename = repos_name;
1607 /* An XSL transformation */
1608 repos->xslt_uri = xslt_uri;
1610 /* Is autoversioning active in this repos? */
1611 repos->autoversioning = dav_svn__get_autoversioning_flag(r);
1613 /* Are bulk updates allowed in this repos? */
1614 repos->bulk_updates = dav_svn__get_bulk_updates_flag(r);
1616 /* Path to activities database */
1617 repos->activities_db = dav_svn__get_activities_db(r);
1618 if (repos->activities_db == NULL)
1619 /* If not specified, use default ($repos/dav/activities.d). */
1620 repos->activities_db = svn_path_join(repos->fs_path,
1621 DEFAULT_ACTIVITY_DB,
1622 r->pool);
1623 else if (fs_parent_path != NULL)
1624 /* If this is a ParentPath-based repository, treat the specified
1625 path as a similar parent directory. */
1626 repos->activities_db = svn_path_join(repos->activities_db,
1627 svn_path_basename(repos->fs_path,
1628 r->pool),
1629 r->pool);
1631 /* Remember various bits for later URL construction */
1632 repos->base_url = ap_construct_url(r->pool, "", r);
1633 repos->special_uri = dav_svn__get_special_uri(r);
1635 /* Remember who is making this request */
1636 repos->username = r->user;
1638 /* Allocate room for capabilities, but don't search for any until
1639 we know that this is a Subversion client. */
1640 repos->client_capabilities = apr_hash_make(repos->pool);
1642 /* Remember if the requesting client is a Subversion client, and if
1643 so, what its capabilities are. */
1645 const char *val = apr_table_get(r->headers_in, "User-Agent");
1647 if (val && (ap_strstr_c(val, "SVN/") == val))
1649 repos->is_svn_client = TRUE;
1651 /* Client capabilities are self-reported. There is no
1652 guarantee the client actually has the capabilities it says
1653 it has, we just assume it is in the client's interests to
1654 report accurately. Also, we only remember the capabilities
1655 the server cares about (even though the client may send
1656 more than that). */
1658 /* Start out assuming no capabilities. */
1659 apr_hash_set(repos->client_capabilities, SVN_RA_CAPABILITY_MERGEINFO,
1660 APR_HASH_KEY_STRING, capability_no);
1662 /* Then see what we can find. */
1663 val = apr_table_get(r->headers_in, "DAV");
1664 if (val)
1666 apr_array_header_t *vals
1667 = svn_cstring_split(val, ",", TRUE, r->pool);
1669 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_MERGEINFO,
1670 vals))
1672 apr_hash_set(repos->client_capabilities,
1673 SVN_RA_CAPABILITY_MERGEINFO,
1674 APR_HASH_KEY_STRING, capability_yes);
1680 /* Retrieve/cache open repository */
1681 repos_key = apr_pstrcat(r->pool, "mod_dav_svn:", fs_path, NULL);
1682 apr_pool_userdata_get(&userdata, repos_key, r->connection->pool);
1683 repos->repos = userdata;
1684 if (repos->repos == NULL)
1686 serr = svn_repos_open(&(repos->repos), fs_path, r->connection->pool);
1687 if (serr != NULL)
1689 /* The error returned by svn_repos_open might contain the
1690 actual path to the failed repository. We don't want to
1691 leak that path back to the client, because that would be
1692 a security risk, but we do want to log the real error on
1693 the server side. */
1694 return dav_svn__sanitize_error(serr, "Could not open the requested "
1695 "SVN filesystem",
1696 HTTP_INTERNAL_SERVER_ERROR, r);
1699 /* Cache the open repos for the next request on this connection */
1700 apr_pool_userdata_set(repos->repos, repos_key,
1701 NULL, r->connection->pool);
1703 /* Store the capabilities of the current connection, making sure
1704 to use the same pool repos->repos itself was created in. */
1705 serr = svn_repos_remember_client_capabilities
1706 (repos->repos, capabilities_as_list(repos->client_capabilities,
1707 r->connection->pool));
1708 if (serr != NULL)
1710 return dav_svn__sanitize_error(serr,
1711 "Error storing client capabilities "
1712 "in repos object",
1713 HTTP_INTERNAL_SERVER_ERROR, r);
1717 /* cache the filesystem object */
1718 repos->fs = svn_repos_fs(repos->repos);
1720 /* capture warnings during cleanup of the FS */
1721 svn_fs_set_warning_func(repos->fs, log_warning, r);
1723 /* if an authenticated username is present, attach it to the FS */
1724 if (r->user)
1726 svn_fs_access_t *access_ctx;
1728 /* The fs is cached in connection->pool, but the fs access
1729 context lives in r->pool. Because the username or token
1730 could change on each request, we need to make sure that the
1731 fs points to a NULL access context after the request is gone. */
1732 cleanup_baton = apr_pcalloc(r->pool, sizeof(*cleanup_baton));
1733 cleanup_baton->pool = r->pool;
1734 cleanup_baton->fs = repos->fs;
1735 apr_pool_cleanup_register(r->pool, cleanup_baton, cleanup_fs_access,
1736 apr_pool_cleanup_null);
1738 /* Create an access context based on the authenticated username. */
1739 serr = svn_fs_create_access(&access_ctx, r->user, r->pool);
1740 if (serr)
1742 return dav_svn__sanitize_error(serr,
1743 "Could not create fs access context",
1744 HTTP_INTERNAL_SERVER_ERROR, r);
1747 /* Attach the access context to the fs. */
1748 serr = svn_fs_set_access(repos->fs, access_ctx);
1749 if (serr)
1751 return dav_svn__sanitize_error(serr, "Could not attach access "
1752 "context to fs",
1753 HTTP_INTERNAL_SERVER_ERROR, r);
1757 /* Look for locktokens in the "If:" request header. */
1758 err = dav_get_locktoken_list(r, &ltl);
1760 /* dav_get_locktoken_list claims to return a NULL list when no
1761 locktokens are present. But it actually throws this error
1762 instead! So we're deliberately trapping/ignoring it.
1764 This is a workaround for a bug in mod_dav. Remove this when the
1765 bug is fixed in mod_dav. See Subversion Issue #2248 */
1766 if (err && (err->error_id != DAV_ERR_IF_ABSENT))
1767 return err;
1769 /* If one or more locktokens are present in the header, push them
1770 into the filesystem access context. */
1771 if (ltl)
1773 svn_fs_access_t *access_ctx;
1774 dav_locktoken_list *list = ltl;
1776 serr = svn_fs_get_access(&access_ctx, repos->fs);
1777 if (serr)
1779 return dav_svn__sanitize_error(serr, "Lock token is in request, "
1780 "but no user name",
1781 HTTP_BAD_REQUEST, r);
1784 do {
1785 serr = svn_fs_access_add_lock_token(access_ctx,
1786 list->locktoken->uuid_str);
1787 if (serr)
1788 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1789 "Error pushing token into filesystem.",
1790 r->pool);
1791 list = list->next;
1793 } while (list);
1797 /* Figure out the type of the resource. Note that we have a PARSE step
1798 which is separate from a PREP step. This is because the PARSE can
1799 map multiple URLs to the same resource type. The PREP operates on
1800 the type of the resource. */
1802 /* skip over the leading "/" in the relative URI */
1803 if (parse_uri(comb, relative + 1, label, use_checked_in))
1804 goto malformed_URI;
1806 #ifdef SVN_DEBUG
1807 if (comb->res.type == DAV_RESOURCE_TYPE_UNKNOWN)
1809 /* Unknown URI. Return NULL to indicate "no resource" */
1810 DBG0("DESIGN FAILURE: should not be UNKNOWN at this point");
1811 *resource = NULL;
1812 return NULL;
1814 #endif
1816 /* prepare the resource for operation */
1817 if ((err = prep_resource(comb)) != NULL)
1818 return err;
1820 /* a GET request for a REGULAR collection resource MUST have a trailing
1821 slash. Redirect to include one if it does not. */
1822 if (comb->res.collection && comb->res.type == DAV_RESOURCE_TYPE_REGULAR
1823 && !had_slash && r->method_number == M_GET)
1825 /* note that we drop r->args. we don't deal with them anyways */
1826 const char *new_path = apr_pstrcat(r->pool,
1827 ap_escape_uri(r->pool, r->uri),
1828 "/",
1829 NULL);
1830 apr_table_setn(r->headers_out, "Location",
1831 ap_construct_url(r->pool, new_path, r));
1832 return dav_new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0,
1833 "Requests for a collection must have a "
1834 "trailing slash on the URI.");
1837 *resource = &comb->res;
1838 return NULL;
1840 malformed_URI:
1841 /* A malformed URI error occurs when a URI indicates the "special" area,
1842 yet it has an improper construction. Generally, this is because some
1843 doofus typed it in manually or has a buggy client. */
1844 /* ### pick something other than HTTP_INTERNAL_SERVER_ERROR */
1845 /* ### are SVN_ERR_APMOD codes within the right numeric space? */
1846 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1847 SVN_ERR_APMOD_MALFORMED_URI,
1848 "The URI indicated a resource within Subversion's "
1849 "special resource area, but does not exist. This is "
1850 "generally caused by a problem in the client "
1851 "software.");
1855 /* Helper func: return the parent of PATH, allocated in POOL. */
1856 static const char *
1857 get_parent_path(const char *path, apr_pool_t *pool)
1859 apr_size_t len;
1860 const char *parentpath, *base_name;
1861 char *tmp = apr_pstrdup(pool, path);
1863 len = strlen(tmp);
1865 if (len > 0)
1867 /* Remove any trailing slash; else svn_path_split() asserts. */
1868 if (tmp[len-1] == '/')
1869 tmp[len-1] = '\0';
1870 svn_path_split(tmp, &parentpath, &base_name, pool);
1872 return parentpath;
1875 return path;
1879 static dav_error *
1880 get_parent_resource(const dav_resource *resource,
1881 dav_resource **parent_resource)
1883 dav_resource *parent;
1884 dav_resource_private *parentinfo;
1885 svn_stringbuf_t *path = resource->info->uri_path;
1887 /* the root of the repository has no parent */
1888 if (path->len == 1 && *path->data == '/')
1890 *parent_resource = NULL;
1891 return NULL;
1894 switch (resource->type)
1896 case DAV_RESOURCE_TYPE_REGULAR:
1898 parent = apr_pcalloc(resource->pool, sizeof(*parent));
1899 parentinfo = apr_pcalloc(resource->pool, sizeof(*parentinfo));
1901 parent->type = DAV_RESOURCE_TYPE_REGULAR;
1902 parent->exists = 1;
1903 parent->collection = 1;
1904 parent->versioned = 1;
1905 parent->hooks = resource->hooks;
1906 parent->pool = resource->pool;
1907 parent->uri = get_parent_path(resource->uri, resource->pool);
1908 parent->info = parentinfo;
1910 parentinfo->pool = resource->info->pool;
1911 parentinfo->uri_path =
1912 svn_stringbuf_create(get_parent_path(resource->info->uri_path->data,
1913 resource->pool), resource->pool);
1914 parentinfo->repos = resource->info->repos;
1915 parentinfo->root = resource->info->root;
1916 parentinfo->r = resource->info->r;
1917 parentinfo->svn_client_options = resource->info->svn_client_options;
1918 parentinfo->repos_path = get_parent_path(resource->info->repos_path,
1919 resource->pool);
1921 *parent_resource = parent;
1922 break;
1924 case DAV_RESOURCE_TYPE_WORKING:
1925 /* The "/" occurring within the URL of working resources is part of
1926 its identifier; it does not establish parent resource relationships.
1927 All working resources have the same parent, which is:
1928 http://host.name/path2repos/$svn/wrk/
1930 *parent_resource =
1931 create_private_resource(resource, DAV_SVN_RESTYPE_WRK_COLLECTION);
1932 break;
1934 case DAV_RESOURCE_TYPE_ACTIVITY:
1935 *parent_resource =
1936 create_private_resource(resource, DAV_SVN_RESTYPE_ACT_COLLECTION);
1937 break;
1939 default:
1940 /* ### needs more work. need parents for other resource types
1942 ### return an error so we can easily identify the cases where
1943 ### we've called this function unexpectedly. */
1944 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1945 apr_psprintf(resource->pool,
1946 "get_parent_resource was called for "
1947 "%s (type %d)",
1948 resource->uri, resource->type));
1949 break;
1952 return NULL;
1956 /* does RES2 live in the same repository as RES1? */
1957 static int
1958 is_our_resource(const dav_resource *res1, const dav_resource *res2)
1960 if (res1->hooks != res2->hooks
1961 || strcmp(res1->info->repos->fs_path, res2->info->repos->fs_path) != 0)
1963 /* a different provider, or a different FS repository */
1964 return 0;
1967 /* coalesce the repository */
1968 if (res1->info->repos != res2->info->repos)
1970 /* ### might be nice to have a pool which we can clear to toss
1971 ### out the old, redundant repos/fs. */
1973 /* have res2 point to res1's filesystem */
1974 res2->info->repos = res1->info->repos;
1976 /* res2's fs_root object is now invalid. regenerate it using
1977 the now-shared filesystem. */
1978 if (res2->info->root.txn_name)
1980 /* reopen the txn by name */
1981 svn_error_clear(svn_fs_open_txn(&(res2->info->root.txn),
1982 res2->info->repos->fs,
1983 res2->info->root.txn_name,
1984 res2->info->repos->pool));
1986 /* regenerate the txn "root" object */
1987 svn_error_clear(svn_fs_txn_root(&(res2->info->root.root),
1988 res2->info->root.txn,
1989 res2->info->repos->pool));
1991 else if (res2->info->root.rev)
1993 /* default: regenerate the revision "root" object */
1994 svn_error_clear(svn_fs_revision_root(&(res2->info->root.root),
1995 res2->info->repos->fs,
1996 res2->info->root.rev,
1997 res2->info->repos->pool));
2001 return 1;
2005 static int
2006 is_same_resource(const dav_resource *res1, const dav_resource *res2)
2008 if (!is_our_resource(res1, res2))
2009 return 0;
2011 /* ### what if the same resource were reached via two URIs? */
2013 return svn_stringbuf_compare(res1->info->uri_path, res2->info->uri_path);
2017 static int
2018 is_parent_resource(const dav_resource *res1, const dav_resource *res2)
2020 apr_size_t len1 = strlen(res1->info->uri_path->data);
2021 apr_size_t len2;
2023 if (!is_our_resource(res1, res2))
2024 return 0;
2026 /* ### what if a resource were reached via two URIs? we ought to define
2027 ### parent/child relations for resources independent of URIs.
2028 ### i.e. define a "canonical" location for each resource, then return
2029 ### the parent based on that location. */
2031 /* res2 is one of our resources, we can use its ->info ptr */
2032 len2 = strlen(res2->info->uri_path->data);
2034 return (len2 > len1
2035 && memcmp(res1->info->uri_path->data, res2->info->uri_path->data,
2036 len1) == 0
2037 && res2->info->uri_path->data[len1] == '/');
2041 #if 0
2042 /* Given an apache request R and a ROOT_PATH to the svn location
2043 block, set *KIND to the node-kind of the URI's associated
2044 (revision, path) pair, if possible.
2046 Public uris, baseline collections, version resources, and working
2047 (non-baseline) resources all have associated (revision, path)
2048 pairs, and thus one of {svn_node_file, svn_node_dir, svn_node_none}
2049 will be returned.
2051 If URI is something more abstract, then set *KIND to
2052 svn_node_unknown. This is true for baselines, working baselines,
2053 version controled configurations, activities, histories, and other
2054 private resources.
2056 static dav_error *
2057 resource_kind(request_rec *r,
2058 const char *uri,
2059 const char *root_path,
2060 svn_node_kind_t *kind)
2062 dav_error *derr;
2063 svn_error_t *serr;
2064 dav_resource *resource;
2065 svn_revnum_t base_rev;
2066 svn_fs_root_t *base_rev_root;
2067 char *saved_uri;
2069 /* Temporarily insert the uri that the user actually wants us to
2070 convert into a resource. Typically, this is already r->uri, so
2071 this is usually a no-op. But sometimes the caller may pass in
2072 the Destination: header uri.
2074 ### WHAT WE REALLY WANT here is to refactor get_resource,
2075 so that some alternate interface actually allows us to specify
2076 the URI to process, i.e. not always process r->uri.
2078 saved_uri = r->uri;
2079 r->uri = apr_pstrdup(r->pool, uri);
2081 /* parse the uri and prep the associated resource. */
2082 derr = get_resource(r, root_path,
2083 /* ### I can't believe that every single
2084 parser ignores the LABEL and USE_CHECKED_IN
2085 args below!! */
2086 "ignored_label", 1,
2087 &resource);
2088 /* Restore r back to normal. */
2089 r->uri = saved_uri;
2091 if (derr)
2092 return derr;
2094 if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
2096 /* Either a public URI or a bc. In both cases, prep_regular()
2097 has already set the 'exists' and 'collection' flags by
2098 querying the appropriate revision root and path. */
2099 if (! resource->exists)
2100 *kind = svn_node_none;
2101 else
2102 *kind = resource->collection ? svn_node_dir : svn_node_file;
2105 else if (resource->type == DAV_RESOURCE_TYPE_VERSION)
2107 if (resource->baselined) /* bln */
2108 *kind = svn_node_unknown;
2110 else /* ver */
2112 derr = fs_check_path(kind, resource->info->root.root,
2113 resource->info->repos_path, r->pool);
2114 if (derr != NULL)
2115 return derr;
2119 else if (resource->type == DAV_RESOURCE_TYPE_WORKING)
2121 if (resource->baselined) /* wbl */
2122 *kind = svn_node_unknown;
2124 else /* wrk */
2126 /* don't call fs_check_path on the txn, but on the original
2127 revision that the txn is based on. */
2128 base_rev = svn_fs_txn_base_revision(resource->info->root.txn);
2129 serr = svn_fs_revision_root(&base_rev_root,
2130 resource->info->repos->fs,
2131 base_rev, r->pool);
2132 if (serr)
2133 return dav_svn__convert_err
2134 (serr, HTTP_INTERNAL_SERVER_ERROR,
2135 apr_psprintf(r->pool,
2136 "Could not open root of revision %ld",
2137 base_rev),
2138 r->pool);
2140 derr = fs_check_path(kind, base_rev_root,
2141 resource->info->repos_path, r->pool);
2142 if (derr != NULL)
2143 return derr;
2147 else
2148 /* act, his, vcc, or some other private resource */
2149 *kind = svn_node_unknown;
2151 return NULL;
2153 #endif
2156 static dav_error *
2157 open_stream(const dav_resource *resource,
2158 dav_stream_mode mode,
2159 dav_stream **stream)
2161 svn_node_kind_t kind;
2162 dav_error *derr;
2163 svn_error_t *serr;
2165 if (mode == DAV_MODE_WRITE_TRUNC || mode == DAV_MODE_WRITE_SEEKABLE)
2167 if (resource->type != DAV_RESOURCE_TYPE_WORKING)
2169 return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
2170 "Resource body changes may only be made to "
2171 "working resources [at this time].");
2175 #if 1
2176 if (mode == DAV_MODE_WRITE_SEEKABLE)
2178 return dav_new_error(resource->pool, HTTP_NOT_IMPLEMENTED, 0,
2179 "Resource body writes cannot use ranges "
2180 "[at this time].");
2182 #endif
2184 /* start building the stream structure */
2185 *stream = apr_pcalloc(resource->pool, sizeof(**stream));
2186 (*stream)->res = resource;
2188 derr = fs_check_path(&kind, resource->info->root.root,
2189 resource->info->repos_path, resource->pool);
2190 if (derr != NULL)
2191 return derr;
2193 if (kind == svn_node_none) /* No existing file. */
2195 serr = svn_fs_make_file(resource->info->root.root,
2196 resource->info->repos_path,
2197 resource->pool);
2199 if (serr != NULL)
2201 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2202 "Could not create file within the "
2203 "repository.",
2204 resource->pool);
2208 /* if the working-resource was auto-checked-out (i.e. came into
2209 existence through the autoversioning feature), then possibly set
2210 the svn:mime-type property based on whatever value mod_mime has
2211 chosen. If the path already has an svn:mime-type property
2212 set, do nothing. */
2213 if (resource->info->auto_checked_out
2214 && resource->info->r->content_type)
2216 svn_string_t *mime_type;
2218 serr = svn_fs_node_prop(&mime_type,
2219 resource->info->root.root,
2220 resource->info->repos_path,
2221 SVN_PROP_MIME_TYPE,
2222 resource->pool);
2224 if (serr != NULL)
2226 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2227 "Error fetching mime-type property.",
2228 resource->pool);
2231 if (!mime_type)
2233 serr = svn_fs_change_node_prop(resource->info->root.root,
2234 resource->info->repos_path,
2235 SVN_PROP_MIME_TYPE,
2236 svn_string_create
2237 (resource->info->r->content_type,
2238 resource->pool),
2239 resource->pool);
2240 if (serr != NULL)
2242 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2243 "Could not set mime-type property.",
2244 resource->pool);
2249 serr = svn_fs_apply_textdelta(&(*stream)->delta_handler,
2250 &(*stream)->delta_baton,
2251 resource->info->root.root,
2252 resource->info->repos_path,
2253 resource->info->base_checksum,
2254 resource->info->result_checksum,
2255 resource->pool);
2257 if (serr != NULL)
2259 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2260 "Could not prepare to write the file",
2261 resource->pool);
2264 /* if the incoming data is an SVNDIFF, then create a stream that
2265 will process the data into windows and invoke the FS window handler
2266 when a window is ready. */
2267 /* ### we need a better way to check the content-type! this is bogus
2268 ### because we're effectively looking at the request_rec. doubly
2269 ### bogus because this means you cannot open arbitrary streams and
2270 ### feed them content (the type is always tied to a request_rec).
2271 ### probably ought to pass the type to open_stream */
2272 if (resource->info->is_svndiff)
2274 (*stream)->wstream =
2275 svn_txdelta_parse_svndiff((*stream)->delta_handler,
2276 (*stream)->delta_baton,
2277 TRUE,
2278 resource->pool);
2281 return NULL;
2285 static dav_error *
2286 close_stream(dav_stream *stream, int commit)
2288 svn_error_t *serr;
2289 apr_pool_t *pool = stream->res->pool;
2291 if (stream->rstream != NULL)
2293 serr = svn_stream_close(stream->rstream);
2294 if (serr)
2295 return dav_svn__convert_err
2296 (serr, HTTP_INTERNAL_SERVER_ERROR,
2297 "mod_dav_svn close_stream: error closing read stream",
2298 pool);
2301 /* if we have a write-stream, then closing it also takes care of the
2302 handler (so make sure not to send a NULL to it, too) */
2303 if (stream->wstream != NULL)
2305 serr = svn_stream_close(stream->wstream);
2306 if (serr)
2307 return dav_svn__convert_err
2308 (serr, HTTP_INTERNAL_SERVER_ERROR,
2309 "mod_dav_svn close_stream: error closing write stream",
2310 pool);
2312 else if (stream->delta_handler != NULL)
2314 serr = (*stream->delta_handler)(NULL, stream->delta_baton);
2315 if (serr)
2316 return dav_svn__convert_err
2317 (serr, HTTP_INTERNAL_SERVER_ERROR,
2318 "mod_dav_svn close_stream: error sending final (null) delta window",
2319 pool);
2322 return NULL;
2326 static dav_error *
2327 write_stream(dav_stream *stream, const void *buf, apr_size_t bufsize)
2329 svn_error_t *serr;
2330 apr_pool_t *pool = stream->res->pool;
2332 if (stream->wstream != NULL)
2334 serr = svn_stream_write(stream->wstream, buf, &bufsize);
2335 /* ### would the returned bufsize ever not match the requested amt? */
2337 else
2339 svn_txdelta_window_t window = { 0 };
2340 svn_txdelta_op_t op;
2341 svn_string_t data;
2343 data.data = buf;
2344 data.len = bufsize;
2346 op.action_code = svn_txdelta_new;
2347 op.offset = 0;
2348 op.length = bufsize;
2350 window.tview_len = bufsize; /* result will be this long */
2351 window.num_ops = 1;
2352 window.ops = &op;
2353 window.new_data = &data;
2355 serr = (*stream->delta_handler)(&window, stream->delta_baton);
2358 if (serr)
2360 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2361 "could not write the file contents",
2362 pool);
2364 return NULL;
2368 static dav_error *
2369 seek_stream(dav_stream *stream, apr_off_t abs_position)
2371 /* ### fill this in */
2373 return dav_new_error(stream->res->pool, HTTP_NOT_IMPLEMENTED, 0,
2374 "Resource body read/write cannot use ranges "
2375 "(at this time)");
2378 /* Returns whether the DAV resource lacks potential for generation of
2379 an ETag (defined as any of the following):
2380 - it doesn't exist
2381 - the resource type isn't REGULAR or VERSION
2382 - the resource is a Baseline */
2383 #define RESOURCE_LACKS_ETAG_POTENTIAL(resource) \
2384 (!resource->exists \
2385 || (resource->type != DAV_RESOURCE_TYPE_REGULAR \
2386 && resource->type != DAV_RESOURCE_TYPE_VERSION) \
2387 || (resource->type == DAV_RESOURCE_TYPE_VERSION \
2388 && resource->baselined))
2391 /* Return the last modification time of RESOURCE, or -1 if the DAV
2392 resource type is not handled, or if an error occurs. Temporary
2393 allocations are made from RESOURCE->POOL. */
2394 static apr_time_t
2395 get_last_modified(const dav_resource *resource)
2397 apr_time_t last_modified;
2398 svn_error_t *serr;
2399 svn_revnum_t created_rev;
2400 svn_string_t *date_time;
2402 if (RESOURCE_LACKS_ETAG_POTENTIAL(resource))
2403 return -1;
2405 if ((serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root,
2406 resource->info->repos_path,
2407 resource->pool)))
2409 svn_error_clear(serr);
2410 return -1;
2413 if ((serr = svn_fs_revision_prop(&date_time, resource->info->repos->fs,
2414 created_rev, "svn:date", resource->pool)))
2416 svn_error_clear(serr);
2417 return -1;
2420 if (date_time == NULL || date_time->data == NULL)
2421 return -1;
2423 if ((serr = svn_time_from_cstring(&last_modified, date_time->data,
2424 resource->pool)))
2426 svn_error_clear(serr);
2427 return -1;
2430 return last_modified;
2434 const char *
2435 dav_svn__getetag(const dav_resource *resource, apr_pool_t *pool)
2437 svn_error_t *serr;
2438 svn_revnum_t created_rev;
2440 if (RESOURCE_LACKS_ETAG_POTENTIAL(resource))
2441 return "";
2443 /* ### what kind of etag to return for activities, etc.? */
2445 if ((serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root,
2446 resource->info->repos_path,
2447 pool)))
2449 /* ### what to do? */
2450 svn_error_clear(serr);
2451 return "";
2454 /* Use the "weak" format of the etag for collections because our GET
2455 requests on collections include dynamic data (the HEAD revision,
2456 the build version of Subversion, etc.). */
2457 return apr_psprintf(pool, "%s\"%ld/%s\"",
2458 resource->collection ? "W/" : "",
2459 created_rev,
2460 apr_xml_quote_string(pool,
2461 resource->info->repos_path, 1));
2465 /* Since dav_svn__getetag() takes a pool argument, this wrapper is for
2466 the mod_dav hooks vtable entry, which does not. */
2467 static const char *
2468 getetag_pathetic(const dav_resource *resource)
2470 return dav_svn__getetag(resource, resource->pool);
2474 static dav_error *
2475 set_headers(request_rec *r, const dav_resource *resource)
2477 svn_error_t *serr;
2478 svn_filesize_t length;
2479 const char *mimetype = NULL;
2480 apr_time_t last_modified;
2482 if (!resource->exists)
2483 return NULL;
2485 last_modified = get_last_modified(resource);
2486 if (last_modified != -1)
2488 /* Note the modification time for the requested resource, and
2489 include the Last-Modified header in the response. */
2490 ap_update_mtime(r, last_modified);
2491 ap_set_last_modified(r);
2494 /* generate our etag and place it into the output */
2495 apr_table_setn(r->headers_out, "ETag",
2496 dav_svn__getetag(resource, resource->pool));
2498 #if 0
2499 /* As version resources don't change, encourage caching. */
2500 /* ### FIXME: This conditional is wrong -- type is often REGULAR,
2501 ### and the resource doesn't seem to be baselined. */
2502 if (resource->type == DAV_RESOURCE_TYPE_VERSION)
2503 /* Cache resource for one week (specified in seconds). */
2504 apr_table_setn(r->headers_out, "Cache-Control", "max-age=604800");
2505 #endif
2507 /* we accept byte-ranges */
2508 apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
2510 /* For a directory, we will send text/html or text/xml. If we have a delta
2511 base, then we will always be generating an svndiff. Otherwise,
2512 we need to fetch the appropriate MIME type from the resource's
2513 properties (and use text/plain if it isn't there). */
2514 if (resource->collection)
2516 if (resource->info->repos->xslt_uri)
2517 mimetype = "text/xml";
2518 else
2519 mimetype = "text/html; charset=UTF-8";
2521 else if (resource->info->delta_base != NULL)
2523 dav_svn__uri_info info;
2525 /* First order of business is to parse it. */
2526 serr = dav_svn__simple_parse_uri(&info, resource,
2527 resource->info->delta_base,
2528 resource->pool);
2530 /* If we successfully parse the base URL, then send an svndiff. */
2531 if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM))
2533 mimetype = SVN_SVNDIFF_MIME_TYPE;
2535 svn_error_clear(serr);
2538 if ((mimetype == NULL)
2539 && ((resource->type == DAV_RESOURCE_TYPE_VERSION)
2540 || (resource->type == DAV_RESOURCE_TYPE_REGULAR))
2541 && (resource->info->repos_path != NULL))
2543 svn_string_t *value;
2545 serr = svn_fs_node_prop(&value,
2546 resource->info->root.root,
2547 resource->info->repos_path,
2548 SVN_PROP_MIME_TYPE,
2549 resource->pool);
2550 if (serr != NULL)
2551 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2552 "could not fetch the resource's MIME type",
2553 resource->pool);
2555 if (value)
2556 mimetype = value->data;
2557 else if ((! resource->info->repos->is_svn_client)
2558 && r->content_type)
2559 mimetype = r->content_type;
2560 else
2561 mimetype = ap_default_type(r);
2563 serr = svn_mime_type_validate(mimetype, resource->pool);
2564 if (serr)
2566 /* Probably serr->apr == SVN_ERR_BAD_MIME_TYPE, but
2567 there's no point even checking. No matter what the
2568 error is, we can't derive the mime type from the
2569 svn:mime-type property. So we resort to the infamous
2570 "mime type of last resort." */
2571 svn_error_clear(serr);
2572 mimetype = "application/octet-stream";
2575 /* if we aren't sending a diff, then we know the length of the file,
2576 so set up the Content-Length header */
2577 serr = svn_fs_file_length(&length,
2578 resource->info->root.root,
2579 resource->info->repos_path,
2580 resource->pool);
2581 if (serr != NULL)
2583 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2584 "could not fetch the resource length",
2585 resource->pool);
2587 ap_set_content_length(r, (apr_off_t) length);
2590 /* set the discovered MIME type */
2591 /* ### it would be best to do this during the findct phase... */
2592 ap_set_content_type(r, mimetype);
2594 return NULL;
2598 typedef struct {
2599 ap_filter_t *output;
2600 apr_pool_t *pool;
2601 } diff_ctx_t;
2604 static svn_error_t *
2605 write_to_filter(void *baton, const char *buffer, apr_size_t *len)
2607 diff_ctx_t *dc = baton;
2608 apr_bucket_brigade *bb;
2609 apr_bucket *bkt;
2610 apr_status_t status;
2612 /* take the current data and shove it into the filter */
2613 bb = apr_brigade_create(dc->pool, dc->output->c->bucket_alloc);
2614 bkt = apr_bucket_transient_create(buffer, *len, dc->output->c->bucket_alloc);
2615 APR_BRIGADE_INSERT_TAIL(bb, bkt);
2616 if ((status = ap_pass_brigade(dc->output, bb)) != APR_SUCCESS) {
2617 return svn_error_create(status, NULL,
2618 "Could not write data to filter");
2621 return SVN_NO_ERROR;
2625 static svn_error_t *
2626 close_filter(void *baton)
2628 diff_ctx_t *dc = baton;
2629 apr_bucket_brigade *bb;
2630 apr_bucket *bkt;
2631 apr_status_t status;
2633 /* done with the file. write an EOS bucket now. */
2634 bb = apr_brigade_create(dc->pool, dc->output->c->bucket_alloc);
2635 bkt = apr_bucket_eos_create(dc->output->c->bucket_alloc);
2636 APR_BRIGADE_INSERT_TAIL(bb, bkt);
2637 if ((status = ap_pass_brigade(dc->output, bb)) != APR_SUCCESS)
2638 return svn_error_create(status, NULL, "Could not write EOS to filter");
2640 return SVN_NO_ERROR;
2644 static dav_error *
2645 deliver(const dav_resource *resource, ap_filter_t *output)
2647 svn_error_t *serr;
2648 apr_bucket_brigade *bb;
2649 apr_bucket *bkt;
2650 apr_status_t status;
2652 /* Check resource type */
2653 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
2654 && resource->type != DAV_RESOURCE_TYPE_VERSION
2655 && resource->type != DAV_RESOURCE_TYPE_WORKING
2656 && resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
2658 return dav_new_error(resource->pool, HTTP_CONFLICT, 0,
2659 "Cannot GET this type of resource.");
2662 if (resource->collection)
2664 const int gen_html = !resource->info->repos->xslt_uri;
2665 apr_hash_t *entries;
2666 apr_pool_t *entry_pool;
2667 apr_array_header_t *sorted;
2668 int i;
2670 /* XML schema for the directory index if xslt_uri is set:
2672 <?xml version="1.0"?>
2673 <?xml-stylesheet type="text/xsl" href="[info->repos->xslt_uri]"?> */
2674 static const char xml_index_dtd[] =
2675 "<!DOCTYPE svn [\n"
2676 " <!ELEMENT svn (index)>\n"
2677 " <!ATTLIST svn version CDATA #REQUIRED\n"
2678 " href CDATA #REQUIRED>\n"
2679 " <!ELEMENT index (updir?, (file | dir)*)>\n"
2680 " <!ATTLIST index name CDATA #IMPLIED\n"
2681 " path CDATA #IMPLIED\n"
2682 " rev CDATA #IMPLIED\n"
2683 " base CDATA #IMPLIED>\n"
2684 " <!ELEMENT updir EMPTY>\n"
2685 " <!ELEMENT file EMPTY>\n"
2686 " <!ATTLIST file name CDATA #REQUIRED\n"
2687 " href CDATA #REQUIRED>\n"
2688 " <!ELEMENT dir EMPTY>\n"
2689 " <!ATTLIST dir name CDATA #REQUIRED\n"
2690 " href CDATA #REQUIRED>\n"
2691 "]>\n";
2693 /* <svn version="1.3.0 (dev-build)"
2694 href="http://subversion.tigris.org">
2695 <index name="[info->repos->repo_name]"
2696 path="[info->repos_path]"
2697 rev="[info->root.rev]">
2698 <file name="foo" href="foo" />
2699 <dir name="bar" href="bar/" />
2700 </index>
2701 </svn> */
2704 /* ### TO-DO: check for a new mod_dav_svn directive here also. */
2705 if (resource->info->restype == DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
2707 apr_hash_index_t *hi;
2708 apr_hash_t *dirents;
2709 const char *fs_parent_path =
2710 dav_svn__get_fs_parent_path(resource->info->r);
2712 serr = svn_io_get_dirents2(&dirents, fs_parent_path, resource->pool);
2713 if (serr != NULL)
2714 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2715 "couldn't fetch dirents of SVNParentPath",
2716 resource->pool);
2718 /* convert an io dirent hash to an fs dirent hash. */
2719 entries = apr_hash_make(resource->pool);
2720 for (hi = apr_hash_first(resource->pool, dirents);
2721 hi; hi = apr_hash_next(hi))
2723 const void *key;
2724 void *val;
2725 svn_io_dirent_t *dirent;
2726 svn_fs_dirent_t *ent = apr_pcalloc(resource->pool, sizeof(*ent));
2728 apr_hash_this(hi, &key, NULL, &val);
2729 dirent = val;
2731 if (dirent->kind != svn_node_dir)
2732 continue;
2734 ent->name = key;
2735 ent->id = NULL; /* ### does it matter? */
2736 ent->kind = dirent->kind;
2738 apr_hash_set(entries, key, APR_HASH_KEY_STRING, ent);
2742 else
2744 serr = svn_fs_dir_entries(&entries, resource->info->root.root,
2745 resource->info->repos_path, resource->pool);
2746 if (serr != NULL)
2747 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2748 "could not fetch directory entries",
2749 resource->pool);
2752 bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
2754 if (gen_html)
2756 const char *title;
2757 if (resource->info->repos_path == NULL)
2758 title = "unknown location";
2759 else
2760 title = resource->info->repos_path;
2762 if (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
2764 if (SVN_IS_VALID_REVNUM(resource->info->root.rev))
2765 title = apr_psprintf(resource->pool,
2766 "Revision %ld: %s",
2767 resource->info->root.rev, title);
2768 if (resource->info->repos->repo_basename)
2769 title = apr_psprintf(resource->pool, "%s - %s",
2770 resource->info->repos->repo_basename,
2771 title);
2772 if (resource->info->repos->repo_name)
2773 title = apr_psprintf(resource->pool, "%s: %s",
2774 resource->info->repos->repo_name,
2775 title);
2778 ap_fprintf(output, bb, "<html><head><title>%s</title></head>\n"
2779 "<body>\n <h2>%s</h2>\n <ul>\n", title, title);
2781 else
2783 const char *name = resource->info->repos->repo_name;
2784 const char *href = resource->info->repos_path;
2785 const char *base = resource->info->repos->repo_basename;
2787 ap_fputs(output, bb, "<?xml version=\"1.0\"?>\n");
2788 ap_fprintf(output, bb,
2789 "<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>\n",
2790 resource->info->repos->xslt_uri);
2791 ap_fputs(output, bb, xml_index_dtd);
2792 ap_fputs(output, bb,
2793 "<svn version=\"" SVN_VERSION "\"\n"
2794 " href=\"http://subversion.tigris.org/\">\n");
2795 ap_fputs(output, bb, " <index");
2796 if (name)
2797 ap_fprintf(output, bb, " name=\"%s\"",
2798 apr_xml_quote_string(resource->pool, name, 1));
2799 if (SVN_IS_VALID_REVNUM(resource->info->root.rev))
2800 ap_fprintf(output, bb, " rev=\"%ld\"",
2801 resource->info->root.rev);
2802 if (href)
2803 ap_fprintf(output, bb, " path=\"%s\"",
2804 apr_xml_quote_string(resource->pool,
2805 href,
2806 1));
2807 if (base)
2808 ap_fprintf(output, bb, " base=\"%s\"", base);
2810 ap_fputs(output, bb, ">\n");
2813 if ((resource->info->repos_path && resource->info->repos_path[1] != '\0')
2814 && (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION))
2816 if (gen_html)
2817 ap_fprintf(output, bb, " <li><a href=\"../\">..</a></li>\n");
2818 else
2819 ap_fprintf(output, bb, " <updir />\n");
2822 /* get a sorted list of the entries */
2823 sorted = svn_sort__hash(entries, svn_sort_compare_items_as_paths,
2824 resource->pool);
2826 entry_pool = svn_pool_create(resource->pool);
2828 for (i = 0; i < sorted->nelts; ++i)
2830 const svn_sort__item_t *item = &APR_ARRAY_IDX(sorted, i,
2831 const svn_sort__item_t);
2832 const svn_fs_dirent_t *entry = item->value;
2833 const char *name = item->key;
2834 const char *href = name;
2835 svn_boolean_t is_dir = (entry->kind == svn_node_dir);
2837 svn_pool_clear(entry_pool);
2839 /* append a trailing slash onto the name for directories. we NEED
2840 this for the href portion so that the relative reference will
2841 descend properly. for the visible portion, it is just nice. */
2842 /* ### The xml output doesn't like to see a trailing slash on
2843 ### the visible portion, so avoid that. */
2844 if (is_dir)
2845 href = apr_pstrcat(entry_pool, href, "/", NULL);
2847 if (gen_html)
2848 name = href;
2850 /* We quote special characters in both XML and HTML. */
2851 name = apr_xml_quote_string(entry_pool, name, !gen_html);
2853 /* According to httpd-2.0.54/include/httpd.h, ap_os_escape_path()
2854 behaves differently on different platforms. It claims to
2855 "convert an OS path to a URL in an OS dependant way".
2856 Nevertheless, there appears to be only one implementation
2857 of the function in httpd, and the code seems completely
2858 platform independent, so we'll assume it's appropriate for
2859 mod_dav_svn to use it to quote outbound paths. */
2860 href = ap_os_escape_path(entry_pool, href, 0);
2861 href = apr_xml_quote_string(entry_pool, href, 1);
2863 if (gen_html)
2865 ap_fprintf(output, bb,
2866 " <li><a href=\"%s\">%s</a></li>\n",
2867 href, name);
2869 else
2871 const char *const tag = (is_dir ? "dir" : "file");
2873 /* This is where we could search for props */
2875 ap_fprintf(output, bb,
2876 " <%s name=\"%s\" href=\"%s\" />\n",
2877 tag, name, href);
2881 svn_pool_destroy(entry_pool);
2883 if (gen_html)
2884 ap_fputs(output, bb,
2885 " </ul>\n <hr noshade><em>Powered by "
2886 "<a href=\"http://subversion.tigris.org/\">Subversion</a> "
2887 "version " SVN_VERSION "."
2888 "</em>\n</body></html>");
2889 else
2890 ap_fputs(output, bb, " </index>\n</svn>\n");
2892 bkt = apr_bucket_eos_create(output->c->bucket_alloc);
2893 APR_BRIGADE_INSERT_TAIL(bb, bkt);
2894 if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS)
2895 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2896 "Could not write EOS to filter.");
2898 return NULL;
2902 /* If we have a base for a delta, then we want to compute an svndiff
2903 between the provided base and the requested resource. For a simple
2904 request, then we just grab the file contents. */
2905 if (resource->info->delta_base != NULL)
2907 dav_svn__uri_info info;
2908 svn_fs_root_t *root;
2909 svn_boolean_t is_file;
2910 svn_txdelta_stream_t *txd_stream;
2911 svn_stream_t *o_stream;
2912 svn_txdelta_window_handler_t handler;
2913 void * h_baton;
2914 diff_ctx_t dc = { 0 };
2916 /* First order of business is to parse it. */
2917 serr = dav_svn__simple_parse_uri(&info, resource,
2918 resource->info->delta_base,
2919 resource->pool);
2921 /* If we successfully parse the base URL, then send an svndiff. */
2922 if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM))
2924 /* We are always accessing the base resource by ID, so open
2925 an ID root. */
2926 serr = svn_fs_revision_root(&root, resource->info->repos->fs,
2927 info.rev, resource->pool);
2928 if (serr != NULL)
2929 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2930 "could not open a root for the base",
2931 resource->pool);
2933 /* verify that it is a file */
2934 serr = svn_fs_is_file(&is_file, root, info.repos_path,
2935 resource->pool);
2936 if (serr != NULL)
2937 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2938 "could not determine if the base "
2939 "is really a file",
2940 resource->pool);
2941 if (!is_file)
2942 return dav_new_error(resource->pool, HTTP_BAD_REQUEST, 0,
2943 "the delta base does not refer to a file");
2945 /* Okay. Let's open up a delta stream for the client to read. */
2946 serr = svn_fs_get_file_delta_stream(&txd_stream,
2947 root, info.repos_path,
2948 resource->info->root.root,
2949 resource->info->repos_path,
2950 resource->pool);
2951 if (serr != NULL)
2952 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2953 "could not prepare to read a delta",
2954 resource->pool);
2956 /* create a stream that svndiff data will be written to,
2957 which will copy it to the network */
2958 dc.output = output;
2959 dc.pool = resource->pool;
2960 o_stream = svn_stream_create(&dc, resource->pool);
2961 svn_stream_set_write(o_stream, write_to_filter);
2962 svn_stream_set_close(o_stream, close_filter);
2964 /* get a handler/baton for writing into the output stream */
2965 svn_txdelta_to_svndiff2(&handler, &h_baton,
2966 o_stream, resource->info->svndiff_version,
2967 resource->pool);
2969 /* got everything set up. read in delta windows and shove them into
2970 the handler, which pushes data into the output stream, which goes
2971 to the network. */
2972 serr = svn_txdelta_send_txstream(txd_stream, handler, h_baton,
2973 resource->pool);
2974 if (serr != NULL)
2975 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2976 "could not deliver the txdelta stream",
2977 resource->pool);
2980 return NULL;
2982 else
2984 svn_error_clear(serr);
2988 /* resource->info->delta_base is NULL, or we had an invalid base URL */
2990 svn_stream_t *stream;
2991 char *block;
2993 serr = svn_fs_file_contents(&stream,
2994 resource->info->root.root,
2995 resource->info->repos_path,
2996 resource->pool);
2997 if (serr != NULL)
2999 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3000 "could not prepare to read the file",
3001 resource->pool);
3004 /* ### one day in the future, we can create a custom bucket type
3005 ### which will read from the FS stream on demand */
3007 block = apr_palloc(resource->pool, SVN__STREAM_CHUNK_SIZE);
3008 while (1) {
3009 apr_size_t bufsize = SVN__STREAM_CHUNK_SIZE;
3011 /* read from the FS ... */
3012 serr = svn_stream_read(stream, block, &bufsize);
3013 if (serr != NULL)
3015 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3016 "could not read the file contents",
3017 resource->pool);
3019 if (bufsize == 0)
3020 break;
3022 /* build a brigade and write to the filter ... */
3023 bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
3024 bkt = apr_bucket_transient_create(block, bufsize,
3025 output->c->bucket_alloc);
3026 APR_BRIGADE_INSERT_TAIL(bb, bkt);
3027 if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
3028 /* ### what to do with status; and that HTTP code... */
3029 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3030 "Could not write data to filter.");
3034 /* done with the file. write an EOS bucket now. */
3035 bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
3036 bkt = apr_bucket_eos_create(output->c->bucket_alloc);
3037 APR_BRIGADE_INSERT_TAIL(bb, bkt);
3038 if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
3039 /* ### what to do with status; and that HTTP code... */
3040 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3041 "Could not write EOS to filter.");
3044 return NULL;
3049 static dav_error *
3050 create_collection(dav_resource *resource)
3052 svn_error_t *serr;
3053 dav_error *err;
3055 if (resource->type != DAV_RESOURCE_TYPE_WORKING
3056 && resource->type != DAV_RESOURCE_TYPE_REGULAR)
3058 return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3059 "Collections can only be created within a working "
3060 "or regular collection [at this time].");
3063 /* ...regular resources allowed only if autoversioning is turned on. */
3064 if (resource->type == DAV_RESOURCE_TYPE_REGULAR
3065 && ! (resource->info->repos->autoversioning))
3066 return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3067 "MKCOL called on regular resource, but "
3068 "autoversioning is not active.");
3070 /* ### note that the parent was checked out at some point, and this
3071 ### is being preformed relative to the working rsrc for that parent */
3073 /* Auto-versioning mkcol of regular resource: */
3074 if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
3076 /* Change the VCR into a WR, in place. This creates a txn and
3077 changes resource->info->root from a rev-root into a txn-root. */
3078 err = dav_svn__checkout(resource,
3079 1 /* auto-checkout */,
3080 0, 0, 0, NULL, NULL);
3081 if (err)
3082 return err;
3085 if ((serr = svn_fs_make_dir(resource->info->root.root,
3086 resource->info->repos_path,
3087 resource->pool)) != NULL)
3089 /* ### need a better error */
3090 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3091 "Could not create the collection.",
3092 resource->pool);
3095 /* Auto-versioning commit of the txn. */
3096 if (resource->info->auto_checked_out)
3098 /* This also changes the WR back into a VCR, in place. */
3099 err = dav_svn__checkin(resource, 0, NULL);
3100 if (err)
3101 return err;
3104 return NULL;
3108 static dav_error *
3109 copy_resource(const dav_resource *src,
3110 dav_resource *dst,
3111 int depth,
3112 dav_response **response)
3114 svn_error_t *serr;
3115 dav_error *err;
3116 const char *src_repos_path, *dst_repos_path;
3118 /* ### source must be from a collection under baseline control. the
3119 ### baseline will (implicitly) indicate the source revision, and the
3120 ### path will be derived simply from the URL path */
3122 /* ### the destination's parent must be a working collection */
3124 /* ### ben goofing around: */
3125 /* char *msg;
3126 apr_psprintf
3127 (src->pool, "Got a COPY request with src arg '%s' and dst arg '%s'",
3128 src->uri, dst->uri);
3130 return dav_new_error(src->pool, HTTP_NOT_IMPLEMENTED, 0, msg);
3133 /* ### Safeguard: see issue #916, whereby we're allowing an
3134 auto-checkout of a baseline for PROPPATCHing, *without* creating
3135 a new baseline afterwards. We need to safeguard here that nobody
3136 is calling COPY with the baseline as a Destination! */
3137 if (dst->baselined && dst->type == DAV_RESOURCE_TYPE_VERSION)
3138 return dav_new_error(src->pool, HTTP_PRECONDITION_FAILED, 0,
3139 "Illegal: COPY Destination is a baseline.");
3141 if (dst->type == DAV_RESOURCE_TYPE_REGULAR
3142 && !(dst->info->repos->autoversioning))
3143 return dav_new_error(dst->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3144 "COPY called on regular resource, but "
3145 "autoversioning is not active.");
3147 /* Auto-versioning copy of regular resource: */
3148 if (dst->type == DAV_RESOURCE_TYPE_REGULAR)
3150 /* Change the VCR into a WR, in place. This creates a txn and
3151 changes dst->info->root from a rev-root into a txn-root. */
3152 err = dav_svn__checkout(dst,
3153 1 /* auto-checkout */,
3154 0, 0, 0, NULL, NULL);
3155 if (err)
3156 return err;
3159 serr = svn_path_get_absolute(&src_repos_path,
3160 svn_repos_path(src->info->repos->repos,
3161 src->pool),
3162 src->pool);
3163 if (!serr)
3164 serr = svn_path_get_absolute(&dst_repos_path,
3165 svn_repos_path(dst->info->repos->repos,
3166 dst->pool),
3167 dst->pool);
3169 if (!serr)
3171 if (strcmp(src_repos_path, dst_repos_path) != 0)
3172 return dav_svn__new_error_tag
3173 (dst->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3174 "Copy source and destination are in different repositories.",
3175 SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG);
3176 serr = svn_fs_copy(src->info->root.root, /* root object of src rev*/
3177 src->info->repos_path, /* relative path of src */
3178 dst->info->root.root, /* root object of dst txn*/
3179 dst->info->repos_path, /* relative path of dst */
3180 src->pool);
3182 if (serr)
3183 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3184 "Unable to make a filesystem copy.",
3185 dst->pool);
3187 /* Auto-versioning commit of the txn. */
3188 if (dst->info->auto_checked_out)
3190 /* This also changes the WR back into a VCR, in place. */
3191 err = dav_svn__checkin(dst, 0, NULL);
3192 if (err)
3193 return err;
3196 return NULL;
3200 static dav_error *
3201 remove_resource(dav_resource *resource, dav_response **response)
3203 svn_error_t *serr;
3204 dav_error *err;
3205 apr_hash_t *locks;
3207 /* Only activities, and working or regular resources can be deleted... */
3208 if (resource->type != DAV_RESOURCE_TYPE_WORKING
3209 && resource->type != DAV_RESOURCE_TYPE_REGULAR
3210 && resource->type != DAV_RESOURCE_TYPE_ACTIVITY)
3211 return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3212 "DELETE called on invalid resource type.");
3214 /* ...and regular resources only if autoversioning is turned on. */
3215 if (resource->type == DAV_RESOURCE_TYPE_REGULAR
3216 && ! (resource->info->repos->autoversioning))
3217 return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3218 "DELETE called on regular resource, but "
3219 "autoversioning is not active.");
3221 /* Handle activity deletions (early exit). */
3222 if (resource->type == DAV_RESOURCE_TYPE_ACTIVITY)
3224 return dav_svn__delete_activity(resource->info->repos,
3225 resource->info->root.activity_id);
3228 /* ### note that the parent was checked out at some point, and this
3229 ### is being preformed relative to the working rsrc for that parent */
3231 /* NOTE: strictly speaking, we cannot determine whether the parent was
3232 ever checked out, and that this working resource is relative to that
3233 checked out parent. It is entirely possible the client checked out
3234 the target resource and just deleted it. Subversion doesn't mind, but
3235 this does imply we are not enforcing the "checkout the parent, then
3236 delete from within" semantic. */
3238 /* Auto-versioning delete of regular resource: */
3239 if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
3241 /* Change the VCR into a WR, in place. This creates a txn and
3242 changes resource->info->root from a rev-root into a txn-root. */
3243 err = dav_svn__checkout(resource,
3244 1 /* auto-checkout */,
3245 0, 0, 0, NULL, NULL);
3246 if (err)
3247 return err;
3250 /* Sanity check: an svn client may have sent a custom request header
3251 containing the revision of the item it thinks it's deleting. In
3252 this case, we enforce the svn-specific semantic that the item
3253 must be up-to-date. */
3254 if (SVN_IS_VALID_REVNUM(resource->info->version_name))
3256 svn_revnum_t created_rev;
3257 serr = svn_fs_node_created_rev(&created_rev,
3258 resource->info->root.root,
3259 resource->info->repos_path,
3260 resource->pool);
3261 if (serr)
3262 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3263 "Could not get created rev of resource",
3264 resource->pool);
3266 if (resource->info->version_name < created_rev)
3268 serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL,
3269 "Item '%s' is out of date",
3270 resource->info->repos_path);
3271 return dav_svn__convert_err(serr, HTTP_CONFLICT,
3272 "Can't DELETE out-of-date resource",
3273 resource->pool);
3277 /* Before attempting the filesystem delete, we need to push any
3278 incoming lock-tokens into the filesystem's access_t. Normally
3279 they come in via 'If:' header, and get_resource()
3280 automatically notices them and does this work for us. In the
3281 case of a directory deletion, however, svn clients are sending
3282 'child' lock-tokens in the DELETE request body. */
3284 err = dav_svn__build_lock_hash(&locks, resource->info->r,
3285 resource->info->repos_path, resource->pool);
3286 if (err != NULL)
3287 return err;
3289 if (apr_hash_count(locks))
3291 err = dav_svn__push_locks(resource, locks, resource->pool);
3292 if (err != NULL)
3293 return err;
3296 if ((serr = svn_fs_delete(resource->info->root.root,
3297 resource->info->repos_path,
3298 resource->pool)) != NULL)
3300 /* ### need a better error */
3301 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3302 "Could not delete the resource",
3303 resource->pool);
3306 /* Auto-versioning commit of the txn. */
3307 if (resource->info->auto_checked_out)
3309 /* This also changes the WR back into a VCR, in place. */
3310 err = dav_svn__checkin(resource, 0, NULL);
3311 if (err)
3312 return err;
3315 return NULL;
3319 static dav_error *
3320 move_resource(dav_resource *src,
3321 dav_resource *dst,
3322 dav_response **response)
3324 svn_error_t *serr;
3325 dav_error *err;
3327 /* NOTE: The svn client does not call the MOVE method yet. Strictly
3328 speaking, we do not need to implement this repository function.
3329 But we do so anyway, so non-deltaV clients can work against the
3330 repository when autoversioning is turned on. Like the svn client,
3331 itself, we define a move to be a copy + delete within a single txn. */
3333 /* Because we have no 'atomic' move, we only allow this method on
3334 two regular resources with autoversioning active. That way we
3335 can auto-checkout a single resource and do the copy + delete
3336 within a single txn. (If we had two working resources, which txn
3337 would we use?) */
3338 if (src->type != DAV_RESOURCE_TYPE_REGULAR
3339 || dst->type != DAV_RESOURCE_TYPE_REGULAR
3340 || !(src->info->repos->autoversioning))
3341 return dav_new_error(dst->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3342 "MOVE only allowed on two public URIs, and "
3343 "autoversioning must be active.");
3345 /* Change the dst VCR into a WR, in place. This creates a txn and
3346 changes dst->info->root from a rev-root into a txn-root. */
3347 err = dav_svn__checkout(dst,
3348 1 /* auto-checkout */,
3349 0, 0, 0, NULL, NULL);
3350 if (err)
3351 return err;
3353 /* Copy the src to the dst. */
3354 serr = svn_fs_copy(src->info->root.root, /* the root object of src rev*/
3355 src->info->repos_path, /* the relative path of src */
3356 dst->info->root.root, /* the root object of dst txn*/
3357 dst->info->repos_path, /* the relative path of dst */
3358 src->pool);
3359 if (serr)
3360 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3361 "Unable to make a filesystem copy.",
3362 dst->pool);
3364 /* Notice: we're deleting the src repos path from the dst's txn_root. */
3365 if ((serr = svn_fs_delete(dst->info->root.root,
3366 src->info->repos_path,
3367 dst->pool)) != NULL)
3368 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3369 "Could not delete the src resource.",
3370 dst->pool);
3372 /* Commit: this also changes the WR back into a VCR, in place. */
3373 err = dav_svn__checkin(dst, 0, NULL);
3374 if (err)
3375 return err;
3377 return NULL;
3381 typedef struct {
3382 /* the input walk parameters */
3383 const dav_walk_params *params;
3385 /* reused as we walk */
3386 dav_walk_resource wres;
3388 /* the current resource */
3389 dav_resource res; /* wres.resource refers here */
3390 dav_resource_private info; /* the info in res */
3391 svn_stringbuf_t *uri; /* the uri within res */
3392 svn_stringbuf_t *repos_path; /* the repos_path within res */
3394 } walker_ctx_t;
3397 static dav_error *
3398 do_walk(walker_ctx_t *ctx, int depth)
3400 const dav_walk_params *params = ctx->params;
3401 int isdir = ctx->res.collection;
3402 dav_error *err;
3403 svn_error_t *serr;
3404 apr_hash_index_t *hi;
3405 apr_size_t path_len;
3406 apr_size_t uri_len;
3407 apr_size_t repos_len;
3408 apr_hash_t *children;
3410 /* Clear the temporary pool. */
3411 svn_pool_clear(ctx->info.pool);
3413 /* The current resource is a collection (possibly here thru recursion)
3414 and this is the invocation for the collection. Alternatively, this is
3415 the first [and only] entry to do_walk() for a member resource, so
3416 this will be the invocation for the member. */
3417 err = (*params->func)(&ctx->wres,
3418 isdir ? DAV_CALLTYPE_COLLECTION : DAV_CALLTYPE_MEMBER);
3419 if (err != NULL)
3420 return err;
3422 /* if we are not to recurse, or this is a member, then we're done */
3423 if (depth == 0 || !isdir)
3424 return NULL;
3426 /* ### for now, let's say that working resources have no children. of
3427 ### course, this isn't true (or "right") for working collections, but
3428 ### we don't actually need to do a walk right now. */
3429 if (params->root->type == DAV_RESOURCE_TYPE_WORKING)
3430 return NULL;
3432 /* ### need to allow more walking in the future */
3433 if (params->root->type != DAV_RESOURCE_TYPE_REGULAR)
3435 return dav_new_error(params->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3436 "Walking the resource hierarchy can only be done "
3437 "on 'regular' resources [at this time].");
3440 /* assert: collection resource. isdir == TRUE. repos_path != NULL. */
3442 /* append "/" to the paths, in preparation for appending child names.
3443 don't add "/" if the paths are simply "/" */
3444 if (ctx->info.uri_path->data[ctx->info.uri_path->len - 1] != '/')
3445 svn_stringbuf_appendcstr(ctx->info.uri_path, "/");
3446 if (ctx->repos_path->data[ctx->repos_path->len - 1] != '/')
3447 svn_stringbuf_appendcstr(ctx->repos_path, "/");
3449 /* NOTE: the URI should already have a trailing "/" */
3451 /* fix up the dependent pointers */
3452 ctx->info.repos_path = ctx->repos_path->data;
3454 /* all of the children exist. also initialize the collection flag. */
3455 ctx->res.exists = TRUE;
3456 ctx->res.collection = FALSE;
3458 /* remember these values so we can chop back to them after each time
3459 we append a child name to the path/uri/repos */
3460 path_len = ctx->info.uri_path->len;
3461 uri_len = ctx->uri->len;
3462 repos_len = ctx->repos_path->len;
3464 /* Tell our logging subsystem that we're listing a directory.
3466 Note: if we cared, we could look at the 'User-Agent:' request
3467 header and distinguish an svn client ('svn ls') from a generic
3468 DAV client. */
3469 dav_svn__operational_log(&ctx->info,
3470 apr_psprintf(params->pool,
3471 "get-dir %s r%ld text",
3472 svn_path_uri_encode(ctx->info.repos_path,
3473 params->pool),
3474 ctx->info.root.rev));
3476 /* fetch this collection's children */
3477 serr = svn_fs_dir_entries(&children, ctx->info.root.root,
3478 ctx->info.repos_path, params->pool);
3479 if (serr != NULL)
3480 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3481 "could not fetch collection members",
3482 params->pool);
3484 /* iterate over the children in this collection */
3485 for (hi = apr_hash_first(params->pool, children); hi; hi = apr_hash_next(hi))
3487 const void *key;
3488 apr_ssize_t klen;
3489 void *val;
3490 svn_fs_dirent_t *dirent;
3492 /* fetch one of the children */
3493 apr_hash_this(hi, &key, &klen, &val);
3494 dirent = val;
3496 /* authorize access to this resource, if applicable */
3497 if (params->walk_type & DAV_WALKTYPE_AUTH)
3499 /* ### how/what to do? */
3502 /* append this child to our buffers */
3503 svn_stringbuf_appendbytes(ctx->info.uri_path, key, klen);
3504 svn_stringbuf_appendbytes(ctx->uri, key, klen);
3505 svn_stringbuf_appendbytes(ctx->repos_path, key, klen);
3507 /* reset the pointers since the above may have changed them */
3508 ctx->res.uri = ctx->uri->data;
3509 ctx->info.repos_path = ctx->repos_path->data;
3511 if (dirent->kind == svn_node_file)
3513 err = (*params->func)(&ctx->wres, DAV_CALLTYPE_MEMBER);
3514 if (err != NULL)
3515 return err;
3517 else
3519 /* this resource is a collection */
3520 ctx->res.collection = TRUE;
3522 /* append a slash to the URI (the path doesn't need it yet) */
3523 svn_stringbuf_appendcstr(ctx->uri, "/");
3524 ctx->res.uri = ctx->uri->data;
3526 /* recurse on this collection */
3527 err = do_walk(ctx, depth - 1);
3528 if (err != NULL)
3529 return err;
3531 /* restore the data */
3532 ctx->res.collection = FALSE;
3535 /* chop the child off the paths and uri. NOTE: no null-term. */
3536 ctx->info.uri_path->len = path_len;
3537 ctx->uri->len = uri_len;
3538 ctx->repos_path->len = repos_len;
3540 return NULL;
3543 static dav_error *
3544 walk(const dav_walk_params *params, int depth, dav_response **response)
3546 /* Thinking about adding support for LOCKNULL resources in this
3547 walker? Check out the (working) code that was removed here:
3548 Author: cmpilato
3549 Date: Fri Mar 18 14:54:02 2005
3550 New Revision: 13475
3553 walker_ctx_t ctx = { 0 };
3554 dav_error *err;
3556 ctx.params = params;
3558 ctx.wres.walk_ctx = params->walk_ctx;
3559 ctx.wres.pool = params->pool;
3560 ctx.wres.resource = &ctx.res;
3562 /* copy the resource over and adjust the "info" reference */
3563 ctx.res = *params->root;
3564 ctx.info = *ctx.res.info;
3566 ctx.res.info = &ctx.info;
3568 /* operate within the proper pool */
3569 ctx.res.pool = params->pool;
3571 /* Don't monkey with the path from params->root. Create a new one.
3572 This path will then be extended/shortened as necessary. */
3573 ctx.info.uri_path = svn_stringbuf_dup(ctx.info.uri_path, params->pool);
3575 /* prep the URI buffer */
3576 ctx.uri = svn_stringbuf_create(params->root->uri, params->pool);
3578 /* same for repos_path */
3579 if (ctx.info.repos_path == NULL)
3580 ctx.repos_path = NULL;
3581 else
3582 ctx.repos_path = svn_stringbuf_create(ctx.info.repos_path, params->pool);
3584 /* if we have a collection, then ensure the URI has a trailing "/" */
3585 /* ### get_resource always kills the trailing slash... */
3586 if (ctx.res.collection && ctx.uri->data[ctx.uri->len - 1] != '/') {
3587 svn_stringbuf_appendcstr(ctx.uri, "/");
3590 /* the current resource's URI is stored in the (telescoping) ctx.uri */
3591 ctx.res.uri = ctx.uri->data;
3593 /* the current resource's repos_path is stored in ctx.repos_path */
3594 if (ctx.repos_path != NULL)
3595 ctx.info.repos_path = ctx.repos_path->data;
3597 /* Create a pool usable by the response. */
3598 ctx.info.pool = svn_pool_create(params->pool);
3600 /* ### is the root already/always open? need to verify */
3602 /* always return the error, and any/all multistatus responses */
3603 err = do_walk(&ctx, depth);
3604 *response = ctx.wres.response;
3606 return err;
3611 /*** Utility functions for resource management ***/
3613 dav_resource *
3614 dav_svn__create_working_resource(dav_resource *base,
3615 const char *activity_id,
3616 const char *txn_name,
3617 int tweak_in_place)
3619 const char *path;
3620 dav_resource *res;
3622 if (base->baselined)
3623 path = apr_psprintf(base->pool,
3624 "/%s/wbl/%s/%ld",
3625 base->info->repos->special_uri,
3626 activity_id, base->info->root.rev);
3627 else
3628 path = apr_psprintf(base->pool, "/%s/wrk/%s%s",
3629 base->info->repos->special_uri,
3630 activity_id, base->info->repos_path);
3631 path = svn_path_uri_encode(path, base->pool);
3633 if (tweak_in_place)
3634 res = base;
3635 else
3637 res = apr_pcalloc(base->pool, sizeof(*res));
3638 res->info = apr_pcalloc(base->pool, sizeof(*res->info));
3641 res->type = DAV_RESOURCE_TYPE_WORKING;
3642 res->exists = TRUE; /* ### not necessarily correct */
3643 res->versioned = TRUE;
3644 res->working = TRUE;
3645 res->baselined = base->baselined;
3646 /* collection = FALSE. ### not necessarily correct */
3648 res->uri = apr_pstrcat(base->pool, base->info->repos->root_path,
3649 path, NULL);
3650 res->hooks = &dav_svn__hooks_repository;
3651 res->pool = base->pool;
3653 res->info->uri_path = svn_stringbuf_create(path, base->pool);
3654 res->info->repos = base->info->repos;
3655 res->info->repos_path = base->info->repos_path;
3656 res->info->root.rev = base->info->root.rev;
3657 res->info->root.activity_id = activity_id;
3658 res->info->root.txn_name = txn_name;
3660 if (tweak_in_place)
3661 return NULL;
3662 else
3663 return res;
3667 dav_error *
3668 dav_svn__working_to_regular_resource(dav_resource *resource)
3670 dav_resource_private *priv = resource->info;
3671 dav_svn_repos *repos = priv->repos;
3672 const char *path;
3673 svn_error_t *serr;
3675 /* no need to change the repos object or repos_path */
3677 /* set type back to REGULAR */
3678 resource->type = DAV_RESOURCE_TYPE_REGULAR;
3680 /* remove the working flag */
3681 resource->working = FALSE;
3683 /* Change the URL into either a baseline-collection or a public one. */
3684 if (priv->root.rev == SVN_INVALID_REVNUM)
3686 serr = svn_fs_youngest_rev(&priv->root.rev, repos->fs, resource->pool);
3687 if (serr != NULL)
3688 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3689 "Could not determine youngest rev.",
3690 resource->pool);
3692 /* create public URL */
3693 path = apr_psprintf(resource->pool, "%s", priv->repos_path);
3695 else
3697 /* if rev was specific, create baseline-collection URL */
3698 path = dav_svn__build_uri(repos, DAV_SVN__BUILD_URI_BC,
3699 priv->root.rev, priv->repos_path,
3700 0, resource->pool);
3702 path = svn_path_uri_encode(path, resource->pool);
3703 priv->uri_path = svn_stringbuf_create(path, resource->pool);
3705 /* change root.root back into a revision root. */
3706 serr = svn_fs_revision_root(&priv->root.root, repos->fs,
3707 priv->root.rev, resource->pool);
3708 if (serr != NULL)
3709 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3710 "Could not open revision root.",
3711 resource->pool);
3713 return NULL;
3717 dav_error *
3718 dav_svn__create_version_resource(dav_resource **version_res,
3719 const char *uri,
3720 apr_pool_t *pool)
3722 int result;
3723 dav_error *err;
3725 dav_resource_combined *comb = apr_pcalloc(pool, sizeof(*comb));
3727 result = parse_version_uri(comb, uri, NULL, 0);
3728 if (result != 0)
3729 return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3730 "Could not parse version resource uri.");
3732 err = prep_version(comb);
3733 if (err)
3734 return err;
3736 *version_res = &comb->res;
3737 return NULL;
3741 const dav_hooks_repository dav_svn__hooks_repository =
3743 1, /* special GET handling */
3744 get_resource,
3745 get_parent_resource,
3746 is_same_resource,
3747 is_parent_resource,
3748 open_stream,
3749 close_stream,
3750 write_stream,
3751 seek_stream,
3752 set_headers,
3753 deliver,
3754 create_collection,
3755 copy_resource,
3756 move_resource,
3757 remove_resource,
3758 walk,
3759 getetag_pathetic