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
27 #include "svn_error.h"
30 #include "svn_base64.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"
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
= {
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"},
63 /* when speaking to a Baseline to reach the Baseline Collection, fetch these
65 static const ne_propname baseline_props
[] =
67 { "DAV:", "baseline-collection" },
68 { "DAV:", SVN_DAV__VERSION_NAME
},
74 /*** Propfind Implementation ***/
77 svn_ra_neon__xml_elmid id
;
79 int is_property
; /* is it a property, or part of some structure? */
83 static const elem_defn elem_definitions
[] =
85 /*** NOTE: Make sure that every item in here is also represented in
86 propfind_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 },
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 },
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[] ***/
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
},
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
},
151 { "", "", ELEM_unknown
, SVN_RA_NEON__XML_COLLECT
},
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. */
177 /* Look up an element definition ID. May return NULL if the elem is
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
)
193 /* Assign URL to RSRC. Use POOL for any allocations. */
195 assign_rsrc_url(svn_ra_neon__resource_t
*rsrc
,
196 const char *url
, apr_pool_t
*pool
)
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
;
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
)
236 if (child
== ELEM_multistatus
)
239 return SVN_RA_NEON__XML_INVALID
;
241 case ELEM_multistatus
:
242 if (child
== ELEM_response
)
245 return SVN_RA_NEON__XML_DECLINE
;
248 if ((child
== ELEM_href
) || (child
== ELEM_propstat
))
251 return SVN_RA_NEON__XML_DECLINE
;
254 if ((child
== ELEM_prop
) || (child
== ELEM_status
))
257 return SVN_RA_NEON__XML_DECLINE
;
260 return child
; /* handle all children of <prop> */
262 case ELEM_baseline_coll
:
263 case ELEM_checked_in
:
265 if (child
== ELEM_href
)
268 return SVN_RA_NEON__XML_DECLINE
; /* not concerned with other types */
270 case ELEM_resourcetype
:
271 if ((child
== ELEM_collection
) || (child
== ELEM_baseline
))
274 return SVN_RA_NEON__XML_DECLINE
; /* not concerned with other types
278 return SVN_RA_NEON__XML_DECLINE
;
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 */
298 svn_stringbuf_setempty(pc
->cdata
);
299 *elem
= elm
? elm
->id
: ELEM_unknown
;
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
);
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
;
324 case ELEM_collection
:
325 pc
->rsrc
->is_collection
= 1;
329 /* these are our user-visible properties, presumably. */
330 pc
->encoding
= ne_xml_get_attr(pc
->parser
, atts
, SVN_DAV_PROP_NS_DAV
,
333 pc
->encoding
= apr_pstrdup(pc
->pool
, pc
->encoding
);
337 /* nothing to do for these */
341 /* Remember the last tag we opened. */
342 pc
->last_open_id
= *elem
;
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
;
356 const char *cdata
= pc
->cdata
->data
;
361 /* Verify that we've received a URL for this resource. */
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
);
371 /* We're at the end of a set of properties. Do the right thing
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
380 apr_hash_index_t
*hi
= apr_hash_first(pc
->pool
, pc
->propbuffer
);
381 for (; hi
; hi
= apr_hash_next(hi
))
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
);
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
;
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. */
419 /* All other href's we'll treat as property values. */
420 name
= parent_defn
->name
;
421 value
= svn_string_create(cdata
, pc
->pool
);
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
);
440 defn
= defn_from_id(state
);
441 if (! (defn
&& defn
->is_property
))
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
);
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. */
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
);
479 static void set_parser(ne_xml_parser
*parser
,
482 propfind_ctx_t
*pc
= baton
;
487 svn_error_t
* svn_ra_neon__get_props(apr_hash_t
**results
,
488 svn_ra_neon__session_t
*sess
,
492 const ne_propname
*which_props
,
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. */
503 apr_hash_set(extra_headers
, "Label", 5, label
);
505 /* It's easier to roll our own PROPFIND here than use neon's current
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? */
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
);
531 svn_stringbuf_appendcstr(body
, "<allprop/></propfind>" DEBUG_CR
);
534 /* Initialize our baton. */
535 memset(&pc
, 0, sizeof(pc
));
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
,
546 svn_ra_neon__xml_collect_cdata
,
548 &pc
, extra_headers
, NULL
, FALSE
, pool
));
554 svn_error_t
* svn_ra_neon__get_props_resource(svn_ra_neon__resource_t
**rsrc
,
555 svn_ra_neon__session_t
*sess
,
558 const ne_propname
*which_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
);
583 apr_hash_this(hi
, NULL
, NULL
, &ent
);
591 *rsrc
= apr_hash_get(props
, url_path
, APR_HASH_KEY_STRING
);
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
);
605 svn_error_t
* svn_ra_neon__get_one_prop(const svn_string_t
**propval
,
606 svn_ra_neon__session_t
*sess
,
609 const ne_propname
*propname
,
612 svn_ra_neon__resource_t
*rsrc
;
613 ne_propname props
[2] = { { 0 } };
615 const svn_string_t
*value
;
617 props
[0] = *propname
;
618 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc
, sess
, url
, label
, props
,
621 name
= apr_pstrcat(pool
, propname
->nspace
, propname
->name
, NULL
);
622 value
= apr_hash_get(rsrc
->propset
, name
, APR_HASH_KEY_STRING
);
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"),
635 svn_error_t
* svn_ra_neon__get_starting_props(svn_ra_neon__resource_t
**rsrc
,
636 svn_ra_neon__session_t
*sess
,
641 return svn_ra_neon__get_props_resource(rsrc
, sess
, url
, label
, starting_props
,
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
,
654 svn_error_t
*err
= SVN_NO_ERROR
;
656 svn_stringbuf_t
*path_s
;
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
,
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
));
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
;
722 tmp
->propset
= apr_hash_make(pool
);
724 for (hi
= apr_hash_first(iterpool
, (*rsrc
)->propset
);
725 hi
; hi
= apr_hash_next(hi
))
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
));
737 *missing_path
= lopped_path
->data
;
738 svn_pool_destroy(iterpool
);
743 svn_error_t
*svn_ra_neon__get_vcc(const char **vcc
,
744 svn_ra_neon__session_t
*sess
,
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
,
760 vcc_s
= apr_hash_get(rsrc
->propset
,
761 SVN_RA_NEON__PROP_VCC
, APR_HASH_KEY_STRING
);
763 return svn_error_create(APR_EGENERAL
, NULL
,
764 _("The VCC property was not found on the "
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
,
776 svn_revnum_t revision
,
777 const ne_propname
*which_props
,
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 /* -------------------------------------------------------------------
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
809 SVN_ERR(svn_ra_neon__search_for_starting_props(&rsrc
, &lopped_path
,
812 vcc
= apr_hash_get(rsrc
->propset
, SVN_RA_NEON__PROP_VCC
, APR_HASH_KEY_STRING
);
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 "
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
),
843 /* if they want the relative path (could be, they're just trying to find
844 the baseline collection), then return it */
847 bc_relative
->data
= my_bc_relative
;
848 bc_relative
->len
= strlen(my_bc_relative
);
851 /* -------------------------------------------------------------------
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
,
895 /* Fetch a specific revision */
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
,
909 /* Return the baseline rsrc, which now contains whatever set of
910 props the caller wanted. */
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
,
922 svn_revnum_t revision
,
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
932 SVN_ERR(svn_ra_neon__get_baseline_props(&my_bc_rel
,
937 baseline_props
, /* specific props */
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 */
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 "
974 "DAV:" SVN_DAV__VERSION_NAME
);
976 *latest_rev
= SVN_STR_TO_REV(vsn_name
->data
);
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
,
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
;
991 *bc_relative
= my_bc_rel
;
997 /* Helper function for svn_ra_neon__do_proppatch() below. */
999 append_setprop(svn_stringbuf_t
*body
,
1001 const svn_string_t
*value
,
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
);
1017 xml_tag_name
= apr_pstrcat(pool
, "C:", name
, NULL
);
1020 /* If there is no value, just generate an empty tag and get outta
1024 svn_stringbuf_appendcstr(body
,
1025 apr_psprintf(pool
, "<%s />", xml_tag_name
));
1029 /* If a property is XML-safe, XML-encode it. Else, base64-encode
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
;
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
));
1053 svn_ra_neon__do_proppatch(svn_ra_neon__session_t
*ras
,
1055 apr_hash_t
*prop_changes
,
1056 apr_array_header_t
*prop_deletes
,
1057 apr_hash_t
*extra_headers
,
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. */
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
))
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. */
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
,
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
;
1132 svn_ra_neon__do_check_path(svn_ra_session_t
*session
,
1134 svn_revnum_t revision
,
1135 svn_node_kind_t
*kind
,
1138 svn_ra_neon__session_t
*ras
= session
->priv
;
1139 const char *url
= ras
->url
->data
;
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:
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. */
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
)
1181 *kind
= svn_node_dir
;
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
;
1198 svn_ra_neon__do_stat(svn_ra_session_t
*session
,
1200 svn_revnum_t revision
,
1201 svn_dirent_t
**dirent
,
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
;
1211 /* If we were given a relative path to append, append it. */
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
))
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
,
1227 url
, revision
, pool
);
1230 if (err
->apr_err
== SVN_ERR_RA_DAV_PATH_NOT_FOUND
)
1233 svn_error_clear(err
);
1235 return SVN_NO_ERROR
;
1241 final_url
= svn_path_url_add_component(bc_url
.data
, bc_relative
.data
,
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
);
1251 if (err
->apr_err
== SVN_ERR_RA_DAV_PATH_NOT_FOUND
)
1254 svn_error_clear(err
);
1256 return SVN_NO_ERROR
;
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
1265 for (hi
= apr_hash_first(pool
, resources
); hi
; hi
= apr_hash_next(hi
))
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
);
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
);
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
))
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
;
1329 return SVN_NO_ERROR
;