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
21 #include <apr_strings.h>
26 #include <http_request.h>
27 #include <http_protocol.h>
29 #include <http_core.h> /* for ap_construct_url */
32 #include "svn_types.h"
33 #include "svn_pools.h"
34 #include "svn_error.h"
37 #include "svn_repos.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_* */
48 #define DEFAULT_ACTIVITY_DB "dav/activities.d"
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
;
64 /* Convenience structure that facilitates combined memory allocation of
65 a dav_resource and dav_resource_private pair. */
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. */
78 fs_check_path(svn_node_kind_t
*kind
,
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
91 if (serr
&& serr
->apr_err
== SVN_ERR_FS_NOT_DIRECTORY
)
93 svn_error_clear(serr
);
94 *kind
= svn_node_none
;
99 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
100 apr_psprintf(pool
, "Error checking kind of "
101 "path '%s' in repository",
112 parse_version_uri(dav_resource_combined
*comb
,
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
, '/');
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
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
)
165 parse_history_uri(dav_resource_combined
*comb
,
172 /* ### what to do with LABEL and USE_CHECKED_IN ?? */
174 comb
->res
.type
= DAV_RESOURCE_TYPE_HISTORY
;
177 comb
->priv
.repos_path
= path
;
184 parse_working_uri(dav_resource_combined
*comb
,
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. */
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
= "/";
215 comb
->priv
.root
.activity_id
= apr_pstrndup(comb
->res
.pool
, path
,
217 comb
->priv
.repos_path
= slash
;
225 parse_activity_uri(dav_resource_combined
*comb
,
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
;
243 parse_vcc_uri(dav_resource_combined
*comb
,
248 /* format: "default" (a singleton) */
250 if (strcmp(path
, DAV_SVN__DEFAULT_VCC_NAME
) != 0)
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 */
272 /* a specific Version Resource; in this case, a Baseline */
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 */
309 parse_baseline_coll_uri(dav_resource_combined
*comb
,
317 /* format: REVISION/REPOS_PATH */
319 /* ### what to do with LABEL and USE_CHECKED_IN ?? */
321 slash
= ap_strchr_c(path
, '/');
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
;
348 parse_baseline_uri(dav_resource_combined
*comb
,
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 */
382 parse_wrk_baseline_uri(dav_resource_combined
*comb
,
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
403 comb
->priv
.root
.activity_id
= apr_pstrndup(comb
->res
.pool
, path
,
405 comb
->priv
.root
.rev
= SVN_STR_TO_REV(slash
+ 1);
407 /* NOTE: comb->priv.repos_path == NULL */
413 static const struct special_defn
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
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. */
436 /* Boolean: are the subcomponents followed by a 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.
471 parse_uri(dav_resource_combined
*comb
,
476 const char *special_uri
= comb
->priv
.repos
->special_uri
;
482 len2
= strlen(special_uri
);
484 && ((ch
= uri
[len2
]) == '/' || ch
== '\0')
485 && memcmp(uri
, special_uri
, len2
) == 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
;
495 const struct special_defn
*defn
;
497 /* skip past the "!svn/" prefix */
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
,
525 /* e.g. "/root/!svn/activity" (we just know "act") */
533 /* if completed the loop, then it is an unrecognized subdir */
534 if (defn
->name
== NULL
)
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
;
555 prep_regular(dav_resource_combined
*comb
)
557 apr_pool_t
*pool
= comb
->res
.pool
;
558 dav_svn_repos
*repos
= comb
->priv
.repos
;
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
);
572 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
573 "Could not determine the proper "
574 "revision to access",
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
);
584 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
585 "Could not open the root of the "
590 derr
= fs_check_path(&kind
, comb
->priv
.root
.root
,
591 comb
->priv
.repos_path
, pool
);
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 *) "";
611 prep_version(dav_resource_combined
*comb
)
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
,
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.",
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
,
650 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
651 "Could not open a revision root.",
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
,
673 prep_history(dav_resource_combined
*comb
)
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
;
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 "
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
,
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 "
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.",
718 if (comb
->res
.baselined
)
720 /* a Working Baseline */
722 /* if the transaction exists, then the working resource exists */
723 comb
->res
.exists
= TRUE
;
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(¤t_author
, comb
->priv
.root
.txn
,
741 SVN_PROP_REVISION_AUTHOR
, pool
);
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.",
750 request_author
.data
= comb
->priv
.repos
->username
;
751 request_author
.len
= strlen(request_author
.data
);
754 serr
= svn_fs_change_txn_prop(comb
->priv
.root
.txn
,
755 SVN_PROP_REVISION_AUTHOR
, &request_author
, pool
);
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.",
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
);
775 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
776 "Could not open the (transaction) root of "
781 derr
= fs_check_path(&kind
, comb
->priv
.root
.root
,
782 comb
->priv
.repos_path
, pool
);
786 comb
->res
.exists
= (kind
== svn_node_none
) ? FALSE
: TRUE
;
787 comb
->res
.collection
= (kind
== svn_node_dir
) ? TRUE
: FALSE
;
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
;
807 prep_private(dav_resource_combined
*comb
)
809 if (comb
->priv
.restype
== DAV_SVN_RESTYPE_VCC
)
813 /* else nothing to do (### for now) */
819 static const struct res_type_handler
821 dav_resource_type type
;
822 dav_error
* (*prep
)(dav_resource_combined
*comb
);
824 } res_type_handlers
[] =
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
},
832 { DAV_RESOURCE_TYPE_ACTIVITY
, prep_activity
},
833 { DAV_RESOURCE_TYPE_PRIVATE
, prep_private
},
835 { 0, NULL
} /* sentinel */
842 ** Set .exists and .collection
843 ** open other, internal bits...
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
)
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
,
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
;
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
,
922 const char **repos_name
,
923 const char **relative_path
,
924 const char **repos_path
)
929 const char *fs_parent_path
;
930 const char *relative
;
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 '/' */
954 had_slash
= (len1
> 0 && uri
[len1
- 1] == '/');
955 if (len1
> 1 && had_slash
)
956 uri
[len1
- 1] = '\0';
959 *trailing_slash
= TRUE
;
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
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')
992 else if (*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. */
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, '/');
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;
1040 magic_component
= apr_pstrndup(r
->pool
, relative
+ 1,
1041 magic_end
- relative
- 1);
1042 relative
= magic_end
;
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 '/'. */
1058 const char *special_uri
= dav_svn__get_special_uri(r
);
1062 len1
= strlen(relative
);
1063 len2
= strlen(special_uri
);
1065 && ((ch
= relative
[len2
]) == '/' || ch
== '\0')
1066 && memcmp(relative
, special_uri
, len2
) == 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.");
1077 const struct special_defn
*defn
;
1079 /* skip past the "!svn/" prefix */
1080 relative
+= 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)
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.*/
1107 const char *end
= NULL
, *start
= relative
+ len3
+ 1;
1109 for (j
= 0; j
< defn
->numcomponents
; j
++)
1111 end
= ap_strchr_c(start
, '/');
1119 /* Did we break from the loop prematurely? */
1120 if (j
!= (defn
->numcomponents
- 1))
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. */
1136 /* Found a slash after the special components. */
1137 *repos_path
= apr_pstrdup(r
->pool
, start
);
1143 dav_new_error(r
->pool
, HTTP_INTERNAL_SERVER_ERROR
,
1144 SVN_ERR_APMOD_MALFORMED_URI
,
1145 "Unknown data after special_uri.");
1152 if (defn
->name
== NULL
)
1154 dav_new_error(r
->pool
, HTTP_INTERNAL_SERVER_ERROR
,
1155 SVN_ERR_APMOD_MALFORMED_URI
,
1156 "Couldn't match subdir after special_uri.");
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
);
1171 /* Context for cleanup handler. */
1172 struct cleanup_fs_access_baton
1179 /* Pool cleanup handler. Make sure fs's access ctx points to NULL
1180 when request pool is destroyed. */
1182 cleanup_fs_access(void *data
)
1185 struct cleanup_fs_access_baton
*baton
= data
;
1187 serr
= svn_fs_set_access(baton
->fs
, NULL
);
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
);
1199 /* Helper func to construct a special 'parentpath' private resource. */
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
;
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
),
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
;
1254 /* --------------- Borrowed from httpd's mod_negotiation.c -------------- */
1256 typedef struct accept_rec
{
1257 char *name
; /* MUST be lowercase */
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 ... */
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
);
1307 continue; /* No '='; just ignore it. */
1310 *cp
++ = '\0'; /* Delimit var */
1311 while (*cp
&& (apr_isspace(*cp
) || *cp
== '='))
1320 (*end
&& *end
!= '\n' && *end
!= '\r' && *end
!= '\"');
1325 for (end
= cp
; (*end
&& !apr_isspace(*end
)); end
++);
1329 *end
= '\0'; /* strip ending quote or return */
1334 && (parm
[1] == '\0' || (parm
[1] == 's' && parm
[2] == '\0')))
1336 result
->quality
= (float) atof(cp
);
1340 if (*accept_line
== ',')
1348 /* @a accept_line is the Accept-Encoding header, which is of the
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
;
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
);
1376 /* ---------------------------------------------------------------------- */
1379 /* qsort comparison function for the quality field of the accept_rec
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. */
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. */
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;
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
,
1418 if (strcmp(rec
.name
, "svndiff1") == 0)
1420 *svndiff_version
= 1;
1423 else if (strcmp(rec
.name
, "svndiff") == 0)
1425 *svndiff_version
= 0;
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
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
),
1446 apr_hash_index_t
*hi
;
1448 for (hi
= apr_hash_first(pool
, capabilities
); hi
; hi
= apr_hash_next(hi
))
1452 apr_hash_this(hi
, &key
, NULL
, &val
);
1453 if (strcmp((const char *) val
, "yes") == 0)
1454 APR_ARRAY_PUSH(list
, const char *) = key
;
1462 get_resource(request_rec
*r
,
1463 const char *root_path
,
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
;
1483 dav_locktoken_list
*ltl
;
1484 struct cleanup_fs_access_baton
*cleanup_baton
;
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
);
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
);
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. */
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
=
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
,
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
,
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
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");
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
,
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
);
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
1690 return dav_svn__sanitize_error(serr
, "Could not open the requested "
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
));
1706 return dav_svn__sanitize_error(serr
,
1707 "Error storing client capabilities "
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 */
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
);
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
);
1747 return dav_svn__sanitize_error(serr
, "Could not attach access "
1749 HTTP_INTERNAL_SERVER_ERROR
, r
);
1753 /* Look for locktokens in the "If:" request header. */
1754 err
= dav_get_locktoken_list(r
, <l
);
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
))
1765 /* If one or more locktokens are present in the header, push them
1766 into the filesystem access context. */
1769 svn_fs_access_t
*access_ctx
;
1770 dav_locktoken_list
*list
= ltl
;
1772 serr
= svn_fs_get_access(&access_ctx
, repos
->fs
);
1775 return dav_svn__sanitize_error(serr
, "Lock token is in request, "
1777 HTTP_BAD_REQUEST
, r
);
1781 serr
= svn_fs_access_add_lock_token(access_ctx
,
1782 list
->locktoken
->uuid_str
);
1784 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
1785 "Error pushing token into filesystem.",
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
))
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");
1812 /* prepare the resource for operation */
1813 if ((err
= prep_resource(comb
)) != NULL
)
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
),
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
;
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 "
1851 /* Helper func: return the parent of PATH, allocated in POOL. */
1853 get_parent_path(const char *path
, apr_pool_t
*pool
)
1856 const char *parentpath
, *base_name
;
1857 char *tmp
= apr_pstrdup(pool
, path
);
1863 /* Remove any trailing slash; else svn_path_split() asserts. */
1864 if (tmp
[len
-1] == '/')
1866 svn_path_split(tmp
, &parentpath
, &base_name
, pool
);
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
;
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
;
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
,
1917 *parent_resource
= parent
;
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/
1927 create_private_resource(resource
, DAV_SVN_RESTYPE_WRK_COLLECTION
);
1930 case DAV_RESOURCE_TYPE_ACTIVITY
:
1932 create_private_resource(resource
, DAV_SVN_RESTYPE_ACT_COLLECTION
);
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 "
1944 resource
->uri
, resource
->type
));
1952 /* does RES2 live in the same repository as RES1? */
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 */
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
));
2002 is_same_resource(const dav_resource
*res1
, const dav_resource
*res2
)
2004 if (!is_our_resource(res1
, res2
))
2007 /* ### what if the same resource were reached via two URIs? */
2009 return svn_stringbuf_compare(res1
->info
->uri_path
, res2
->info
->uri_path
);
2014 is_parent_resource(const dav_resource
*res1
, const dav_resource
*res2
)
2016 apr_size_t len1
= strlen(res1
->info
->uri_path
->data
);
2019 if (!is_our_resource(res1
, res2
))
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
);
2031 && memcmp(res1
->info
->uri_path
->data
, res2
->info
->uri_path
->data
,
2033 && res2
->info
->uri_path
->data
[len1
] == '/');
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}
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
2053 resource_kind(request_rec
*r
,
2055 const char *root_path
,
2056 svn_node_kind_t
*kind
)
2060 dav_resource
*resource
;
2061 svn_revnum_t base_rev
;
2062 svn_fs_root_t
*base_rev_root
;
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.
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
2084 /* Restore r back to normal. */
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
;
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
;
2108 derr
= fs_check_path(kind
, resource
->info
->root
.root
,
2109 resource
->info
->repos_path
, r
->pool
);
2115 else if (resource
->type
== DAV_RESOURCE_TYPE_WORKING
)
2117 if (resource
->baselined
) /* wbl */
2118 *kind
= svn_node_unknown
;
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
,
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",
2136 derr
= fs_check_path(kind
, base_rev_root
,
2137 resource
->info
->repos_path
, r
->pool
);
2144 /* act, his, vcc, or some other private resource */
2145 *kind
= svn_node_unknown
;
2153 open_stream(const dav_resource
*resource
,
2154 dav_stream_mode mode
,
2155 dav_stream
**stream
)
2157 svn_node_kind_t kind
;
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].");
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 "
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
);
2189 if (kind
== svn_node_none
) /* No existing file. */
2191 serr
= svn_fs_make_file(resource
->info
->root
.root
,
2192 resource
->info
->repos_path
,
2197 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2198 "Could not create file within the "
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
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
,
2222 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2223 "Error fetching mime-type property.",
2229 serr
= svn_fs_change_node_prop(resource
->info
->root
.root
,
2230 resource
->info
->repos_path
,
2233 (resource
->info
->r
->content_type
,
2238 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2239 "Could not set mime-type property.",
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
,
2255 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2256 "Could not prepare to write the file",
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
,
2282 close_stream(dav_stream
*stream
, int commit
)
2285 apr_pool_t
*pool
= stream
->res
->pool
;
2287 if (stream
->rstream
!= NULL
)
2289 serr
= svn_stream_close(stream
->rstream
);
2291 return dav_svn__convert_err
2292 (serr
, HTTP_INTERNAL_SERVER_ERROR
,
2293 "mod_dav_svn close_stream: error closing read stream",
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
);
2303 return dav_svn__convert_err
2304 (serr
, HTTP_INTERNAL_SERVER_ERROR
,
2305 "mod_dav_svn close_stream: error closing write stream",
2308 else if (stream
->delta_handler
!= NULL
)
2310 serr
= (*stream
->delta_handler
)(NULL
, stream
->delta_baton
);
2312 return dav_svn__convert_err
2313 (serr
, HTTP_INTERNAL_SERVER_ERROR
,
2314 "mod_dav_svn close_stream: error sending final (null) delta window",
2323 write_stream(dav_stream
*stream
, const void *buf
, apr_size_t bufsize
)
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? */
2335 svn_txdelta_window_t window
= { 0 };
2336 svn_txdelta_op_t op
;
2342 op
.action_code
= svn_txdelta_new
;
2344 op
.length
= bufsize
;
2346 window
.tview_len
= bufsize
; /* result will be this long */
2349 window
.new_data
= &data
;
2351 serr
= (*stream
->delta_handler
)(&window
, stream
->delta_baton
);
2356 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2357 "could not write the file contents",
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 "
2374 /* Returns whether the DAV resource lacks potential for generation of
2375 an ETag (defined as any of the following):
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. */
2391 get_last_modified(const dav_resource
*resource
)
2393 apr_time_t last_modified
;
2395 svn_revnum_t created_rev
;
2396 svn_string_t
*date_time
;
2398 if (RESOURCE_LACKS_ETAG_POTENTIAL(resource
))
2401 if ((serr
= svn_fs_node_created_rev(&created_rev
, resource
->info
->root
.root
,
2402 resource
->info
->repos_path
,
2405 svn_error_clear(serr
);
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
);
2416 if (date_time
== NULL
|| date_time
->data
== NULL
)
2419 if ((serr
= svn_time_from_cstring(&last_modified
, date_time
->data
,
2422 svn_error_clear(serr
);
2426 return last_modified
;
2431 dav_svn__getetag(const dav_resource
*resource
, apr_pool_t
*pool
)
2434 svn_revnum_t created_rev
;
2436 if (RESOURCE_LACKS_ETAG_POTENTIAL(resource
))
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
,
2445 /* ### what to do? */
2446 svn_error_clear(serr
);
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/" : "",
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. */
2464 getetag_pathetic(const dav_resource
*resource
)
2466 return dav_svn__getetag(resource
, resource
->pool
);
2471 set_headers(request_rec
*r
, const dav_resource
*resource
)
2474 svn_filesize_t length
;
2475 const char *mimetype
= NULL
;
2476 apr_time_t last_modified
;
2478 if (!resource
->exists
)
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
));
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");
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";
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
,
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
,
2547 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2548 "could not fetch the resource's MIME type",
2552 mimetype
= value
->data
;
2553 else if ((! resource
->info
->repos
->is_svn_client
)
2555 mimetype
= r
->content_type
;
2557 mimetype
= ap_default_type(r
);
2559 serr
= svn_mime_type_validate(mimetype
, resource
->pool
);
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
,
2579 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2580 "could not fetch the resource length",
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
);
2595 ap_filter_t
*output
;
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
;
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
;
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
;
2641 deliver(const dav_resource
*resource
, ap_filter_t
*output
)
2644 apr_bucket_brigade
*bb
;
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
;
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
[] =
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"
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/" />
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
);
2710 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2711 "couldn't fetch dirents of SVNParentPath",
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
))
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
);
2727 if (dirent
->kind
!= svn_node_dir
)
2731 ent
->id
= NULL
; /* ### does it matter? */
2732 ent
->kind
= dirent
->kind
;
2734 apr_hash_set(entries
, key
, APR_HASH_KEY_STRING
, ent
);
2740 serr
= svn_fs_dir_entries(&entries
, resource
->info
->root
.root
,
2741 resource
->info
->repos_path
, resource
->pool
);
2743 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2744 "could not fetch directory entries",
2748 bb
= apr_brigade_create(resource
->pool
, output
->c
->bucket_alloc
);
2753 if (resource
->info
->repos_path
== NULL
)
2754 title
= "unknown location";
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
,
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
,
2768 if (resource
->info
->repos
->repo_name
)
2769 title
= apr_psprintf(resource
->pool
, "%s: %s",
2770 resource
->info
->repos
->repo_name
,
2774 ap_fprintf(output
, bb
, "<html><head><title>%s</title></head>\n"
2775 "<body>\n <h2>%s</h2>\n <ul>\n", title
, title
);
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");
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
);
2799 ap_fprintf(output
, bb
, " path=\"%s\"",
2800 apr_xml_quote_string(resource
->pool
,
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
))
2813 ap_fprintf(output
, bb
, " <li><a href=\"../\">..</a></li>\n");
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
,
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. */
2841 href
= apr_pstrcat(entry_pool
, href
, "/", NULL
);
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);
2861 ap_fprintf(output
, bb
,
2862 " <li><a href=\"%s\">%s</a></li>\n",
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",
2877 svn_pool_destroy(entry_pool
);
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>");
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.");
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
;
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
,
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
2922 serr
= svn_fs_revision_root(&root
, resource
->info
->repos
->fs
,
2923 info
.rev
, resource
->pool
);
2925 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2926 "could not open a root for the base",
2929 /* verify that it is a file */
2930 serr
= svn_fs_is_file(&is_file
, root
, info
.repos_path
,
2933 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2934 "could not determine if the base "
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
,
2948 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2949 "could not prepare to read a delta",
2952 /* create a stream that svndiff data will be written to,
2953 which will copy it to the network */
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
,
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
2968 serr
= svn_txdelta_send_txstream(txd_stream
, handler
, h_baton
,
2971 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2972 "could not deliver the txdelta stream",
2980 svn_error_clear(serr
);
2984 /* resource->info->delta_base is NULL, or we had an invalid base URL */
2986 svn_stream_t
*stream
;
2989 serr
= svn_fs_file_contents(&stream
,
2990 resource
->info
->root
.root
,
2991 resource
->info
->repos_path
,
2995 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
2996 "could not prepare to read the file",
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
);
3005 apr_size_t bufsize
= SVN__STREAM_CHUNK_SIZE
;
3007 /* read from the FS ... */
3008 serr
= svn_stream_read(stream
, block
, &bufsize
);
3011 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
3012 "could not read the file contents",
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.");
3046 create_collection(dav_resource
*resource
)
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
);
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.",
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
);
3105 copy_resource(const dav_resource
*src
,
3108 dav_response
**response
)
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: */
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
);
3155 serr
= svn_path_get_absolute(&src_repos_path
,
3156 svn_repos_path(src
->info
->repos
->repos
,
3160 serr
= svn_path_get_absolute(&dst_repos_path
,
3161 svn_repos_path(dst
->info
->repos
->repos
,
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 */
3179 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
3180 "Unable to make a filesystem copy.",
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
);
3197 remove_resource(dav_resource
*resource
, dav_response
**response
)
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
);
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
,
3258 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
3259 "Could not get created rev of resource",
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",
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
);
3285 if (apr_hash_count(locks
))
3287 err
= dav_svn__push_locks(resource
, locks
, resource
->pool
);
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",
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
);
3316 move_resource(dav_resource
*src
,
3318 dav_response
**response
)
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
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
);
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 */
3356 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
3357 "Unable to make a filesystem copy.",
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.",
3368 /* Commit: this also changes the WR back into a VCR, in place. */
3369 err
= dav_svn__checkin(dst
, 0, NULL
);
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 */
3394 do_walk(walker_ctx_t
*ctx
, int depth
)
3396 const dav_walk_params
*params
= ctx
->params
;
3397 int isdir
= ctx
->res
.collection
;
3400 apr_hash_index_t
*hi
;
3401 apr_size_t path_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
);
3418 /* if we are not to recurse, or this is a member, then we're done */
3419 if (depth
== 0 || !isdir
)
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
)
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
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
,
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
);
3476 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
3477 "could not fetch collection members",
3480 /* iterate over the children in this collection */
3481 for (hi
= apr_hash_first(params
->pool
, children
); hi
; hi
= apr_hash_next(hi
))
3486 svn_fs_dirent_t
*dirent
;
3488 /* fetch one of the children */
3489 apr_hash_this(hi
, &key
, &klen
, &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
);
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);
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
;
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:
3545 Date: Fri Mar 18 14:54:02 2005
3549 walker_ctx_t ctx
= { 0 };
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
;
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
;
3607 /*** Utility functions for resource management ***/
3610 dav_svn__create_working_resource(dav_resource
*base
,
3611 const char *activity_id
,
3612 const char *txn_name
,
3618 if (base
->baselined
)
3619 path
= apr_psprintf(base
->pool
,
3621 base
->info
->repos
->special_uri
,
3622 activity_id
, base
->info
->root
.rev
);
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
);
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
,
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
;
3664 dav_svn__working_to_regular_resource(dav_resource
*resource
)
3666 dav_resource_private
*priv
= resource
->info
;
3667 dav_svn_repos
*repos
= priv
->repos
;
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
);
3684 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
3685 "Could not determine youngest rev.",
3688 /* create public URL */
3689 path
= apr_psprintf(resource
->pool
, "%s", priv
->repos_path
);
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
,
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
);
3705 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
3706 "Could not open revision root.",
3714 dav_svn__create_version_resource(dav_resource
**version_res
,
3721 dav_resource_combined
*comb
= apr_pcalloc(pool
, sizeof(*comb
));
3723 result
= parse_version_uri(comb
, uri
, NULL
, 0);
3725 return dav_new_error(pool
, HTTP_INTERNAL_SERVER_ERROR
, 0,
3726 "Could not parse version resource uri.");
3728 err
= prep_version(comb
);
3732 *version_res
= &comb
->res
;
3737 const dav_hooks_repository dav_svn__hooks_repository
=
3739 1, /* special GET handling */
3741 get_parent_resource
,