Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_ra_neon / props.c
blob1877f0f8f60f5e521e82e34b69d1343d831fa1ab
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 svn_string_t *propval;
643 SVN_ERR(svn_ra_neon__get_props_resource(rsrc, sess, url, label,
644 starting_props, pool));
646 /* Cache some of the resource information. */
648 if (! sess->vcc)
650 propval = apr_hash_get((*rsrc)->propset,
651 SVN_RA_NEON__PROP_VCC,
652 APR_HASH_KEY_STRING);
653 if (propval)
654 sess->vcc = apr_pstrdup(sess->pool, propval->data);
657 if (! sess->uuid)
659 propval = apr_hash_get((*rsrc)->propset,
660 SVN_RA_NEON__PROP_REPOSITORY_UUID,
661 APR_HASH_KEY_STRING);
662 if (propval)
663 sess->uuid = apr_pstrdup(sess->pool, propval->data);
666 return SVN_NO_ERROR;
671 svn_error_t *
672 svn_ra_neon__search_for_starting_props(svn_ra_neon__resource_t **rsrc,
673 const char **missing_path,
674 svn_ra_neon__session_t *sess,
675 const char *url,
676 apr_pool_t *pool)
678 svn_error_t *err = SVN_NO_ERROR;
679 apr_size_t len;
680 svn_stringbuf_t *path_s;
681 ne_uri parsed_url;
682 svn_stringbuf_t *lopped_path =
683 svn_stringbuf_create(url, pool); /* initialize to make sure it'll fit
684 without reallocating */
685 apr_pool_t *iterpool = svn_pool_create(pool);
687 /* Split the url into its component pieces (scheme, host, path,
688 etc). We want the path part. */
689 ne_uri_parse(url, &parsed_url);
690 if (parsed_url.path == NULL)
692 ne_uri_free(&parsed_url);
693 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
694 _("Neon was unable to parse URL '%s'"), url);
697 svn_stringbuf_setempty(lopped_path);
698 path_s = svn_stringbuf_create(parsed_url.path, pool);
699 ne_uri_free(&parsed_url);
701 /* Try to get the starting_props from the public url. If the
702 resource no longer exists in HEAD, we'll get a failure. That's
703 fine: just keep removing components and trying to get the
704 starting_props from parent directories. */
705 while (! svn_path_is_empty(path_s->data))
707 svn_pool_clear(iterpool);
708 err = svn_ra_neon__get_starting_props(rsrc, sess, path_s->data,
709 NULL, iterpool);
710 if (! err)
711 break; /* found an existing parent! */
713 if (err->apr_err != SVN_ERR_RA_DAV_PATH_NOT_FOUND)
714 return err; /* found a _real_ error */
716 /* else... lop off the basename and try again. */
717 svn_stringbuf_set(lopped_path,
718 svn_path_join(svn_path_basename(path_s->data, iterpool),
719 lopped_path->data, iterpool));
721 len = path_s->len;
722 svn_path_remove_component(path_s);
724 /* if we detect an infinite loop, get out. */
725 if (path_s->len == len)
726 return svn_error_quick_wrap
727 (err, _("The path was not part of a repository"));
729 svn_error_clear(err);
732 /* error out if entire URL was bogus (not a single part of it exists
733 in the repository!) */
734 if (svn_path_is_empty(path_s->data))
735 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
736 _("No part of path '%s' was found in "
737 "repository HEAD"), parsed_url.path);
739 /* Duplicate rsrc out of iterpool into pool */
741 apr_hash_index_t *hi;
742 svn_ra_neon__resource_t *tmp = apr_pcalloc(pool, sizeof(*tmp));
743 tmp->url = apr_pstrdup(pool, (*rsrc)->url);
744 tmp->is_collection = (*rsrc)->is_collection;
745 tmp->pool = pool;
746 tmp->propset = apr_hash_make(pool);
748 for (hi = apr_hash_first(iterpool, (*rsrc)->propset);
749 hi; hi = apr_hash_next(hi))
751 const void *key;
752 void *val;
754 apr_hash_this(hi, &key, NULL, &val);
755 apr_hash_set(tmp->propset, apr_pstrdup(pool, key), APR_HASH_KEY_STRING,
756 svn_string_dup(val, pool));
759 *rsrc = tmp;
761 *missing_path = lopped_path->data;
762 svn_pool_destroy(iterpool);
763 return SVN_NO_ERROR;
767 svn_error_t *svn_ra_neon__get_vcc(const char **vcc,
768 svn_ra_neon__session_t *sess,
769 const char *url,
770 apr_pool_t *pool)
772 svn_ra_neon__resource_t *rsrc;
773 const char *lopped_path;
775 /* Look for memory-cached VCC in the RA session. */
776 if (sess->vcc)
778 *vcc = sess->vcc;
779 return SVN_NO_ERROR;
782 /* ### Someday, possibly look for disk-cached VCC via get_wcprop callback. */
784 /* Finally, resort to a set of PROPFINDs up parent directories. */
785 SVN_ERR(svn_ra_neon__search_for_starting_props(&rsrc, &lopped_path,
786 sess, url, pool));
788 if (! sess->vcc)
790 /* ### better error reporting... */
791 return svn_error_create(APR_EGENERAL, NULL,
792 _("The VCC property was not found on the "
793 "resource"));
796 *vcc = sess->vcc;
797 return SVN_NO_ERROR;
801 svn_error_t *svn_ra_neon__get_baseline_props(svn_string_t *bc_relative,
802 svn_ra_neon__resource_t **bln_rsrc,
803 svn_ra_neon__session_t *sess,
804 const char *url,
805 svn_revnum_t revision,
806 const ne_propname *which_props,
807 apr_pool_t *pool)
809 svn_ra_neon__resource_t *rsrc;
810 const char *vcc;
811 const svn_string_t *relative_path;
812 const char *my_bc_relative;
813 const char *lopped_path;
815 /* ### we may be able to replace some/all of this code with an
816 ### expand-property REPORT when that is available on the server. */
818 /* -------------------------------------------------------------------
819 STEP 1
821 Fetch the following properties from the given URL (or, if URL no
822 longer exists in HEAD, get the properties from the nearest
823 still-existing parent resource):
825 *) DAV:version-controlled-configuration so that we can reach the
826 baseline information.
828 *) svn:baseline-relative-path so that we can find this resource
829 within a Baseline Collection. If we need to search up parent
830 directories, then the relative path is this property value
831 *plus* any trailing components we had to chop off.
833 *) DAV:resourcetype so that we can identify whether this resource
834 is a collection or not -- assuming we never had to search up
835 parent directories.
838 SVN_ERR(svn_ra_neon__search_for_starting_props(&rsrc, &lopped_path,
839 sess, url, pool));
841 SVN_ERR(svn_ra_neon__get_vcc(&vcc, sess, url, pool));
842 if (vcc == NULL)
844 /* ### better error reporting... */
846 /* ### need an SVN_ERR here */
847 return svn_error_create(APR_EGENERAL, NULL,
848 _("The VCC property was not found on the "
849 "resource"));
852 /* Allocate our own bc_relative path. */
853 relative_path = apr_hash_get(rsrc->propset,
854 SVN_RA_NEON__PROP_BASELINE_RELPATH,
855 APR_HASH_KEY_STRING);
856 if (relative_path == NULL)
858 /* ### better error reporting... */
859 /* ### need an SVN_ERR here */
860 return svn_error_create(APR_EGENERAL, NULL,
861 _("The relative-path property was not "
862 "found on the resource"));
865 /* don't forget to tack on the parts we lopped off in order to find
866 the VCC... We are expected to return a URI decoded relative
867 path, so decode the lopped path first. */
868 my_bc_relative = svn_path_join(relative_path->data,
869 svn_path_uri_decode(lopped_path, pool),
870 pool);
872 /* if they want the relative path (could be, they're just trying to find
873 the baseline collection), then return it */
874 if (bc_relative)
876 bc_relative->data = my_bc_relative;
877 bc_relative->len = strlen(my_bc_relative);
880 /* -------------------------------------------------------------------
881 STEP 2
883 We have the Version Controlled Configuration (VCC). From here, we
884 need to reach the Baseline for specified revision.
886 If the revision is SVN_INVALID_REVNUM, then we're talking about
887 the HEAD revision. We have one extra step to reach the Baseline:
889 *) Fetch the DAV:checked-in from the VCC; it points to the Baseline.
891 If we have a specific revision, then we use a Label header when
892 fetching props from the VCC. This will direct us to the Baseline
893 with that label (in this case, the label == the revision number).
895 From the Baseline, we fetch the following properties:
897 *) DAV:baseline-collection, which is a complete tree of the Baseline
898 (in SVN terms, this tree is rooted at a specific revision)
900 *) DAV:version-name to get the revision of the Baseline that we are
901 querying. When asking about the HEAD, this tells us its revision.
904 if (revision == SVN_INVALID_REVNUM)
906 /* Fetch the latest revision */
908 const svn_string_t *baseline;
910 /* Get the Baseline from the DAV:checked-in value, then fetch its
911 DAV:baseline-collection property. */
912 /* ### should wrap this with info about rsrc==VCC */
913 SVN_ERR(svn_ra_neon__get_one_prop(&baseline, sess, vcc, NULL,
914 &svn_ra_neon__checked_in_prop, pool));
916 /* ### do we want to optimize the props we fetch, based on what the
917 ### user asked for? i.e. omit version-name if latest_rev is NULL */
918 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc, sess,
919 baseline->data, NULL,
920 which_props, pool));
922 else
924 /* Fetch a specific revision */
926 char label[20];
928 /* ### send Label hdr, get DAV:baseline-collection [from the baseline] */
930 apr_snprintf(label, sizeof(label), "%ld", revision);
932 /* ### do we want to optimize the props we fetch, based on what the
933 ### user asked for? i.e. omit version-name if latest_rev is NULL */
934 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc, sess, vcc, label,
935 which_props, pool));
938 /* Return the baseline rsrc, which now contains whatever set of
939 props the caller wanted. */
940 *bln_rsrc = rsrc;
941 return SVN_NO_ERROR;
945 svn_error_t *svn_ra_neon__get_baseline_info(svn_boolean_t *is_dir,
946 svn_string_t *bc_url,
947 svn_string_t *bc_relative,
948 svn_revnum_t *latest_rev,
949 svn_ra_neon__session_t *sess,
950 const char *url,
951 svn_revnum_t revision,
952 apr_pool_t *pool)
954 svn_ra_neon__resource_t *baseline_rsrc, *rsrc;
955 const svn_string_t *my_bc_url;
956 svn_string_t my_bc_rel;
958 /* Go fetch a BASELINE_RSRC that contains specific properties we
959 want. This routine will also fill in BC_RELATIVE as best it
960 can. */
961 SVN_ERR(svn_ra_neon__get_baseline_props(&my_bc_rel,
962 &baseline_rsrc,
963 sess,
964 url,
965 revision,
966 baseline_props, /* specific props */
967 pool));
969 /* baseline_rsrc now points at the Baseline. We will checkout from
970 the DAV:baseline-collection. The revision we are checking out is
971 in DAV:version-name */
973 /* Allocate our own copy of bc_url regardless. */
974 my_bc_url = apr_hash_get(baseline_rsrc->propset,
975 SVN_RA_NEON__PROP_BASELINE_COLLECTION,
976 APR_HASH_KEY_STRING);
977 if (my_bc_url == NULL)
979 /* ### better error reporting... */
980 /* ### need an SVN_ERR here */
981 return svn_error_create(APR_EGENERAL, NULL,
982 _("'DAV:baseline-collection' was not present "
983 "on the baseline resource"));
986 /* maybe return bc_url to the caller */
987 if (bc_url)
988 *bc_url = *my_bc_url;
990 if (latest_rev != NULL)
992 const svn_string_t *vsn_name= apr_hash_get(baseline_rsrc->propset,
993 SVN_RA_NEON__PROP_VERSION_NAME,
994 APR_HASH_KEY_STRING);
995 if (vsn_name == NULL)
997 /* ### better error reporting... */
999 /* ### need an SVN_ERR here */
1000 return svn_error_createf(APR_EGENERAL, NULL,
1001 _("'%s' was not present on the baseline "
1002 "resource"),
1003 "DAV:" SVN_DAV__VERSION_NAME);
1005 *latest_rev = SVN_STR_TO_REV(vsn_name->data);
1008 if (is_dir != NULL)
1010 /* query the DAV:resourcetype of the full, assembled URL. */
1011 const char *full_bc_url = svn_path_url_add_component(my_bc_url->data,
1012 my_bc_rel.data,
1013 pool);
1014 SVN_ERR(svn_ra_neon__get_starting_props(&rsrc, sess, full_bc_url,
1015 NULL, pool));
1016 *is_dir = rsrc->is_collection;
1019 if (bc_relative)
1020 *bc_relative = my_bc_rel;
1022 return SVN_NO_ERROR;
1026 /* Helper function for svn_ra_neon__do_proppatch() below. */
1027 static void
1028 append_setprop(svn_stringbuf_t *body,
1029 const char *name,
1030 const svn_string_t *value,
1031 apr_pool_t *pool)
1033 const char *encoding = "";
1034 const char *xml_safe;
1035 const char *xml_tag_name;
1037 /* Map property names to namespaces */
1038 #define NSLEN (sizeof(SVN_PROP_PREFIX) - 1)
1039 if (strncmp(name, SVN_PROP_PREFIX, NSLEN) == 0)
1041 xml_tag_name = apr_pstrcat(pool, "S:", name + NSLEN, NULL);
1043 #undef NSLEN
1044 else
1046 xml_tag_name = apr_pstrcat(pool, "C:", name, NULL);
1049 /* If there is no value, just generate an empty tag and get outta
1050 here. */
1051 if (! value)
1053 svn_stringbuf_appendcstr(body,
1054 apr_psprintf(pool, "<%s />", xml_tag_name));
1055 return;
1058 /* If a property is XML-safe, XML-encode it. Else, base64-encode
1059 it. */
1060 if (svn_xml_is_xml_safe(value->data, value->len))
1062 svn_stringbuf_t *xml_esc = NULL;
1063 svn_xml_escape_cdata_string(&xml_esc, value, pool);
1064 xml_safe = xml_esc->data;
1066 else
1068 const svn_string_t *base64ed = svn_base64_encode_string(value, pool);
1069 encoding = " V:encoding=\"base64\"";
1070 xml_safe = base64ed->data;
1073 svn_stringbuf_appendcstr(body,
1074 apr_psprintf(pool,"<%s %s>%s</%s>",
1075 xml_tag_name, encoding,
1076 xml_safe, xml_tag_name));
1077 return;
1081 svn_error_t *
1082 svn_ra_neon__do_proppatch(svn_ra_neon__session_t *ras,
1083 const char *url,
1084 apr_hash_t *prop_changes,
1085 apr_array_header_t *prop_deletes,
1086 apr_hash_t *extra_headers,
1087 apr_pool_t *pool)
1089 svn_error_t *err;
1090 svn_stringbuf_t *body;
1091 apr_pool_t *subpool = svn_pool_create(pool);
1093 /* just punt if there are no changes to make. */
1094 if ((prop_changes == NULL || (! apr_hash_count(prop_changes)))
1095 && (prop_deletes == NULL || prop_deletes->nelts == 0))
1096 return SVN_NO_ERROR;
1098 /* easier to roll our own PROPPATCH here than use ne_proppatch(), which
1099 * doesn't really do anything clever. */
1100 body = svn_stringbuf_create
1101 ("<?xml version=\"1.0\" encoding=\"utf-8\" ?>" DEBUG_CR
1102 "<D:propertyupdate xmlns:D=\"DAV:\" xmlns:V=\""
1103 SVN_DAV_PROP_NS_DAV "\" xmlns:C=\""
1104 SVN_DAV_PROP_NS_CUSTOM "\" xmlns:S=\""
1105 SVN_DAV_PROP_NS_SVN "\">" DEBUG_CR, pool);
1107 /* Handle property changes. */
1108 if (prop_changes)
1110 apr_hash_index_t *hi;
1111 svn_stringbuf_appendcstr(body, "<D:set><D:prop>");
1112 for (hi = apr_hash_first(pool, prop_changes); hi; hi = apr_hash_next(hi))
1114 const void *key;
1115 void *val;
1116 svn_pool_clear(subpool);
1117 apr_hash_this(hi, &key, NULL, &val);
1118 append_setprop(body, key, val, subpool);
1120 svn_stringbuf_appendcstr(body, "</D:prop></D:set>");
1123 /* Handle property deletions. */
1124 if (prop_deletes)
1126 int n;
1127 svn_stringbuf_appendcstr(body, "<D:remove><D:prop>");
1128 for (n = 0; n < prop_deletes->nelts; n++)
1130 const char *name = APR_ARRAY_IDX(prop_deletes, n, const char *);
1131 svn_pool_clear(subpool);
1132 append_setprop(body, name, NULL, subpool);
1134 svn_stringbuf_appendcstr(body, "</D:prop></D:remove>");
1136 svn_pool_destroy(subpool);
1138 /* Finish up the body. */
1139 svn_stringbuf_appendcstr(body, "</D:propertyupdate>");
1141 /* Finish up the headers. */
1142 if (! extra_headers)
1143 extra_headers = apr_hash_make(pool);
1144 apr_hash_set(extra_headers, "Content-Type", APR_HASH_KEY_STRING,
1145 "text/xml; charset=UTF-8");
1147 err = svn_ra_neon__simple_request(NULL, ras, "PROPPATCH", url,
1148 extra_headers, body->data,
1149 200, 207, pool);
1150 if (err)
1151 return svn_error_create
1152 (SVN_ERR_RA_DAV_PROPPATCH_FAILED, err,
1153 _("At least one property change failed; repository is unchanged"));
1155 return SVN_NO_ERROR;
1160 svn_error_t *
1161 svn_ra_neon__do_check_path(svn_ra_session_t *session,
1162 const char *path,
1163 svn_revnum_t revision,
1164 svn_node_kind_t *kind,
1165 apr_pool_t *pool)
1167 svn_ra_neon__session_t *ras = session->priv;
1168 const char *url = ras->url->data;
1169 svn_error_t *err;
1170 svn_boolean_t is_dir;
1172 /* ### For now, using svn_ra_neon__get_baseline_info() works because
1173 we only have three possibilities: dir, file, or none. When we
1174 add symlinks, we will need to do something different. Here's one
1175 way described by Greg Stein:
1177 That is a PROPFIND (Depth:0) for the DAV:resourcetype property.
1179 You can use the svn_ra_neon__get_one_prop() function to fetch
1180 it. If the PROPFIND fails with a 404, then you have
1181 svn_node_none. If the resulting property looks like:
1183 <D:resourcetype>
1184 <D:collection/>
1185 </D:resourcetype>
1187 Then it is a collection (directory; svn_node_dir). Otherwise,
1188 it is a regular resource (svn_node_file).
1190 The harder part is parsing the resourcetype property. "Proper"
1191 parsing means treating it as an XML property and looking for
1192 the DAV:collection element in there. To do that, however, means
1193 that get_one_prop() can't be used. I think there may be some
1194 Neon functions for parsing XML properties; we'd need to
1195 look. That would probably be the best approach. (an alternative
1196 is to use apr_xml_* parsing functions on the returned string;
1197 get back a DOM-like thing, and look for the element).
1200 /* If we were given a relative path to append, append it. */
1201 if (path)
1202 url = svn_path_url_add_component(url, path, pool);
1204 err = svn_ra_neon__get_baseline_info(&is_dir, NULL, NULL, NULL,
1205 ras, url, revision, pool);
1207 if (err == SVN_NO_ERROR)
1209 if (is_dir)
1210 *kind = svn_node_dir;
1211 else
1212 *kind = svn_node_file;
1214 else if (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
1217 svn_error_clear(err);
1218 *kind = svn_node_none;
1219 return SVN_NO_ERROR;
1222 return err;
1226 svn_error_t *
1227 svn_ra_neon__do_stat(svn_ra_session_t *session,
1228 const char *path,
1229 svn_revnum_t revision,
1230 svn_dirent_t **dirent,
1231 apr_pool_t *pool)
1233 svn_ra_neon__session_t *ras = session->priv;
1234 const char *url = ras->url->data;
1235 const char *final_url;
1236 apr_hash_t *resources;
1237 apr_hash_index_t *hi;
1238 svn_error_t *err;
1240 /* If we were given a relative path to append, append it. */
1241 if (path)
1242 url = svn_path_url_add_component(url, path, pool);
1244 /* Invalid revision means HEAD, which is just the public URL. */
1245 if (! SVN_IS_VALID_REVNUM(revision))
1247 final_url = url;
1249 else
1251 /* Else, convert (rev, path) into an opaque server-generated URL. */
1252 svn_string_t bc_url, bc_relative;
1254 err = svn_ra_neon__get_baseline_info(NULL, &bc_url, &bc_relative,
1255 NULL, ras,
1256 url, revision, pool);
1257 if (err)
1259 if (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
1261 /* easy out: */
1262 svn_error_clear(err);
1263 *dirent = NULL;
1264 return SVN_NO_ERROR;
1266 else
1267 return err;
1270 final_url = svn_path_url_add_component(bc_url.data, bc_relative.data,
1271 pool);
1274 /* Depth-zero PROPFIND is the One True DAV Way. */
1275 err = svn_ra_neon__get_props(&resources, ras, final_url,
1276 SVN_RA_NEON__DEPTH_ZERO,
1277 NULL, NULL /* all props */, pool);
1278 if (err)
1280 if (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
1282 /* easy out: */
1283 svn_error_clear(err);
1284 *dirent = NULL;
1285 return SVN_NO_ERROR;
1287 else
1288 return err;
1291 /* Copying parsing code from svn_ra_neon__get_dir() here. The hash
1292 of resources only contains one item, but there's no other way to
1293 get the item. */
1294 for (hi = apr_hash_first(pool, resources); hi; hi = apr_hash_next(hi))
1296 void *val;
1297 svn_ra_neon__resource_t *resource;
1298 const svn_string_t *propval;
1299 apr_hash_index_t *h;
1300 svn_dirent_t *entry;
1302 apr_hash_this(hi, NULL, NULL, &val);
1303 resource = val;
1305 entry = apr_pcalloc(pool, sizeof(*entry));
1307 entry->kind = resource->is_collection ? svn_node_dir : svn_node_file;
1309 /* entry->size is already 0 by virtue of pcalloc(). */
1310 if (entry->kind == svn_node_file)
1312 propval = apr_hash_get(resource->propset,
1313 SVN_RA_NEON__PROP_GETCONTENTLENGTH,
1314 APR_HASH_KEY_STRING);
1315 if (propval)
1316 entry->size = svn__atoui64(propval->data);
1319 /* does this resource contain any 'dead' properties? */
1320 for (h = apr_hash_first(pool, resource->propset);
1321 h; h = apr_hash_next(h))
1323 const void *kkey;
1324 apr_hash_this(h, &kkey, NULL, NULL);
1326 if (strncmp((const char *)kkey, SVN_DAV_PROP_NS_CUSTOM,
1327 sizeof(SVN_DAV_PROP_NS_CUSTOM) - 1) == 0)
1328 entry->has_props = TRUE;
1330 else if (strncmp((const char *)kkey, SVN_DAV_PROP_NS_SVN,
1331 sizeof(SVN_DAV_PROP_NS_SVN) - 1) == 0)
1332 entry->has_props = TRUE;
1335 /* created_rev & friends */
1336 propval = apr_hash_get(resource->propset,
1337 SVN_RA_NEON__PROP_VERSION_NAME,
1338 APR_HASH_KEY_STRING);
1339 if (propval != NULL)
1340 entry->created_rev = SVN_STR_TO_REV(propval->data);
1342 propval = apr_hash_get(resource->propset,
1343 SVN_RA_NEON__PROP_CREATIONDATE,
1344 APR_HASH_KEY_STRING);
1345 if (propval != NULL)
1346 SVN_ERR(svn_time_from_cstring(&(entry->time),
1347 propval->data, pool));
1349 propval = apr_hash_get(resource->propset,
1350 SVN_RA_NEON__PROP_CREATOR_DISPLAYNAME,
1351 APR_HASH_KEY_STRING);
1352 if (propval != NULL)
1353 entry->last_author = propval->data;
1355 *dirent = entry;
1358 return SVN_NO_ERROR;