| /* |
| * liveprops.c: mod_dav_svn live property provider functions for Subversion |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2002 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| |
| |
| #include <httpd.h> |
| #include <util_xml.h> |
| #include <apr_tables.h> |
| #include <mod_dav.h> |
| |
| #include "dav_svn.h" |
| |
| |
| /* |
| ** The namespace URIs that we use. This list and the enumeration must |
| ** stay in sync. |
| */ |
| static const char * const dav_svn_namespace_uris[] = |
| { |
| "DAV:", |
| SVN_PROP_PREFIX, /* ### need to get this approved from IANA */ |
| |
| NULL /* sentinel */ |
| }; |
| enum { |
| DAV_SVN_NAMESPACE_URI_DAV, /* the DAV: namespace URI */ |
| DAV_SVN_NAMESPACE_URI /* the svn: namespace URI */ |
| }; |
| |
| #define SVN_RO_DAV_PROP(name) \ |
| { DAV_SVN_NAMESPACE_URI_DAV, #name, DAV_PROPID_##name, 0 } |
| #define SVN_RW_DAV_PROP(name) \ |
| { DAV_SVN_NAMESPACE_URI_DAV, #name, DAV_PROPID_##name, 1 } |
| #define SVN_RO_DAV_PROP2(sym,name) \ |
| { DAV_SVN_NAMESPACE_URI_DAV, #name, DAV_PROPID_##sym, 0 } |
| #define SVN_RW_DAV_PROP2(sym,name) \ |
| { DAV_SVN_NAMESPACE_URI_DAV, #name, DAV_PROPID_##sym, 1 } |
| |
| #define SVN_RO_SVN_PROP(sym,name) \ |
| { DAV_SVN_NAMESPACE_URI, #name, SVN_PROPID_##sym, 0 } |
| #define SVN_RW_SVN_PROP(sym,name) \ |
| { DAV_SVN_NAMESPACE_URI, #name, SVN_PROPID_##sym, 1 } |
| |
| enum { |
| SVN_PROPID_baseline_relative_path = 1 |
| }; |
| |
| static const dav_liveprop_spec dav_svn_props[] = |
| { |
| /* ### don't worry about these for a bit */ |
| #if 0 |
| /* WebDAV properties */ |
| SVN_RO_DAV_PROP(getcontentlanguage), /* ### make this r/w? */ |
| SVN_RO_DAV_PROP(getcontentlength), |
| SVN_RO_DAV_PROP(getcontenttype), /* ### make this r/w? */ |
| #endif |
| SVN_RO_DAV_PROP(getetag), |
| SVN_RO_DAV_PROP(creationdate), |
| #if 0 |
| SVN_RO_DAV_PROP(getlastmodified), |
| #endif |
| |
| /* DeltaV properties */ |
| SVN_RO_DAV_PROP2(baseline_collection, baseline-collection), |
| SVN_RO_DAV_PROP2(checked_in, checked-in), |
| SVN_RO_DAV_PROP2(version_controlled_configuration, |
| version-controlled-configuration), |
| SVN_RO_DAV_PROP2(version_name, version-name), |
| SVN_RO_DAV_PROP2(creator_displayname, creator-displayname), |
| |
| /* SVN properties */ |
| SVN_RO_SVN_PROP(baseline_relative_path, baseline-relative-path), |
| |
| { 0 } /* sentinel */ |
| }; |
| |
| static const dav_liveprop_group dav_svn_liveprop_group = |
| { |
| dav_svn_props, |
| dav_svn_namespace_uris, |
| &dav_svn_hooks_liveprop |
| }; |
| |
| |
| static dav_prop_insert dav_svn_insert_prop(const dav_resource *resource, |
| int propid, dav_prop_insert what, |
| ap_text_header *phdr) |
| { |
| const char *value; |
| const char *s; |
| apr_pool_t *p = resource->pool; |
| const dav_liveprop_spec *info; |
| int global_ns; |
| svn_error_t *serr; |
| |
| /* |
| ** None of SVN provider properties are defined if the resource does not |
| ** exist. Just bail for this case. |
| ** |
| ** Even though we state that the SVN properties are not defined, the |
| ** client cannot store dead values -- we deny that thru the is_writable |
| ** hook function. |
| */ |
| if (!resource->exists) |
| return DAV_PROP_INSERT_NOTSUPP; |
| |
| /* ### we may want to respond to DAV_PROPID_resourcetype for PRIVATE |
| ### resources. need to think on "proper" interaction with mod_dav */ |
| |
| switch (propid) |
| { |
| case DAV_PROPID_creationdate: |
| { |
| svn_revnum_t committed_rev = SVN_INVALID_REVNUM; |
| svn_string_t *committed_date = NULL; |
| |
| /* Get the CR field out of the node's skel. Notice that the |
| root object might be an ID root -or- a revision root. */ |
| serr = svn_fs_node_created_rev(&committed_rev, |
| resource->info->root.root, |
| DAV_SVN_REPOS_PATH(resource), p); |
| if (serr != NULL) |
| { |
| /* ### what to do? */ |
| value = "###error###"; |
| break; |
| } |
| |
| /* Get the date property of the created revision. */ |
| serr = svn_fs_revision_prop(&committed_date, |
| resource->info->repos->fs, |
| committed_rev, |
| SVN_PROP_REVISION_DATE, p); |
| if (serr != NULL) |
| { |
| /* ### what to do? */ |
| value = "###error###"; |
| break; |
| } |
| |
| if (committed_date == NULL) |
| return DAV_PROP_INSERT_NOTDEF; |
| |
| value = apr_xml_quote_string(p, committed_date->data, 1); |
| break; |
| } |
| |
| case DAV_PROPID_creator_displayname: |
| { |
| svn_revnum_t committed_rev = SVN_INVALID_REVNUM; |
| svn_string_t *last_author = NULL; |
| |
| /* Get the CR field out of the node's skel. Notice that the |
| root object might be an ID root -or- a revision root. */ |
| serr = svn_fs_node_created_rev(&committed_rev, |
| resource->info->root.root, |
| DAV_SVN_REPOS_PATH(resource), p); |
| if (serr != NULL) |
| { |
| /* ### what to do? */ |
| value = "###error###"; |
| break; |
| } |
| |
| /* Get the date property of the created revision. */ |
| serr = svn_fs_revision_prop(&last_author, |
| resource->info->repos->fs, |
| committed_rev, |
| SVN_PROP_REVISION_AUTHOR, p); |
| if (serr != NULL) |
| { |
| /* ### what to do? */ |
| value = "###error###"; |
| break; |
| } |
| |
| if (last_author == NULL) |
| return DAV_PROP_INSERT_NOTDEF; |
| |
| value = apr_xml_quote_string(p, last_author->data, 1); |
| break; |
| } |
| |
| case DAV_PROPID_getcontentlanguage: |
| /* ### need something here */ |
| return DAV_PROP_INSERT_NOTSUPP; |
| break; |
| |
| case DAV_PROPID_getcontentlength: |
| /* our property, but not defined on collection resources */ |
| if (resource->collection) |
| return DAV_PROP_INSERT_NOTSUPP; |
| |
| /* ### call svn_fs_file_length() */ |
| value = "0"; |
| break; |
| |
| case DAV_PROPID_getcontenttype: |
| /* ### need something here */ |
| /* ### maybe application/octet-stream and text/plain? */ |
| return DAV_PROP_INSERT_NOTSUPP; |
| break; |
| |
| case DAV_PROPID_getetag: |
| value = dav_svn_getetag(resource); |
| break; |
| |
| case DAV_PROPID_getlastmodified: |
| /* ### need a modified date */ |
| return DAV_PROP_INSERT_NOTSUPP; |
| break; |
| |
| case DAV_PROPID_baseline_collection: |
| /* only defined for Baselines */ |
| /* ### whoops. also defined for a VCC. deal with it later. */ |
| if (resource->type != DAV_RESOURCE_TYPE_VERSION || !resource->baselined) |
| return DAV_PROP_INSERT_NOTSUPP; |
| value = dav_svn_build_uri(resource->info->repos, DAV_SVN_BUILD_URI_BC, |
| resource->info->root.rev, NULL, |
| 1 /* add_href */, p); |
| break; |
| |
| case DAV_PROPID_checked_in: |
| /* only defined for VCRs (in the public space and in a BC space) */ |
| /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */ |
| if (resource->type == DAV_RESOURCE_TYPE_PRIVATE |
| && resource->info->restype == DAV_SVN_RESTYPE_VCC) |
| { |
| svn_revnum_t revnum; |
| |
| serr = svn_fs_youngest_rev(&revnum, resource->info->repos->fs, p); |
| if (serr != NULL) |
| { |
| /* ### what to do? */ |
| value = "###error###"; |
| break; |
| } |
| s = dav_svn_build_uri(resource->info->repos, |
| DAV_SVN_BUILD_URI_BASELINE, |
| revnum, NULL, |
| 0 /* add_href */, p); |
| value = apr_psprintf(p, "<D:href>%s</D:href>", |
| apr_xml_quote_string(p, s, 1)); |
| } |
| else if (resource->type != DAV_RESOURCE_TYPE_REGULAR) |
| { |
| /* not defined for this resource type */ |
| return DAV_PROP_INSERT_NOTSUPP; |
| } |
| else |
| { |
| svn_fs_id_t *id; |
| svn_stringbuf_t *stable_id; |
| |
| serr = svn_fs_node_id(&id, resource->info->root.root, |
| resource->info->repos_path, p); |
| if (serr != NULL) |
| { |
| /* ### what to do? */ |
| value = "###error###"; |
| break; |
| } |
| |
| stable_id = svn_fs_unparse_id(id, p); |
| svn_stringbuf_appendcstr(stable_id, resource->info->repos_path); |
| |
| s = dav_svn_build_uri(resource->info->repos, |
| DAV_SVN_BUILD_URI_VERSION, |
| SVN_INVALID_REVNUM, stable_id->data, |
| 0 /* add_href */, p); |
| value = apr_psprintf(p, "<D:href>%s</D:href>", |
| apr_xml_quote_string(p, s, 1)); |
| } |
| break; |
| |
| case DAV_PROPID_version_controlled_configuration: |
| /* only defined for VCRs */ |
| /* ### VCRs within the BC should not have this property! */ |
| /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */ |
| if (resource->type != DAV_RESOURCE_TYPE_REGULAR) |
| return DAV_PROP_INSERT_NOTSUPP; |
| value = dav_svn_build_uri(resource->info->repos, DAV_SVN_BUILD_URI_VCC, |
| SVN_IGNORED_REVNUM, NULL, |
| 1 /* add_href */, p); |
| break; |
| |
| case DAV_PROPID_version_name: |
| /* only defined for Version Resources and Baselines */ |
| /* ### whoops. also defined for VCRs. deal with it later. */ |
| if ((resource->type != DAV_RESOURCE_TYPE_VERSION) |
| && (! resource->versioned)) |
| return DAV_PROP_INSERT_NOTSUPP; |
| |
| if (resource->baselined) |
| { |
| /* just the revision number for baselines */ |
| value = apr_psprintf(p, "%ld", resource->info->root.rev); |
| } |
| else |
| { |
| svn_revnum_t committed_rev = SVN_INVALID_REVNUM; |
| |
| /* Get the CR field out of the node's skel. Notice that the |
| root object might be an ID root -or- a revision root. */ |
| serr = svn_fs_node_created_rev(&committed_rev, |
| resource->info->root.root, |
| DAV_SVN_REPOS_PATH(resource), p); |
| if (serr != NULL) |
| { |
| /* ### what to do? */ |
| value = "###error###"; |
| break; |
| } |
| |
| /* Convert the revision into a quoted string */ |
| s = apr_psprintf(p, "%ld", committed_rev); |
| value = apr_xml_quote_string(p, s, 1); |
| } |
| break; |
| |
| case SVN_PROPID_baseline_relative_path: |
| /* only defined for VCRs */ |
| /* ### VCRs within the BC should not have this property! */ |
| /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */ |
| if (resource->type != DAV_RESOURCE_TYPE_REGULAR) |
| return DAV_PROP_INSERT_NOTSUPP; |
| |
| /* drop the leading slash, so it is relative */ |
| s = resource->info->repos_path + 1; |
| value = apr_xml_quote_string(p, s, 1); |
| break; |
| |
| default: |
| /* ### what the heck was this property? */ |
| return DAV_PROP_INSERT_NOTDEF; |
| } |
| |
| /* assert: value != NULL */ |
| |
| /* get the information and global NS index for the property */ |
| global_ns = dav_get_liveprop_info(propid, &dav_svn_liveprop_group, &info); |
| |
| /* assert: info != NULL && info->name != NULL */ |
| |
| if (what == DAV_PROP_INSERT_NAME |
| || (what == DAV_PROP_INSERT_VALUE && *value == '\0')) { |
| s = apr_psprintf(p, "<lp%d:%s/>" DEBUG_CR, global_ns, info->name); |
| } |
| else if (what == DAV_PROP_INSERT_VALUE) { |
| s = apr_psprintf(p, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR, |
| global_ns, info->name, value, global_ns, info->name); |
| } |
| else { |
| /* assert: what == DAV_PROP_INSERT_SUPPORTED */ |
| s = apr_psprintf(p, |
| "<D:supported-live-property D:name=\"%s\" " |
| "D:namespace=\"%s\"/>" DEBUG_CR, |
| info->name, dav_svn_namespace_uris[info->ns]); |
| } |
| ap_text_append(p, phdr, s); |
| |
| /* we inserted whatever was asked for */ |
| return what; |
| } |
| |
| static int dav_svn_is_writable(const dav_resource *resource, int propid) |
| { |
| const dav_liveprop_spec *info; |
| |
| (void) dav_get_liveprop_info(propid, &dav_svn_liveprop_group, &info); |
| return info->is_writable; |
| } |
| |
| static dav_error * dav_svn_patch_validate(const dav_resource *resource, |
| const ap_xml_elem *elem, |
| int operation, void **context, |
| int *defer_to_dead) |
| { |
| /* NOTE: this function will not be called unless/until we have |
| modifiable (writable) live properties. */ |
| return NULL; |
| } |
| |
| static dav_error * dav_svn_patch_exec(const dav_resource *resource, |
| const ap_xml_elem *elem, |
| int operation, void *context, |
| dav_liveprop_rollback **rollback_ctx) |
| { |
| /* NOTE: this function will not be called unless/until we have |
| modifiable (writable) live properties. */ |
| return NULL; |
| } |
| |
| static void dav_svn_patch_commit(const dav_resource *resource, |
| int operation, void *context, |
| dav_liveprop_rollback *rollback_ctx) |
| { |
| /* NOTE: this function will not be called unless/until we have |
| modifiable (writable) live properties. */ |
| } |
| |
| static dav_error * dav_svn_patch_rollback(const dav_resource *resource, |
| int operation, void *context, |
| dav_liveprop_rollback *rollback_ctx) |
| { |
| /* NOTE: this function will not be called unless/until we have |
| modifiable (writable) live properties. */ |
| return NULL; |
| } |
| |
| const dav_hooks_liveprop dav_svn_hooks_liveprop = { |
| dav_svn_insert_prop, |
| dav_svn_is_writable, |
| dav_svn_namespace_uris, |
| dav_svn_patch_validate, |
| dav_svn_patch_exec, |
| dav_svn_patch_commit, |
| dav_svn_patch_rollback, |
| }; |
| |
| void dav_svn_gather_propsets(apr_array_header_t *uris) |
| { |
| /* ### what should we use for a URL to describe the available prop set? */ |
| /* ### for now... nothing. we will *only* have DAV properties */ |
| #if 0 |
| *(const char **)apr_array_push(uris) = |
| "<http://subversion.tigris.org/dav/propset/svn/1>"; |
| #endif |
| } |
| |
| int dav_svn_find_liveprop(const dav_resource *resource, |
| const char *ns_uri, const char *name, |
| const dav_hooks_liveprop **hooks) |
| { |
| /* don't try to find any liveprops if this isn't "our" resource */ |
| if (resource->hooks != &dav_svn_hooks_repos) |
| return 0; |
| |
| return dav_do_find_liveprop(ns_uri, name, &dav_svn_liveprop_group, hooks); |
| } |
| |
| void dav_svn_insert_all_liveprops(request_rec *r, const dav_resource *resource, |
| dav_prop_insert what, ap_text_header *phdr) |
| { |
| const dav_liveprop_spec *spec; |
| |
| /* don't insert any liveprops if this isn't "our" resource */ |
| if (resource->hooks != &dav_svn_hooks_repos) |
| return; |
| |
| if (!resource->exists) { |
| /* a lock-null resource */ |
| /* |
| ** ### technically, we should insert empty properties. dunno offhand |
| ** ### what part of the spec said this, but it was essentially thus: |
| ** ### "the properties should be defined, but may have no value". |
| */ |
| return; |
| } |
| |
| for (spec = dav_svn_props; spec->name != NULL; ++spec) |
| { |
| (void) dav_svn_insert_prop(resource, spec->propid, what, phdr); |
| } |
| |
| /* ### we know the others aren't defined as liveprops */ |
| } |
| |
| void dav_svn_register_uris(apr_pool_t *p) |
| { |
| /* register the namespace URIs */ |
| dav_register_liveprop_group(p, &dav_svn_liveprop_group); |
| } |
| |
| |
| /* |
| * local variables: |
| * eval: (load-file "../../tools/dev/svn-dev.el") |
| * end: |
| */ |