]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_ra_serf/property.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_ra_serf / property.c
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 \f
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 \f
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. */
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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
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
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 }