summaryrefslogtreecommitdiff
path: root/subversion/mod_dav_svn
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/mod_dav_svn')
-rw-r--r--subversion/mod_dav_svn/activity.c16
-rw-r--r--subversion/mod_dav_svn/authz.c37
-rw-r--r--subversion/mod_dav_svn/dav_svn.h112
-rw-r--r--subversion/mod_dav_svn/deadprops.c68
-rw-r--r--subversion/mod_dav_svn/liveprops.c92
-rw-r--r--subversion/mod_dav_svn/lock.c69
-rw-r--r--subversion/mod_dav_svn/merge.c43
-rw-r--r--subversion/mod_dav_svn/mirror.c34
-rw-r--r--subversion/mod_dav_svn/mod_dav_svn.c348
-rw-r--r--subversion/mod_dav_svn/posts/create_txn.c55
-rw-r--r--subversion/mod_dav_svn/reports/deleted-rev.c3
-rw-r--r--subversion/mod_dav_svn/reports/file-revs.c9
-rw-r--r--subversion/mod_dav_svn/reports/get-location-segments.c34
-rw-r--r--subversion/mod_dav_svn/reports/get-locations.c3
-rw-r--r--subversion/mod_dav_svn/reports/inherited-props.c236
-rw-r--r--subversion/mod_dav_svn/reports/log.c6
-rw-r--r--subversion/mod_dav_svn/reports/mergeinfo.c3
-rw-r--r--subversion/mod_dav_svn/reports/replay.c62
-rw-r--r--subversion/mod_dav_svn/reports/update.c398
-rw-r--r--subversion/mod_dav_svn/repos.c581
-rw-r--r--subversion/mod_dav_svn/util.c67
-rw-r--r--subversion/mod_dav_svn/version.c122
22 files changed, 1852 insertions, 546 deletions
diff --git a/subversion/mod_dav_svn/activity.c b/subversion/mod_dav_svn/activity.c
index 3f6a5d6..895c4cf 100644
--- a/subversion/mod_dav_svn/activity.c
+++ b/subversion/mod_dav_svn/activity.c
@@ -28,6 +28,7 @@
#include <httpd.h>
#include <mod_dav.h>
+#include "svn_hash.h"
#include "svn_checksum.h"
#include "svn_error.h"
#include "svn_io.h"
@@ -240,17 +241,23 @@ dav_svn__store_activity(const dav_svn_repos *repos,
dav_error *
dav_svn__create_txn(const dav_svn_repos *repos,
const char **ptxn_name,
+ apr_hash_t *revprops,
apr_pool_t *pool)
{
svn_revnum_t rev;
svn_fs_txn_t *txn;
svn_error_t *serr;
- apr_hash_t *revprop_table = apr_hash_make(pool);
+
+ if (! revprops)
+ {
+ revprops = apr_hash_make(pool);
+ }
if (repos->username)
{
- apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
- svn_string_create(repos->username, pool));
+ svn_hash_sets(revprops,
+ SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(repos->username, pool));
}
serr = svn_fs_youngest_rev(&rev, repos->fs, pool);
@@ -262,8 +269,7 @@ dav_svn__create_txn(const dav_svn_repos *repos,
}
serr = svn_repos_fs_begin_txn_for_commit2(&txn, repos->repos, rev,
- revprop_table,
- repos->pool);
+ revprops, repos->pool);
if (serr != NULL)
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
diff --git a/subversion/mod_dav_svn/authz.c b/subversion/mod_dav_svn/authz.c
index a32b133..27763b0 100644
--- a/subversion/mod_dav_svn/authz.c
+++ b/subversion/mod_dav_svn/authz.c
@@ -97,6 +97,43 @@ dav_svn__allow_read(request_rec *r,
}
+svn_boolean_t
+dav_svn__allow_list_repos(request_rec *r,
+ const char *repos_name,
+ apr_pool_t *pool)
+{
+ const char *uri;
+ request_rec *subreq;
+ svn_boolean_t allowed = FALSE;
+
+ /* Easy out: if the admin has explicitly set 'SVNPathAuthz Off',
+ then this whole callback does nothing. */
+ if (! dav_svn__get_pathauthz_flag(r))
+ {
+ return TRUE;
+ }
+
+ /* Do not use short_circuit mode: bypass provider expects R to be request to
+ the repository to find repository relative authorization file. */
+
+ /* Build a Public Resource uri representing repository root. */
+ uri = svn_urlpath__join(dav_svn__get_root_dir(r),
+ svn_path_uri_encode(repos_name, pool), pool);
+
+ /* Check if GET would work against this uri. */
+ subreq = ap_sub_req_method_uri("GET", uri, r, r->output_filters);
+
+ if (subreq)
+ {
+ if (subreq->status == HTTP_OK)
+ allowed = TRUE;
+
+ ap_destroy_sub_req(subreq);
+ }
+
+ return allowed;
+}
+
/* This function implements 'svn_repos_authz_func_t', specifically
for read authorization.
diff --git a/subversion/mod_dav_svn/dav_svn.h b/subversion/mod_dav_svn/dav_svn.h
index ab776f1..8786518 100644
--- a/subversion/mod_dav_svn/dav_svn.h
+++ b/subversion/mod_dav_svn/dav_svn.h
@@ -52,6 +52,15 @@ extern "C" {
/* a pool-key for the shared dav_svn_root used by autoversioning */
#define DAV_SVN__AUTOVERSIONING_ACTIVITY "svn-autoversioning-activity"
+/* Option values for SVNAllowBulkUpdates. Note that
+ it's important that CONF_BULKUPD_DEFAULT is 0 to make
+ dav_svn_merge_dir_config do the right thing. */
+typedef enum dav_svn__bulk_upd_conf {
+ CONF_BULKUPD_DEFAULT,
+ CONF_BULKUPD_ON,
+ CONF_BULKUPD_OFF,
+ CONF_BULKUPD_PREFER
+} dav_svn__bulk_upd_conf;
/* dav_svn_repos
*
@@ -110,7 +119,7 @@ typedef struct dav_svn_repos {
svn_boolean_t autoversioning;
/* Whether bulk updates are allowed for this repository. */
- svn_boolean_t bulk_updates;
+ dav_svn__bulk_upd_conf bulk_updates;
/* Whether HTTP protocol version 2 is allowed to be used. */
svn_boolean_t v2_protocol;
@@ -278,11 +287,15 @@ struct dav_resource_private {
svn_boolean_t auto_checked_out;
/* was this resource fetched using our public peg-/working-rev CGI
- interface (ie: /path/to/item?p=PEGREV]? */
+ interface (ie: /path/to/item?p=PEGREV)? */
svn_boolean_t pegged;
/* Cache any revprop change error */
svn_error_t *revprop_error;
+
+ /* was keyword substitution requested using our public CGI interface
+ (ie: /path/to/item?kw=1)? */
+ svn_boolean_t keyword_subst;
};
@@ -302,10 +315,7 @@ const char *dav_svn__get_fs_parent_path(request_rec *r);
svn_boolean_t dav_svn__get_autoversioning_flag(request_rec *r);
/* for the repository referred to by this request, are bulk updates allowed? */
-svn_boolean_t dav_svn__get_bulk_updates_flag(request_rec *r);
-
-/* for the repository referred to by this request, should httpv2 be advertised? */
-svn_boolean_t dav_svn__get_v2_protocol_flag(request_rec *r);
+dav_svn__bulk_upd_conf dav_svn__get_bulk_updates_flag(request_rec *r);
/* for the repository referred to by this request, are subrequests active? */
svn_boolean_t dav_svn__get_pathauthz_flag(request_rec *r);
@@ -316,6 +326,9 @@ svn_boolean_t dav_svn__get_txdelta_cache_flag(request_rec *r);
/* for the repository referred to by this request, is fulltext caching active? */
svn_boolean_t dav_svn__get_fulltext_cache_flag(request_rec *r);
+/* for the repository referred to by this request, is revprop caching active? */
+svn_boolean_t dav_svn__get_revprop_cache_flag(request_rec *r);
+
/* for the repository referred to by this request, are subrequests bypassed?
* A function pointer if yes, NULL if not.
*/
@@ -325,6 +338,17 @@ authz_svn__subreq_bypass_func_t dav_svn__get_pathauthz_bypass(request_rec *r);
SVNParentPath allowed? */
svn_boolean_t dav_svn__get_list_parentpath_flag(request_rec *r);
+/* For the repository referred to by this request, should HTTPv2
+ protocol support be advertised? Note that this also takes into
+ account the support level expected of based on the specified
+ master server version (if provided via SVNMasterVersion). */
+svn_boolean_t dav_svn__check_httpv2_support(request_rec *r);
+
+/* For the repository referred to by this request, should ephemeral
+ txnprop support be advertised? */
+svn_boolean_t dav_svn__check_ephemeral_txnprops_support(request_rec *r);
+
+
/* SPECIAL URI
@@ -373,6 +397,11 @@ const char *dav_svn__get_xslt_uri(request_rec *r);
/* ### Is this assumed to be URI-encoded? */
const char *dav_svn__get_master_uri(request_rec *r);
+/* Return the version of the master server (used for mirroring) iff a
+ master URI is in place for this location; otherwise, return NULL.
+ Comes from the <SVNMasterVersion> directive. */
+svn_version_t *dav_svn__get_master_version(request_rec *r);
+
/* Return the disk path to the activities db.
Comes from the <SVNActivitiesDB> directive. */
const char *dav_svn__get_activities_db(request_rec *r);
@@ -383,7 +412,10 @@ const char *dav_svn__get_activities_db(request_rec *r);
const char *dav_svn__get_root_dir(request_rec *r);
/* Return the data compression level to be used over the wire. */
-int dav_svn__get_compression_level(void);
+int dav_svn__get_compression_level(request_rec *r);
+
+/* Return the hook script environment parsed from the configuration. */
+const char *dav_svn__get_hooks_env(request_rec *r);
/** For HTTP protocol v2, these are the new URIs and URI stubs
returned to the client in our OPTIONS response. They all depend
@@ -414,10 +446,17 @@ const char *dav_svn__get_vtxn_root_stub(request_rec *r);
/*** activity.c ***/
/* Create a new transaction based on HEAD in REPOS, setting *PTXN_NAME
- to the name of that transaction. Use POOL for allocations. */
+ to the name of that transaction. REVPROPS is an optional hash of
+ const char * property names and const svn_string_t * values which
+ will be set as transactions properties on the transaction this
+ function creates. Use POOL for allocations.
+
+ NOTE: This function will overwrite the svn:author property, if
+ any, found in REVPROPS. */
dav_error *
dav_svn__create_txn(const dav_svn_repos *repos,
const char **ptxn_name,
+ apr_hash_t *revprops,
apr_pool_t *pool);
/* If it exists, abort the transaction named TXN_NAME from REPOS. Use
@@ -614,6 +653,7 @@ static const dav_report_elem dav_svn__reports_list[] = {
{ SVN_XML_NAMESPACE, "replay-report" },
{ SVN_XML_NAMESPACE, "get-deleted-rev-report" },
{ SVN_XML_NAMESPACE, SVN_DAV__MERGEINFO_REPORT },
+ { SVN_XML_NAMESPACE, SVN_DAV__INHERITED_PROPS_REPORT },
{ NULL, NULL },
};
@@ -661,23 +701,22 @@ dav_svn__get_deleted_rev_report(const dav_resource *resource,
const apr_xml_doc *doc,
ap_filter_t *output);
+dav_error *
+dav_svn__get_inherited_props_report(const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output);
/*** posts/ ***/
-/* The list of Subversion's custom POSTs. */
-/* ### TODO: Populate this list and transmit its contents in the
- ### OPTIONS response.
-static const char * dav_svn__posts_list[] = {
- "create-txn",
- NULL
-};
-*/
-
/* The various POST handlers, defined in posts/, and used by repos.c. */
dav_error *
dav_svn__post_create_txn(const dav_resource *resource,
svn_skel_t *request_skel,
ap_filter_t *output);
+dav_error *
+dav_svn__post_create_txn_with_props(const dav_resource *resource,
+ svn_skel_t *request_skel,
+ ap_filter_t *output);
/*** authz.c ***/
@@ -720,6 +759,20 @@ dav_svn__allow_read_resource(const dav_resource *resource,
apr_pool_t *pool);
+/* Return TRUE iff the current user (as determined by Apache's
+ authentication system) has permission to read repository REPOS_NAME.
+ This will invoke any authz modules loaded into Apache unless this
+ Subversion location has been configured to bypass those in favor of a
+ direct lookup in the Subversion authz subsystem. Use POOL for any
+ temporary allocation.
+ IMPORTANT: R must be request for DAV_SVN_RESTYPE_PARENTPATH_COLLECTION
+ resource.
+*/
+svn_boolean_t
+dav_svn__allow_list_repos(request_rec *r,
+ const char *repos_name,
+ apr_pool_t *pool);
+
/* If authz is enabled in the specified BATON, return a read authorization
function. Otherwise, return NULL. */
svn_repos_authz_func_t
@@ -733,11 +786,15 @@ dav_svn__authz_read_func(dav_svn__authz_read_baton *baton);
processing. See dav_new_error_tag for parameter documentation.
Note that DESC may be null (it's hard to track this down from
dav_new_error_tag()'s documentation, but see the dav_error type,
- which says that its desc field may be NULL). */
+ which says that its desc field may be NULL).
+
+ If ERROR_ID is 0, SVN_ERR_RA_DAV_REQUEST_FAILED will be used as a
+ default value for the error code.
+*/
dav_error *
dav_svn__new_error_tag(apr_pool_t *pool,
int status,
- int errno_id,
+ int error_id,
const char *desc,
const char *namespace,
const char *tagname);
@@ -748,11 +805,15 @@ dav_svn__new_error_tag(apr_pool_t *pool,
processing. See dav_new_error for parameter documentation.
Note that DESC may be null (it's hard to track this down from
dav_new_error()'s documentation, but see the dav_error type,
- which says that its desc field may be NULL). */
+ which says that its desc field may be NULL).
+
+ If ERROR_ID is 0, SVN_ERR_RA_DAV_REQUEST_FAILED will be used as a
+ default value for the error code.
+*/
dav_error *
dav_svn__new_error(apr_pool_t *pool,
int status,
- int errno_id,
+ int error_id,
const char *desc);
@@ -808,7 +869,8 @@ enum dav_svn__build_what {
DAV_SVN__BUILD_URI_BC, /* a Baseline Collection */
DAV_SVN__BUILD_URI_PUBLIC, /* the "public" VCR */
DAV_SVN__BUILD_URI_VERSION, /* a Version Resource */
- DAV_SVN__BUILD_URI_VCC /* a Version Controlled Configuration */
+ DAV_SVN__BUILD_URI_VCC, /* a Version Controlled Configuration */
+ DAV_SVN__BUILD_URI_REVROOT /* HTTPv2: Revision Root resource */
};
const char *
@@ -841,6 +903,12 @@ dav_svn__simple_parse_uri(dav_svn__uri_info *info,
const char *uri,
apr_pool_t *pool);
+/* Test the request R to determine if we should return the list of
+ * repositories at the parent path. Only true if SVNListParentPath directive
+ * is 'on' and the request is for our configured root path. */
+svn_boolean_t
+dav_svn__is_parentpath_list(request_rec *r);
+
int dav_svn__find_ns(const apr_array_header_t *namespaces, const char *uri);
diff --git a/subversion/mod_dav_svn/deadprops.c b/subversion/mod_dav_svn/deadprops.c
index faf51b9..5d228a4 100644
--- a/subversion/mod_dav_svn/deadprops.c
+++ b/subversion/mod_dav_svn/deadprops.c
@@ -1,5 +1,7 @@
/*
- * deadprops.c: mod_dav_svn dead property provider functions for Subversion
+ * deadprops.c: mod_dav_svn provider functions for "dead properties"
+ * (properties implemented by Subversion or its users,
+ * not as part of the WebDAV specification).
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
@@ -26,6 +28,7 @@
#include <httpd.h>
#include <mod_dav.h>
+#include "svn_hash.h"
#include "svn_xml.h"
#include "svn_pools.h"
#include "svn_dav.h"
@@ -134,7 +137,7 @@ get_value(dav_db *db, const dav_prop_name *name, svn_string_t **pvalue)
propname, db->p);
else
serr = svn_repos_fs_revision_prop(pvalue,
- db->resource->info-> repos->repos,
+ db->resource->info->repos->repos,
db->resource->info->root.rev,
propname, db->authz_read_func,
db->authz_read_baton, db->p);
@@ -160,6 +163,23 @@ get_value(dav_db *db, const dav_prop_name *name, svn_string_t **pvalue)
}
+static svn_error_t *
+change_txn_prop(svn_fs_txn_t *txn,
+ const char *propname,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ if (strcmp(propname, SVN_PROP_REVISION_AUTHOR) == 0)
+ return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ "Attempted to modify 'svn:author' property "
+ "on a transaction");
+
+ SVN_ERR(svn_repos_fs_change_txn_prop(txn, propname, value, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
static dav_error *
save_value(dav_db *db, const dav_prop_name *name,
const svn_string_t *const *old_value_p,
@@ -168,13 +188,14 @@ save_value(dav_db *db, const dav_prop_name *name,
const char *propname;
svn_error_t *serr;
const dav_resource *resource = db->resource;
+ apr_pool_t *subpool;
/* get the repos-local name */
get_repos_propname(db, name, &propname);
if (propname == NULL)
{
- if (db->resource->info->repos->autoversioning)
+ if (resource->info->repos->autoversioning)
/* ignore the unknown namespace of the incoming prop. */
propname = name->name;
else
@@ -202,13 +223,15 @@ save_value(dav_db *db, const dav_prop_name *name,
*/
- if (db->resource->baselined)
+ /* A subpool to cope with mod_dav making multiple calls, e.g. during
+ PROPPATCH with multiple values. */
+ subpool = svn_pool_create(resource->pool);
+ if (resource->baselined)
{
- if (db->resource->working)
+ if (resource->working)
{
- serr = svn_repos_fs_change_txn_prop(resource->info->root.txn,
- propname, value,
- resource->pool);
+ serr = change_txn_prop(resource->info->root.txn, propname,
+ value, subpool);
}
else
{
@@ -219,7 +242,7 @@ save_value(dav_db *db, const dav_prop_name *name,
TRUE, TRUE,
db->authz_read_func,
db->authz_read_baton,
- resource->pool);
+ subpool);
/* Prepare any hook failure message to get sent over the wire */
if (serr)
@@ -242,20 +265,21 @@ save_value(dav_db *db, const dav_prop_name *name,
dav_svn__operational_log(resource->info,
svn_log__change_rev_prop(
resource->info->root.rev,
- propname, resource->pool));
+ propname, subpool));
}
}
else if (resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION)
{
- serr = svn_repos_fs_change_txn_prop(resource->info->root.txn,
- propname, value, resource->pool);
+ serr = change_txn_prop(resource->info->root.txn, propname,
+ value, subpool);
}
else
{
serr = svn_repos_fs_change_node_prop(resource->info->root.root,
get_repos_path(resource->info),
- propname, value, resource->pool);
+ propname, value, subpool);
}
+ svn_pool_destroy(subpool);
if (serr != NULL)
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
@@ -512,10 +536,6 @@ db_store(dav_db *db,
/* ### namespace check? */
if (elem->first_child && !strcmp(elem->first_child->name, SVN_DAV__OLD_VALUE))
{
- const char *propname;
-
- get_repos_propname(db, name, &propname);
-
/* Parse OLD_PROPVAL. */
old_propval = svn_string_create(dav_xml_get_cdata(elem->first_child, pool,
0 /* strip_white */),
@@ -540,6 +560,7 @@ db_remove(dav_db *db, const dav_prop_name *name)
{
svn_error_t *serr;
const char *propname;
+ apr_pool_t *subpool;
/* get the repos-local name */
get_repos_propname(db, name, &propname);
@@ -548,11 +569,15 @@ db_remove(dav_db *db, const dav_prop_name *name)
if (propname == NULL)
return NULL;
+ /* A subpool to cope with mod_dav making multiple calls, e.g. during
+ PROPPATCH with multiple values. */
+ subpool = svn_pool_create(db->resource->pool);
+
/* Working Baseline or Working (Version) Resource */
if (db->resource->baselined)
if (db->resource->working)
- serr = svn_repos_fs_change_txn_prop(db->resource->info->root.txn,
- propname, NULL, db->resource->pool);
+ serr = change_txn_prop(db->resource->info->root.txn, propname,
+ NULL, subpool);
else
/* ### VIOLATING deltaV: you can't proppatch a baseline, it's
not a working resource! But this is how we currently
@@ -564,11 +589,12 @@ db_remove(dav_db *db, const dav_prop_name *name)
propname, NULL, NULL, TRUE, TRUE,
db->authz_read_func,
db->authz_read_baton,
- db->resource->pool);
+ subpool);
else
serr = svn_repos_fs_change_node_prop(db->resource->info->root.root,
get_repos_path(db->resource->info),
- propname, NULL, db->resource->pool);
+ propname, NULL, subpool);
+ svn_pool_destroy(subpool);
if (serr != NULL)
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not remove a property",
diff --git a/subversion/mod_dav_svn/liveprops.c b/subversion/mod_dav_svn/liveprops.c
index 5640116..725ee92 100644
--- a/subversion/mod_dav_svn/liveprops.c
+++ b/subversion/mod_dav_svn/liveprops.c
@@ -1,5 +1,7 @@
/*
- * liveprops.c: mod_dav_svn live property provider functions for Subversion
+ * liveprops.c: mod_dav_svn provider functions for "live properties"
+ * (properties implemented by the WebDAV specification
+ * itself, not unique to Subversion or its users).
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
@@ -75,7 +77,8 @@ enum {
SVN_PROPID_baseline_relative_path = 1,
SVN_PROPID_md5_checksum,
SVN_PROPID_repository_uuid,
- SVN_PROPID_deadprop_count
+ SVN_PROPID_deadprop_count,
+ SVN_PROPID_sha1_checksum
};
@@ -106,6 +109,7 @@ static const dav_liveprop_spec props[] =
SVN_RO_SVN_PROP(md5_checksum, md5-checksum),
SVN_RO_SVN_PROP(repository_uuid, repository-uuid),
SVN_RO_SVN_PROP(deadprop_count, deadprop-count),
+ SVN_RO_SVN_PROP(sha1_checksum, sha1-checksum),
{ 0 } /* sentinel */
};
@@ -273,15 +277,18 @@ insert_prop_internal(const dav_resource *resource,
int propid,
dav_prop_insert what,
apr_text_header *phdr,
- apr_pool_t *scratch_pool,
- apr_pool_t *result_pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
const char *value = NULL;
const char *s;
const dav_liveprop_spec *info;
- int global_ns;
+ long global_ns;
svn_error_t *serr;
+ /* ### TODO proper errors */
+ static const char *const error_value = "###error###";
+
/*
** Almost none of the SVN provider properties are defined if the
** resource does not exist. We do need to return the one VCC
@@ -378,14 +385,14 @@ insert_prop_internal(const dav_resource *resource,
scratch_pool);
if (serr != NULL)
{
- ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
resource->info->r,
"Can't get created-rev of '%s': "
"%s",
resource->info->repos_path,
serr->message);
svn_error_clear(serr);
- value = "###error###";
+ value = error_value;
break;
}
}
@@ -401,14 +408,14 @@ insert_prop_internal(const dav_resource *resource,
scratch_pool);
if (serr)
{
- ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
resource->info->r,
"Can't get author of r%ld: "
"%s",
committed_rev,
serr->message);
svn_error_clear(serr);
- value = "###error###";
+ value = error_value;
break;
}
@@ -429,15 +436,22 @@ insert_prop_internal(const dav_resource *resource,
svn_filesize_t len = 0;
/* our property, but not defined on collection resources */
- if (resource->collection || resource->baselined)
+ if (resource->type == DAV_RESOURCE_TYPE_ACTIVITY
+ || resource->collection || resource->baselined)
return DAV_PROP_INSERT_NOTSUPP;
serr = svn_fs_file_length(&len, resource->info->root.root,
resource->info->repos_path, scratch_pool);
if (serr != NULL)
{
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
+ resource->info->r,
+ "Can't get filesize of '%s': "
+ "%s",
+ resource->info->repos_path,
+ serr->message);
svn_error_clear(serr);
- value = "0"; /* ### what to do? */
+ value = error_value;
break;
}
@@ -453,7 +467,9 @@ insert_prop_internal(const dav_resource *resource,
svn_string_t *pval;
const char *mime_type = NULL;
- if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION)
+ if (resource->type == DAV_RESOURCE_TYPE_ACTIVITY
+ || (resource->baselined
+ && resource->type == DAV_RESOURCE_TYPE_VERSION))
return DAV_PROP_INSERT_NOTSUPP;
if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
@@ -494,6 +510,8 @@ insert_prop_internal(const dav_resource *resource,
there's no point even checking. No matter what the
error is, we can't claim to have a mime type for
this resource. */
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, serr->apr_err,
+ resource->info->r, "%s", serr->message);
svn_error_clear(serr);
return DAV_PROP_INSERT_NOTDEF;
}
@@ -547,7 +565,7 @@ insert_prop_internal(const dav_resource *resource,
scratch_pool);
if (serr != NULL)
{
- ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
resource->info->r,
"Can't get youngest revision in '%s': "
"%s",
@@ -555,7 +573,7 @@ insert_prop_internal(const dav_resource *resource,
scratch_pool),
serr->message);
svn_error_clear(serr);
- value = "###error###";
+ value = error_value;
break;
}
s = dav_svn__build_uri(resource->info->repos,
@@ -627,14 +645,14 @@ insert_prop_internal(const dav_resource *resource,
scratch_pool);
if (serr != NULL)
{
- ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
resource->info->r,
"Can't get created-rev of '%s': "
"%s",
resource->info->repos_path,
serr->message);
svn_error_clear(serr);
- value = "###error###";
+ value = error_value;
break;
}
@@ -657,6 +675,7 @@ insert_prop_internal(const dav_resource *resource,
break;
case SVN_PROPID_md5_checksum:
+ case SVN_PROPID_sha1_checksum:
if ((! resource->collection)
&& (! resource->baselined)
&& (resource->type == DAV_RESOURCE_TYPE_REGULAR
@@ -665,24 +684,37 @@ insert_prop_internal(const dav_resource *resource,
{
svn_node_kind_t kind;
svn_checksum_t *checksum;
+ svn_checksum_kind_t checksum_kind;
+
+ if (propid == SVN_PROPID_md5_checksum)
+ {
+ checksum_kind = svn_checksum_md5;
+ }
+ else
+ {
+ checksum_kind = svn_checksum_sha1;
+ }
serr = svn_fs_check_path(&kind, resource->info->root.root,
resource->info->repos_path, scratch_pool);
if (!serr && kind == svn_node_file)
- serr = svn_fs_file_checksum(&checksum, svn_checksum_md5,
+ serr = svn_fs_file_checksum(&checksum, checksum_kind,
resource->info->root.root,
resource->info->repos_path, TRUE,
scratch_pool);
if (serr != NULL)
{
- ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
resource->info->r,
- "Can't fetch or compute MD5 checksum of '%s': "
+ "Can't fetch or compute %s checksum of '%s': "
"%s",
+ checksum_kind == svn_checksum_md5
+ ? "MD5"
+ : "SHA1",
resource->info->repos_path,
serr->message);
svn_error_clear(serr);
- value = "###error###";
+ value = error_value;
break;
}
@@ -703,14 +735,14 @@ insert_prop_internal(const dav_resource *resource,
serr = svn_fs_get_uuid(resource->info->repos->fs, &value, scratch_pool);
if (serr != NULL)
{
- ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
resource->info->r,
"Can't fetch UUID of '%s': "
"%s",
svn_fs_path(resource->info->repos->fs, scratch_pool),
serr->message);
svn_error_clear(serr);
- value = "###error###";
+ value = error_value;
break;
}
break;
@@ -728,14 +760,14 @@ insert_prop_internal(const dav_resource *resource,
resource->info->repos_path, scratch_pool);
if (serr != NULL)
{
- ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, serr->apr_err,
resource->info->r,
"Can't fetch proplist of '%s': "
"%s",
resource->info->repos_path,
serr->message);
svn_error_clear(serr);
- value = "###error###";
+ value = error_value;
break;
}
@@ -758,11 +790,11 @@ insert_prop_internal(const dav_resource *resource,
if (what == DAV_PROP_INSERT_NAME
|| (what == DAV_PROP_INSERT_VALUE && *value == '\0')) {
- s = apr_psprintf(result_pool, "<lp%d:%s/>" DEBUG_CR, global_ns,
+ s = apr_psprintf(result_pool, "<lp%ld:%s/>" DEBUG_CR, global_ns,
info->name);
}
else if (what == DAV_PROP_INSERT_VALUE) {
- s = apr_psprintf(result_pool, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
+ s = apr_psprintf(result_pool, "<lp%ld:%s>%s</lp%ld:%s>" DEBUG_CR,
global_ns, info->name, value, global_ns, info->name);
}
else {
@@ -793,7 +825,7 @@ insert_prop(const dav_resource *resource,
scratch_pool = svn_pool_create(result_pool);
rv = insert_prop_internal(resource, propid, what, phdr,
- scratch_pool, result_pool);
+ result_pool, scratch_pool);
svn_pool_destroy(scratch_pool);
return rv;
@@ -802,10 +834,10 @@ insert_prop(const dav_resource *resource,
static int
is_writable(const dav_resource *resource, int propid)
{
- const dav_liveprop_spec *info;
+ const dav_liveprop_spec *info = NULL;
(void) dav_get_liveprop_info(propid, &dav_svn__liveprop_group, &info);
- return info->is_writable;
+ return info ? info->is_writable : FALSE;
}
@@ -931,7 +963,7 @@ dav_svn__insert_all_liveprops(request_rec *r,
{
svn_pool_clear(iterpool);
(void) insert_prop_internal(resource, spec->propid, what, phdr,
- iterpool, resource->pool);
+ resource->pool, iterpool);
}
svn_pool_destroy(iterpool);
diff --git a/subversion/mod_dav_svn/lock.c b/subversion/mod_dav_svn/lock.c
index c74fec9..68d6de5 100644
--- a/subversion/mod_dav_svn/lock.c
+++ b/subversion/mod_dav_svn/lock.c
@@ -28,6 +28,7 @@
#include <http_log.h>
#include <mod_dav.h>
+#include "svn_hash.h"
#include "svn_fs.h"
#include "svn_repos.h"
#include "svn_dav.h"
@@ -142,6 +143,8 @@ unescape_xml(const char **output,
if (apr_err)
{
char errbuf[1024];
+
+ errbuf[0] = '\0';
(void)apr_xml_parser_geterror(xml_parser, errbuf, sizeof(errbuf));
return dav_svn__new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
DAV_ERR_LOCK_SAVE_LOCK, errbuf);
@@ -149,7 +152,7 @@ unescape_xml(const char **output,
apr_xml_to_text(pool, xml_doc->root, APR_XML_X2T_INNER,
xml_doc->namespaces, NULL, output, NULL);
- return SVN_NO_ERROR;
+ return NULL;
}
@@ -450,7 +453,8 @@ get_locks(dav_lockdb *lockdb,
lock. For the --force case, this is required and for the non-force case,
we allow the filesystem to produce a better error for svn clients.
*/
- if (info->r->method_number == M_LOCK)
+ if (info->r->method_number == M_LOCK
+ && resource->info->repos->is_svn_client)
{
*locks = NULL;
return 0;
@@ -591,7 +595,8 @@ has_locks(dav_lockdb *lockdb, const dav_resource *resource, int *locks_present)
lock. For the --force case, this is required and for the non-force case,
we allow the filesystem to produce a better error for svn clients.
*/
- if (info->r->method_number == M_LOCK)
+ if (info->r->method_number == M_LOCK
+ && resource->info->repos->is_svn_client)
{
*locks_present = 0;
return 0;
@@ -640,6 +645,19 @@ append_locks(dav_lockdb *lockdb,
svn_lock_t *slock;
svn_error_t *serr;
dav_error *derr;
+ dav_svn_repos *repos = resource->info->repos;
+
+ /* We don't allow anonymous locks */
+ if (! repos->username)
+ return dav_svn__new_error(resource->pool, HTTP_NOT_IMPLEMENTED,
+ DAV_ERR_LOCK_SAVE_LOCK,
+ "Anonymous lock creation is not allowed.");
+
+ /* Not a path in the repository so can't lock it. */
+ if (! resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_LOCK_SAVE_LOCK,
+ "Attempted to lock path not in repository.");
/* If the resource's fs path is unreadable, we don't allow a lock to
be created on it. */
@@ -663,11 +681,10 @@ append_locks(dav_lockdb *lockdb,
svn_fs_txn_t *txn;
svn_fs_root_t *txn_root;
const char *conflict_msg;
- dav_svn_repos *repos = resource->info->repos;
apr_hash_t *revprop_table = apr_hash_make(resource->pool);
- apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING, svn_string_create(repos->username,
- resource->pool));
+ svn_hash_sets(revprop_table,
+ SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(repos->username, resource->pool));
if (resource->info->repos->is_svn_client)
return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED,
@@ -741,14 +758,14 @@ append_locks(dav_lockdb *lockdb,
/* Convert the dav_lock into an svn_lock_t. */
derr = dav_lock_to_svn_lock(&slock, lock, resource->info->repos_path,
- info, resource->info->repos->is_svn_client,
+ info, repos->is_svn_client,
resource->pool);
if (derr)
return derr;
/* Now use the svn_lock_t to actually perform the lock. */
serr = svn_repos_fs_lock(&slock,
- resource->info->repos->repos,
+ repos->repos,
slock->path,
slock->token,
slock->comment,
@@ -761,14 +778,21 @@ append_locks(dav_lockdb *lockdb,
if (serr && serr->apr_err == SVN_ERR_FS_NO_USER)
{
svn_error_clear(serr);
- return dav_svn__new_error(resource->pool, HTTP_UNAUTHORIZED,
+ return dav_svn__new_error(resource->pool, HTTP_NOT_IMPLEMENTED,
DAV_ERR_LOCK_SAVE_LOCK,
"Anonymous lock creation is not allowed.");
}
+ else if (serr && (serr->apr_err == SVN_ERR_REPOS_HOOK_FAILURE ||
+ serr->apr_err == SVN_ERR_FS_NO_SUCH_LOCK ||
+ serr->apr_err == SVN_ERR_FS_LOCK_EXPIRED ||
+ SVN_ERR_IS_LOCK_ERROR(serr)))
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Failed to create new lock.",
+ resource->pool);
else if (serr)
- return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
- "Failed to create new lock.",
- resource->pool);
+ return dav_svn__sanitize_error(serr, "Failed to create new lock.",
+ HTTP_INTERNAL_SERVER_ERROR,
+ resource->info->r);
/* A standard webdav LOCK response doesn't include any information
@@ -864,7 +888,7 @@ remove_lock(dav_lockdb *lockdb,
if (serr && serr->apr_err == SVN_ERR_FS_NO_USER)
{
svn_error_clear(serr);
- return dav_svn__new_error(resource->pool, HTTP_UNAUTHORIZED,
+ return dav_svn__new_error(resource->pool, HTTP_NOT_IMPLEMENTED,
DAV_ERR_LOCK_SAVE_LOCK,
"Anonymous lock removal is not allowed.");
}
@@ -931,7 +955,7 @@ refresh_locks(dav_lockdb *lockdb,
current lock on the incoming resource? */
if ((! slock)
|| (strcmp(token->uuid_str, slock->token) != 0))
- return dav_svn__new_error(resource->pool, HTTP_UNAUTHORIZED,
+ return dav_svn__new_error(resource->pool, HTTP_PRECONDITION_FAILED,
DAV_ERR_LOCK_SAVE_LOCK,
"Lock refresh request doesn't match existing "
"lock.");
@@ -952,14 +976,21 @@ refresh_locks(dav_lockdb *lockdb,
if (serr && serr->apr_err == SVN_ERR_FS_NO_USER)
{
svn_error_clear(serr);
- return dav_svn__new_error(resource->pool, HTTP_UNAUTHORIZED,
+ return dav_svn__new_error(resource->pool, HTTP_NOT_IMPLEMENTED,
DAV_ERR_LOCK_SAVE_LOCK,
"Anonymous lock refreshing is not allowed.");
}
+ else if (serr && (serr->apr_err == SVN_ERR_REPOS_HOOK_FAILURE ||
+ serr->apr_err == SVN_ERR_FS_NO_SUCH_LOCK ||
+ serr->apr_err == SVN_ERR_FS_LOCK_EXPIRED ||
+ SVN_ERR_IS_LOCK_ERROR(serr)))
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Failed to refresh existing lock.",
+ resource->pool);
else if (serr)
- return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
- "Failed to refresh existing lock.",
- resource->pool);
+ return dav_svn__sanitize_error(serr, "Failed to refresh existing lock.",
+ HTTP_INTERNAL_SERVER_ERROR,
+ resource->info->r);
/* Convert the refreshed lock into a dav_lock and return it. */
svn_lock_to_dav_lock(&dlock, slock, FALSE, resource->exists, resource->pool);
diff --git a/subversion/mod_dav_svn/merge.c b/subversion/mod_dav_svn/merge.c
index 9d69b47..3d6d80b 100644
--- a/subversion/mod_dav_svn/merge.c
+++ b/subversion/mod_dav_svn/merge.c
@@ -29,6 +29,7 @@
#include <httpd.h>
#include <util_filter.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_fs.h"
#include "svn_props.h"
@@ -169,14 +170,14 @@ do_resources(const dav_svn_repos *repos,
{
/* If we haven't already sent this path, send it (and then
remember that we sent it). */
- if (! apr_hash_get(sent, path, APR_HASH_KEY_STRING))
+ if (! svn_hash_gets(sent, path))
{
svn_node_kind_t kind;
SVN_ERR(svn_fs_check_path(&kind, root, path, subpool));
SVN_ERR(send_response(repos, root, path,
kind == svn_node_dir,
output, bb, subpool));
- apr_hash_set(sent, path, APR_HASH_KEY_STRING, (void *)1);
+ svn_hash_sets(sent, path, (void *)1);
}
}
if (send_parent)
@@ -186,11 +187,11 @@ do_resources(const dav_svn_repos *repos,
pool, not subpool, because it stays in the sent hash
afterwards. */
const char *parent = svn_fspath__dirname(path, pool);
- if (! apr_hash_get(sent, parent, APR_HASH_KEY_STRING))
+ if (! svn_hash_gets(sent, parent))
{
SVN_ERR(send_response(repos, root, parent,
TRUE, output, bb, subpool));
- apr_hash_set(sent, parent, APR_HASH_KEY_STRING, (void *)1);
+ svn_hash_sets(sent, parent, (void *)1);
}
}
}
@@ -223,6 +224,7 @@ dav_svn__merge_response(ap_filter_t *output,
svn_string_t *creationdate, *creator_displayname;
const char *post_commit_err_elem = NULL,
*post_commit_header_info = NULL;
+ apr_status_t status;
serr = svn_fs_revision_root(&root, repos->fs, new_rev, pool);
if (serr != NULL)
@@ -283,7 +285,7 @@ dav_svn__merge_response(ap_filter_t *output,
}
- (void) ap_fputstrs(output, bb,
+ status = ap_fputstrs(output, bb,
DAV_XML_HEADER DEBUG_CR
"<D:merge-response xmlns:D=\"DAV:\"",
post_commit_header_info,
@@ -303,30 +305,43 @@ dav_svn__merge_response(ap_filter_t *output,
post_commit_err_elem, DEBUG_CR
"<D:version-name>", rev, "</D:version-name>" DEBUG_CR,
NULL);
+ if (status != APR_SUCCESS)
+ return dav_svn__new_error(repos->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not write output");
+
if (creationdate)
{
- (void) ap_fputstrs(output, bb,
+ status = ap_fputstrs(output, bb,
"<D:creationdate>",
apr_xml_quote_string(pool, creationdate->data, 1),
"</D:creationdate>" DEBUG_CR,
NULL);
+ if (status != APR_SUCCESS)
+ return dav_svn__new_error(repos->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not write output");
}
if (creator_displayname)
{
- (void) ap_fputstrs(output, bb,
+ status = ap_fputstrs(output, bb,
"<D:creator-displayname>",
apr_xml_quote_string(pool,
creator_displayname->data, 1),
"</D:creator-displayname>" DEBUG_CR,
NULL);
+ if (status != APR_SUCCESS)
+ return dav_svn__new_error(repos->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not write output");
}
- (void) ap_fputstrs(output, bb,
+ status = ap_fputstrs(output, bb,
"</D:prop>" DEBUG_CR
"<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
"</D:propstat>" DEBUG_CR
"</D:response>" DEBUG_CR,
NULL);
+ if (status != APR_SUCCESS)
+ return dav_svn__new_error(repos->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not write output");
/* ONLY have dir_delta drive the editor if the caller asked us to
generate a full MERGE response. svn clients can ask us to
@@ -355,12 +370,18 @@ dav_svn__merge_response(ap_filter_t *output,
}
/* wrap up the merge response */
- (void) ap_fputs(output, bb,
+ status = ap_fputs(output, bb,
"</D:updated-set>" DEBUG_CR
"</D:merge-response>" DEBUG_CR);
+ if (status != APR_SUCCESS)
+ return dav_svn__new_error(repos->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not write output");
/* send whatever is left in the brigade */
- (void) ap_pass_brigade(output, bb);
+ status = ap_pass_brigade(output, bb);
+ if (status != APR_SUCCESS)
+ return dav_svn__new_error(repos->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not write output");
- return SVN_NO_ERROR;
+ return NULL;
}
diff --git a/subversion/mod_dav_svn/mirror.c b/subversion/mod_dav_svn/mirror.c
index 610b640..e8b19a8 100644
--- a/subversion/mod_dav_svn/mirror.c
+++ b/subversion/mod_dav_svn/mirror.c
@@ -39,12 +39,17 @@
URI_SEGMENT is the URI bits relative to the repository root (but if
non-empty, *does* have a leading slash delimiter).
MASTER_URI and URI_SEGMENT are not URI-encoded. */
-static void proxy_request_fixup(request_rec *r,
- const char *master_uri,
- const char *uri_segment)
+static int proxy_request_fixup(request_rec *r,
+ const char *master_uri,
+ const char *uri_segment)
{
- assert((uri_segment[0] == '\0')
- || (uri_segment[0] == '/'));
+ if (uri_segment[0] != '\0' && uri_segment[0] != '/')
+ {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, SVN_ERR_BAD_CONFIG_VALUE, r,
+ "Invalid URI segment '%s' in slave fixup",
+ uri_segment);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
r->proxyreq = PROXYREQ_REVERSE;
r->uri = r->unparsed_uri;
@@ -54,9 +59,20 @@ static void proxy_request_fixup(request_rec *r,
(char *)NULL),
r->pool);
r->handler = "proxy-server";
+
+ /* ### FIXME: Seems we could avoid adding some or all of these
+ filters altogether when the root_dir (that is, the slave's
+ location, relative to the server root) and path portion of
+ the master_uri (the master's location, relative to the
+ server root) are identical, rather than adding them here
+ and then trying to remove them later. (See the filter
+ removal logic in dav_svn__location_in_filter() and
+ dav_svn__location_body_filter(). -- cmpilato */
+
ap_add_output_filter("LocationRewrite", NULL, r, r->connection);
ap_add_output_filter("ReposRewrite", NULL, r, r->connection);
ap_add_input_filter("IncomingRewrite", NULL, r, r->connection);
+ return OK;
}
@@ -91,8 +107,10 @@ int dav_svn__proxy_request_fixup(request_rec *r)
"/txn/", (char *)NULL))
|| ap_strstr_c(seg, apr_pstrcat(r->pool, special_uri,
"/txr/", (char *)NULL))) {
+ int rv;
seg += strlen(root_dir);
- proxy_request_fixup(r, master_uri, seg);
+ rv = proxy_request_fixup(r, master_uri, seg);
+ if (rv) return rv;
}
}
return OK;
@@ -106,8 +124,10 @@ int dav_svn__proxy_request_fixup(request_rec *r)
r->method_number == M_LOCK ||
r->method_number == M_UNLOCK ||
ap_strstr_c(seg, special_uri))) {
+ int rv;
seg += strlen(root_dir);
- proxy_request_fixup(r, master_uri, seg);
+ rv = proxy_request_fixup(r, master_uri, seg);
+ if (rv) return rv;
return OK;
}
}
diff --git a/subversion/mod_dav_svn/mod_dav_svn.c b/subversion/mod_dav_svn/mod_dav_svn.c
index 5bdb322..a97d307 100644
--- a/subversion/mod_dav_svn/mod_dav_svn.c
+++ b/subversion/mod_dav_svn/mod_dav_svn.c
@@ -22,7 +22,10 @@
* ====================================================================
*/
+#include <stdlib.h>
+
#include <apr_strings.h>
+#include <apr_hash.h>
#include <httpd.h>
#include <http_config.h>
@@ -31,6 +34,7 @@
#include <ap_provider.h>
#include <mod_dav.h>
+#include "svn_hash.h"
#include "svn_version.h"
#include "svn_cache_config.h"
#include "svn_utf.h"
@@ -39,6 +43,7 @@
#include "mod_dav_svn.h"
#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
#include "dav_svn.h"
#include "mod_authz_svn.h"
@@ -55,6 +60,13 @@
/* per-server configuration */
typedef struct server_conf_t {
const char *special_uri;
+ svn_boolean_t use_utf8;
+
+ /* The compression level we will pass to svn_txdelta_to_svndiff3()
+ * for wire-compression. Negative value used to specify default
+ compression level. */
+ int compression_level;
+
} server_conf_t;
@@ -82,15 +94,18 @@ typedef struct dir_conf_t {
const char *xslt_uri; /* XSL transform URI */
const char *fs_parent_path; /* path to parent of SVN FS'es */
enum conf_flag autoversioning; /* whether autoversioning is active */
- enum conf_flag bulk_updates; /* whether bulk updates are allowed */
+ dav_svn__bulk_upd_conf bulk_updates; /* whether bulk updates are allowed */
enum conf_flag v2_protocol; /* whether HTTP v2 is advertised */
enum path_authz_conf path_authz_method; /* how GET subrequests are handled */
enum conf_flag list_parentpath; /* whether to allow GET of parentpath */
const char *root_dir; /* our top-level directory */
const char *master_uri; /* URI to the master SVN repos */
+ svn_version_t *master_version; /* version of master server */
const char *activities_db; /* path to activities database(s) */
enum conf_flag txdelta_cache; /* whether to enable txdelta caching */
enum conf_flag fulltext_cache; /* whether to enable fulltext caching */
+ enum conf_flag revprop_cache; /* whether to enable revprop caching */
+ const char *hooks_env; /* path to hook script env config file */
} dir_conf_t;
@@ -103,14 +118,12 @@ extern module AP_MODULE_DECLARE_DATA dav_svn_module;
/* The authz_svn provider for bypassing path authz. */
static authz_svn__subreq_bypass_func_t pathauthz_bypass_func = NULL;
-/* The compression level we will pass to svn_txdelta_to_svndiff3()
- * for wire-compression */
-static int svn__compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
-
static int
init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
svn_error_t *serr;
+ server_conf_t *conf;
+
ap_add_version_component(p, "SVN/" SVN_VER_NUMBER);
serr = svn_fs_initialize(p);
@@ -123,7 +136,8 @@ init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
}
/* This returns void, so we can't check for error. */
- svn_utf_initialize(p);
+ conf = ap_get_module_config(s->module_config, &dav_svn_module);
+ svn_utf_initialize2(conf->use_utf8, p);
return OK;
}
@@ -154,7 +168,11 @@ init_dso(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
static void *
create_server_config(apr_pool_t *p, server_rec *s)
{
- return apr_pcalloc(p, sizeof(server_conf_t));
+ server_conf_t *conf = apr_pcalloc(p, sizeof(server_conf_t));
+
+ conf->compression_level = -1;
+
+ return conf;
}
@@ -170,6 +188,17 @@ merge_server_config(apr_pool_t *p, void *base, void *overrides)
newconf->special_uri = INHERIT_VALUE(parent, child, special_uri);
+ if (child->compression_level < 0)
+ {
+ /* Inherit compression level from parent if not configured for this
+ VirtualHost. */
+ newconf->compression_level = parent->compression_level;
+ }
+ else
+ {
+ newconf->compression_level = child->compression_level;
+ }
+
return newconf;
}
@@ -185,8 +214,9 @@ create_dir_config(apr_pool_t *p, char *dir)
<Location /blah> directive. So we treat it as a urlpath. */
if (dir)
conf->root_dir = svn_urlpath__canonicalize(dir, p);
- conf->bulk_updates = CONF_FLAG_ON;
+ conf->bulk_updates = CONF_BULKUPD_DEFAULT;
conf->v2_protocol = CONF_FLAG_ON;
+ conf->hooks_env = NULL;
return conf;
}
@@ -204,6 +234,7 @@ merge_dir_config(apr_pool_t *p, void *base, void *overrides)
newconf->fs_path = INHERIT_VALUE(parent, child, fs_path);
newconf->master_uri = INHERIT_VALUE(parent, child, master_uri);
+ newconf->master_version = INHERIT_VALUE(parent, child, master_version);
newconf->activities_db = INHERIT_VALUE(parent, child, activities_db);
newconf->repo_name = INHERIT_VALUE(parent, child, repo_name);
newconf->xslt_uri = INHERIT_VALUE(parent, child, xslt_uri);
@@ -215,14 +246,16 @@ merge_dir_config(apr_pool_t *p, void *base, void *overrides)
newconf->list_parentpath = INHERIT_VALUE(parent, child, list_parentpath);
newconf->txdelta_cache = INHERIT_VALUE(parent, child, txdelta_cache);
newconf->fulltext_cache = INHERIT_VALUE(parent, child, fulltext_cache);
+ newconf->revprop_cache = INHERIT_VALUE(parent, child, revprop_cache);
newconf->root_dir = INHERIT_VALUE(parent, child, root_dir);
+ newconf->hooks_env = INHERIT_VALUE(parent, child, hooks_env);
if (parent->fs_path)
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
"mod_dav_svn: nested Location '%s' hinders access to '%s' "
"in SVNPath Location '%s'",
child->root_dir,
- svn_fspath__skip_ancestor(parent->root_dir, child->root_dir),
+ svn_urlpath__skip_ancestor(parent->root_dir, child->root_dir),
parent->root_dir);
return newconf;
@@ -270,6 +303,25 @@ SVNMasterURI_cmd(cmd_parms *cmd, void *config, const char *arg1)
static const char *
+SVNMasterVersion_cmd(cmd_parms *cmd, void *config, const char *arg1)
+{
+ dir_conf_t *conf = config;
+ svn_error_t *err;
+ svn_version_t *version;
+
+ err = svn_version__parse_version_string(&version, arg1, cmd->pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return "Malformed master server version string.";
+ }
+
+ conf->master_version = version;
+ return NULL;
+}
+
+
+static const char *
SVNActivitiesDB_cmd(cmd_parms *cmd, void *config, const char *arg1)
{
dir_conf_t *conf = config;
@@ -306,14 +358,26 @@ SVNAutoversioning_cmd(cmd_parms *cmd, void *config, int arg)
static const char *
-SVNAllowBulkUpdates_cmd(cmd_parms *cmd, void *config, int arg)
+SVNAllowBulkUpdates_cmd(cmd_parms *cmd, void *config, const char *arg1)
{
dir_conf_t *conf = config;
- if (arg)
- conf->bulk_updates = CONF_FLAG_ON;
+ if (apr_strnatcasecmp("on", arg1) == 0)
+ {
+ conf->bulk_updates = CONF_BULKUPD_ON;
+ }
+ else if (apr_strnatcasecmp("off", arg1) == 0)
+ {
+ conf->bulk_updates = CONF_BULKUPD_OFF;
+ }
+ else if (apr_strnatcasecmp("prefer", arg1) == 0)
+ {
+ conf->bulk_updates = CONF_BULKUPD_PREFER;
+ }
else
- conf->bulk_updates = CONF_FLAG_OFF;
+ {
+ return "Unrecognized value for SVNAllowBulkUpdates directive";
+ }
return NULL;
}
@@ -466,6 +530,19 @@ SVNCacheFullTexts_cmd(cmd_parms *cmd, void *config, int arg)
}
static const char *
+SVNCacheRevProps_cmd(cmd_parms *cmd, void *config, int arg)
+{
+ dir_conf_t *conf = config;
+
+ if (arg)
+ conf->revprop_cache = CONF_FLAG_ON;
+ else
+ conf->revprop_cache = CONF_FLAG_OFF;
+
+ return NULL;
+}
+
+static const char *
SVNInMemoryCacheSize_cmd(cmd_parms *cmd, void *config, const char *arg1)
{
svn_cache_config_t settings = *svn_cache_config_get();
@@ -488,6 +565,7 @@ SVNInMemoryCacheSize_cmd(cmd_parms *cmd, void *config, const char *arg1)
static const char *
SVNCompressionLevel_cmd(cmd_parms *cmd, void *config, const char *arg1)
{
+ server_conf_t *conf;
int value = 0;
svn_error_t *err = svn_cstring_atoi(&value, arg1);
if (err)
@@ -505,7 +583,31 @@ SVNCompressionLevel_cmd(cmd_parms *cmd, void *config, const char *arg1)
(int)SVN_DELTA_COMPRESSION_LEVEL_NONE,
(int)SVN_DELTA_COMPRESSION_LEVEL_MAX);
- svn__compression_level = value;
+ conf = ap_get_module_config(cmd->server->module_config,
+ &dav_svn_module);
+ conf->compression_level = value;
+
+ return NULL;
+}
+
+static const char *
+SVNUseUTF8_cmd(cmd_parms *cmd, void *config, int arg)
+{
+ server_conf_t *conf;
+
+ conf = ap_get_module_config(cmd->server->module_config,
+ &dav_svn_module);
+ conf->use_utf8 = arg;
+
+ return NULL;
+}
+
+static const char *
+SVNHooksEnv_cmd(cmd_parms *cmd, void *config, const char *arg1)
+{
+ dir_conf_t *conf = config;
+
+ conf->hooks_env = svn_dirent_internal_style(arg1, cmd->pool);
return NULL;
}
@@ -573,7 +675,7 @@ dav_svn_get_repos_path(request_rec *r,
/* Construct the full path from the parent path base directory
and the repository name. */
- *repos_path = svn_urlpath__join(fs_parent_path, repos_name, r->pool);
+ *repos_path = svn_dirent_join(fs_parent_path, repos_name, r->pool);
return NULL;
}
@@ -608,6 +710,16 @@ dav_svn__get_master_uri(request_rec *r)
}
+svn_version_t *
+dav_svn__get_master_version(request_rec *r)
+{
+ dir_conf_t *conf;
+
+ conf = ap_get_module_config(r->per_dir_config, &dav_svn_module);
+ return conf->master_uri ? conf->master_version : NULL;
+}
+
+
const char *
dav_svn__get_xslt_uri(request_rec *r)
{
@@ -694,23 +806,55 @@ dav_svn__get_autoversioning_flag(request_rec *r)
}
-svn_boolean_t
+dav_svn__bulk_upd_conf
dav_svn__get_bulk_updates_flag(request_rec *r)
{
dir_conf_t *conf;
conf = ap_get_module_config(r->per_dir_config, &dav_svn_module);
- return conf->bulk_updates == CONF_FLAG_ON;
+
+ /* SVNAllowBulkUpdates is 'on' by default. */
+ if (conf->bulk_updates == CONF_BULKUPD_DEFAULT)
+ return CONF_BULKUPD_ON;
+ else
+ return conf->bulk_updates;
}
svn_boolean_t
-dav_svn__get_v2_protocol_flag(request_rec *r)
+dav_svn__check_httpv2_support(request_rec *r)
{
dir_conf_t *conf;
+ svn_boolean_t available;
conf = ap_get_module_config(r->per_dir_config, &dav_svn_module);
- return conf->v2_protocol == CONF_FLAG_ON;
+ available = conf->v2_protocol == CONF_FLAG_ON;
+
+ /* If our configuration says that HTTPv2 is available, but we are
+ proxying requests to a master Subversion server which lacks
+ support for HTTPv2, we dumb ourselves down. */
+ if (available)
+ {
+ svn_version_t *version = dav_svn__get_master_version(r);
+ if (version && (! svn_version__at_least(version, 1, 7, 0)))
+ available = FALSE;
+ }
+ return available;
+}
+
+
+svn_boolean_t
+dav_svn__check_ephemeral_txnprops_support(request_rec *r)
+{
+ svn_version_t *version = dav_svn__get_master_version(r);
+
+ /* We know this server supports ephemeral txnprops. But if we're
+ proxying requests to a master server, we need to see if it
+ supports them, too. */
+ if (version && (! svn_version__at_least(version, 1, 8, 0)))
+ return FALSE;
+
+ return TRUE;
}
@@ -781,10 +925,41 @@ dav_svn__get_fulltext_cache_flag(request_rec *r)
}
+svn_boolean_t
+dav_svn__get_revprop_cache_flag(request_rec *r)
+{
+ dir_conf_t *conf;
+
+ conf = ap_get_module_config(r->per_dir_config, &dav_svn_module);
+ return conf->revprop_cache == CONF_FLAG_ON;
+}
+
+
int
-dav_svn__get_compression_level(void)
+dav_svn__get_compression_level(request_rec *r)
{
- return svn__compression_level;
+ server_conf_t *conf;
+
+ conf = ap_get_module_config(r->server->module_config,
+ &dav_svn_module);
+
+ if (conf->compression_level < 0)
+ {
+ return SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
+ }
+ else
+ {
+ return conf->compression_level;
+ }
+}
+
+const char *
+dav_svn__get_hooks_env(request_rec *r)
+{
+ dir_conf_t *conf;
+
+ conf = ap_get_module_config(r->per_dir_config, &dav_svn_module);
+ return conf->hooks_env;
}
static void
@@ -924,7 +1099,95 @@ static int dav_svn__handler(request_rec *r)
return DECLINED;
}
+#define NO_MAP_TO_STORAGE_NOTE "dav_svn-no-map-to-storage"
+/* Fill the filename on the request with a bogus path since we aren't serving
+ * a file off the disk. This means that <Directory> blocks will not match and
+ * that %f in logging formats will show as "svn:/path/to/repo/path/in/repo". */
+static int dav_svn__translate_name(request_rec *r)
+{
+ const char *fs_path, *repos_basename, *repos_path, *slash;
+ const char *ignore_cleaned_uri, *ignore_relative_path;
+ int ignore_had_slash;
+ dir_conf_t *conf = ap_get_module_config(r->per_dir_config, &dav_svn_module);
+
+ /* module is not configured, bail out early */
+ if (!conf->fs_path && !conf->fs_parent_path)
+ return DECLINED;
+
+ if (dav_svn__is_parentpath_list(r))
+ {
+ /* SVNListParentPath is on and the request is for the conf->root_dir,
+ * so just set the repos_basename to an empty string and the repos_path
+ * to NULL so we end up just reporting our parent path as the bogus
+ * path. */
+ repos_basename = "";
+ repos_path = NULL;
+ }
+ else
+ {
+ /* Retrieve path to repo and within repo for the request */
+ dav_error *err = dav_svn_split_uri(r, r->uri, conf->root_dir,
+ &ignore_cleaned_uri,
+ &ignore_had_slash, &repos_basename,
+ &ignore_relative_path, &repos_path);
+ if (err)
+ {
+ dav_svn__log_err(r, err, APLOG_ERR);
+ return err->status;
+ }
+ }
+
+ if (conf->fs_parent_path)
+ {
+ fs_path = svn_dirent_join(conf->fs_parent_path, repos_basename,
+ r->pool);
+ }
+ else
+ {
+ fs_path = conf->fs_path;
+ }
+
+ /* Avoid a trailing slash on the bogus path when repos_path is just "/" and
+ * ensure that there is always a slash between fs_path and repos_path as
+ * long as the repos_path is not an empty path. */
+ slash = "";
+ if (repos_path)
+ {
+ if ('/' == repos_path[0] && '\0' == repos_path[1])
+ repos_path = NULL;
+ else if ('/' != repos_path[0] && '\0' != repos_path[0])
+ slash = "/";
+ }
+
+ /* Combine 'svn:', fs_path and repos_path to produce the bogus path we're
+ * placing in r->filename. We can't use our standard join helpers such
+ * as svn_dirent_join. fs_path is a dirent and repos_path is a fspath
+ * (that can be trivially converted to a relpath by skipping the leading
+ * slash). In general it is safe to join these, but when a path in a
+ * repository is 'trunk/c:hi' this results in a non canonical dirent on
+ * Windows. Instead we just cat them together. */
+ r->filename = apr_pstrcat(r->pool,
+ "svn:", fs_path, slash, repos_path, NULL);
+
+ /* Leave a note to ourselves so that we know not to decline in the
+ * map_to_storage hook. */
+ apr_table_setn(r->notes, NO_MAP_TO_STORAGE_NOTE, (const char*)1);
+ return OK;
+}
+
+/* Prevent core_map_to_storage from running if we prevented the r->filename
+ * from being set since core_map_to_storage doesn't like r->filename being
+ * bogus. */
+static int dav_svn__map_to_storage(request_rec *r)
+{
+ /* Check a note we left in translate_name since map_to_storage doesn't
+ * have access to our configuration. */
+ if (apr_table_get(r->notes, NO_MAP_TO_STORAGE_NOTE))
+ return OK;
+
+ return DECLINED;
+}
@@ -977,16 +1240,22 @@ static const command_rec cmds[] =
"specifies a URI to access a master Subversion repository"),
/* per directory/location */
+ AP_INIT_TAKE1("SVNMasterVersion", SVNMasterVersion_cmd, NULL, ACCESS_CONF,
+ "specifies the Subversion release version of a master "
+ "Subversion server "),
+
+ /* per directory/location */
AP_INIT_TAKE1("SVNActivitiesDB", SVNActivitiesDB_cmd, NULL, ACCESS_CONF,
"specifies the location in the filesystem in which the "
"activities database(s) should be stored"),
/* per directory/location */
- AP_INIT_FLAG("SVNAllowBulkUpdates", SVNAllowBulkUpdates_cmd, NULL,
- ACCESS_CONF|RSRC_CONF,
- "enables support for bulk update-style requests (as opposed to "
- "only skeletal reports that require additional per-file "
- "downloads."),
+ AP_INIT_TAKE1("SVNAllowBulkUpdates", SVNAllowBulkUpdates_cmd, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "enables support for bulk update-style requests (On, default), "
+ "as opposed to only skeletal reports that require additional "
+ "per-file downloads (Off). Use Prefer to tell the svn client "
+ "to always use bulk update requests, if supported."),
/* per directory/location */
AP_INIT_FLAG("SVNAdvertiseV2Protocol", SVNAdvertiseV2Protocol_cmd, NULL,
@@ -1008,6 +1277,14 @@ static const command_rec cmds[] =
"if sufficient in-memory cache is available "
"(default is Off)."),
+ /* per directory/location */
+ AP_INIT_FLAG("SVNCacheRevProps", SVNCacheRevProps_cmd, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "speeds up 'svn ls -v', export and checkout operations"
+ "but should only be enabled under the conditions described"
+ "in the documentation"
+ "(default is Off)."),
+
/* per server */
AP_INIT_TAKE1("SVNInMemoryCacheSize", SVNInMemoryCacheSize_cmd, NULL,
RSRC_CONF,
@@ -1021,6 +1298,19 @@ static const command_rec cmds[] =
"content over the network (0 for no compression, 9 for "
"maximum, 5 is default)."),
+ /* per server */
+ AP_INIT_FLAG("SVNUseUTF8",
+ SVNUseUTF8_cmd, NULL,
+ RSRC_CONF,
+ "use UTF-8 as native character encoding (default is ASCII)."),
+
+ /* per directory/location */
+ AP_INIT_TAKE1("SVNHooksEnv", SVNHooksEnv_cmd, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "Sets the path to the configuration file for the environment "
+ "of hook scripts. If not absolute, the path is relative to "
+ "the repository's conf directory (by default the hooks-env "
+ "file in the repository is used)."),
{ NULL }
};
@@ -1071,6 +1361,12 @@ register_hooks(apr_pool_t *pconf)
ap_register_input_filter("IncomingRewrite", dav_svn__location_in_filter,
NULL, AP_FTYPE_CONTENT_SET);
ap_hook_fixups(dav_svn__proxy_request_fixup, NULL, NULL, APR_HOOK_MIDDLE);
+ /* translate_name hook is LAST so that it doesn't interfere with modules
+ * like mod_alias that are MIDDLE. */
+ ap_hook_translate_name(dav_svn__translate_name, NULL, NULL, APR_HOOK_LAST);
+ /* map_to_storage hook is LAST to avoid interferring with mod_http's
+ * handling of OPTIONS and TRACE. */
+ ap_hook_map_to_storage(dav_svn__map_to_storage, NULL, NULL, APR_HOOK_LAST);
}
diff --git a/subversion/mod_dav_svn/posts/create_txn.c b/subversion/mod_dav_svn/posts/create_txn.c
index c19b123..4775749 100644
--- a/subversion/mod_dav_svn/posts/create_txn.c
+++ b/subversion/mod_dav_svn/posts/create_txn.c
@@ -42,13 +42,64 @@ dav_svn__post_create_txn(const dav_resource *resource,
request_rec *r = resource->info->r;
/* Create a Subversion repository transaction based on HEAD. */
- if ((derr = dav_svn__create_txn(resource->info->repos, &txn_name,
+ if ((derr = dav_svn__create_txn(resource->info->repos, &txn_name, NULL,
resource->pool)))
return derr;
/* Build a "201 Created" response with header that tells the
client our new transaction's name. */
- vtxn_name = apr_table_get(r->headers_in, SVN_DAV_VTXN_NAME_HEADER);
+ vtxn_name = apr_table_get(r->headers_in, SVN_DAV_VTXN_NAME_HEADER);
+ if (vtxn_name && vtxn_name[0])
+ {
+ /* If the client supplied a vtxn name then store a mapping from
+ the client name to the FS transaction name in the activity
+ database. */
+ if ((derr = dav_svn__store_activity(resource->info->repos,
+ vtxn_name, txn_name)))
+ return derr;
+ apr_table_set(r->headers_out, SVN_DAV_VTXN_NAME_HEADER, vtxn_name);
+ }
+ else
+ apr_table_set(r->headers_out, SVN_DAV_TXN_NAME_HEADER, txn_name);
+
+ r->status = HTTP_CREATED;
+
+ return NULL;
+}
+
+
+/* Respond to a "create-txn-with-props" POST request.
+ *
+ * Syntax: ( create-txn-with-props (PROPNAME PROPVAL [PROPNAME PROPVAL ...])
+ */
+dav_error *
+dav_svn__post_create_txn_with_props(const dav_resource *resource,
+ svn_skel_t *request_skel,
+ ap_filter_t *output)
+{
+ const char *txn_name;
+ const char *vtxn_name;
+ dav_error *derr;
+ svn_error_t *err;
+ request_rec *r = resource->info->r;
+ apr_hash_t *revprops;
+ svn_skel_t *proplist_skel = request_skel->children->next;
+
+ if ((err = svn_skel__parse_proplist(&revprops, proplist_skel,
+ resource->pool)))
+ {
+ return dav_svn__convert_err(err, HTTP_BAD_REQUEST,
+ "Malformatted request skel", resource->pool);
+ }
+
+ /* Create a Subversion repository transaction based on HEAD. */
+ if ((derr = dav_svn__create_txn(resource->info->repos, &txn_name,
+ revprops, resource->pool)))
+ return derr;
+
+ /* Build a "201 Created" response with header that tells the
+ client our new transaction's name. */
+ vtxn_name = apr_table_get(r->headers_in, SVN_DAV_VTXN_NAME_HEADER);
if (vtxn_name && vtxn_name[0])
{
/* If the client supplied a vtxn name then store a mapping from
diff --git a/subversion/mod_dav_svn/reports/deleted-rev.c b/subversion/mod_dav_svn/reports/deleted-rev.c
index dc2bccd..66d0192 100644
--- a/subversion/mod_dav_svn/reports/deleted-rev.c
+++ b/subversion/mod_dav_svn/reports/deleted-rev.c
@@ -56,6 +56,9 @@ dav_svn__get_deleted_rev_report(const dav_resource *resource,
dav_error *derr = NULL;
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
if (ns == -1)
return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
diff --git a/subversion/mod_dav_svn/reports/file-revs.c b/subversion/mod_dav_svn/reports/file-revs.c
index f8a15c1..108fd7f 100644
--- a/subversion/mod_dav_svn/reports/file-revs.c
+++ b/subversion/mod_dav_svn/reports/file-revs.c
@@ -52,6 +52,9 @@ struct file_rev_baton {
/* SVNDIFF version to use when sending to client. */
int svndiff_version;
+ /* Compression level to use for SVNDIFF. */
+ int compression_level;
+
/* Used by the delta iwndow handler. */
svn_txdelta_window_handler_t window_handler;
void *window_baton;
@@ -208,7 +211,7 @@ file_rev_handler(void *baton,
pool);
svn_txdelta_to_svndiff3(&frb->window_handler, &frb->window_baton,
base64_stream, frb->svndiff_version,
- dav_svn__get_compression_level(), pool);
+ frb->compression_level, pool);
*window_handler = delta_window_handler;
*window_baton = frb;
/* Start the txdelta element wich will be terminated by the window
@@ -251,6 +254,9 @@ dav_svn__file_revs_report(const dav_resource *resource,
arb.repos = resource->info->repos;
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
/* ### This is done on other places, but the document element is
in this namespace, so is this necessary at all? */
@@ -306,6 +312,7 @@ dav_svn__file_revs_report(const dav_resource *resource,
frb.output = output;
frb.needs_header = TRUE;
frb.svndiff_version = resource->info->svndiff_version;
+ frb.compression_level = dav_svn__get_compression_level(resource->info->r);
/* file_rev_handler will send header first time it is called. */
diff --git a/subversion/mod_dav_svn/reports/get-location-segments.c b/subversion/mod_dav_svn/reports/get-location-segments.c
index bba0cc2..d3e91e4 100644
--- a/subversion/mod_dav_svn/reports/get-location-segments.c
+++ b/subversion/mod_dav_svn/reports/get-location-segments.c
@@ -123,6 +123,9 @@ dav_svn__get_location_segments_report(const dav_resource *resource,
struct location_segment_baton location_segment_baton;
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
if (ns == -1)
{
@@ -178,17 +181,36 @@ dav_svn__get_location_segments_report(const dav_resource *resource,
"Not all parameters passed.",
SVN_DAV_ERROR_NAMESPACE,
SVN_DAV_ERROR_TAG);
- if (SVN_IS_VALID_REVNUM(start_rev)
- && SVN_IS_VALID_REVNUM(end_rev)
- && (end_rev > start_rev))
+
+ /* No START_REV or PEG_REVISION? We'll use HEAD. */
+ if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision))
+ {
+ svn_revnum_t youngest;
+
+ serr = svn_fs_youngest_rev(&youngest, resource->info->repos->fs,
+ resource->pool);
+ if (serr != NULL)
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Could not determine youngest revision",
+ resource->pool);
+
+ if (!SVN_IS_VALID_REVNUM(start_rev))
+ start_rev = youngest;
+ if (!SVN_IS_VALID_REVNUM(peg_revision))
+ peg_revision = youngest;
+ }
+
+ /* No END_REV? We'll use 0. */
+ if (!SVN_IS_VALID_REVNUM(end_rev))
+ end_rev = 0;
+
+ if (end_rev > start_rev)
return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
"End revision must not be younger than "
"start revision",
SVN_DAV_ERROR_NAMESPACE,
SVN_DAV_ERROR_TAG);
- if (SVN_IS_VALID_REVNUM(peg_revision)
- && SVN_IS_VALID_REVNUM(start_rev)
- && (start_rev > peg_revision))
+ if (start_rev > peg_revision)
return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
"Start revision must not be younger than "
"peg revision",
diff --git a/subversion/mod_dav_svn/reports/get-locations.c b/subversion/mod_dav_svn/reports/get-locations.c
index 8764ddb..164045f 100644
--- a/subversion/mod_dav_svn/reports/get-locations.c
+++ b/subversion/mod_dav_svn/reports/get-locations.c
@@ -106,6 +106,9 @@ dav_svn__get_locations_report(const dav_resource *resource,
sizeof(svn_revnum_t));
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
if (ns == -1)
{
diff --git a/subversion/mod_dav_svn/reports/inherited-props.c b/subversion/mod_dav_svn/reports/inherited-props.c
new file mode 100644
index 0000000..ce0f4de
--- /dev/null
+++ b/subversion/mod_dav_svn/reports/inherited-props.c
@@ -0,0 +1,236 @@
+/*
+ * inherited-props.c: mod_dav_svn REPORT handler for querying inherited props.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include <apr_xml.h>
+
+#include <http_request.h>
+#include <http_log.h>
+#include <mod_dav.h>
+
+#include "svn_pools.h"
+#include "svn_repos.h"
+#include "svn_xml.h"
+#include "svn_path.h"
+#include "svn_dav.h"
+#include "svn_props.h"
+#include "svn_base64.h"
+
+#include "private/svn_fspath.h"
+#include "private/svn_dav_protocol.h"
+#include "private/svn_log.h"
+#include "private/svn_mergeinfo_private.h"
+
+#include "../dav_svn.h"
+
+dav_error *
+dav_svn__get_inherited_props_report(const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output)
+{
+ svn_error_t *serr;
+ dav_error *derr = NULL;
+ apr_xml_elem *child;
+ apr_array_header_t *inherited_props;
+ dav_svn__authz_read_baton arb;
+ int ns;
+ apr_bucket_brigade *bb;
+ const char *path = "/";
+ svn_fs_root_t *root;
+ int i;
+ svn_revnum_t rev = SVN_INVALID_REVNUM;
+ apr_pool_t *iterpool;
+
+ /* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
+ ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
+ if (ns == -1)
+ {
+ return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not contain the 'svn:' "
+ "namespace, so it is not going to have "
+ "certain required elements.",
+ SVN_DAV_ERROR_NAMESPACE,
+ SVN_DAV_ERROR_TAG);
+ }
+
+ iterpool = svn_pool_create(resource->pool);
+
+ for (child = doc->root->first_child;
+ child != NULL;
+ child = child->next)
+ {
+ /* if this element isn't one of ours, then skip it */
+ if (child->ns != ns)
+ continue;
+
+ if (strcmp(child->name, SVN_DAV__REVISION) == 0)
+ {
+ rev = SVN_STR_TO_REV(dav_xml_get_cdata(child, iterpool, 1));
+ }
+ else if (strcmp(child->name, SVN_DAV__PATH) == 0)
+ {
+ path = dav_xml_get_cdata(child, resource->pool, 0);
+ if ((derr = dav_svn__test_canonical(path, iterpool)))
+ return derr;
+ path = svn_fspath__join(resource->info->repos_path, path,
+ resource->pool);
+ }
+ /* else unknown element; skip it */
+ }
+
+ /* Build authz read baton */
+ arb.r = resource->info->r;
+ arb.repos = resource->info->repos;
+
+ /* Build inherited property brigade */
+ bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
+
+ serr = svn_fs_revision_root(&root, resource->info->repos->fs,
+ rev, resource->pool);
+ if (serr != NULL)
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "couldn't retrieve revision root",
+ resource->pool);
+
+ serr = svn_repos_fs_get_inherited_props(&inherited_props, root, path, NULL,
+ dav_svn__authz_read_func(&arb),
+ &arb, resource->pool, iterpool);
+ if (serr)
+ {
+ derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST, serr->message,
+ resource->pool);
+ goto cleanup;
+ }
+
+ serr = dav_svn__brigade_puts(bb, output,
+ DAV_XML_HEADER DEBUG_CR
+ "<S:" SVN_DAV__INHERITED_PROPS_REPORT " "
+ "xmlns:S=\"" SVN_XML_NAMESPACE "\" "
+ "xmlns:D=\"DAV:\">" DEBUG_CR);
+ if (serr)
+ {
+ derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST, serr->message,
+ resource->pool);
+ goto cleanup;
+ }
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_ITEM ">"
+ DEBUG_CR
+ "<S:" SVN_DAV__IPROP_PATH ">%s</S:" SVN_DAV__IPROP_PATH ">"
+ DEBUG_CR,
+ apr_xml_quote_string(resource->pool, elt->path_or_url, 0));
+
+ if (!serr)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(resource->pool, elt->prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ svn_string_t *propval = svn__apr_hash_index_val(hi);
+ const char *xml_safe;
+
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_PROPNAME ">%s</S:"
+ SVN_DAV__IPROP_PROPNAME ">" DEBUG_CR,
+ apr_xml_quote_string(iterpool, propname, 0));
+
+ if (!serr)
+ {
+ if (svn_xml_is_xml_safe(propval->data, propval->len))
+ {
+ svn_stringbuf_t *tmp = NULL;
+ svn_xml_escape_cdata_string(&tmp, propval,
+ iterpool);
+ xml_safe = tmp->data;
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_PROPVAL ">%s</S:"
+ SVN_DAV__IPROP_PROPVAL ">" DEBUG_CR, xml_safe);
+ }
+ else
+ {
+ xml_safe = svn_base64_encode_string2(
+ propval, TRUE, iterpool)->data;
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_PROPVAL
+ " encoding=\"base64\"" ">%s</S:"
+ SVN_DAV__IPROP_PROPVAL ">" DEBUG_CR, xml_safe);
+ }
+ }
+
+ if (serr)
+ break;
+ }
+ if (!serr)
+ serr = dav_svn__brigade_printf(bb, output,
+ "</S:" SVN_DAV__IPROP_ITEM ">"
+ DEBUG_CR);
+ }
+
+ if (serr)
+ {
+ derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Error ending REPORT response.",
+ resource->pool);
+ goto cleanup;
+ }
+ }
+
+ if ((serr = dav_svn__brigade_puts(bb, output,
+ "</S:" SVN_DAV__INHERITED_PROPS_REPORT ">"
+ DEBUG_CR)))
+ {
+ derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Error ending REPORT response.",
+ resource->pool);
+ goto cleanup;
+ }
+
+ cleanup:
+
+ /* Log this 'high level' svn action. */
+ dav_svn__operational_log(resource->info,
+ svn_log__get_inherited_props(path, rev,
+ resource->pool));
+ svn_pool_destroy(iterpool);
+ return dav_svn__final_flush_or_error(resource->info->r, bb, output,
+ derr, resource->pool);
+}
diff --git a/subversion/mod_dav_svn/reports/log.c b/subversion/mod_dav_svn/reports/log.c
index 8a5fd6e..acd33ed 100644
--- a/subversion/mod_dav_svn/reports/log.c
+++ b/subversion/mod_dav_svn/reports/log.c
@@ -307,6 +307,9 @@ dav_svn__log_report(const dav_resource *resource,
= apr_array_make(resource->pool, 1, sizeof(const char *));
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
if (ns == -1)
{
@@ -341,10 +344,9 @@ dav_svn__log_report(const dav_resource *resource,
dav_xml_get_cdata(child, resource->pool, 1));
if (serr)
{
- derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST,
+ return dav_svn__convert_err(serr, HTTP_BAD_REQUEST,
"Malformed CDATA in element "
"\"limit\"", resource->pool);
- goto cleanup;
}
}
else if (strcmp(child->name, "discover-changed-paths") == 0)
diff --git a/subversion/mod_dav_svn/reports/mergeinfo.c b/subversion/mod_dav_svn/reports/mergeinfo.c
index 79eb86c..15c3071 100644
--- a/subversion/mod_dav_svn/reports/mergeinfo.c
+++ b/subversion/mod_dav_svn/reports/mergeinfo.c
@@ -67,6 +67,9 @@ dav_svn__get_mergeinfo_report(const dav_resource *resource,
= apr_array_make(resource->pool, 0, sizeof(const char *));
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
if (ns == -1)
{
diff --git a/subversion/mod_dav_svn/reports/replay.c b/subversion/mod_dav_svn/reports/replay.c
index 7679ee3..0d57f32 100644
--- a/subversion/mod_dav_svn/reports/replay.c
+++ b/subversion/mod_dav_svn/reports/replay.c
@@ -47,6 +47,7 @@ typedef struct edit_baton_t {
ap_filter_t *output;
svn_boolean_t started;
svn_boolean_t sending_textdelta;
+ int compression_level;
} edit_baton_t;
@@ -326,7 +327,7 @@ apply_textdelta(void *file_baton,
eb->output,
pool),
0,
- dav_svn__get_compression_level(),
+ eb->compression_level,
pool);
eb->sending_textdelta = TRUE;
@@ -367,6 +368,7 @@ make_editor(const svn_delta_editor_t **editor,
void **edit_baton,
apr_bucket_brigade *bb,
ap_filter_t *output,
+ int compression_level,
apr_pool_t *pool)
{
edit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb));
@@ -376,6 +378,7 @@ make_editor(const svn_delta_editor_t **editor,
eb->output = output;
eb->started = FALSE;
eb->sending_textdelta = FALSE;
+ eb->compression_level = compression_level;
e->set_target_revision = set_target_revision;
e->open_root = open_root;
@@ -415,11 +418,11 @@ dav_svn__replay_report(const dav_resource *resource,
{
dav_error *derr = NULL;
svn_revnum_t low_water_mark = SVN_INVALID_REVNUM;
- svn_revnum_t rev = SVN_INVALID_REVNUM;
+ svn_revnum_t rev;
const svn_delta_editor_t *editor;
svn_boolean_t send_deltas = TRUE;
dav_svn__authz_read_baton arb;
- const char *base_dir = resource->info->repos_path;
+ const char *base_dir;
apr_bucket_brigade *bb;
apr_xml_elem *child;
svn_fs_root_t *root;
@@ -427,9 +430,26 @@ dav_svn__replay_report(const dav_resource *resource,
void *edit_baton;
int ns;
- /* The request won't have a repos_path if it's for the root. */
- if (! base_dir)
- base_dir = "";
+ /* In Subversion 1.8, we allowed this REPORT to be issue against a
+ revision resource. Doing so means the REV is part of the request
+ URL, and BASE_DIR is embedded in the request body.
+
+ The old-school (and incorrect, see issue #4287 --
+ http://subversion.tigris.org/issues/show_bug.cgi?id=4287) way was
+ to REPORT on the public URL of the BASE_DIR and embed the REV in
+ the report body.
+ */
+ if (resource->baselined
+ && (resource->type == DAV_RESOURCE_TYPE_VERSION))
+ {
+ rev = resource->info->root.rev;
+ base_dir = NULL;
+ }
+ else
+ {
+ rev = SVN_INVALID_REVNUM;
+ base_dir = resource->info->repos_path;
+ }
arb.r = resource->info->r;
arb.repos = resource->info->repos;
@@ -452,9 +472,17 @@ dav_svn__replay_report(const dav_resource *resource,
if (strcmp(child->name, "revision") == 0)
{
+ if (SVN_IS_VALID_REVNUM(rev))
+ {
+ /* Uh... we already have a revision to use, either
+ because this tag is non-unique or because the
+ request was submitted against a revision-bearing
+ resource URL. Either way, something's not
+ right. */
+ return malformed_element_error("revision", resource->pool);
+ }
+
cdata = dav_xml_get_cdata(child, resource->pool, 1);
- if (! cdata)
- return malformed_element_error("revision", resource->pool);
rev = SVN_STR_TO_REV(cdata);
}
else if (strcmp(child->name, "low-water-mark") == 0)
@@ -478,7 +506,16 @@ dav_svn__replay_report(const dav_resource *resource,
svn_error_clear(err);
return malformed_element_error("send-deltas", resource->pool);
}
- send_deltas = parsed_val ? TRUE : FALSE;
+ send_deltas = parsed_val != 0;
+ }
+ else if (strcmp(child->name, "include-path") == 0)
+ {
+ cdata = dav_xml_get_cdata(child, resource->pool, 1);
+ if ((derr = dav_svn__test_canonical(cdata, resource->pool)))
+ return derr;
+
+ /* Force BASE_DIR to be a relative path, not an fspath. */
+ base_dir = svn_relpath_canonicalize(cdata, resource->pool);
}
}
}
@@ -495,6 +532,9 @@ dav_svn__replay_report(const dav_resource *resource,
"Request was missing the low-water-mark argument.",
SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG);
+ if (! base_dir)
+ base_dir = "";
+
bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
if ((err = svn_fs_revision_root(&root, resource->info->repos->fs, rev,
@@ -506,7 +546,9 @@ dav_svn__replay_report(const dav_resource *resource,
goto cleanup;
}
- make_editor(&editor, &edit_baton, bb, output, resource->pool);
+ make_editor(&editor, &edit_baton, bb, output,
+ dav_svn__get_compression_level(resource->info->r),
+ resource->pool);
if ((err = svn_repos_replay2(root, base_dir, low_water_mark,
send_deltas, editor, edit_baton,
diff --git a/subversion/mod_dav_svn/reports/update.c b/subversion/mod_dav_svn/reports/update.c
index 0154325..541d551 100644
--- a/subversion/mod_dav_svn/reports/update.c
+++ b/subversion/mod_dav_svn/reports/update.c
@@ -29,6 +29,7 @@
#include <http_log.h>
#include <mod_dav.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_repos.h"
#include "svn_fs.h"
@@ -45,6 +46,7 @@
#include "../dav_svn.h"
+/* State baton for the overall update process. */
typedef struct update_ctx_t {
const dav_resource *resource;
@@ -80,26 +82,58 @@ typedef struct update_ctx_t {
/* True iff client requested all data inline in the report. */
svn_boolean_t send_all;
+ /* True iff client requested that properties be transmitted
+ inline. (This is implied when "send_all" is set.) */
+ svn_boolean_t include_props;
+
/* SVNDIFF version to send to client. */
int svndiff_version;
+
+ /* Compression level of SVNDIFF deltas. */
+ int compression_level;
+
+ /* Did the client submit this REPORT request via the HTTPv2 "me
+ resource" and are we advertising support for as much? */
+ svn_boolean_t enable_v2_response;
+
} update_ctx_t;
+
+/* State baton for a file or directory. */
typedef struct item_baton_t {
apr_pool_t *pool;
update_ctx_t *uc;
- struct item_baton_t *parent; /* the parent of this item. */
- const char *name; /* the single-component name of this item */
- const char *path; /* a telescoping extension of uc->anchor */
- const char *path2; /* a telescoping extension of uc->dst_path */
- const char *path3; /* a telescoping extension of uc->dst_path
- without dst_path as prefix. */
- const char *base_checksum; /* base_checksum (from apply_textdelta) */
+ /* Uplink -- the parent of this item. */
+ struct item_baton_t *parent;
+
+ /* Single-component name of this item. */
+ const char *name;
+
+ /* Telescoping extension paths ... */
+ const char *path; /* ... of uc->anchor. */
+ const char *path2; /* ... of uc->dst_path. */
+ const char *path3; /* ... uc->dst_path, without dst_path prefix. */
- svn_boolean_t text_changed; /* Did the file's contents change? */
- svn_boolean_t added; /* File added? (Implies text_changed.) */
- svn_boolean_t copyfrom; /* File copied? */
- apr_array_header_t *removed_props; /* array of const char * prop names */
+ /* Base_checksum (from apply_textdelta). */
+ const char *base_checksum;
+
+ /* Did the file's contents change? */
+ svn_boolean_t text_changed;
+
+ /* File/dir added? (Implies text_changed for files.) */
+ svn_boolean_t added;
+
+ /* File/dir copied? */
+ svn_boolean_t copyfrom;
+
+ /* Does the client need to fetch additional properties for this
+ item? */
+ svn_boolean_t fetch_props;
+
+ /* Array of const char * names of removed properties. (Used only
+ for copied files/dirs in skelta mode.) */
+ apr_array_header_t *removed_props;
} item_baton_t;
@@ -120,7 +154,7 @@ add_to_path_map(apr_hash_t *hash, const char *path, const char *linkpath)
const char *repos_path = linkpath ? linkpath : norm_path;
/* now, geez, put the path in the map already! */
- apr_hash_set(hash, path, APR_HASH_KEY_STRING, repos_path);
+ svn_hash_sets(hash, path, repos_path);
}
@@ -136,7 +170,7 @@ get_from_path_map(apr_hash_t *hash, const char *path, apr_pool_t *pool)
if (! hash)
return apr_pstrdup(pool, path);
- if ((repos_path = apr_hash_get(hash, path, APR_HASH_KEY_STRING)))
+ if ((repos_path = svn_hash_gets(hash, path)))
{
/* what luck! this path is a hash key! if there is a linkpath,
use that, else return the path itself. */
@@ -219,9 +253,18 @@ send_vsn_url(item_baton_t *baton, apr_pool_t *pool)
path = get_real_fs_path(baton, pool);
revision = dav_svn__get_safe_cr(baton->uc->rev_root, path, pool);
- href = dav_svn__build_uri(baton->uc->resource->info->repos,
- DAV_SVN__BUILD_URI_VERSION,
- revision, path, 0 /* add_href */, pool);
+ if (baton->uc->enable_v2_response)
+ {
+ href = dav_svn__build_uri(baton->uc->resource->info->repos,
+ DAV_SVN__BUILD_URI_REVROOT,
+ revision, path, 0 /* add_href */, pool);
+ }
+ else
+ {
+ href = dav_svn__build_uri(baton->uc->resource->info->repos,
+ DAV_SVN__BUILD_URI_VERSION,
+ revision, path, 0 /* add_href */, pool);
+ }
return dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
"<D:checked-in><D:href>%s</D:href>"
@@ -401,7 +444,7 @@ open_helper(svn_boolean_t is_dir,
static svn_error_t *
-close_helper(svn_boolean_t is_dir, item_baton_t *baton)
+close_helper(svn_boolean_t is_dir, item_baton_t *baton, apr_pool_t *pool)
{
if (baton->uc->resource_walk)
return SVN_NO_ERROR;
@@ -415,14 +458,21 @@ close_helper(svn_boolean_t is_dir, item_baton_t *baton)
for (i = 0; i < baton->removed_props->nelts; i++)
{
- /* We already XML-escaped the property name in change_xxx_prop. */
qname = APR_ARRAY_IDX(baton->removed_props, i, const char *);
+ qname = apr_xml_quote_string(pool, qname, 1);
SVN_ERR(dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
"<S:remove-prop name=\"%s\"/>"
DEBUG_CR, qname));
}
}
+ /* If our client need to fetch properties, let it know. */
+ if (baton->fetch_props)
+ SVN_ERR(dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
+ "<S:fetch-props/>" DEBUG_CR));
+
+
+ /* Let's tie it off, nurse. */
if (baton->added)
SVN_ERR(dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
"</S:add-%s>" DEBUG_CR,
@@ -442,13 +492,13 @@ maybe_start_update_report(update_ctx_t *uc)
{
if ((! uc->resource_walk) && (! uc->started_update))
{
- SVN_ERR(dav_svn__brigade_printf(uc->bb, uc->output,
- DAV_XML_HEADER DEBUG_CR
- "<S:update-report xmlns:S=\""
- SVN_XML_NAMESPACE "\" "
- "xmlns:V=\"" SVN_DAV_PROP_NS_DAV "\" "
- "xmlns:D=\"DAV:\" %s>" DEBUG_CR,
- uc->send_all ? "send-all=\"true\"" : ""));
+ SVN_ERR(dav_svn__brigade_printf(
+ uc->bb, uc->output,
+ DAV_XML_HEADER DEBUG_CR "<S:update-report xmlns:S=\""
+ SVN_XML_NAMESPACE "\" xmlns:V=\"" SVN_DAV_PROP_NS_DAV "\" "
+ "xmlns:D=\"DAV:\" %s %s>" DEBUG_CR,
+ uc->send_all ? "send-all=\"true\"" : "",
+ uc->include_props ? "inline-props=\"true\"" : ""));
uc->started_update = TRUE;
}
@@ -551,10 +601,10 @@ upd_add_directory(const char *path,
static svn_error_t *
upd_open_directory(const char *path,
- void *parent_baton,
- svn_revnum_t base_revision,
- apr_pool_t *pool,
- void **child_baton)
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
{
return open_helper(TRUE /* is_dir */,
path, parent_baton, base_revision, pool, child_baton);
@@ -562,79 +612,115 @@ upd_open_directory(const char *path,
static svn_error_t *
+send_propchange(item_baton_t *b,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ const char *qname;
+
+ /* Ensure that the property name is XML-safe. */
+ qname = apr_xml_quote_string(pool, name, 1);
+
+ if (value)
+ {
+ const char *qval;
+
+ if (svn_xml_is_xml_safe(value->data, value->len))
+ {
+ svn_stringbuf_t *tmp = NULL;
+ svn_xml_escape_cdata_string(&tmp, value, pool);
+ qval = tmp->data;
+ SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
+ "<S:set-prop name=\"%s\">",
+ qname));
+ }
+ else
+ {
+ qval = svn_base64_encode_string2(value, TRUE, pool)->data;
+ SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
+ "<S:set-prop name=\"%s\" "
+ "encoding=\"base64\">" DEBUG_CR,
+ qname));
+ }
+
+ SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output, qval));
+ SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output,
+ "</S:set-prop>" DEBUG_CR));
+ }
+ else /* value is null, so this is a prop removal */
+ {
+ SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
+ "<S:remove-prop name=\"%s\"/>"
+ DEBUG_CR,
+ qname));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
upd_change_xxx_prop(void *baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
item_baton_t *b = baton;
- const char *qname;
/* Resource walks say nothing about props. */
if (b->uc->resource_walk)
return SVN_NO_ERROR;
- /* Else this not a resource walk, so either send props or cache them
- to send later, depending on whether this is a modern report
- response or not. */
-
- qname = apr_xml_quote_string(b->pool, name, 1);
-
- /* apr_xml_quote_string doesn't realloc if there is nothing to
- quote, so dup the name, but only if necessary. */
- if (qname == name)
- qname = apr_pstrdup(b->pool, name);
+ /* If we get here, this not a resource walk, so either send props or
+ cache them to send later, depending on whether this is a modern
+ report response or not. */
/* Even if we are not in send-all mode we have the prop changes already,
so send them to the client now instead of telling the client to fetch
them later. */
- if (b->uc->send_all || !b->added)
+ if (b->uc->send_all)
{
- if (value)
+ SVN_ERR(send_propchange(b, name, value, pool));
+ }
+ else
+ {
+ if (b->added)
{
- const char *qval;
-
- if (svn_xml_is_xml_safe(value->data, value->len))
+ /* This is an addition in "skelta" (that is, "not send-all")
+ mode so there is no strict need for an inline response.
+ Clients will assume that added objects need all to have
+ all their properties explicitly fetched from the
+ server. */
+
+ /* That said, beginning in Subversion 1.8, clients might
+ request even in skelta mode that we transmit properties
+ on added files and directories explicitly. */
+ if (value && b->uc->include_props)
{
- svn_stringbuf_t *tmp = NULL;
- svn_xml_escape_cdata_string(&tmp, value, pool);
- qval = tmp->data;
- SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
- "<S:set-prop name=\"%s\">",
- qname));
+ SVN_ERR(send_propchange(b, name, value, pool));
}
- else
+
+ /* Now, if the object is actually a copy and this is a
+ property removal, we'll still need to cache (and later
+ transmit) property removals, because fetching the
+ object's current property set alone isn't sufficient to
+ communicate the fact that additional properties were, in
+ fact, removed from the copied base object in order to
+ arrive at that set. */
+ if (b->copyfrom && (! value))
{
- qval = svn_base64_encode_string2(value, TRUE, pool)->data;
- SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
- "<S:set-prop name=\"%s\" "
- "encoding=\"base64\">" DEBUG_CR,
- qname));
- }
+ if (! b->removed_props)
+ b->removed_props = apr_array_make(b->pool, 1, sizeof(name));
- SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output, qval));
- SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output,
- "</S:set-prop>" DEBUG_CR));
+ APR_ARRAY_PUSH(b->removed_props, const char *) = name;
+ }
}
- else /* value is null, so this is a prop removal */
+ else
{
- SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
- "<S:remove-prop name=\"%s\"/>"
- DEBUG_CR,
- qname));
+ /* "skelta" mode non-addition. Just send the change. */
+ SVN_ERR(send_propchange(b, name, value, pool));
}
}
- else if (!value) /* This is an addition in 'skelta' mode so there is no
- need for an inline response since property fetching
- is implied in addition. We still need to cache
- property removals because a copied path might
- have removed properties. */
- {
- if (! b->removed_props)
- b->removed_props = apr_array_make(b->pool, 1, sizeof(name));
-
- APR_ARRAY_PUSH(b->removed_props, const char *) = qname;
- }
return SVN_NO_ERROR;
}
@@ -643,7 +729,7 @@ upd_change_xxx_prop(void *baton,
static svn_error_t *
upd_close_directory(void *dir_baton, apr_pool_t *pool)
{
- return close_helper(TRUE /* is_dir */, dir_baton);
+ return close_helper(TRUE /* is_dir */, dir_baton, pool);
}
@@ -720,18 +806,6 @@ window_handler(svn_txdelta_window_t *window, void *baton)
return SVN_NO_ERROR;
}
-
-/* This implements 'svn_txdelta_window_handler_t'.
- During a resource walk, the driver sends an empty window as a
- boolean indicating that a change happened to this file, but we
- don't want to send anything over the wire as a result. */
-static svn_error_t *
-dummy_window_handler(svn_txdelta_window_t *window, void *baton)
-{
- return SVN_NO_ERROR;
-}
-
-
static svn_error_t *
upd_apply_textdelta(void *file_baton,
const char *base_checksum,
@@ -751,7 +825,7 @@ upd_apply_textdelta(void *file_baton,
we don't actually want to transmit text-deltas. */
if (file->uc->resource_walk || (! file->uc->send_all))
{
- *handler = dummy_window_handler;
+ *handler = svn_delta_noop_window_handler;
*handler_baton = NULL;
return SVN_NO_ERROR;
}
@@ -766,7 +840,7 @@ upd_apply_textdelta(void *file_baton,
svn_txdelta_to_svndiff3(&(wb->handler), &(wb->handler_baton),
base64_stream, file->uc->svndiff_version,
- dav_svn__get_compression_level(), file->pool);
+ file->uc->compression_level, file->pool);
*handler = window_handler;
*handler_baton = wb;
@@ -816,7 +890,7 @@ upd_close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool)
text_checksum));
}
- return close_helper(FALSE /* is_dir */, file);
+ return close_helper(FALSE /* is_dir */, file, pool);
}
@@ -845,6 +919,49 @@ malformed_element_error(const char *tagname, apr_pool_t *pool)
}
+/* Validate that REVISION is a valid revision number for repository in
+ which YOUNGEST is the latest revision. Use RESOURCE as a
+ convenient way to access the request record and a pool for error
+ messaging. (It's okay if REVISION is SVN_INVALID_REVNUM, as in
+ the related contexts that just means "the youngest revision".)
+
+ REVTYPE is just a string describing the type/purpose of REVISION,
+ used in the generated error string. */
+static dav_error *
+validate_input_revision(svn_revnum_t revision,
+ svn_revnum_t youngest,
+ const char *revtype,
+ const dav_resource *resource)
+{
+ if (! SVN_IS_VALID_REVNUM(revision))
+ return SVN_NO_ERROR;
+
+ if (revision > youngest)
+ {
+ svn_error_t *serr;
+
+ if (dav_svn__get_master_uri(resource->info->r))
+ {
+ serr = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ "No such %s '%ld' found in the repository. "
+ "Perhaps the repository is out of date with "
+ "respect to the master repository?",
+ revtype, revision);
+ }
+ else
+ {
+ serr = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ "No such %s '%ld' found in the repository.",
+ revtype, revision);
+ }
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Invalid revision found in update report "
+ "request.", resource->pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+
dav_error *
dav_svn__update_report(const dav_resource *resource,
const apr_xml_doc *doc,
@@ -854,8 +971,7 @@ dav_svn__update_report(const dav_resource *resource,
apr_xml_elem *child;
void *rbaton = NULL;
update_ctx_t uc = { 0 };
- svn_revnum_t revnum = SVN_INVALID_REVNUM;
- svn_boolean_t revnum_is_head = FALSE;
+ svn_revnum_t youngest, revnum = SVN_INVALID_REVNUM;
svn_revnum_t from_revnum = SVN_INVALID_REVNUM;
int ns;
/* entry_counter and entry_is_empty are for operational logging. */
@@ -901,11 +1017,15 @@ dav_svn__update_report(const dav_resource *resource,
SVN_DAV_ERROR_TAG);
}
- /* If server configuration permits bulk updates (a report with props
- and textdeltas inline, rather than placeholder tags that tell the
- client to do further fetches), look to see if client requested as
- much. */
- if (repos->bulk_updates)
+ /* SVNAllowBulkUpdates On/Prefer: server configuration permits bulk updates
+ (a report with props and textdeltas inline, rather than placeholder tags
+ that tell the client to do further fetches), look to see if client
+ requested as much.
+
+ SVNAllowBulkUpdates Off: no bulk updates allowed, force skelta mode.
+ */
+ if (repos->bulk_updates == CONF_BULKUPD_ON ||
+ repos->bulk_updates == CONF_BULKUPD_PREFER)
{
apr_xml_attr *this_attr;
@@ -915,11 +1035,20 @@ dav_svn__update_report(const dav_resource *resource,
&& (strcmp(this_attr->value, "true") == 0))
{
uc.send_all = TRUE;
+ uc.include_props = TRUE;
break;
}
}
}
+ /* Ask the repository about its youngest revision (which we'll need
+ for some input validation later). */
+ if ((serr = svn_fs_youngest_rev(&youngest, repos->fs, resource->pool)))
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Could not determine the youngest "
+ "revision for the update process.",
+ resource->pool);
+
for (child = doc->root->first_child; child != NULL; child = child->next)
{
/* Note that child->name might not match any of the cases below.
@@ -1037,6 +1166,31 @@ dav_svn__update_report(const dav_resource *resource,
if (strcmp(cdata, "no") == 0)
text_deltas = FALSE;
}
+ if (child->ns == ns && strcmp(child->name, "include-props") == 0)
+ {
+ cdata = dav_xml_get_cdata(child, resource->pool, 1);
+ if (! *cdata)
+ return malformed_element_error(child->name, resource->pool);
+ if (strcmp(cdata, "no") != 0)
+ uc.include_props = TRUE;
+ }
+ }
+
+ /* If a target revision wasn't requested, or the requested target
+ revision was invalid, just update to HEAD as of the moment we
+ queried the youngest revision. Otherwise, at least make sure the
+ request makes sense in light of that youngest revision
+ number. */
+ if (! SVN_IS_VALID_REVNUM(revnum))
+ {
+ revnum = youngest;
+ }
+ else
+ {
+ derr = validate_input_revision(revnum, youngest, "target revision",
+ resource);
+ if (derr)
+ return derr;
}
if (!saw_depth && !saw_recursive && (requested_depth == svn_depth_unknown))
@@ -1054,25 +1208,17 @@ dav_svn__update_report(const dav_resource *resource,
SVN_DAV_ERROR_TAG);
}
- /* If a revision for this operation was not dictated to us, this
- means "update to whatever the current HEAD is now". */
- if (revnum == SVN_INVALID_REVNUM)
- {
- if ((serr = svn_fs_youngest_rev(&revnum, repos->fs, resource->pool)))
- return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
- "Could not determine the youngest "
- "revision for the update process.",
- resource->pool);
- revnum_is_head = TRUE;
- }
-
uc.svndiff_version = resource->info->svndiff_version;
+ uc.compression_level = dav_svn__get_compression_level(resource->info->r);
uc.resource = resource;
uc.output = output;
uc.anchor = src_path;
uc.target = target;
uc.bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
uc.pathmap = NULL;
+ uc.enable_v2_response = ((resource->info->restype == DAV_SVN_RESTYPE_ME)
+ && (resource->info->repos->v2_protocol));
+
if (dst_path) /* we're doing a 'switch' */
{
if (*target)
@@ -1133,7 +1279,7 @@ dav_svn__update_report(const dav_resource *resource,
editor->close_file = upd_close_file;
editor->absent_file = upd_absent_file;
editor->close_edit = upd_close_edit;
- if ((serr = svn_repos_begin_report2(&rbaton, revnum,
+ if ((serr = svn_repos_begin_report3(&rbaton, revnum,
repos->repos,
src_path, target,
dst_path,
@@ -1144,6 +1290,7 @@ dav_svn__update_report(const dav_resource *resource,
editor, &uc,
dav_svn__authz_read_func(&arb),
&arb,
+ 0, /* disable zero-copy for now */
resource->pool)))
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
@@ -1179,27 +1326,10 @@ dav_svn__update_report(const dav_resource *resource,
{
rev = SVN_STR_TO_REV(this_attr->value);
saw_rev = TRUE;
- if (revnum_is_head && rev > revnum)
- {
- if (dav_svn__get_master_uri(resource->info->r))
- return dav_svn__new_error_tag(
- resource->pool,
- HTTP_INTERNAL_SERVER_ERROR, 0,
- "A reported revision is higher than the "
- "current repository HEAD revision. "
- "Perhaps the repository is out of date "
- "with respect to the master repository?",
- SVN_DAV_ERROR_NAMESPACE,
- SVN_DAV_ERROR_TAG);
- else
- return dav_svn__new_error_tag(
- resource->pool,
- HTTP_INTERNAL_SERVER_ERROR, 0,
- "A reported revision is higher than the "
- "current repository HEAD revision.",
- SVN_DAV_ERROR_NAMESPACE,
- SVN_DAV_ERROR_TAG);
- }
+ if ((derr = validate_input_revision(rev, youngest,
+ "reported revision",
+ resource)))
+ return derr;
}
else if (strcmp(this_attr->name, "depth") == 0)
depth = svn_depth_from_word(this_attr->value);
diff --git a/subversion/mod_dav_svn/repos.c b/subversion/mod_dav_svn/repos.c
index 4407896..590cca9 100644
--- a/subversion/mod_dav_svn/repos.c
+++ b/subversion/mod_dav_svn/repos.c
@@ -37,6 +37,7 @@
#define CORE_PRIVATE /* To make ap_show_mpm public in 2.2 */
#include <http_config.h>
+#include "svn_hash.h"
#include "svn_types.h"
#include "svn_pools.h"
#include "svn_error.h"
@@ -48,6 +49,7 @@
#include "svn_version.h"
#include "svn_props.h"
#include "svn_ctype.h"
+#include "svn_subst.h"
#include "mod_dav_svn.h"
#include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
#include "svn_dirent_uri.h"
@@ -506,6 +508,9 @@ parse_vtxnstub_uri(dav_resource_combined *comb,
if (parse_txnstub_uri(comb, path, label, use_checked_in))
return TRUE;
+ if (!comb->priv.root.txn_name)
+ return TRUE;
+
comb->priv.root.vtxn_name = comb->priv.root.txn_name;
comb->priv.root.txn_name = dav_svn__get_txn(comb->priv.repos,
comb->priv.root.vtxn_name);
@@ -574,6 +579,9 @@ parse_vtxnroot_uri(dav_resource_combined *comb,
if (parse_txnroot_uri(comb, path, label, use_checked_in))
return TRUE;
+ if (!comb->priv.root.txn_name)
+ return TRUE;
+
comb->priv.root.vtxn_name = comb->priv.root.txn_name;
comb->priv.root.txn_name = dav_svn__get_txn(comb->priv.repos,
comb->priv.root.vtxn_name);
@@ -919,6 +927,10 @@ prep_working(dav_resource_combined *comb)
point. */
if (txn_name == NULL)
{
+ if (!comb->priv.root.activity_id)
+ return dav_svn__new_error(comb->res.pool, HTTP_BAD_REQUEST, 0,
+ "The request did not specify an activity ID");
+
txn_name = dav_svn__get_txn(comb->priv.repos,
comb->priv.root.activity_id);
if (txn_name == NULL)
@@ -1029,8 +1041,13 @@ prep_working(dav_resource_combined *comb)
static dav_error *
prep_activity(dav_resource_combined *comb)
{
- const char *txn_name = dav_svn__get_txn(comb->priv.repos,
- comb->priv.root.activity_id);
+ const char *txn_name;
+
+ if (!comb->priv.root.activity_id)
+ return dav_svn__new_error(comb->res.pool, HTTP_BAD_REQUEST, 0,
+ "The request did not specify an activity ID");
+
+ txn_name = dav_svn__get_txn(comb->priv.repos, comb->priv.root.activity_id);
comb->priv.root.txn_name = txn_name;
comb->res.exists = txn_name != NULL;
@@ -1149,8 +1166,11 @@ create_private_resource(const dav_resource *base,
comb->res.collection = TRUE; /* ### always true? */
/* versioned = baselined = working = FALSE */
- comb->res.uri = apr_pstrcat(base->pool, base->info->repos->root_path,
- path->data, (char *)NULL);
+ if (base->info->repos->root_path[1])
+ comb->res.uri = apr_pstrcat(base->pool, base->info->repos->root_path,
+ path->data, (char *)NULL);
+ else
+ comb->res.uri = path->data;
comb->res.info = &comb->priv;
comb->res.hooks = &dav_svn__hooks_repository;
comb->res.pool = base->pool;
@@ -1499,7 +1519,7 @@ get_parentpath_resource(request_rec *r,
repos->xslt_uri = dav_svn__get_xslt_uri(r);
repos->autoversioning = dav_svn__get_autoversioning_flag(r);
repos->bulk_updates = dav_svn__get_bulk_updates_flag(r);
- repos->v2_protocol = dav_svn__get_v2_protocol_flag(r);
+ repos->v2_protocol = dav_svn__check_httpv2_support(r);
repos->base_url = ap_construct_url(r->pool, "", r);
repos->special_uri = dav_svn__get_special_uri(r);
repos->username = r->user;
@@ -1573,7 +1593,7 @@ static const char *get_entry(apr_pool_t *p, accept_rec *result,
for (cp = parm; (*cp && !svn_ctype_isspace(*cp) && *cp != '='); ++cp)
{
- *cp = apr_tolower(*cp);
+ *cp = (char)apr_tolower(*cp);
}
if (!*cp)
@@ -1785,14 +1805,97 @@ do_out_of_date_check(dav_resource_combined *comb, request_rec *r)
"Could not get created rev of "
"resource", r->pool);
- if (comb->priv.version_name < created_rev)
+ if (SVN_IS_VALID_REVNUM(created_rev))
{
- serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL,
- "Item '%s' is out of date",
- comb->priv.repos_path);
- return dav_svn__convert_err(serr, HTTP_CONFLICT,
- "Attempting to modify out-of-date resource.",
- r->pool);
+ if (comb->priv.version_name < created_rev)
+ {
+ serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL,
+ comb->res.collection
+ ? "Directory '%s' is out of date"
+ : (comb->res.exists
+ ? "File '%s' is out of date"
+ : "'%s' is out of date"),
+ comb->priv.repos_path);
+ return dav_svn__convert_err(serr, HTTP_CONFLICT,
+ "Attempting to modify out-of-date resource.",
+ r->pool);
+ }
+ }
+ else if (SVN_IS_VALID_REVNUM(comb->priv.version_name)
+ && comb->res.collection)
+ {
+ /* Issue #4480: With HTTPv2 we can receive the first change for a
+ directory after it has been made mutable, because one of its
+ descendants was changed before changing the directory.
+
+ We have to check if whatever the node is in HEAD is equivalent
+ to what it was in the provided BASE revision.
+
+ If the node was copied, we would process it before its decendants
+ and we already performed quite a few checks when making it mutable
+ via its descendant, so what we should really check here is if the
+ properties changed since the BASE version.
+
+ ### I think svn_fs_node_relation() checks for more changes than we
+ should check for here. Needs further review. But it looks like\
+ this check matches the checks in the libsvn_fs commit editor.
+
+ For now I would say reporting out of date in a few too many
+ cases is safer than not reporting out of date when we should.
+ */
+ svn_revnum_t txn_base_rev;
+ svn_fs_root_t *txn_base_root;
+ svn_fs_root_t *rev_root;
+ const svn_fs_id_t *txn_base_id;
+ const svn_fs_id_t *rev_id;
+
+ txn_base_rev = svn_fs_txn_base_revision(comb->res.info->root.txn);
+
+ if (comb->priv.version_name == txn_base_rev)
+ return NULL; /* Easy out: Nothing changed */
+
+ serr = svn_fs_revision_root(&txn_base_root, comb->res.info->repos->fs,
+ txn_base_rev, r->pool);
+
+ if (!serr)
+ serr = svn_fs_node_id(&txn_base_id, txn_base_root,
+ comb->priv.repos_path, r->pool);
+
+ if (serr != NULL)
+ {
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Could not open youngest revision root "
+ "for verification against the base "
+ "revision", r->pool);
+ }
+
+ serr = svn_fs_revision_root(&rev_root, comb->res.info->repos->fs,
+ comb->priv.version_name, r->pool);
+
+ if (!serr)
+ serr = svn_fs_node_id(&rev_id, rev_root,
+ comb->priv.repos_path, r->pool);
+
+ if (serr != NULL)
+ {
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Could not open the base revision"
+ "for verification against the youngest "
+ "revision", r->pool);
+ }
+
+ svn_fs_close_root(rev_root);
+ svn_fs_close_root(txn_base_root);
+
+ if (0 != svn_fs_compare_ids(txn_base_id, rev_id))
+ {
+ serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL,
+ "Directory '%s' is out of date",
+ comb->priv.repos_path);
+ return dav_svn__convert_err(serr, HTTP_CONFLICT,
+ "Attempting to modify out-of-date resource.",
+ r->pool);
+ }
}
return NULL;
@@ -1817,9 +1920,17 @@ parse_querystring(request_rec *r, const char *query,
apr_table_t *pairs = querystring_to_table(query, pool);
const char *prevstr = apr_table_get(pairs, "p");
const char *wrevstr;
+ const char *keyword_subst;
+
+ /* Will we be doing keyword substitution? */
+ keyword_subst = apr_table_get(pairs, "kw");
+ if (keyword_subst && (strcmp(keyword_subst, "1") == 0))
+ comb->priv.keyword_subst = TRUE;
if (prevstr)
{
+ while (*prevstr == 'r')
+ prevstr++;
peg_rev = SVN_STR_TO_REV(prevstr);
if (!SVN_IS_VALID_REVNUM(peg_rev))
return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0,
@@ -1837,6 +1948,8 @@ parse_querystring(request_rec *r, const char *query,
wrevstr = apr_table_get(pairs, "r");
if (wrevstr)
{
+ while (*wrevstr == 'r')
+ wrevstr++;
working_rev = SVN_STR_TO_REV(wrevstr);
if (!SVN_IS_VALID_REVNUM(working_rev))
return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0,
@@ -1872,7 +1985,7 @@ parse_querystring(request_rec *r, const char *query,
}
else
{
- const char *newpath;
+ const char *newpath, *location;
apr_hash_t *locations;
apr_array_header_t *loc_revs = apr_array_make(pool, 1,
sizeof(svn_revnum_t));
@@ -1901,13 +2014,17 @@ parse_querystring(request_rec *r, const char *query,
/* Redirect folks to a canonical, peg-revision-only location.
If they used a peg revision in this request, we can use a
permanent redirect. If they didn't (peg-rev is HEAD), we can
- only use a temporary redirect. */
- apr_table_setn(r->headers_out, "Location",
- ap_construct_url(r->pool,
- apr_psprintf(r->pool, "%s%s?p=%ld",
- comb->priv.repos->root_path,
- newpath, working_rev),
- r));
+ only use a temporary redirect. In either case, preserve the
+ "keyword_subst" state in the redirected location, too. */
+ location = ap_construct_url(r->pool,
+ apr_psprintf(r->pool, "%s%s?p=%ld%s",
+ (comb->priv.repos->root_path[1]
+ ? comb->priv.repos->root_path
+ : ""),
+ newpath, working_rev,
+ keyword_subst ? "&kw=1" : ""),
+ r);
+ apr_table_setn(r->headers_out, "Location", location);
return dav_svn__new_error(r->pool,
prevstr ? HTTP_MOVED_PERMANENTLY
: HTTP_MOVED_TEMPORARILY,
@@ -1917,8 +2034,6 @@ parse_querystring(request_rec *r, const char *query,
return NULL;
}
-
-
static dav_error *
get_resource(request_rec *r,
const char *root_path,
@@ -1952,26 +2067,31 @@ get_resource(request_rec *r,
/* Special case: detect and build the SVNParentPath as a unique type
of private resource, iff the SVNListParentPath directive is 'on'. */
- if (fs_parent_path && dav_svn__get_list_parentpath_flag(r))
+ if (dav_svn__is_parentpath_list(r))
{
- char *uri = apr_pstrdup(r->pool, r->uri);
- char *parentpath = apr_pstrdup(r->pool, root_path);
- apr_size_t uri_len = strlen(uri);
- apr_size_t parentpath_len = strlen(parentpath);
-
- if (uri[uri_len-1] == '/')
- uri[uri_len-1] = '\0';
+ /* Only allow GET and HEAD on the parentpath resource
+ * httpd uses the same method_number for HEAD as GET */
+ if (r->method_number != M_GET)
+ {
+ int status;
- if (parentpath[parentpath_len-1] == '/')
- parentpath[parentpath_len-1] = '\0';
+ /* Marshall the error back to the client by generating by
+ * way of the dav_svn__error_response_tag trick. */
+ err = dav_svn__new_error(r->pool, HTTP_METHOD_NOT_ALLOWED,
+ SVN_ERR_APMOD_MALFORMED_URI,
+ "The URI does not contain the name "
+ "of a repository.");
+ /* can't use r->allowed since the default handler isn't called */
+ apr_table_setn(r->headers_out, "Allow", "GET,HEAD");
+ status = dav_svn__error_response_tag(r, err);
- if (strcmp(parentpath, uri) == 0)
- {
- err = get_parentpath_resource(r, resource);
- if (err)
- return err;
- return NULL;
+ return dav_push_error(r->pool, status, err->error_id, NULL, err);
}
+
+ err = get_parentpath_resource(r, resource);
+ if (err)
+ return err;
+ return NULL;
}
/* This does all the work of interpreting/splitting the request uri. */
@@ -2075,7 +2195,7 @@ get_resource(request_rec *r,
repos->bulk_updates = dav_svn__get_bulk_updates_flag(r);
/* Are we advertising HTTP v2 protocol support? */
- repos->v2_protocol = dav_svn__get_v2_protocol_flag(r);
+ repos->v2_protocol = dav_svn__check_httpv2_support(r);
/* Path to activities database */
repos->activities_db = dav_svn__get_activities_db(r);
@@ -2120,8 +2240,9 @@ get_resource(request_rec *r,
more than that). */
/* Start out assuming no capabilities. */
- apr_hash_set(repos->client_capabilities, SVN_RA_CAPABILITY_MERGEINFO,
- APR_HASH_KEY_STRING, capability_no);
+ svn_hash_sets(repos->client_capabilities,
+ SVN_RA_CAPABILITY_MERGEINFO,
+ capability_no);
/* Then see what we can find. */
val = apr_table_get(r->headers_in, "DAV");
@@ -2132,9 +2253,8 @@ get_resource(request_rec *r,
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
{
- apr_hash_set(repos->client_capabilities,
- SVN_RA_CAPABILITY_MERGEINFO,
- APR_HASH_KEY_STRING, capability_yes);
+ svn_hash_sets(repos->client_capabilities,
+ SVN_RA_CAPABILITY_MERGEINFO, capability_yes);
}
}
}
@@ -2150,14 +2270,12 @@ get_resource(request_rec *r,
/* construct FS configuration parameters */
fs_config = apr_hash_make(r->connection->pool);
- apr_hash_set(fs_config,
- SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
- APR_HASH_KEY_STRING,
- dav_svn__get_txdelta_cache_flag(r) ? "1" : "0");
- apr_hash_set(fs_config,
- SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
- APR_HASH_KEY_STRING,
- dav_svn__get_fulltext_cache_flag(r) ? "1" : "0");
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
+ dav_svn__get_txdelta_cache_flag(r) ? "1" :"0");
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
+ dav_svn__get_fulltext_cache_flag(r) ? "1" :"0");
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
+ dav_svn__get_revprop_cache_flag(r) ? "1" :"0");
/* Disallow BDB/event until issue 4157 is fixed. */
if (!strcmp(ap_show_mpm(), "event"))
@@ -2210,6 +2328,14 @@ get_resource(request_rec *r,
"in repos object",
HTTP_INTERNAL_SERVER_ERROR, r);
}
+
+ /* Configure hook script environment variables. */
+ serr = svn_repos_hooks_setenv(repos->repos, dav_svn__get_hooks_env(r),
+ r->pool);
+ if (serr)
+ return dav_svn__sanitize_error(serr,
+ "Error settings hooks environment",
+ HTTP_INTERNAL_SERVER_ERROR, r);
}
/* cache the filesystem object */
@@ -2385,21 +2511,12 @@ get_parent_path(const char *path,
svn_boolean_t is_urlpath,
apr_pool_t *pool)
{
- apr_size_t len;
- char *tmp = apr_pstrdup(pool, path);
-
- len = strlen(tmp);
-
- if (len > 0)
+ if (*path != '\0') /* not an empty string */
{
- /* Remove any trailing slash; else svn_path_dirname() asserts. */
- if (tmp[len-1] == '/')
- tmp[len-1] = '\0';
-
if (is_urlpath)
- return svn_urlpath__dirname(tmp, pool);
+ return svn_urlpath__dirname(path, pool);
else
- return svn_fspath__dirname(tmp, pool);
+ return svn_fspath__dirname(path, pool);
}
return path;
@@ -2435,13 +2552,18 @@ get_parent_resource(const dav_resource *resource,
parent->versioned = 1;
parent->hooks = resource->hooks;
parent->pool = resource->pool;
- parent->uri = get_parent_path(resource->uri, TRUE, resource->pool);
+ parent->uri = get_parent_path(svn_urlpath__canonicalize(resource->uri,
+ resource->pool),
+ TRUE, resource->pool);
parent->info = parentinfo;
parentinfo->uri_path =
- svn_stringbuf_create(get_parent_path(resource->info->uri_path->data,
- TRUE, resource->pool),
- resource->pool);
+ svn_stringbuf_create(
+ get_parent_path(
+ svn_urlpath__canonicalize(resource->info->uri_path->data,
+ resource->pool),
+ TRUE, resource->pool),
+ resource->pool);
parentinfo->repos = resource->info->repos;
parentinfo->root = resource->info->root;
parentinfo->r = resource->info->r;
@@ -2577,121 +2699,6 @@ is_parent_resource(const dav_resource *res1, const dav_resource *res2)
}
-#if 0
-/* Given an apache request R and a ROOT_PATH to the svn location
- block, set *KIND to the node-kind of the URI's associated
- (revision, path) pair, if possible.
-
- Public uris, baseline collections, version resources, and working
- (non-baseline) resources all have associated (revision, path)
- pairs, and thus one of {svn_node_file, svn_node_dir, svn_node_none}
- will be returned.
-
- If URI is something more abstract, then set *KIND to
- svn_node_unknown. This is true for baselines, working baselines,
- version controled configurations, activities, histories, and other
- private resources.
-*/
-static dav_error *
-resource_kind(request_rec *r,
- const char *uri,
- const char *root_path,
- svn_node_kind_t *kind)
-{
- dav_error *derr;
- svn_error_t *serr;
- dav_resource *resource;
- svn_revnum_t base_rev;
- svn_fs_root_t *base_rev_root;
- char *saved_uri;
-
- /* Temporarily insert the uri that the user actually wants us to
- convert into a resource. Typically, this is already r->uri, so
- this is usually a no-op. But sometimes the caller may pass in
- the Destination: header uri.
-
- ### WHAT WE REALLY WANT here is to refactor get_resource,
- so that some alternate interface actually allows us to specify
- the URI to process, i.e. not always process r->uri.
- */
- saved_uri = r->uri;
- r->uri = apr_pstrdup(r->pool, uri);
-
- /* parse the uri and prep the associated resource. */
- derr = get_resource(r, root_path,
- /* ### I can't believe that every single
- parser ignores the LABEL and USE_CHECKED_IN
- args below!! */
- "ignored_label", 1,
- &resource);
- /* Restore r back to normal. */
- r->uri = saved_uri;
-
- if (derr)
- return derr;
-
- if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
- {
- /* Either a public URI or a bc. In both cases, prep_regular()
- has already set the 'exists' and 'collection' flags by
- querying the appropriate revision root and path. */
- if (! resource->exists)
- *kind = svn_node_none;
- else
- *kind = resource->collection ? svn_node_dir : svn_node_file;
- }
-
- else if (resource->type == DAV_RESOURCE_TYPE_VERSION)
- {
- if (resource->baselined) /* bln */
- *kind = svn_node_unknown;
-
- else /* ver */
- {
- derr = fs_check_path(kind, resource->info->root.root,
- resource->info->repos_path, r->pool);
- if (derr != NULL)
- return derr;
- }
- }
-
- else if (resource->type == DAV_RESOURCE_TYPE_WORKING)
- {
- if (resource->baselined) /* wbl */
- *kind = svn_node_unknown;
-
- else /* wrk */
- {
- /* don't call fs_check_path on the txn, but on the original
- revision that the txn is based on. */
- base_rev = svn_fs_txn_base_revision(resource->info->root.txn);
- serr = svn_fs_revision_root(&base_rev_root,
- resource->info->repos->fs,
- base_rev, r->pool);
- if (serr)
- return dav_svn__convert_err
- (serr, HTTP_INTERNAL_SERVER_ERROR,
- apr_psprintf(r->pool,
- "Could not open root of revision %ld",
- base_rev),
- r->pool);
-
- derr = fs_check_path(kind, base_rev_root,
- resource->info->repos_path, r->pool);
- if (derr != NULL)
- return derr;
- }
- }
-
- else
- /* act, his, vcc, or some other private resource */
- *kind = svn_node_unknown;
-
- return NULL;
-}
-#endif
-
-
static dav_error *
open_stream(const dav_resource *resource,
dav_stream_mode mode,
@@ -2711,14 +2718,13 @@ open_stream(const dav_resource *resource,
}
}
-#if 1
+ /* ### TODO: Can we support range writes someday? */
if (mode == DAV_MODE_WRITE_SEEKABLE)
{
return dav_svn__new_error(resource->pool, HTTP_NOT_IMPLEMENTED, 0,
"Resource body writes cannot use ranges "
"(at this time).");
}
-#endif
/* start building the stream structure */
*stream = apr_pcalloc(resource->pool, sizeof(**stream));
@@ -3034,14 +3040,12 @@ set_headers(request_rec *r, const dav_resource *resource)
apr_table_setn(r->headers_out, "ETag",
dav_svn__getetag(resource, resource->pool));
-#if 0
/* As version resources don't change, encourage caching. */
- /* ### FIXME: This conditional is wrong -- type is often REGULAR,
- ### and the resource doesn't seem to be baselined. */
- if (resource->type == DAV_RESOURCE_TYPE_VERSION)
+ if ((resource->type == DAV_RESOURCE_TYPE_REGULAR
+ && resource->versioned && !resource->collection)
+ || resource->type == DAV_RESOURCE_TYPE_VERSION)
/* Cache resource for one week (specified in seconds). */
apr_table_setn(r->headers_out, "Cache-Control", "max-age=604800");
-#endif
/* we accept byte-ranges */
apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
@@ -3070,6 +3074,13 @@ set_headers(request_rec *r, const dav_resource *resource)
if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM))
{
mimetype = SVN_SVNDIFF_MIME_TYPE;
+
+ /* Note the base that this svndiff is based on, and tell any
+ intermediate caching proxies that this header is
+ significant. */
+ apr_table_setn(r->headers_out, "Vary", SVN_DAV_DELTA_BASE_HEADER);
+ apr_table_setn(r->headers_out, SVN_DAV_DELTA_BASE_HEADER,
+ resource->info->delta_base);
}
svn_error_clear(serr);
}
@@ -3122,19 +3133,23 @@ set_headers(request_rec *r, const dav_resource *resource)
mimetype = "text/plain";
- /* if we aren't sending a diff, then we know the length of the file,
- so set up the Content-Length header */
- serr = svn_fs_file_length(&length,
- resource->info->root.root,
- resource->info->repos_path,
- resource->pool);
- if (serr != NULL)
+ /* if we aren't sending a diff and aren't expanding keywords,
+ then we know the exact length of the file, so set up the
+ Content-Length header. */
+ if (! resource->info->keyword_subst)
{
- return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
- "could not fetch the resource length",
- resource->pool);
+ serr = svn_fs_file_length(&length,
+ resource->info->root.root,
+ resource->info->repos_path,
+ resource->pool);
+ if (serr != NULL)
+ {
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "could not fetch the resource length",
+ resource->pool);
+ }
+ ap_set_content_length(r, (apr_off_t) length);
}
- ap_set_content_length(r, (apr_off_t) length);
}
/* set the discovered MIME type */
@@ -3151,7 +3166,7 @@ typedef struct diff_ctx_t {
} diff_ctx_t;
-static svn_error_t *
+static svn_error_t * __attribute__((warn_unused_result))
write_to_filter(void *baton, const char *buffer, apr_size_t *len)
{
diff_ctx_t *dc = baton;
@@ -3172,7 +3187,7 @@ write_to_filter(void *baton, const char *buffer, apr_size_t *len)
}
-static svn_error_t *
+static svn_error_t * __attribute__((warn_unused_result))
close_filter(void *baton)
{
diff_ctx_t *dc = baton;
@@ -3285,7 +3300,7 @@ deliver(const dav_resource *resource, ap_filter_t *output)
if (dirent->kind == svn_node_file && dirent->special)
{
svn_node_kind_t resolved_kind;
- const char *link_path =
+ const char *link_path =
svn_dirent_join(fs_parent_path, key, resource->pool);
serr = svn_io_check_resolved_path(link_path, &resolved_kind,
@@ -3298,7 +3313,7 @@ deliver(const dav_resource *resource, ap_filter_t *output)
resource->pool);
if (resolved_kind != svn_node_dir)
continue;
-
+
dirent->kind = svn_node_dir;
}
else if (dirent->kind != svn_node_dir)
@@ -3308,7 +3323,7 @@ deliver(const dav_resource *resource, ap_filter_t *output)
ent->id = NULL; /* ### does it matter? */
ent->kind = dirent->kind;
- apr_hash_set(entries, key, APR_HASH_KEY_STRING, ent);
+ svn_hash_sets(entries, key, ent);
}
}
@@ -3445,9 +3460,9 @@ deliver(const dav_resource *resource, ap_filter_t *output)
}
else
{
- /* ### TODO: We could test for readability of the root
- directory of each repository and hide those that
- the user can't see. */
+ if (! dav_svn__allow_list_repos(resource->info->r,
+ entry->name, entry_pool))
+ continue;
}
/* append a trailing slash onto the name for directories. we NEED
@@ -3622,7 +3637,7 @@ deliver(const dav_resource *resource, ap_filter_t *output)
/* get a handler/baton for writing into the output stream */
svn_txdelta_to_svndiff3(&handler, &h_baton,
o_stream, resource->info->svndiff_version,
- dav_svn__get_compression_level(),
+ dav_svn__get_compression_level(resource->info->r),
resource->pool);
/* got everything set up. read in delta windows and shove them into
@@ -3660,6 +3675,77 @@ deliver(const dav_resource *resource, ap_filter_t *output)
resource->pool);
}
+ /* Perform keywords substitution if requested by client */
+ if (resource->info->keyword_subst)
+ {
+ svn_string_t *keywords;
+
+ serr = svn_fs_node_prop(&keywords,
+ resource->info->root.root,
+ resource->info->repos_path,
+ SVN_PROP_KEYWORDS,
+ resource->pool);
+ if (serr != NULL)
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "could not get fetch '"
+ SVN_PROP_KEYWORDS "' property for "
+ "for keywords substitution",
+ resource->pool);
+
+ if (keywords)
+ {
+ apr_hash_t *kw;
+ svn_revnum_t cmt_rev;
+ const char *str_cmt_rev, *str_uri, *str_root;
+ const char *cmt_date, *cmt_author;
+ apr_time_t when = 0;
+
+ serr = svn_repos_get_committed_info(&cmt_rev,
+ &cmt_date,
+ &cmt_author,
+ resource->info->root.root,
+ resource->info->repos_path,
+ resource->pool);
+ if (serr != NULL)
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "could not fetch committed info "
+ "for keywords substitution",
+ resource->pool);
+
+ serr = svn_time_from_cstring(&when, cmt_date, resource->pool);
+ if (serr != NULL)
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "could not parse committed date "
+ "for keywords substitution",
+ resource->pool);
+ str_cmt_rev = apr_psprintf(resource->pool, "%ld", cmt_rev);
+ str_uri = apr_pstrcat(resource->pool,
+ resource->info->repos->base_url,
+ ap_escape_uri(resource->pool,
+ resource->info->r->uri),
+ NULL);
+ str_root = apr_pstrcat(resource->pool,
+ resource->info->repos->base_url,
+ resource->info->repos->root_path,
+ NULL);
+
+ serr = svn_subst_build_keywords3(&kw, keywords->data,
+ str_cmt_rev, str_uri, str_root,
+ when, cmt_author,
+ resource->pool);
+ if (serr != NULL)
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "could not perform keywords "
+ "substitution", resource->pool);
+
+ /* Replace the raw file STREAM with a wrapper that
+ handles keyword translation. */
+ stream = svn_subst_stream_translated(
+ svn_stream_disown(stream, resource->pool),
+ NULL, FALSE, kw, TRUE, resource->pool);
+ }
+ }
+
/* ### one day in the future, we can create a custom bucket type
### which will read from the FS stream on demand */
@@ -4073,7 +4159,9 @@ typedef struct walker_ctx_t {
static dav_error *
-do_walk(walker_ctx_t *ctx, int depth)
+do_walk(walker_ctx_t *ctx,
+ int depth,
+ apr_pool_t *scratch_pool)
{
const dav_walk_params *params = ctx->params;
int isdir = ctx->res.collection;
@@ -4146,19 +4234,19 @@ do_walk(walker_ctx_t *ctx, int depth)
svn_log__get_dir(ctx->info.repos_path,
ctx->info.root.rev,
TRUE, FALSE, SVN_DIRENT_ALL,
- params->pool));
+ scratch_pool));
/* fetch this collection's children */
serr = svn_fs_dir_entries(&children, ctx->info.root.root,
- ctx->info.repos_path, params->pool);
+ ctx->info.repos_path, scratch_pool);
if (serr != NULL)
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not fetch collection members",
params->pool);
/* iterate over the children in this collection */
- iterpool = svn_pool_create(params->pool);
- for (hi = apr_hash_first(params->pool, children); hi; hi = apr_hash_next(hi))
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, children); hi; hi = apr_hash_next(hi))
{
const void *key;
apr_ssize_t klen;
@@ -4211,7 +4299,7 @@ do_walk(walker_ctx_t *ctx, int depth)
ctx->res.uri = ctx->uri->data;
/* recurse on this collection */
- err = do_walk(ctx, depth - 1);
+ err = do_walk(ctx, depth - 1, iterpool);
if (err != NULL)
return err;
@@ -4293,7 +4381,7 @@ walk(const dav_walk_params *params, int depth, dav_response **response)
/* ### is the root already/always open? need to verify */
/* always return the error, and any/all multistatus responses */
- err = do_walk(&ctx, depth);
+ err = do_walk(&ctx, depth, params->pool);
*response = ctx.wres.response;
return err;
@@ -4338,8 +4426,11 @@ dav_svn__create_working_resource(dav_resource *base,
res->baselined = base->baselined;
/* collection = FALSE. ### not necessarily correct */
- res->uri = apr_pstrcat(base->pool, base->info->repos->root_path,
- path, (char *)NULL);
+ if (base->info->repos->root_path[1])
+ res->uri = apr_pstrcat(base->pool, base->info->repos->root_path,
+ path, (char *)NULL);
+ else
+ res->uri = path;
res->hooks = &dav_svn__hooks_repository;
res->pool = base->pool;
@@ -4437,7 +4528,7 @@ handle_post_request(request_rec *r,
dav_resource *resource,
ap_filter_t *output)
{
- svn_skel_t *request_skel;
+ svn_skel_t *request_skel, *post_skel;
int status;
apr_pool_t *pool = resource->pool;
@@ -4454,18 +4545,51 @@ handle_post_request(request_rec *r,
return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0,
"Unable to identify skel POST request flavor.");
- if (svn_skel__matches_atom(request_skel->children, "create-txn"))
+ post_skel = request_skel->children;
+
+ /* NOTE: If you add POST handlers here, you'll want to advertise
+ that the server supports them, too. See version.c:get_option(). */
+
+ if (svn_skel__matches_atom(post_skel, "create-txn"))
{
return dav_svn__post_create_txn(resource, request_skel, output);
}
- else
+ else if (svn_skel__matches_atom(post_skel, "create-txn-with-props"))
{
- return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0,
- "Unsupported skel POST request flavor.");
+ return dav_svn__post_create_txn_with_props(resource,
+ request_skel, output);
}
- /* NOTREACHED */
+
+ return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0,
+ "Unsupported skel POST request flavor.");
}
+
+/* A stripped down version of mod_dav's dav_handle_err so that POST
+ errors, which are not passed via mod_dav, are handled in the same
+ way as errors for requests that are passed via mod_dav. */
+static int
+handle_err(request_rec *r, dav_error *err)
+{
+ dav_error *stackerr = err;
+
+ dav_svn__log_err(r, err, APLOG_ERR);
+
+ /* our error messages are safe; tell Apache this */
+ apr_table_setn(r->notes, "verbose-error-to", "*");
+
+ /* We might be able to generate a standard <D:error> response.
+ Search the error stack for an errortag. */
+ while (stackerr != NULL && stackerr->tagname == NULL)
+ stackerr = stackerr->prev;
+
+ if (stackerr != NULL && stackerr->tagname != NULL)
+ return dav_svn__error_response_tag(r, stackerr);
+
+ return err->status;
+}
+
+
int dav_svn__method_post(request_rec *r)
{
dav_resource *resource;
@@ -4498,9 +4622,8 @@ int dav_svn__method_post(request_rec *r)
if (derr)
{
/* POST is not a DAV method and so mod_dav isn't involved and
- won't log this error. Do it explicitly. */
- dav_svn__log_err(r, derr, APLOG_ERR);
- return dav_svn__error_response_tag(r, derr);
+ won't handle this error. Do it explicitly. */
+ return handle_err(r, derr);
}
return OK;
diff --git a/subversion/mod_dav_svn/util.c b/subversion/mod_dav_svn/util.c
index c37762f..2890502 100644
--- a/subversion/mod_dav_svn/util.c
+++ b/subversion/mod_dav_svn/util.c
@@ -45,6 +45,9 @@ dav_svn__new_error(apr_pool_t *pool,
int error_id,
const char *desc)
{
+ if (error_id == 0)
+ error_id = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
/*
* Note: dav_new_error() in httpd 2.0/2.2 always treated
* the errno field in dav_error as an apr_status_t when
@@ -53,9 +56,11 @@ dav_svn__new_error(apr_pool_t *pool,
* > 2.2 below perpetuates this.
*/
#if AP_MODULE_MAGIC_AT_LEAST(20091119,0)
- /* old code assumed errno was valid; keep assuming */
- return dav_new_error(pool, status, error_id, errno, desc);
+ return dav_new_error(pool, status, error_id, 0, desc);
#else
+
+ errno = 0; /* For the same reason as in dav_svn__new_error_tag */
+
return dav_new_error(pool, status, error_id, desc);
#endif
}
@@ -68,6 +73,9 @@ dav_svn__new_error_tag(apr_pool_t *pool,
const char *namespace,
const char *tagname)
{
+ if (error_id == 0)
+ error_id = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
#if AP_MODULE_MAGIC_AT_LEAST(20091119,0)
return dav_new_error_tag(pool, status, error_id, 0,
desc, namespace, tagname);
@@ -264,6 +272,11 @@ dav_svn__build_uri(const dav_svn_repos *repos,
href1, root_path, special_uri,
revision, path_uri, href2);
+ case DAV_SVN__BUILD_URI_REVROOT:
+ return apr_psprintf(pool, "%s%s/%s/rvr/%ld%s%s",
+ href1, root_path, special_uri,
+ revision, path_uri, href2);
+
case DAV_SVN__BUILD_URI_VCC:
return apr_psprintf(pool, "%s%s/%s/vcc/" DAV_SVN__DEFAULT_VCC_NAME "%s",
href1, root_path, special_uri, href2);
@@ -412,6 +425,32 @@ dav_svn__simple_parse_uri(dav_svn__uri_info *info,
"Unsupported URI form");
}
+svn_boolean_t
+dav_svn__is_parentpath_list(request_rec *r)
+{
+ const char *fs_parent_path = dav_svn__get_fs_parent_path(r);
+
+ if (fs_parent_path && dav_svn__get_list_parentpath_flag(r))
+ {
+ const char *root_path = dav_svn__get_root_dir(r);
+ char *uri = apr_pstrdup(r->pool, r->uri);
+ char *parentpath = apr_pstrdup(r->pool, root_path);
+ apr_size_t uri_len = strlen(uri);
+ apr_size_t parentpath_len = strlen(parentpath);
+
+ if (uri[uri_len-1] == '/')
+ uri[uri_len-1] = '\0';
+
+ if (parentpath[parentpath_len-1] == '/')
+ parentpath[parentpath_len-1] = '\0';
+
+ if (strcmp(parentpath, uri) == 0)
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
/* ### move this into apr_xml */
int
@@ -520,11 +559,23 @@ dav_svn__sanitize_error(svn_error_t *serr,
svn_error_t *safe_err = serr;
if (new_msg != NULL)
{
+ /* Purge error tracing from the error chain. */
+ svn_error_t *purged_serr = svn_error_purge_tracing(serr);
+
/* Sanitization is necessary. Create a new, safe error and
log the original error. */
- safe_err = svn_error_create(serr->apr_err, NULL, new_msg);
- ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r,
- "%s", serr->message);
+ safe_err = svn_error_create(purged_serr->apr_err, NULL, new_msg);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r,
+ "%s", purged_serr->message);
+
+ /* Log the entire error chain. */
+ while (purged_serr->child)
+ {
+ purged_serr = purged_serr->child;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r,
+ "%s", purged_serr->message);
+ }
+
svn_error_clear(serr);
}
return dav_svn__convert_err(safe_err, http_status,
@@ -596,7 +647,7 @@ dav_svn__final_flush_or_error(request_rec *r,
if (! do_flush)
{
/* Ask about the length of the bucket brigade, ignoring errors. */
- apr_off_t len;
+ apr_off_t len = 0;
(void)apr_brigade_length(bb, FALSE, &len);
do_flush = (len != 0);
}
@@ -731,7 +782,7 @@ request_body_to_string(svn_string_t **request_str,
}
else
{
- buf = svn_stringbuf_create("", pool);
+ buf = svn_stringbuf_create_empty(pool);
}
brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc);
@@ -787,7 +838,7 @@ request_body_to_string(svn_string_t **request_str,
apr_brigade_destroy(brigade);
/* Make an svn_string_t from our svn_stringbuf_t. */
- *request_str = svn_string_create("", pool);
+ *request_str = svn_string_create_empty(pool);
(*request_str)->data = buf->data;
(*request_str)->len = buf->len;
return OK;
diff --git a/subversion/mod_dav_svn/version.c b/subversion/mod_dav_svn/version.c
index b532085..98208a9 100644
--- a/subversion/mod_dav_svn/version.c
+++ b/subversion/mod_dav_svn/version.c
@@ -28,6 +28,7 @@
#include <http_log.h>
#include <mod_dav.h>
+#include "svn_hash.h"
#include "svn_fs.h"
#include "svn_xml.h"
#include "svn_repos.h"
@@ -37,7 +38,9 @@
#include "svn_props.h"
#include "svn_dav.h"
#include "svn_base64.h"
+#include "svn_version.h"
#include "private/svn_repos_private.h"
+#include "private/svn_subr_private.h"
#include "private/svn_dav_protocol.h"
#include "private/svn_log.h"
#include "private/svn_fspath.h"
@@ -146,6 +149,9 @@ get_vsn_options(apr_pool_t *p, apr_text_header *phdr)
apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS);
apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY);
+ apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_INHERITED_PROPS);
+ apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_INLINE_PROPS);
+ apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS);
/* Mergeinfo is a special case: here we merely say that the server
* knows how to handle mergeinfo -- whether the repository does too
* is a separate matter.
@@ -192,6 +198,14 @@ get_option(const dav_resource *resource,
}
}
+ /* If we're allowed (by configuration) to do so, advertise support
+ for ephemeral transaction properties. */
+ if (dav_svn__check_ephemeral_txnprops_support(r))
+ {
+ apr_table_addn(r->headers_out, "DAV",
+ SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS);
+ }
+
if (resource->info->repos->fs)
{
svn_error_t *serr;
@@ -230,10 +244,56 @@ get_option(const dav_resource *resource,
}
}
+ if (resource->info->repos->repos)
+ {
+ svn_error_t *serr;
+ svn_boolean_t has;
+
+ serr = svn_repos_has_capability(resource->info->repos->repos, &has,
+ SVN_REPOS_CAPABILITY_MERGEINFO,
+ r->pool);
+ if (serr)
+ return dav_svn__convert_err
+ (serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Error fetching repository capabilities",
+ resource->pool);
+
+ apr_table_set(r->headers_out, SVN_DAV_REPOSITORY_MERGEINFO,
+ has ? "yes" : "no");
+ }
+
/* Welcome to the 2nd generation of the svn HTTP protocol, now
DeltaV-free! If we're configured to advise this support, do so. */
if (resource->info->repos->v2_protocol)
{
+ int i;
+ svn_version_t *master_version = dav_svn__get_master_version(r);
+ dav_svn__bulk_upd_conf bulk_upd_conf = dav_svn__get_bulk_updates_flag(r);
+
+ /* The list of Subversion's custom POSTs and which versions of
+ Subversion support them. We need this latter information
+ when acting as a WebDAV slave -- we don't want to claim
+ support for a POST type if the master server which will
+ actually have to handle it won't recognize it.
+
+ Keep this in sync with what's handled in handle_post_request().
+ */
+ struct posts_versions_t {
+ const char *post_name;
+ svn_version_t min_version;
+ } posts_versions[] = {
+ { "create-txn", { 1, 7, 0, "" } },
+ { "create-txn-with-props", { 1, 8, 0, "" } },
+ };
+
+ /* Add the header which indicates that this server can handle
+ replay REPORTs submitted against an HTTP v2 revision resource. */
+ apr_table_addn(r->headers_out, "DAV",
+ SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE);
+
+ /* Add a bunch of HTTP v2 headers which carry resource and
+ resource stub URLs that the client can use to naively build
+ addressable resources. */
apr_table_set(r->headers_out, SVN_DAV_ROOT_URI_HEADER, repos_root_uri);
apr_table_set(r->headers_out, SVN_DAV_ME_RESOURCE_HEADER,
apr_pstrcat(resource->pool, repos_root_uri, "/",
@@ -256,6 +316,26 @@ get_option(const dav_resource *resource,
apr_table_set(r->headers_out, SVN_DAV_VTXN_STUB_HEADER,
apr_pstrcat(resource->pool, repos_root_uri, "/",
dav_svn__get_vtxn_stub(r), (char *)NULL));
+ apr_table_set(r->headers_out, SVN_DAV_ALLOW_BULK_UPDATES,
+ bulk_upd_conf == CONF_BULKUPD_ON ? "On" :
+ bulk_upd_conf == CONF_BULKUPD_OFF ? "Off" : "Prefer");
+
+ /* Report the supported POST types. */
+ for (i = 0; i < sizeof(posts_versions)/sizeof(posts_versions[0]); ++i)
+ {
+ /* If we're proxying to a master server and its version
+ number is declared, we can selectively filter out POST
+ types that it doesn't support. */
+ if (master_version
+ && (! svn_version__at_least(master_version,
+ posts_versions[i].min_version.major,
+ posts_versions[i].min_version.minor,
+ posts_versions[i].min_version.patch)))
+ continue;
+
+ apr_table_addn(r->headers_out, SVN_DAV_SUPPORTED_POSTS_HEADER,
+ apr_pstrdup(resource->pool, posts_versions[i].post_name));
+ }
}
return NULL;
@@ -398,7 +478,7 @@ dav_svn__checkout(dav_resource *resource,
shared_activity = apr_pstrdup(resource->info->r->pool, uuid_buf);
derr = dav_svn__create_txn(resource->info->repos, &shared_txn_name,
- resource->info->r->pool);
+ NULL, resource->info->r->pool);
if (derr) return derr;
derr = dav_svn__store_activity(resource->info->repos,
@@ -953,8 +1033,11 @@ dav_svn__checkin(dav_resource *resource,
if (serr)
{
+ int status;
+
if (serr->apr_err == SVN_ERR_FS_CONFLICT)
{
+ status = HTTP_CONFLICT;
msg = apr_psprintf(resource->pool,
"A conflict occurred during the CHECKIN "
"processing. The problem occurred with "
@@ -962,10 +1045,12 @@ dav_svn__checkin(dav_resource *resource,
conflict_msg);
}
else
- msg = "An error occurred while committing the transaction.";
+ {
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ msg = "An error occurred while committing the transaction.";
+ }
- return dav_svn__convert_err(serr, HTTP_CONFLICT, msg,
- resource->pool);
+ return dav_svn__convert_err(serr, status, msg, resource->pool);
}
else
{
@@ -1093,6 +1178,10 @@ deliver_report(request_rec *r,
{
return dav_svn__get_deleted_rev_report(resource, doc, output);
}
+ else if (strcmp(doc->root->name, SVN_DAV__INHERITED_PROPS_REPORT) == 0)
+ {
+ return dav_svn__get_inherited_props_report(resource, doc, output);
+ }
/* NOTE: if you add a report, don't forget to add it to the
* dav_svn__reports_list[] array.
*/
@@ -1138,7 +1227,8 @@ make_activity(dav_resource *resource)
SVN_DAV_ERROR_NAMESPACE,
SVN_DAV_ERROR_TAG);
- err = dav_svn__create_txn(resource->info->repos, &txn_name, resource->pool);
+ err = dav_svn__create_txn(resource->info->repos, &txn_name,
+ NULL, resource->pool);
if (err != NULL)
return err;
@@ -1180,7 +1270,7 @@ dav_svn__build_lock_hash(apr_hash_t **locks,
if (! doc)
{
*locks = hash;
- return SVN_NO_ERROR;
+ return NULL;
}
/* Sanity check. */
@@ -1191,7 +1281,7 @@ dav_svn__build_lock_hash(apr_hash_t **locks,
definitely no lock-tokens to harvest. This is likely a
request from an old client. */
*locks = hash;
- return SVN_NO_ERROR;
+ return NULL;
}
if ((doc->root->ns == ns)
@@ -1217,7 +1307,7 @@ dav_svn__build_lock_hash(apr_hash_t **locks,
if (! child)
{
*locks = hash;
- return SVN_NO_ERROR;
+ return NULL;
}
/* Then look for N different <lock> structures within. */
@@ -1243,7 +1333,7 @@ dav_svn__build_lock_hash(apr_hash_t **locks,
lockpath = svn_fspath__join(path_prefix, cdata, pool);
if (lockpath && locktoken)
{
- apr_hash_set(hash, lockpath, APR_HASH_KEY_STRING, locktoken);
+ svn_hash_sets(hash, lockpath, locktoken);
lockpath = NULL;
locktoken = NULL;
}
@@ -1253,7 +1343,7 @@ dav_svn__build_lock_hash(apr_hash_t **locks,
locktoken = dav_xml_get_cdata(lfchild, pool, 1);
if (lockpath && *locktoken)
{
- apr_hash_set(hash, lockpath, APR_HASH_KEY_STRING, locktoken);
+ svn_hash_sets(hash, lockpath, locktoken);
lockpath = NULL;
locktoken = NULL;
}
@@ -1262,7 +1352,7 @@ dav_svn__build_lock_hash(apr_hash_t **locks,
}
*locks = hash;
- return SVN_NO_ERROR;
+ return NULL;
}
@@ -1455,8 +1545,11 @@ merge(dav_resource *target,
if (serr)
{
const char *msg;
+ int status;
+
if (serr->apr_err == SVN_ERR_FS_CONFLICT)
{
+ status = HTTP_CONFLICT;
/* ### we need to convert the conflict path into a URI */
msg = apr_psprintf(pool,
"A conflict occurred during the MERGE "
@@ -1465,9 +1558,12 @@ merge(dav_resource *target,
conflict);
}
else
- msg = "An error occurred while committing the transaction.";
+ {
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ msg = "An error occurred while committing the transaction.";
+ }
- return dav_svn__convert_err(serr, HTTP_CONFLICT, msg, pool);
+ return dav_svn__convert_err(serr, status, msg, pool);
}
else
{