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 {
45 INITIAL = XML_STATE_INITIAL,
59 * This structure represents a pending PROPFIND response.
61 typedef struct propfind_context_t {
62 svn_ra_serf__handler_t *handler;
64 /* the requested path */
67 /* the requested version (in string form) */
70 /* the request depth */
73 /* the list of requested properties */
74 const svn_ra_serf__dav_props_t *find_props;
76 svn_ra_serf__prop_func_t prop_func;
77 void *prop_func_baton;
79 /* hash table containing all the properties associated with the
80 * "current" <propstat> tag. These will get copied into RET_PROPS
81 * if the status code similarly associated indicates that they are
82 * "good"; otherwise, they'll get discarded.
89 #define S_ SVN_XML_NAMESPACE
90 static const svn_ra_serf__xml_transition_t propfind_ttable[] = {
91 { INITIAL, D_, "multistatus", MULTISTATUS,
92 FALSE, { NULL }, TRUE },
94 { MULTISTATUS, D_, "response", RESPONSE,
95 FALSE, { NULL }, FALSE },
97 { RESPONSE, D_, "href", HREF,
98 TRUE, { NULL }, TRUE },
100 { RESPONSE, D_, "propstat", PROPSTAT,
101 FALSE, { NULL }, TRUE },
103 { PROPSTAT, D_, "status", STATUS,
104 TRUE, { NULL }, TRUE },
106 { PROPSTAT, D_, "prop", PROP,
107 FALSE, { NULL }, FALSE },
109 { PROP, "*", "*", PROPVAL,
110 TRUE, { "?V:encoding", NULL }, TRUE },
112 { PROPVAL, D_, "collection", COLLECTION,
113 FALSE, { NULL }, TRUE },
115 { PROPVAL, D_, "href", HREF_VALUE,
116 TRUE, { NULL }, TRUE },
121 static const int propfind_expected_status[] = {
126 /* Return the HTTP status code contained in STATUS_LINE, or 0 if
127 there's a problem parsing it. */
128 static apr_int64_t parse_status_code(const char *status_line)
130 /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */
131 if (status_line[0] == 'H' &&
132 status_line[1] == 'T' &&
133 status_line[2] == 'T' &&
134 status_line[3] == 'P' &&
135 status_line[4] == '/' &&
136 (status_line[5] >= '0' && status_line[5] <= '9') &&
137 status_line[6] == '.' &&
138 (status_line[7] >= '0' && status_line[7] <= '9') &&
139 status_line[8] == ' ')
143 return apr_strtoi64(status_line + 8, &reason, 10);
148 /* Conforms to svn_ra_serf__xml_opened_t */
150 propfind_opened(svn_ra_serf__xml_estate_t *xes,
153 const svn_ra_serf__dav_props_t *tag,
154 apr_pool_t *scratch_pool)
156 propfind_context_t *ctx = baton;
158 if (entered_state == PROPVAL)
160 svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->xmlns);
161 svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name);
163 else if (entered_state == PROPSTAT)
165 ctx->ps_props = apr_hash_make(svn_ra_serf__xml_state_pool(xes));
171 /* Set PROPS for NS:NAME VAL. Helper for propfind_closed */
173 set_ns_prop(apr_hash_t *ns_props,
174 const char *ns, const char *name,
175 const svn_string_t *val, apr_pool_t *result_pool)
177 apr_hash_t *props = svn_hash_gets(ns_props, ns);
181 props = apr_hash_make(result_pool);
182 ns = apr_pstrdup(result_pool, ns);
183 svn_hash_sets(ns_props, ns, props);
188 name = apr_pstrdup(result_pool, name);
189 val = svn_string_dup(val, result_pool);
192 svn_hash_sets(props, name, val);
195 /* Conforms to svn_ra_serf__xml_closed_t */
197 propfind_closed(svn_ra_serf__xml_estate_t *xes,
200 const svn_string_t *cdata,
202 apr_pool_t *scratch_pool)
204 propfind_context_t *ctx = baton;
206 if (leaving_state == MULTISTATUS)
208 /* We've gathered all the data from the reponse. Add this item
209 onto the "done list". External callers will then know this
210 request has been completed (tho stray response bytes may still
213 else if (leaving_state == HREF)
217 if (strcmp(ctx->depth, "1") == 0)
218 path = svn_urlpath__canonicalize(cdata->data, scratch_pool);
222 svn_ra_serf__xml_note(xes, RESPONSE, "path", path);
224 SVN_ERR(ctx->prop_func(ctx->prop_func_baton,
227 cdata, scratch_pool));
229 else if (leaving_state == COLLECTION)
231 svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection");
233 else if (leaving_state == HREF_VALUE)
235 svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data);
237 else if (leaving_state == STATUS)
239 /* Parse the status field, and remember if this is a property
240 that we wish to ignore. (Typically, if it's not a 200, the
241 status will be 404 to indicate that a property we
242 specifically requested from the server doesn't exist.) */
243 apr_int64_t status = parse_status_code(cdata->data);
245 svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*");
247 else if (leaving_state == PROPVAL)
249 const char *encoding;
250 const svn_string_t *val_str;
253 const char *altvalue;
255 if ((altvalue = svn_hash_gets(attrs, "altvalue")) != NULL)
257 val_str = svn_string_create(altvalue, scratch_pool);
259 else if ((encoding = svn_hash_gets(attrs, "V:encoding")) != NULL)
261 if (strcmp(encoding, "base64") != 0)
262 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
264 _("Got unrecognized encoding '%s'"),
267 /* Decode into the right pool. */
268 val_str = svn_base64_decode_string(cdata, scratch_pool);
272 /* Copy into the right pool. */
276 /* The current path sits on the RESPONSE state.
278 Now, it would be nice if we could, at this point, know that
279 the status code for this property indicated a problem -- then
280 we could simply bail out here and ignore the property.
281 Sadly, though, we might get the status code *after* we get
282 the property value. So we'll carry on with our processing
283 here, setting the property and value as expected. Once we
284 know for sure the status code associate with the property,
285 we'll decide its fate. */
287 ns = svn_hash_gets(attrs, "ns");
288 name = svn_hash_gets(attrs, "name");
290 set_ns_prop(ctx->ps_props, ns, name, val_str,
291 apr_hash_pool_get(ctx->ps_props));
295 apr_hash_t *gathered;
297 SVN_ERR_ASSERT(leaving_state == PROPSTAT);
299 gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE);
301 /* If we've squirreled away a note that says we want to ignore
302 these properties, we'll do so. Otherwise, we need to copy
303 them from the temporary hash into the ctx->ret_props hash. */
304 if (! svn_hash_gets(gathered, "ignore-prop"))
306 apr_hash_index_t *hi_ns;
308 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
311 path = svn_hash_gets(gathered, "path");
315 for (hi_ns = apr_hash_first(scratch_pool, ctx->ps_props);
317 hi_ns = apr_hash_next(hi_ns))
319 const char *ns = apr_hash_this_key(hi_ns);
320 apr_hash_t *props = apr_hash_this_val(hi_ns);
321 apr_hash_index_t *hi_prop;
323 svn_pool_clear(iterpool);
325 for (hi_prop = apr_hash_first(iterpool, props);
327 hi_prop = apr_hash_next(hi_prop))
329 const char *name = apr_hash_this_key(hi_prop);
330 const svn_string_t *value = apr_hash_this_val(hi_prop);
332 SVN_ERR(ctx->prop_func(ctx->prop_func_baton, path,
333 ns, name, value, iterpool));
337 svn_pool_destroy(iterpool);
340 ctx->ps_props = NULL; /* Allocated in PROPSTAT state pool */
349 setup_propfind_headers(serf_bucket_t *headers,
351 apr_pool_t *pool /* request pool */,
352 apr_pool_t *scratch_pool)
354 propfind_context_t *ctx = setup_baton;
356 serf_bucket_headers_setn(headers, "Depth", ctx->depth);
359 serf_bucket_headers_setn(headers, "Label", ctx->label);
365 #define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">"
366 #define PROPFIND_TRAILER "</propfind>"
368 /* Implements svn_ra_serf__request_body_delegate_t */
370 create_propfind_body(serf_bucket_t **bkt,
372 serf_bucket_alloc_t *alloc,
373 apr_pool_t *pool /* request pool */,
374 apr_pool_t *scratch_pool)
376 propfind_context_t *ctx = setup_baton;
378 serf_bucket_t *body_bkt, *tmp;
379 const svn_ra_serf__dav_props_t *prop;
380 svn_boolean_t requested_allprop = FALSE;
382 body_bkt = serf_bucket_aggregate_create(alloc);
384 prop = ctx->find_props;
385 while (prop && prop->xmlns)
387 /* special case the allprop case. */
388 if (strcmp(prop->name, "allprop") == 0)
390 requested_allprop = TRUE;
396 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
397 sizeof(PROPFIND_HEADER)-1,
399 serf_bucket_aggregate_append(body_bkt, tmp);
401 /* If we're not doing an allprop, add <prop> tags. */
402 if (!requested_allprop)
404 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
407 serf_bucket_aggregate_append(body_bkt, tmp);
410 prop = ctx->find_props;
411 while (prop && prop->xmlns)
413 /* <*propname* xmlns="*propns*" /> */
414 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
415 serf_bucket_aggregate_append(body_bkt, tmp);
417 tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
418 serf_bucket_aggregate_append(body_bkt, tmp);
420 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
421 sizeof(" xmlns=\"")-1,
423 serf_bucket_aggregate_append(body_bkt, tmp);
425 tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc);
426 serf_bucket_aggregate_append(body_bkt, tmp);
428 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
430 serf_bucket_aggregate_append(body_bkt, tmp);
435 if (!requested_allprop)
437 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
440 serf_bucket_aggregate_append(body_bkt, tmp);
443 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
444 sizeof(PROPFIND_TRAILER)-1,
446 serf_bucket_aggregate_append(body_bkt, tmp);
454 svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler,
455 svn_ra_serf__session_t *sess,
459 const svn_ra_serf__dav_props_t *find_props,
460 svn_ra_serf__prop_func_t prop_func,
461 void *prop_func_baton,
464 propfind_context_t *new_prop_ctx;
465 svn_ra_serf__handler_t *handler;
466 svn_ra_serf__xml_context_t *xmlctx;
468 new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
470 new_prop_ctx->path = path;
471 new_prop_ctx->find_props = find_props;
472 new_prop_ctx->prop_func = prop_func;
473 new_prop_ctx->prop_func_baton = prop_func_baton;
474 new_prop_ctx->depth = depth;
476 if (SVN_IS_VALID_REVNUM(rev))
478 new_prop_ctx->label = apr_ltoa(pool, rev);
482 new_prop_ctx->label = NULL;
485 xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
491 handler = svn_ra_serf__create_expat_handler(sess, xmlctx,
492 propfind_expected_status,
495 handler->method = "PROPFIND";
496 handler->path = path;
497 handler->body_delegate = create_propfind_body;
498 handler->body_type = "text/xml";
499 handler->body_delegate_baton = new_prop_ctx;
500 handler->header_delegate = setup_propfind_headers;
501 handler->header_delegate_baton = new_prop_ctx;
503 handler->no_dav_headers = TRUE;
505 new_prop_ctx->handler = handler;
507 *propfind_handler = handler;
513 svn_ra_serf__deliver_svn_props(void *baton,
517 const svn_string_t *value,
518 apr_pool_t *scratch_pool)
520 apr_hash_t *props = baton;
521 apr_pool_t *result_pool = apr_hash_pool_get(props);
522 const char *prop_name;
524 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
525 if (prop_name == NULL)
528 svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool));
534 * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties
535 * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash
536 * (const char * -> svn_string_t *) to the values.
539 deliver_node_props(void *baton,
543 const svn_string_t *value,
544 apr_pool_t *scratch_pool)
546 apr_hash_t *nss = baton;
548 apr_pool_t *result_pool = apr_hash_pool_get(nss);
550 props = svn_hash_gets(nss, ns);
554 props = apr_hash_make(result_pool);
556 ns = apr_pstrdup(result_pool, ns);
557 svn_hash_sets(nss, ns, props);
560 name = apr_pstrdup(result_pool, name);
561 svn_hash_sets(props, name, svn_string_dup(value, result_pool));
567 svn_ra_serf__fetch_node_props(apr_hash_t **results,
568 svn_ra_serf__session_t *session,
570 svn_revnum_t revision,
571 const svn_ra_serf__dav_props_t *which_props,
572 apr_pool_t *result_pool,
573 apr_pool_t *scratch_pool)
576 svn_ra_serf__handler_t *handler;
578 props = apr_hash_make(result_pool);
580 SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
581 url, revision, "0", which_props,
583 props, scratch_pool));
585 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
592 svn_ra_serf__svnname_from_wirename(const char *ns,
594 apr_pool_t *result_pool)
596 if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
597 return apr_pstrdup(result_pool, name);
599 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
600 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
602 if (strcmp(ns, SVN_PROP_PREFIX) == 0)
603 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
605 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
606 return SVN_PROP_ENTRY_COMMITTED_REV;
608 if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
609 return SVN_PROP_ENTRY_COMMITTED_DATE;
611 if (strcmp(name, "creator-displayname") == 0)
612 return SVN_PROP_ENTRY_LAST_AUTHOR;
614 if (strcmp(name, "repository-uuid") == 0)
615 return SVN_PROP_ENTRY_UUID;
617 if (strcmp(name, "lock-token") == 0)
618 return SVN_PROP_ENTRY_LOCK_TOKEN;
620 if (strcmp(name, "checked-in") == 0)
621 return SVN_RA_SERF__WC_CHECKED_IN_URL;
623 if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
625 /* Here DAV: properties not yet converted to svn: properties should be
630 /* An unknown namespace, must be a custom property. */
631 return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL);
635 * Contact the server (using CONN) to calculate baseline
636 * information for BASELINE_URL at REVISION (which may be
637 * SVN_INVALID_REVNUM to query the HEAD revision).
639 * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
640 * retrieved from the server as part of this process (which should
641 * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the
642 * baseline collection URL.
645 retrieve_baseline_info(svn_revnum_t *actual_revision,
646 const char **basecoll_url_p,
647 svn_ra_serf__session_t *session,
648 const char *baseline_url,
649 svn_revnum_t revision,
650 apr_pool_t *result_pool,
651 apr_pool_t *scratch_pool)
654 apr_hash_t *dav_props;
655 const char *basecoll_url;
657 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session,
658 baseline_url, revision,
660 scratch_pool, scratch_pool));
661 dav_props = apr_hash_get(props, "DAV:", 4);
662 /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */
664 basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
667 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
668 _("The PROPFIND response did not include "
669 "the requested baseline-collection value"));
671 *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
675 const char *version_name;
677 version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
682 SVN_ERR(svn_cstring_atoi64(&rev, version_name));
683 *actual_revision = (svn_revnum_t)rev;
686 if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision))
687 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
688 _("The PROPFIND response did not include "
689 "the requested version-name value"));
696 /* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
697 revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
698 collection URL is also returned.
700 Do the work over CONN.
702 *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
703 temporary allocations will be made in SCRATCH_POOL. */
705 v1_get_youngest_revnum(svn_revnum_t *youngest,
706 const char **basecoll_url,
707 svn_ra_serf__session_t *session,
709 apr_pool_t *result_pool,
710 apr_pool_t *scratch_pool)
712 const char *baseline_url;
715 /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
716 revision) will return the latest Baseline resource's URL. */
717 SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url,
720 scratch_pool, scratch_pool));
723 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
724 _("The OPTIONS response did not include "
725 "the requested checked-in value"));
727 baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
729 /* From the Baseline resource, we can fetch the DAV:baseline-collection
730 and DAV:version-name properties. The latter is the revision number,
731 which is formally the name used in Label: headers. */
733 /* First check baseline information cache. */
734 SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
741 SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session,
742 baseline_url, SVN_INVALID_REVNUM,
743 scratch_pool, scratch_pool));
744 SVN_ERR(svn_ra_serf__blncache_set(session->blncache,
745 baseline_url, *youngest,
746 bc_url, scratch_pool));
749 if (basecoll_url != NULL)
750 *basecoll_url = apr_pstrdup(result_pool, bc_url);
757 svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
758 svn_ra_serf__session_t *session,
759 apr_pool_t *scratch_pool)
763 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
764 return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
765 youngest, session, scratch_pool));
767 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
769 return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
771 scratch_pool, scratch_pool));
775 /* Set *BC_URL to the baseline collection url for REVISION. If REVISION
776 is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
778 *REVNUM_USED will be set to the revision used.
780 Uses the specified CONN, which is part of SESSION.
782 All allocations (results and temporary) are performed in POOL. */
784 get_baseline_info(const char **bc_url,
785 svn_revnum_t *revnum_used,
786 svn_ra_serf__session_t *session,
787 svn_revnum_t revision,
788 apr_pool_t *result_pool,
789 apr_pool_t *scratch_pool)
791 /* If we detected HTTP v2 support on the server, we can construct
792 the baseline collection URL ourselves, and fetch the latest
793 revision (if needed) with an OPTIONS request. */
794 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
796 if (SVN_IS_VALID_REVNUM(revision))
798 *revnum_used = revision;
802 SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
803 revnum_used, session, scratch_pool));
806 *bc_url = apr_psprintf(result_pool, "%s/%ld",
807 session->rev_root_stub, *revnum_used);
810 /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */
815 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
817 if (SVN_IS_VALID_REVNUM(revision))
819 /* First check baseline information cache. */
820 SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
822 revision, result_pool));
825 SVN_ERR(retrieve_baseline_info(NULL, bc_url, session,
827 result_pool, scratch_pool));
828 SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
833 *revnum_used = revision;
837 SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
839 result_pool, scratch_pool));
848 svn_ra_serf__get_stable_url(const char **stable_url,
849 svn_revnum_t *latest_revnum,
850 svn_ra_serf__session_t *session,
852 svn_revnum_t revision,
853 apr_pool_t *result_pool,
854 apr_pool_t *scratch_pool)
856 const char *basecoll_url;
857 const char *repos_relpath;
858 svn_revnum_t revnum_used;
860 /* No URL? No sweat. We'll use the session URL. */
862 url = session->session_url.path;
864 SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
865 session, revision, scratch_pool, scratch_pool));
866 SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
867 session, scratch_pool));
869 *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
872 *latest_revnum = revnum_used;
879 svn_ra_serf__fetch_dav_prop(const char **value,
880 svn_ra_serf__session_t *session,
882 svn_revnum_t revision,
883 const char *propname,
884 apr_pool_t *result_pool,
885 apr_pool_t *scratch_pool)
888 apr_hash_t *dav_props;
890 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision,
892 scratch_pool, scratch_pool));
893 dav_props = apr_hash_get(props, "DAV:", 4);
894 if (dav_props == NULL)
895 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
896 _("The PROPFIND response did not include "
897 "the requested 'DAV:' properties"));
899 /* We wouldn't get here if the resource was not found (404), so the
900 property should be present.
902 Note: it is okay to call apr_pstrdup() with NULL. */
903 *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
908 /* Removes all non regular properties from PROPS */
910 svn_ra_serf__keep_only_regular_props(apr_hash_t *props,
911 apr_pool_t *scratch_pool)
913 apr_hash_index_t *hi;
915 for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
917 const char *propname = apr_hash_this_key(hi);
919 if (svn_property_kind2(propname) != svn_prop_regular_kind)
920 svn_hash_sets(props, propname, NULL);