xref: /trueos/contrib/subversion/subversion/libsvn_ra_serf/property.c (revision fd121a9c7b0c558bd6523654b46615e4cf221074)
1 /*
2  * property.c : property routines for ra_serf
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23 
24 
25 
26 #include <serf.h>
27 
28 #include "svn_hash.h"
29 #include "svn_path.h"
30 #include "svn_base64.h"
31 #include "svn_xml.h"
32 #include "svn_props.h"
33 #include "svn_dirent_uri.h"
34 
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"
39 
40 #include "ra_serf.h"
41 
42 
43 /* Our current parsing state we're in for the PROPFIND response. */
44 typedef enum prop_state_e {
45   INITIAL = 0,
46   MULTISTATUS,
47   RESPONSE,
48   HREF,
49   PROPSTAT,
50   STATUS,
51   PROP,
52   PROPVAL,
53   COLLECTION,
54   HREF_VALUE
55 } prop_state_e;
56 
57 
58 /*
59  * This structure represents a pending PROPFIND response.
60  */
61 typedef struct propfind_context_t {
62   /* pool to issue allocations from */
63   apr_pool_t *pool;
64 
65   svn_ra_serf__handler_t *handler;
66 
67   /* associated serf session */
68   svn_ra_serf__session_t *sess;
69   svn_ra_serf__connection_t *conn;
70 
71   /* the requested path */
72   const char *path;
73 
74   /* the requested version (number and string form) */
75   svn_revnum_t rev;
76   const char *label;
77 
78   /* the request depth */
79   const char *depth;
80 
81   /* the list of requested properties */
82   const svn_ra_serf__dav_props_t *find_props;
83 
84   /* hash table that will be updated with the properties
85    *
86    * This can be shared between multiple propfind_context_t
87    * structures
88    */
89   apr_hash_t *ret_props;
90 
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.
95    */
96   apr_hash_t *ps_props;
97 
98   /* If not-NULL, add us to this list when we're done. */
99   svn_ra_serf__list_t **done_list;
100 
101   svn_ra_serf__list_t done_item;
102 
103 } propfind_context_t;
104 
105 
106 #define D_ "DAV:"
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 },
111 
112   { MULTISTATUS, D_, "response", RESPONSE,
113     FALSE, { NULL }, FALSE },
114 
115   { RESPONSE, D_, "href", HREF,
116     TRUE, { NULL }, TRUE },
117 
118   { RESPONSE, D_, "propstat", PROPSTAT,
119     FALSE, { NULL }, TRUE },
120 
121   { PROPSTAT, D_, "status", STATUS,
122     TRUE, { NULL }, TRUE },
123 
124   { PROPSTAT, D_, "prop", PROP,
125     FALSE, { NULL }, FALSE },
126 
127   { PROP, "*", "*", PROPVAL,
128     TRUE, { "?V:encoding", NULL }, TRUE },
129 
130   { PROPVAL, D_, "collection", COLLECTION,
131     FALSE, { NULL }, TRUE },
132 
133   { PROPVAL, D_, "href", HREF_VALUE,
134     TRUE, { NULL }, TRUE },
135 
136   { 0 }
137 };
138 
139 
140 /* Return the HTTP status code contained in STATUS_LINE, or 0 if
141    there's a problem parsing it. */
parse_status_code(const char * status_line)142 static int parse_status_code(const char *status_line)
143 {
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] == ' ')
154     {
155       char *reason;
156 
157       return apr_strtoi64(status_line + 8, &reason, 10);
158     }
159   return 0;
160 }
161 
162 
163 /* Conforms to svn_ra_serf__path_rev_walker_t  */
164 static svn_error_t *
copy_into_ret_props(void * baton,const char * path,apr_ssize_t path_len,const char * ns,apr_ssize_t ns_len,const char * name,apr_ssize_t name_len,const svn_string_t * val,apr_pool_t * pool)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,
170                     apr_pool_t *pool)
171 {
172   propfind_context_t *ctx = baton;
173 
174   svn_ra_serf__set_ver_prop(ctx->ret_props, path, ctx->rev, ns, name,
175                             val, ctx->pool);
176   return SVN_NO_ERROR;
177 }
178 
179 
180 /* Conforms to svn_ra_serf__xml_opened_t  */
181 static svn_error_t *
propfind_opened(svn_ra_serf__xml_estate_t * xes,void * baton,int entered_state,const svn_ra_serf__dav_props_t * tag,apr_pool_t * scratch_pool)182 propfind_opened(svn_ra_serf__xml_estate_t *xes,
183                 void *baton,
184                 int entered_state,
185                 const svn_ra_serf__dav_props_t *tag,
186                 apr_pool_t *scratch_pool)
187 {
188   propfind_context_t *ctx = baton;
189 
190   if (entered_state == PROPVAL)
191     {
192       svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->namespace);
193       svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name);
194     }
195   else if (entered_state == PROPSTAT)
196     {
197       ctx->ps_props = apr_hash_make(ctx->pool);
198     }
199 
200   return SVN_NO_ERROR;
201 }
202 
203 
204 /* Conforms to svn_ra_serf__xml_closed_t  */
205 static svn_error_t *
propfind_closed(svn_ra_serf__xml_estate_t * xes,void * baton,int leaving_state,const svn_string_t * cdata,apr_hash_t * attrs,apr_pool_t * scratch_pool)206 propfind_closed(svn_ra_serf__xml_estate_t *xes,
207                 void *baton,
208                 int leaving_state,
209                 const svn_string_t *cdata,
210                 apr_hash_t *attrs,
211                 apr_pool_t *scratch_pool)
212 {
213   propfind_context_t *ctx = baton;
214 
215   if (leaving_state == MULTISTATUS)
216     {
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
220          arrive).  */
221       if (ctx->done_list)
222         {
223           ctx->done_item.data = ctx->handler;
224           ctx->done_item.next = *ctx->done_list;
225           *ctx->done_list = &ctx->done_item;
226         }
227     }
228   else if (leaving_state == HREF)
229     {
230       const char *path;
231       const svn_string_t *val_str;
232 
233       if (strcmp(ctx->depth, "1") == 0)
234         path = svn_urlpath__canonicalize(cdata->data, scratch_pool);
235       else
236         path = ctx->path;
237 
238       svn_ra_serf__xml_note(xes, RESPONSE, "path", path);
239 
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,
244                                 ctx->pool);
245     }
246   else if (leaving_state == COLLECTION)
247     {
248       svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection");
249     }
250   else if (leaving_state == HREF_VALUE)
251     {
252       svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data);
253     }
254   else if (leaving_state == STATUS)
255     {
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);
261       if (status != 200)
262         svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*");
263     }
264   else if (leaving_state == PROPVAL)
265     {
266       const char *encoding = svn_hash_gets(attrs, "V:encoding");
267       const svn_string_t *val_str;
268       apr_hash_t *gathered;
269       const char *path;
270       const char *ns;
271       const char *name;
272       const char *altvalue;
273 
274       if (encoding)
275         {
276           if (strcmp(encoding, "base64") != 0)
277             return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
278                                      NULL,
279                                      _("Got unrecognized encoding '%s'"),
280                                      encoding);
281 
282           /* Decode into the right pool.  */
283           val_str = svn_base64_decode_string(cdata, ctx->pool);
284         }
285       else
286         {
287           /* Copy into the right pool.  */
288           val_str = svn_string_dup(cdata, ctx->pool);
289         }
290 
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.
294 
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);
304 
305       /* These will be dup'd into CTX->POOL, as necessary.  */
306       path = svn_hash_gets(gathered, "path");
307       if (path == NULL)
308         path = ctx->path;
309 
310       ns = svn_hash_gets(attrs, "ns");
311       name = apr_pstrdup(ctx->pool,
312                          svn_hash_gets(attrs, "name"));
313 
314       altvalue = svn_hash_gets(attrs, "altvalue");
315       if (altvalue != NULL)
316         val_str = svn_string_create(altvalue, ctx->pool);
317 
318       svn_ra_serf__set_ver_prop(ctx->ps_props,
319                                 path, ctx->rev, ns, name, val_str,
320                                 ctx->pool);
321     }
322   else
323     {
324       apr_hash_t *gathered;
325 
326       SVN_ERR_ASSERT(leaving_state == PROPSTAT);
327 
328       gathered = svn_ra_serf__xml_gather_since(xes, PROPSTAT);
329 
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"))
334         {
335           SVN_ERR(svn_ra_serf__walk_all_paths(ctx->ps_props, ctx->rev,
336                                               copy_into_ret_props, ctx,
337                                               scratch_pool));
338         }
339 
340       ctx->ps_props = NULL;
341     }
342 
343   return SVN_NO_ERROR;
344 }
345 
346 
347 const svn_string_t *
svn_ra_serf__get_ver_prop_string(apr_hash_t * props,const char * path,svn_revnum_t rev,const char * ns,const char * name)348 svn_ra_serf__get_ver_prop_string(apr_hash_t *props,
349                                  const char *path,
350                                  svn_revnum_t rev,
351                                  const char *ns,
352                                  const char *name)
353 {
354   apr_hash_t *ver_props, *path_props, *ns_props;
355   void *val = NULL;
356 
357   ver_props = apr_hash_get(props, &rev, sizeof(rev));
358   if (ver_props)
359     {
360       path_props = svn_hash_gets(ver_props, path);
361 
362       if (path_props)
363         {
364           ns_props = svn_hash_gets(path_props, ns);
365           if (ns_props)
366             {
367               val = svn_hash_gets(ns_props, name);
368             }
369         }
370     }
371 
372   return val;
373 }
374 
375 const char *
svn_ra_serf__get_ver_prop(apr_hash_t * props,const char * path,svn_revnum_t rev,const char * ns,const char * name)376 svn_ra_serf__get_ver_prop(apr_hash_t *props,
377                           const char *path,
378                           svn_revnum_t rev,
379                           const char *ns,
380                           const char *name)
381 {
382   const svn_string_t *val;
383 
384   val = svn_ra_serf__get_ver_prop_string(props, path, rev, ns, name);
385 
386   if (val)
387     {
388       return val->data;
389     }
390 
391   return NULL;
392 }
393 
394 const svn_string_t *
svn_ra_serf__get_prop_string(apr_hash_t * props,const char * path,const char * ns,const char * name)395 svn_ra_serf__get_prop_string(apr_hash_t *props,
396                              const char *path,
397                              const char *ns,
398                              const char *name)
399 {
400   return svn_ra_serf__get_ver_prop_string(props, path, SVN_INVALID_REVNUM,
401                                           ns, name);
402 }
403 
404 const char *
svn_ra_serf__get_prop(apr_hash_t * props,const char * path,const char * ns,const char * name)405 svn_ra_serf__get_prop(apr_hash_t *props,
406                       const char *path,
407                       const char *ns,
408                       const char *name)
409 {
410   return svn_ra_serf__get_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name);
411 }
412 
413 void
svn_ra_serf__set_ver_prop(apr_hash_t * props,const char * path,svn_revnum_t rev,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * pool)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)
418 {
419   apr_hash_t *ver_props, *path_props, *ns_props;
420 
421   ver_props = apr_hash_get(props, &rev, sizeof(rev));
422   if (!ver_props)
423     {
424       ver_props = apr_hash_make(pool);
425       apr_hash_set(props, apr_pmemdup(pool, &rev, sizeof(rev)), sizeof(rev),
426                    ver_props);
427     }
428 
429   path_props = svn_hash_gets(ver_props, path);
430 
431   if (!path_props)
432     {
433       path_props = apr_hash_make(pool);
434       path = apr_pstrdup(pool, path);
435       svn_hash_sets(ver_props, path, path_props);
436 
437       /* todo: we know that we'll fail the next check, but fall through
438        * for now for simplicity's sake.
439        */
440     }
441 
442   ns_props = svn_hash_gets(path_props, ns);
443   if (!ns_props)
444     {
445       ns_props = apr_hash_make(pool);
446       ns = apr_pstrdup(pool, ns);
447       svn_hash_sets(path_props, ns, ns_props);
448     }
449 
450   svn_hash_sets(ns_props, name, val);
451 }
452 
453 void
svn_ra_serf__set_prop(apr_hash_t * props,const char * path,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * pool)454 svn_ra_serf__set_prop(apr_hash_t *props,
455                       const char *path,
456                       const char *ns, const char *name,
457                       const svn_string_t *val, apr_pool_t *pool)
458 {
459   svn_ra_serf__set_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name,
460                             val, pool);
461 }
462 
463 
464 static svn_error_t *
setup_propfind_headers(serf_bucket_t * headers,void * setup_baton,apr_pool_t * pool)465 setup_propfind_headers(serf_bucket_t *headers,
466                         void *setup_baton,
467                         apr_pool_t *pool)
468 {
469   propfind_context_t *ctx = setup_baton;
470 
471   serf_bucket_headers_setn(headers, "Depth", ctx->depth);
472   if (ctx->label)
473     {
474       serf_bucket_headers_setn(headers, "Label", ctx->label);
475     }
476 
477   return SVN_NO_ERROR;
478 }
479 
480 #define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">"
481 #define PROPFIND_TRAILER "</propfind>"
482 
483 static svn_error_t *
create_propfind_body(serf_bucket_t ** bkt,void * setup_baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool)484 create_propfind_body(serf_bucket_t **bkt,
485                      void *setup_baton,
486                      serf_bucket_alloc_t *alloc,
487                      apr_pool_t *pool)
488 {
489   propfind_context_t *ctx = setup_baton;
490 
491   serf_bucket_t *body_bkt, *tmp;
492   const svn_ra_serf__dav_props_t *prop;
493   svn_boolean_t requested_allprop = FALSE;
494 
495   body_bkt = serf_bucket_aggregate_create(alloc);
496 
497   prop = ctx->find_props;
498   while (prop && prop->namespace)
499     {
500       /* special case the allprop case. */
501       if (strcmp(prop->name, "allprop") == 0)
502         {
503           requested_allprop = TRUE;
504         }
505 
506       /* <*propname* xmlns="*propns*" /> */
507       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
508       serf_bucket_aggregate_append(body_bkt, tmp);
509 
510       tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
511       serf_bucket_aggregate_append(body_bkt, tmp);
512 
513       tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
514                                           sizeof(" xmlns=\"")-1,
515                                           alloc);
516       serf_bucket_aggregate_append(body_bkt, tmp);
517 
518       tmp = SERF_BUCKET_SIMPLE_STRING(prop->namespace, alloc);
519       serf_bucket_aggregate_append(body_bkt, tmp);
520 
521       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
522                                           alloc);
523       serf_bucket_aggregate_append(body_bkt, tmp);
524 
525       prop++;
526     }
527 
528   /* If we're not doing an allprop, add <prop> tags. */
529   if (!requested_allprop)
530     {
531       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
532                                           sizeof("<prop>")-1,
533                                           alloc);
534       serf_bucket_aggregate_prepend(body_bkt, tmp);
535     }
536 
537   tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
538                                       sizeof(PROPFIND_HEADER)-1,
539                                       alloc);
540 
541   serf_bucket_aggregate_prepend(body_bkt, tmp);
542 
543   if (!requested_allprop)
544     {
545       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
546                                           sizeof("</prop>")-1,
547                                           alloc);
548       serf_bucket_aggregate_append(body_bkt, tmp);
549     }
550 
551   tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
552                                       sizeof(PROPFIND_TRAILER)-1,
553                                       alloc);
554   serf_bucket_aggregate_append(body_bkt, tmp);
555 
556   *bkt = body_bkt;
557   return SVN_NO_ERROR;
558 }
559 
560 
561 svn_error_t *
svn_ra_serf__deliver_props(svn_ra_serf__handler_t ** propfind_handler,apr_hash_t * ret_props,svn_ra_serf__session_t * sess,svn_ra_serf__connection_t * conn,const char * path,svn_revnum_t rev,const char * depth,const svn_ra_serf__dav_props_t * find_props,svn_ra_serf__list_t ** done_list,apr_pool_t * pool)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,
566                            const char *path,
567                            svn_revnum_t rev,
568                            const char *depth,
569                            const svn_ra_serf__dav_props_t *find_props,
570                            svn_ra_serf__list_t **done_list,
571                            apr_pool_t *pool)
572 {
573   propfind_context_t *new_prop_ctx;
574   svn_ra_serf__handler_t *handler;
575   svn_ra_serf__xml_context_t *xmlctx;
576 
577   new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
578 
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;
588 
589   if (SVN_IS_VALID_REVNUM(rev))
590     {
591       new_prop_ctx->label = apr_ltoa(pool, rev);
592     }
593   else
594     {
595       new_prop_ctx->label = NULL;
596     }
597 
598   xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
599                                            propfind_opened,
600                                            propfind_closed,
601                                            NULL,
602                                            new_prop_ctx,
603                                            pool);
604   handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
605 
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;
613 
614   handler->session = new_prop_ctx->sess;
615   handler->conn = new_prop_ctx->conn;
616 
617   new_prop_ctx->handler = handler;
618 
619   *propfind_handler = handler;
620 
621   return SVN_NO_ERROR;
622 }
623 
624 
625 /*
626  * This helper function will block until the PROP_CTX indicates that is done
627  * or another error is returned.
628  */
629 svn_error_t *
svn_ra_serf__wait_for_props(svn_ra_serf__handler_t * handler,apr_pool_t * scratch_pool)630 svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler,
631                             apr_pool_t *scratch_pool)
632 {
633   svn_error_t *err;
634   svn_error_t *err2;
635 
636   err = svn_ra_serf__context_run_one(handler, scratch_pool);
637 
638   err2 = svn_ra_serf__error_on_status(handler->sline,
639                                       handler->path,
640                                       handler->location);
641 
642   return svn_error_compose_create(err2, err);
643 }
644 
645 /*
646  * This is a blocking version of deliver_props.
647  */
648 svn_error_t *
svn_ra_serf__retrieve_props(apr_hash_t ** results,svn_ra_serf__session_t * sess,svn_ra_serf__connection_t * conn,const char * url,svn_revnum_t rev,const char * depth,const svn_ra_serf__dav_props_t * props,apr_pool_t * result_pool,apr_pool_t * scratch_pool)649 svn_ra_serf__retrieve_props(apr_hash_t **results,
650                             svn_ra_serf__session_t *sess,
651                             svn_ra_serf__connection_t *conn,
652                             const char *url,
653                             svn_revnum_t rev,
654                             const char *depth,
655                             const svn_ra_serf__dav_props_t *props,
656                             apr_pool_t *result_pool,
657                             apr_pool_t *scratch_pool)
658 {
659   svn_ra_serf__handler_t *handler;
660 
661   *results = apr_hash_make(result_pool);
662 
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));
666 
667   return SVN_NO_ERROR;
668 }
669 
670 
671 svn_error_t *
svn_ra_serf__fetch_node_props(apr_hash_t ** results,svn_ra_serf__connection_t * conn,const char * url,svn_revnum_t revision,const svn_ra_serf__dav_props_t * which_props,apr_pool_t * result_pool,apr_pool_t * scratch_pool)672 svn_ra_serf__fetch_node_props(apr_hash_t **results,
673                               svn_ra_serf__connection_t *conn,
674                               const char *url,
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)
679 {
680   apr_hash_t *multiprops;
681   apr_hash_t *ver_props;
682 
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));
690 
691   ver_props = apr_hash_get(multiprops, &revision, sizeof(revision));
692   if (ver_props != NULL)
693     {
694       *results = svn_hash_gets(ver_props, url);
695       if (*results != NULL)
696         return SVN_NO_ERROR;
697     }
698 
699   return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
700                           _("The PROPFIND response did not include "
701                             "the requested properties"));
702 }
703 
704 
705 svn_error_t *
svn_ra_serf__walk_node_props(apr_hash_t * props,svn_ra_serf__walker_visitor_t walker,void * baton,apr_pool_t * scratch_pool)706 svn_ra_serf__walk_node_props(apr_hash_t *props,
707                              svn_ra_serf__walker_visitor_t walker,
708                              void *baton,
709                              apr_pool_t *scratch_pool)
710 {
711   apr_pool_t *iterpool;
712   apr_hash_index_t *ns_hi;
713 
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))
717     {
718       void *ns_val;
719       const void *ns_name;
720       apr_hash_index_t *name_hi;
721 
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.  */
725 
726       apr_hash_this(ns_hi, &ns_name, NULL, &ns_val);
727 
728       for (name_hi = apr_hash_first(scratch_pool, ns_val); name_hi;
729            name_hi = apr_hash_next(name_hi))
730         {
731           void *prop_val;
732           const void *prop_name;
733 
734           /* See note above, regarding clearing of this pool.  */
735           svn_pool_clear(iterpool);
736 
737           apr_hash_this(name_hi, &prop_name, NULL, &prop_val);
738 
739           SVN_ERR(walker(baton, ns_name, prop_name, prop_val, iterpool));
740         }
741     }
742   svn_pool_destroy(iterpool);
743 
744   return SVN_NO_ERROR;
745 }
746 
747 
748 svn_error_t *
svn_ra_serf__walk_all_props(apr_hash_t * props,const char * name,svn_revnum_t rev,svn_ra_serf__walker_visitor_t walker,void * baton,apr_pool_t * scratch_pool)749 svn_ra_serf__walk_all_props(apr_hash_t *props,
750                             const char *name,
751                             svn_revnum_t rev,
752                             svn_ra_serf__walker_visitor_t walker,
753                             void *baton,
754                             apr_pool_t *scratch_pool)
755 {
756   apr_hash_t *ver_props;
757   apr_hash_t *path_props;
758 
759   ver_props = apr_hash_get(props, &rev, sizeof(rev));
760   if (!ver_props)
761     return SVN_NO_ERROR;
762 
763   path_props = svn_hash_gets(ver_props, name);
764   if (!path_props)
765     return SVN_NO_ERROR;
766 
767   return svn_error_trace(svn_ra_serf__walk_node_props(path_props,
768                                                       walker, baton,
769                                                       scratch_pool));
770 }
771 
772 
773 svn_error_t *
svn_ra_serf__walk_all_paths(apr_hash_t * props,svn_revnum_t rev,svn_ra_serf__path_rev_walker_t walker,void * baton,apr_pool_t * pool)774 svn_ra_serf__walk_all_paths(apr_hash_t *props,
775                             svn_revnum_t rev,
776                             svn_ra_serf__path_rev_walker_t walker,
777                             void *baton,
778                             apr_pool_t *pool)
779 {
780   apr_hash_index_t *path_hi;
781   apr_hash_t *ver_props;
782 
783   ver_props = apr_hash_get(props, &rev, sizeof(rev));
784 
785   if (!ver_props)
786     {
787       return SVN_NO_ERROR;
788     }
789 
790   for (path_hi = apr_hash_first(pool, ver_props); path_hi;
791        path_hi = apr_hash_next(path_hi))
792     {
793       void *path_props;
794       const void *path_name;
795       apr_ssize_t path_len;
796       apr_hash_index_t *ns_hi;
797 
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))
801         {
802           void *ns_val;
803           const void *ns_name;
804           apr_ssize_t ns_len;
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))
809             {
810               void *prop_val;
811               const void *prop_name;
812               apr_ssize_t prop_len;
813 
814               apr_hash_this(name_hi, &prop_name, &prop_len, &prop_val);
815               /* use a subpool? */
816               SVN_ERR(walker(baton, path_name, path_len, ns_name, ns_len,
817                              prop_name, prop_len, prop_val, pool));
818             }
819         }
820     }
821 
822   return SVN_NO_ERROR;
823 }
824 
825 
826 const char *
svn_ra_serf__svnname_from_wirename(const char * ns,const char * name,apr_pool_t * result_pool)827 svn_ra_serf__svnname_from_wirename(const char *ns,
828                                    const char *name,
829                                    apr_pool_t *result_pool)
830 {
831   if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
832     return apr_pstrdup(result_pool, name);
833 
834   if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
835     return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
836 
837   if (strcmp(ns, SVN_PROP_PREFIX) == 0)
838     return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
839 
840   if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
841     return SVN_PROP_ENTRY_COMMITTED_REV;
842 
843   if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
844     return SVN_PROP_ENTRY_COMMITTED_DATE;
845 
846   if (strcmp(name, "creator-displayname") == 0)
847     return SVN_PROP_ENTRY_LAST_AUTHOR;
848 
849   if (strcmp(name, "repository-uuid") == 0)
850     return SVN_PROP_ENTRY_UUID;
851 
852   if (strcmp(name, "lock-token") == 0)
853     return SVN_PROP_ENTRY_LOCK_TOKEN;
854 
855   if (strcmp(name, "checked-in") == 0)
856     return SVN_RA_SERF__WC_CHECKED_IN_URL;
857 
858   if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
859     {
860       /* Here DAV: properties not yet converted to svn: properties should be
861          ignored. */
862       return NULL;
863     }
864 
865   /* An unknown namespace, must be a custom property. */
866   return apr_pstrcat(result_pool, ns, name, (char *)NULL);
867 }
868 
869 
870 /* Conforms to svn_ra_serf__walker_visitor_t  */
871 static svn_error_t *
set_flat_props(void * baton,const char * ns,const char * name,const svn_string_t * value,apr_pool_t * pool)872 set_flat_props(void *baton,
873                const char *ns,
874                const char *name,
875                const svn_string_t *value,
876                apr_pool_t *pool)
877 {
878   apr_hash_t *props = baton;
879   apr_pool_t *result_pool = apr_hash_pool_get(props);
880   const char *prop_name;
881 
882   /* ### is VAL in the proper pool?  */
883 
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);
887 
888   return SVN_NO_ERROR;
889 }
890 
891 
892 svn_error_t *
svn_ra_serf__flatten_props(apr_hash_t ** flat_props,apr_hash_t * props,apr_pool_t * result_pool,apr_pool_t * scratch_pool)893 svn_ra_serf__flatten_props(apr_hash_t **flat_props,
894                            apr_hash_t *props,
895                            apr_pool_t *result_pool,
896                            apr_pool_t *scratch_pool)
897 {
898   *flat_props = apr_hash_make(result_pool);
899 
900   return svn_error_trace(svn_ra_serf__walk_node_props(
901                             props,
902                             set_flat_props,
903                             *flat_props /* baton */,
904                             scratch_pool));
905 }
906 
907 
908 static svn_error_t *
select_revprops(void * baton,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * scratch_pool)909 select_revprops(void *baton,
910                 const char *ns,
911                 const char *name,
912                 const svn_string_t *val,
913                 apr_pool_t *scratch_pool)
914 {
915   apr_hash_t *revprops = baton;
916   apr_pool_t *result_pool = apr_hash_pool_get(revprops);
917   const char *prop_name;
918 
919   /* ### copy NAME into the RESULT_POOL?  */
920   /* ### copy VAL into the RESULT_POOL?  */
921 
922   if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
923     prop_name = name;
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)
929     prop_name = name;
930   else
931     {
932       /* do nothing for now? */
933       return SVN_NO_ERROR;
934     }
935 
936   svn_hash_sets(revprops, prop_name, val);
937 
938   return SVN_NO_ERROR;
939 }
940 
941 
942 svn_error_t *
svn_ra_serf__select_revprops(apr_hash_t ** revprops,const char * name,svn_revnum_t rev,apr_hash_t * all_revprops,apr_pool_t * result_pool,apr_pool_t * scratch_pool)943 svn_ra_serf__select_revprops(apr_hash_t **revprops,
944                              const char *name,
945                              svn_revnum_t rev,
946                              apr_hash_t *all_revprops,
947                              apr_pool_t *result_pool,
948                              apr_pool_t *scratch_pool)
949 {
950   *revprops = apr_hash_make(result_pool);
951 
952   return svn_error_trace(svn_ra_serf__walk_all_props(
953                             all_revprops, name, rev,
954                             select_revprops, *revprops,
955                             scratch_pool));
956 }
957 
958 
959 /*
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).
963  *
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.
968  */
969 static svn_error_t *
retrieve_baseline_info(svn_revnum_t * actual_revision,const char ** basecoll_url_p,svn_ra_serf__connection_t * conn,const char * baseline_url,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
977 {
978   apr_hash_t *props;
979   apr_hash_t *dav_props;
980   const char *basecoll_url;
981 
982   SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn,
983                                         baseline_url, revision,
984                                         baseline_props,
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.  */
988 
989   basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
990   if (!basecoll_url)
991     {
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"));
995     }
996   *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
997 
998   if (actual_revision)
999     {
1000       const char *version_name;
1001 
1002       version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
1003       if (!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"));
1007 
1008       *actual_revision = SVN_STR_TO_REV(version_name);
1009     }
1010 
1011   return SVN_NO_ERROR;
1012 }
1013 
1014 
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.
1018 
1019    Do the work over CONN.
1020 
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 *
v1_get_youngest_revnum(svn_revnum_t * youngest,const char ** basecoll_url,svn_ra_serf__connection_t * conn,const char * vcc_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
1030 {
1031   const char *baseline_url;
1032   const char *bc_url;
1033 
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,
1037                                       SVN_INVALID_REVNUM,
1038                                       "checked-in",
1039                                       scratch_pool, scratch_pool));
1040   if (!baseline_url)
1041     {
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"));
1045     }
1046   baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
1047 
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.  */
1051 
1052   /* First check baseline information cache. */
1053   SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
1054                                                   youngest,
1055                                                   conn->session->blncache,
1056                                                   baseline_url,
1057                                                   scratch_pool));
1058   if (!bc_url)
1059     {
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));
1066     }
1067 
1068   if (basecoll_url != NULL)
1069     *basecoll_url = apr_pstrdup(result_pool, bc_url);
1070 
1071   return SVN_NO_ERROR;
1072 }
1073 
1074 
1075 svn_error_t *
svn_ra_serf__get_youngest_revnum(svn_revnum_t * youngest,svn_ra_serf__session_t * session,apr_pool_t * scratch_pool)1076 svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
1077                                  svn_ra_serf__session_t *session,
1078                                  apr_pool_t *scratch_pool)
1079 {
1080   const char *vcc_url;
1081 
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));
1085 
1086   SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, scratch_pool));
1087 
1088   return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
1089                                                 session->conns[0], vcc_url,
1090                                                 scratch_pool, scratch_pool));
1091 }
1092 
1093 
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.
1096 
1097    *REVNUM_USED will be set to the revision used.
1098 
1099    Uses the specified CONN, which is part of SESSION.
1100 
1101    All allocations (results and temporary) are performed in POOL.  */
1102 static svn_error_t *
get_baseline_info(const char ** bc_url,svn_revnum_t * revnum_used,svn_ra_serf__session_t * session,svn_ra_serf__connection_t * conn,svn_revnum_t revision,apr_pool_t * pool)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,
1108                   apr_pool_t *pool)
1109 {
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))
1114     {
1115       if (SVN_IS_VALID_REVNUM(revision))
1116         {
1117           *revnum_used = revision;
1118         }
1119       else
1120         {
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"));
1127         }
1128 
1129       *bc_url = apr_psprintf(pool, "%s/%ld",
1130                              session->rev_root_stub, *revnum_used);
1131     }
1132 
1133   /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt.  */
1134   else
1135     {
1136       const char *vcc_url;
1137 
1138       SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, conn, pool));
1139 
1140       if (SVN_IS_VALID_REVNUM(revision))
1141         {
1142           /* First check baseline information cache. */
1143           SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
1144                                                    session->blncache,
1145                                                    revision, pool));
1146           if (!*bc_url)
1147             {
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));
1152             }
1153 
1154           *revnum_used = revision;
1155         }
1156       else
1157         {
1158           SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
1159                                          conn, vcc_url,
1160                                          pool, pool));
1161         }
1162     }
1163 
1164   return SVN_NO_ERROR;
1165 }
1166 
1167 
1168 svn_error_t *
svn_ra_serf__get_stable_url(const char ** stable_url,svn_revnum_t * latest_revnum,svn_ra_serf__session_t * session,svn_ra_serf__connection_t * conn,const char * url,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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,
1173                             const char *url,
1174                             svn_revnum_t revision,
1175                             apr_pool_t *result_pool,
1176                             apr_pool_t *scratch_pool)
1177 {
1178   const char *basecoll_url;
1179   const char *repos_relpath;
1180   svn_revnum_t revnum_used;
1181 
1182   /* No URL? No sweat. We'll use the session URL.  */
1183   if (! url)
1184     url = session->session_url.path;
1185 
1186   /* If the caller didn't provide a specific connection for us to use,
1187      we'll use the default connection.  */
1188   if (! conn)
1189     conn = session->conns[0];
1190 
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));
1195 
1196   *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
1197                                             result_pool);
1198   if (latest_revnum)
1199     *latest_revnum = revnum_used;
1200 
1201   return SVN_NO_ERROR;
1202 }
1203 
1204 
1205 svn_error_t *
svn_ra_serf__get_resource_type(svn_node_kind_t * kind,apr_hash_t * props)1206 svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
1207                                apr_hash_t *props)
1208 {
1209   apr_hash_t *dav_props;
1210   const char *res_type;
1211 
1212   dav_props = apr_hash_get(props, "DAV:", 4);
1213   res_type = svn_prop_get_value(dav_props, "resourcetype");
1214   if (!res_type)
1215     {
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"));
1220     }
1221 
1222   if (strcmp(res_type, "collection") == 0)
1223     {
1224       *kind = svn_node_dir;
1225     }
1226   else
1227     {
1228       *kind = svn_node_file;
1229     }
1230 
1231   return SVN_NO_ERROR;
1232 }
1233 
1234 
1235 svn_error_t *
svn_ra_serf__fetch_dav_prop(const char ** value,svn_ra_serf__connection_t * conn,const char * url,svn_revnum_t revision,const char * propname,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1236 svn_ra_serf__fetch_dav_prop(const char **value,
1237                             svn_ra_serf__connection_t *conn,
1238                             const char *url,
1239                             svn_revnum_t revision,
1240                             const char *propname,
1241                             apr_pool_t *result_pool,
1242                             apr_pool_t *scratch_pool)
1243 {
1244   apr_hash_t *props;
1245   apr_hash_t *dav_props;
1246 
1247   SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn, url, revision,
1248                                         checked_in_props,
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"));
1255 
1256   /* We wouldn't get here if the resource was not found (404), so the
1257      property should be present.
1258 
1259      Note: it is okay to call apr_pstrdup() with NULL.  */
1260   *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
1261 
1262   return SVN_NO_ERROR;
1263 }
1264