| /* |
| * props.c : routines for fetching DAV properties |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2007 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 "svn_error.h" |
| #include "svn_path.h" |
| #include "svn_dav.h" |
| #include "svn_base64.h" |
| #include "svn_xml.h" |
| #include "svn_time.h" |
| #include "svn_pools.h" |
| #include "svn_props.h" |
| #include "../libsvn_ra/ra_loader.h" |
| |
| #include "private/svn_dav_protocol.h" |
| #include "svn_private_config.h" |
| |
| #include "ra_neon.h" |
| |
| |
| /* some definitions of various properties that may be fetched */ |
| const ne_propname svn_ra_neon__vcc_prop = { |
| "DAV:", "version-controlled-configuration" |
| }; |
| const ne_propname svn_ra_neon__checked_in_prop = { |
| "DAV:", "checked-in" |
| }; |
| |
| /* 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" }, |
| { "DAV:", "resourcetype" }, |
| { SVN_DAV_PROP_NS_DAV, "baseline-relative-path" }, |
| { SVN_DAV_PROP_NS_DAV, "repository-uuid"}, |
| { NULL } |
| }; |
| |
| /* when speaking to a Baseline to reach the Baseline Collection, fetch these |
| properties. */ |
| static const ne_propname baseline_props[] = |
| { |
| { "DAV:", "baseline-collection" }, |
| { "DAV:", SVN_DAV__VERSION_NAME }, |
| { NULL } |
| }; |
| |
| |
| |
| /*** Propfind Implementation ***/ |
| |
| typedef struct { |
| svn_ra_neon__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[] = |
| { |
| /*** NOTE: Make sure that every item in here is also represented in |
| propfind_elements[] ***/ |
| |
| /* DAV elements */ |
| { ELEM_multistatus, "DAV:multistatus", 0 }, |
| { ELEM_response, "DAV:response", 0 }, |
| { ELEM_href, "DAV:href", SVN_RA_NEON__XML_CDATA }, |
| { ELEM_propstat, "DAV:propstat", 0 }, |
| { ELEM_prop, "DAV:prop", 0 }, |
| { ELEM_status, "DAV:status", SVN_RA_NEON__XML_CDATA }, |
| { ELEM_baseline, "DAV:baseline", SVN_RA_NEON__XML_CDATA }, |
| { ELEM_collection, "DAV:collection", SVN_RA_NEON__XML_CDATA }, |
| { ELEM_resourcetype, "DAV:resourcetype", 0 }, |
| { ELEM_baseline_coll, SVN_RA_NEON__PROP_BASELINE_COLLECTION, 0 }, |
| { ELEM_checked_in, SVN_RA_NEON__PROP_CHECKED_IN, 0 }, |
| { ELEM_vcc, SVN_RA_NEON__PROP_VCC, 0 }, |
| { ELEM_version_name, SVN_RA_NEON__PROP_VERSION_NAME, 1 }, |
| { ELEM_get_content_length, SVN_RA_NEON__PROP_GETCONTENTLENGTH, 1 }, |
| { ELEM_creationdate, SVN_RA_NEON__PROP_CREATIONDATE, 1 }, |
| { ELEM_creator_displayname, SVN_RA_NEON__PROP_CREATOR_DISPLAYNAME, 1 }, |
| |
| /* SVN elements */ |
| { ELEM_baseline_relpath, SVN_RA_NEON__PROP_BASELINE_RELPATH, 1 }, |
| { ELEM_md5_checksum, SVN_RA_NEON__PROP_MD5_CHECKSUM, 1 }, |
| { ELEM_repository_uuid, SVN_RA_NEON__PROP_REPOSITORY_UUID, 1 }, |
| { ELEM_deadprop_count, SVN_RA_NEON__PROP_DEADPROP_COUNT, 1 }, |
| { 0 } |
| }; |
| |
| |
| static const svn_ra_neon__xml_elm_t propfind_elements[] = |
| { |
| /*** NOTE: Make sure that every item in here is also represented in |
| elem_definitions[] ***/ |
| |
| /* DAV elements */ |
| { "DAV:", "multistatus", ELEM_multistatus, 0 }, |
| { "DAV:", "response", ELEM_response, 0 }, |
| { "DAV:", "href", ELEM_href, SVN_RA_NEON__XML_CDATA }, |
| { "DAV:", "propstat", ELEM_propstat, 0 }, |
| { "DAV:", "prop", ELEM_prop, 0 }, |
| { "DAV:", "status", ELEM_status, SVN_RA_NEON__XML_CDATA }, |
| { "DAV:", "baseline", ELEM_baseline, SVN_RA_NEON__XML_CDATA }, |
| { "DAV:", "baseline-collection", ELEM_baseline_coll, SVN_RA_NEON__XML_CDATA }, |
| { "DAV:", "checked-in", ELEM_checked_in, 0 }, |
| { "DAV:", "collection", ELEM_collection, SVN_RA_NEON__XML_CDATA }, |
| { "DAV:", "resourcetype", ELEM_resourcetype, 0 }, |
| { "DAV:", "version-controlled-configuration", ELEM_vcc, 0 }, |
| { "DAV:", SVN_DAV__VERSION_NAME, ELEM_version_name, SVN_RA_NEON__XML_CDATA }, |
| { "DAV:", "getcontentlength", ELEM_get_content_length, |
| SVN_RA_NEON__XML_CDATA }, |
| { "DAV:", SVN_DAV__CREATIONDATE, ELEM_creationdate, SVN_RA_NEON__XML_CDATA }, |
| { "DAV:", "creator-displayname", ELEM_creator_displayname, |
| SVN_RA_NEON__XML_CDATA }, |
| |
| /* SVN elements */ |
| { SVN_DAV_PROP_NS_DAV, "baseline-relative-path", ELEM_baseline_relpath, |
| SVN_RA_NEON__XML_CDATA }, |
| { SVN_DAV_PROP_NS_DAV, "md5-checksum", ELEM_md5_checksum, |
| SVN_RA_NEON__XML_CDATA }, |
| { SVN_DAV_PROP_NS_DAV, "repository-uuid", ELEM_repository_uuid, |
| SVN_RA_NEON__XML_CDATA }, |
| { SVN_DAV_PROP_NS_DAV, "deadprop-count", ELEM_deadprop_count, |
| SVN_RA_NEON__XML_CDATA }, |
| |
| /* Unknowns */ |
| { "", "", ELEM_unknown, SVN_RA_NEON__XML_COLLECT }, |
| |
| { NULL } |
| }; |
| |
| |
| typedef struct propfind_ctx_t |
| { |
| /*WARNING: WANT_CDATA should stay the first element in the baton: |
| svn_ra_neon__xml_collect_cdata() assumes the baton starts with a stringbuf. |
| */ |
| svn_stringbuf_t *cdata; |
| apr_hash_t *props; /* const char *URL-PATH -> svn_ra_neon__resource_t */ |
| |
| svn_ra_neon__resource_t *rsrc; /* the current resource. */ |
| const char *encoding; /* property encoding (or NULL) */ |
| int status; /* status for the current <propstat> (or 0 if unknown). */ |
| apr_hash_t *propbuffer; /* holds properties until their status is known. */ |
| svn_ra_neon__xml_elmid last_open_id; /* the id of the last opened tag. */ |
| ne_xml_parser *parser; /* xml parser handling the PROPSET request. */ |
| |
| apr_pool_t *pool; |
| |
| } propfind_ctx_t; |
| |
| |
| /* Look up an element definition ID. May return NULL if the elem is |
| not recognized. */ |
| static const elem_defn *defn_from_id(svn_ra_neon__xml_elmid id) |
| { |
| const elem_defn *defn; |
| |
| for (defn = elem_definitions; defn->name != NULL; ++defn) |
| { |
| if (id == defn->id) |
| return defn; |
| } |
| |
| return NULL; |
| } |
| |
| |
| /* Assign URL to RSRC. Use POOL for any allocations. */ |
| static svn_error_t * |
| assign_rsrc_url(svn_ra_neon__resource_t *rsrc, |
| const char *url, apr_pool_t *pool) |
| { |
| char *url_path; |
| apr_size_t len; |
| ne_uri parsed_url; |
| |
| /* 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). |
| */ |
| if (ne_uri_parse(url, &parsed_url) != 0) |
| { |
| ne_uri_free(&parsed_url); |
| return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, |
| _("Unable to parse URL '%s'"), url); |
| } |
| |
| url_path = apr_pstrdup(pool, parsed_url.path); |
| ne_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'; |
| rsrc->url = url_path; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Determine whether we're receiving the expected XML response. |
| Return CHILD when interested in receiving the child's contents |
| or one of SVN_RA_NEON__XML_INVALID and SVN_RA_NEON__XML_DECLINE |
| when respectively this is the incorrect response or |
| the element (and its children) are uninteresting */ |
| static int validate_element(svn_ra_neon__xml_elmid parent, |
| svn_ra_neon__xml_elmid child) |
| { |
| switch (parent) |
| { |
| case ELEM_root: |
| if (child == ELEM_multistatus) |
| return child; |
| else |
| return SVN_RA_NEON__XML_INVALID; |
| |
| case ELEM_multistatus: |
| if (child == ELEM_response) |
| return child; |
| else |
| return SVN_RA_NEON__XML_DECLINE; |
| |
| case ELEM_response: |
| if ((child == ELEM_href) || (child == ELEM_propstat)) |
| return child; |
| else |
| return SVN_RA_NEON__XML_DECLINE; |
| |
| case ELEM_propstat: |
| if ((child == ELEM_prop) || (child == ELEM_status)) |
| return child; |
| else |
| return SVN_RA_NEON__XML_DECLINE; |
| |
| case ELEM_prop: |
| return child; /* handle all children of <prop> */ |
| |
| case ELEM_baseline_coll: |
| case ELEM_checked_in: |
| case ELEM_vcc: |
| if (child == ELEM_href) |
| return child; |
| else |
| return SVN_RA_NEON__XML_DECLINE; /* not concerned with other types */ |
| |
| case ELEM_resourcetype: |
| if ((child == ELEM_collection) || (child == ELEM_baseline)) |
| return child; |
| else |
| return SVN_RA_NEON__XML_DECLINE; /* not concerned with other types |
| (### now) */ |
| |
| default: |
| return SVN_RA_NEON__XML_DECLINE; |
| } |
| |
| /* NOTREACHED */ |
| } |
| |
| |
| static svn_error_t * |
| start_element(int *elem, void *baton, int parent, |
| const char *nspace, const char *name, const char **atts) |
| { |
| propfind_ctx_t *pc = baton; |
| const svn_ra_neon__xml_elm_t *elm |
| = svn_ra_neon__lookup_xml_elem(propfind_elements, nspace, name); |
| |
| |
| *elem = elm ? validate_element(parent, elm->id) : SVN_RA_NEON__XML_DECLINE; |
| if (*elem < 1) /* not a valid element */ |
| return SVN_NO_ERROR; |
| |
| svn_stringbuf_setempty(pc->cdata); |
| *elem = elm ? elm->id : ELEM_unknown; |
| switch (*elem) |
| { |
| case ELEM_response: |
| if (pc->rsrc) |
| return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); |
| /* Create a new resource. */ |
| pc->rsrc = apr_pcalloc(pc->pool, sizeof(*(pc->rsrc))); |
| pc->rsrc->pool = pc->pool; |
| pc->rsrc->propset = apr_hash_make(pc->pool); |
| pc->status = 0; |
| break; |
| |
| case ELEM_propstat: |
| pc->status = 0; |
| break; |
| |
| case ELEM_href: |
| /* Remember this <href>'s parent so that when we close this tag, |
| we know to whom the URL assignment belongs. Could be the |
| resource itself, or one of the properties: |
| ELEM_baseline_coll, ELEM_checked_in, ELEM_vcc: */ |
| pc->rsrc->href_parent = pc->last_open_id; |
| break; |
| |
| case ELEM_collection: |
| pc->rsrc->is_collection = 1; |
| break; |
| |
| case ELEM_unknown: |
| /* these are our user-visible properties, presumably. */ |
| pc->encoding = ne_xml_get_attr(pc->parser, atts, SVN_DAV_PROP_NS_DAV, |
| "encoding"); |
| if (pc->encoding) |
| pc->encoding = apr_pstrdup(pc->pool, pc->encoding); |
| break; |
| |
| default: |
| /* nothing to do for these */ |
| break; |
| } |
| |
| /* Remember the last tag we opened. */ |
| pc->last_open_id = *elem; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * end_element(void *baton, int state, |
| const char *nspace, const char *name) |
| { |
| propfind_ctx_t *pc = baton; |
| svn_ra_neon__resource_t *rsrc = pc->rsrc; |
| const svn_string_t *value = NULL; |
| const elem_defn *parent_defn; |
| const elem_defn *defn; |
| ne_status status; |
| const char *cdata = pc->cdata->data; |
| |
| switch (state) |
| { |
| case ELEM_response: |
| /* Verify that we've received a URL for this resource. */ |
| if (!pc->rsrc->url) |
| return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); |
| |
| /* Store the resource in the top-level hash table. */ |
| apr_hash_set(pc->props, pc->rsrc->url, APR_HASH_KEY_STRING, pc->rsrc); |
| pc->rsrc = NULL; |
| return SVN_NO_ERROR; |
| |
| case ELEM_propstat: |
| /* We're at the end of a set of properties. Do the right thing |
| status-wise. */ |
| if (pc->status) |
| { |
| /* We have a status. Loop over the buffered properties, and |
| if the status is a good one (200), copy them into the |
| resources's property hash. Regardless of the status, |
| we'll be removing these from the temporary buffer as we |
| go along. */ |
| apr_hash_index_t *hi = apr_hash_first(pc->pool, pc->propbuffer); |
| for (; hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| apr_ssize_t klen; |
| void *val; |
| apr_hash_this(hi, &key, &klen, &val); |
| if (pc->status == 200) |
| apr_hash_set(rsrc->propset, key, klen, val); |
| apr_hash_set(pc->propbuffer, key, klen, NULL); |
| } |
| } |
| else if (! pc->status) |
| { |
| /* No status at all? Bogosity. */ |
| return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); |
| } |
| return SVN_NO_ERROR; |
| |
| case ELEM_status: |
| /* Parse the <status> tag's CDATA for a status code. */ |
| if (ne_parse_statusline(cdata, &status)) |
| return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); |
| free(status.reason_phrase); |
| pc->status = status.code; |
| return SVN_NO_ERROR; |
| |
| case ELEM_href: |
| /* Special handling for <href> that belongs to the <response> tag. */ |
| if (rsrc->href_parent == ELEM_response) |
| return assign_rsrc_url(pc->rsrc, cdata, pc->pool); |
| |
| /* Use the parent element's name, not the href. */ |
| parent_defn = defn_from_id(rsrc->href_parent); |
| |
| /* No known parent? Get outta here. */ |
| if (!parent_defn) |
| return SVN_NO_ERROR; |
| |
| /* All other href's we'll treat as property values. */ |
| name = parent_defn->name; |
| value = svn_string_create(cdata, pc->pool); |
| break; |
| |
| default: |
| /*** This case is, as usual, for everything not covered by other |
| cases. ELM->id should be either ELEM_unknown, or one of |
| the ids in the elem_definitions[] structure. In this case, |
| we seek to handle properties. Since ELEM_unknown should |
| only occur for properties, we will handle that id. All |
| other ids will be searched for in the elem_definitions[] |
| structure to determine if they are properties. Properties, |
| we handle; all else hits the road. ***/ |
| |
| if (state == ELEM_unknown) |
| { |
| name = apr_pstrcat(pc->pool, nspace, name, NULL); |
| } |
| else |
| { |
| defn = defn_from_id(state); |
| if (! (defn && defn->is_property)) |
| return SVN_NO_ERROR; |
| name = defn->name; |
| } |
| |
| /* Check for encoding attribute. */ |
| if (pc->encoding == NULL) { |
| /* Handle the property value by converting it to string. */ |
| value = svn_string_create(cdata, pc->pool); |
| break; |
| } |
| |
| /* Check for known encoding type */ |
| if (strcmp(pc->encoding, "base64") != 0) |
| return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); |
| |
| /* There is an encoding on this property, handle it. |
| * the braces are needed to allocate "in" on the stack. */ |
| { |
| svn_string_t in; |
| in.data = cdata; |
| in.len = strlen(cdata); |
| value = svn_base64_decode_string(&in, pc->pool); |
| } |
| |
| pc->encoding = NULL; /* Reset encoding for future attribute(s). */ |
| } |
| |
| /*** Handling resource properties from here out. ***/ |
| |
| /* Add properties to the temporary propbuffer. At the end of the |
| <propstat>, we'll either dump the props as invalid or move them |
| into the resource's property hash. */ |
| apr_hash_set(pc->propbuffer, name, APR_HASH_KEY_STRING, value); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static void set_parser(ne_xml_parser *parser, |
| void *baton) |
| { |
| propfind_ctx_t *pc = baton; |
| pc->parser = parser; |
| } |
| |
| |
| svn_error_t * svn_ra_neon__get_props(apr_hash_t **results, |
| svn_ra_neon__session_t *sess, |
| const char *url, |
| int depth, |
| const char *label, |
| const ne_propname *which_props, |
| apr_pool_t *pool) |
| { |
| propfind_ctx_t pc; |
| svn_stringbuf_t *body; |
| apr_hash_t *extra_headers = apr_hash_make(pool); |
| |
| svn_ra_neon__add_depth_header(extra_headers, depth); |
| |
| /* If we have a label, use it. */ |
| if (label != NULL) |
| apr_hash_set(extra_headers, "Label", 5, label); |
| |
| /* It's easier to roll our own PROPFIND here than use neon's current |
| interfaces. */ |
| /* The start of the request body is fixed: */ |
| body = svn_stringbuf_create |
| ("<?xml version=\"1.0\" encoding=\"utf-8\"?>" DEBUG_CR |
| "<propfind xmlns=\"DAV:\">" DEBUG_CR, pool); |
| |
| /* Are we asking for specific propert(y/ies), or just all of them? */ |
| if (which_props) |
| { |
| int n; |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| |
| svn_stringbuf_appendcstr(body, "<prop>" DEBUG_CR); |
| for (n = 0; which_props[n].name != NULL; n++) |
| { |
| svn_pool_clear(iterpool); |
| svn_stringbuf_appendcstr |
| (body, apr_pstrcat(iterpool, "<", which_props[n].name, " xmlns=\"", |
| which_props[n].nspace, "\"/>" DEBUG_CR, NULL)); |
| } |
| svn_stringbuf_appendcstr(body, "</prop></propfind>" DEBUG_CR); |
| svn_pool_destroy(iterpool); |
| } |
| else |
| { |
| svn_stringbuf_appendcstr(body, "<allprop/></propfind>" DEBUG_CR); |
| } |
| |
| /* Initialize our baton. */ |
| memset(&pc, 0, sizeof(pc)); |
| pc.pool = pool; |
| pc.propbuffer = apr_hash_make(pool); |
| pc.props = apr_hash_make(pool); |
| pc.cdata = svn_stringbuf_create("", pool); |
| |
| /* Create and dispatch the request! */ |
| SVN_ERR(svn_ra_neon__parsed_request(sess, "PROPFIND", url, |
| body->data, 0, |
| set_parser, |
| start_element, |
| svn_ra_neon__xml_collect_cdata, |
| end_element, |
| &pc, extra_headers, NULL, FALSE, pool)); |
| |
| *results = pc.props; |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * svn_ra_neon__get_props_resource(svn_ra_neon__resource_t **rsrc, |
| svn_ra_neon__session_t *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_neon__get_props(&props, sess, url_path, SVN_RA_NEON__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 = NULL; |
| } |
| 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, NULL, |
| _("Failed to find label '%s' for URL '%s'"), |
| label ? label : "NULL", url_path); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * svn_ra_neon__get_one_prop(const svn_string_t **propval, |
| svn_ra_neon__session_t *sess, |
| const char *url, |
| const char *label, |
| const ne_propname *propname, |
| apr_pool_t *pool) |
| { |
| svn_ra_neon__resource_t *rsrc; |
| ne_propname props[2] = { { 0 } }; |
| const char *name; |
| const svn_string_t *value; |
| |
| props[0] = *propname; |
| SVN_ERR(svn_ra_neon__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_FS_NOT_FOUND, NULL, |
| _("'%s' was not present on the resource"), |
| name); |
| } |
| |
| *propval = value; |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * svn_ra_neon__get_starting_props(svn_ra_neon__resource_t **rsrc, |
| svn_ra_neon__session_t *sess, |
| const char *url, |
| const char *label, |
| apr_pool_t *pool) |
| { |
| svn_string_t *propval; |
| |
| SVN_ERR(svn_ra_neon__get_props_resource(rsrc, sess, url, label, |
| starting_props, pool)); |
| |
| /* Cache some of the resource information. */ |
| |
| if (! sess->vcc) |
| { |
| propval = apr_hash_get((*rsrc)->propset, |
| SVN_RA_NEON__PROP_VCC, |
| APR_HASH_KEY_STRING); |
| if (propval) |
| sess->vcc = apr_pstrdup(sess->pool, propval->data); |
| } |
| |
| if (! sess->uuid) |
| { |
| propval = apr_hash_get((*rsrc)->propset, |
| SVN_RA_NEON__PROP_REPOSITORY_UUID, |
| APR_HASH_KEY_STRING); |
| if (propval) |
| sess->uuid = apr_pstrdup(sess->pool, propval->data); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| svn_error_t * |
| svn_ra_neon__search_for_starting_props(svn_ra_neon__resource_t **rsrc, |
| const char **missing_path, |
| svn_ra_neon__session_t *sess, |
| const char *url, |
| apr_pool_t *pool) |
| { |
| svn_error_t *err = SVN_NO_ERROR; |
| apr_size_t len; |
| svn_stringbuf_t *path_s; |
| ne_uri parsed_url; |
| svn_stringbuf_t *lopped_path = |
| svn_stringbuf_create(url, pool); /* initialize to make sure it'll fit |
| without reallocating */ |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| |
| /* Split the url into its component pieces (scheme, host, path, |
| etc). We want the path part. */ |
| ne_uri_parse(url, &parsed_url); |
| if (parsed_url.path == NULL) |
| { |
| ne_uri_free(&parsed_url); |
| return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, |
| _("Neon was unable to parse URL '%s'"), url); |
| } |
| |
| svn_stringbuf_setempty(lopped_path); |
| path_s = svn_stringbuf_create(parsed_url.path, pool); |
| ne_uri_free(&parsed_url); |
| |
| /* 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. */ |
| while (! svn_path_is_empty(path_s->data)) |
| { |
| svn_pool_clear(iterpool); |
| err = svn_ra_neon__get_starting_props(rsrc, sess, path_s->data, |
| NULL, iterpool); |
| if (! err) |
| break; /* found an existing parent! */ |
| |
| if (err->apr_err != SVN_ERR_FS_NOT_FOUND) |
| return err; /* found a _real_ error */ |
| |
| /* else... lop off the basename and try again. */ |
| svn_stringbuf_set(lopped_path, |
| svn_path_join(svn_path_basename(path_s->data, iterpool), |
| lopped_path->data, iterpool)); |
| |
| len = path_s->len; |
| svn_path_remove_component(path_s); |
| |
| /* if we detect an infinite loop, get out. */ |
| if (path_s->len == len) |
| return svn_error_quick_wrap |
| (err, _("The path was not part of a repository")); |
| |
| svn_error_clear(err); |
| } |
| |
| /* error out if entire URL was bogus (not a single part of it exists |
| in the repository!) */ |
| if (svn_path_is_empty(path_s->data)) |
| return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, |
| _("No part of path '%s' was found in " |
| "repository HEAD"), parsed_url.path); |
| |
| /* Duplicate rsrc out of iterpool into pool */ |
| { |
| apr_hash_index_t *hi; |
| svn_ra_neon__resource_t *tmp = apr_pcalloc(pool, sizeof(*tmp)); |
| tmp->url = apr_pstrdup(pool, (*rsrc)->url); |
| tmp->is_collection = (*rsrc)->is_collection; |
| tmp->pool = pool; |
| tmp->propset = apr_hash_make(pool); |
| |
| for (hi = apr_hash_first(iterpool, (*rsrc)->propset); |
| hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| |
| apr_hash_this(hi, &key, NULL, &val); |
| apr_hash_set(tmp->propset, apr_pstrdup(pool, key), APR_HASH_KEY_STRING, |
| svn_string_dup(val, pool)); |
| } |
| |
| *rsrc = tmp; |
| } |
| *missing_path = lopped_path->data; |
| svn_pool_destroy(iterpool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t *svn_ra_neon__get_vcc(const char **vcc, |
| svn_ra_neon__session_t *sess, |
| const char *url, |
| apr_pool_t *pool) |
| { |
| svn_ra_neon__resource_t *rsrc; |
| const char *lopped_path; |
| |
| /* Look for memory-cached VCC in the RA session. */ |
| if (sess->vcc) |
| { |
| *vcc = sess->vcc; |
| return SVN_NO_ERROR; |
| } |
| |
| /* ### Someday, possibly look for disk-cached VCC via get_wcprop callback. */ |
| |
| /* Finally, resort to a set of PROPFINDs up parent directories. */ |
| SVN_ERR(svn_ra_neon__search_for_starting_props(&rsrc, &lopped_path, |
| sess, url, pool)); |
| |
| if (! sess->vcc) |
| { |
| /* ### better error reporting... */ |
| return svn_error_create(APR_EGENERAL, NULL, |
| _("The VCC property was not found on the " |
| "resource")); |
| } |
| |
| *vcc = sess->vcc; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t *svn_ra_neon__get_baseline_props(svn_string_t *bc_relative, |
| svn_ra_neon__resource_t **bln_rsrc, |
| svn_ra_neon__session_t *sess, |
| const char *url, |
| svn_revnum_t revision, |
| const ne_propname *which_props, |
| apr_pool_t *pool) |
| { |
| svn_ra_neon__resource_t *rsrc; |
| const char *vcc; |
| const svn_string_t *relative_path; |
| const char *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. |
| */ |
| |
| SVN_ERR(svn_ra_neon__search_for_starting_props(&rsrc, &lopped_path, |
| sess, url, pool)); |
| |
| SVN_ERR(svn_ra_neon__get_vcc(&vcc, sess, url, pool)); |
| if (vcc == NULL) |
| { |
| /* ### better error reporting... */ |
| |
| /* ### need an SVN_ERR here */ |
| return svn_error_create(APR_EGENERAL, NULL, |
| _("The VCC property was not found on the " |
| "resource")); |
| } |
| |
| /* Allocate our own bc_relative path. */ |
| relative_path = apr_hash_get(rsrc->propset, |
| SVN_RA_NEON__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, NULL, |
| _("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... We are expected to return a URI decoded relative |
| path, so decode the lopped path first. */ |
| my_bc_relative = svn_path_join(relative_path->data, |
| svn_path_uri_decode(lopped_path, pool), |
| pool); |
| |
| /* if they want the relative path (could be, they're just trying to find |
| the baseline collection), then return it */ |
| if (bc_relative) |
| { |
| bc_relative->data = my_bc_relative; |
| bc_relative->len = strlen(my_bc_relative); |
| } |
| |
| /* ------------------------------------------------------------------- |
| 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_neon__get_one_prop(&baseline, sess, vcc, NULL, |
| &svn_ra_neon__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_neon__get_props_resource(&rsrc, sess, |
| baseline->data, NULL, |
| which_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_neon__get_props_resource(&rsrc, sess, vcc, label, |
| which_props, pool)); |
| } |
| |
| /* Return the baseline rsrc, which now contains whatever set of |
| props the caller wanted. */ |
| *bln_rsrc = rsrc; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t *svn_ra_neon__get_baseline_info(svn_boolean_t *is_dir, |
| svn_string_t *bc_url, |
| svn_string_t *bc_relative, |
| svn_revnum_t *latest_rev, |
| svn_ra_neon__session_t *sess, |
| const char *url, |
| svn_revnum_t revision, |
| apr_pool_t *pool) |
| { |
| svn_ra_neon__resource_t *baseline_rsrc, *rsrc; |
| const svn_string_t *my_bc_url; |
| svn_string_t my_bc_rel; |
| |
| /* Go fetch a BASELINE_RSRC that contains specific properties we |
| want. This routine will also fill in BC_RELATIVE as best it |
| can. */ |
| SVN_ERR(svn_ra_neon__get_baseline_props(&my_bc_rel, |
| &baseline_rsrc, |
| sess, |
| url, |
| revision, |
| baseline_props, /* specific props */ |
| pool)); |
| |
| /* baseline_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 = apr_hash_get(baseline_rsrc->propset, |
| SVN_RA_NEON__PROP_BASELINE_COLLECTION, |
| APR_HASH_KEY_STRING); |
| if (my_bc_url == NULL) |
| { |
| /* ### better error reporting... */ |
| /* ### need an SVN_ERR here */ |
| return svn_error_create(APR_EGENERAL, NULL, |
| _("'DAV:baseline-collection' was not present " |
| "on the baseline resource")); |
| } |
| |
| /* maybe return bc_url to the caller */ |
| if (bc_url) |
| *bc_url = *my_bc_url; |
| |
| if (latest_rev != NULL) |
| { |
| const svn_string_t *vsn_name= apr_hash_get(baseline_rsrc->propset, |
| SVN_RA_NEON__PROP_VERSION_NAME, |
| APR_HASH_KEY_STRING); |
| if (vsn_name == NULL) |
| { |
| /* ### better error reporting... */ |
| |
| /* ### need an SVN_ERR here */ |
| return svn_error_createf(APR_EGENERAL, NULL, |
| _("'%s' was not present on the baseline " |
| "resource"), |
| "DAV:" SVN_DAV__VERSION_NAME); |
| } |
| *latest_rev = SVN_STR_TO_REV(vsn_name->data); |
| } |
| |
| if (is_dir != NULL) |
| { |
| /* query the DAV:resourcetype of the full, assembled URL. */ |
| const char *full_bc_url = svn_path_url_add_component(my_bc_url->data, |
| my_bc_rel.data, |
| pool); |
| SVN_ERR(svn_ra_neon__get_starting_props(&rsrc, sess, full_bc_url, |
| NULL, pool)); |
| *is_dir = rsrc->is_collection; |
| } |
| |
| if (bc_relative) |
| *bc_relative = my_bc_rel; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Helper function for svn_ra_neon__do_proppatch() below. */ |
| static void |
| append_setprop(svn_stringbuf_t *body, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| const char *encoding = ""; |
| const char *xml_safe; |
| const char *xml_tag_name; |
| |
| /* Map property names to namespaces */ |
| #define NSLEN (sizeof(SVN_PROP_PREFIX) - 1) |
| if (strncmp(name, SVN_PROP_PREFIX, NSLEN) == 0) |
| { |
| xml_tag_name = apr_pstrcat(pool, "S:", name + NSLEN, NULL); |
| } |
| #undef NSLEN |
| else |
| { |
| xml_tag_name = apr_pstrcat(pool, "C:", name, NULL); |
| } |
| |
| /* If there is no value, just generate an empty tag and get outta |
| here. */ |
| if (! value) |
| { |
| svn_stringbuf_appendcstr(body, |
| apr_psprintf(pool, "<%s />", xml_tag_name)); |
| return; |
| } |
| |
| /* If a property is XML-safe, XML-encode it. Else, base64-encode |
| it. */ |
| if (svn_xml_is_xml_safe(value->data, value->len)) |
| { |
| svn_stringbuf_t *xml_esc = NULL; |
| svn_xml_escape_cdata_string(&xml_esc, value, pool); |
| xml_safe = xml_esc->data; |
| } |
| else |
| { |
| const svn_string_t *base64ed = svn_base64_encode_string2(value, TRUE, |
| pool); |
| encoding = " V:encoding=\"base64\""; |
| xml_safe = base64ed->data; |
| } |
| |
| svn_stringbuf_appendcstr(body, |
| apr_psprintf(pool,"<%s %s>%s</%s>", |
| xml_tag_name, encoding, |
| xml_safe, xml_tag_name)); |
| return; |
| } |
| |
| |
| svn_error_t * |
| svn_ra_neon__do_proppatch(svn_ra_neon__session_t *ras, |
| const char *url, |
| apr_hash_t *prop_changes, |
| apr_array_header_t *prop_deletes, |
| apr_hash_t *extra_headers, |
| apr_pool_t *pool) |
| { |
| svn_error_t *err; |
| svn_stringbuf_t *body; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| |
| /* just punt if there are no changes to make. */ |
| if ((prop_changes == NULL || (! apr_hash_count(prop_changes))) |
| && (prop_deletes == NULL || prop_deletes->nelts == 0)) |
| return SVN_NO_ERROR; |
| |
| /* easier to roll our own PROPPATCH here than use ne_proppatch(), which |
| * doesn't really do anything clever. */ |
| body = svn_stringbuf_create |
| ("<?xml version=\"1.0\" encoding=\"utf-8\" ?>" DEBUG_CR |
| "<D:propertyupdate xmlns:D=\"DAV:\" xmlns:V=\"" |
| SVN_DAV_PROP_NS_DAV "\" xmlns:C=\"" |
| SVN_DAV_PROP_NS_CUSTOM "\" xmlns:S=\"" |
| SVN_DAV_PROP_NS_SVN "\">" DEBUG_CR, pool); |
| |
| /* Handle property changes. */ |
| if (prop_changes) |
| { |
| apr_hash_index_t *hi; |
| svn_stringbuf_appendcstr(body, "<D:set><D:prop>"); |
| for (hi = apr_hash_first(pool, prop_changes); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| svn_pool_clear(subpool); |
| apr_hash_this(hi, &key, NULL, &val); |
| append_setprop(body, key, val, subpool); |
| } |
| svn_stringbuf_appendcstr(body, "</D:prop></D:set>"); |
| } |
| |
| /* Handle property deletions. */ |
| if (prop_deletes) |
| { |
| int n; |
| svn_stringbuf_appendcstr(body, "<D:remove><D:prop>"); |
| for (n = 0; n < prop_deletes->nelts; n++) |
| { |
| const char *name = APR_ARRAY_IDX(prop_deletes, n, const char *); |
| svn_pool_clear(subpool); |
| append_setprop(body, name, NULL, subpool); |
| } |
| svn_stringbuf_appendcstr(body, "</D:prop></D:remove>"); |
| } |
| svn_pool_destroy(subpool); |
| |
| /* Finish up the body. */ |
| svn_stringbuf_appendcstr(body, "</D:propertyupdate>"); |
| |
| /* Finish up the headers. */ |
| if (! extra_headers) |
| extra_headers = apr_hash_make(pool); |
| apr_hash_set(extra_headers, "Content-Type", APR_HASH_KEY_STRING, |
| "text/xml; charset=UTF-8"); |
| |
| err = svn_ra_neon__simple_request(NULL, ras, "PROPPATCH", url, |
| extra_headers, body->data, |
| 200, 207, pool); |
| if (err) |
| return svn_error_create |
| (SVN_ERR_RA_DAV_PROPPATCH_FAILED, err, |
| _("At least one property change failed; repository is unchanged")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| svn_error_t * |
| svn_ra_neon__do_check_path(svn_ra_session_t *session, |
| const char *path, |
| svn_revnum_t revision, |
| svn_node_kind_t *kind, |
| apr_pool_t *pool) |
| { |
| svn_ra_neon__session_t *ras = session->priv; |
| const char *url = ras->url->data; |
| svn_error_t *err; |
| svn_boolean_t is_dir; |
| |
| /* ### For now, using svn_ra_neon__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_neon__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) |
| url = svn_path_url_add_component(url, path, pool); |
| |
| err = svn_ra_neon__get_baseline_info(&is_dir, NULL, NULL, NULL, |
| ras, url, revision, pool); |
| |
| if (err == SVN_NO_ERROR) |
| { |
| if (is_dir) |
| *kind = svn_node_dir; |
| else |
| *kind = svn_node_file; |
| } |
| else if (err->apr_err == SVN_ERR_FS_NOT_FOUND) |
| { |
| |
| svn_error_clear(err); |
| err = SVN_NO_ERROR; |
| *kind = svn_node_none; |
| } |
| |
| return err; |
| } |
| |
| |
| svn_error_t * |
| svn_ra_neon__do_stat(svn_ra_session_t *session, |
| const char *path, |
| svn_revnum_t revision, |
| svn_dirent_t **dirent, |
| apr_pool_t *pool) |
| { |
| svn_ra_neon__session_t *ras = session->priv; |
| const char *url = ras->url->data; |
| const char *final_url; |
| apr_hash_t *resources; |
| apr_hash_index_t *hi; |
| svn_error_t *err; |
| |
| /* If we were given a relative path to append, append it. */ |
| if (path) |
| url = svn_path_url_add_component(url, path, pool); |
| |
| /* Invalid revision means HEAD, which is just the public URL. */ |
| if (! SVN_IS_VALID_REVNUM(revision)) |
| { |
| final_url = url; |
| } |
| else |
| { |
| /* Else, convert (rev, path) into an opaque server-generated URL. */ |
| svn_string_t bc_url, bc_relative; |
| |
| err = svn_ra_neon__get_baseline_info(NULL, &bc_url, &bc_relative, |
| NULL, ras, |
| url, revision, pool); |
| if (err) |
| { |
| if (err->apr_err == SVN_ERR_FS_NOT_FOUND) |
| { |
| /* easy out: */ |
| svn_error_clear(err); |
| *dirent = NULL; |
| return SVN_NO_ERROR; |
| } |
| else |
| return err; |
| } |
| |
| final_url = svn_path_url_add_component(bc_url.data, bc_relative.data, |
| pool); |
| } |
| |
| /* Depth-zero PROPFIND is the One True DAV Way. */ |
| err = svn_ra_neon__get_props(&resources, ras, final_url, |
| SVN_RA_NEON__DEPTH_ZERO, |
| NULL, NULL /* all props */, pool); |
| if (err) |
| { |
| if (err->apr_err == SVN_ERR_FS_NOT_FOUND) |
| { |
| /* easy out: */ |
| svn_error_clear(err); |
| *dirent = NULL; |
| return SVN_NO_ERROR; |
| } |
| else |
| return err; |
| } |
| |
| /* Copying parsing code from svn_ra_neon__get_dir() here. The hash |
| of resources only contains one item, but there's no other way to |
| get the item. */ |
| for (hi = apr_hash_first(pool, resources); hi; hi = apr_hash_next(hi)) |
| { |
| void *val; |
| svn_ra_neon__resource_t *resource; |
| const svn_string_t *propval; |
| apr_hash_index_t *h; |
| svn_dirent_t *entry; |
| |
| apr_hash_this(hi, NULL, NULL, &val); |
| resource = val; |
| |
| entry = apr_pcalloc(pool, sizeof(*entry)); |
| |
| entry->kind = resource->is_collection ? svn_node_dir : svn_node_file; |
| |
| /* entry->size is already 0 by virtue of pcalloc(). */ |
| if (entry->kind == svn_node_file) |
| { |
| propval = apr_hash_get(resource->propset, |
| SVN_RA_NEON__PROP_GETCONTENTLENGTH, |
| APR_HASH_KEY_STRING); |
| if (propval) |
| entry->size = svn__atoui64(propval->data); |
| } |
| |
| /* does this resource contain any 'dead' properties? */ |
| for (h = apr_hash_first(pool, resource->propset); |
| h; h = apr_hash_next(h)) |
| { |
| const void *kkey; |
| apr_hash_this(h, &kkey, NULL, NULL); |
| |
| if (strncmp((const char *)kkey, SVN_DAV_PROP_NS_CUSTOM, |
| sizeof(SVN_DAV_PROP_NS_CUSTOM) - 1) == 0) |
| entry->has_props = TRUE; |
| |
| else if (strncmp((const char *)kkey, SVN_DAV_PROP_NS_SVN, |
| sizeof(SVN_DAV_PROP_NS_SVN) - 1) == 0) |
| entry->has_props = TRUE; |
| } |
| |
| /* created_rev & friends */ |
| propval = apr_hash_get(resource->propset, |
| SVN_RA_NEON__PROP_VERSION_NAME, |
| APR_HASH_KEY_STRING); |
| if (propval != NULL) |
| entry->created_rev = SVN_STR_TO_REV(propval->data); |
| |
| propval = apr_hash_get(resource->propset, |
| SVN_RA_NEON__PROP_CREATIONDATE, |
| APR_HASH_KEY_STRING); |
| if (propval != NULL) |
| SVN_ERR(svn_time_from_cstring(&(entry->time), |
| propval->data, pool)); |
| |
| propval = apr_hash_get(resource->propset, |
| SVN_RA_NEON__PROP_CREATOR_DISPLAYNAME, |
| APR_HASH_KEY_STRING); |
| if (propval != NULL) |
| entry->last_author = propval->data; |
| |
| *dirent = entry; |
| } |
| |
| return SVN_NO_ERROR; |
| } |