diff options
Diffstat (limited to 'subversion/mod_dav_svn')
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 { |