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;
393 /* <*propname* xmlns="*propns*" /> */
394 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
395 serf_bucket_aggregate_append(body_bkt, tmp);
397 tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
398 serf_bucket_aggregate_append(body_bkt, tmp);
400 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
401 sizeof(" xmlns=\"")-1,
403 serf_bucket_aggregate_append(body_bkt, tmp);
405 tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc);
406 serf_bucket_aggregate_append(body_bkt, tmp);
408 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
410 serf_bucket_aggregate_append(body_bkt, tmp);
415 /* If we're not doing an allprop, add <prop> tags. */
416 if (!requested_allprop)
418 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
421 serf_bucket_aggregate_prepend(body_bkt, tmp);
424 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
425 sizeof(PROPFIND_HEADER)-1,
428 serf_bucket_aggregate_prepend(body_bkt, tmp);
430 if (!requested_allprop)
432 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
435 serf_bucket_aggregate_append(body_bkt, tmp);
438 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
439 sizeof(PROPFIND_TRAILER)-1,
441 serf_bucket_aggregate_append(body_bkt, tmp);
449 svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler,
450 svn_ra_serf__session_t *sess,
454 const svn_ra_serf__dav_props_t *find_props,
455 svn_ra_serf__prop_func_t prop_func,
456 void *prop_func_baton,
459 propfind_context_t *new_prop_ctx;
460 svn_ra_serf__handler_t *handler;
461 svn_ra_serf__xml_context_t *xmlctx;
463 new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
465 new_prop_ctx->path = path;
466 new_prop_ctx->find_props = find_props;
467 new_prop_ctx->prop_func = prop_func;
468 new_prop_ctx->prop_func_baton = prop_func_baton;
469 new_prop_ctx->depth = depth;
471 if (SVN_IS_VALID_REVNUM(rev))
473 new_prop_ctx->label = apr_ltoa(pool, rev);
477 new_prop_ctx->label = NULL;
480 xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
486 handler = svn_ra_serf__create_expat_handler(sess, xmlctx,
487 propfind_expected_status,
490 handler->method = "PROPFIND";
491 handler->path = path;
492 handler->body_delegate = create_propfind_body;
493 handler->body_type = "text/xml";
494 handler->body_delegate_baton = new_prop_ctx;
495 handler->header_delegate = setup_propfind_headers;
496 handler->header_delegate_baton = new_prop_ctx;
498 handler->no_dav_headers = TRUE;
500 new_prop_ctx->handler = handler;
502 *propfind_handler = handler;
508 svn_ra_serf__deliver_svn_props(void *baton,
512 const svn_string_t *value,
513 apr_pool_t *scratch_pool)
515 apr_hash_t *props = baton;
516 apr_pool_t *result_pool = apr_hash_pool_get(props);
517 const char *prop_name;
519 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
520 if (prop_name == NULL)
523 svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool));
529 * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties
530 * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash
531 * (const char * -> svn_string_t *) to the values.
534 deliver_node_props(void *baton,
538 const svn_string_t *value,
539 apr_pool_t *scratch_pool)
541 apr_hash_t *nss = baton;
543 apr_pool_t *result_pool = apr_hash_pool_get(nss);
545 props = svn_hash_gets(nss, ns);
549 props = apr_hash_make(result_pool);
551 ns = apr_pstrdup(result_pool, ns);
552 svn_hash_sets(nss, ns, props);
555 name = apr_pstrdup(result_pool, name);
556 svn_hash_sets(props, name, svn_string_dup(value, result_pool));
562 svn_ra_serf__fetch_node_props(apr_hash_t **results,
563 svn_ra_serf__session_t *session,
565 svn_revnum_t revision,
566 const svn_ra_serf__dav_props_t *which_props,
567 apr_pool_t *result_pool,
568 apr_pool_t *scratch_pool)
571 svn_ra_serf__handler_t *handler;
573 props = apr_hash_make(result_pool);
575 SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
576 url, revision, "0", which_props,
578 props, scratch_pool));
580 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
587 svn_ra_serf__svnname_from_wirename(const char *ns,
589 apr_pool_t *result_pool)
591 if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
592 return apr_pstrdup(result_pool, name);
594 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
595 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
597 if (strcmp(ns, SVN_PROP_PREFIX) == 0)
598 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
600 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
601 return SVN_PROP_ENTRY_COMMITTED_REV;
603 if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
604 return SVN_PROP_ENTRY_COMMITTED_DATE;
606 if (strcmp(name, "creator-displayname") == 0)
607 return SVN_PROP_ENTRY_LAST_AUTHOR;
609 if (strcmp(name, "repository-uuid") == 0)
610 return SVN_PROP_ENTRY_UUID;
612 if (strcmp(name, "lock-token") == 0)
613 return SVN_PROP_ENTRY_LOCK_TOKEN;
615 if (strcmp(name, "checked-in") == 0)
616 return SVN_RA_SERF__WC_CHECKED_IN_URL;
618 if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
620 /* Here DAV: properties not yet converted to svn: properties should be
625 /* An unknown namespace, must be a custom property. */
626 return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL);
630 * Contact the server (using CONN) to calculate baseline
631 * information for BASELINE_URL at REVISION (which may be
632 * SVN_INVALID_REVNUM to query the HEAD revision).
634 * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
635 * retrieved from the server as part of this process (which should
636 * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the
637 * baseline collection URL.
640 retrieve_baseline_info(svn_revnum_t *actual_revision,
641 const char **basecoll_url_p,
642 svn_ra_serf__session_t *session,
643 const char *baseline_url,
644 svn_revnum_t revision,
645 apr_pool_t *result_pool,
646 apr_pool_t *scratch_pool)
649 apr_hash_t *dav_props;
650 const char *basecoll_url;
652 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session,
653 baseline_url, revision,
655 scratch_pool, scratch_pool));
656 dav_props = apr_hash_get(props, "DAV:", 4);
657 /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */
659 basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
662 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
663 _("The PROPFIND response did not include "
664 "the requested baseline-collection value"));
666 *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
670 const char *version_name;
672 version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
677 SVN_ERR(svn_cstring_atoi64(&rev, version_name));
678 *actual_revision = (svn_revnum_t)rev;
681 if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision))
682 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
683 _("The PROPFIND response did not include "
684 "the requested version-name value"));
691 /* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
692 revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
693 collection URL is also returned.
695 Do the work over CONN.
697 *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
698 temporary allocations will be made in SCRATCH_POOL. */
700 v1_get_youngest_revnum(svn_revnum_t *youngest,
701 const char **basecoll_url,
702 svn_ra_serf__session_t *session,
704 apr_pool_t *result_pool,
705 apr_pool_t *scratch_pool)
707 const char *baseline_url;
710 /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
711 revision) will return the latest Baseline resource's URL. */
712 SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url,
715 scratch_pool, scratch_pool));
718 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
719 _("The OPTIONS response did not include "
720 "the requested checked-in value"));
722 baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
724 /* From the Baseline resource, we can fetch the DAV:baseline-collection
725 and DAV:version-name properties. The latter is the revision number,
726 which is formally the name used in Label: headers. */
728 /* First check baseline information cache. */
729 SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
736 SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session,
737 baseline_url, SVN_INVALID_REVNUM,
738 scratch_pool, scratch_pool));
739 SVN_ERR(svn_ra_serf__blncache_set(session->blncache,
740 baseline_url, *youngest,
741 bc_url, scratch_pool));
744 if (basecoll_url != NULL)
745 *basecoll_url = apr_pstrdup(result_pool, bc_url);
752 svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
753 svn_ra_serf__session_t *session,
754 apr_pool_t *scratch_pool)
758 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
759 return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
760 youngest, session, scratch_pool));
762 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
764 return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
766 scratch_pool, scratch_pool));
770 /* Set *BC_URL to the baseline collection url for REVISION. If REVISION
771 is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
773 *REVNUM_USED will be set to the revision used.
775 Uses the specified CONN, which is part of SESSION.
777 All allocations (results and temporary) are performed in POOL. */
779 get_baseline_info(const char **bc_url,
780 svn_revnum_t *revnum_used,
781 svn_ra_serf__session_t *session,
782 svn_revnum_t revision,
783 apr_pool_t *result_pool,
784 apr_pool_t *scratch_pool)
786 /* If we detected HTTP v2 support on the server, we can construct
787 the baseline collection URL ourselves, and fetch the latest
788 revision (if needed) with an OPTIONS request. */
789 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
791 if (SVN_IS_VALID_REVNUM(revision))
793 *revnum_used = revision;
797 SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
798 revnum_used, session, scratch_pool));
801 *bc_url = apr_psprintf(result_pool, "%s/%ld",
802 session->rev_root_stub, *revnum_used);
805 /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */
810 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
812 if (SVN_IS_VALID_REVNUM(revision))
814 /* First check baseline information cache. */
815 SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
817 revision, result_pool));
820 SVN_ERR(retrieve_baseline_info(NULL, bc_url, session,
822 result_pool, scratch_pool));
823 SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
828 *revnum_used = revision;
832 SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
834 result_pool, scratch_pool));
843 svn_ra_serf__get_stable_url(const char **stable_url,
844 svn_revnum_t *latest_revnum,
845 svn_ra_serf__session_t *session,
847 svn_revnum_t revision,
848 apr_pool_t *result_pool,
849 apr_pool_t *scratch_pool)
851 const char *basecoll_url;
852 const char *repos_relpath;
853 svn_revnum_t revnum_used;
855 /* No URL? No sweat. We'll use the session URL. */
857 url = session->session_url.path;
859 SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
860 session, revision, scratch_pool, scratch_pool));
861 SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
862 session, scratch_pool));
864 *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
867 *latest_revnum = revnum_used;
874 svn_ra_serf__fetch_dav_prop(const char **value,
875 svn_ra_serf__session_t *session,
877 svn_revnum_t revision,
878 const char *propname,
879 apr_pool_t *result_pool,
880 apr_pool_t *scratch_pool)
883 apr_hash_t *dav_props;
885 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision,
887 scratch_pool, scratch_pool));
888 dav_props = apr_hash_get(props, "DAV:", 4);
889 if (dav_props == NULL)
890 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
891 _("The PROPFIND response did not include "
892 "the requested 'DAV:' properties"));
894 /* We wouldn't get here if the resource was not found (404), so the
895 property should be present.
897 Note: it is okay to call apr_pstrdup() with NULL. */
898 *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
903 /* Removes all non regular properties from PROPS */
905 svn_ra_serf__keep_only_regular_props(apr_hash_t *props,
906 apr_pool_t *scratch_pool)
908 apr_hash_index_t *hi;
910 for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
912 const char *propname = apr_hash_this_key(hi);
914 if (svn_property_kind2(propname) != svn_prop_regular_kind)
915 svn_hash_sets(props, propname, NULL);