Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / mod_dav_svn / repos.c
blobe942fc49e90fefe6d07c1045d6f485e9570bade5
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->base_url = ap_construct_url(r->pool, "", r);
1230 repos->special_uri = dav_svn__get_special_uri(r);
1231 repos->username = r->user;
1232 repos->client_capabilities = apr_hash_make(repos->pool);
1234 /* Make sure this type of resource always has a trailing slash; if
1235 not, redirect to a URI that does. */
1236 if (r->uri[len-1] != '/')
1238 new_uri = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri),
1239 "/", NULL);
1240 apr_table_setn(r->headers_out, "Location",
1241 ap_construct_url(r->pool, new_uri, r));
1242 return dav_new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0,
1243 "Requests for a collection must have a "
1244 "trailing slash on the URI.");
1247 /* No other "prepping" of resource needs to happen -- no opening
1248 of a repository or anything like that, because, well, there's
1249 no repository to open. */
1250 *resource = &comb->res;
1251 return NULL;
1254 /* --------------- Borrowed from httpd's mod_negotiation.c -------------- */
1256 typedef struct accept_rec {
1257 char *name; /* MUST be lowercase */
1258 float quality;
1259 } accept_rec;
1262 * Get a single Accept-encoding line from ACCEPT_LINE, and place the
1263 * information we have parsed out of it into RESULT.
1266 static const char *get_entry(apr_pool_t *p, accept_rec *result,
1267 const char *accept_line)
1269 result->quality = 1.0f;
1272 * Note that this handles what I gather is the "old format",
1274 * Accept: text/html text/plain moo/zot
1276 * without any compatibility kludges --- if the token after the
1277 * MIME type begins with a semicolon, we know we're looking at parms,
1278 * otherwise, we know we aren't. (So why all the pissing and moaning
1279 * in the CERN server code? I must be missing something).
1282 result->name = ap_get_token(p, &accept_line, 0);
1283 ap_str_tolower(result->name); /* You want case insensitive,
1284 * you'll *get* case insensitive.
1287 while (*accept_line == ';')
1289 /* Parameters ... */
1291 char *parm;
1292 char *cp;
1293 char *end;
1295 ++accept_line;
1296 parm = ap_get_token(p, &accept_line, 1);
1298 /* Look for 'var = value' --- and make sure the var is in lcase. */
1300 for (cp = parm; (*cp && !apr_isspace(*cp) && *cp != '='); ++cp)
1302 *cp = apr_tolower(*cp);
1305 if (!*cp)
1307 continue; /* No '='; just ignore it. */
1310 *cp++ = '\0'; /* Delimit var */
1311 while (*cp && (apr_isspace(*cp) || *cp == '='))
1313 ++cp;
1316 if (*cp == '"')
1318 ++cp;
1319 for (end = cp;
1320 (*end && *end != '\n' && *end != '\r' && *end != '\"');
1321 end++);
1323 else
1325 for (end = cp; (*end && !apr_isspace(*end)); end++);
1327 if (*end)
1329 *end = '\0'; /* strip ending quote or return */
1331 ap_str_tolower(cp);
1333 if (parm[0] == 'q'
1334 && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0')))
1336 result->quality = (float) atof(cp);
1340 if (*accept_line == ',')
1342 ++accept_line;
1345 return accept_line;
1348 /* @a accept_line is the Accept-Encoding header, which is of the
1349 format:
1351 Accept-Encoding: name; q=N;
1353 This function will return an array of accept_rec structures that
1354 contain the accepted encodings and the quality each one has
1355 associated with them.
1357 static apr_array_header_t *do_header_line(apr_pool_t *p,
1358 const char *accept_line)
1360 apr_array_header_t *accept_recs;
1362 if (!accept_line)
1363 return NULL;
1365 accept_recs = apr_array_make(p, 10, sizeof(accept_rec));
1367 while (*accept_line)
1369 accept_rec *prefs = (accept_rec *) apr_array_push(accept_recs);
1370 accept_line = get_entry(p, prefs, accept_line);
1373 return accept_recs;
1376 /* ---------------------------------------------------------------------- */
1379 /* qsort comparison function for the quality field of the accept_rec
1380 structure */
1381 static int sort_encoding_pref(const void *accept_rec1, const void *accept_rec2)
1383 float diff = ((const accept_rec *) accept_rec1)->quality -
1384 ((const accept_rec *) accept_rec2)->quality;
1385 return (diff == 0 ? 0 : (diff > 0 ? -1 : 1));
1388 /* Parse and handle any possible Accept-Encoding header that has been
1389 sent as part of the request. */
1390 static void
1391 negotiate_encoding_prefs(request_rec *r, int *svndiff_version)
1393 /* It would be nice if mod_negotiation
1394 <http://httpd.apache.org/docs-2.1/mod/mod_negotiation.html> could
1395 handle the Accept-Encoding header parsing for us. Sadly, it
1396 looks like its data structures and routines are private (see
1397 httpd/modules/mappers/mod_negotiation.c). Thus, we duplicate the
1398 necessary ones in this file. */
1399 int i;
1400 const apr_array_header_t *encoding_prefs;
1401 encoding_prefs = do_header_line(r->pool,
1402 apr_table_get(r->headers_in,
1403 "Accept-Encoding"));
1405 if (!encoding_prefs || apr_is_empty_array(encoding_prefs))
1407 *svndiff_version = 0;
1408 return;
1411 *svndiff_version = 0;
1412 qsort(encoding_prefs->elts, (size_t) encoding_prefs->nelts,
1413 sizeof(accept_rec), sort_encoding_pref);
1414 for (i = 0; i < encoding_prefs->nelts; i++)
1416 struct accept_rec rec = APR_ARRAY_IDX(encoding_prefs, i,
1417 struct accept_rec);
1418 if (strcmp(rec.name, "svndiff1") == 0)
1420 *svndiff_version = 1;
1421 break;
1423 else if (strcmp(rec.name, "svndiff") == 0)
1425 *svndiff_version = 0;
1426 break;
1432 /* The only two possible values for a capability. */
1433 static const char *capability_yes = "yes";
1434 static const char *capability_no = "no";
1436 /* Convert CAPABILITIES, a hash table mapping 'const char *' keys to
1437 * "yes" or "no" values, to a list of all keys whose value is "yes".
1438 * Return the list, allocated in POOL, and use POOL for all temporary
1439 * allocation.
1441 static apr_array_header_t *
1442 capabilities_as_list(apr_hash_t *capabilities, apr_pool_t *pool)
1444 apr_array_header_t *list = apr_array_make(pool, apr_hash_count(capabilities),
1445 sizeof(char *));
1446 apr_hash_index_t *hi;
1448 for (hi = apr_hash_first(pool, capabilities); hi; hi = apr_hash_next(hi))
1450 const void *key;
1451 void *val;
1452 apr_hash_this(hi, &key, NULL, &val);
1453 if (strcmp((const char *) val, "yes") == 0)
1454 APR_ARRAY_PUSH(list, const char *) = key;
1457 return list;
1461 static dav_error *
1462 get_resource(request_rec *r,
1463 const char *root_path,
1464 const char *label,
1465 int use_checked_in,
1466 dav_resource **resource)
1468 const char *fs_path;
1469 const char *repo_name;
1470 const char *xslt_uri;
1471 const char *fs_parent_path;
1472 dav_resource_combined *comb;
1473 dav_svn_repos *repos;
1474 const char *cleaned_uri;
1475 const char *repos_name;
1476 const char *relative;
1477 const char *repos_path;
1478 const char *repos_key;
1479 const char *version_name;
1480 svn_error_t *serr;
1481 dav_error *err;
1482 int had_slash;
1483 dav_locktoken_list *ltl;
1484 struct cleanup_fs_access_baton *cleanup_baton;
1485 void *userdata;
1487 repo_name = dav_svn__get_repo_name(r);
1488 xslt_uri = dav_svn__get_xslt_uri(r);
1489 fs_parent_path = dav_svn__get_fs_parent_path(r);
1491 /* Special case: detect and build the SVNParentPath as a unique type
1492 of private resource, iff the SVNListParentPath directive is 'on'. */
1493 if (fs_parent_path && dav_svn__get_list_parentpath_flag(r))
1495 char *uri = apr_pstrdup(r->pool, r->uri);
1496 char *parentpath = apr_pstrdup(r->pool, root_path);
1497 apr_size_t uri_len = strlen(uri);
1498 apr_size_t parentpath_len = strlen(parentpath);
1500 if (uri[uri_len-1] == '/')
1501 uri[uri_len-1] = '\0';
1503 if (parentpath[parentpath_len-1] == '/')
1504 parentpath[parentpath_len-1] = '\0';
1506 if (strcmp(parentpath, uri) == 0)
1508 err = get_parentpath_resource(r, root_path, resource);
1509 if (err)
1510 return err;
1511 return NULL;
1515 /* This does all the work of interpreting/splitting the request uri. */
1516 err = dav_svn_split_uri(r, r->uri, root_path,
1517 &cleaned_uri, &had_slash,
1518 &repos_name, &relative, &repos_path);
1519 if (err)
1520 return err;
1522 /* The path that we will eventually try to open as an svn
1523 repository. Normally defined by the SVNPath directive. */
1524 fs_path = dav_svn__get_fs_path(r);
1526 /* If the SVNParentPath directive was used instead... */
1527 if (fs_parent_path != NULL)
1529 /* ...then the URL to the repository is actually one implicit
1530 component longer... */
1531 root_path = svn_path_join(root_path, repos_name, r->pool);
1532 /* ...and we need to specify exactly what repository to open. */
1533 fs_path = svn_path_join(fs_parent_path, repos_name, r->pool);
1536 /* Start building and filling a 'combination' object. */
1537 comb = apr_pcalloc(r->pool, sizeof(*comb));
1538 comb->res.info = &comb->priv;
1539 comb->res.hooks = &dav_svn__hooks_repository;
1540 comb->res.pool = r->pool;
1541 comb->res.uri = cleaned_uri;
1543 /* Original request, off which to generate subrequests later. */
1544 comb->priv.r = r;
1546 /* ### ugly hack to carry over Content-Type data to the open_stream, which
1547 ### does not have access to the request headers. */
1549 const char *ct = apr_table_get(r->headers_in, "content-type");
1551 comb->priv.is_svndiff =
1552 ct != NULL
1553 && strcmp(ct, SVN_SVNDIFF_MIME_TYPE) == 0;
1556 negotiate_encoding_prefs(r, &comb->priv.svndiff_version);
1558 /* ### and another hack for computing diffs to send to the client */
1559 comb->priv.delta_base = apr_table_get(r->headers_in,
1560 SVN_DAV_DELTA_BASE_HEADER);
1562 /* Gather any options requested by an svn client. */
1563 comb->priv.svn_client_options = apr_table_get(r->headers_in,
1564 SVN_DAV_OPTIONS_HEADER);
1566 /* See if the client sent a custom 'version name' request header. */
1567 version_name = apr_table_get(r->headers_in, SVN_DAV_VERSION_NAME_HEADER);
1568 comb->priv.version_name
1569 = version_name ? SVN_STR_TO_REV(version_name): SVN_INVALID_REVNUM;
1571 /* Remember checksums, if any. */
1572 comb->priv.base_checksum =
1573 apr_table_get(r->headers_in, SVN_DAV_BASE_FULLTEXT_MD5_HEADER);
1574 comb->priv.result_checksum =
1575 apr_table_get(r->headers_in, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER);
1577 /* "relative" is part of the "uri" string, so it has the proper
1578 lifetime to store here. */
1579 /* ### that comment no longer applies. we're creating a string with its
1580 ### own lifetime now. so WHY are we using a string? hmm... */
1581 comb->priv.uri_path = svn_stringbuf_create(relative, r->pool);
1583 /* initialize this until we put something real here */
1584 comb->priv.root.rev = SVN_INVALID_REVNUM;
1586 /* create the repository structure and stash it away */
1587 repos = apr_pcalloc(r->pool, sizeof(*repos));
1588 repos->pool = r->pool;
1590 comb->priv.repos = repos;
1592 /* We are assuming the root_path will live at least as long as this
1593 resource. Considering that it typically comes from the per-dir
1594 config in mod_dav, this is valid for now. */
1595 repos->root_path = svn_path_uri_encode(root_path, r->pool);
1597 /* where is the SVN FS for this resource? */
1598 repos->fs_path = fs_path;
1600 /* A name for the repository */
1601 repos->repo_name = repo_name;
1603 /* The repository filesystem basename */
1604 repos->repo_basename = repos_name;
1606 /* An XSL transformation */
1607 repos->xslt_uri = xslt_uri;
1609 /* Is autoversioning active in this repos? */
1610 repos->autoversioning = dav_svn__get_autoversioning_flag(r);
1612 /* Path to activities database */
1613 repos->activities_db = dav_svn__get_activities_db(r);
1614 if (repos->activities_db == NULL)
1615 /* If not specified, use default ($repos/dav/activities.d). */
1616 repos->activities_db = svn_path_join(repos->fs_path,
1617 DEFAULT_ACTIVITY_DB,
1618 r->pool);
1619 else if (fs_parent_path != NULL)
1620 /* If this is a ParentPath-based repository, treat the specified
1621 path as a similar parent directory. */
1622 repos->activities_db = svn_path_join(repos->activities_db,
1623 svn_path_basename(repos->fs_path,
1624 r->pool),
1625 r->pool);
1627 /* Remember various bits for later URL construction */
1628 repos->base_url = ap_construct_url(r->pool, "", r);
1629 repos->special_uri = dav_svn__get_special_uri(r);
1631 /* Remember who is making this request */
1632 repos->username = r->user;
1634 /* Allocate room for capabilities, but don't search for any until
1635 we know that this is a Subversion client. */
1636 repos->client_capabilities = apr_hash_make(repos->pool);
1638 /* Remember if the requesting client is a Subversion client, and if
1639 so, what its capabilities are. */
1641 const char *val = apr_table_get(r->headers_in, "User-Agent");
1643 if (val && (ap_strstr_c(val, "SVN/") == val))
1645 repos->is_svn_client = TRUE;
1647 /* Client capabilities are self-reported. There is no
1648 guarantee the client actually has the capabilities it says
1649 it has, we just assume it is in the client's interests to
1650 report accurately. Also, we only remember the capabilities
1651 the server cares about (even though the client may send
1652 more than that). */
1654 /* Start out assuming no capabilities. */
1655 apr_hash_set(repos->client_capabilities, SVN_RA_CAPABILITY_MERGEINFO,
1656 APR_HASH_KEY_STRING, capability_no);
1658 /* Then see what we can find. */
1659 val = apr_table_get(r->headers_in, "DAV");
1660 if (val)
1662 apr_array_header_t *vals
1663 = svn_cstring_split(val, ",", TRUE, r->pool);
1665 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_MERGEINFO,
1666 vals))
1668 apr_hash_set(repos->client_capabilities,
1669 SVN_RA_CAPABILITY_MERGEINFO,
1670 APR_HASH_KEY_STRING, capability_yes);
1676 /* Retrieve/cache open repository */
1677 repos_key = apr_pstrcat(r->pool, "mod_dav_svn:", fs_path, NULL);
1678 apr_pool_userdata_get(&userdata, repos_key, r->connection->pool);
1679 repos->repos = userdata;
1680 if (repos->repos == NULL)
1682 serr = svn_repos_open(&(repos->repos), fs_path, r->connection->pool);
1683 if (serr != NULL)
1685 /* The error returned by svn_repos_open might contain the
1686 actual path to the failed repository. We don't want to
1687 leak that path back to the client, because that would be
1688 a security risk, but we do want to log the real error on
1689 the server side. */
1690 return dav_svn__sanitize_error(serr, "Could not open the requested "
1691 "SVN filesystem",
1692 HTTP_INTERNAL_SERVER_ERROR, r);
1695 /* Cache the open repos for the next request on this connection */
1696 apr_pool_userdata_set(repos->repos, repos_key,
1697 NULL, r->connection->pool);
1699 /* Store the capabilities of the current connection, making sure
1700 to use the same pool repos->repos itself was created in. */
1701 serr = svn_repos_remember_client_capabilities
1702 (repos->repos, capabilities_as_list(repos->client_capabilities,
1703 r->connection->pool));
1704 if (serr != NULL)
1706 return dav_svn__sanitize_error(serr,
1707 "Error storing client capabilities "
1708 "in repos object",
1709 HTTP_INTERNAL_SERVER_ERROR, r);
1713 /* cache the filesystem object */
1714 repos->fs = svn_repos_fs(repos->repos);
1716 /* capture warnings during cleanup of the FS */
1717 svn_fs_set_warning_func(repos->fs, log_warning, r);
1719 /* if an authenticated username is present, attach it to the FS */
1720 if (r->user)
1722 svn_fs_access_t *access_ctx;
1724 /* The fs is cached in connection->pool, but the fs access
1725 context lives in r->pool. Because the username or token
1726 could change on each request, we need to make sure that the
1727 fs points to a NULL access context after the request is gone. */
1728 cleanup_baton = apr_pcalloc(r->pool, sizeof(*cleanup_baton));
1729 cleanup_baton->pool = r->pool;
1730 cleanup_baton->fs = repos->fs;
1731 apr_pool_cleanup_register(r->pool, cleanup_baton, cleanup_fs_access,
1732 apr_pool_cleanup_null);
1734 /* Create an access context based on the authenticated username. */
1735 serr = svn_fs_create_access(&access_ctx, r->user, r->pool);
1736 if (serr)
1738 return dav_svn__sanitize_error(serr,
1739 "Could not create fs access context",
1740 HTTP_INTERNAL_SERVER_ERROR, r);
1743 /* Attach the access context to the fs. */
1744 serr = svn_fs_set_access(repos->fs, access_ctx);
1745 if (serr)
1747 return dav_svn__sanitize_error(serr, "Could not attach access "
1748 "context to fs",
1749 HTTP_INTERNAL_SERVER_ERROR, r);
1753 /* Look for locktokens in the "If:" request header. */
1754 err = dav_get_locktoken_list(r, &ltl);
1756 /* dav_get_locktoken_list claims to return a NULL list when no
1757 locktokens are present. But it actually throws this error
1758 instead! So we're deliberately trapping/ignoring it.
1760 This is a workaround for a bug in mod_dav. Remove this when the
1761 bug is fixed in mod_dav. See Subversion Issue #2248 */
1762 if (err && (err->error_id != DAV_ERR_IF_ABSENT))
1763 return err;
1765 /* If one or more locktokens are present in the header, push them
1766 into the filesystem access context. */
1767 if (ltl)
1769 svn_fs_access_t *access_ctx;
1770 dav_locktoken_list *list = ltl;
1772 serr = svn_fs_get_access(&access_ctx, repos->fs);
1773 if (serr)
1775 return dav_svn__sanitize_error(serr, "Lock token is in request, "
1776 "but no user name",
1777 HTTP_BAD_REQUEST, r);
1780 do {
1781 serr = svn_fs_access_add_lock_token(access_ctx,
1782 list->locktoken->uuid_str);
1783 if (serr)
1784 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1785 "Error pushing token into filesystem.",
1786 r->pool);
1787 list = list->next;
1789 } while (list);
1793 /* Figure out the type of the resource. Note that we have a PARSE step
1794 which is separate from a PREP step. This is because the PARSE can
1795 map multiple URLs to the same resource type. The PREP operates on
1796 the type of the resource. */
1798 /* skip over the leading "/" in the relative URI */
1799 if (parse_uri(comb, relative + 1, label, use_checked_in))
1800 goto malformed_URI;
1802 #ifdef SVN_DEBUG
1803 if (comb->res.type == DAV_RESOURCE_TYPE_UNKNOWN)
1805 /* Unknown URI. Return NULL to indicate "no resource" */
1806 DBG0("DESIGN FAILURE: should not be UNKNOWN at this point");
1807 *resource = NULL;
1808 return NULL;
1810 #endif
1812 /* prepare the resource for operation */
1813 if ((err = prep_resource(comb)) != NULL)
1814 return err;
1816 /* a GET request for a REGULAR collection resource MUST have a trailing
1817 slash. Redirect to include one if it does not. */
1818 if (comb->res.collection && comb->res.type == DAV_RESOURCE_TYPE_REGULAR
1819 && !had_slash && r->method_number == M_GET)
1821 /* note that we drop r->args. we don't deal with them anyways */
1822 const char *new_path = apr_pstrcat(r->pool,
1823 ap_escape_uri(r->pool, r->uri),
1824 "/",
1825 NULL);
1826 apr_table_setn(r->headers_out, "Location",
1827 ap_construct_url(r->pool, new_path, r));
1828 return dav_new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0,
1829 "Requests for a collection must have a "
1830 "trailing slash on the URI.");
1833 *resource = &comb->res;
1834 return NULL;
1836 malformed_URI:
1837 /* A malformed URI error occurs when a URI indicates the "special" area,
1838 yet it has an improper construction. Generally, this is because some
1839 doofus typed it in manually or has a buggy client. */
1840 /* ### pick something other than HTTP_INTERNAL_SERVER_ERROR */
1841 /* ### are SVN_ERR_APMOD codes within the right numeric space? */
1842 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
1843 SVN_ERR_APMOD_MALFORMED_URI,
1844 "The URI indicated a resource within Subversion's "
1845 "special resource area, but does not exist. This is "
1846 "generally caused by a problem in the client "
1847 "software.");
1851 /* Helper func: return the parent of PATH, allocated in POOL. */
1852 static const char *
1853 get_parent_path(const char *path, apr_pool_t *pool)
1855 apr_size_t len;
1856 const char *parentpath, *base_name;
1857 char *tmp = apr_pstrdup(pool, path);
1859 len = strlen(tmp);
1861 if (len > 0)
1863 /* Remove any trailing slash; else svn_path_split() asserts. */
1864 if (tmp[len-1] == '/')
1865 tmp[len-1] = '\0';
1866 svn_path_split(tmp, &parentpath, &base_name, pool);
1868 return parentpath;
1871 return path;
1875 static dav_error *
1876 get_parent_resource(const dav_resource *resource,
1877 dav_resource **parent_resource)
1879 dav_resource *parent;
1880 dav_resource_private *parentinfo;
1881 svn_stringbuf_t *path = resource->info->uri_path;
1883 /* the root of the repository has no parent */
1884 if (path->len == 1 && *path->data == '/')
1886 *parent_resource = NULL;
1887 return NULL;
1890 switch (resource->type)
1892 case DAV_RESOURCE_TYPE_REGULAR:
1894 parent = apr_pcalloc(resource->pool, sizeof(*parent));
1895 parentinfo = apr_pcalloc(resource->pool, sizeof(*parentinfo));
1897 parent->type = DAV_RESOURCE_TYPE_REGULAR;
1898 parent->exists = 1;
1899 parent->collection = 1;
1900 parent->versioned = 1;
1901 parent->hooks = resource->hooks;
1902 parent->pool = resource->pool;
1903 parent->uri = get_parent_path(resource->uri, resource->pool);
1904 parent->info = parentinfo;
1906 parentinfo->pool = resource->info->pool;
1907 parentinfo->uri_path =
1908 svn_stringbuf_create(get_parent_path(resource->info->uri_path->data,
1909 resource->pool), resource->pool);
1910 parentinfo->repos = resource->info->repos;
1911 parentinfo->root = resource->info->root;
1912 parentinfo->r = resource->info->r;
1913 parentinfo->svn_client_options = resource->info->svn_client_options;
1914 parentinfo->repos_path = get_parent_path(resource->info->repos_path,
1915 resource->pool);
1917 *parent_resource = parent;
1918 break;
1920 case DAV_RESOURCE_TYPE_WORKING:
1921 /* The "/" occurring within the URL of working resources is part of
1922 its identifier; it does not establish parent resource relationships.
1923 All working resources have the same parent, which is:
1924 http://host.name/path2repos/$svn/wrk/
1926 *parent_resource =
1927 create_private_resource(resource, DAV_SVN_RESTYPE_WRK_COLLECTION);
1928 break;
1930 case DAV_RESOURCE_TYPE_ACTIVITY:
1931 *parent_resource =
1932 create_private_resource(resource, DAV_SVN_RESTYPE_ACT_COLLECTION);
1933 break;
1935 default:
1936 /* ### needs more work. need parents for other resource types
1938 ### return an error so we can easily identify the cases where
1939 ### we've called this function unexpectedly. */
1940 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1941 apr_psprintf(resource->pool,
1942 "get_parent_resource was called for "
1943 "%s (type %d)",
1944 resource->uri, resource->type));
1945 break;
1948 return NULL;
1952 /* does RES2 live in the same repository as RES1? */
1953 static int
1954 is_our_resource(const dav_resource *res1, const dav_resource *res2)
1956 if (res1->hooks != res2->hooks
1957 || strcmp(res1->info->repos->fs_path, res2->info->repos->fs_path) != 0)
1959 /* a different provider, or a different FS repository */
1960 return 0;
1963 /* coalesce the repository */
1964 if (res1->info->repos != res2->info->repos)
1966 /* ### might be nice to have a pool which we can clear to toss
1967 ### out the old, redundant repos/fs. */
1969 /* have res2 point to res1's filesystem */
1970 res2->info->repos = res1->info->repos;
1972 /* res2's fs_root object is now invalid. regenerate it using
1973 the now-shared filesystem. */
1974 if (res2->info->root.txn_name)
1976 /* reopen the txn by name */
1977 svn_error_clear(svn_fs_open_txn(&(res2->info->root.txn),
1978 res2->info->repos->fs,
1979 res2->info->root.txn_name,
1980 res2->info->repos->pool));
1982 /* regenerate the txn "root" object */
1983 svn_error_clear(svn_fs_txn_root(&(res2->info->root.root),
1984 res2->info->root.txn,
1985 res2->info->repos->pool));
1987 else if (res2->info->root.rev)
1989 /* default: regenerate the revision "root" object */
1990 svn_error_clear(svn_fs_revision_root(&(res2->info->root.root),
1991 res2->info->repos->fs,
1992 res2->info->root.rev,
1993 res2->info->repos->pool));
1997 return 1;
2001 static int
2002 is_same_resource(const dav_resource *res1, const dav_resource *res2)
2004 if (!is_our_resource(res1, res2))
2005 return 0;
2007 /* ### what if the same resource were reached via two URIs? */
2009 return svn_stringbuf_compare(res1->info->uri_path, res2->info->uri_path);
2013 static int
2014 is_parent_resource(const dav_resource *res1, const dav_resource *res2)
2016 apr_size_t len1 = strlen(res1->info->uri_path->data);
2017 apr_size_t len2;
2019 if (!is_our_resource(res1, res2))
2020 return 0;
2022 /* ### what if a resource were reached via two URIs? we ought to define
2023 ### parent/child relations for resources independent of URIs.
2024 ### i.e. define a "canonical" location for each resource, then return
2025 ### the parent based on that location. */
2027 /* res2 is one of our resources, we can use its ->info ptr */
2028 len2 = strlen(res2->info->uri_path->data);
2030 return (len2 > len1
2031 && memcmp(res1->info->uri_path->data, res2->info->uri_path->data,
2032 len1) == 0
2033 && res2->info->uri_path->data[len1] == '/');
2037 #if 0
2038 /* Given an apache request R and a ROOT_PATH to the svn location
2039 block, set *KIND to the node-kind of the URI's associated
2040 (revision, path) pair, if possible.
2042 Public uris, baseline collections, version resources, and working
2043 (non-baseline) resources all have associated (revision, path)
2044 pairs, and thus one of {svn_node_file, svn_node_dir, svn_node_none}
2045 will be returned.
2047 If URI is something more abstract, then set *KIND to
2048 svn_node_unknown. This is true for baselines, working baselines,
2049 version controled configurations, activities, histories, and other
2050 private resources.
2052 static dav_error *
2053 resource_kind(request_rec *r,
2054 const char *uri,
2055 const char *root_path,
2056 svn_node_kind_t *kind)
2058 dav_error *derr;
2059 svn_error_t *serr;
2060 dav_resource *resource;
2061 svn_revnum_t base_rev;
2062 svn_fs_root_t *base_rev_root;
2063 char *saved_uri;
2065 /* Temporarily insert the uri that the user actually wants us to
2066 convert into a resource. Typically, this is already r->uri, so
2067 this is usually a no-op. But sometimes the caller may pass in
2068 the Destination: header uri.
2070 ### WHAT WE REALLY WANT here is to refactor get_resource,
2071 so that some alternate interface actually allows us to specify
2072 the URI to process, i.e. not always process r->uri.
2074 saved_uri = r->uri;
2075 r->uri = apr_pstrdup(r->pool, uri);
2077 /* parse the uri and prep the associated resource. */
2078 derr = get_resource(r, root_path,
2079 /* ### I can't believe that every single
2080 parser ignores the LABEL and USE_CHECKED_IN
2081 args below!! */
2082 "ignored_label", 1,
2083 &resource);
2084 /* Restore r back to normal. */
2085 r->uri = saved_uri;
2087 if (derr)
2088 return derr;
2090 if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
2092 /* Either a public URI or a bc. In both cases, prep_regular()
2093 has already set the 'exists' and 'collection' flags by
2094 querying the appropriate revision root and path. */
2095 if (! resource->exists)
2096 *kind = svn_node_none;
2097 else
2098 *kind = resource->collection ? svn_node_dir : svn_node_file;
2101 else if (resource->type == DAV_RESOURCE_TYPE_VERSION)
2103 if (resource->baselined) /* bln */
2104 *kind = svn_node_unknown;
2106 else /* ver */
2108 derr = fs_check_path(kind, resource->info->root.root,
2109 resource->info->repos_path, r->pool);
2110 if (derr != NULL)
2111 return derr;
2115 else if (resource->type == DAV_RESOURCE_TYPE_WORKING)
2117 if (resource->baselined) /* wbl */
2118 *kind = svn_node_unknown;
2120 else /* wrk */
2122 /* don't call fs_check_path on the txn, but on the original
2123 revision that the txn is based on. */
2124 base_rev = svn_fs_txn_base_revision(resource->info->root.txn);
2125 serr = svn_fs_revision_root(&base_rev_root,
2126 resource->info->repos->fs,
2127 base_rev, r->pool);
2128 if (serr)
2129 return dav_svn__convert_err
2130 (serr, HTTP_INTERNAL_SERVER_ERROR,
2131 apr_psprintf(r->pool,
2132 "Could not open root of revision %ld",
2133 base_rev),
2134 r->pool);
2136 derr = fs_check_path(kind, base_rev_root,
2137 resource->info->repos_path, r->pool);
2138 if (derr != NULL)
2139 return derr;
2143 else
2144 /* act, his, vcc, or some other private resource */
2145 *kind = svn_node_unknown;
2147 return NULL;
2149 #endif
2152 static dav_error *
2153 open_stream(const dav_resource *resource,
2154 dav_stream_mode mode,
2155 dav_stream **stream)
2157 svn_node_kind_t kind;
2158 dav_error *derr;
2159 svn_error_t *serr;
2161 if (mode == DAV_MODE_WRITE_TRUNC || mode == DAV_MODE_WRITE_SEEKABLE)
2163 if (resource->type != DAV_RESOURCE_TYPE_WORKING)
2165 return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
2166 "Resource body changes may only be made to "
2167 "working resources [at this time].");
2171 #if 1
2172 if (mode == DAV_MODE_WRITE_SEEKABLE)
2174 return dav_new_error(resource->pool, HTTP_NOT_IMPLEMENTED, 0,
2175 "Resource body writes cannot use ranges "
2176 "[at this time].");
2178 #endif
2180 /* start building the stream structure */
2181 *stream = apr_pcalloc(resource->pool, sizeof(**stream));
2182 (*stream)->res = resource;
2184 derr = fs_check_path(&kind, resource->info->root.root,
2185 resource->info->repos_path, resource->pool);
2186 if (derr != NULL)
2187 return derr;
2189 if (kind == svn_node_none) /* No existing file. */
2191 serr = svn_fs_make_file(resource->info->root.root,
2192 resource->info->repos_path,
2193 resource->pool);
2195 if (serr != NULL)
2197 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2198 "Could not create file within the "
2199 "repository.",
2200 resource->pool);
2204 /* if the working-resource was auto-checked-out (i.e. came into
2205 existence through the autoversioning feature), then possibly set
2206 the svn:mime-type property based on whatever value mod_mime has
2207 chosen. If the path already has an svn:mime-type property
2208 set, do nothing. */
2209 if (resource->info->auto_checked_out
2210 && resource->info->r->content_type)
2212 svn_string_t *mime_type;
2214 serr = svn_fs_node_prop(&mime_type,
2215 resource->info->root.root,
2216 resource->info->repos_path,
2217 SVN_PROP_MIME_TYPE,
2218 resource->pool);
2220 if (serr != NULL)
2222 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2223 "Error fetching mime-type property.",
2224 resource->pool);
2227 if (!mime_type)
2229 serr = svn_fs_change_node_prop(resource->info->root.root,
2230 resource->info->repos_path,
2231 SVN_PROP_MIME_TYPE,
2232 svn_string_create
2233 (resource->info->r->content_type,
2234 resource->pool),
2235 resource->pool);
2236 if (serr != NULL)
2238 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2239 "Could not set mime-type property.",
2240 resource->pool);
2245 serr = svn_fs_apply_textdelta(&(*stream)->delta_handler,
2246 &(*stream)->delta_baton,
2247 resource->info->root.root,
2248 resource->info->repos_path,
2249 resource->info->base_checksum,
2250 resource->info->result_checksum,
2251 resource->pool);
2253 if (serr != NULL)
2255 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2256 "Could not prepare to write the file",
2257 resource->pool);
2260 /* if the incoming data is an SVNDIFF, then create a stream that
2261 will process the data into windows and invoke the FS window handler
2262 when a window is ready. */
2263 /* ### we need a better way to check the content-type! this is bogus
2264 ### because we're effectively looking at the request_rec. doubly
2265 ### bogus because this means you cannot open arbitrary streams and
2266 ### feed them content (the type is always tied to a request_rec).
2267 ### probably ought to pass the type to open_stream */
2268 if (resource->info->is_svndiff)
2270 (*stream)->wstream =
2271 svn_txdelta_parse_svndiff((*stream)->delta_handler,
2272 (*stream)->delta_baton,
2273 TRUE,
2274 resource->pool);
2277 return NULL;
2281 static dav_error *
2282 close_stream(dav_stream *stream, int commit)
2284 svn_error_t *serr;
2285 apr_pool_t *pool = stream->res->pool;
2287 if (stream->rstream != NULL)
2289 serr = svn_stream_close(stream->rstream);
2290 if (serr)
2291 return dav_svn__convert_err
2292 (serr, HTTP_INTERNAL_SERVER_ERROR,
2293 "mod_dav_svn close_stream: error closing read stream",
2294 pool);
2297 /* if we have a write-stream, then closing it also takes care of the
2298 handler (so make sure not to send a NULL to it, too) */
2299 if (stream->wstream != NULL)
2301 serr = svn_stream_close(stream->wstream);
2302 if (serr)
2303 return dav_svn__convert_err
2304 (serr, HTTP_INTERNAL_SERVER_ERROR,
2305 "mod_dav_svn close_stream: error closing write stream",
2306 pool);
2308 else if (stream->delta_handler != NULL)
2310 serr = (*stream->delta_handler)(NULL, stream->delta_baton);
2311 if (serr)
2312 return dav_svn__convert_err
2313 (serr, HTTP_INTERNAL_SERVER_ERROR,
2314 "mod_dav_svn close_stream: error sending final (null) delta window",
2315 pool);
2318 return NULL;
2322 static dav_error *
2323 write_stream(dav_stream *stream, const void *buf, apr_size_t bufsize)
2325 svn_error_t *serr;
2326 apr_pool_t *pool = stream->res->pool;
2328 if (stream->wstream != NULL)
2330 serr = svn_stream_write(stream->wstream, buf, &bufsize);
2331 /* ### would the returned bufsize ever not match the requested amt? */
2333 else
2335 svn_txdelta_window_t window = { 0 };
2336 svn_txdelta_op_t op;
2337 svn_string_t data;
2339 data.data = buf;
2340 data.len = bufsize;
2342 op.action_code = svn_txdelta_new;
2343 op.offset = 0;
2344 op.length = bufsize;
2346 window.tview_len = bufsize; /* result will be this long */
2347 window.num_ops = 1;
2348 window.ops = &op;
2349 window.new_data = &data;
2351 serr = (*stream->delta_handler)(&window, stream->delta_baton);
2354 if (serr)
2356 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2357 "could not write the file contents",
2358 pool);
2360 return NULL;
2364 static dav_error *
2365 seek_stream(dav_stream *stream, apr_off_t abs_position)
2367 /* ### fill this in */
2369 return dav_new_error(stream->res->pool, HTTP_NOT_IMPLEMENTED, 0,
2370 "Resource body read/write cannot use ranges "
2371 "(at this time)");
2374 /* Returns whether the DAV resource lacks potential for generation of
2375 an ETag (defined as any of the following):
2376 - it doesn't exist
2377 - the resource type isn't REGULAR or VERSION
2378 - the resource is a Baseline */
2379 #define RESOURCE_LACKS_ETAG_POTENTIAL(resource) \
2380 (!resource->exists \
2381 || (resource->type != DAV_RESOURCE_TYPE_REGULAR \
2382 && resource->type != DAV_RESOURCE_TYPE_VERSION) \
2383 || (resource->type == DAV_RESOURCE_TYPE_VERSION \
2384 && resource->baselined))
2387 /* Return the last modification time of RESOURCE, or -1 if the DAV
2388 resource type is not handled, or if an error occurs. Temporary
2389 allocations are made from RESOURCE->POOL. */
2390 static apr_time_t
2391 get_last_modified(const dav_resource *resource)
2393 apr_time_t last_modified;
2394 svn_error_t *serr;
2395 svn_revnum_t created_rev;
2396 svn_string_t *date_time;
2398 if (RESOURCE_LACKS_ETAG_POTENTIAL(resource))
2399 return -1;
2401 if ((serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root,
2402 resource->info->repos_path,
2403 resource->pool)))
2405 svn_error_clear(serr);
2406 return -1;
2409 if ((serr = svn_fs_revision_prop(&date_time, resource->info->repos->fs,
2410 created_rev, "svn:date", resource->pool)))
2412 svn_error_clear(serr);
2413 return -1;
2416 if (date_time == NULL || date_time->data == NULL)
2417 return -1;
2419 if ((serr = svn_time_from_cstring(&last_modified, date_time->data,
2420 resource->pool)))
2422 svn_error_clear(serr);
2423 return -1;
2426 return last_modified;
2430 const char *
2431 dav_svn__getetag(const dav_resource *resource, apr_pool_t *pool)
2433 svn_error_t *serr;
2434 svn_revnum_t created_rev;
2436 if (RESOURCE_LACKS_ETAG_POTENTIAL(resource))
2437 return "";
2439 /* ### what kind of etag to return for activities, etc.? */
2441 if ((serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root,
2442 resource->info->repos_path,
2443 pool)))
2445 /* ### what to do? */
2446 svn_error_clear(serr);
2447 return "";
2450 /* Use the "weak" format of the etag for collections because our GET
2451 requests on collections include dynamic data (the HEAD revision,
2452 the build version of Subversion, etc.). */
2453 return apr_psprintf(pool, "%s\"%ld/%s\"",
2454 resource->collection ? "W/" : "",
2455 created_rev,
2456 apr_xml_quote_string(pool,
2457 resource->info->repos_path, 1));
2461 /* Since dav_svn__getetag() takes a pool argument, this wrapper is for
2462 the mod_dav hooks vtable entry, which does not. */
2463 static const char *
2464 getetag_pathetic(const dav_resource *resource)
2466 return dav_svn__getetag(resource, resource->pool);
2470 static dav_error *
2471 set_headers(request_rec *r, const dav_resource *resource)
2473 svn_error_t *serr;
2474 svn_filesize_t length;
2475 const char *mimetype = NULL;
2476 apr_time_t last_modified;
2478 if (!resource->exists)
2479 return NULL;
2481 last_modified = get_last_modified(resource);
2482 if (last_modified != -1)
2484 /* Note the modification time for the requested resource, and
2485 include the Last-Modified header in the response. */
2486 ap_update_mtime(r, last_modified);
2487 ap_set_last_modified(r);
2490 /* generate our etag and place it into the output */
2491 apr_table_setn(r->headers_out, "ETag",
2492 dav_svn__getetag(resource, resource->pool));
2494 #if 0
2495 /* As version resources don't change, encourage caching. */
2496 /* ### FIXME: This conditional is wrong -- type is often REGULAR,
2497 ### and the resource doesn't seem to be baselined. */
2498 if (resource->type == DAV_RESOURCE_TYPE_VERSION)
2499 /* Cache resource for one week (specified in seconds). */
2500 apr_table_setn(r->headers_out, "Cache-Control", "max-age=604800");
2501 #endif
2503 /* we accept byte-ranges */
2504 apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
2506 /* For a directory, we will send text/html or text/xml. If we have a delta
2507 base, then we will always be generating an svndiff. Otherwise,
2508 we need to fetch the appropriate MIME type from the resource's
2509 properties (and use text/plain if it isn't there). */
2510 if (resource->collection)
2512 if (resource->info->repos->xslt_uri)
2513 mimetype = "text/xml";
2514 else
2515 mimetype = "text/html; charset=UTF-8";
2517 else if (resource->info->delta_base != NULL)
2519 dav_svn__uri_info info;
2521 /* First order of business is to parse it. */
2522 serr = dav_svn__simple_parse_uri(&info, resource,
2523 resource->info->delta_base,
2524 resource->pool);
2526 /* If we successfully parse the base URL, then send an svndiff. */
2527 if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM))
2529 mimetype = SVN_SVNDIFF_MIME_TYPE;
2531 svn_error_clear(serr);
2534 if ((mimetype == NULL)
2535 && ((resource->type == DAV_RESOURCE_TYPE_VERSION)
2536 || (resource->type == DAV_RESOURCE_TYPE_REGULAR))
2537 && (resource->info->repos_path != NULL))
2539 svn_string_t *value;
2541 serr = svn_fs_node_prop(&value,
2542 resource->info->root.root,
2543 resource->info->repos_path,
2544 SVN_PROP_MIME_TYPE,
2545 resource->pool);
2546 if (serr != NULL)
2547 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2548 "could not fetch the resource's MIME type",
2549 resource->pool);
2551 if (value)
2552 mimetype = value->data;
2553 else if ((! resource->info->repos->is_svn_client)
2554 && r->content_type)
2555 mimetype = r->content_type;
2556 else
2557 mimetype = ap_default_type(r);
2559 serr = svn_mime_type_validate(mimetype, resource->pool);
2560 if (serr)
2562 /* Probably serr->apr == SVN_ERR_BAD_MIME_TYPE, but
2563 there's no point even checking. No matter what the
2564 error is, we can't derive the mime type from the
2565 svn:mime-type property. So we resort to the infamous
2566 "mime type of last resort." */
2567 svn_error_clear(serr);
2568 mimetype = "application/octet-stream";
2571 /* if we aren't sending a diff, then we know the length of the file,
2572 so set up the Content-Length header */
2573 serr = svn_fs_file_length(&length,
2574 resource->info->root.root,
2575 resource->info->repos_path,
2576 resource->pool);
2577 if (serr != NULL)
2579 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2580 "could not fetch the resource length",
2581 resource->pool);
2583 ap_set_content_length(r, (apr_off_t) length);
2586 /* set the discovered MIME type */
2587 /* ### it would be best to do this during the findct phase... */
2588 ap_set_content_type(r, mimetype);
2590 return NULL;
2594 typedef struct {
2595 ap_filter_t *output;
2596 apr_pool_t *pool;
2597 } diff_ctx_t;
2600 static svn_error_t *
2601 write_to_filter(void *baton, const char *buffer, apr_size_t *len)
2603 diff_ctx_t *dc = baton;
2604 apr_bucket_brigade *bb;
2605 apr_bucket *bkt;
2606 apr_status_t status;
2608 /* take the current data and shove it into the filter */
2609 bb = apr_brigade_create(dc->pool, dc->output->c->bucket_alloc);
2610 bkt = apr_bucket_transient_create(buffer, *len, dc->output->c->bucket_alloc);
2611 APR_BRIGADE_INSERT_TAIL(bb, bkt);
2612 if ((status = ap_pass_brigade(dc->output, bb)) != APR_SUCCESS) {
2613 return svn_error_create(status, NULL,
2614 "Could not write data to filter");
2617 return SVN_NO_ERROR;
2621 static svn_error_t *
2622 close_filter(void *baton)
2624 diff_ctx_t *dc = baton;
2625 apr_bucket_brigade *bb;
2626 apr_bucket *bkt;
2627 apr_status_t status;
2629 /* done with the file. write an EOS bucket now. */
2630 bb = apr_brigade_create(dc->pool, dc->output->c->bucket_alloc);
2631 bkt = apr_bucket_eos_create(dc->output->c->bucket_alloc);
2632 APR_BRIGADE_INSERT_TAIL(bb, bkt);
2633 if ((status = ap_pass_brigade(dc->output, bb)) != APR_SUCCESS)
2634 return svn_error_create(status, NULL, "Could not write EOS to filter");
2636 return SVN_NO_ERROR;
2640 static dav_error *
2641 deliver(const dav_resource *resource, ap_filter_t *output)
2643 svn_error_t *serr;
2644 apr_bucket_brigade *bb;
2645 apr_bucket *bkt;
2646 apr_status_t status;
2648 /* Check resource type */
2649 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
2650 && resource->type != DAV_RESOURCE_TYPE_VERSION
2651 && resource->type != DAV_RESOURCE_TYPE_WORKING
2652 && resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
2654 return dav_new_error(resource->pool, HTTP_CONFLICT, 0,
2655 "Cannot GET this type of resource.");
2658 if (resource->collection)
2660 const int gen_html = !resource->info->repos->xslt_uri;
2661 apr_hash_t *entries;
2662 apr_pool_t *entry_pool;
2663 apr_array_header_t *sorted;
2664 int i;
2666 /* XML schema for the directory index if xslt_uri is set:
2668 <?xml version="1.0"?>
2669 <?xml-stylesheet type="text/xsl" href="[info->repos->xslt_uri]"?> */
2670 static const char xml_index_dtd[] =
2671 "<!DOCTYPE svn [\n"
2672 " <!ELEMENT svn (index)>\n"
2673 " <!ATTLIST svn version CDATA #REQUIRED\n"
2674 " href CDATA #REQUIRED>\n"
2675 " <!ELEMENT index (updir?, (file | dir)*)>\n"
2676 " <!ATTLIST index name CDATA #IMPLIED\n"
2677 " path CDATA #IMPLIED\n"
2678 " rev CDATA #IMPLIED\n"
2679 " base CDATA #IMPLIED>\n"
2680 " <!ELEMENT updir EMPTY>\n"
2681 " <!ELEMENT file EMPTY>\n"
2682 " <!ATTLIST file name CDATA #REQUIRED\n"
2683 " href CDATA #REQUIRED>\n"
2684 " <!ELEMENT dir EMPTY>\n"
2685 " <!ATTLIST dir name CDATA #REQUIRED\n"
2686 " href CDATA #REQUIRED>\n"
2687 "]>\n";
2689 /* <svn version="1.3.0 (dev-build)"
2690 href="http://subversion.tigris.org">
2691 <index name="[info->repos->repo_name]"
2692 path="[info->repos_path]"
2693 rev="[info->root.rev]">
2694 <file name="foo" href="foo" />
2695 <dir name="bar" href="bar/" />
2696 </index>
2697 </svn> */
2700 /* ### TO-DO: check for a new mod_dav_svn directive here also. */
2701 if (resource->info->restype == DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
2703 apr_hash_index_t *hi;
2704 apr_hash_t *dirents;
2705 const char *fs_parent_path =
2706 dav_svn__get_fs_parent_path(resource->info->r);
2708 serr = svn_io_get_dirents2(&dirents, fs_parent_path, resource->pool);
2709 if (serr != NULL)
2710 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2711 "couldn't fetch dirents of SVNParentPath",
2712 resource->pool);
2714 /* convert an io dirent hash to an fs dirent hash. */
2715 entries = apr_hash_make(resource->pool);
2716 for (hi = apr_hash_first(resource->pool, dirents);
2717 hi; hi = apr_hash_next(hi))
2719 const void *key;
2720 void *val;
2721 svn_io_dirent_t *dirent;
2722 svn_fs_dirent_t *ent = apr_pcalloc(resource->pool, sizeof(*ent));
2724 apr_hash_this(hi, &key, NULL, &val);
2725 dirent = val;
2727 if (dirent->kind != svn_node_dir)
2728 continue;
2730 ent->name = key;
2731 ent->id = NULL; /* ### does it matter? */
2732 ent->kind = dirent->kind;
2734 apr_hash_set(entries, key, APR_HASH_KEY_STRING, ent);
2738 else
2740 serr = svn_fs_dir_entries(&entries, resource->info->root.root,
2741 resource->info->repos_path, resource->pool);
2742 if (serr != NULL)
2743 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2744 "could not fetch directory entries",
2745 resource->pool);
2748 bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
2750 if (gen_html)
2752 const char *title;
2753 if (resource->info->repos_path == NULL)
2754 title = "unknown location";
2755 else
2756 title = resource->info->repos_path;
2758 if (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)
2760 if (SVN_IS_VALID_REVNUM(resource->info->root.rev))
2761 title = apr_psprintf(resource->pool,
2762 "Revision %ld: %s",
2763 resource->info->root.rev, title);
2764 if (resource->info->repos->repo_basename)
2765 title = apr_psprintf(resource->pool, "%s - %s",
2766 resource->info->repos->repo_basename,
2767 title);
2768 if (resource->info->repos->repo_name)
2769 title = apr_psprintf(resource->pool, "%s: %s",
2770 resource->info->repos->repo_name,
2771 title);
2774 ap_fprintf(output, bb, "<html><head><title>%s</title></head>\n"
2775 "<body>\n <h2>%s</h2>\n <ul>\n", title, title);
2777 else
2779 const char *name = resource->info->repos->repo_name;
2780 const char *href = resource->info->repos_path;
2781 const char *base = resource->info->repos->repo_basename;
2783 ap_fputs(output, bb, "<?xml version=\"1.0\"?>\n");
2784 ap_fprintf(output, bb,
2785 "<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>\n",
2786 resource->info->repos->xslt_uri);
2787 ap_fputs(output, bb, xml_index_dtd);
2788 ap_fputs(output, bb,
2789 "<svn version=\"" SVN_VERSION "\"\n"
2790 " href=\"http://subversion.tigris.org/\">\n");
2791 ap_fputs(output, bb, " <index");
2792 if (name)
2793 ap_fprintf(output, bb, " name=\"%s\"",
2794 apr_xml_quote_string(resource->pool, name, 1));
2795 if (SVN_IS_VALID_REVNUM(resource->info->root.rev))
2796 ap_fprintf(output, bb, " rev=\"%ld\"",
2797 resource->info->root.rev);
2798 if (href)
2799 ap_fprintf(output, bb, " path=\"%s\"",
2800 apr_xml_quote_string(resource->pool,
2801 href,
2802 1));
2803 if (base)
2804 ap_fprintf(output, bb, " base=\"%s\"", base);
2806 ap_fputs(output, bb, ">\n");
2809 if ((resource->info->repos_path && resource->info->repos_path[1] != '\0')
2810 && (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION))
2812 if (gen_html)
2813 ap_fprintf(output, bb, " <li><a href=\"../\">..</a></li>\n");
2814 else
2815 ap_fprintf(output, bb, " <updir />\n");
2818 /* get a sorted list of the entries */
2819 sorted = svn_sort__hash(entries, svn_sort_compare_items_as_paths,
2820 resource->pool);
2822 entry_pool = svn_pool_create(resource->pool);
2824 for (i = 0; i < sorted->nelts; ++i)
2826 const svn_sort__item_t *item = &APR_ARRAY_IDX(sorted, i,
2827 const svn_sort__item_t);
2828 const svn_fs_dirent_t *entry = item->value;
2829 const char *name = item->key;
2830 const char *href = name;
2831 svn_boolean_t is_dir = (entry->kind == svn_node_dir);
2833 svn_pool_clear(entry_pool);
2835 /* append a trailing slash onto the name for directories. we NEED
2836 this for the href portion so that the relative reference will
2837 descend properly. for the visible portion, it is just nice. */
2838 /* ### The xml output doesn't like to see a trailing slash on
2839 ### the visible portion, so avoid that. */
2840 if (is_dir)
2841 href = apr_pstrcat(entry_pool, href, "/", NULL);
2843 if (gen_html)
2844 name = href;
2846 /* We quote special characters in both XML and HTML. */
2847 name = apr_xml_quote_string(entry_pool, name, !gen_html);
2849 /* According to httpd-2.0.54/include/httpd.h, ap_os_escape_path()
2850 behaves differently on different platforms. It claims to
2851 "convert an OS path to a URL in an OS dependant way".
2852 Nevertheless, there appears to be only one implementation
2853 of the function in httpd, and the code seems completely
2854 platform independent, so we'll assume it's appropriate for
2855 mod_dav_svn to use it to quote outbound paths. */
2856 href = ap_os_escape_path(entry_pool, href, 0);
2857 href = apr_xml_quote_string(entry_pool, href, 1);
2859 if (gen_html)
2861 ap_fprintf(output, bb,
2862 " <li><a href=\"%s\">%s</a></li>\n",
2863 href, name);
2865 else
2867 const char *const tag = (is_dir ? "dir" : "file");
2869 /* This is where we could search for props */
2871 ap_fprintf(output, bb,
2872 " <%s name=\"%s\" href=\"%s\" />\n",
2873 tag, name, href);
2877 svn_pool_destroy(entry_pool);
2879 if (gen_html)
2880 ap_fputs(output, bb,
2881 " </ul>\n <hr noshade><em>Powered by "
2882 "<a href=\"http://subversion.tigris.org/\">Subversion</a> "
2883 "version " SVN_VERSION "."
2884 "</em>\n</body></html>");
2885 else
2886 ap_fputs(output, bb, " </index>\n</svn>\n");
2888 bkt = apr_bucket_eos_create(output->c->bucket_alloc);
2889 APR_BRIGADE_INSERT_TAIL(bb, bkt);
2890 if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS)
2891 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2892 "Could not write EOS to filter.");
2894 return NULL;
2898 /* If we have a base for a delta, then we want to compute an svndiff
2899 between the provided base and the requested resource. For a simple
2900 request, then we just grab the file contents. */
2901 if (resource->info->delta_base != NULL)
2903 dav_svn__uri_info info;
2904 svn_fs_root_t *root;
2905 svn_boolean_t is_file;
2906 svn_txdelta_stream_t *txd_stream;
2907 svn_stream_t *o_stream;
2908 svn_txdelta_window_handler_t handler;
2909 void * h_baton;
2910 diff_ctx_t dc = { 0 };
2912 /* First order of business is to parse it. */
2913 serr = dav_svn__simple_parse_uri(&info, resource,
2914 resource->info->delta_base,
2915 resource->pool);
2917 /* If we successfully parse the base URL, then send an svndiff. */
2918 if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM))
2920 /* We are always accessing the base resource by ID, so open
2921 an ID root. */
2922 serr = svn_fs_revision_root(&root, resource->info->repos->fs,
2923 info.rev, resource->pool);
2924 if (serr != NULL)
2925 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2926 "could not open a root for the base",
2927 resource->pool);
2929 /* verify that it is a file */
2930 serr = svn_fs_is_file(&is_file, root, info.repos_path,
2931 resource->pool);
2932 if (serr != NULL)
2933 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2934 "could not determine if the base "
2935 "is really a file",
2936 resource->pool);
2937 if (!is_file)
2938 return dav_new_error(resource->pool, HTTP_BAD_REQUEST, 0,
2939 "the delta base does not refer to a file");
2941 /* Okay. Let's open up a delta stream for the client to read. */
2942 serr = svn_fs_get_file_delta_stream(&txd_stream,
2943 root, info.repos_path,
2944 resource->info->root.root,
2945 resource->info->repos_path,
2946 resource->pool);
2947 if (serr != NULL)
2948 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2949 "could not prepare to read a delta",
2950 resource->pool);
2952 /* create a stream that svndiff data will be written to,
2953 which will copy it to the network */
2954 dc.output = output;
2955 dc.pool = resource->pool;
2956 o_stream = svn_stream_create(&dc, resource->pool);
2957 svn_stream_set_write(o_stream, write_to_filter);
2958 svn_stream_set_close(o_stream, close_filter);
2960 /* get a handler/baton for writing into the output stream */
2961 svn_txdelta_to_svndiff2(&handler, &h_baton,
2962 o_stream, resource->info->svndiff_version,
2963 resource->pool);
2965 /* got everything set up. read in delta windows and shove them into
2966 the handler, which pushes data into the output stream, which goes
2967 to the network. */
2968 serr = svn_txdelta_send_txstream(txd_stream, handler, h_baton,
2969 resource->pool);
2970 if (serr != NULL)
2971 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2972 "could not deliver the txdelta stream",
2973 resource->pool);
2976 return NULL;
2978 else
2980 svn_error_clear(serr);
2984 /* resource->info->delta_base is NULL, or we had an invalid base URL */
2986 svn_stream_t *stream;
2987 char *block;
2989 serr = svn_fs_file_contents(&stream,
2990 resource->info->root.root,
2991 resource->info->repos_path,
2992 resource->pool);
2993 if (serr != NULL)
2995 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
2996 "could not prepare to read the file",
2997 resource->pool);
3000 /* ### one day in the future, we can create a custom bucket type
3001 ### which will read from the FS stream on demand */
3003 block = apr_palloc(resource->pool, SVN__STREAM_CHUNK_SIZE);
3004 while (1) {
3005 apr_size_t bufsize = SVN__STREAM_CHUNK_SIZE;
3007 /* read from the FS ... */
3008 serr = svn_stream_read(stream, block, &bufsize);
3009 if (serr != NULL)
3011 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3012 "could not read the file contents",
3013 resource->pool);
3015 if (bufsize == 0)
3016 break;
3018 /* build a brigade and write to the filter ... */
3019 bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
3020 bkt = apr_bucket_transient_create(block, bufsize,
3021 output->c->bucket_alloc);
3022 APR_BRIGADE_INSERT_TAIL(bb, bkt);
3023 if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
3024 /* ### what to do with status; and that HTTP code... */
3025 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3026 "Could not write data to filter.");
3030 /* done with the file. write an EOS bucket now. */
3031 bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
3032 bkt = apr_bucket_eos_create(output->c->bucket_alloc);
3033 APR_BRIGADE_INSERT_TAIL(bb, bkt);
3034 if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
3035 /* ### what to do with status; and that HTTP code... */
3036 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3037 "Could not write EOS to filter.");
3040 return NULL;
3045 static dav_error *
3046 create_collection(dav_resource *resource)
3048 svn_error_t *serr;
3049 dav_error *err;
3051 if (resource->type != DAV_RESOURCE_TYPE_WORKING
3052 && resource->type != DAV_RESOURCE_TYPE_REGULAR)
3054 return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3055 "Collections can only be created within a working "
3056 "or regular collection [at this time].");
3059 /* ...regular resources allowed only if autoversioning is turned on. */
3060 if (resource->type == DAV_RESOURCE_TYPE_REGULAR
3061 && ! (resource->info->repos->autoversioning))
3062 return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3063 "MKCOL called on regular resource, but "
3064 "autoversioning is not active.");
3066 /* ### note that the parent was checked out at some point, and this
3067 ### is being preformed relative to the working rsrc for that parent */
3069 /* Auto-versioning mkcol of regular resource: */
3070 if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
3072 /* Change the VCR into a WR, in place. This creates a txn and
3073 changes resource->info->root from a rev-root into a txn-root. */
3074 err = dav_svn__checkout(resource,
3075 1 /* auto-checkout */,
3076 0, 0, 0, NULL, NULL);
3077 if (err)
3078 return err;
3081 if ((serr = svn_fs_make_dir(resource->info->root.root,
3082 resource->info->repos_path,
3083 resource->pool)) != NULL)
3085 /* ### need a better error */
3086 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3087 "Could not create the collection.",
3088 resource->pool);
3091 /* Auto-versioning commit of the txn. */
3092 if (resource->info->auto_checked_out)
3094 /* This also changes the WR back into a VCR, in place. */
3095 err = dav_svn__checkin(resource, 0, NULL);
3096 if (err)
3097 return err;
3100 return NULL;
3104 static dav_error *
3105 copy_resource(const dav_resource *src,
3106 dav_resource *dst,
3107 int depth,
3108 dav_response **response)
3110 svn_error_t *serr;
3111 dav_error *err;
3112 const char *src_repos_path, *dst_repos_path;
3114 /* ### source must be from a collection under baseline control. the
3115 ### baseline will (implicitly) indicate the source revision, and the
3116 ### path will be derived simply from the URL path */
3118 /* ### the destination's parent must be a working collection */
3120 /* ### ben goofing around: */
3121 /* char *msg;
3122 apr_psprintf
3123 (src->pool, "Got a COPY request with src arg '%s' and dst arg '%s'",
3124 src->uri, dst->uri);
3126 return dav_new_error(src->pool, HTTP_NOT_IMPLEMENTED, 0, msg);
3129 /* ### Safeguard: see issue #916, whereby we're allowing an
3130 auto-checkout of a baseline for PROPPATCHing, *without* creating
3131 a new baseline afterwards. We need to safeguard here that nobody
3132 is calling COPY with the baseline as a Destination! */
3133 if (dst->baselined && dst->type == DAV_RESOURCE_TYPE_VERSION)
3134 return dav_new_error(src->pool, HTTP_PRECONDITION_FAILED, 0,
3135 "Illegal: COPY Destination is a baseline.");
3137 if (dst->type == DAV_RESOURCE_TYPE_REGULAR
3138 && !(dst->info->repos->autoversioning))
3139 return dav_new_error(dst->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3140 "COPY called on regular resource, but "
3141 "autoversioning is not active.");
3143 /* Auto-versioning copy of regular resource: */
3144 if (dst->type == DAV_RESOURCE_TYPE_REGULAR)
3146 /* Change the VCR into a WR, in place. This creates a txn and
3147 changes dst->info->root from a rev-root into a txn-root. */
3148 err = dav_svn__checkout(dst,
3149 1 /* auto-checkout */,
3150 0, 0, 0, NULL, NULL);
3151 if (err)
3152 return err;
3155 serr = svn_path_get_absolute(&src_repos_path,
3156 svn_repos_path(src->info->repos->repos,
3157 src->pool),
3158 src->pool);
3159 if (!serr)
3160 serr = svn_path_get_absolute(&dst_repos_path,
3161 svn_repos_path(dst->info->repos->repos,
3162 dst->pool),
3163 dst->pool);
3165 if (!serr)
3167 if (strcmp(src_repos_path, dst_repos_path) != 0)
3168 return dav_svn__new_error_tag
3169 (dst->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3170 "Copy source and destination are in different repositories.",
3171 SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG);
3172 serr = svn_fs_copy(src->info->root.root, /* root object of src rev*/
3173 src->info->repos_path, /* relative path of src */
3174 dst->info->root.root, /* root object of dst txn*/
3175 dst->info->repos_path, /* relative path of dst */
3176 src->pool);
3178 if (serr)
3179 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3180 "Unable to make a filesystem copy.",
3181 dst->pool);
3183 /* Auto-versioning commit of the txn. */
3184 if (dst->info->auto_checked_out)
3186 /* This also changes the WR back into a VCR, in place. */
3187 err = dav_svn__checkin(dst, 0, NULL);
3188 if (err)
3189 return err;
3192 return NULL;
3196 static dav_error *
3197 remove_resource(dav_resource *resource, dav_response **response)
3199 svn_error_t *serr;
3200 dav_error *err;
3201 apr_hash_t *locks;
3203 /* Only activities, and working or regular resources can be deleted... */
3204 if (resource->type != DAV_RESOURCE_TYPE_WORKING
3205 && resource->type != DAV_RESOURCE_TYPE_REGULAR
3206 && resource->type != DAV_RESOURCE_TYPE_ACTIVITY)
3207 return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3208 "DELETE called on invalid resource type.");
3210 /* ...and regular resources only if autoversioning is turned on. */
3211 if (resource->type == DAV_RESOURCE_TYPE_REGULAR
3212 && ! (resource->info->repos->autoversioning))
3213 return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3214 "DELETE called on regular resource, but "
3215 "autoversioning is not active.");
3217 /* Handle activity deletions (early exit). */
3218 if (resource->type == DAV_RESOURCE_TYPE_ACTIVITY)
3220 return dav_svn__delete_activity(resource->info->repos,
3221 resource->info->root.activity_id);
3224 /* ### note that the parent was checked out at some point, and this
3225 ### is being preformed relative to the working rsrc for that parent */
3227 /* NOTE: strictly speaking, we cannot determine whether the parent was
3228 ever checked out, and that this working resource is relative to that
3229 checked out parent. It is entirely possible the client checked out
3230 the target resource and just deleted it. Subversion doesn't mind, but
3231 this does imply we are not enforcing the "checkout the parent, then
3232 delete from within" semantic. */
3234 /* Auto-versioning delete of regular resource: */
3235 if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
3237 /* Change the VCR into a WR, in place. This creates a txn and
3238 changes resource->info->root from a rev-root into a txn-root. */
3239 err = dav_svn__checkout(resource,
3240 1 /* auto-checkout */,
3241 0, 0, 0, NULL, NULL);
3242 if (err)
3243 return err;
3246 /* Sanity check: an svn client may have sent a custom request header
3247 containing the revision of the item it thinks it's deleting. In
3248 this case, we enforce the svn-specific semantic that the item
3249 must be up-to-date. */
3250 if (SVN_IS_VALID_REVNUM(resource->info->version_name))
3252 svn_revnum_t created_rev;
3253 serr = svn_fs_node_created_rev(&created_rev,
3254 resource->info->root.root,
3255 resource->info->repos_path,
3256 resource->pool);
3257 if (serr)
3258 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3259 "Could not get created rev of resource",
3260 resource->pool);
3262 if (resource->info->version_name < created_rev)
3264 serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL,
3265 "Item '%s' is out of date",
3266 resource->info->repos_path);
3267 return dav_svn__convert_err(serr, HTTP_CONFLICT,
3268 "Can't DELETE out-of-date resource",
3269 resource->pool);
3273 /* Before attempting the filesystem delete, we need to push any
3274 incoming lock-tokens into the filesystem's access_t. Normally
3275 they come in via 'If:' header, and get_resource()
3276 automatically notices them and does this work for us. In the
3277 case of a directory deletion, however, svn clients are sending
3278 'child' lock-tokens in the DELETE request body. */
3280 err = dav_svn__build_lock_hash(&locks, resource->info->r,
3281 resource->info->repos_path, resource->pool);
3282 if (err != NULL)
3283 return err;
3285 if (apr_hash_count(locks))
3287 err = dav_svn__push_locks(resource, locks, resource->pool);
3288 if (err != NULL)
3289 return err;
3292 if ((serr = svn_fs_delete(resource->info->root.root,
3293 resource->info->repos_path,
3294 resource->pool)) != NULL)
3296 /* ### need a better error */
3297 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3298 "Could not delete the resource",
3299 resource->pool);
3302 /* Auto-versioning commit of the txn. */
3303 if (resource->info->auto_checked_out)
3305 /* This also changes the WR back into a VCR, in place. */
3306 err = dav_svn__checkin(resource, 0, NULL);
3307 if (err)
3308 return err;
3311 return NULL;
3315 static dav_error *
3316 move_resource(dav_resource *src,
3317 dav_resource *dst,
3318 dav_response **response)
3320 svn_error_t *serr;
3321 dav_error *err;
3323 /* NOTE: The svn client does not call the MOVE method yet. Strictly
3324 speaking, we do not need to implement this repository function.
3325 But we do so anyway, so non-deltaV clients can work against the
3326 repository when autoversioning is turned on. Like the svn client,
3327 itself, we define a move to be a copy + delete within a single txn. */
3329 /* Because we have no 'atomic' move, we only allow this method on
3330 two regular resources with autoversioning active. That way we
3331 can auto-checkout a single resource and do the copy + delete
3332 within a single txn. (If we had two working resources, which txn
3333 would we use?) */
3334 if (src->type != DAV_RESOURCE_TYPE_REGULAR
3335 || dst->type != DAV_RESOURCE_TYPE_REGULAR
3336 || !(src->info->repos->autoversioning))
3337 return dav_new_error(dst->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3338 "MOVE only allowed on two public URIs, and "
3339 "autoversioning must be active.");
3341 /* Change the dst VCR into a WR, in place. This creates a txn and
3342 changes dst->info->root from a rev-root into a txn-root. */
3343 err = dav_svn__checkout(dst,
3344 1 /* auto-checkout */,
3345 0, 0, 0, NULL, NULL);
3346 if (err)
3347 return err;
3349 /* Copy the src to the dst. */
3350 serr = svn_fs_copy(src->info->root.root, /* the root object of src rev*/
3351 src->info->repos_path, /* the relative path of src */
3352 dst->info->root.root, /* the root object of dst txn*/
3353 dst->info->repos_path, /* the relative path of dst */
3354 src->pool);
3355 if (serr)
3356 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3357 "Unable to make a filesystem copy.",
3358 dst->pool);
3360 /* Notice: we're deleting the src repos path from the dst's txn_root. */
3361 if ((serr = svn_fs_delete(dst->info->root.root,
3362 src->info->repos_path,
3363 dst->pool)) != NULL)
3364 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3365 "Could not delete the src resource.",
3366 dst->pool);
3368 /* Commit: this also changes the WR back into a VCR, in place. */
3369 err = dav_svn__checkin(dst, 0, NULL);
3370 if (err)
3371 return err;
3373 return NULL;
3377 typedef struct {
3378 /* the input walk parameters */
3379 const dav_walk_params *params;
3381 /* reused as we walk */
3382 dav_walk_resource wres;
3384 /* the current resource */
3385 dav_resource res; /* wres.resource refers here */
3386 dav_resource_private info; /* the info in res */
3387 svn_stringbuf_t *uri; /* the uri within res */
3388 svn_stringbuf_t *repos_path; /* the repos_path within res */
3390 } walker_ctx_t;
3393 static dav_error *
3394 do_walk(walker_ctx_t *ctx, int depth)
3396 const dav_walk_params *params = ctx->params;
3397 int isdir = ctx->res.collection;
3398 dav_error *err;
3399 svn_error_t *serr;
3400 apr_hash_index_t *hi;
3401 apr_size_t path_len;
3402 apr_size_t uri_len;
3403 apr_size_t repos_len;
3404 apr_hash_t *children;
3406 /* Clear the temporary pool. */
3407 svn_pool_clear(ctx->info.pool);
3409 /* The current resource is a collection (possibly here thru recursion)
3410 and this is the invocation for the collection. Alternatively, this is
3411 the first [and only] entry to do_walk() for a member resource, so
3412 this will be the invocation for the member. */
3413 err = (*params->func)(&ctx->wres,
3414 isdir ? DAV_CALLTYPE_COLLECTION : DAV_CALLTYPE_MEMBER);
3415 if (err != NULL)
3416 return err;
3418 /* if we are not to recurse, or this is a member, then we're done */
3419 if (depth == 0 || !isdir)
3420 return NULL;
3422 /* ### for now, let's say that working resources have no children. of
3423 ### course, this isn't true (or "right") for working collections, but
3424 ### we don't actually need to do a walk right now. */
3425 if (params->root->type == DAV_RESOURCE_TYPE_WORKING)
3426 return NULL;
3428 /* ### need to allow more walking in the future */
3429 if (params->root->type != DAV_RESOURCE_TYPE_REGULAR)
3431 return dav_new_error(params->pool, HTTP_METHOD_NOT_ALLOWED, 0,
3432 "Walking the resource hierarchy can only be done "
3433 "on 'regular' resources [at this time].");
3436 /* assert: collection resource. isdir == TRUE. repos_path != NULL. */
3438 /* append "/" to the paths, in preparation for appending child names.
3439 don't add "/" if the paths are simply "/" */
3440 if (ctx->info.uri_path->data[ctx->info.uri_path->len - 1] != '/')
3441 svn_stringbuf_appendcstr(ctx->info.uri_path, "/");
3442 if (ctx->repos_path->data[ctx->repos_path->len - 1] != '/')
3443 svn_stringbuf_appendcstr(ctx->repos_path, "/");
3445 /* NOTE: the URI should already have a trailing "/" */
3447 /* fix up the dependent pointers */
3448 ctx->info.repos_path = ctx->repos_path->data;
3450 /* all of the children exist. also initialize the collection flag. */
3451 ctx->res.exists = TRUE;
3452 ctx->res.collection = FALSE;
3454 /* remember these values so we can chop back to them after each time
3455 we append a child name to the path/uri/repos */
3456 path_len = ctx->info.uri_path->len;
3457 uri_len = ctx->uri->len;
3458 repos_len = ctx->repos_path->len;
3460 /* Tell our logging subsystem that we're listing a directory.
3462 Note: if we cared, we could look at the 'User-Agent:' request
3463 header and distinguish an svn client ('svn ls') from a generic
3464 DAV client. */
3465 dav_svn__operational_log(&ctx->info,
3466 apr_psprintf(params->pool,
3467 "get-dir %s r%ld text",
3468 svn_path_uri_encode(ctx->info.repos_path,
3469 params->pool),
3470 ctx->info.root.rev));
3472 /* fetch this collection's children */
3473 serr = svn_fs_dir_entries(&children, ctx->info.root.root,
3474 ctx->info.repos_path, params->pool);
3475 if (serr != NULL)
3476 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3477 "could not fetch collection members",
3478 params->pool);
3480 /* iterate over the children in this collection */
3481 for (hi = apr_hash_first(params->pool, children); hi; hi = apr_hash_next(hi))
3483 const void *key;
3484 apr_ssize_t klen;
3485 void *val;
3486 svn_fs_dirent_t *dirent;
3488 /* fetch one of the children */
3489 apr_hash_this(hi, &key, &klen, &val);
3490 dirent = val;
3492 /* authorize access to this resource, if applicable */
3493 if (params->walk_type & DAV_WALKTYPE_AUTH)
3495 /* ### how/what to do? */
3498 /* append this child to our buffers */
3499 svn_stringbuf_appendbytes(ctx->info.uri_path, key, klen);
3500 svn_stringbuf_appendbytes(ctx->uri, key, klen);
3501 svn_stringbuf_appendbytes(ctx->repos_path, key, klen);
3503 /* reset the pointers since the above may have changed them */
3504 ctx->res.uri = ctx->uri->data;
3505 ctx->info.repos_path = ctx->repos_path->data;
3507 if (dirent->kind == svn_node_file)
3509 err = (*params->func)(&ctx->wres, DAV_CALLTYPE_MEMBER);
3510 if (err != NULL)
3511 return err;
3513 else
3515 /* this resource is a collection */
3516 ctx->res.collection = TRUE;
3518 /* append a slash to the URI (the path doesn't need it yet) */
3519 svn_stringbuf_appendcstr(ctx->uri, "/");
3520 ctx->res.uri = ctx->uri->data;
3522 /* recurse on this collection */
3523 err = do_walk(ctx, depth - 1);
3524 if (err != NULL)
3525 return err;
3527 /* restore the data */
3528 ctx->res.collection = FALSE;
3531 /* chop the child off the paths and uri. NOTE: no null-term. */
3532 ctx->info.uri_path->len = path_len;
3533 ctx->uri->len = uri_len;
3534 ctx->repos_path->len = repos_len;
3536 return NULL;
3539 static dav_error *
3540 walk(const dav_walk_params *params, int depth, dav_response **response)
3542 /* Thinking about adding support for LOCKNULL resources in this
3543 walker? Check out the (working) code that was removed here:
3544 Author: cmpilato
3545 Date: Fri Mar 18 14:54:02 2005
3546 New Revision: 13475
3549 walker_ctx_t ctx = { 0 };
3550 dav_error *err;
3552 ctx.params = params;
3554 ctx.wres.walk_ctx = params->walk_ctx;
3555 ctx.wres.pool = params->pool;
3556 ctx.wres.resource = &ctx.res;
3558 /* copy the resource over and adjust the "info" reference */
3559 ctx.res = *params->root;
3560 ctx.info = *ctx.res.info;
3562 ctx.res.info = &ctx.info;
3564 /* operate within the proper pool */
3565 ctx.res.pool = params->pool;
3567 /* Don't monkey with the path from params->root. Create a new one.
3568 This path will then be extended/shortened as necessary. */
3569 ctx.info.uri_path = svn_stringbuf_dup(ctx.info.uri_path, params->pool);
3571 /* prep the URI buffer */
3572 ctx.uri = svn_stringbuf_create(params->root->uri, params->pool);
3574 /* same for repos_path */
3575 if (ctx.info.repos_path == NULL)
3576 ctx.repos_path = NULL;
3577 else
3578 ctx.repos_path = svn_stringbuf_create(ctx.info.repos_path, params->pool);
3580 /* if we have a collection, then ensure the URI has a trailing "/" */
3581 /* ### get_resource always kills the trailing slash... */
3582 if (ctx.res.collection && ctx.uri->data[ctx.uri->len - 1] != '/') {
3583 svn_stringbuf_appendcstr(ctx.uri, "/");
3586 /* the current resource's URI is stored in the (telescoping) ctx.uri */
3587 ctx.res.uri = ctx.uri->data;
3589 /* the current resource's repos_path is stored in ctx.repos_path */
3590 if (ctx.repos_path != NULL)
3591 ctx.info.repos_path = ctx.repos_path->data;
3593 /* Create a pool usable by the response. */
3594 ctx.info.pool = svn_pool_create(params->pool);
3596 /* ### is the root already/always open? need to verify */
3598 /* always return the error, and any/all multistatus responses */
3599 err = do_walk(&ctx, depth);
3600 *response = ctx.wres.response;
3602 return err;
3607 /*** Utility functions for resource management ***/
3609 dav_resource *
3610 dav_svn__create_working_resource(dav_resource *base,
3611 const char *activity_id,
3612 const char *txn_name,
3613 int tweak_in_place)
3615 const char *path;
3616 dav_resource *res;
3618 if (base->baselined)
3619 path = apr_psprintf(base->pool,
3620 "/%s/wbl/%s/%ld",
3621 base->info->repos->special_uri,
3622 activity_id, base->info->root.rev);
3623 else
3624 path = apr_psprintf(base->pool, "/%s/wrk/%s%s",
3625 base->info->repos->special_uri,
3626 activity_id, base->info->repos_path);
3627 path = svn_path_uri_encode(path, base->pool);
3629 if (tweak_in_place)
3630 res = base;
3631 else
3633 res = apr_pcalloc(base->pool, sizeof(*res));
3634 res->info = apr_pcalloc(base->pool, sizeof(*res->info));
3637 res->type = DAV_RESOURCE_TYPE_WORKING;
3638 res->exists = TRUE; /* ### not necessarily correct */
3639 res->versioned = TRUE;
3640 res->working = TRUE;
3641 res->baselined = base->baselined;
3642 /* collection = FALSE. ### not necessarily correct */
3644 res->uri = apr_pstrcat(base->pool, base->info->repos->root_path,
3645 path, NULL);
3646 res->hooks = &dav_svn__hooks_repository;
3647 res->pool = base->pool;
3649 res->info->uri_path = svn_stringbuf_create(path, base->pool);
3650 res->info->repos = base->info->repos;
3651 res->info->repos_path = base->info->repos_path;
3652 res->info->root.rev = base->info->root.rev;
3653 res->info->root.activity_id = activity_id;
3654 res->info->root.txn_name = txn_name;
3656 if (tweak_in_place)
3657 return NULL;
3658 else
3659 return res;
3663 dav_error *
3664 dav_svn__working_to_regular_resource(dav_resource *resource)
3666 dav_resource_private *priv = resource->info;
3667 dav_svn_repos *repos = priv->repos;
3668 const char *path;
3669 svn_error_t *serr;
3671 /* no need to change the repos object or repos_path */
3673 /* set type back to REGULAR */
3674 resource->type = DAV_RESOURCE_TYPE_REGULAR;
3676 /* remove the working flag */
3677 resource->working = FALSE;
3679 /* Change the URL into either a baseline-collection or a public one. */
3680 if (priv->root.rev == SVN_INVALID_REVNUM)
3682 serr = svn_fs_youngest_rev(&priv->root.rev, repos->fs, resource->pool);
3683 if (serr != NULL)
3684 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3685 "Could not determine youngest rev.",
3686 resource->pool);
3688 /* create public URL */
3689 path = apr_psprintf(resource->pool, "%s", priv->repos_path);
3691 else
3693 /* if rev was specific, create baseline-collection URL */
3694 path = dav_svn__build_uri(repos, DAV_SVN__BUILD_URI_BC,
3695 priv->root.rev, priv->repos_path,
3696 0, resource->pool);
3698 path = svn_path_uri_encode(path, resource->pool);
3699 priv->uri_path = svn_stringbuf_create(path, resource->pool);
3701 /* change root.root back into a revision root. */
3702 serr = svn_fs_revision_root(&priv->root.root, repos->fs,
3703 priv->root.rev, resource->pool);
3704 if (serr != NULL)
3705 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
3706 "Could not open revision root.",
3707 resource->pool);
3709 return NULL;
3713 dav_error *
3714 dav_svn__create_version_resource(dav_resource **version_res,
3715 const char *uri,
3716 apr_pool_t *pool)
3718 int result;
3719 dav_error *err;
3721 dav_resource_combined *comb = apr_pcalloc(pool, sizeof(*comb));
3723 result = parse_version_uri(comb, uri, NULL, 0);
3724 if (result != 0)
3725 return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0,
3726 "Could not parse version resource uri.");
3728 err = prep_version(comb);
3729 if (err)
3730 return err;
3732 *version_res = &comb->res;
3733 return NULL;
3737 const dav_hooks_repository dav_svn__hooks_repository =
3739 1, /* special GET handling */
3740 get_resource,
3741 get_parent_resource,
3742 is_same_resource,
3743 is_parent_resource,
3744 open_stream,
3745 close_stream,
3746 write_stream,
3747 seek_stream,
3748 set_headers,
3749 deliver,
3750 create_collection,
3751 copy_resource,
3752 move_resource,
3753 remove_resource,
3754 walk,
3755 getetag_pathetic