property.c revision 251881
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. */
44typedef 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 */
61typedef 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
108static 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. */
142static 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  */
164static svn_error_t *
165copy_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  */
181static svn_error_t *
182propfind_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  */
205static svn_error_t *
206propfind_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
347const svn_string_t *
348svn_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
375const char *
376svn_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
394const svn_string_t *
395svn_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
404const char *
405svn_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
413void
414svn_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
453void
454svn_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
464static svn_error_t *
465setup_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
483static svn_error_t *
484create_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
561svn_error_t *
562svn_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 */
629svn_error_t *
630svn_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.code,
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 */
648svn_error_t *
649svn_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
671svn_error_t *
672svn_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
705svn_error_t *
706svn_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
748svn_error_t *
749svn_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
773svn_error_t *
774svn_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
826const char *
827svn_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  */
871static svn_error_t *
872set_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
892svn_error_t *
893svn_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
908static svn_error_t *
909select_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
942svn_error_t *
943svn_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 */
969static svn_error_t *
970retrieve_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.  */
1023static svn_error_t *
1024v1_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
1075svn_error_t *
1076svn_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.  */
1102static svn_error_t *
1103get_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
1168svn_error_t *
1169svn_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
1205svn_error_t *
1206svn_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
1235svn_error_t *
1236svn_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