Index: subversion/mod_dav_svn/dav_svn.h =================================================================== --- subversion/mod_dav_svn/dav_svn.h (revision 1724205) +++ subversion/mod_dav_svn/dav_svn.h (working copy) @@ -432,6 +432,9 @@ 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); +/* Return whether depth-infinity PROPFIND requests are allowed. */ +svn_boolean_t dav_svn__get_allow_depthinfinity(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 on the 'special uri', which is configurable in httpd.conf. **/ @@ -497,6 +500,9 @@ dav_svn__store_activity(const dav_svn_repos *repos /* POST request handler. (Used by HTTP protocol v2 clients only.) */ int dav_svn__method_post(request_rec *r); +/* PROPFIND request handler. */ +int dav_svn__method_propfind(request_rec *r); + /* Request handler to GET Subversion internal status (FSFS cache). */ int dav_svn__status(request_rec *r); @@ -1099,7 +1105,64 @@ apr_status_t dav_svn__location_header_filter(ap_fi apr_status_t dav_svn__location_body_filter(ap_filter_t *f, apr_bucket_brigade *bb); +dav_error * +dav_svn__get_resource(request_rec *r, + int label_allowed, + int use_checked_in, + dav_resource **res_p); +dav_prop_insert +dav_svn__insert_liveprop(const dav_resource *resource, + int propid, + dav_prop_insert what, + apr_text_header *phdr, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +void +dav_svn__output_all_liveprops(request_rec *r, + const dav_resource *resource, + dav_prop_insert what, + apr_text_header *phdr, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +void +dav_svn__define_deadprop_namespaces(dav_xmlns_info *xi); + +dav_error * +dav_svn__output_deadprop_value(const dav_resource *resource, + const dav_prop_name *name, + dav_xmlns_info *xi, + apr_text_header *phdr, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + int *found, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +dav_error * +dav_svn__output_all_deadprops(const dav_resource *resource, + dav_xmlns_info *xi, + apr_text_header *phdr, + dav_prop_insert what, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +dav_lockdb * +dav_svn__open_lockdb(request_rec *r, int ro, int force); + +dav_error * +dav_svn__get_locks(dav_lockdb *lockdb, + const dav_resource *resource, + int calltype, + dav_lock **locks, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + #ifdef __cplusplus } #endif /* __cplusplus */ Index: subversion/mod_dav_svn/deadprops.c =================================================================== --- subversion/mod_dav_svn/deadprops.c (revision 1724205) +++ subversion/mod_dav_svn/deadprops.c (working copy) @@ -47,9 +47,6 @@ struct dav_db { apr_hash_t *props; apr_hash_index_t *hi; - /* used for constructing repos-local names for properties */ - svn_stringbuf_t *work; - /* passed to svn_repos_ funcs that fetch revprops. */ svn_repos_authz_func_t authz_read_func; void *authz_read_baton; @@ -71,16 +68,15 @@ get_repos_path(struct dav_resource_private *info) /* construct the repos-local name for the given DAV property name */ static void -get_repos_propname(dav_db *db, - const dav_prop_name *name, - const char **repos_propname) +get_repos_propname(const dav_prop_name *name, + const char **repos_propname, + apr_pool_t *pool) { if (strcmp(name->ns, SVN_DAV_PROP_NS_SVN) == 0) { /* recombine the namespace ("svn:") and the name. */ - svn_stringbuf_set(db->work, SVN_PROP_PREFIX); - svn_stringbuf_appendcstr(db->work, name->name); - *repos_propname = db->work->data; + *repos_propname = apr_pstrcat(pool, SVN_PROP_PREFIX, name->name, + SVN_VA_NULL); } else if (strcmp(name->ns, SVN_DAV_PROP_NS_CUSTOM) == 0) { @@ -95,28 +91,15 @@ static void static dav_error * -get_value(dav_db *db, const dav_prop_name *name, svn_string_t **pvalue) +get_value(const dav_resource *resource, + const char *propname, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + svn_string_t **pvalue, + apr_pool_t *pool) { - const char *propname; svn_error_t *serr; - /* get the repos-local name */ - get_repos_propname(db, name, &propname); - - if (propname == NULL) - { - /* we know these are not present. */ - *pvalue = NULL; - return NULL; - } - - /* If db->props exists, then use it to obtain property value. */ - if (db->props) - { - *pvalue = svn_hash_gets(db->props, propname); - return NULL; - } - /* We've got three different types of properties (node, txn, and revision), and we've got two different protocol versions to deal with. Let's try to make some sense of this, shall we? @@ -135,34 +118,34 @@ static dav_error * */ - if (db->resource->baselined) + if (resource->baselined) { - if (db->resource->type == DAV_RESOURCE_TYPE_WORKING) - serr = svn_fs_txn_prop(pvalue, db->resource->info->root.txn, - propname, db->p); + if (resource->type == DAV_RESOURCE_TYPE_WORKING) + serr = svn_fs_txn_prop(pvalue, resource->info->root.txn, + propname, pool); else serr = svn_repos_fs_revision_prop(pvalue, - db->resource->info->repos->repos, - db->resource->info->root.rev, - propname, db->authz_read_func, - db->authz_read_baton, db->p); + resource->info->repos->repos, + resource->info->root.rev, + propname, authz_read_func, + authz_read_baton, pool); } - else if (db->resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION) + else if (resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION) { - serr = svn_fs_txn_prop(pvalue, db->resource->info->root.txn, - propname, db->p); + serr = svn_fs_txn_prop(pvalue, resource->info->root.txn, + propname, pool); } else { - serr = svn_fs_node_prop(pvalue, db->resource->info->root.root, - get_repos_path(db->resource->info), - propname, db->p); + serr = svn_fs_node_prop(pvalue, resource->info->root.root, + get_repos_path(resource->info), + propname, pool); } if (serr != NULL) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not fetch a property", - db->resource->pool); + resource->pool); return NULL; } @@ -195,8 +178,12 @@ save_value(dav_db *db, const dav_prop_name *name, const dav_resource *resource = db->resource; apr_pool_t *subpool; + /* A subpool to cope with mod_dav making multiple calls, e.g. during + PROPPATCH with multiple values. */ + subpool = svn_pool_create(resource->pool); + /* get the repos-local name */ - get_repos_propname(db, name, &propname); + get_repos_propname(name, &propname, subpool); if (propname == NULL) { @@ -204,10 +191,13 @@ save_value(dav_db *db, const dav_prop_name *name, /* ignore the unknown namespace of the incoming prop. */ propname = name->name; else - return dav_svn__new_error(db->p, HTTP_CONFLICT, 0, 0, - "Properties may only be defined in the " - SVN_DAV_PROP_NS_SVN " and " - SVN_DAV_PROP_NS_CUSTOM " namespaces."); + { + svn_pool_destroy(subpool); + return dav_svn__new_error(db->p, HTTP_CONFLICT, 0, 0, + "Properties may only be defined in the " + SVN_DAV_PROP_NS_SVN " and " + SVN_DAV_PROP_NS_CUSTOM " namespaces."); + } } /* We've got three different types of properties (node, txn, and @@ -228,9 +218,6 @@ save_value(dav_db *db, const dav_prop_name *name, */ - /* 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 (resource->working) @@ -343,9 +330,6 @@ db_open(apr_pool_t *p, db->resource = resource; db->p = svn_pool_create(p); - /* ### temp hack */ - db->work = svn_stringbuf_create_empty(db->p); - /* make our path-based authz callback available to svn_repos_* funcs. */ arb = apr_pcalloc(p, sizeof(*arb)); arb->r = resource->info->r; @@ -368,8 +352,8 @@ db_close(dav_db *db) } -static dav_error * -db_define_namespaces(dav_db *db, dav_xmlns_info *xi) +void +dav_svn__define_deadprop_namespaces(dav_xmlns_info *xi) { dav_xmlns_add(xi, "S", SVN_DAV_PROP_NS_SVN); dav_xmlns_add(xi, "C", SVN_DAV_PROP_NS_CUSTOM); @@ -376,41 +360,35 @@ db_close(dav_db *db) dav_xmlns_add(xi, "V", SVN_DAV_PROP_NS_DAV); /* ### we don't have any other possible namespaces right now. */ +} +static dav_error * +db_define_namespaces(dav_db *db, dav_xmlns_info *xi) +{ + dav_svn__define_deadprop_namespaces(xi); + return NULL; } -static dav_error * -db_output_value(dav_db *db, - const dav_prop_name *name, - dav_xmlns_info *xi, - apr_text_header *phdr, - int *found) +static void +encode_prop(const dav_prop_name *name, + const svn_string_t *propval, + apr_text_header *phdr, + apr_pool_t *result_pool) { const char *prefix; const char *s; - svn_string_t *propval; - dav_error *err; - apr_pool_t *pool = db->resource->pool; - if ((err = get_value(db, name, &propval)) != NULL) - return err; - - /* return whether the prop was found, then punt or handle it. */ - *found = (propval != NULL); - if (propval == NULL) - return NULL; - if (strcmp(name->ns, SVN_DAV_PROP_NS_CUSTOM) == 0) prefix = "C:"; else prefix = "S:"; - if (propval->len == 0) + if (!propval || propval->len == 0) { - /* empty value. add an empty elem. */ - s = apr_psprintf(pool, "<%s%s/>" DEBUG_CR, prefix, name->name); - apr_text_append(pool, phdr, s); + /* add an empty elem. */ + s = apr_psprintf(result_pool, "<%s%s/>" DEBUG_CR, prefix, name->name); + apr_text_append(result_pool, phdr, s); } else { @@ -423,7 +401,7 @@ db_close(dav_db *db) if (! svn_xml_is_xml_safe(propval->data, propval->len)) { const svn_string_t *enc_propval - = svn_base64_encode_string2(propval, TRUE, pool); + = svn_base64_encode_string2(propval, TRUE, result_pool); xml_safe = enc_propval->data; encoding = " V:encoding=\"base64\""; } @@ -430,25 +408,97 @@ db_close(dav_db *db) else { svn_stringbuf_t *xmlval = NULL; - svn_xml_escape_cdata_string(&xmlval, propval, pool); + svn_xml_escape_cdata_string(&xmlval, propval, result_pool); xml_safe = xmlval->data; } - s = apr_psprintf(pool, "<%s%s%s>", prefix, name->name, encoding); - apr_text_append(pool, phdr, s); + s = apr_psprintf(result_pool, "<%s%s%s>", prefix, name->name, encoding); + apr_text_append(result_pool, phdr, s); /* the value is in our pool which means it has the right lifetime. */ /* ### at least, per the current mod_dav architecture/API */ - apr_text_append(pool, phdr, xml_safe); + apr_text_append(result_pool, phdr, xml_safe); - s = apr_psprintf(pool, "" DEBUG_CR, prefix, name->name); - apr_text_append(pool, phdr, s); + s = apr_psprintf(result_pool, "" DEBUG_CR, prefix, name->name); + apr_text_append(result_pool, phdr, s); } +} +dav_error * +dav_svn__output_deadprop_value(const dav_resource *resource, + const dav_prop_name *name, + dav_xmlns_info *xi, + apr_text_header *phdr, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + int *found, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *propname; + svn_string_t *propval; + + /* get the repos-local name */ + get_repos_propname(name, &propname, scratch_pool); + + if (propname == NULL) + propval = NULL; + else + { + dav_error *err; + err = get_value(resource, propname, authz_read_func, + authz_read_baton, &propval, scratch_pool); + if (err != NULL) + return err; + } + + /* return whether the prop was found, then punt or handle it. */ + *found = (propval != NULL); + if (*found) + encode_prop(name, propval, phdr, result_pool); + return NULL; } +static dav_error * +db_output_value(dav_db *db, + const dav_prop_name *name, + dav_xmlns_info *xi, + apr_text_header *phdr, + int *found) +{ + apr_pool_t *pool = db->resource->pool; + const char *propname; + svn_string_t *propval; + /* get the repos-local name */ + get_repos_propname(name, &propname, pool); + + if (propname == NULL) + propval = NULL; + else + { + /* If db->props exists, then use it to obtain property value. */ + if (db->props) + propval = svn_hash_gets(db->props, propname); + else + { + dav_error *err; + err = get_value(db->resource, propname, db->authz_read_func, + db->authz_read_baton, &propval, pool); + if (err != NULL) + return err; + } + } + + /* return whether the prop was found, then punt or handle it. */ + *found = (propval != NULL); + if (*found) + encode_prop(name, propval, phdr, pool); + + return NULL; +} + static dav_error * db_map_namespaces(dav_db *db, const apr_array_header_t *namespaces, @@ -567,17 +617,20 @@ db_remove(dav_db *db, const dav_prop_name *name) const char *propname; apr_pool_t *subpool; + /* A subpool to cope with mod_dav making multiple calls, e.g. during + PROPPATCH with multiple values. */ + subpool = svn_pool_create(db->resource->pool); + /* get the repos-local name */ - get_repos_propname(db, name, &propname); + get_repos_propname(name, &propname, subpool); /* ### non-svn props aren't in our repos, so punt for now */ if (propname == NULL) - return NULL; + { + svn_pool_destroy(subpool); + 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) @@ -621,7 +674,7 @@ db_exists(dav_db *db, const dav_prop_name *name) int retval; /* get the repos-local name */ - get_repos_propname(db, name, &propname); + get_repos_propname(name, &propname, db->p); /* ### non-svn props aren't in our repos */ if (propname == NULL) @@ -651,8 +704,26 @@ db_exists(dav_db *db, const dav_prop_name *name) return retval; } -static void get_name(dav_db *db, dav_prop_name *pname) +static void +get_dav_name(dav_prop_name *dav_propname, const char *svn_propname) { +#define PREFIX_LEN (sizeof(SVN_PROP_PREFIX) - 1) + if (strncmp(svn_propname, SVN_PROP_PREFIX, PREFIX_LEN) == 0) +#undef PREFIX_LEN + { + dav_propname->ns = SVN_DAV_PROP_NS_SVN; + dav_propname->name = svn_propname + 4; + } + else + { + dav_propname->ns = SVN_DAV_PROP_NS_CUSTOM; + dav_propname->name = svn_propname; + } +} + +static void +get_name(dav_db *db, dav_prop_name *pname) +{ if (db->hi == NULL) { pname->ns = pname->name = NULL; @@ -660,19 +731,7 @@ db_exists(dav_db *db, const dav_prop_name *name) else { const char *name = apr_hash_this_key(db->hi); - -#define PREFIX_LEN (sizeof(SVN_PROP_PREFIX) - 1) - if (strncmp(name, SVN_PROP_PREFIX, PREFIX_LEN) == 0) -#undef PREFIX_LEN - { - pname->ns = SVN_DAV_PROP_NS_SVN; - pname->name = name + 4; - } - else - { - pname->ns = SVN_DAV_PROP_NS_CUSTOM; - pname->name = name; - } + get_dav_name(pname, name); } } @@ -761,7 +820,97 @@ db_next_name(dav_db *db, dav_prop_name *pname) return NULL; } +dav_error * +dav_svn__output_all_deadprops(const dav_resource *resource, + dav_xmlns_info *xi, + apr_text_header *phdr, + dav_prop_insert what, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* for operational logging */ + const char *action = NULL; + apr_hash_t *props; + svn_error_t *serr; + apr_hash_index_t *hi; + /* Working Baseline, Baseline, or (Working) Version resource */ + if (resource->baselined) + { + if (resource->type == DAV_RESOURCE_TYPE_WORKING) + serr = svn_fs_txn_proplist(&props, + resource->info->root.txn, + scratch_pool); + else + { + action = svn_log__rev_proplist(resource->info->root.rev, + resource->pool); + serr = svn_repos_fs_revision_proplist( + &props, + resource->info->repos->repos, + resource->info->root.rev, + authz_read_func, + authz_read_baton, + scratch_pool); + } + } + else + { + svn_node_kind_t kind; + serr = svn_fs_node_proplist(&props, + resource->info->root.root, + get_repos_path(resource->info), + scratch_pool); + if (! serr) + serr = svn_fs_check_path(&kind, resource->info->root.root, + get_repos_path(resource->info), + scratch_pool); + + if (! serr) + { + if (kind == svn_node_dir) + action = svn_log__get_dir(resource->info->repos_path, + resource->info->root.rev, + FALSE, TRUE, 0, resource->pool); + else + action = svn_log__get_file(resource->info->repos_path, + resource->info->root.rev, + FALSE, TRUE, resource->pool); + } + } + if (serr != NULL) + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "could not begin sequencing through " + "properties", + resource->pool); + + for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi)) + { + const char *propname = apr_hash_this_key(hi); + const svn_string_t *propval = apr_hash_this_val(hi); + dav_prop_name dav_propname; + + get_dav_name(&dav_propname, propname); + + /* Only DAV_PROP_INSERT_NAME and DAV_PROP_INSERT_VALUE are currently + allowed. */ + if (what == DAV_PROP_INSERT_NAME) + encode_prop(&dav_propname, NULL, phdr, result_pool); + else if (what == DAV_PROP_INSERT_VALUE) + encode_prop(&dav_propname, propval, phdr, result_pool); + else + SVN_ERR_MALFUNCTION_NO_RETURN(); + } + + /* If we have a high-level action to log, do so. */ + if (action != NULL) + dav_svn__operational_log(resource->info, action); + + return NULL; +} + static dav_error * db_get_rollback(dav_db *db, const dav_prop_name *name, Index: subversion/mod_dav_svn/liveprops.c =================================================================== --- subversion/mod_dav_svn/liveprops.c (revision 1724205) +++ subversion/mod_dav_svn/liveprops.c (working copy) @@ -95,6 +95,7 @@ static const dav_liveprop_spec props[] = SVN_RO_DAV_PROP(getetag), SVN_RO_DAV_PROP(creationdate), SVN_RO_DAV_PROP(getlastmodified), + SVN_RO_DAV_PROP(resourcetype), /* DeltaV properties */ SVN_RO_DAV_PROP2(baseline_collection, baseline-collection), @@ -273,13 +274,13 @@ get_last_modified_time(const char **datestring, return 0; } -static dav_prop_insert -insert_prop_internal(const dav_resource *resource, - int propid, - dav_prop_insert what, - apr_text_header *phdr, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +dav_prop_insert +dav_svn__insert_liveprop(const dav_resource *resource, + int propid, + dav_prop_insert what, + apr_text_header *phdr, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { const char *value = NULL; const char *s; @@ -311,6 +312,44 @@ get_last_modified_time(const char **datestring, switch (propid) { + case DAV_PROPID_resourcetype: + switch (resource->type) + { + case DAV_RESOURCE_TYPE_VERSION: + if (resource->baselined) + { + value = ""; + break; + } + /* fall through */ + case DAV_RESOURCE_TYPE_REGULAR: + case DAV_RESOURCE_TYPE_WORKING: + if (resource->collection) + { + value = ""; + } + else + { + /* ### should we denote lock-null resources? */ + value = ""; /* becomes: */ + } + break; + case DAV_RESOURCE_TYPE_HISTORY: + value = ""; + break; + case DAV_RESOURCE_TYPE_WORKSPACE: + value = ""; + break; + case DAV_RESOURCE_TYPE_ACTIVITY: + value = ""; + break; + + default: + /* ### bad juju */ + return DAV_PROP_INSERT_NOTDEF; + } + break; + case DAV_PROPID_getlastmodified: case DAV_PROPID_creationdate: { @@ -873,8 +912,8 @@ insert_prop(const dav_resource *resource, scratch pool for insert_prop() callback. */ scratch_pool = svn_pool_create(result_pool); - rv = insert_prop_internal(resource, propid, what, phdr, - result_pool, scratch_pool); + rv = dav_svn__insert_liveprop(resource, propid, what, phdr, + result_pool, scratch_pool); svn_pool_destroy(scratch_pool); return rv; @@ -989,6 +1028,18 @@ dav_svn__insert_all_liveprops(request_rec *r, dav_prop_insert what, apr_text_header *phdr) { + dav_svn__output_all_liveprops(r, resource, what, phdr, + resource->pool, resource->pool); +} + +void +dav_svn__output_all_liveprops(request_rec *r, + const dav_resource *resource, + dav_prop_insert what, + apr_text_header *phdr, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ const dav_liveprop_spec *spec; apr_pool_t *iterpool; @@ -1007,12 +1058,12 @@ dav_svn__insert_all_liveprops(request_rec *r, return; } - iterpool = svn_pool_create(resource->pool); + iterpool = svn_pool_create(scratch_pool); for (spec = props; spec->name != NULL; ++spec) { svn_pool_clear(iterpool); - (void) insert_prop_internal(resource, spec->propid, what, phdr, - resource->pool, iterpool); + (void) dav_svn__insert_liveprop(resource, spec->propid, what, phdr, + result_pool, iterpool); } svn_pool_destroy(iterpool); Index: subversion/mod_dav_svn/lock.c =================================================================== --- subversion/mod_dav_svn/lock.c (revision 1724205) +++ subversion/mod_dav_svn/lock.c (working copy) @@ -314,8 +314,8 @@ compare_locktoken(const dav_locktoken *lt1, const * may occur. * If force != 0, locking operations will definitely occur. */ -static dav_error * -open_lockdb(request_rec *r, int ro, int force, dav_lockdb **lockdb) +dav_lockdb * +dav_svn__open_lockdb(request_rec *r, int ro, int force) { const char *svn_client_options, *version_name; dav_lockdb *db = apr_pcalloc(r->pool, sizeof(*db)); @@ -349,11 +349,19 @@ compare_locktoken(const dav_locktoken *lt1, const db->ro = ro; db->info = info; - *lockdb = db; - return 0; + return db; } +static dav_error * +open_lockdb(request_rec *r, int ro, int force, dav_lockdb **lockdb) +{ + *lockdb = dav_svn__open_lockdb(r, ro, force); + + return NULL; +} + + /* Indicates completion of locking operations */ static void close_lockdb(dav_lockdb *lockdb) @@ -428,11 +436,13 @@ create_lock(dav_lockdb *lockdb, const dav_resource ** #define DAV_GETLOCKS_PARTIAL 1 -- leave indirects partially filled ** #define DAV_GETLOCKS_COMPLETE 2 -- fill out indirect locks */ -static dav_error * -get_locks(dav_lockdb *lockdb, - const dav_resource *resource, - int calltype, - dav_lock **locks) +dav_error * +dav_svn__get_locks(dav_lockdb *lockdb, + const dav_resource *resource, + int calltype, + dav_lock **locks, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { dav_lockdb_private *info = lockdb->info; svn_error_t *serr; @@ -468,7 +478,7 @@ create_lock(dav_lockdb *lockdb, const dav_resource /* If the resource's fs path is unreadable, we don't want to say anything about locks attached to it.*/ if (! dav_svn__allow_read_resource(resource, SVN_INVALID_REVNUM, - resource->pool)) + scratch_pool)) return dav_svn__new_error(resource->pool, HTTP_FORBIDDEN, DAV_ERR_LOCK_SAVE_LOCK, 0, "Path is not accessible."); @@ -476,7 +486,7 @@ create_lock(dav_lockdb *lockdb, const dav_resource serr = svn_fs_get_lock(&slock, resource->info->repos->fs, resource->info->repos_path, - resource->pool); + scratch_pool); if (serr) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Failed to check path for a lock.", @@ -485,7 +495,7 @@ create_lock(dav_lockdb *lockdb, const dav_resource if (slock != NULL) { svn_lock_to_dav_lock(&lock, slock, info->lock_break, - resource->exists, resource->pool); + resource->exists, result_pool); /* If we are talking to an svn client that wants to unlock a path, tell mod_dav that the lock is owned by the user trying @@ -506,7 +516,7 @@ create_lock(dav_lockdb *lockdb, const dav_resource /* Let svn clients know who "owns" the slock. */ apr_table_setn(info->r->headers_out, SVN_DAV_LOCK_OWNER_HEADER, - slock->owner); + apr_pstrdup(resource->pool, slock->owner)); } *locks = lock; @@ -513,7 +523,28 @@ create_lock(dav_lockdb *lockdb, const dav_resource return 0; } +static dav_error * +get_locks(dav_lockdb *lockdb, + const dav_resource *resource, + int calltype, + dav_lock **locks) +{ + apr_pool_t *result_pool = resource->pool; + apr_pool_t *scratch_pool; + dav_error *derr; + /* Create subpool and destroy on return, because mod_dav doesn't provide + scratch pool for get_locks() callback. */ + scratch_pool = svn_pool_create(result_pool); + + derr = dav_svn__get_locks(lockdb, resource, calltype, locks, + result_pool, scratch_pool); + + svn_pool_destroy(scratch_pool); + + return derr; +} + /* ** Find a particular lock on a resource (specified by its locktoken). ** Index: subversion/mod_dav_svn/mod_dav_svn.c =================================================================== --- subversion/mod_dav_svn/mod_dav_svn.c (revision 1724205) +++ subversion/mod_dav_svn/mod_dav_svn.c (working copy) @@ -108,6 +108,7 @@ typedef struct dir_conf_t { enum conf_flag nodeprop_cache; /* whether to enable nodeprop caching */ enum conf_flag block_read; /* whether to enable block read mode */ const char *hooks_env; /* path to hook script env config file */ + enum conf_flag allow_depthinfinity; /* answer to depth-infinity PROPFINDs */ } dir_conf_t; @@ -242,6 +243,7 @@ create_dir_config(apr_pool_t *p, char *dir) conf->hooks_env = NULL; conf->txdelta_cache = CONF_FLAG_DEFAULT; conf->nodeprop_cache = CONF_FLAG_DEFAULT; + conf->allow_depthinfinity = CONF_FLAG_DEFAULT; return conf; } @@ -276,6 +278,8 @@ merge_dir_config(apr_pool_t *p, void *base, void * newconf->block_read = INHERIT_VALUE(parent, child, block_read); newconf->root_dir = INHERIT_VALUE(parent, child, root_dir); newconf->hooks_env = INHERIT_VALUE(parent, child, hooks_env); + newconf->allow_depthinfinity = INHERIT_VALUE(parent, child, + allow_depthinfinity); if (parent->fs_path) ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, @@ -665,6 +669,19 @@ SVNHooksEnv_cmd(cmd_parms *cmd, void *config, cons return NULL; } +static const char * +DAVDepthInfinity_cmd(cmd_parms *cmd, void *config, int arg) +{ + dir_conf_t *conf = config; + + if (arg) + conf->allow_depthinfinity = CONF_FLAG_ON; + else + conf->allow_depthinfinity = CONF_FLAG_OFF; + + return DECLINE_CMD; +} + static svn_boolean_t get_conf_flag(enum conf_flag flag, svn_boolean_t default_value) { @@ -1053,6 +1070,15 @@ dav_svn__get_hooks_env(request_rec *r) return conf->hooks_env; } +svn_boolean_t +dav_svn__get_allow_depthinfinity(request_rec *r) +{ + dir_conf_t *conf; + + conf = ap_get_module_config(r->per_dir_config, &dav_svn_module); + return conf->allow_depthinfinity == CONF_FLAG_ON; +} + static void merge_xml_filter_insert(request_rec *r) { @@ -1170,6 +1196,25 @@ merge_xml_in_filter(ap_filter_t *f, } +/* Response handler for PROPFIND requests. */ +static int dav_svn__propfind(request_rec *r) +{ + dir_conf_t *conf = ap_get_module_config(r->per_dir_config, &dav_svn_module); + + if (conf->fs_path || conf->fs_parent_path) + { + /* HTTP-defined Methods we handle */ + r->allowed = 0 + | (AP_METHOD_BIT << M_PROPFIND); + + if (r->method_number == M_PROPFIND) + return dav_svn__method_propfind(r); + } + + return DECLINED; +} + + /* Response handler for POST requests (protocol-v2 commits). */ static int dav_svn__handler(request_rec *r) { @@ -1278,6 +1323,11 @@ static int dav_svn__map_to_storage(request_rec *r) /* Implements the #cmds member of Apache's #module vtable. */ static const command_rec cmds[] = { + /* per directory/location, or per server */ + AP_INIT_FLAG("DAVDepthInfinity", DAVDepthInfinity_cmd, NULL, + ACCESS_CONF|RSRC_CONF, + "allow Depth infinity PROPFIND requests"), + /* per directory/location */ AP_INIT_TAKE1("SVNPath", SVNPath_cmd, NULL, ACCESS_CONF, "specifies the location in the filesystem for a Subversion " @@ -1426,6 +1476,8 @@ static dav_provider provider = static void register_hooks(apr_pool_t *pconf) { + static const char * const dav_module_name[] = { "mod_dav.c", NULL }; + ap_hook_pre_config(init_dso, NULL, NULL, APR_HOOK_REALLY_FIRST); ap_hook_post_config(init, NULL, NULL, APR_HOOK_MIDDLE); @@ -1444,6 +1496,9 @@ register_hooks(apr_pool_t *pconf) /* Handler to GET Subversion's FSFS cache stats, a bit like mod_status. */ ap_hook_handler(dav_svn__status, NULL, NULL, APR_HOOK_MIDDLE); + /* custom handler for PROPFIND requests, set to run before mod_dav's one */ + ap_hook_handler(dav_svn__propfind, NULL, dav_module_name, APR_HOOK_MIDDLE); + /* live property handling */ dav_hook_gather_propsets(dav_svn__gather_propsets, NULL, NULL, APR_HOOK_MIDDLE); Index: subversion/mod_dav_svn/propfind.c =================================================================== --- subversion/mod_dav_svn/propfind.c (nonexistent) +++ subversion/mod_dav_svn/propfind.c (working copy) @@ -0,0 +1,1144 @@ +/* + * propfind.c: mod_dav_svn PROPFIND handler + * + * ==================================================================== + * 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 +#include +#include + +#include +#include +#include +#include +#include + +#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 "private/svn_fspath.h" +#include "private/svn_dav_protocol.h" +#include "private/svn_log.h" + +#include "dav_svn.h" + +/* Apache's URI escaping does not replace '&' since that is a valid character + in a URI (to form a query section). We must explicitly handle it so that + we can embed the URI into an XML document. */ +static const char * +xml_escape_uri(const char *uri, apr_pool_t *pool) +{ + const char *e_uri = ap_escape_uri(pool, uri); + + /* Check the easy case... */ + if (ap_strchr_c(e_uri, '&') == NULL) + return e_uri; + + /* There was a '&', so more work is needed... sigh. */ + + /* Note: this is a teeny bit of overkill since we know there are + no '<' or '>' characters, but who cares. */ + return apr_xml_quote_string(pool, e_uri, 0); +} + +/* Write a complete RESPONSE object out as a xml + element. Data is sent into brigade BB, which is auto-flushed into + OUTPUT filter stack. Use POOL for any temporary allocations. + + [Presumably the tag has already been written; this + routine is shared by send_multistatus() and stream_response().] +*/ +static void +send_one_response(dav_response *response, + apr_bucket_brigade *bb, + ap_filter_t *output, + apr_pool_t *pool) +{ + apr_text *t = NULL; + + if (response->propresult.xmlns == NULL) + { + ap_fputs(output, bb, ""); + } + else + { + ap_fputs(output, bb, "propresult.xmlns; t; t = t->next) + ap_fputs(output, bb, t->text); + + ap_fputc(output, bb, '>'); + } + + ap_fputstrs(output, bb, + DEBUG_CR "", + xml_escape_uri(response->href, pool), + "" DEBUG_CR, + NULL); + + if (response->propresult.propstats == NULL) + { + /* Use the Status-Line text from Apache. Note, this will + * default to 500 Internal Server Error if response->status + * is not a known (or valid) status code. + */ + ap_fputstrs(output, bb, + "HTTP/1.1 ", + ap_get_status_line(response->status), + "" DEBUG_CR, + NULL); + } + else + { + /* Assume this includes and is quoted properly. */ + for (t = response->propresult.propstats; t; t = t->next) + ap_fputs(output, bb, t->text); + } + + if (response->desc != NULL) + { + /* We supply the description, so we know it doesn't have to + have any escaping/encoding applied to it. */ + ap_fputstrs(output, bb, + "", + response->desc, + "" DEBUG_CR, + NULL); + } + + ap_fputs(output, bb, "" DEBUG_CR); +} + +/* Factorized helper function: prep request_rec R for a multistatus + response and write tag into BB, destined for + R->output_filters. Use xml NAMESPACES in initial tag, if + non-NULL. */ +static void +begin_multistatus(apr_bucket_brigade *bb, + request_rec *r, + int status, + apr_array_header_t *namespaces) +{ + /* Set the correct status and Content-Type. */ + r->status = status; + ap_set_content_type(r, DAV_XML_CONTENT_TYPE); + + /* Send the headers and actual multistatus response now... */ + ap_fputs(r->output_filters, bb, + DAV_XML_HEADER DEBUG_CR "nelts; i--;) + ap_fprintf(r->output_filters, bb, " xmlns:ns%d=\"%s\"", i, + APR_XML_GET_URI_ITEM(namespaces, i)); + } + + ap_fputs(r->output_filters, bb, ">" DEBUG_CR); +} + +/* Finish a multistatus response started by begin_multistatus(). */ +static apr_status_t +finish_multistatus(request_rec *r, apr_bucket_brigade *bb) +{ + apr_bucket *b; + + ap_fputs(r->output_filters, bb, "" DEBUG_CR); + + /* Indicate the end of the response body. */ + b = apr_bucket_eos_create(r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + + /* Deliver whatever might be remaining in the brigade. */ + return ap_pass_brigade(r->output_filters, bb); +} + +static void +stream_response(request_rec *r, + const dav_resource *resource, + apr_bucket_brigade *bb, + int status, + dav_get_props_result *propstats, + apr_pool_t *pool) +{ + dav_response resp = { 0 }; + + resp.href = resource->uri; + resp.status = status; + + if (propstats) + resp.propresult = *propstats; + + send_one_response(&resp, bb, r->output_filters, pool); +} + +/* NOTE: core_props[] and the following enum must stay in sync. */ +static const char * const core_props[] = +{ + "getcontenttype", + "getcontentlanguage", + "lockdiscovery", + "supportedlock", + + NULL /* sentinel */ +}; + +enum +{ + DAV_PROPID_CORE_getcontenttype = DAV_PROPID_CORE, + DAV_PROPID_CORE_getcontentlanguage, + DAV_PROPID_CORE_lockdiscovery, + DAV_PROPID_CORE_supportedlock, + + DAV_PROPID_CORE_UNKNOWN +}; + +static int +find_liveprop_provider(const dav_resource *resource, + const char *ns_uri, + const char *propname, + const dav_hooks_liveprop **provider) +{ + int propid; + + *provider = NULL; + + if (ns_uri == NULL) + /* Policy: liveprop providers cannot define no-namespace properties. */ + return DAV_PROPID_CORE_UNKNOWN; + + /* Check liveprop providers first, so they can define core properties. */ + propid = dav_run_find_liveprop(resource, ns_uri, propname, provider); + if (propid != 0) + return propid; + + /* Check for core property. */ + if (strcmp(ns_uri, "DAV:") == 0) + { + const char * const *p = core_props; + + for (propid = DAV_PROPID_CORE; *p != NULL; ++p, ++propid) + { + if (strcmp(propname, *p) == 0) + return propid; + } + } + + /* No provider for this property. */ + return DAV_PROPID_CORE_UNKNOWN; +} + +static const char * +get_ns_uri(int ns, apr_xml_doc *doc) +{ + if (ns == APR_XML_NS_NONE) + return NULL; + else if (ns == APR_XML_NS_DAV_ID) + return "DAV:"; + else + return APR_XML_GET_URI_ITEM(doc->namespaces, ns); +} + +static void +find_liveprop(const dav_resource *resource, + apr_xml_elem *elem, + const char *ns_uri) +{ + dav_elem_private *priv = elem->priv; + const dav_hooks_liveprop *hooks; + + priv->propid = find_liveprop_provider(resource, ns_uri, elem->name, &hooks); + + /* ### This test seems redundant... */ + if (priv->propid != DAV_PROPID_CORE_UNKNOWN) + priv->provider = hooks; +} + +static void +output_prop_name(apr_pool_t *pool, const dav_prop_name *name, + dav_xmlns_info *xi, apr_text_header *phdr) +{ + const char *s; + + if (*name->ns == '\0') + { + s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name->name); + } + else + { + const char *prefix = dav_xmlns_add_uri(xi, name->ns); + + s = apr_psprintf(pool, "<%s:%s/>" DEBUG_CR, prefix, name->name); + } + + apr_text_append(pool, phdr, s); +} + +/* Do a sub-request to fetch properties for the target resource's URI. */ +static request_rec * +do_prop_subreq(request_rec *r, + const dav_resource *resource, + apr_pool_t *pool) +{ + /* Need to escape the uri that's in the resource struct because during + the property walker it's not encoded. */ + const char *e_uri = ap_escape_uri(pool, resource->uri); + + /* Perform a "GET" on the resource's URI (note that the resource + may not correspond to the current request!). */ + return ap_sub_req_lookup_uri(e_uri, r, NULL); +} + +static dav_error * +insert_coreprop(request_rec *r, + const dav_resource *resource, + dav_lockdb *lockdb, + int propid, + const char *name, + dav_prop_insert what, + apr_text_header *phdr, + dav_prop_insert *inserted, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *value = NULL; + dav_error *err; + request_rec *subreq = NULL; + + *inserted = DAV_PROP_INSERT_NOTDEF; + + /* Fast-path the common case. */ + if (propid == DAV_PROPID_CORE_UNKNOWN) + return NULL; + + switch (propid) + { + case DAV_PROPID_CORE_lockdiscovery: + { + dav_lock *locks; + + err = dav_svn__get_locks(lockdb, resource, DAV_GETLOCKS_RESOLVED, + &locks, result_pool, scratch_pool); + if (err != NULL) + return dav_push_error(r->pool, err->status, 0, + "DAV:lockdiscovery could not be " + "determined due to a problem fetching " + "the locks for this resource.", + err); + + /* Fast-path the no-locks case. */ + if (locks == NULL) + { + value = ""; + } + else + { + dav_buffer wb_lock = { 0 }; + int count = 0; + dav_lock *cur_lock; + + for (cur_lock = locks; cur_lock != NULL; cur_lock = cur_lock->next) + count++; + + dav_set_bufsize(scratch_pool, &wb_lock, count * 300); + + value = dav_lock_get_activelock(r, locks, &wb_lock); + value = apr_pstrdup(result_pool, wb_lock.buf); + } + break; + } + + case DAV_PROPID_CORE_supportedlock: + { + value = lockdb->hooks->get_supportedlock(resource); + break; + } + + case DAV_PROPID_CORE_getcontenttype: + { + subreq = do_prop_subreq(r, resource, scratch_pool); + + if (subreq->content_type != NULL) + value = apr_pstrdup(result_pool, subreq->content_type); + break; + } + + case DAV_PROPID_CORE_getcontentlanguage: + { + const char *lang; + + subreq = do_prop_subreq(r, resource, scratch_pool); + + lang = apr_table_get(subreq->headers_out, "Content-Language"); + if (lang) + value = apr_pstrdup(result_pool, lang); + break; + } + + default: + break; + } + + /* If something was supplied, then insert it. */ + if (value != NULL) + { + const char *s; + + if (what == DAV_PROP_INSERT_VALUE) + { + if (*value != '\0') + { + /* Use D: prefix to refer to the DAV: namespace URI. */ + s = apr_psprintf(result_pool, "%s" DEBUG_CR, + name, value, name); + } + else + { + /* Use D: prefix to refer to the DAV: namespace URI. */ + s = apr_psprintf(result_pool, "" DEBUG_CR, name); + } + } + else if (what == DAV_PROP_INSERT_NAME) + { + /* Use D: prefix to refer to the DAV: namespace URI. */ + s = apr_psprintf(result_pool, "" DEBUG_CR, name); + } + else + { + SVN_ERR_MALFUNCTION_NO_RETURN(); + } + + apr_text_append(result_pool, phdr, s); + + *inserted = what; + } + + return NULL; +} + +static dav_get_props_result +get_props(request_rec *r, + const dav_resource *resource, + dav_lockdb *lockdb, + apr_xml_doc *doc, + svn_repos_authz_func_t authz_func, + void *authz_baton, + apr_pool_t *pool) +{ + apr_xml_elem *elem = dav_find_child(doc->root, "prop"); + apr_text_header hdr_good = { 0 }; + apr_text_header hdr_bad = { 0 }; + apr_text_header hdr_ns = { 0 }; + int have_good = 0; + dav_get_props_result result = { 0 }; + char *marks_liveprop; + dav_xmlns_info *xi; + int xi_filled = 0; + + /* ### We should pass in TWO buffers -- one for keys, one for the marks. */ + + /* We will ALWAYS provide a "good" result, even if it is EMPTY. */ + apr_text_append(pool, &hdr_good, + "" DEBUG_CR "" DEBUG_CR); + + /* ### The marks should be in a buffer! */ + /* Allocate zeroed-memory for the marks. These marks indicate which + liveprop namespaces we've generated into the output xmlns buffer. */ + + /* Same for the liveprops. */ + marks_liveprop = apr_pcalloc(pool, dav_get_liveprop_ns_count() + 1); + + xi = dav_xmlns_create(pool); + + for (elem = elem->first_child; elem; elem = elem->next) + { + dav_elem_private *priv; + dav_error *err; + dav_prop_insert inserted; + dav_prop_name name; + int found; + + /* First try live property providers; if they don't handle the + property, then try looking it up in the dead property database. */ + if (elem->priv == NULL) + elem->priv = apr_pcalloc(pool, sizeof(*priv)); + + priv = elem->priv; + + /* Cache the propid; this function could be called many times. */ + if (priv->propid == 0) + find_liveprop(resource, elem, get_ns_uri(elem->ns, doc)); + + if (priv->provider) + { + /* Ask the provider (that defined this prop) to insert the prop. */ + inserted = dav_svn__insert_liveprop(resource, priv->propid, + DAV_PROP_INSERT_VALUE, &hdr_good, + pool, pool); + } + else + { + /* Insert the property. Returns 1 if an insertion was done. + ### Need to propagate the error to the caller... + ### Skip it for now, as if nothing was inserted. + */ + (void) insert_coreprop(r, resource, lockdb, priv->propid, + elem->name, DAV_PROP_INSERT_VALUE, + &hdr_good, &inserted, pool, pool); + } + + if (inserted == DAV_PROP_INSERT_VALUE) + { + const char *const *scan_ns_uri; + have_good = 1; + + if (priv->provider) + { + for (scan_ns_uri = priv->provider->namespace_uris; + *scan_ns_uri != NULL; ++scan_ns_uri) + { + long ns; + const char *s; + + ns = dav_get_liveprop_ns_index(*scan_ns_uri); + if (marks_liveprop[ns]) + continue; + marks_liveprop[ns] = 1; + + s = apr_psprintf(pool, " xmlns:lp%ld=\"%s\"", ns, + *scan_ns_uri); + apr_text_append(pool, &hdr_ns, s); + } + + /* Property added. Move on to the next property. */ + continue; + } + } + else if (inserted == DAV_PROP_INSERT_NOTDEF) + { + /* Nothing to do. Fall thru to allow property to be handled + as a dead property. */ + } + + /* The property wasn't a live property, so look in the dead property + database. */ + if (elem->ns == APR_XML_NS_NONE) + name.ns = ""; + else + name.ns = APR_XML_GET_URI_ITEM(doc->namespaces, elem->ns); + name.name = elem->name; + + err = dav_svn__output_deadprop_value(resource, &name, xi, &hdr_good, + authz_func, authz_baton, &found, + pool, pool); + if (err != NULL) + /* ### What to do? Continue doesn't seem right... */ + continue; + + if (found) + { + have_good = 1; + + /* If we haven't added the db's namespaces, then do so... */ + if (!xi_filled) + { + dav_svn__define_deadprop_namespaces(xi); + xi_filled = 1; + } + continue; + } + + /* Not found as a live OR dead property. Add a record to the "bad" + propstats. */ + + /* Make sure we've started our "bad" propstat. */ + if (hdr_bad.first == NULL) + { + apr_text_append(pool, &hdr_bad, + "" DEBUG_CR "" DEBUG_CR); + } + + /* Output this property's name (into the "bad" propstats). */ + output_prop_name(pool, &name, xi, &hdr_bad); + } + + apr_text_append(pool, &hdr_good, + "" DEBUG_CR + "HTTP/1.1 200 OK" DEBUG_CR + "" DEBUG_CR); + + /* Default to start with the good. */ + result.propstats = hdr_good.first; + + /* We may not have any "bad" results. */ + if (hdr_bad.first != NULL) + { + /* "close" the bad propstat. */ + apr_text_append(pool, &hdr_bad, + "" DEBUG_CR + "HTTP/1.1 404 Not Found" DEBUG_CR + "" DEBUG_CR); + + /* If there are no good props, then just return the bad. */ + if (!have_good) + result.propstats = hdr_bad.first; + else + /* Hook the bad propstat to the end of the good one. */ + hdr_good.last->next = hdr_bad.first; + } + + /* Add in all the various namespaces, and return them. */ + dav_xmlns_generate(xi, &hdr_ns); + result.xmlns = hdr_ns.first; + + return result; +} + +static dav_get_props_result +get_allprops(request_rec *r, + const dav_resource *resource, + dav_lockdb *lockdb, + dav_prop_insert what, + svn_repos_authz_func_t authz_func, + void *authz_baton, + apr_pool_t *pool) +{ + apr_text_header hdr = { 0 }; + apr_text_header hdr_ns = { 0 }; + dav_get_props_result result = { 0 }; + dav_prop_insert unused_inserted; + dav_xmlns_info *xi; + + /* Initialize the result with some start tags... */ + apr_text_append(pool, &hdr, + "" DEBUG_CR + "" DEBUG_CR); + + /* If there ARE properties, then scan them. */ + xi = dav_xmlns_create(pool); + + /* Define (up front) any namespaces the db might need. */ + dav_svn__define_deadprop_namespaces(xi); + + /* ### Need to propagate the error. */ + (void) dav_svn__output_all_deadprops(resource, xi, &hdr, what, + authz_func, authz_baton, + pool, pool); + + /* All namespaces have been entered into xi. Generate them into + the output now. */ + dav_xmlns_generate(xi, &hdr_ns); + + /* Add namespaces for all the liveprop providers. */ + dav_add_all_liveprop_xmlns(pool, &hdr_ns); + + dav_svn__output_all_liveprops(r, resource, what, &hdr, pool, pool); + + /* Insert the standard properties. + ### Need to propagate the error. */ + (void) insert_coreprop(r, resource, lockdb, + DAV_PROPID_CORE_supportedlock, "supportedlock", + what, &hdr, &unused_inserted, pool, pool); + (void) insert_coreprop(r, resource, lockdb, + DAV_PROPID_CORE_lockdiscovery, "lockdiscovery", + what, &hdr, &unused_inserted, pool, pool); + + /* ### There's special handling for and + ### properties in mod_dav. If these + ### aren't stored as dead properties, mod_dav does a subrequest + ### to get their values (if any). Need to check if we still need + ### that and adjust the code. */ + + /* Terminate the result. */ + apr_text_append(pool, &hdr, + "" DEBUG_CR + "HTTP/1.1 200 OK" DEBUG_CR + "" DEBUG_CR); + + result.propstats = hdr.first; + result.xmlns = hdr_ns.first; + return result; +} + +static dav_error * +send_props_for(request_rec *r, + const dav_resource *resource, + apr_xml_doc *doc, + apr_bucket_brigade *bb, + int propfind_type, + dav_lockdb *lockdb, + const char *relpath, + svn_boolean_t is_dir, + apr_pool_t *pool) +{ + dav_resource res = { 0 }; + dav_resource_private privres = { 0 }; + dav_get_props_result propstats = { 0 }; + const char *uri_path; + dav_svn__authz_read_baton authz_baton = { 0 }; + svn_repos_authz_func_t authz_func; + + authz_baton.r = r; + authz_baton.repos = resource->info->repos; + authz_func = dav_svn__authz_read_func(&authz_baton); + + /* Prepare the struct dav_resource. */ + privres = *resource->info; + uri_path = svn_urlpath__join(privres.uri_path->data, relpath, pool); + privres.uri_path = svn_stringbuf_create(uri_path, pool); + + /* This value will be NULL for resources that have no corresponding + resource within the repository (such as the PRIVATE resources, + Baselines, or Working Baselines). */ + if (privres.repos_path) + privres.repos_path = svn_fspath__join(privres.repos_path, + relpath, pool); + + res = *resource; + res.info = &privres; + res.exists = TRUE; + res.collection = is_dir; + res.uri = svn_urlpath__join(res.uri, relpath, pool); + + /* If we have a collection, then ensure the URI has a trailing slash. */ + if (is_dir && res.uri[strlen(res.uri) - 1] != '/') + res.uri = apr_pstrcat(pool, res.uri, "/", SVN_VA_NULL); + + switch (propfind_type) + { + case DAV_PROPFIND_IS_PROP: + propstats = get_props(r, &res, lockdb, doc, authz_func, + &authz_baton, pool); + break; + case DAV_PROPFIND_IS_PROPNAME: + propstats = get_allprops(r, &res, lockdb, DAV_PROP_INSERT_NAME, + authz_func, &authz_baton, pool); + break; + case DAV_PROPFIND_IS_ALLPROP: + propstats = get_allprops(r, &res, lockdb, DAV_PROP_INSERT_VALUE, + authz_func, &authz_baton, pool); + break; + default: + SVN_ERR_MALFUNCTION_NO_RETURN(); + } + + stream_response(r, &res, bb, 0, &propstats, pool); + + return NULL; +} + +static void +log_err(request_rec *r, dav_error *err, int level) +{ + dav_error *errscan; + + for (errscan = err; errscan != NULL; errscan = errscan->prev) + { + if (errscan->desc == NULL) + continue; + +#if AP_MODULE_MAGIC_AT_LEAST(20091119, 0) + ap_log_rerror(APLOG_MARK, level, errscan->aprerr, r, "%s [%d, #%d]", + errscan->desc, errscan->status, errscan->error_id); +#else + if (errscan->save_errno != 0) + { + errno = errscan->save_errno; + ap_log_rerror(APLOG_MARK, level, errno, r, "%s [%d, #%d]", + errscan->desc, errscan->status, errscan->error_id); + } + else + { + ap_log_rerror(APLOG_MARK, level, 0, r, "%s [%d, #%d]", + errscan->desc, errscan->status, errscan->error_id); + } +#endif + } +} + +/* Send a nice response back to the user. In most cases, Apache doesn't + allow us to provide details in the body about what happened. This + function allows us to completely specify the response body. */ +static int +error_response(request_rec *r, int status, const char *body) +{ + r->status = status; + + /* ### I really don't think this is needed; gotta test. */ + r->status_line = ap_get_status_line(status); + + ap_set_content_type(r, "text/html; charset=ISO-8859-1"); + + /* Begin the response now... */ + ap_rvputs(r, + DAV_RESPONSE_BODY_1, + r->status_line, + DAV_RESPONSE_BODY_2, + &r->status_line[4], + DAV_RESPONSE_BODY_3, + body, + DAV_RESPONSE_BODY_4, + ap_psignature("
\n", r), + DAV_RESPONSE_BODY_5, + NULL); + + /* The response has been sent. + ### Use of DONE obviates logging..! */ + return DONE; +} + +/* Send a "standardized" error response based on the error's + namespace & tag. */ +static int +error_response_tag(request_rec *r, dav_error *err) +{ + r->status = err->status; + + /* ### I really don't think this is needed; gotta test. */ + r->status_line = ap_get_status_line(err->status); + + ap_set_content_type(r, DAV_XML_CONTENT_TYPE); + + ap_rputs(DAV_XML_HEADER DEBUG_CR + "desc != NULL) + { + /* ### Should move this namespace somewhere (with the others!). */ + ap_rputs(" xmlns:m=\"http://apache.org/dav/xmlns\"", r); + } + + if (err->namespace != NULL) + { + ap_rprintf(r, + " xmlns:C=\"%s\">" DEBUG_CR + "" DEBUG_CR, + err->namespace, err->tagname); + } + else + { + ap_rprintf(r, + ">" DEBUG_CR + "" DEBUG_CR, err->tagname); + } + + /* Here's our mod_dav specific tag. */ + if (err->desc != NULL) + { + ap_rprintf(r, + "" DEBUG_CR + "%s" DEBUG_CR + "" DEBUG_CR, + err->error_id, + apr_xml_quote_string(r->pool, err->desc, 0)); + } + + ap_rputs("" DEBUG_CR, r); + + /* The response has been sent. + ### Use of DONE obviates logging..! */ + return DONE; +} + +static void +send_multistatus(request_rec *r, + int status, + dav_response *first, + apr_array_header_t *namespaces) +{ + apr_pool_t *subpool; + apr_bucket_brigade *bb + = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + begin_multistatus(bb, r, status, namespaces); + + apr_pool_create(&subpool, r->pool); + + for (; first != NULL; first = first->next) + { + apr_pool_clear(subpool); + send_one_response(first, bb, r->output_filters, subpool); + } + apr_pool_destroy(subpool); + + finish_multistatus(r, bb); +} + +/* Handle the standard error processing. ERR must be non-NULL. */ +static int +handle_err(request_rec *r, dav_error *err, dav_response *response) +{ + log_err(r, err, APLOG_ERR); + + if (response == NULL) + { + dav_error *stackerr = err; + + /* Our error messages are safe; tell Apache this. */ + apr_table_setn(r->notes, "verbose-error-to", "*"); + + /* Didn't get a multistatus response passed in, but we still + might be able to generate a standard response. + Search the error stack for an errortag. */ + while (stackerr != NULL && stackerr->tagname == NULL) + stackerr = stackerr->prev; + + if (stackerr != NULL && stackerr->tagname != NULL) + return error_response_tag(r, stackerr); + + return err->status; + } + + /* Send the multistatus and tell Apache the request/response is DONE. */ + send_multistatus(r, err->status, response, NULL); + return DONE; +} + +static dav_error * +walk_dir(request_rec *r, + const dav_resource *resource, + apr_xml_doc *doc, + apr_bucket_brigade *bb, + int propfind_type, + dav_lockdb *lockdb, + int depth, + const char *relpath, + apr_pool_t *pool) +{ + dav_error *derr; + apr_hash_t *children; + svn_error_t *serr; + apr_pool_t *iterpool; + apr_hash_index_t *hi; + const char *fspath; + + if (depth == 0) + return NULL; + + fspath = svn_fspath__join(resource->info->repos_path, relpath, pool); + serr = svn_fs_dir_entries(&children, resource->info->root.root, + fspath, pool); + if (serr != NULL) + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "could not fetch collection members", + r->pool); + + iterpool = svn_pool_create(pool); + for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi)) + { + const char *name = apr_hash_this_key(hi); + const svn_fs_dirent_t *dirent = apr_hash_this_val(hi); + + svn_pool_clear(iterpool); + + if (dav_svn__allow_read(resource->info->r, resource->info->repos, + svn_fspath__join(fspath, name, iterpool), + resource->info->root.rev, iterpool)) + { + const char *new_relpath; + + new_relpath = svn_relpath_join(relpath, name, iterpool); + derr = send_props_for(r, resource, doc, bb, propfind_type, lockdb, + new_relpath, dirent->kind == svn_node_dir, + iterpool); + if (derr != NULL) + return derr; + + if (dirent->kind == svn_node_dir) + { + derr = walk_dir(r, resource, doc, bb, propfind_type, lockdb, + depth - 1, new_relpath, iterpool); + if (derr != NULL) + return derr; + } + } + } + svn_pool_destroy(iterpool); + + return NULL; +} + +int dav_svn__method_propfind(request_rec *r) +{ + dav_resource *resource; + int depth; + dav_error *err; + int result; + apr_xml_doc *doc; + const apr_xml_elem *child; + int propfind_type; + apr_bucket_brigade *bb; + dav_lockdb *lockdb; + + /* Resolve the resource. */ + err = dav_svn__get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */, + &resource); + if (err != NULL) + return handle_err(r, err, NULL); + + if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) + { + /* Apache will supply a default error for this. */ + return HTTP_NOT_FOUND; + } + + if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) + { + /* dav_get_depth() supplies additional information for the + * default message. */ + return HTTP_BAD_REQUEST; + } + + if (depth == DAV_INFINITY && resource->collection) + { + /* Default is to DISALLOW these requests. */ + if (!dav_svn__get_allow_depthinfinity(r)) + return error_response(r, HTTP_FORBIDDEN, + apr_psprintf(r->pool, + "PROPFIND requests with a " + "Depth of \"infinity\" are " + "not allowed for %s.", + ap_escape_html(r->pool, r->uri))); + } + + if ((result = ap_xml_parse_input(r, &doc)) != OK) + { + return result; + } + + /* Note: doc == NULL if no request body. */ + + if (doc && !dav_validate_root(doc, "propfind")) + { + /* This supplies additional information for the default message. */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "The \"propfind\" element was not found."); + return HTTP_BAD_REQUEST; + } + + /* ### Validate that only one of these three elements is present. */ + if (doc == NULL || (child = dav_find_child(doc->root, "allprop")) != NULL) + { + /* Note: no request body implies allprop. */ + propfind_type = DAV_PROPFIND_IS_ALLPROP; + } + else if ((child = dav_find_child(doc->root, "propname")) != NULL) + { + propfind_type = DAV_PROPFIND_IS_PROPNAME; + } + else if ((child = dav_find_child(doc->root, "prop")) != NULL) + { + propfind_type = DAV_PROPFIND_IS_PROP; + } + else + { + /* "propfind" element must have one of the above three children. */ + + /* This supplies additional information for the default message. */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "The \"propfind\" element does not contain one of " + "the required child elements (the specific command)."); + return HTTP_BAD_REQUEST; + } + + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + /* ### Should open read-only. */ + lockdb = dav_svn__open_lockdb(r, 0 /* ro */, 0 /* force */); + + /* Send tag, with all doc->namespaces attached. */ + + /* NOTE: we *cannot* leave out the doc's namespaces from the + initial tag. if a 404 was generated for an HREF, + then we need to spit out the doc's namespaces for use by the + 404. Note that elements will override these ns0, + ns1, etc, but NOT within the scope for the + badprops. */ + begin_multistatus(bb, r, HTTP_MULTI_STATUS, + doc ? doc->namespaces : NULL); + + /* Cannot walk an SVNParentPath collection, there is no repository. */ + if (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION) + { + err = send_props_for(r, resource, doc, bb, propfind_type, lockdb, "", + resource->collection, r->pool); + + if (err == NULL && resource->collection && depth > 0) + { + if (resource->type == DAV_RESOURCE_TYPE_REGULAR) + { + /* Tell our logging subsystem that we're listing a directory. + + Note: if we cared, we could look at the 'User-Agent:' request + header and distinguish an svn client ('svn ls') from a generic + DAV client. */ + dav_svn__operational_log( + resource->info, + svn_log__get_dir(resource->info->repos_path, + resource->info->root.rev, TRUE, FALSE, + SVN_DIRENT_ALL, r->pool)); + + err = walk_dir(r, resource, doc, bb, propfind_type, lockdb, + depth, "", r->pool); + } + else if (resource->type == DAV_RESOURCE_TYPE_WORKING) + { + /* ### For now, let's say that working resources have no children. + ### Of course, this isn't true (or "right") for working + ### collections, but we don't actually need to do a walk + ### right now. */ + } + else + { + err = dav_svn__new_error( + r->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0, + "Walking the resource hierarchy can only be " + "done on 'regular' resources [at this time]."); + } + } + } + + if (err != NULL) + { + /* If an error occurred during the resource walk, there's + basically nothing we can do but abort the connection and + log an error. This is one of the limitations of HTTP; it + needs to "know" the entire status of the response before + generating it, which is just impossible in these streamy + response situations. */ + err = dav_push_error(r->pool, err->status, 0, + "Provider encountered an error while streaming" + " a multistatus PROPFIND response.", + err); + log_err(r, err, APLOG_ERR); + r->connection->aborted = 1; + return DONE; + } + + finish_multistatus(r, bb); + + return DONE; +} Property changes on: subversion/mod_dav_svn/propfind.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: subversion/mod_dav_svn/repos.c =================================================================== --- subversion/mod_dav_svn/repos.c (revision 1724205) +++ subversion/mod_dav_svn/repos.c (working copy) @@ -2606,7 +2606,39 @@ get_resource(request_rec *r, "software."); } +dav_error * +dav_svn__get_resource(request_rec *r, + int label_allowed, + int use_checked_in, + dav_resource **res_p) +{ + const char *label = NULL; + dav_error *err; + /* If the request target can be overridden, get any target selector. */ + if (label_allowed) + label = apr_table_get(r->headers_in, "label"); + + /* Resolve the resource. */ + err = get_resource(r, dav_svn__get_root_dir(r), label, + use_checked_in, res_p); + if (err != NULL) + { + err = dav_push_error(r->pool, err->status, 0, + "Could not fetch resource information.", err); + return err; + } + + /* ### Hmm. This doesn't feel like the right place or thing to do. + + If there were any input headers requiring a Vary header in the + response, add it now. */ + dav_add_vary_header(r, r, *res_p); + + return NULL; +} + + /* Helper func: return the parent of PATH, allocated in POOL. If IS_URLPATH is set, PATH is a urlpath; otherwise, it's either a relpath or an fspath. */