In the command-line client, forbid
[svn.git] / subversion / libsvn_ra_neon / props.c
blob7e63e841a7b66c030ff2fed20ba7d058e068d1a2
1 /*
2 * props.c : routines for fetching DAV properties
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <apr_pools.h>
22 #include <apr_tables.h>
23 #include <apr_strings.h>
24 #define APR_WANT_STRFUNC
25 #include <apr_want.h>
27 #include "svn_error.h"
28 #include "svn_path.h"
29 #include "svn_dav.h"
30 #include "svn_base64.h"
31 #include "svn_xml.h"
32 #include "svn_time.h"
33 #include "svn_pools.h"
34 #include "svn_props.h"
35 #include "../libsvn_ra/ra_loader.h"
37 #include "private/svn_dav_protocol.h"
38 #include "svn_private_config.h"
40 #include "ra_neon.h"
43 /* some definitions of various properties that may be fetched */
44 const ne_propname svn_ra_neon__vcc_prop = {
45 "DAV:", "version-controlled-configuration"
47 const ne_propname svn_ra_neon__checked_in_prop = {
48 "DAV:", "checked-in"
51 /* when we begin a checkout, we fetch these from the "public" resources to
52 steer us towards a Baseline Collection. we fetch the resourcetype to
53 verify that we're accessing a collection. */
54 static const ne_propname starting_props[] =
56 { "DAV:", "version-controlled-configuration" },
57 { "DAV:", "resourcetype" },
58 { SVN_DAV_PROP_NS_DAV, "baseline-relative-path" },
59 { SVN_DAV_PROP_NS_DAV, "repository-uuid"},
60 { NULL }
63 /* when speaking to a Baseline to reach the Baseline Collection, fetch these
64 properties. */
65 static const ne_propname baseline_props[] =
67 { "DAV:", "baseline-collection" },
68 { "DAV:", SVN_DAV__VERSION_NAME },
69 { NULL }
74 /*** Propfind Implementation ***/
76 typedef struct {
77 svn_ra_neon__xml_elmid id;
78 const char *name;
79 int is_property; /* is it a property, or part of some structure? */
80 } elem_defn;
83 static const elem_defn elem_definitions[] =
85 /*** NOTE: Make sure that every item in here is also represented in
86 propfind_elements[] ***/
88 /* DAV elements */
89 { ELEM_multistatus, "DAV:multistatus", 0 },
90 { ELEM_response, "DAV:response", 0 },
91 { ELEM_href, "DAV:href", SVN_RA_NEON__XML_CDATA },
92 { ELEM_propstat, "DAV:propstat", 0 },
93 { ELEM_prop, "DAV:prop", 0 },
94 { ELEM_status, "DAV:status", SVN_RA_NEON__XML_CDATA },
95 { ELEM_baseline, "DAV:baseline", SVN_RA_NEON__XML_CDATA },
96 { ELEM_collection, "DAV:collection", SVN_RA_NEON__XML_CDATA },
97 { ELEM_resourcetype, "DAV:resourcetype", 0 },
98 { ELEM_baseline_coll, SVN_RA_NEON__PROP_BASELINE_COLLECTION, 0 },
99 { ELEM_checked_in, SVN_RA_NEON__PROP_CHECKED_IN, 0 },
100 { ELEM_vcc, SVN_RA_NEON__PROP_VCC, 0 },
101 { ELEM_version_name, SVN_RA_NEON__PROP_VERSION_NAME, 1 },
102 { ELEM_get_content_length, SVN_RA_NEON__PROP_GETCONTENTLENGTH, 1 },
103 { ELEM_creationdate, SVN_RA_NEON__PROP_CREATIONDATE, 1 },
104 { ELEM_creator_displayname, SVN_RA_NEON__PROP_CREATOR_DISPLAYNAME, 1 },
106 /* SVN elements */
107 { ELEM_baseline_relpath, SVN_RA_NEON__PROP_BASELINE_RELPATH, 1 },
108 { ELEM_md5_checksum, SVN_RA_NEON__PROP_MD5_CHECKSUM, 1 },
109 { ELEM_repository_uuid, SVN_RA_NEON__PROP_REPOSITORY_UUID, 1 },
110 { ELEM_deadprop_count, SVN_RA_NEON__PROP_DEADPROP_COUNT, 1 },
111 { 0 }
115 static const svn_ra_neon__xml_elm_t propfind_elements[] =
117 /*** NOTE: Make sure that every item in here is also represented in
118 elem_definitions[] ***/
120 /* DAV elements */
121 { "DAV:", "multistatus", ELEM_multistatus, 0 },
122 { "DAV:", "response", ELEM_response, 0 },
123 { "DAV:", "href", ELEM_href, SVN_RA_NEON__XML_CDATA },
124 { "DAV:", "propstat", ELEM_propstat, 0 },
125 { "DAV:", "prop", ELEM_prop, 0 },
126 { "DAV:", "status", ELEM_status, SVN_RA_NEON__XML_CDATA },
127 { "DAV:", "baseline", ELEM_baseline, SVN_RA_NEON__XML_CDATA },
128 { "DAV:", "baseline-collection", ELEM_baseline_coll, SVN_RA_NEON__XML_CDATA },
129 { "DAV:", "checked-in", ELEM_checked_in, 0 },
130 { "DAV:", "collection", ELEM_collection, SVN_RA_NEON__XML_CDATA },
131 { "DAV:", "resourcetype", ELEM_resourcetype, 0 },
132 { "DAV:", "version-controlled-configuration", ELEM_vcc, 0 },
133 { "DAV:", SVN_DAV__VERSION_NAME, ELEM_version_name, SVN_RA_NEON__XML_CDATA },
134 { "DAV:", "getcontentlength", ELEM_get_content_length,
135 SVN_RA_NEON__XML_CDATA },
136 { "DAV:", SVN_DAV__CREATIONDATE, ELEM_creationdate, SVN_RA_NEON__XML_CDATA },
137 { "DAV:", "creator-displayname", ELEM_creator_displayname,
138 SVN_RA_NEON__XML_CDATA },
140 /* SVN elements */
141 { SVN_DAV_PROP_NS_DAV, "baseline-relative-path", ELEM_baseline_relpath,
142 SVN_RA_NEON__XML_CDATA },
143 { SVN_DAV_PROP_NS_DAV, "md5-checksum", ELEM_md5_checksum,
144 SVN_RA_NEON__XML_CDATA },
145 { SVN_DAV_PROP_NS_DAV, "repository-uuid", ELEM_repository_uuid,
146 SVN_RA_NEON__XML_CDATA },
147 { SVN_DAV_PROP_NS_DAV, "deadprop-count", ELEM_deadprop_count,
148 SVN_RA_NEON__XML_CDATA },
150 /* Unknowns */
151 { "", "", ELEM_unknown, SVN_RA_NEON__XML_COLLECT },
153 { NULL }
157 typedef struct propfind_ctx_t
159 /*WARNING: WANT_CDATA should stay the first element in the baton:
160 svn_ra_neon__xml_collect_cdata() assumes the baton starts with a stringbuf.
162 svn_stringbuf_t *cdata;
163 apr_hash_t *props; /* const char *URL-PATH -> svn_ra_neon__resource_t */
165 svn_ra_neon__resource_t *rsrc; /* the current resource. */
166 const char *encoding; /* property encoding (or NULL) */
167 int status; /* status for the current <propstat> (or 0 if unknown). */
168 apr_hash_t *propbuffer; /* holds properties until their status is known. */
169 svn_ra_neon__xml_elmid last_open_id; /* the id of the last opened tag. */
170 ne_xml_parser *parser; /* xml parser handling the PROPSET request. */
172 apr_pool_t *pool;
174 } propfind_ctx_t;
177 /* Look up an element definition ID. May return NULL if the elem is
178 not recognized. */
179 static const elem_defn *defn_from_id(svn_ra_neon__xml_elmid id)
181 const elem_defn *defn;
183 for (defn = elem_definitions; defn->name != NULL; ++defn)
185 if (id == defn->id)
186 return defn;
189 return NULL;
193 /* Assign URL to RSRC. Use POOL for any allocations. */
194 static svn_error_t *
195 assign_rsrc_url(svn_ra_neon__resource_t *rsrc,
196 const char *url, apr_pool_t *pool)
198 char *url_path;
199 apr_size_t len;
200 ne_uri parsed_url;
202 /* Parse the PATH element out of the URL.
203 NOTE: mod_dav does not (currently) use an absolute URL, but simply a
204 server-relative path (i.e. this uri_parse is effectively a no-op).
206 if (ne_uri_parse(url, &parsed_url) != 0)
208 ne_uri_free(&parsed_url);
209 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
210 _("Unable to parse URL '%s'"), url);
213 url_path = apr_pstrdup(pool, parsed_url.path);
214 ne_uri_free(&parsed_url);
216 /* Clean up trailing slashes from the URL. */
217 len = strlen(url_path);
218 if (len > 1 && url_path[len - 1] == '/')
219 url_path[len - 1] = '\0';
220 rsrc->url = url_path;
222 return SVN_NO_ERROR;
225 /* Determine whether we're receiving the expected XML response.
226 Return CHILD when interested in receiving the child's contents
227 or one of SVN_RA_NEON__XML_INVALID and SVN_RA_NEON__XML_DECLINE
228 when respectively this is the incorrect response or
229 the element (and its children) are uninteresting */
230 static int validate_element(svn_ra_neon__xml_elmid parent,
231 svn_ra_neon__xml_elmid child)
233 switch (parent)
235 case ELEM_root:
236 if (child == ELEM_multistatus)
237 return child;
238 else
239 return SVN_RA_NEON__XML_INVALID;
241 case ELEM_multistatus:
242 if (child == ELEM_response)
243 return child;
244 else
245 return SVN_RA_NEON__XML_DECLINE;
247 case ELEM_response:
248 if ((child == ELEM_href) || (child == ELEM_propstat))
249 return child;
250 else
251 return SVN_RA_NEON__XML_DECLINE;
253 case ELEM_propstat:
254 if ((child == ELEM_prop) || (child == ELEM_status))
255 return child;
256 else
257 return SVN_RA_NEON__XML_DECLINE;
259 case ELEM_prop:
260 return child; /* handle all children of <prop> */
262 case ELEM_baseline_coll:
263 case ELEM_checked_in:
264 case ELEM_vcc:
265 if (child == ELEM_href)
266 return child;
267 else
268 return SVN_RA_NEON__XML_DECLINE; /* not concerned with other types */
270 case ELEM_resourcetype:
271 if ((child == ELEM_collection) || (child == ELEM_baseline))
272 return child;
273 else
274 return SVN_RA_NEON__XML_DECLINE; /* not concerned with other types
275 (### now) */
277 default:
278 return SVN_RA_NEON__XML_DECLINE;
281 /* NOTREACHED */
285 static svn_error_t *
286 start_element(int *elem, void *baton, int parent,
287 const char *nspace, const char *name, const char **atts)
289 propfind_ctx_t *pc = baton;
290 const svn_ra_neon__xml_elm_t *elm
291 = svn_ra_neon__lookup_xml_elem(propfind_elements, nspace, name);
294 *elem = elm ? validate_element(parent, elm->id) : SVN_RA_NEON__XML_DECLINE;
295 if (*elem < 1) /* not a valid element */
296 return SVN_NO_ERROR;
298 svn_stringbuf_setempty(pc->cdata);
299 *elem = elm ? elm->id : ELEM_unknown;
300 switch (*elem)
302 case ELEM_response:
303 if (pc->rsrc)
304 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
305 /* Create a new resource. */
306 pc->rsrc = apr_pcalloc(pc->pool, sizeof(*(pc->rsrc)));
307 pc->rsrc->pool = pc->pool;
308 pc->rsrc->propset = apr_hash_make(pc->pool);
309 pc->status = 0;
310 break;
312 case ELEM_propstat:
313 pc->status = 0;
314 break;
316 case ELEM_href:
317 /* Remember this <href>'s parent so that when we close this tag,
318 we know to whom the URL assignment belongs. Could be the
319 resource itself, or one of the properties:
320 ELEM_baseline_coll, ELEM_checked_in, ELEM_vcc: */
321 pc->rsrc->href_parent = pc->last_open_id;
322 break;
324 case ELEM_collection:
325 pc->rsrc->is_collection = 1;
326 break;
328 case ELEM_unknown:
329 /* these are our user-visible properties, presumably. */
330 pc->encoding = ne_xml_get_attr(pc->parser, atts, SVN_DAV_PROP_NS_DAV,
331 "encoding");
332 if (pc->encoding)
333 pc->encoding = apr_pstrdup(pc->pool, pc->encoding);
334 break;
336 default:
337 /* nothing to do for these */
338 break;
341 /* Remember the last tag we opened. */
342 pc->last_open_id = *elem;
343 return SVN_NO_ERROR;
347 static svn_error_t * end_element(void *baton, int state,
348 const char *nspace, const char *name)
350 propfind_ctx_t *pc = baton;
351 svn_ra_neon__resource_t *rsrc = pc->rsrc;
352 const svn_string_t *value = NULL;
353 const elem_defn *parent_defn;
354 const elem_defn *defn;
355 ne_status status;
356 const char *cdata = pc->cdata->data;
358 switch (state)
360 case ELEM_response:
361 /* Verify that we've received a URL for this resource. */
362 if (!pc->rsrc->url)
363 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
365 /* Store the resource in the top-level hash table. */
366 apr_hash_set(pc->props, pc->rsrc->url, APR_HASH_KEY_STRING, pc->rsrc);
367 pc->rsrc = NULL;
368 return SVN_NO_ERROR;
370 case ELEM_propstat:
371 /* We're at the end of a set of properties. Do the right thing
372 status-wise. */
373 if (pc->status)
375 /* We have a status. Loop over the buffered properties, and
376 if the status is a good one (200), copy them into the
377 resources's property hash. Regardless of the status,
378 we'll be removing these from the temporary buffer as we
379 go along. */
380 apr_hash_index_t *hi = apr_hash_first(pc->pool, pc->propbuffer);
381 for (; hi; hi = apr_hash_next(hi))
383 const void *key;
384 apr_ssize_t klen;
385 void *val;
386 apr_hash_this(hi, &key, &klen, &val);
387 if (pc->status == 200)
388 apr_hash_set(rsrc->propset, key, klen, val);
389 apr_hash_set(pc->propbuffer, key, klen, NULL);
392 else if (! pc->status)
394 /* No status at all? Bogosity. */
395 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
397 return SVN_NO_ERROR;
399 case ELEM_status:
400 /* Parse the <status> tag's CDATA for a status code. */
401 if (ne_parse_statusline(cdata, &status))
402 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
403 free(status.reason_phrase);
404 pc->status = status.code;
405 return SVN_NO_ERROR;
407 case ELEM_href:
408 /* Special handling for <href> that belongs to the <response> tag. */
409 if (rsrc->href_parent == ELEM_response)
410 return assign_rsrc_url(pc->rsrc, cdata, pc->pool);
412 /* Use the parent element's name, not the href. */
413 parent_defn = defn_from_id(rsrc->href_parent);
415 /* No known parent? Get outta here. */
416 if (!parent_defn)
417 return SVN_NO_ERROR;
419 /* All other href's we'll treat as property values. */
420 name = parent_defn->name;
421 value = svn_string_create(cdata, pc->pool);
422 break;
424 default:
425 /*** This case is, as usual, for everything not covered by other
426 cases. ELM->id should be either ELEM_unknown, or one of
427 the ids in the elem_definitions[] structure. In this case,
428 we seek to handle properties. Since ELEM_unknown should
429 only occur for properties, we will handle that id. All
430 other ids will be searched for in the elem_definitions[]
431 structure to determine if they are properties. Properties,
432 we handle; all else hits the road. ***/
434 if (state == ELEM_unknown)
436 name = apr_pstrcat(pc->pool, nspace, name, NULL);
438 else
440 defn = defn_from_id(state);
441 if (! (defn && defn->is_property))
442 return SVN_NO_ERROR;
443 name = defn->name;
446 /* Check for encoding attribute. */
447 if (pc->encoding == NULL) {
448 /* Handle the property value by converting it to string. */
449 value = svn_string_create(cdata, pc->pool);
450 break;
453 /* Check for known encoding type */
454 if (strcmp(pc->encoding, "base64") != 0)
455 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
457 /* There is an encoding on this property, handle it.
458 * the braces are needed to allocate "in" on the stack. */
460 svn_string_t in;
461 in.data = cdata;
462 in.len = strlen(cdata);
463 value = svn_base64_decode_string(&in, pc->pool);
466 pc->encoding = NULL; /* Reset encoding for future attribute(s). */
469 /*** Handling resource properties from here out. ***/
471 /* Add properties to the temporary propbuffer. At the end of the
472 <propstat>, we'll either dump the props as invalid or move them
473 into the resource's property hash. */
474 apr_hash_set(pc->propbuffer, name, APR_HASH_KEY_STRING, value);
475 return SVN_NO_ERROR;
479 static void set_parser(ne_xml_parser *parser,
480 void *baton)
482 propfind_ctx_t *pc = baton;
483 pc->parser = parser;
487 svn_error_t * svn_ra_neon__get_props(apr_hash_t **results,
488 svn_ra_neon__session_t *sess,
489 const char *url,
490 int depth,
491 const char *label,
492 const ne_propname *which_props,
493 apr_pool_t *pool)
495 propfind_ctx_t pc;
496 svn_stringbuf_t *body;
497 apr_hash_t *extra_headers = apr_hash_make(pool);
499 svn_ra_neon__add_depth_header(extra_headers, depth);
501 /* If we have a label, use it. */
502 if (label != NULL)
503 apr_hash_set(extra_headers, "Label", 5, label);
505 /* It's easier to roll our own PROPFIND here than use neon's current
506 interfaces. */
507 /* The start of the request body is fixed: */
508 body = svn_stringbuf_create
509 ("<?xml version=\"1.0\" encoding=\"utf-8\"?>" DEBUG_CR
510 "<propfind xmlns=\"DAV:\">" DEBUG_CR, pool);
512 /* Are we asking for specific propert(y/ies), or just all of them? */
513 if (which_props)
515 int n;
516 apr_pool_t *iterpool = svn_pool_create(pool);
518 svn_stringbuf_appendcstr(body, "<prop>" DEBUG_CR);
519 for (n = 0; which_props[n].name != NULL; n++)
521 svn_pool_clear(iterpool);
522 svn_stringbuf_appendcstr
523 (body, apr_pstrcat(iterpool, "<", which_props[n].name, " xmlns=\"",
524 which_props[n].nspace, "\"/>" DEBUG_CR, NULL));
526 svn_stringbuf_appendcstr(body, "</prop></propfind>" DEBUG_CR);
527 svn_pool_destroy(iterpool);
529 else
531 svn_stringbuf_appendcstr(body, "<allprop/></propfind>" DEBUG_CR);
534 /* Initialize our baton. */
535 memset(&pc, 0, sizeof(pc));
536 pc.pool = pool;
537 pc.propbuffer = apr_hash_make(pool);
538 pc.props = apr_hash_make(pool);
539 pc.cdata = svn_stringbuf_create("", pool);
541 /* Create and dispatch the request! */
542 SVN_ERR(svn_ra_neon__parsed_request(sess, "PROPFIND", url,
543 body->data, 0,
544 set_parser,
545 start_element,
546 svn_ra_neon__xml_collect_cdata,
547 end_element,
548 &pc, extra_headers, NULL, FALSE, pool));
550 *results = pc.props;
551 return SVN_NO_ERROR;
554 svn_error_t * svn_ra_neon__get_props_resource(svn_ra_neon__resource_t **rsrc,
555 svn_ra_neon__session_t *sess,
556 const char *url,
557 const char *label,
558 const ne_propname *which_props,
559 apr_pool_t *pool)
561 apr_hash_t *props;
562 char * url_path = apr_pstrdup(pool, url);
563 int len = strlen(url);
564 /* Clean up any trailing slashes. */
565 if (len > 1 && url[len - 1] == '/')
566 url_path[len - 1] = '\0';
568 SVN_ERR(svn_ra_neon__get_props(&props, sess, url_path, SVN_RA_NEON__DEPTH_ZERO,
569 label, which_props, pool));
571 /* ### HACK. We need to have the client canonicalize paths, get rid
572 of double slashes and such. This check is just a check against
573 non-SVN servers; in the long run we want to re-enable this. */
574 if (1 || label != NULL)
576 /* pick out the first response: the URL requested will not match
577 * the response href. */
578 apr_hash_index_t *hi = apr_hash_first(pool, props);
580 if (hi)
582 void *ent;
583 apr_hash_this(hi, NULL, NULL, &ent);
584 *rsrc = ent;
586 else
587 *rsrc = NULL;
589 else
591 *rsrc = apr_hash_get(props, url_path, APR_HASH_KEY_STRING);
594 if (*rsrc == NULL)
596 /* ### hmmm, should have been in there... */
597 return svn_error_createf(APR_EGENERAL, NULL,
598 _("Failed to find label '%s' for URL '%s'"),
599 label ? label : "NULL", url_path);
602 return SVN_NO_ERROR;
605 svn_error_t * svn_ra_neon__get_one_prop(const svn_string_t **propval,
606 svn_ra_neon__session_t *sess,
607 const char *url,
608 const char *label,
609 const ne_propname *propname,
610 apr_pool_t *pool)
612 svn_ra_neon__resource_t *rsrc;
613 ne_propname props[2] = { { 0 } };
614 const char *name;
615 const svn_string_t *value;
617 props[0] = *propname;
618 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc, sess, url, label, props,
619 pool));
621 name = apr_pstrcat(pool, propname->nspace, propname->name, NULL);
622 value = apr_hash_get(rsrc->propset, name, APR_HASH_KEY_STRING);
623 if (value == NULL)
625 /* ### need an SVN_ERR here */
626 return svn_error_createf(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
627 _("'%s' was not present on the resource"),
628 name);
631 *propval = value;
632 return SVN_NO_ERROR;
635 svn_error_t * svn_ra_neon__get_starting_props(svn_ra_neon__resource_t **rsrc,
636 svn_ra_neon__session_t *sess,
637 const char *url,
638 const char *label,
639 apr_pool_t *pool)
641 return svn_ra_neon__get_props_resource(rsrc, sess, url, label, starting_props,
642 pool);
647 svn_error_t *
648 svn_ra_neon__search_for_starting_props(svn_ra_neon__resource_t **rsrc,
649 const char **missing_path,
650 svn_ra_neon__session_t *sess,
651 const char *url,
652 apr_pool_t *pool)
654 svn_error_t *err = SVN_NO_ERROR;
655 apr_size_t len;
656 svn_stringbuf_t *path_s;
657 ne_uri parsed_url;
658 svn_stringbuf_t *lopped_path =
659 svn_stringbuf_create(url, pool); /* initialize to make sure it'll fit
660 without reallocating */
661 apr_pool_t *iterpool = svn_pool_create(pool);
663 /* Split the url into its component pieces (scheme, host, path,
664 etc). We want the path part. */
665 ne_uri_parse(url, &parsed_url);
666 if (parsed_url.path == NULL)
668 ne_uri_free(&parsed_url);
669 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
670 _("Neon was unable to parse URL '%s'"), url);
673 svn_stringbuf_setempty(lopped_path);
674 path_s = svn_stringbuf_create(parsed_url.path, pool);
675 ne_uri_free(&parsed_url);
677 /* Try to get the starting_props from the public url. If the
678 resource no longer exists in HEAD, we'll get a failure. That's
679 fine: just keep removing components and trying to get the
680 starting_props from parent directories. */
681 while (! svn_path_is_empty(path_s->data))
683 svn_pool_clear(iterpool);
684 err = svn_ra_neon__get_starting_props(rsrc, sess, path_s->data,
685 NULL, iterpool);
686 if (! err)
687 break; /* found an existing parent! */
689 if (err->apr_err != SVN_ERR_RA_DAV_PATH_NOT_FOUND)
690 return err; /* found a _real_ error */
692 /* else... lop off the basename and try again. */
693 svn_stringbuf_set(lopped_path,
694 svn_path_join(svn_path_basename(path_s->data, iterpool),
695 lopped_path->data, iterpool));
697 len = path_s->len;
698 svn_path_remove_component(path_s);
700 /* if we detect an infinite loop, get out. */
701 if (path_s->len == len)
702 return svn_error_quick_wrap
703 (err, _("The path was not part of a repository"));
705 svn_error_clear(err);
708 /* error out if entire URL was bogus (not a single part of it exists
709 in the repository!) */
710 if (svn_path_is_empty(path_s->data))
711 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
712 _("No part of path '%s' was found in "
713 "repository HEAD"), parsed_url.path);
715 /* Duplicate rsrc out of iterpool into pool */
717 apr_hash_index_t *hi;
718 svn_ra_neon__resource_t *tmp = apr_pcalloc(pool, sizeof(*tmp));
719 tmp->url = apr_pstrdup(pool, (*rsrc)->url);
720 tmp->is_collection = (*rsrc)->is_collection;
721 tmp->pool = pool;
722 tmp->propset = apr_hash_make(pool);
724 for (hi = apr_hash_first(iterpool, (*rsrc)->propset);
725 hi; hi = apr_hash_next(hi))
727 const void *key;
728 void *val;
730 apr_hash_this(hi, &key, NULL, &val);
731 apr_hash_set(tmp->propset, apr_pstrdup(pool, key), APR_HASH_KEY_STRING,
732 svn_string_dup(val, pool));
735 *rsrc = tmp;
737 *missing_path = lopped_path->data;
738 svn_pool_destroy(iterpool);
739 return SVN_NO_ERROR;
743 svn_error_t *svn_ra_neon__get_vcc(const char **vcc,
744 svn_ra_neon__session_t *sess,
745 const char *url,
746 apr_pool_t *pool)
748 svn_ra_neon__resource_t *rsrc;
749 const char *lopped_path;
750 const svn_string_t *vcc_s;
752 /* ### Someday, possibly look for memory-cached VCC in the RA session. */
754 /* ### Someday, possibly look for disk-cached VCC via get_wcprop callback. */
756 /* Finally, resort to a set of PROPFINDs up parent directories. */
757 SVN_ERR(svn_ra_neon__search_for_starting_props(&rsrc, &lopped_path,
758 sess, url, pool));
760 vcc_s = apr_hash_get(rsrc->propset,
761 SVN_RA_NEON__PROP_VCC, APR_HASH_KEY_STRING);
762 if (! vcc_s)
763 return svn_error_create(APR_EGENERAL, NULL,
764 _("The VCC property was not found on the "
765 "resource"));
767 *vcc = vcc_s->data;
768 return SVN_NO_ERROR;
772 svn_error_t *svn_ra_neon__get_baseline_props(svn_string_t *bc_relative,
773 svn_ra_neon__resource_t **bln_rsrc,
774 svn_ra_neon__session_t *sess,
775 const char *url,
776 svn_revnum_t revision,
777 const ne_propname *which_props,
778 apr_pool_t *pool)
780 svn_ra_neon__resource_t *rsrc;
781 const svn_string_t *vcc;
782 const svn_string_t *relative_path;
783 const char *my_bc_relative;
784 const char *lopped_path;
786 /* ### we may be able to replace some/all of this code with an
787 ### expand-property REPORT when that is available on the server. */
789 /* -------------------------------------------------------------------
790 STEP 1
792 Fetch the following properties from the given URL (or, if URL no
793 longer exists in HEAD, get the properties from the nearest
794 still-existing parent resource):
796 *) DAV:version-controlled-configuration so that we can reach the
797 baseline information.
799 *) svn:baseline-relative-path so that we can find this resource
800 within a Baseline Collection. If we need to search up parent
801 directories, then the relative path is this property value
802 *plus* any trailing components we had to chop off.
804 *) DAV:resourcetype so that we can identify whether this resource
805 is a collection or not -- assuming we never had to search up
806 parent directories.
809 SVN_ERR(svn_ra_neon__search_for_starting_props(&rsrc, &lopped_path,
810 sess, url, pool));
812 vcc = apr_hash_get(rsrc->propset, SVN_RA_NEON__PROP_VCC, APR_HASH_KEY_STRING);
813 if (vcc == NULL)
815 /* ### better error reporting... */
817 /* ### need an SVN_ERR here */
818 return svn_error_create(APR_EGENERAL, NULL,
819 _("The VCC property was not found on the "
820 "resource"));
823 /* Allocate our own bc_relative path. */
824 relative_path = apr_hash_get(rsrc->propset,
825 SVN_RA_NEON__PROP_BASELINE_RELPATH,
826 APR_HASH_KEY_STRING);
827 if (relative_path == NULL)
829 /* ### better error reporting... */
830 /* ### need an SVN_ERR here */
831 return svn_error_create(APR_EGENERAL, NULL,
832 _("The relative-path property was not "
833 "found on the resource"));
836 /* don't forget to tack on the parts we lopped off in order to find
837 the VCC... We are expected to return a URI decoded relative
838 path, so decode the lopped path first. */
839 my_bc_relative = svn_path_join(relative_path->data,
840 svn_path_uri_decode(lopped_path, pool),
841 pool);
843 /* if they want the relative path (could be, they're just trying to find
844 the baseline collection), then return it */
845 if (bc_relative)
847 bc_relative->data = my_bc_relative;
848 bc_relative->len = strlen(my_bc_relative);
851 /* -------------------------------------------------------------------
852 STEP 2
854 We have the Version Controlled Configuration (VCC). From here, we
855 need to reach the Baseline for specified revision.
857 If the revision is SVN_INVALID_REVNUM, then we're talking about
858 the HEAD revision. We have one extra step to reach the Baseline:
860 *) Fetch the DAV:checked-in from the VCC; it points to the Baseline.
862 If we have a specific revision, then we use a Label header when
863 fetching props from the VCC. This will direct us to the Baseline
864 with that label (in this case, the label == the revision number).
866 From the Baseline, we fetch the following properties:
868 *) DAV:baseline-collection, which is a complete tree of the Baseline
869 (in SVN terms, this tree is rooted at a specific revision)
871 *) DAV:version-name to get the revision of the Baseline that we are
872 querying. When asking about the HEAD, this tells us its revision.
875 if (revision == SVN_INVALID_REVNUM)
877 /* Fetch the latest revision */
879 const svn_string_t *baseline;
881 /* Get the Baseline from the DAV:checked-in value, then fetch its
882 DAV:baseline-collection property. */
883 /* ### should wrap this with info about rsrc==VCC */
884 SVN_ERR(svn_ra_neon__get_one_prop(&baseline, sess, vcc->data, NULL,
885 &svn_ra_neon__checked_in_prop, pool));
887 /* ### do we want to optimize the props we fetch, based on what the
888 ### user asked for? i.e. omit version-name if latest_rev is NULL */
889 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc, sess,
890 baseline->data, NULL,
891 which_props, pool));
893 else
895 /* Fetch a specific revision */
897 char label[20];
899 /* ### send Label hdr, get DAV:baseline-collection [from the baseline] */
901 apr_snprintf(label, sizeof(label), "%ld", revision);
903 /* ### do we want to optimize the props we fetch, based on what the
904 ### user asked for? i.e. omit version-name if latest_rev is NULL */
905 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc, sess, vcc->data, label,
906 which_props, pool));
909 /* Return the baseline rsrc, which now contains whatever set of
910 props the caller wanted. */
911 *bln_rsrc = rsrc;
912 return SVN_NO_ERROR;
916 svn_error_t *svn_ra_neon__get_baseline_info(svn_boolean_t *is_dir,
917 svn_string_t *bc_url,
918 svn_string_t *bc_relative,
919 svn_revnum_t *latest_rev,
920 svn_ra_neon__session_t *sess,
921 const char *url,
922 svn_revnum_t revision,
923 apr_pool_t *pool)
925 svn_ra_neon__resource_t *baseline_rsrc, *rsrc;
926 const svn_string_t *my_bc_url;
927 svn_string_t my_bc_rel;
929 /* Go fetch a BASELINE_RSRC that contains specific properties we
930 want. This routine will also fill in BC_RELATIVE as best it
931 can. */
932 SVN_ERR(svn_ra_neon__get_baseline_props(&my_bc_rel,
933 &baseline_rsrc,
934 sess,
935 url,
936 revision,
937 baseline_props, /* specific props */
938 pool));
940 /* baseline_rsrc now points at the Baseline. We will checkout from
941 the DAV:baseline-collection. The revision we are checking out is
942 in DAV:version-name */
944 /* Allocate our own copy of bc_url regardless. */
945 my_bc_url = apr_hash_get(baseline_rsrc->propset,
946 SVN_RA_NEON__PROP_BASELINE_COLLECTION,
947 APR_HASH_KEY_STRING);
948 if (my_bc_url == NULL)
950 /* ### better error reporting... */
951 /* ### need an SVN_ERR here */
952 return svn_error_create(APR_EGENERAL, NULL,
953 _("'DAV:baseline-collection' was not present "
954 "on the baseline resource"));
957 /* maybe return bc_url to the caller */
958 if (bc_url)
959 *bc_url = *my_bc_url;
961 if (latest_rev != NULL)
963 const svn_string_t *vsn_name= apr_hash_get(baseline_rsrc->propset,
964 SVN_RA_NEON__PROP_VERSION_NAME,
965 APR_HASH_KEY_STRING);
966 if (vsn_name == NULL)
968 /* ### better error reporting... */
970 /* ### need an SVN_ERR here */
971 return svn_error_createf(APR_EGENERAL, NULL,
972 _("'%s' was not present on the baseline "
973 "resource"),
974 "DAV:" SVN_DAV__VERSION_NAME);
976 *latest_rev = SVN_STR_TO_REV(vsn_name->data);
979 if (is_dir != NULL)
981 /* query the DAV:resourcetype of the full, assembled URL. */
982 const char *full_bc_url = svn_path_url_add_component(my_bc_url->data,
983 my_bc_rel.data,
984 pool);
985 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc, sess, full_bc_url,
986 NULL, starting_props, pool));
987 *is_dir = rsrc->is_collection;
990 if (bc_relative)
991 *bc_relative = my_bc_rel;
993 return SVN_NO_ERROR;
997 /* Helper function for svn_ra_neon__do_proppatch() below. */
998 static void
999 append_setprop(svn_stringbuf_t *body,
1000 const char *name,
1001 const svn_string_t *value,
1002 apr_pool_t *pool)
1004 const char *encoding = "";
1005 const char *xml_safe;
1006 const char *xml_tag_name;
1008 /* Map property names to namespaces */
1009 #define NSLEN (sizeof(SVN_PROP_PREFIX) - 1)
1010 if (strncmp(name, SVN_PROP_PREFIX, NSLEN) == 0)
1012 xml_tag_name = apr_pstrcat(pool, "S:", name + NSLEN, NULL);
1014 #undef NSLEN
1015 else
1017 xml_tag_name = apr_pstrcat(pool, "C:", name, NULL);
1020 /* If there is no value, just generate an empty tag and get outta
1021 here. */
1022 if (! value)
1024 svn_stringbuf_appendcstr(body,
1025 apr_psprintf(pool, "<%s />", xml_tag_name));
1026 return;
1029 /* If a property is XML-safe, XML-encode it. Else, base64-encode
1030 it. */
1031 if (svn_xml_is_xml_safe(value->data, value->len))
1033 svn_stringbuf_t *xml_esc = NULL;
1034 svn_xml_escape_cdata_string(&xml_esc, value, pool);
1035 xml_safe = xml_esc->data;
1037 else
1039 const svn_string_t *base64ed = svn_base64_encode_string(value, pool);
1040 encoding = " V:encoding=\"base64\"";
1041 xml_safe = base64ed->data;
1044 svn_stringbuf_appendcstr(body,
1045 apr_psprintf(pool,"<%s %s>%s</%s>",
1046 xml_tag_name, encoding,
1047 xml_safe, xml_tag_name));
1048 return;
1052 svn_error_t *
1053 svn_ra_neon__do_proppatch(svn_ra_neon__session_t *ras,
1054 const char *url,
1055 apr_hash_t *prop_changes,
1056 apr_array_header_t *prop_deletes,
1057 apr_hash_t *extra_headers,
1058 apr_pool_t *pool)
1060 svn_error_t *err;
1061 svn_stringbuf_t *body;
1062 apr_pool_t *subpool = svn_pool_create(pool);
1064 /* just punt if there are no changes to make. */
1065 if ((prop_changes == NULL || (! apr_hash_count(prop_changes)))
1066 && (prop_deletes == NULL || prop_deletes->nelts == 0))
1067 return SVN_NO_ERROR;
1069 /* easier to roll our own PROPPATCH here than use ne_proppatch(), which
1070 * doesn't really do anything clever. */
1071 body = svn_stringbuf_create
1072 ("<?xml version=\"1.0\" encoding=\"utf-8\" ?>" DEBUG_CR
1073 "<D:propertyupdate xmlns:D=\"DAV:\" xmlns:V=\""
1074 SVN_DAV_PROP_NS_DAV "\" xmlns:C=\""
1075 SVN_DAV_PROP_NS_CUSTOM "\" xmlns:S=\""
1076 SVN_DAV_PROP_NS_SVN "\">" DEBUG_CR, pool);
1078 /* Handle property changes. */
1079 if (prop_changes)
1081 apr_hash_index_t *hi;
1082 svn_stringbuf_appendcstr(body, "<D:set><D:prop>");
1083 for (hi = apr_hash_first(pool, prop_changes); hi; hi = apr_hash_next(hi))
1085 const void *key;
1086 void *val;
1087 svn_pool_clear(subpool);
1088 apr_hash_this(hi, &key, NULL, &val);
1089 append_setprop(body, key, val, subpool);
1091 svn_stringbuf_appendcstr(body, "</D:prop></D:set>");
1094 /* Handle property deletions. */
1095 if (prop_deletes)
1097 int n;
1098 svn_stringbuf_appendcstr(body, "<D:remove><D:prop>");
1099 for (n = 0; n < prop_deletes->nelts; n++)
1101 const char *name = APR_ARRAY_IDX(prop_deletes, n, const char *);
1102 svn_pool_clear(subpool);
1103 append_setprop(body, name, NULL, subpool);
1105 svn_stringbuf_appendcstr(body, "</D:prop></D:remove>");
1107 svn_pool_destroy(subpool);
1109 /* Finish up the body. */
1110 svn_stringbuf_appendcstr(body, "</D:propertyupdate>");
1112 /* Finish up the headers. */
1113 if (! extra_headers)
1114 extra_headers = apr_hash_make(pool);
1115 apr_hash_set(extra_headers, "Content-Type", APR_HASH_KEY_STRING,
1116 "text/xml; charset=UTF-8");
1118 err = svn_ra_neon__simple_request(NULL, ras, "PROPPATCH", url,
1119 extra_headers, body->data,
1120 200, 207, pool);
1121 if (err)
1122 return svn_error_create
1123 (SVN_ERR_RA_DAV_PROPPATCH_FAILED, err,
1124 _("At least one property change failed; repository is unchanged"));
1126 return SVN_NO_ERROR;
1131 svn_error_t *
1132 svn_ra_neon__do_check_path(svn_ra_session_t *session,
1133 const char *path,
1134 svn_revnum_t revision,
1135 svn_node_kind_t *kind,
1136 apr_pool_t *pool)
1138 svn_ra_neon__session_t *ras = session->priv;
1139 const char *url = ras->url->data;
1140 svn_error_t *err;
1141 svn_boolean_t is_dir;
1143 /* ### For now, using svn_ra_neon__get_baseline_info() works because
1144 we only have three possibilities: dir, file, or none. When we
1145 add symlinks, we will need to do something different. Here's one
1146 way described by Greg Stein:
1148 That is a PROPFIND (Depth:0) for the DAV:resourcetype property.
1150 You can use the svn_ra_neon__get_one_prop() function to fetch
1151 it. If the PROPFIND fails with a 404, then you have
1152 svn_node_none. If the resulting property looks like:
1154 <D:resourcetype>
1155 <D:collection/>
1156 </D:resourcetype>
1158 Then it is a collection (directory; svn_node_dir). Otherwise,
1159 it is a regular resource (svn_node_file).
1161 The harder part is parsing the resourcetype property. "Proper"
1162 parsing means treating it as an XML property and looking for
1163 the DAV:collection element in there. To do that, however, means
1164 that get_one_prop() can't be used. I think there may be some
1165 Neon functions for parsing XML properties; we'd need to
1166 look. That would probably be the best approach. (an alternative
1167 is to use apr_xml_* parsing functions on the returned string;
1168 get back a DOM-like thing, and look for the element).
1171 /* If we were given a relative path to append, append it. */
1172 if (path)
1173 url = svn_path_url_add_component(url, path, pool);
1175 err = svn_ra_neon__get_baseline_info(&is_dir, NULL, NULL, NULL,
1176 ras, url, revision, pool);
1178 if (err == SVN_NO_ERROR)
1180 if (is_dir)
1181 *kind = svn_node_dir;
1182 else
1183 *kind = svn_node_file;
1185 else if (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
1188 svn_error_clear(err);
1189 *kind = svn_node_none;
1190 return SVN_NO_ERROR;
1193 return err;
1197 svn_error_t *
1198 svn_ra_neon__do_stat(svn_ra_session_t *session,
1199 const char *path,
1200 svn_revnum_t revision,
1201 svn_dirent_t **dirent,
1202 apr_pool_t *pool)
1204 svn_ra_neon__session_t *ras = session->priv;
1205 const char *url = ras->url->data;
1206 const char *final_url;
1207 apr_hash_t *resources;
1208 apr_hash_index_t *hi;
1209 svn_error_t *err;
1211 /* If we were given a relative path to append, append it. */
1212 if (path)
1213 url = svn_path_url_add_component(url, path, pool);
1215 /* Invalid revision means HEAD, which is just the public URL. */
1216 if (! SVN_IS_VALID_REVNUM(revision))
1218 final_url = url;
1220 else
1222 /* Else, convert (rev, path) into an opaque server-generated URL. */
1223 svn_string_t bc_url, bc_relative;
1225 err = svn_ra_neon__get_baseline_info(NULL, &bc_url, &bc_relative,
1226 NULL, ras,
1227 url, revision, pool);
1228 if (err)
1230 if (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
1232 /* easy out: */
1233 svn_error_clear(err);
1234 *dirent = NULL;
1235 return SVN_NO_ERROR;
1237 else
1238 return err;
1241 final_url = svn_path_url_add_component(bc_url.data, bc_relative.data,
1242 pool);
1245 /* Depth-zero PROPFIND is the One True DAV Way. */
1246 err = svn_ra_neon__get_props(&resources, ras, final_url,
1247 SVN_RA_NEON__DEPTH_ZERO,
1248 NULL, NULL /* all props */, pool);
1249 if (err)
1251 if (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
1253 /* easy out: */
1254 svn_error_clear(err);
1255 *dirent = NULL;
1256 return SVN_NO_ERROR;
1258 else
1259 return err;
1262 /* Copying parsing code from svn_ra_neon__get_dir() here. The hash
1263 of resources only contains one item, but there's no other way to
1264 get the item. */
1265 for (hi = apr_hash_first(pool, resources); hi; hi = apr_hash_next(hi))
1267 void *val;
1268 svn_ra_neon__resource_t *resource;
1269 const svn_string_t *propval;
1270 apr_hash_index_t *h;
1271 svn_dirent_t *entry;
1273 apr_hash_this(hi, NULL, NULL, &val);
1274 resource = val;
1276 entry = apr_pcalloc(pool, sizeof(*entry));
1278 entry->kind = resource->is_collection ? svn_node_dir : svn_node_file;
1280 /* entry->size is already 0 by virtue of pcalloc(). */
1281 if (entry->kind == svn_node_file)
1283 propval = apr_hash_get(resource->propset,
1284 SVN_RA_NEON__PROP_GETCONTENTLENGTH,
1285 APR_HASH_KEY_STRING);
1286 if (propval)
1287 entry->size = svn__atoui64(propval->data);
1290 /* does this resource contain any 'dead' properties? */
1291 for (h = apr_hash_first(pool, resource->propset);
1292 h; h = apr_hash_next(h))
1294 const void *kkey;
1295 apr_hash_this(h, &kkey, NULL, NULL);
1297 if (strncmp((const char *)kkey, SVN_DAV_PROP_NS_CUSTOM,
1298 sizeof(SVN_DAV_PROP_NS_CUSTOM) - 1) == 0)
1299 entry->has_props = TRUE;
1301 else if (strncmp((const char *)kkey, SVN_DAV_PROP_NS_SVN,
1302 sizeof(SVN_DAV_PROP_NS_SVN) - 1) == 0)
1303 entry->has_props = TRUE;
1306 /* created_rev & friends */
1307 propval = apr_hash_get(resource->propset,
1308 SVN_RA_NEON__PROP_VERSION_NAME,
1309 APR_HASH_KEY_STRING);
1310 if (propval != NULL)
1311 entry->created_rev = SVN_STR_TO_REV(propval->data);
1313 propval = apr_hash_get(resource->propset,
1314 SVN_RA_NEON__PROP_CREATIONDATE,
1315 APR_HASH_KEY_STRING);
1316 if (propval != NULL)
1317 SVN_ERR(svn_time_from_cstring(&(entry->time),
1318 propval->data, pool));
1320 propval = apr_hash_get(resource->propset,
1321 SVN_RA_NEON__PROP_CREATOR_DISPLAYNAME,
1322 APR_HASH_KEY_STRING);
1323 if (propval != NULL)
1324 entry->last_author = propval->data;
1326 *dirent = entry;
1329 return SVN_NO_ERROR;