2 * mod_dav_svn.c: an Apache mod_dav sub-module to provide a Subversion
5 * ====================================================================
6 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
8 * This software is licensed as described in the file COPYING, which
9 * you should have received as part of this distribution. The terms
10 * are also available at http://subversion.tigris.org/license-1.html.
11 * If newer versions of this license are posted there, you may use a
12 * newer version instead, at your option.
14 * This software consists of voluntary contributions made by many
15 * individuals. For exact contribution history, see the revision
16 * history and logs, available at http://subversion.tigris.org/.
17 * ====================================================================
20 #include <apr_strings.h>
23 #include <http_config.h>
24 #include <http_request.h>
26 #include <ap_provider.h>
29 #include "svn_version.h"
33 #include "mod_dav_svn.h"
36 #include "mod_authz_svn.h"
39 /* This is the default "special uri" used for SVN's special resources
40 (e.g. working resources, activities) */
41 #define SVN_DEFAULT_SPECIAL_URI "!svn"
43 /* This is the value to be given to SVNPathAuthz to bypass the apache
44 * subreq mechanism and make a call directly to mod_authz_svn. */
45 #define PATHAUTHZ_BYPASS_ARG "short_circuit"
47 /* per-server configuration */
49 const char *special_uri
;
53 /* A tri-state enum used for per directory on/off flags. Note that
54 it's important that CONF_FLAG_DEFAULT is 0 to make
55 dav_svn_merge_dir_config do the right thing. */
62 /* An enum used for the per directory configuration path_authz_method. */
63 enum path_authz_conf
{
64 CONF_PATHAUTHZ_DEFAULT
,
70 /* per-dir configuration */
72 const char *fs_path
; /* path to the SVN FS */
73 const char *repo_name
; /* repository name */
74 const char *xslt_uri
; /* XSL transform URI */
75 const char *fs_parent_path
; /* path to parent of SVN FS'es */
76 enum conf_flag autoversioning
; /* whether autoversioning is active */
77 enum path_authz_conf path_authz_method
; /* how GET subrequests are handled */
78 enum conf_flag list_parentpath
; /* whether to allow GET of parentpath */
79 const char *root_dir
; /* our top-level directory */
80 const char *master_uri
; /* URI to the master SVN repos */
81 const char *activities_db
; /* path to activities database(s) */
85 #define INHERIT_VALUE(parent, child, field) \
86 ((child)->field ? (child)->field : (parent)->field)
89 extern module AP_MODULE_DECLARE_DATA dav_svn_module
;
91 /* The authz_svn provider for bypassing path authz. */
92 static authz_svn__subreq_bypass_func_t pathauthz_bypass_func
= NULL
;
95 init(apr_pool_t
*p
, apr_pool_t
*plog
, apr_pool_t
*ptemp
, server_rec
*s
)
98 ap_add_version_component(p
, "SVN/" SVN_VER_NUMBER
);
100 serr
= svn_fs_initialize(p
);
103 ap_log_perror(APLOG_MARK
, APLOG_ERR
, serr
->apr_err
, p
,
104 "mod_dav_svn: error calling svn_fs_initialize: '%s'",
105 serr
->message
? serr
->message
: "(no more info)");
106 return HTTP_INTERNAL_SERVER_ERROR
;
109 /* This returns void, so we can't check for error. */
110 svn_utf_initialize(p
);
116 init_dso(apr_pool_t
*pconf
, apr_pool_t
*plog
, apr_pool_t
*ptemp
)
118 /* This isn't ideal, we're not actually being called before any
119 pool is created, but we are being called before the server or
120 request pools are created, which is probably good enough for
123 svn_dso_initialize();
129 create_server_config(apr_pool_t
*p
, server_rec
*s
)
131 return apr_pcalloc(p
, sizeof(server_conf_t
));
136 merge_server_config(apr_pool_t
*p
, void *base
, void *overrides
)
138 server_conf_t
*parent
= base
;
139 server_conf_t
*child
= overrides
;
140 server_conf_t
*newconf
;
142 newconf
= apr_pcalloc(p
, sizeof(*newconf
));
144 newconf
->special_uri
= INHERIT_VALUE(parent
, child
, special_uri
);
151 create_dir_config(apr_pool_t
*p
, char *dir
)
153 /* NOTE: dir==NULL creates the default per-dir config */
154 dir_conf_t
*conf
= apr_pcalloc(p
, sizeof(*conf
));
156 conf
->root_dir
= dir
;
163 merge_dir_config(apr_pool_t
*p
, void *base
, void *overrides
)
165 dir_conf_t
*parent
= base
;
166 dir_conf_t
*child
= overrides
;
169 newconf
= apr_pcalloc(p
, sizeof(*newconf
));
171 newconf
->fs_path
= INHERIT_VALUE(parent
, child
, fs_path
);
172 newconf
->master_uri
= INHERIT_VALUE(parent
, child
, master_uri
);
173 newconf
->activities_db
= INHERIT_VALUE(parent
, child
, activities_db
);
174 newconf
->repo_name
= INHERIT_VALUE(parent
, child
, repo_name
);
175 newconf
->xslt_uri
= INHERIT_VALUE(parent
, child
, xslt_uri
);
176 newconf
->fs_parent_path
= INHERIT_VALUE(parent
, child
, fs_parent_path
);
177 newconf
->autoversioning
= INHERIT_VALUE(parent
, child
, autoversioning
);
178 newconf
->path_authz_method
= INHERIT_VALUE(parent
, child
, path_authz_method
);
179 newconf
->list_parentpath
= INHERIT_VALUE(parent
, child
, list_parentpath
);
180 /* Prefer our parent's value over our new one - hence the swap. */
181 newconf
->root_dir
= INHERIT_VALUE(child
, parent
, root_dir
);
188 SVNReposName_cmd(cmd_parms
*cmd
, void *config
, const char *arg1
)
190 dir_conf_t
*conf
= config
;
192 conf
->repo_name
= apr_pstrdup(cmd
->pool
, arg1
);
199 SVNMasterURI_cmd(cmd_parms
*cmd
, void *config
, const char *arg1
)
201 dir_conf_t
*conf
= config
;
203 conf
->master_uri
= apr_pstrdup(cmd
->pool
, arg1
);
210 SVNActivitiesDB_cmd(cmd_parms
*cmd
, void *config
, const char *arg1
)
212 dir_conf_t
*conf
= config
;
214 conf
->activities_db
= apr_pstrdup(cmd
->pool
, arg1
);
221 SVNIndexXSLT_cmd(cmd_parms
*cmd
, void *config
, const char *arg1
)
223 dir_conf_t
*conf
= config
;
225 conf
->xslt_uri
= apr_pstrdup(cmd
->pool
, arg1
);
232 SVNAutoversioning_cmd(cmd_parms
*cmd
, void *config
, int arg
)
234 dir_conf_t
*conf
= config
;
237 conf
->autoversioning
= CONF_FLAG_ON
;
239 conf
->autoversioning
= CONF_FLAG_OFF
;
246 SVNPathAuthz_cmd(cmd_parms
*cmd
, void *config
, const char *arg1
)
248 dir_conf_t
*conf
= config
;
250 if (apr_strnatcasecmp("off", arg1
) == 0)
251 conf
->path_authz_method
= CONF_PATHAUTHZ_OFF
;
252 else if (apr_strnatcasecmp(PATHAUTHZ_BYPASS_ARG
,arg1
) == 0)
254 conf
->path_authz_method
= CONF_PATHAUTHZ_BYPASS
;
255 if (pathauthz_bypass_func
== NULL
)
256 pathauthz_bypass_func
=ap_lookup_provider(
257 AUTHZ_SVN__SUBREQ_BYPASS_PROV_GRP
,
258 AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME
,
259 AUTHZ_SVN__SUBREQ_BYPASS_PROV_VER
);
262 conf
->path_authz_method
= CONF_PATHAUTHZ_ON
;
269 SVNListParentPath_cmd(cmd_parms
*cmd
, void *config
, int arg
)
271 dir_conf_t
*conf
= config
;
274 conf
->list_parentpath
= CONF_FLAG_ON
;
276 conf
->list_parentpath
= CONF_FLAG_OFF
;
283 SVNPath_cmd(cmd_parms
*cmd
, void *config
, const char *arg1
)
285 dir_conf_t
*conf
= config
;
287 if (conf
->fs_parent_path
!= NULL
)
288 return "SVNPath cannot be defined at same time as SVNParentPath.";
290 conf
->fs_path
= svn_path_internal_style(apr_pstrdup(cmd
->pool
, arg1
),
298 SVNParentPath_cmd(cmd_parms
*cmd
, void *config
, const char *arg1
)
300 dir_conf_t
*conf
= config
;
302 if (conf
->fs_path
!= NULL
)
303 return "SVNParentPath cannot be defined at same time as SVNPath.";
305 conf
->fs_parent_path
= svn_path_internal_style(apr_pstrdup(cmd
->pool
, arg1
),
313 SVNSpecialURI_cmd(cmd_parms
*cmd
, void *config
, const char *arg1
)
319 uri
= apr_pstrdup(cmd
->pool
, arg1
);
321 /* apply a bit of processing to the thing:
322 - eliminate .. and . components
323 - eliminate double slashes
324 - eliminate leading and trailing slashes
331 if (len
> 0 && uri
[len
- 1] == '/')
334 return "The special URI path must have at least one component.";
336 conf
= ap_get_module_config(cmd
->server
->module_config
,
338 conf
->special_uri
= uri
;
344 /** Accessor functions for the module's configuration state **/
347 dav_svn__get_fs_path(request_rec
*r
)
351 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
352 return conf
->fs_path
;
357 dav_svn__get_fs_parent_path(request_rec
*r
)
361 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
362 return conf
->fs_parent_path
;
366 AP_MODULE_DECLARE(dav_error
*)
367 dav_svn_get_repos_path(request_rec
*r
,
368 const char *root_path
,
369 const char **repos_path
)
373 const char *fs_parent_path
;
374 const char *repos_name
;
375 const char *ignored_path_in_repos
;
376 const char *ignored_cleaned_uri
;
377 const char *ignored_relative
;
378 int ignored_had_slash
;
381 /* Handle the SVNPath case. */
382 fs_path
= dav_svn__get_fs_path(r
);
386 *repos_path
= fs_path
;
390 /* Handle the SVNParentPath case. If neither directive was used,
391 dav_svn_split_uri will throw a suitable error for us - we do
392 not need to check that here. */
393 fs_parent_path
= dav_svn__get_fs_parent_path(r
);
395 /* Split the svn URI to get the name of the repository below
397 derr
= dav_svn_split_uri(r
, r
->uri
, root_path
,
398 &ignored_cleaned_uri
, &ignored_had_slash
,
400 &ignored_relative
, &ignored_path_in_repos
);
404 /* Construct the full path from the parent path base directory
405 and the repository name. */
406 *repos_path
= svn_path_join(fs_parent_path
, repos_name
, r
->pool
);
412 dav_svn__get_repo_name(request_rec
*r
)
416 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
417 return conf
->repo_name
;
422 dav_svn__get_root_dir(request_rec
*r
)
426 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
427 return conf
->root_dir
;
432 dav_svn__get_master_uri(request_rec
*r
)
436 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
437 return conf
->master_uri
;
442 dav_svn__get_xslt_uri(request_rec
*r
)
446 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
447 return conf
->xslt_uri
;
452 dav_svn__get_special_uri(request_rec
*r
)
456 conf
= ap_get_module_config(r
->server
->module_config
,
458 return conf
->special_uri
? conf
->special_uri
: SVN_DEFAULT_SPECIAL_URI
;
463 dav_svn__get_autoversioning_flag(request_rec
*r
)
467 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
468 return conf
->autoversioning
== CONF_FLAG_ON
;
472 /* FALSE if path authorization should be skipped.
473 * TRUE if either the bypass or the apache subrequest methods should be used.
476 dav_svn__get_pathauthz_flag(request_rec
*r
)
480 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
481 return conf
->path_authz_method
!= CONF_PATHAUTHZ_OFF
;
484 /* Function pointer if we should use the bypass directly to mod_authz_svn.
486 authz_svn__subreq_bypass_func_t
487 dav_svn__get_pathauthz_bypass(request_rec
*r
)
491 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
493 if (conf
->path_authz_method
==CONF_PATHAUTHZ_BYPASS
)
494 return pathauthz_bypass_func
;
500 dav_svn__get_list_parentpath_flag(request_rec
*r
)
504 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
505 return conf
->list_parentpath
== CONF_FLAG_ON
;
510 dav_svn__get_activities_db(request_rec
*r
)
514 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
515 return conf
->activities_db
;
520 merge_xml_filter_insert(request_rec
*r
)
522 /* We only care about MERGE and DELETE requests. */
523 if ((r
->method_number
== M_MERGE
)
524 || (r
->method_number
== M_DELETE
))
527 conf
= ap_get_module_config(r
->per_dir_config
, &dav_svn_module
);
529 /* We only care if we are configured. */
530 if (conf
->fs_path
|| conf
->fs_parent_path
)
532 ap_add_input_filter("SVN-MERGE", NULL
, r
, r
->connection
);
539 apr_bucket_brigade
*bb
;
540 apr_xml_parser
*parser
;
546 merge_xml_in_filter(ap_filter_t
*f
,
547 apr_bucket_brigade
*bb
,
548 ap_input_mode_t mode
,
549 apr_read_type_e block
,
553 request_rec
*r
= f
->r
;
554 merge_ctx_t
*ctx
= f
->ctx
;
558 /* We shouldn't be added if we're not a MERGE/DELETE, but double check. */
559 if ((r
->method_number
!= M_MERGE
)
560 && (r
->method_number
!= M_DELETE
))
562 ap_remove_input_filter(f
);
563 return ap_get_brigade(f
->next
, bb
, mode
, block
, readbytes
);
568 f
->ctx
= ctx
= apr_palloc(r
->pool
, sizeof(*ctx
));
569 ctx
->parser
= apr_xml_parser_create(r
->pool
);
570 ctx
->bb
= apr_brigade_create(r
->pool
, r
->connection
->bucket_alloc
);
571 apr_pool_create(&ctx
->pool
, r
->pool
);
574 rv
= ap_get_brigade(f
->next
, ctx
->bb
, mode
, block
, readbytes
);
576 if (rv
!= APR_SUCCESS
)
579 for (bucket
= APR_BRIGADE_FIRST(ctx
->bb
);
580 bucket
!= APR_BRIGADE_SENTINEL(ctx
->bb
);
581 bucket
= APR_BUCKET_NEXT(bucket
))
586 if (APR_BUCKET_IS_EOS(bucket
))
592 if (APR_BUCKET_IS_METADATA(bucket
))
595 rv
= apr_bucket_read(bucket
, &data
, &len
, APR_BLOCK_READ
);
596 if (rv
!= APR_SUCCESS
)
599 rv
= apr_xml_parser_feed(ctx
->parser
, data
, len
);
600 if (rv
!= APR_SUCCESS
)
602 /* Clean up the parser. */
603 (void) apr_xml_parser_done(ctx
->parser
, NULL
);
608 /* This will clear-out the ctx->bb as well. */
609 APR_BRIGADE_CONCAT(bb
, ctx
->bb
);
615 /* Remove ourselves now. */
616 ap_remove_input_filter(f
);
618 /* tell the parser that we're done */
619 rv
= apr_xml_parser_done(ctx
->parser
, &pdoc
);
620 if (rv
== APR_SUCCESS
)
622 #if APR_CHARSET_EBCDIC
623 apr_xml_parser_convert_doc(r
->pool
, pdoc
, ap_hdrs_from_ascii
);
625 /* stash the doc away for mod_dav_svn's later use. */
626 rv
= apr_pool_userdata_set(pdoc
, "svn-request-body",
628 if (rv
!= APR_SUCCESS
)
639 /** Module framework stuff **/
641 static const command_rec cmds
[] =
643 /* per directory/location */
644 AP_INIT_TAKE1("SVNPath", SVNPath_cmd
, NULL
, ACCESS_CONF
,
645 "specifies the location in the filesystem for a Subversion "
646 "repository's files."),
649 AP_INIT_TAKE1("SVNSpecialURI", SVNSpecialURI_cmd
, NULL
, RSRC_CONF
,
650 "specify the URI component for special Subversion "
653 /* per directory/location */
654 AP_INIT_TAKE1("SVNReposName", SVNReposName_cmd
, NULL
, ACCESS_CONF
,
655 "specify the name of a Subversion repository"),
657 /* per directory/location */
658 AP_INIT_TAKE1("SVNIndexXSLT", SVNIndexXSLT_cmd
, NULL
, ACCESS_CONF
,
659 "specify the URI of an XSL transformation for "
660 "directory indexes"),
662 /* per directory/location */
663 AP_INIT_TAKE1("SVNParentPath", SVNParentPath_cmd
, NULL
, ACCESS_CONF
,
664 "specifies the location in the filesystem whose "
665 "subdirectories are assumed to be Subversion repositories."),
667 /* per directory/location */
668 AP_INIT_FLAG("SVNAutoversioning", SVNAutoversioning_cmd
, NULL
,
669 ACCESS_CONF
|RSRC_CONF
, "turn on deltaV autoversioning."),
671 /* per directory/location */
672 AP_INIT_TAKE1("SVNPathAuthz", SVNPathAuthz_cmd
, NULL
,
673 ACCESS_CONF
|RSRC_CONF
,
674 "control path-based authz by enabling subrequests(On,default), "
675 "disabling subrequests(Off), or"
676 "querying mod_authz_svn directly(" PATHAUTHZ_BYPASS_ARG
")"),
678 /* per directory/location */
679 AP_INIT_FLAG("SVNListParentPath", SVNListParentPath_cmd
, NULL
,
680 ACCESS_CONF
|RSRC_CONF
, "allow GET of SVNParentPath."),
682 /* per directory/location */
683 AP_INIT_TAKE1("SVNMasterURI", SVNMasterURI_cmd
, NULL
, ACCESS_CONF
,
684 "specifies a URI to access a master Subversion repository"),
686 /* per directory/location */
687 AP_INIT_TAKE1("SVNActivitiesDB", SVNActivitiesDB_cmd
, NULL
, ACCESS_CONF
,
688 "specifies the location in the filesystem in which the "
689 "activities database(s) should be stored"),
696 static dav_provider provider
=
698 &dav_svn__hooks_repository
,
699 &dav_svn__hooks_propdb
,
700 &dav_svn__hooks_locks
,
708 register_hooks(apr_pool_t
*pconf
)
710 ap_hook_pre_config(init_dso
, NULL
, NULL
, APR_HOOK_REALLY_FIRST
);
711 ap_hook_post_config(init
, NULL
, NULL
, APR_HOOK_MIDDLE
);
714 dav_register_provider(pconf
, "svn", &provider
);
716 /* input filter to read MERGE bodies. */
717 ap_register_input_filter("SVN-MERGE", merge_xml_in_filter
, NULL
,
719 ap_hook_insert_filter(merge_xml_filter_insert
, NULL
, NULL
,
722 /* live property handling */
723 dav_hook_gather_propsets(dav_svn__gather_propsets
, NULL
, NULL
,
725 dav_hook_find_liveprop(dav_svn__find_liveprop
, NULL
, NULL
, APR_HOOK_MIDDLE
);
726 dav_hook_insert_all_liveprops(dav_svn__insert_all_liveprops
, NULL
, NULL
,
728 dav_register_liveprop_group(pconf
, &dav_svn__liveprop_group
);
730 /* Proxy / mirroring filters and fixups */
731 ap_register_output_filter("LocationRewrite", dav_svn__location_header_filter
,
732 NULL
, AP_FTYPE_CONTENT_SET
);
733 ap_register_output_filter("ReposRewrite", dav_svn__location_body_filter
,
734 NULL
, AP_FTYPE_CONTENT_SET
);
735 ap_register_input_filter("IncomingRewrite", dav_svn__location_in_filter
,
736 NULL
, AP_FTYPE_CONTENT_SET
);
737 ap_hook_fixups(dav_svn__proxy_merge_fixup
, NULL
, NULL
, APR_HOOK_MIDDLE
);
741 module AP_MODULE_DECLARE_DATA dav_svn_module
=
743 STANDARD20_MODULE_STUFF
,
744 create_dir_config
, /* dir config creater */
745 merge_dir_config
, /* dir merger --- default is to override */
746 create_server_config
, /* server config */
747 merge_server_config
, /* merge server config */
748 cmds
, /* command table */
749 register_hooks
, /* register hooks */