/* * props.c : routines for fetching DAV properties * * ==================================================================== * Copyright (c) 2000-2004 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 #include #include #define APR_WANT_STRFUNC #include #include #include #include #include #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 "svn_private_config.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" }; /* 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:", "version-name" }, { NULL } }; /*** Propfind Implementation ***/ typedef struct { svn_ra_dav__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_DAV__XML_CDATA }, { ELEM_propstat, "DAV:propstat", 0 }, { ELEM_prop, "DAV:prop", 0 }, { ELEM_status, "DAV:status", SVN_RA_DAV__XML_CDATA }, { ELEM_baseline, "DAV:baseline", SVN_RA_DAV__XML_CDATA }, { ELEM_collection, "DAV:collection", SVN_RA_DAV__XML_CDATA }, { ELEM_resourcetype, "DAV:resourcetype", 0 }, { 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 }, { ELEM_get_content_length, SVN_RA_DAV__PROP_GETCONTENTLENGTH, 1 }, { ELEM_creationdate, SVN_RA_DAV__PROP_CREATIONDATE, 1 }, { ELEM_creator_displayname, SVN_RA_DAV__PROP_CREATOR_DISPLAYNAME, 1 }, /* SVN elements */ { ELEM_baseline_relpath, SVN_RA_DAV__PROP_BASELINE_RELPATH, 1 }, { ELEM_md5_checksum, SVN_RA_DAV__PROP_MD5_CHECKSUM, 1 }, { ELEM_repository_uuid, SVN_RA_DAV__PROP_REPOSITORY_UUID, 1 }, { ELEM_deadprop_count, SVN_RA_DAV__PROP_DEADPROP_COUNT, 1 }, { 0 } }; static const svn_ra_dav__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_DAV__XML_CDATA }, { "DAV:", "propstat", ELEM_propstat, 0 }, { "DAV:", "prop", ELEM_prop, 0 }, { "DAV:", "status", ELEM_status, SVN_RA_DAV__XML_CDATA }, { "DAV:", "baseline", ELEM_baseline, SVN_RA_DAV__XML_CDATA }, { "DAV:", "baseline-collection", ELEM_baseline_coll, SVN_RA_DAV__XML_CDATA }, { "DAV:", "checked-in", ELEM_checked_in, 0 }, { "DAV:", "collection", ELEM_collection, SVN_RA_DAV__XML_CDATA }, { "DAV:", "resourcetype", ELEM_resourcetype, 0 }, { "DAV:", "version-controlled-configuration", ELEM_vcc, 0 }, { "DAV:", "version-name", ELEM_version_name, SVN_RA_DAV__XML_CDATA }, { "DAV:", "getcontentlength", ELEM_get_content_length, SVN_RA_DAV__XML_CDATA }, { "DAV:", "creationdate", ELEM_creationdate, SVN_RA_DAV__XML_CDATA }, { "DAV:", "creator-displayname", ELEM_creator_displayname, SVN_RA_DAV__XML_CDATA }, /* SVN elements */ { SVN_DAV_PROP_NS_DAV, "baseline-relative-path", ELEM_baseline_relpath, SVN_RA_DAV__XML_CDATA }, { SVN_DAV_PROP_NS_DAV, "md5-checksum", ELEM_md5_checksum, SVN_RA_DAV__XML_CDATA }, { SVN_DAV_PROP_NS_DAV, "repository-uuid", ELEM_repository_uuid, SVN_RA_DAV__XML_CDATA }, { SVN_DAV_PROP_NS_DAV, "deadprop-count", ELEM_deadprop_count, SVN_RA_DAV__XML_CDATA }, /* Unknowns */ { "", "", ELEM_unknown, SVN_RA_DAV__XML_COLLECT }, { NULL } }; typedef struct propfind_ctx_t { apr_hash_t *props; /* const char *URL-PATH -> svn_ra_dav_resource_t */ svn_ra_dav_resource_t *rsrc; /* the current resource. */ const char *encoding; /* property encoding (or NULL) */ int status; /* status for the current (or 0 if unknown). */ apr_hash_t *propbuffer; /* holds properties until their status is known. */ svn_ra_dav__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_dav__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 void assign_rsrc_url(svn_ra_dav_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). */ (void) ne_uri_parse(url, &parsed_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; } static int validate_element(void *userdata, svn_ra_dav__xml_elmid parent, svn_ra_dav__xml_elmid child) { switch (parent) { case ELEM_root: if (child == ELEM_multistatus) return SVN_RA_DAV__XML_VALID; else return SVN_RA_DAV__XML_INVALID; case ELEM_multistatus: if (child == ELEM_response) return SVN_RA_DAV__XML_VALID; else return SVN_RA_DAV__XML_DECLINE; case ELEM_response: if ((child == ELEM_href) || (child == ELEM_propstat)) return SVN_RA_DAV__XML_VALID; else return SVN_RA_DAV__XML_DECLINE; case ELEM_propstat: if ((child == ELEM_prop) || (child == ELEM_status)) return SVN_RA_DAV__XML_VALID; else return SVN_RA_DAV__XML_DECLINE; case ELEM_prop: return SVN_RA_DAV__XML_VALID; /* handle all children of */ case ELEM_baseline_coll: case ELEM_checked_in: case ELEM_vcc: if (child == ELEM_href) return SVN_RA_DAV__XML_VALID; else return SVN_RA_DAV__XML_DECLINE; /* not concerned with other types */ case ELEM_resourcetype: if ((child == ELEM_collection) || (child == ELEM_baseline)) return SVN_RA_DAV__XML_VALID; else return SVN_RA_DAV__XML_DECLINE; /* not concerned with other types (### now) */ default: return SVN_RA_DAV__XML_DECLINE; } /* NOTREACHED */ } static int start_element(void *userdata, const svn_ra_dav__xml_elm_t *elm, const char **atts) { propfind_ctx_t *pc = userdata; switch (elm->id) { case ELEM_response: if (pc->rsrc) return SVN_RA_DAV__XML_INVALID; /* 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 '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 = elm->id; return SVN_RA_DAV__XML_VALID; } static int end_element(void *userdata, const svn_ra_dav__xml_elm_t *elm, const char *cdata) { propfind_ctx_t *pc = userdata; svn_ra_dav_resource_t *rsrc = pc->rsrc; const char *name; const svn_string_t *value = NULL; const elem_defn *parent_defn; const elem_defn *defn; ne_status status; switch (elm->id) { case ELEM_response: /* Verify that we've received a URL for this resource. */ if (!pc->rsrc->url) return SVN_RA_DAV__XML_INVALID; /* 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_RA_DAV__XML_VALID; 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_RA_DAV__XML_INVALID; } return SVN_RA_DAV__XML_VALID; case ELEM_status: /* Parse the tag's CDATA for a status code. */ if (ne_parse_statusline(cdata, &status)) return SVN_RA_DAV__XML_INVALID; free(status.reason_phrase); pc->status = status.code; return SVN_RA_DAV__XML_VALID; case ELEM_href: /* Special handling for that belongs to the tag. */ if (rsrc->href_parent == ELEM_response) { assign_rsrc_url(pc->rsrc, cdata, pc->pool); return SVN_RA_DAV__XML_VALID; } /* 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_RA_DAV__XML_VALID; /* 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 (elm->id == ELEM_unknown) { name = apr_pstrcat(pc->pool, elm->nspace, elm->name, NULL); } else { defn = defn_from_id(elm->id); if (! (defn && defn->is_property)) return SVN_RA_DAV__XML_VALID; 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_RA_DAV__XML_INVALID; /* 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 , 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_RA_DAV__XML_VALID; } static void set_parser(ne_xml_parser *parser, void *baton) { propfind_ctx_t *pc = baton; pc->parser = parser; } 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) { svn_error_t *err = SVN_NO_ERROR; propfind_ctx_t pc; ne_buffer *body; apr_hash_t *extra_headers = apr_hash_make(pool); /* Add a Depth header. */ if (depth == NE_DEPTH_ZERO) apr_hash_set(extra_headers, "Depth", 5, "0"); else if (depth == NE_DEPTH_ONE) apr_hash_set(extra_headers, "Depth", 5, "1"); else if (depth == NE_DEPTH_INFINITE) apr_hash_set(extra_headers, "Depth", 5, "infinite"); else abort(); /* somebody passed some poo to our function. */ /* 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. */ body = ne_buffer_create(); /* The start of the request body is fixed: */ ne_buffer_zappend(body, "" DEBUG_CR "" DEBUG_CR); /* Are we asking for specific propert(y/ies), or just all of them? */ if (which_props) { int n; ne_buffer_zappend(body, "" DEBUG_CR); for (n = 0; which_props[n].name != NULL; n++) { ne_buffer_concat(body, "<", which_props[n].name, " xmlns=\"", which_props[n].nspace, "\"/>" DEBUG_CR, NULL); } ne_buffer_zappend(body, "" DEBUG_CR); } else { ne_buffer_zappend(body, "" 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); /* Create and dispatch the request! */ err = svn_ra_dav__parsed_request_compat(sess, "PROPFIND", url, body->data, 0, set_parser, propfind_elements, validate_element, start_element, end_element, &pc, extra_headers, NULL, FALSE, pool); ne_buffer_destroy(body); *results = pc.props; return err; } 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 = 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_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 svn_string_t *value; 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_DAV_PROPS_NOT_FOUND, NULL, _("'%s' was not present on the resource"), name); } *propval = value; return SVN_NO_ERROR; } svn_error_t * svn_ra_dav__get_starting_props(svn_ra_dav_resource_t **rsrc, ne_session *sess, const char *url, const char *label, apr_pool_t *pool) { return svn_ra_dav__get_props_resource(rsrc, sess, url, label, starting_props, pool); } svn_error_t * svn_ra_dav__search_for_starting_props(svn_ra_dav_resource_t **rsrc, const char **missing_path, ne_session *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; const char *lopped_path = ""; /* 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) { return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("Neon was unable to parse URL '%s'"), url); } path_s = svn_stringbuf_create(parsed_url.path, pool); /* 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)) { err = svn_ra_dav__get_starting_props(rsrc, sess, path_s->data, NULL, pool); if (! err) break; /* found an existing parent! */ if (err->apr_err != SVN_ERR_RA_DAV_PATH_NOT_FOUND) goto error; /* found a _real_ error */ /* else... lop off the basename and try again. */ lopped_path = svn_path_join(svn_path_basename(path_s->data, pool), lopped_path, pool); len = path_s->len; svn_path_remove_component(path_s); /* if we detect an infinite loop, get out. */ if (path_s->len == len) { err = svn_error_quick_wrap (err, _("The path was not part of a repository")); goto error; } 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)) { err = svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("No part of path '%s' was found in " "repository HEAD"), parsed_url.path); goto error; } *missing_path = lopped_path; error: ne_uri_free(&parsed_url); return err; } svn_error_t *svn_ra_dav__get_vcc(const char **vcc, ne_session *sess, const char *url, apr_pool_t *pool) { svn_ra_dav_resource_t *rsrc; const char *lopped_path; const svn_string_t *vcc_s; /* ### Someday, possibly look for memory-cached VCC in the RA session. */ /* ### 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_dav__search_for_starting_props(&rsrc, &lopped_path, sess, url, pool)); vcc_s = apr_hash_get(rsrc->propset, SVN_RA_DAV__PROP_VCC, APR_HASH_KEY_STRING); if (! vcc_s) return svn_error_create(APR_EGENERAL, NULL, _("The VCC property was not found on the " "resource")); *vcc = vcc_s->data; return SVN_NO_ERROR; } svn_error_t *svn_ra_dav__get_baseline_props(svn_string_t *bc_relative, svn_ra_dav_resource_t **bln_rsrc, ne_session *sess, const char *url, svn_revnum_t revision, const ne_propname *which_props, apr_pool_t *pool) { svn_ra_dav_resource_t *rsrc; const svn_string_t *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_dav__search_for_starting_props(&rsrc, &lopped_path, sess, url, pool)); 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, 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_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, 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_dav__get_one_prop(&baseline, sess, vcc->data, 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, 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_dav__get_props_resource(&rsrc, sess, vcc->data, 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_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 *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_dav__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_DAV__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_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, NULL, _("'DAV:version-name' was not present" " on the baseline resource")); } *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_dav__get_props_resource(&rsrc, sess, full_bc_url, NULL, starting_props, pool)); *is_dir = rsrc->is_collection; } if (bc_relative) *bc_relative = my_bc_rel; return SVN_NO_ERROR; } /* Helper function for svn_ra_dav__do_proppatch() below. */ static void do_setprop(ne_buffer *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) { ne_buffer_concat(body, "<", xml_tag_name, "/>", NULL); 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_string(value, pool); encoding = " V:encoding=\"base64\""; xml_safe = base64ed->data; } ne_buffer_concat(body, "<", xml_tag_name, encoding, ">", xml_safe, "", NULL); return; } svn_error_t * svn_ra_dav__do_proppatch(svn_ra_dav__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) { ne_request *req; int code; ne_buffer *body; /* ### using an ne_buffer because it can realloc */ svn_error_t *err; /* 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 = ne_buffer_create(); ne_buffer_zappend(body, "" DEBUG_CR ""); /* Handle property changes. */ if (prop_changes) { apr_hash_index_t *hi; apr_pool_t *subpool = svn_pool_create(pool); ne_buffer_zappend(body, ""); 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); do_setprop(body, key, val, subpool); } ne_buffer_zappend(body, ""); svn_pool_destroy(subpool); } /* Handle property deletions. */ if (prop_deletes) { int n; ne_buffer_zappend(body, ""); for (n = 0; n < prop_deletes->nelts; n++) { const char *name = APR_ARRAY_IDX(prop_deletes, n, const char *); do_setprop(body, name, NULL, pool); } ne_buffer_zappend(body, ""); } /* Finish up the body. */ ne_buffer_zappend(body, ""); req = ne_request_create(ras->sess, "PROPPATCH", url); ne_set_request_body_buffer(req, body->data, ne_buffer_size(body)); ne_add_request_header(req, "Content-Type", "text/xml; charset=UTF-8"); /* add any extra headers passed in by caller. */ if (extra_headers != NULL) { apr_hash_index_t *hi; for (hi = apr_hash_first(pool, extra_headers); hi; hi = apr_hash_next(hi)) { const void *key; void *val; apr_hash_this(hi, &key, NULL, &val); ne_add_request_header(req, (const char *) key, (const char *) val); } } code = ne_simple_request(ras->sess, req); if (code == NE_OK) { err = SVN_NO_ERROR; } else { /* WebDAV spec says that if any part of a PROPPATCH fails, the entire 'commit' is rejected. */ err = svn_error_create (SVN_ERR_RA_DAV_PROPPATCH_FAILED, NULL, _("At least one property change failed; repository is unchanged")); } ne_buffer_destroy(body); return err; } svn_error_t * svn_ra_dav__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_dav__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_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: 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_dav__get_baseline_info(&is_dir, NULL, NULL, NULL, ras->sess, 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_RA_DAV_PATH_NOT_FOUND) { svn_error_clear(err); *kind = svn_node_none; return SVN_NO_ERROR; } return err; } svn_error_t * svn_ra_dav__do_stat(svn_ra_session_t *session, const char *path, svn_revnum_t revision, svn_dirent_t **dirent, apr_pool_t *pool) { svn_ra_dav__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_dav__get_baseline_info(NULL, &bc_url, &bc_relative, NULL, ras->sess, url, revision, pool); if (err) { if (err->apr_err == SVN_ERR_RA_DAV_PATH_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_dav__get_props(&resources, ras->sess, final_url, NE_DEPTH_ZERO, NULL, NULL /* all props */, pool); if (err) { if (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND) { /* easy out: */ svn_error_clear(err); *dirent = NULL; return SVN_NO_ERROR; } else return err; } /* Copying parsing code from svn_ra_dav__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)) { const void *key; void *val; const char *childname; svn_ra_dav_resource_t *resource; const svn_string_t *propval; apr_hash_index_t *h; svn_dirent_t *entry; apr_hash_this(hi, &key, NULL, &val); childname = key; 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_DAV__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; void *vval; apr_hash_this(h, &kkey, NULL, &vval); 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_DAV__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_DAV__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_DAV__PROP_CREATOR_DISPLAYNAME, APR_HASH_KEY_STRING); if (propval != NULL) entry->last_author = propval->data; *dirent = entry; } return SVN_NO_ERROR; }