2 * property.c : property routines for ra_serf
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
30 #include "svn_base64.h"
32 #include "svn_props.h"
33 #include "svn_dirent_uri.h"
35 #include "private/svn_dav_protocol.h"
36 #include "private/svn_fspath.h"
37 #include "private/svn_string_private.h"
38 #include "svn_private_config.h"
43 /* Our current parsing state we're in for the PROPFIND response. */
44 typedef enum prop_state_e {
59 * This structure represents a pending PROPFIND response.
61 typedef struct propfind_context_t {
62 /* pool to issue allocations from */
65 svn_ra_serf__handler_t *handler;
67 /* associated serf session */
68 svn_ra_serf__session_t *sess;
69 svn_ra_serf__connection_t *conn;
71 /* the requested path */
74 /* the requested version (number and string form) */
78 /* the request depth */
81 /* the list of requested properties */
82 const svn_ra_serf__dav_props_t *find_props;
84 /* hash table that will be updated with the properties
86 * This can be shared between multiple propfind_context_t
89 apr_hash_t *ret_props;
91 /* hash table containing all the properties associated with the
92 * "current" <propstat> tag. These will get copied into RET_PROPS
93 * if the status code similarly associated indicates that they are
94 * "good"; otherwise, they'll get discarded.
98 /* If not-NULL, add us to this list when we're done. */
99 svn_ra_serf__list_t **done_list;
101 svn_ra_serf__list_t done_item;
103 } propfind_context_t;
107 #define S_ SVN_XML_NAMESPACE
108 static const svn_ra_serf__xml_transition_t propfind_ttable[] = {
109 { INITIAL, D_, "multistatus", MULTISTATUS,
110 FALSE, { NULL }, TRUE },
112 { MULTISTATUS, D_, "response", RESPONSE,
113 FALSE, { NULL }, FALSE },
115 { RESPONSE, D_, "href", HREF,
116 TRUE, { NULL }, TRUE },
118 { RESPONSE, D_, "propstat", PROPSTAT,
119 FALSE, { NULL }, TRUE },
121 { PROPSTAT, D_, "status", STATUS,
122 TRUE, { NULL }, TRUE },
124 { PROPSTAT, D_, "prop", PROP,
125 FALSE, { NULL }, FALSE },
127 { PROP, "*", "*", PROPVAL,
128 TRUE, { "?V:encoding", NULL }, TRUE },
130 { PROPVAL, D_, "collection", COLLECTION,
131 FALSE, { NULL }, TRUE },
133 { PROPVAL, D_, "href", HREF_VALUE,
134 TRUE, { NULL }, TRUE },
140 /* Return the HTTP status code contained in STATUS_LINE, or 0 if
141 there's a problem parsing it. */
142 static int parse_status_code(const char *status_line)
144 /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */
145 if (status_line[0] == 'H' &&
146 status_line[1] == 'T' &&
147 status_line[2] == 'T' &&
148 status_line[3] == 'P' &&
149 status_line[4] == '/' &&
150 (status_line[5] >= '0' && status_line[5] <= '9') &&
151 status_line[6] == '.' &&
152 (status_line[7] >= '0' && status_line[7] <= '9') &&
153 status_line[8] == ' ')
157 return apr_strtoi64(status_line + 8, &reason, 10);
163 /* Conforms to svn_ra_serf__path_rev_walker_t */
165 copy_into_ret_props(void *baton,
166 const char *path, apr_ssize_t path_len,
167 const char *ns, apr_ssize_t ns_len,
168 const char *name, apr_ssize_t name_len,
169 const svn_string_t *val,
172 propfind_context_t *ctx = baton;
174 svn_ra_serf__set_ver_prop(ctx->ret_props, path, ctx->rev, ns, name,
180 /* Conforms to svn_ra_serf__xml_opened_t */
182 propfind_opened(svn_ra_serf__xml_estate_t *xes,
185 const svn_ra_serf__dav_props_t *tag,
186 apr_pool_t *scratch_pool)
188 propfind_context_t *ctx = baton;
190 if (entered_state == PROPVAL)
192 svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->namespace);
193 svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name);
195 else if (entered_state == PROPSTAT)
197 ctx->ps_props = apr_hash_make(ctx->pool);
204 /* Conforms to svn_ra_serf__xml_closed_t */
206 propfind_closed(svn_ra_serf__xml_estate_t *xes,
209 const svn_string_t *cdata,
211 apr_pool_t *scratch_pool)
213 propfind_context_t *ctx = baton;
215 if (leaving_state == MULTISTATUS)
217 /* We've gathered all the data from the reponse. Add this item
218 onto the "done list". External callers will then know this
219 request has been completed (tho stray response bytes may still
223 ctx->done_item.data = ctx->handler;
224 ctx->done_item.next = *ctx->done_list;
225 *ctx->done_list = &ctx->done_item;
228 else if (leaving_state == HREF)
231 const svn_string_t *val_str;
233 if (strcmp(ctx->depth, "1") == 0)
234 path = svn_urlpath__canonicalize(cdata->data, scratch_pool);
238 svn_ra_serf__xml_note(xes, RESPONSE, "path", path);
240 /* Copy the value into the right pool, then save the HREF. */
241 val_str = svn_string_dup(cdata, ctx->pool);
242 svn_ra_serf__set_ver_prop(ctx->ret_props,
243 path, ctx->rev, D_, "href", val_str,
246 else if (leaving_state == COLLECTION)
248 svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection");
250 else if (leaving_state == HREF_VALUE)
252 svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data);
254 else if (leaving_state == STATUS)
256 /* Parse the status field, and remember if this is a property
257 that we wish to ignore. (Typically, if it's not a 200, the
258 status will be 404 to indicate that a property we
259 specifically requested from the server doesn't exist.) */
260 int status = parse_status_code(cdata->data);
262 svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*");
264 else if (leaving_state == PROPVAL)
266 const char *encoding = svn_hash_gets(attrs, "V:encoding");
267 const svn_string_t *val_str;
268 apr_hash_t *gathered;
272 const char *altvalue;
276 if (strcmp(encoding, "base64") != 0)
277 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
279 _("Got unrecognized encoding '%s'"),
282 /* Decode into the right pool. */
283 val_str = svn_base64_decode_string(cdata, ctx->pool);
287 /* Copy into the right pool. */
288 val_str = svn_string_dup(cdata, ctx->pool);
291 /* The current path sits on the RESPONSE state. Gather up all the
292 state from this PROPVAL to the (grandparent) RESPONSE state,
293 and grab the path from there.
295 Now, it would be nice if we could, at this point, know that
296 the status code for this property indicated a problem -- then
297 we could simply bail out here and ignore the property.
298 Sadly, though, we might get the status code *after* we get
299 the property value. So we'll carry on with our processing
300 here, setting the property and value as expected. Once we
301 know for sure the status code associate with the property,
302 we'll decide its fate. */
303 gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE);
305 /* These will be dup'd into CTX->POOL, as necessary. */
306 path = svn_hash_gets(gathered, "path");
310 ns = svn_hash_gets(attrs, "ns");
311 name = apr_pstrdup(ctx->pool,
312 svn_hash_gets(attrs, "name"));
314 altvalue = svn_hash_gets(attrs, "altvalue");
315 if (altvalue != NULL)
316 val_str = svn_string_create(altvalue, ctx->pool);
318 svn_ra_serf__set_ver_prop(ctx->ps_props,
319 path, ctx->rev, ns, name, val_str,
324 apr_hash_t *gathered;
326 SVN_ERR_ASSERT(leaving_state == PROPSTAT);
328 gathered = svn_ra_serf__xml_gather_since(xes, PROPSTAT);
330 /* If we've squirreled away a note that says we want to ignore
331 these properties, we'll do so. Otherwise, we need to copy
332 them from the temporary hash into the ctx->ret_props hash. */
333 if (! svn_hash_gets(gathered, "ignore-prop"))
335 SVN_ERR(svn_ra_serf__walk_all_paths(ctx->ps_props, ctx->rev,
336 copy_into_ret_props, ctx,
340 ctx->ps_props = NULL;
348 svn_ra_serf__get_ver_prop_string(apr_hash_t *props,
354 apr_hash_t *ver_props, *path_props, *ns_props;
357 ver_props = apr_hash_get(props, &rev, sizeof(rev));
360 path_props = svn_hash_gets(ver_props, path);
364 ns_props = svn_hash_gets(path_props, ns);
367 val = svn_hash_gets(ns_props, name);
376 svn_ra_serf__get_ver_prop(apr_hash_t *props,
382 const svn_string_t *val;
384 val = svn_ra_serf__get_ver_prop_string(props, path, rev, ns, name);
395 svn_ra_serf__get_prop_string(apr_hash_t *props,
400 return svn_ra_serf__get_ver_prop_string(props, path, SVN_INVALID_REVNUM,
405 svn_ra_serf__get_prop(apr_hash_t *props,
410 return svn_ra_serf__get_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name);
414 svn_ra_serf__set_ver_prop(apr_hash_t *props,
415 const char *path, svn_revnum_t rev,
416 const char *ns, const char *name,
417 const svn_string_t *val, apr_pool_t *pool)
419 apr_hash_t *ver_props, *path_props, *ns_props;
421 ver_props = apr_hash_get(props, &rev, sizeof(rev));
424 ver_props = apr_hash_make(pool);
425 apr_hash_set(props, apr_pmemdup(pool, &rev, sizeof(rev)), sizeof(rev),
429 path_props = svn_hash_gets(ver_props, path);
433 path_props = apr_hash_make(pool);
434 path = apr_pstrdup(pool, path);
435 svn_hash_sets(ver_props, path, path_props);
437 /* todo: we know that we'll fail the next check, but fall through
438 * for now for simplicity's sake.
442 ns_props = svn_hash_gets(path_props, ns);
445 ns_props = apr_hash_make(pool);
446 ns = apr_pstrdup(pool, ns);
447 svn_hash_sets(path_props, ns, ns_props);
450 svn_hash_sets(ns_props, name, val);
454 svn_ra_serf__set_prop(apr_hash_t *props,
456 const char *ns, const char *name,
457 const svn_string_t *val, apr_pool_t *pool)
459 svn_ra_serf__set_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name,
465 setup_propfind_headers(serf_bucket_t *headers,
469 propfind_context_t *ctx = setup_baton;
471 serf_bucket_headers_setn(headers, "Depth", ctx->depth);
474 serf_bucket_headers_setn(headers, "Label", ctx->label);
480 #define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">"
481 #define PROPFIND_TRAILER "</propfind>"
484 create_propfind_body(serf_bucket_t **bkt,
486 serf_bucket_alloc_t *alloc,
489 propfind_context_t *ctx = setup_baton;
491 serf_bucket_t *body_bkt, *tmp;
492 const svn_ra_serf__dav_props_t *prop;
493 svn_boolean_t requested_allprop = FALSE;
495 body_bkt = serf_bucket_aggregate_create(alloc);
497 prop = ctx->find_props;
498 while (prop && prop->namespace)
500 /* special case the allprop case. */
501 if (strcmp(prop->name, "allprop") == 0)
503 requested_allprop = TRUE;
506 /* <*propname* xmlns="*propns*" /> */
507 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
508 serf_bucket_aggregate_append(body_bkt, tmp);
510 tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
511 serf_bucket_aggregate_append(body_bkt, tmp);
513 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
514 sizeof(" xmlns=\"")-1,
516 serf_bucket_aggregate_append(body_bkt, tmp);
518 tmp = SERF_BUCKET_SIMPLE_STRING(prop->namespace, alloc);
519 serf_bucket_aggregate_append(body_bkt, tmp);
521 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
523 serf_bucket_aggregate_append(body_bkt, tmp);
528 /* If we're not doing an allprop, add <prop> tags. */
529 if (!requested_allprop)
531 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
534 serf_bucket_aggregate_prepend(body_bkt, tmp);
537 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
538 sizeof(PROPFIND_HEADER)-1,
541 serf_bucket_aggregate_prepend(body_bkt, tmp);
543 if (!requested_allprop)
545 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
548 serf_bucket_aggregate_append(body_bkt, tmp);
551 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
552 sizeof(PROPFIND_TRAILER)-1,
554 serf_bucket_aggregate_append(body_bkt, tmp);
562 svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler,
563 apr_hash_t *ret_props,
564 svn_ra_serf__session_t *sess,
565 svn_ra_serf__connection_t *conn,
569 const svn_ra_serf__dav_props_t *find_props,
570 svn_ra_serf__list_t **done_list,
573 propfind_context_t *new_prop_ctx;
574 svn_ra_serf__handler_t *handler;
575 svn_ra_serf__xml_context_t *xmlctx;
577 new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
579 new_prop_ctx->pool = apr_hash_pool_get(ret_props);
580 new_prop_ctx->path = path;
581 new_prop_ctx->find_props = find_props;
582 new_prop_ctx->ret_props = ret_props;
583 new_prop_ctx->depth = depth;
584 new_prop_ctx->sess = sess;
585 new_prop_ctx->conn = conn;
586 new_prop_ctx->rev = rev;
587 new_prop_ctx->done_list = done_list;
589 if (SVN_IS_VALID_REVNUM(rev))
591 new_prop_ctx->label = apr_ltoa(pool, rev);
595 new_prop_ctx->label = NULL;
598 xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
604 handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
606 handler->method = "PROPFIND";
607 handler->path = path;
608 handler->body_delegate = create_propfind_body;
609 handler->body_type = "text/xml";
610 handler->body_delegate_baton = new_prop_ctx;
611 handler->header_delegate = setup_propfind_headers;
612 handler->header_delegate_baton = new_prop_ctx;
614 handler->session = new_prop_ctx->sess;
615 handler->conn = new_prop_ctx->conn;
617 new_prop_ctx->handler = handler;
619 *propfind_handler = handler;
626 * This helper function will block until the PROP_CTX indicates that is done
627 * or another error is returned.
630 svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler,
631 apr_pool_t *scratch_pool)
636 err = svn_ra_serf__context_run_one(handler, scratch_pool);
638 err2 = svn_ra_serf__error_on_status(handler->sline,
642 return svn_error_compose_create(err2, err);
646 * This is a blocking version of deliver_props.
649 svn_ra_serf__retrieve_props(apr_hash_t **results,
650 svn_ra_serf__session_t *sess,
651 svn_ra_serf__connection_t *conn,
655 const svn_ra_serf__dav_props_t *props,
656 apr_pool_t *result_pool,
657 apr_pool_t *scratch_pool)
659 svn_ra_serf__handler_t *handler;
661 *results = apr_hash_make(result_pool);
663 SVN_ERR(svn_ra_serf__deliver_props(&handler, *results, sess, conn, url,
664 rev, depth, props, NULL, result_pool));
665 SVN_ERR(svn_ra_serf__wait_for_props(handler, scratch_pool));
672 svn_ra_serf__fetch_node_props(apr_hash_t **results,
673 svn_ra_serf__connection_t *conn,
675 svn_revnum_t revision,
676 const svn_ra_serf__dav_props_t *which_props,
677 apr_pool_t *result_pool,
678 apr_pool_t *scratch_pool)
680 apr_hash_t *multiprops;
681 apr_hash_t *ver_props;
683 /* Note: a couple extra hash tables and whatnot get into RESULT_POOL.
684 Not a big deal at this point. Theoretically, we could fetch all
685 props into SCRATCH_POOL, then copy just the REVISION/URL props
686 into RESULT_POOL. Too much work for too little gain... */
687 SVN_ERR(svn_ra_serf__retrieve_props(&multiprops, conn->session, conn,
688 url, revision, "0", which_props,
689 result_pool, scratch_pool));
691 ver_props = apr_hash_get(multiprops, &revision, sizeof(revision));
692 if (ver_props != NULL)
694 *results = svn_hash_gets(ver_props, url);
695 if (*results != NULL)
699 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
700 _("The PROPFIND response did not include "
701 "the requested properties"));
706 svn_ra_serf__walk_node_props(apr_hash_t *props,
707 svn_ra_serf__walker_visitor_t walker,
709 apr_pool_t *scratch_pool)
711 apr_pool_t *iterpool;
712 apr_hash_index_t *ns_hi;
714 iterpool = svn_pool_create(scratch_pool);
715 for (ns_hi = apr_hash_first(scratch_pool, props); ns_hi;
716 ns_hi = apr_hash_next(ns_hi))
720 apr_hash_index_t *name_hi;
722 /* NOTE: We do not clear ITERPOOL in this loop. Generally, there are
723 very few namespaces, so this loop will not have many iterations.
724 Instead, ITERPOOL is used for the inner loop. */
726 apr_hash_this(ns_hi, &ns_name, NULL, &ns_val);
728 for (name_hi = apr_hash_first(scratch_pool, ns_val); name_hi;
729 name_hi = apr_hash_next(name_hi))
732 const void *prop_name;
734 /* See note above, regarding clearing of this pool. */
735 svn_pool_clear(iterpool);
737 apr_hash_this(name_hi, &prop_name, NULL, &prop_val);
739 SVN_ERR(walker(baton, ns_name, prop_name, prop_val, iterpool));
742 svn_pool_destroy(iterpool);
749 svn_ra_serf__walk_all_props(apr_hash_t *props,
752 svn_ra_serf__walker_visitor_t walker,
754 apr_pool_t *scratch_pool)
756 apr_hash_t *ver_props;
757 apr_hash_t *path_props;
759 ver_props = apr_hash_get(props, &rev, sizeof(rev));
763 path_props = svn_hash_gets(ver_props, name);
767 return svn_error_trace(svn_ra_serf__walk_node_props(path_props,
774 svn_ra_serf__walk_all_paths(apr_hash_t *props,
776 svn_ra_serf__path_rev_walker_t walker,
780 apr_hash_index_t *path_hi;
781 apr_hash_t *ver_props;
783 ver_props = apr_hash_get(props, &rev, sizeof(rev));
790 for (path_hi = apr_hash_first(pool, ver_props); path_hi;
791 path_hi = apr_hash_next(path_hi))
794 const void *path_name;
795 apr_ssize_t path_len;
796 apr_hash_index_t *ns_hi;
798 apr_hash_this(path_hi, &path_name, &path_len, &path_props);
799 for (ns_hi = apr_hash_first(pool, path_props); ns_hi;
800 ns_hi = apr_hash_next(ns_hi))
805 apr_hash_index_t *name_hi;
806 apr_hash_this(ns_hi, &ns_name, &ns_len, &ns_val);
807 for (name_hi = apr_hash_first(pool, ns_val); name_hi;
808 name_hi = apr_hash_next(name_hi))
811 const void *prop_name;
812 apr_ssize_t prop_len;
814 apr_hash_this(name_hi, &prop_name, &prop_len, &prop_val);
816 SVN_ERR(walker(baton, path_name, path_len, ns_name, ns_len,
817 prop_name, prop_len, prop_val, pool));
827 svn_ra_serf__svnname_from_wirename(const char *ns,
829 apr_pool_t *result_pool)
831 if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
832 return apr_pstrdup(result_pool, name);
834 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
835 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
837 if (strcmp(ns, SVN_PROP_PREFIX) == 0)
838 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
840 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
841 return SVN_PROP_ENTRY_COMMITTED_REV;
843 if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
844 return SVN_PROP_ENTRY_COMMITTED_DATE;
846 if (strcmp(name, "creator-displayname") == 0)
847 return SVN_PROP_ENTRY_LAST_AUTHOR;
849 if (strcmp(name, "repository-uuid") == 0)
850 return SVN_PROP_ENTRY_UUID;
852 if (strcmp(name, "lock-token") == 0)
853 return SVN_PROP_ENTRY_LOCK_TOKEN;
855 if (strcmp(name, "checked-in") == 0)
856 return SVN_RA_SERF__WC_CHECKED_IN_URL;
858 if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
860 /* Here DAV: properties not yet converted to svn: properties should be
865 /* An unknown namespace, must be a custom property. */
866 return apr_pstrcat(result_pool, ns, name, (char *)NULL);
870 /* Conforms to svn_ra_serf__walker_visitor_t */
872 set_flat_props(void *baton,
875 const svn_string_t *value,
878 apr_hash_t *props = baton;
879 apr_pool_t *result_pool = apr_hash_pool_get(props);
880 const char *prop_name;
882 /* ### is VAL in the proper pool? */
884 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
885 if (prop_name != NULL)
886 svn_hash_sets(props, prop_name, value);
893 svn_ra_serf__flatten_props(apr_hash_t **flat_props,
895 apr_pool_t *result_pool,
896 apr_pool_t *scratch_pool)
898 *flat_props = apr_hash_make(result_pool);
900 return svn_error_trace(svn_ra_serf__walk_node_props(
903 *flat_props /* baton */,
909 select_revprops(void *baton,
912 const svn_string_t *val,
913 apr_pool_t *scratch_pool)
915 apr_hash_t *revprops = baton;
916 apr_pool_t *result_pool = apr_hash_pool_get(revprops);
917 const char *prop_name;
919 /* ### copy NAME into the RESULT_POOL? */
920 /* ### copy VAL into the RESULT_POOL? */
922 if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
924 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
925 prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
926 else if (strcmp(ns, SVN_PROP_PREFIX) == 0)
927 prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
928 else if (strcmp(ns, "") == 0)
932 /* do nothing for now? */
936 svn_hash_sets(revprops, prop_name, val);
943 svn_ra_serf__select_revprops(apr_hash_t **revprops,
946 apr_hash_t *all_revprops,
947 apr_pool_t *result_pool,
948 apr_pool_t *scratch_pool)
950 *revprops = apr_hash_make(result_pool);
952 return svn_error_trace(svn_ra_serf__walk_all_props(
953 all_revprops, name, rev,
954 select_revprops, *revprops,
960 * Contact the server (using CONN) to calculate baseline
961 * information for BASELINE_URL at REVISION (which may be
962 * SVN_INVALID_REVNUM to query the HEAD revision).
964 * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
965 * retrieved from the server as part of this process (which should
966 * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the
967 * baseline collection URL.
970 retrieve_baseline_info(svn_revnum_t *actual_revision,
971 const char **basecoll_url_p,
972 svn_ra_serf__connection_t *conn,
973 const char *baseline_url,
974 svn_revnum_t revision,
975 apr_pool_t *result_pool,
976 apr_pool_t *scratch_pool)
979 apr_hash_t *dav_props;
980 const char *basecoll_url;
982 SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn,
983 baseline_url, revision,
985 scratch_pool, scratch_pool));
986 dav_props = apr_hash_get(props, "DAV:", 4);
987 /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */
989 basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
992 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
993 _("The PROPFIND response did not include "
994 "the requested baseline-collection value"));
996 *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
1000 const char *version_name;
1002 version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
1004 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
1005 _("The PROPFIND response did not include "
1006 "the requested version-name value"));
1008 *actual_revision = SVN_STR_TO_REV(version_name);
1011 return SVN_NO_ERROR;
1015 /* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
1016 revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
1017 collection URL is also returned.
1019 Do the work over CONN.
1021 *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
1022 temporary allocations will be made in SCRATCH_POOL. */
1023 static svn_error_t *
1024 v1_get_youngest_revnum(svn_revnum_t *youngest,
1025 const char **basecoll_url,
1026 svn_ra_serf__connection_t *conn,
1027 const char *vcc_url,
1028 apr_pool_t *result_pool,
1029 apr_pool_t *scratch_pool)
1031 const char *baseline_url;
1034 /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
1035 revision) will return the latest Baseline resource's URL. */
1036 SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, conn, vcc_url,
1039 scratch_pool, scratch_pool));
1042 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1043 _("The OPTIONS response did not include "
1044 "the requested checked-in value"));
1046 baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
1048 /* From the Baseline resource, we can fetch the DAV:baseline-collection
1049 and DAV:version-name properties. The latter is the revision number,
1050 which is formally the name used in Label: headers. */
1052 /* First check baseline information cache. */
1053 SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
1055 conn->session->blncache,
1060 SVN_ERR(retrieve_baseline_info(youngest, &bc_url, conn,
1061 baseline_url, SVN_INVALID_REVNUM,
1062 scratch_pool, scratch_pool));
1063 SVN_ERR(svn_ra_serf__blncache_set(conn->session->blncache,
1064 baseline_url, *youngest,
1065 bc_url, scratch_pool));
1068 if (basecoll_url != NULL)
1069 *basecoll_url = apr_pstrdup(result_pool, bc_url);
1071 return SVN_NO_ERROR;
1076 svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
1077 svn_ra_serf__session_t *session,
1078 apr_pool_t *scratch_pool)
1080 const char *vcc_url;
1082 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
1083 return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
1084 youngest, session->conns[0], scratch_pool));
1086 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, scratch_pool));
1088 return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
1089 session->conns[0], vcc_url,
1090 scratch_pool, scratch_pool));
1094 /* Set *BC_URL to the baseline collection url for REVISION. If REVISION
1095 is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
1097 *REVNUM_USED will be set to the revision used.
1099 Uses the specified CONN, which is part of SESSION.
1101 All allocations (results and temporary) are performed in POOL. */
1102 static svn_error_t *
1103 get_baseline_info(const char **bc_url,
1104 svn_revnum_t *revnum_used,
1105 svn_ra_serf__session_t *session,
1106 svn_ra_serf__connection_t *conn,
1107 svn_revnum_t revision,
1110 /* If we detected HTTP v2 support on the server, we can construct
1111 the baseline collection URL ourselves, and fetch the latest
1112 revision (if needed) with an OPTIONS request. */
1113 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
1115 if (SVN_IS_VALID_REVNUM(revision))
1117 *revnum_used = revision;
1121 SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
1122 revnum_used, conn, pool));
1123 if (! SVN_IS_VALID_REVNUM(*revnum_used))
1124 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1125 _("The OPTIONS response did not include "
1126 "the youngest revision"));
1129 *bc_url = apr_psprintf(pool, "%s/%ld",
1130 session->rev_root_stub, *revnum_used);
1133 /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */
1136 const char *vcc_url;
1138 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, conn, pool));
1140 if (SVN_IS_VALID_REVNUM(revision))
1142 /* First check baseline information cache. */
1143 SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
1148 SVN_ERR(retrieve_baseline_info(NULL, bc_url, conn,
1149 vcc_url, revision, pool, pool));
1150 SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
1151 revision, *bc_url, pool));
1154 *revnum_used = revision;
1158 SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
1164 return SVN_NO_ERROR;
1169 svn_ra_serf__get_stable_url(const char **stable_url,
1170 svn_revnum_t *latest_revnum,
1171 svn_ra_serf__session_t *session,
1172 svn_ra_serf__connection_t *conn,
1174 svn_revnum_t revision,
1175 apr_pool_t *result_pool,
1176 apr_pool_t *scratch_pool)
1178 const char *basecoll_url;
1179 const char *repos_relpath;
1180 svn_revnum_t revnum_used;
1182 /* No URL? No sweat. We'll use the session URL. */
1184 url = session->session_url.path;
1186 /* If the caller didn't provide a specific connection for us to use,
1187 we'll use the default connection. */
1189 conn = session->conns[0];
1191 SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
1192 session, conn, revision, scratch_pool));
1193 SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
1194 session, conn, scratch_pool));
1196 *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
1199 *latest_revnum = revnum_used;
1201 return SVN_NO_ERROR;
1206 svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
1209 apr_hash_t *dav_props;
1210 const char *res_type;
1212 dav_props = apr_hash_get(props, "DAV:", 4);
1213 res_type = svn_prop_get_value(dav_props, "resourcetype");
1216 /* How did this happen? */
1217 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
1218 _("The PROPFIND response did not include the "
1219 "requested resourcetype value"));
1222 if (strcmp(res_type, "collection") == 0)
1224 *kind = svn_node_dir;
1228 *kind = svn_node_file;
1231 return SVN_NO_ERROR;
1236 svn_ra_serf__fetch_dav_prop(const char **value,
1237 svn_ra_serf__connection_t *conn,
1239 svn_revnum_t revision,
1240 const char *propname,
1241 apr_pool_t *result_pool,
1242 apr_pool_t *scratch_pool)
1245 apr_hash_t *dav_props;
1247 SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn, url, revision,
1249 scratch_pool, scratch_pool));
1250 dav_props = apr_hash_get(props, "DAV:", 4);
1251 if (dav_props == NULL)
1252 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
1253 _("The PROPFIND response did not include "
1254 "the requested 'DAV:' properties"));
1256 /* We wouldn't get here if the resource was not found (404), so the
1257 property should be present.
1259 Note: it is okay to call apr_pstrdup() with NULL. */
1260 *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
1262 return SVN_NO_ERROR;