| /* |
| * props.c : routines for fetching DAV properties |
| * |
| * ==================================================================== |
| * 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 <apr_pools.h> |
| #include <apr_tables.h> |
| #include <apr_strings.h> |
| #define APR_WANT_STRFUNC |
| #include <apr_want.h> |
| |
| #include <ne_basic.h> |
| #include <ne_props.h> |
| #include <ne_xml.h> |
| |
| #include "svn_error.h" |
| #include "svn_delta.h" |
| #include "svn_ra.h" |
| #include "svn_path.h" |
| |
| #include "ra_dav.h" |
| |
| |
| /* some definitions of various properties that may be fetched */ |
| const ne_propname svn_ra_dav__vcc_prop = { |
| "DAV:", "version-controlled-configuration" |
| }; |
| const ne_propname svn_ra_dav__checked_in_prop = { |
| "DAV:", "checked-in" |
| }; |
| |
| typedef struct { |
| ne_xml_elmid id; |
| const char *name; |
| int is_property; /* is it a property, or part of some structure? */ |
| } elem_defn; |
| |
| static const elem_defn elem_definitions[] = |
| { |
| /* DAV elements */ |
| { ELEM_baseline_coll, SVN_RA_DAV__PROP_BASELINE_COLLECTION, 0 }, |
| { ELEM_checked_in, SVN_RA_DAV__PROP_CHECKED_IN, 0 }, |
| { ELEM_vcc, SVN_RA_DAV__PROP_VCC, 0 }, |
| { ELEM_version_name, SVN_RA_DAV__PROP_VERSION_NAME, 1 }, |
| |
| /* SVN elements */ |
| { ELEM_baseline_relpath, SVN_RA_DAV__PROP_BASELINE_RELPATH, 1 }, |
| |
| { 0 } |
| }; |
| |
| static const struct ne_xml_elm neon_descriptions[] = |
| { |
| /* DAV elements */ |
| { "DAV:", "baseline-collection", ELEM_baseline_coll, NE_XML_CDATA }, |
| { "DAV:", "checked-in", ELEM_checked_in, 0 }, |
| { "DAV:", "collection", ELEM_collection, NE_XML_CDATA }, |
| { "DAV:", "href", NE_ELM_href, NE_XML_CDATA }, |
| { "DAV:", "resourcetype", ELEM_resourcetype, 0 }, |
| { "DAV:", "version-controlled-configuration", ELEM_vcc, 0 }, |
| { "DAV:", "version-name", ELEM_version_name, NE_XML_CDATA }, |
| |
| /* SVN elements */ |
| { SVN_PROP_PREFIX, "baseline-relative-path", ELEM_baseline_relpath, |
| NE_XML_CDATA }, |
| |
| { NULL } |
| }; |
| |
| typedef struct { |
| /* PROPS: URL-PATH -> RESOURCE (const char * -> svn_ra_dav_resource_t *) */ |
| apr_hash_t *props; |
| |
| apr_pool_t *pool; |
| |
| ne_propfind_handler *dph; |
| |
| } prop_ctx_t; |
| |
| /* when we begin a checkout, we fetch these from the "public" resources to |
| steer us towards a Baseline Collection. we fetch the resourcetype to |
| verify that we're accessing a collection. */ |
| static const ne_propname starting_props[] = |
| { |
| { "DAV:", "version-controlled-configuration" }, |
| { SVN_PROP_PREFIX, "baseline-relative-path" }, |
| { "DAV:", "resourcetype" }, |
| { NULL } |
| }; |
| |
| /* when speaking to a Baseline to reach the Baseline Collection, fetch these |
| properties. */ |
| static const ne_propname baseline_props[] = |
| { |
| { "DAV:", "baseline-collection" }, |
| { "DAV:", "version-name" }, |
| { NULL } |
| }; |
| |
| |
| |
| /* look up an element definition. may return NULL if the elem is not |
| recognized. */ |
| static const elem_defn *defn_from_id(ne_xml_elmid id) |
| { |
| const elem_defn *defn; |
| |
| for (defn = elem_definitions; defn->name != NULL; ++defn) |
| { |
| if (id == defn->id) |
| return defn; |
| } |
| |
| return NULL; |
| } |
| |
| static void *create_private(void *userdata, const char *url) |
| { |
| prop_ctx_t *pc = userdata; |
| struct uri parsed_url; |
| char *url_path; |
| svn_ra_dav_resource_t *r = apr_pcalloc(pc->pool, sizeof(*r)); |
| apr_size_t len; |
| svn_string_t my_url; |
| svn_stringbuf_t *url_str; |
| |
| my_url.data = url; |
| my_url.len = strlen(url); |
| url_str = svn_path_uri_decode(&my_url, pc->pool); |
| |
| r->pool = pc->pool; |
| |
| /* parse the PATH element out of the URL |
| |
| Note: mod_dav does not (currently) use an absolute URL, but simply a |
| server-relative path (i.e. this uri_parse is effectively a no-op). |
| */ |
| (void) uri_parse(url_str->data, &parsed_url, NULL); |
| url_path = apr_pstrdup(pc->pool, parsed_url.path); |
| uri_free(&parsed_url); |
| |
| /* clean up trailing slashes from the URL */ |
| len = strlen(url_path); |
| if (len > 1 && url_path[len - 1] == '/') |
| url_path[len - 1] = '\0'; |
| r->url = url_path; |
| |
| /* the properties for this resource */ |
| r->propset = apr_hash_make(pc->pool); |
| |
| /* store this resource into the top-level hash table */ |
| apr_hash_set(pc->props, url_path, APR_HASH_KEY_STRING, r); |
| |
| return r; |
| } |
| |
| static int add_to_hash(void *userdata, const ne_propname *pname, |
| const char *value, const ne_status *status) |
| { |
| svn_ra_dav_resource_t *r = userdata; |
| const char *name; |
| |
| name = apr_pstrcat(r->pool, pname->nspace, pname->name, NULL); |
| value = apr_pstrdup(r->pool, value); |
| |
| /* ### woah... what about a binary VALUE with a NULL character? */ |
| apr_hash_set(r->propset, name, APR_HASH_KEY_STRING, value); |
| |
| return 0; |
| } |
| |
| static void process_results(void *userdata, const char *uri, |
| const ne_prop_result_set *rset) |
| { |
| /* prop_ctx_t *pc = userdata; */ |
| svn_ra_dav_resource_t *r = ne_propset_private(rset); |
| |
| /* ### should use ne_propset_status(rset) to determine whether the |
| * ### PROPFIND failed for the properties we're interested in. */ |
| (void) ne_propset_iterate(rset, add_to_hash, r); |
| } |
| |
| static int validate_element(void *userdata, ne_xml_elmid parent, ne_xml_elmid child) |
| { |
| switch (parent) |
| { |
| case NE_ELM_prop: |
| switch (child) |
| { |
| case ELEM_baseline_coll: |
| case ELEM_baseline_relpath: |
| case ELEM_checked_in: |
| case ELEM_resourcetype: |
| case ELEM_vcc: |
| case ELEM_version_name: |
| return NE_XML_VALID; |
| |
| default: |
| /* some other, unrecognized property */ |
| return NE_XML_DECLINE; |
| } |
| |
| case ELEM_baseline_coll: |
| case ELEM_checked_in: |
| case ELEM_vcc: |
| if (child == NE_ELM_href) |
| return NE_XML_VALID; |
| else |
| return NE_XML_DECLINE; /* not concerned with other types */ |
| |
| case ELEM_resourcetype: |
| if (child == ELEM_collection) |
| return NE_XML_VALID; |
| else |
| return NE_XML_DECLINE; /* not concerned with other types (### now) */ |
| |
| default: |
| return NE_XML_DECLINE; |
| } |
| |
| /* NOTREACHED */ |
| } |
| |
| static int start_element(void *userdata, const struct ne_xml_elm *elm, |
| const char **atts) |
| { |
| prop_ctx_t *pc = userdata; |
| svn_ra_dav_resource_t *r = ne_propfind_current_private(pc->dph); |
| |
| switch (elm->id) |
| { |
| case ELEM_collection: |
| r->is_collection = 1; |
| break; |
| |
| case ELEM_baseline_coll: |
| case ELEM_checked_in: |
| case ELEM_vcc: |
| /* each of these contains a DAV:href element that we want to process */ |
| r->href_parent = elm->id; |
| break; |
| |
| default: |
| /* nothing to do for these */ |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int end_element(void *userdata, const struct ne_xml_elm *elm, |
| const char *cdata) |
| { |
| prop_ctx_t *pc = userdata; |
| svn_ra_dav_resource_t *r = ne_propfind_current_private(pc->dph); |
| const char *name; |
| |
| if (elm->id == NE_ELM_href) |
| { |
| /* use the parent element's name, not the href */ |
| const elem_defn *parent_defn = defn_from_id(r->href_parent); |
| |
| name = parent_defn ? parent_defn->name : NULL; |
| |
| /* if name == NULL, then we don't know about this DAV:href. leave name |
| NULL so that we don't store a property. */ |
| } |
| else |
| { |
| const elem_defn *defn = defn_from_id(elm->id); |
| |
| /* if this element isn't a property, then skip it */ |
| if (defn == NULL || !defn->is_property) |
| return 0; |
| |
| name = defn->name; |
| } |
| |
| if (name != NULL) |
| apr_hash_set(r->propset, name, APR_HASH_KEY_STRING, |
| apr_pstrdup(pc->pool, cdata)); |
| |
| return 0; |
| } |
| |
| svn_error_t * svn_ra_dav__get_props(apr_hash_t **results, |
| ne_session *sess, |
| const char *url, |
| int depth, |
| const char *label, |
| const ne_propname *which_props, |
| apr_pool_t *pool) |
| { |
| ne_xml_parser *hip; |
| int rv; |
| prop_ctx_t pc = { 0 }; |
| svn_string_t my_url; |
| svn_stringbuf_t *url_str; |
| ne_request *req; |
| int status_code; |
| |
| my_url.data = url; |
| my_url.len = strlen(url); |
| url_str = svn_path_uri_encode(&my_url, pool); |
| |
| pc.pool = pool; |
| pc.props = apr_hash_make(pc.pool); |
| |
| pc.dph = ne_propfind_create(sess, url_str->data, depth); |
| ne_propfind_set_private(pc.dph, create_private, &pc); |
| hip = ne_propfind_get_parser(pc.dph); |
| ne_xml_push_handler(hip, neon_descriptions, |
| validate_element, start_element, end_element, &pc); |
| req = ne_propfind_get_request(pc.dph); |
| |
| if (label != NULL) |
| { |
| /* get the request pointer and add a Label header */ |
| ne_add_request_header(req, "Label", label); |
| } |
| |
| if (which_props) |
| { |
| rv = ne_propfind_named(pc.dph, which_props, process_results, &pc); |
| } |
| else |
| { |
| rv = ne_propfind_allprop(pc.dph, process_results, &pc); |
| } |
| |
| status_code = ne_get_status(req)->code; |
| |
| ne_propfind_destroy(pc.dph); |
| |
| if (rv != NE_OK) |
| { |
| const char *msg = apr_psprintf(pool, "PROPFIND of %s", url_str->data); |
| return svn_ra_dav__convert_error(sess, msg, rv, pool); |
| } |
| |
| if (404 == status_code) |
| return svn_error_createf(SVN_ERR_RA_PROPS_NOT_FOUND, 0, NULL, pool, |
| "Failed to fetch props for '%s'", url_str->data); |
| |
| *results = pc.props; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * svn_ra_dav__get_props_resource(svn_ra_dav_resource_t **rsrc, |
| ne_session *sess, |
| const char *url, |
| const char *label, |
| const ne_propname *which_props, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *props; |
| char * url_path = apr_pstrdup(pool, url); |
| int len = strlen(url); |
| /* Clean up any trailing slashes. */ |
| if (len > 1 && url[len - 1] == '/') |
| url_path[len - 1] = '\0'; |
| |
| SVN_ERR( svn_ra_dav__get_props(&props, sess, url_path, NE_DEPTH_ZERO, |
| label, which_props, pool) ); |
| |
| /* ### HACK. We need to have the client canonicalize paths, get rid |
| of double slashes and such. This check is just a check against |
| non-SVN servers; in the long run we want to re-enable this. */ |
| if (1 || label != NULL) |
| { |
| /* pick out the first response: the URL requested will not match |
| * the response href. */ |
| apr_hash_index_t *hi = apr_hash_first(pool, props); |
| |
| if (hi) |
| { |
| void *ent; |
| apr_hash_this(hi, NULL, NULL, &ent); |
| *rsrc = ent; |
| } |
| } |
| else |
| { |
| *rsrc = apr_hash_get(props, url_path, APR_HASH_KEY_STRING); |
| } |
| |
| if (*rsrc == NULL) |
| { |
| /* ### hmmm, should have been in there... */ |
| return svn_error_createf(APR_EGENERAL, 0, NULL, pool, |
| "failed to find label \"%s\" for url \"%s\"", |
| label, url_path); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * svn_ra_dav__get_one_prop(const svn_string_t **propval, |
| ne_session *sess, |
| const char *url, |
| const char *label, |
| const ne_propname *propname, |
| apr_pool_t *pool) |
| { |
| svn_ra_dav_resource_t *rsrc; |
| ne_propname props[2] = { { 0 } }; |
| const char *name; |
| const char *value; |
| svn_string_t *sv; |
| |
| props[0] = *propname; |
| SVN_ERR( svn_ra_dav__get_props_resource(&rsrc, sess, url, label, props, |
| pool) ); |
| |
| name = apr_pstrcat(pool, propname->nspace, propname->name, NULL); |
| value = apr_hash_get(rsrc->propset, name, APR_HASH_KEY_STRING); |
| if (value == NULL) |
| { |
| /* ### need an SVN_ERR here */ |
| return svn_error_createf(SVN_ERR_RA_PROPS_NOT_FOUND, 0, NULL, pool, |
| "%s was not present on the resource.", name); |
| } |
| |
| /* ### hmm. we can't deal with embedded NULLs right now... */ |
| sv = apr_palloc(pool, sizeof(*sv)); |
| sv->data = value; |
| sv->len = strlen(value); |
| *propval = sv; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t *svn_ra_dav__get_baseline_info(svn_boolean_t *is_dir, |
| svn_string_t *bc_url, |
| svn_string_t *bc_relative, |
| svn_revnum_t *latest_rev, |
| ne_session *sess, |
| const char *url, |
| svn_revnum_t revision, |
| apr_pool_t *pool) |
| { |
| svn_ra_dav_resource_t *rsrc; |
| const char *vcc; |
| struct uri parsed_url; |
| svn_string_t *my_bc_url, *my_bc_relative; |
| const char *lopped_path = ""; |
| |
| /* ### we may be able to replace some/all of this code with an |
| ### expand-property REPORT when that is available on the server. */ |
| |
| /* ------------------------------------------------------------------- |
| STEP 1 |
| |
| Fetch the following properties from the given URL (or, if URL no |
| longer exists in HEAD, get the properties from the nearest |
| still-existing parent resource): |
| |
| *) DAV:version-controlled-configuration so that we can reach the |
| baseline information. |
| |
| *) svn:baseline-relative-path so that we can find this resource |
| within a Baseline Collection. If we need to search up parent |
| directories, then the relative path is this property value |
| *plus* any trailing components we had to chop off. |
| |
| *) DAV:resourcetype so that we can identify whether this resource |
| is a collection or not -- assuming we never had to search up |
| parent directories. |
| */ |
| |
| /* Split the url into it's component pieces (schema, host, path, |
| etc). We want the path part. */ |
| uri_parse (url, &parsed_url, NULL); |
| |
| /* ### do we want to optimize the props we fetch, based on what the |
| ### user has requested? i.e. omit resourcetype when is_dir is NULL |
| ### and omit relpath when bc_relative is NULL. */ |
| |
| { |
| /* Try to get the starting_props from the public url. If the |
| resource no longer exists in HEAD, we'll get a failure. That's |
| fine: just keep removing components and trying to get the |
| starting_props from parent directories. */ |
| svn_error_t *err; |
| svn_stringbuf_t *path_s = svn_stringbuf_create (parsed_url.path, pool); |
| |
| while (! svn_path_is_empty (path_s)) |
| { |
| err = svn_ra_dav__get_props_resource(&rsrc, sess, path_s->data, |
| NULL, starting_props, pool); |
| if (! err) |
| break; /* found an existing parent! */ |
| |
| if (err->apr_err != SVN_ERR_RA_REQUEST_FAILED) |
| return err; /* found a _real_ error */ |
| |
| /* else... lop off the basename and try again. */ |
| lopped_path = svn_path_join (lopped_path, |
| svn_path_basename (path_s->data, pool), |
| pool); |
| svn_path_remove_component (path_s); |
| |
| } |
| |
| if (svn_path_is_empty (path_s)) |
| /* entire URL was bogus; not a single part of it exists in |
| the repository! */ |
| return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, 0, NULL, pool, |
| "No part of path '%s' was found in" |
| "repository HEAD.", parsed_url.path); |
| } |
| |
| uri_free(&parsed_url); |
| |
| vcc = apr_hash_get(rsrc->propset, SVN_RA_DAV__PROP_VCC, APR_HASH_KEY_STRING); |
| if (vcc == NULL) |
| { |
| /* ### better error reporting... */ |
| |
| /* ### need an SVN_ERR here */ |
| return svn_error_create(APR_EGENERAL, 0, NULL, pool, |
| "The VCC property was not found on the " |
| "resource."); |
| } |
| |
| /* Allocate our own bc_relative path. */ |
| my_bc_relative = svn_string_create ("", pool); |
| { |
| const char *relative_path; |
| |
| relative_path = apr_hash_get(rsrc->propset, |
| SVN_RA_DAV__PROP_BASELINE_RELPATH, |
| APR_HASH_KEY_STRING); |
| if (relative_path == NULL) |
| { |
| /* ### better error reporting... */ |
| /* ### need an SVN_ERR here */ |
| return svn_error_create(APR_EGENERAL, 0, NULL, pool, |
| "The relative-path property was not " |
| "found on the resource."); |
| } |
| |
| /* don't forget to tack on the parts we lopped off in order |
| to find the VCC... */ |
| my_bc_relative->data = svn_path_join (relative_path, lopped_path, pool); |
| my_bc_relative->len = strlen(my_bc_relative->data); |
| } |
| |
| /* if they want the relative path (could be, they're just trying to find |
| the baseline collection), then return it */ |
| if (bc_relative != NULL) |
| { |
| bc_relative->data = my_bc_relative->data; |
| bc_relative->len = my_bc_relative->len; |
| } |
| |
| /* shortcut: no need to do more work if the data isn't needed. */ |
| if (bc_url == NULL && latest_rev == NULL && is_dir == NULL) |
| return SVN_NO_ERROR; |
| |
| /* ------------------------------------------------------------------- |
| STEP 2 |
| |
| We have the Version Controlled Configuration (VCC). From here, we |
| need to reach the Baseline for specified revision. |
| |
| If the revision is SVN_INVALID_REVNUM, then we're talking about |
| the HEAD revision. We have one extra step to reach the Baseline: |
| |
| *) Fetch the DAV:checked-in from the VCC; it points to the Baseline. |
| |
| If we have a specific revision, then we use a Label header when |
| fetching props from the VCC. This will direct us to the Baseline |
| with that label (in this case, the label == the revision number). |
| |
| From the Baseline, we fetch the following properties: |
| |
| *) DAV:baseline-collection, which is a complete tree of the Baseline |
| (in SVN terms, this tree is rooted at a specific revision) |
| |
| *) DAV:version-name to get the revision of the Baseline that we are |
| querying. When asking about the HEAD, this tells us its revision. |
| */ |
| |
| if (revision == SVN_INVALID_REVNUM) |
| { |
| /* Fetch the latest revision */ |
| |
| const svn_string_t *baseline; |
| |
| /* Get the Baseline from the DAV:checked-in value, then fetch its |
| DAV:baseline-collection property. */ |
| /* ### should wrap this with info about rsrc==VCC */ |
| SVN_ERR( svn_ra_dav__get_one_prop(&baseline, sess, vcc, NULL, |
| &svn_ra_dav__checked_in_prop, pool) ); |
| |
| /* ### do we want to optimize the props we fetch, based on what the |
| ### user asked for? i.e. omit version-name if latest_rev is NULL */ |
| SVN_ERR( svn_ra_dav__get_props_resource(&rsrc, sess, |
| baseline->data, NULL, |
| baseline_props, pool) ); |
| } |
| else |
| { |
| /* Fetch a specific revision */ |
| |
| char label[20]; |
| |
| /* ### send Label hdr, get DAV:baseline-collection [from the baseline] */ |
| |
| apr_snprintf(label, sizeof(label), "%ld", revision); |
| |
| /* ### do we want to optimize the props we fetch, based on what the |
| ### user asked for? i.e. omit version-name if latest_rev is NULL */ |
| SVN_ERR( svn_ra_dav__get_props_resource(&rsrc, sess, vcc, label, |
| baseline_props, pool) ); |
| } |
| |
| /* rsrc now points at the Baseline. We will checkout from the |
| DAV:baseline-collection. The revision we are checking out is in |
| DAV:version-name */ |
| |
| /* Allocate our own copy of bc_url regardless. */ |
| my_bc_url = svn_string_create ("", pool); |
| my_bc_url->data = apr_hash_get(rsrc->propset, |
| SVN_RA_DAV__PROP_BASELINE_COLLECTION, |
| APR_HASH_KEY_STRING); |
| if (my_bc_url->data == NULL) |
| { |
| /* ### better error reporting... */ |
| /* ### need an SVN_ERR here */ |
| return svn_error_create(APR_EGENERAL, 0, NULL, pool, |
| "DAV:baseline-collection was not present " |
| "on the baseline resource."); |
| } |
| my_bc_url->len = strlen(my_bc_url->data); |
| |
| /* maybe return bc_url to the caller */ |
| if (bc_url != NULL) |
| { |
| bc_url->data = my_bc_url->data; |
| bc_url->len = my_bc_url->len; |
| } |
| |
| if (latest_rev != NULL) |
| { |
| const char *vsn_name; |
| |
| vsn_name = apr_hash_get(rsrc->propset, |
| SVN_RA_DAV__PROP_VERSION_NAME, |
| APR_HASH_KEY_STRING); |
| if (vsn_name == NULL) |
| { |
| /* ### better error reporting... */ |
| |
| /* ### need an SVN_ERR here */ |
| return svn_error_create(APR_EGENERAL, 0, NULL, pool, |
| "DAV:version-name was not present on the " |
| "baseline resource."); |
| } |
| *latest_rev = SVN_STR_TO_REV(vsn_name); |
| } |
| |
| if (is_dir != NULL) |
| { |
| /* query the DAV:resourcetype of the full, assembled URL. */ |
| char *full_bc_url = svn_path_join(my_bc_url->data, |
| my_bc_relative->data, |
| pool); |
| SVN_ERR( svn_ra_dav__get_props_resource(&rsrc, sess, full_bc_url, |
| NULL, starting_props, pool)); |
| |
| *is_dir = rsrc->is_collection; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_ra_dav__do_check_path(svn_node_kind_t *kind, |
| void *session_baton, |
| const char *path, |
| svn_revnum_t revision) |
| { |
| svn_ra_session_t *ras = session_baton; |
| svn_stringbuf_t *url = svn_stringbuf_create (ras->url, ras->pool); |
| svn_error_t *err; |
| svn_boolean_t is_dir; |
| |
| /* ### For now, using svn_ra_dav__get_baseline_info() works because |
| we only have three possibilities: dir, file, or none. When we |
| add symlinks, we will need to do something different. Here's one |
| way described by Greg Stein: |
| |
| That is a PROPFIND (Depth:0) for the DAV:resourcetype property. |
| |
| You can use the svn_ra_dav__get_one_prop() function to fetch |
| it. If the PROPFIND fails with a 404, then you have |
| svn_node_none. If the resulting property looks like: |
| |
| <D:resourcetype> |
| <D:collection/> |
| </D:resourcetype> |
| |
| Then it is a collection (directory; svn_node_dir). Otherwise, |
| it is a regular resource (svn_node_file). |
| |
| The harder part is parsing the resourcetype property. "Proper" |
| parsing means treating it as an XML property and looking for |
| the DAV:collection element in there. To do that, however, means |
| that get_one_prop() can't be used. I think there may be some |
| Neon functions for parsing XML properties; we'd need to |
| look. That would probably be the best approach. (an alternative |
| is to use apr_xml_* parsing functions on the returned string; |
| get back a DOM-like thing, and look for the element). |
| */ |
| |
| /* If we were given a relative path to append, append it. */ |
| if (path) |
| svn_path_add_component_nts(url, path); |
| |
| err = svn_ra_dav__get_baseline_info(&is_dir, |
| NULL, |
| NULL, |
| NULL, |
| ras->sess, |
| url->data, |
| revision, |
| ras->pool); |
| |
| if (err == SVN_NO_ERROR) |
| { |
| if (is_dir) |
| *kind = svn_node_dir; |
| else |
| *kind = svn_node_file; |
| } |
| else /* some error, read the comment below */ |
| { |
| /* ### This is way too general. We should only convert the |
| * error to `svn_node_none' if we're sure that's what the error |
| * means; for example, the test used to be this |
| * |
| * (err && (err->apr_err == SVN_ERR_RA_PROPS_NOT_FOUND)) |
| * |
| * which seemed reasonable... |
| * |
| * However, right now svn_ra_dav__get_props() returns a generic |
| * error when the entity doesn't exist. It's APR_EGENERAL or |
| * something like that, and ne_get_status(req)->code == 500, not |
| * 404. I don't know whether this is something that can be |
| * improved just in that function, or if the server will need to |
| * be more descriptive about the error. Greg, thoughts? |
| */ |
| |
| *kind = svn_node_none; |
| return SVN_NO_ERROR; |
| } |
| |
| return err; |
| } |
| |
| |
| |
| /* |
| * local variables: |
| * eval: (load-file "../../tools/dev/svn-dev.el") |
| * end: */ |