property.c revision 299742
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 = XML_STATE_INITIAL,
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  svn_ra_serf__handler_t *handler;
63
64  /* the requested path */
65  const char *path;
66
67  /* the requested version (in string form) */
68  const char *label;
69
70  /* the request depth */
71  const char *depth;
72
73  /* the list of requested properties */
74  const svn_ra_serf__dav_props_t *find_props;
75
76  svn_ra_serf__prop_func_t prop_func;
77  void *prop_func_baton;
78
79  /* hash table containing all the properties associated with the
80   * "current" <propstat> tag.  These will get copied into RET_PROPS
81   * if the status code similarly associated indicates that they are
82   * "good"; otherwise, they'll get discarded.
83   */
84  apr_hash_t *ps_props;
85} propfind_context_t;
86
87
88#define D_ "DAV:"
89#define S_ SVN_XML_NAMESPACE
90static const svn_ra_serf__xml_transition_t propfind_ttable[] = {
91  { INITIAL, D_, "multistatus", MULTISTATUS,
92    FALSE, { NULL }, TRUE },
93
94  { MULTISTATUS, D_, "response", RESPONSE,
95    FALSE, { NULL }, FALSE },
96
97  { RESPONSE, D_, "href", HREF,
98    TRUE, { NULL }, TRUE },
99
100  { RESPONSE, D_, "propstat", PROPSTAT,
101    FALSE, { NULL }, TRUE },
102
103  { PROPSTAT, D_, "status", STATUS,
104    TRUE, { NULL }, TRUE },
105
106  { PROPSTAT, D_, "prop", PROP,
107    FALSE, { NULL }, FALSE },
108
109  { PROP, "*", "*", PROPVAL,
110    TRUE, { "?V:encoding", NULL }, TRUE },
111
112  { PROPVAL, D_, "collection", COLLECTION,
113    FALSE, { NULL }, TRUE },
114
115  { PROPVAL, D_, "href", HREF_VALUE,
116    TRUE, { NULL }, TRUE },
117
118  { 0 }
119};
120
121static const int propfind_expected_status[] = {
122  207,
123  0
124};
125
126/* Return the HTTP status code contained in STATUS_LINE, or 0 if
127   there's a problem parsing it. */
128static apr_int64_t parse_status_code(const char *status_line)
129{
130  /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */
131  if (status_line[0] == 'H' &&
132      status_line[1] == 'T' &&
133      status_line[2] == 'T' &&
134      status_line[3] == 'P' &&
135      status_line[4] == '/' &&
136      (status_line[5] >= '0' && status_line[5] <= '9') &&
137      status_line[6] == '.' &&
138      (status_line[7] >= '0' && status_line[7] <= '9') &&
139      status_line[8] == ' ')
140    {
141      char *reason;
142
143      return apr_strtoi64(status_line + 8, &reason, 10);
144    }
145  return 0;
146}
147
148/* Conforms to svn_ra_serf__xml_opened_t  */
149static svn_error_t *
150propfind_opened(svn_ra_serf__xml_estate_t *xes,
151                void *baton,
152                int entered_state,
153                const svn_ra_serf__dav_props_t *tag,
154                apr_pool_t *scratch_pool)
155{
156  propfind_context_t *ctx = baton;
157
158  if (entered_state == PROPVAL)
159    {
160        svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->xmlns);
161      svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name);
162    }
163  else if (entered_state == PROPSTAT)
164    {
165      ctx->ps_props = apr_hash_make(svn_ra_serf__xml_state_pool(xes));
166    }
167
168  return SVN_NO_ERROR;
169}
170
171/* Set PROPS for NS:NAME VAL. Helper for propfind_closed */
172static void
173set_ns_prop(apr_hash_t *ns_props,
174            const char *ns, const char *name,
175            const svn_string_t *val, apr_pool_t *result_pool)
176{
177  apr_hash_t *props = svn_hash_gets(ns_props, ns);
178
179  if (!props)
180    {
181      props = apr_hash_make(result_pool);
182      ns = apr_pstrdup(result_pool, ns);
183      svn_hash_sets(ns_props, ns, props);
184    }
185
186  if (val)
187    {
188      name = apr_pstrdup(result_pool, name);
189      val = svn_string_dup(val, result_pool);
190    }
191
192  svn_hash_sets(props, name, val);
193}
194
195/* Conforms to svn_ra_serf__xml_closed_t  */
196static svn_error_t *
197propfind_closed(svn_ra_serf__xml_estate_t *xes,
198                void *baton,
199                int leaving_state,
200                const svn_string_t *cdata,
201                apr_hash_t *attrs,
202                apr_pool_t *scratch_pool)
203{
204  propfind_context_t *ctx = baton;
205
206  if (leaving_state == MULTISTATUS)
207    {
208      /* We've gathered all the data from the reponse. Add this item
209         onto the "done list". External callers will then know this
210         request has been completed (tho stray response bytes may still
211         arrive).  */
212    }
213  else if (leaving_state == HREF)
214    {
215      const char *path;
216
217      if (strcmp(ctx->depth, "1") == 0)
218        path = svn_urlpath__canonicalize(cdata->data, scratch_pool);
219      else
220        path = ctx->path;
221
222      svn_ra_serf__xml_note(xes, RESPONSE, "path", path);
223
224      SVN_ERR(ctx->prop_func(ctx->prop_func_baton,
225                             path,
226                             D_, "href",
227                             cdata, scratch_pool));
228    }
229  else if (leaving_state == COLLECTION)
230    {
231      svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection");
232    }
233  else if (leaving_state == HREF_VALUE)
234    {
235      svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data);
236    }
237  else if (leaving_state == STATUS)
238    {
239      /* Parse the status field, and remember if this is a property
240         that we wish to ignore.  (Typically, if it's not a 200, the
241         status will be 404 to indicate that a property we
242         specifically requested from the server doesn't exist.)  */
243      apr_int64_t status = parse_status_code(cdata->data);
244      if (status != 200)
245        svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*");
246    }
247  else if (leaving_state == PROPVAL)
248    {
249      const char *encoding;
250      const svn_string_t *val_str;
251      const char *ns;
252      const char *name;
253      const char *altvalue;
254
255      if ((altvalue = svn_hash_gets(attrs, "altvalue")) != NULL)
256        {
257          val_str = svn_string_create(altvalue, scratch_pool);
258        }
259      else if ((encoding = svn_hash_gets(attrs, "V:encoding")) != NULL)
260        {
261          if (strcmp(encoding, "base64") != 0)
262            return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
263                                     NULL,
264                                     _("Got unrecognized encoding '%s'"),
265                                     encoding);
266
267          /* Decode into the right pool.  */
268          val_str = svn_base64_decode_string(cdata, scratch_pool);
269        }
270      else
271        {
272          /* Copy into the right pool.  */
273          val_str = cdata;
274        }
275
276      /* The current path sits on the RESPONSE state.
277
278         Now, it would be nice if we could, at this point, know that
279         the status code for this property indicated a problem -- then
280         we could simply bail out here and ignore the property.
281         Sadly, though, we might get the status code *after* we get
282         the property value.  So we'll carry on with our processing
283         here, setting the property and value as expected.  Once we
284         know for sure the status code associate with the property,
285         we'll decide its fate.  */
286
287      ns = svn_hash_gets(attrs, "ns");
288      name = svn_hash_gets(attrs, "name");
289
290      set_ns_prop(ctx->ps_props, ns, name, val_str,
291                  apr_hash_pool_get(ctx->ps_props));
292    }
293  else
294    {
295      apr_hash_t *gathered;
296
297      SVN_ERR_ASSERT(leaving_state == PROPSTAT);
298
299      gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE);
300
301      /* If we've squirreled away a note that says we want to ignore
302         these properties, we'll do so.  Otherwise, we need to copy
303         them from the temporary hash into the ctx->ret_props hash. */
304      if (! svn_hash_gets(gathered, "ignore-prop"))
305        {
306          apr_hash_index_t *hi_ns;
307          const char *path;
308          apr_pool_t *iterpool = svn_pool_create(scratch_pool);
309
310
311          path = svn_hash_gets(gathered, "path");
312          if (!path)
313            path = ctx->path;
314
315          for (hi_ns = apr_hash_first(scratch_pool, ctx->ps_props);
316               hi_ns;
317               hi_ns = apr_hash_next(hi_ns))
318            {
319              const char *ns = apr_hash_this_key(hi_ns);
320              apr_hash_t *props = apr_hash_this_val(hi_ns);
321              apr_hash_index_t *hi_prop;
322
323              svn_pool_clear(iterpool);
324
325              for (hi_prop = apr_hash_first(iterpool, props);
326                   hi_prop;
327                   hi_prop = apr_hash_next(hi_prop))
328                {
329                  const char *name = apr_hash_this_key(hi_prop);
330                  const svn_string_t *value = apr_hash_this_val(hi_prop);
331
332                  SVN_ERR(ctx->prop_func(ctx->prop_func_baton, path,
333                                         ns, name, value, iterpool));
334                }
335            }
336
337          svn_pool_destroy(iterpool);
338        }
339
340      ctx->ps_props = NULL; /* Allocated in PROPSTAT state pool */
341    }
342
343  return SVN_NO_ERROR;
344}
345
346
347
348static svn_error_t *
349setup_propfind_headers(serf_bucket_t *headers,
350                       void *setup_baton,
351                       apr_pool_t *pool /* request pool */,
352                       apr_pool_t *scratch_pool)
353{
354  propfind_context_t *ctx = setup_baton;
355
356  serf_bucket_headers_setn(headers, "Depth", ctx->depth);
357  if (ctx->label)
358    {
359      serf_bucket_headers_setn(headers, "Label", ctx->label);
360    }
361
362  return SVN_NO_ERROR;
363}
364
365#define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">"
366#define PROPFIND_TRAILER "</propfind>"
367
368/* Implements svn_ra_serf__request_body_delegate_t */
369static svn_error_t *
370create_propfind_body(serf_bucket_t **bkt,
371                     void *setup_baton,
372                     serf_bucket_alloc_t *alloc,
373                     apr_pool_t *pool /* request pool */,
374                     apr_pool_t *scratch_pool)
375{
376  propfind_context_t *ctx = setup_baton;
377
378  serf_bucket_t *body_bkt, *tmp;
379  const svn_ra_serf__dav_props_t *prop;
380  svn_boolean_t requested_allprop = FALSE;
381
382  body_bkt = serf_bucket_aggregate_create(alloc);
383
384  prop = ctx->find_props;
385  while (prop && prop->xmlns)
386    {
387      /* special case the allprop case. */
388      if (strcmp(prop->name, "allprop") == 0)
389        {
390          requested_allprop = TRUE;
391        }
392
393      /* <*propname* xmlns="*propns*" /> */
394      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
395      serf_bucket_aggregate_append(body_bkt, tmp);
396
397      tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
398      serf_bucket_aggregate_append(body_bkt, tmp);
399
400      tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
401                                          sizeof(" xmlns=\"")-1,
402                                          alloc);
403      serf_bucket_aggregate_append(body_bkt, tmp);
404
405      tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc);
406      serf_bucket_aggregate_append(body_bkt, tmp);
407
408      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
409                                          alloc);
410      serf_bucket_aggregate_append(body_bkt, tmp);
411
412      prop++;
413    }
414
415  /* If we're not doing an allprop, add <prop> tags. */
416  if (!requested_allprop)
417    {
418      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
419                                          sizeof("<prop>")-1,
420                                          alloc);
421      serf_bucket_aggregate_prepend(body_bkt, tmp);
422    }
423
424  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
425                                      sizeof(PROPFIND_HEADER)-1,
426                                      alloc);
427
428  serf_bucket_aggregate_prepend(body_bkt, tmp);
429
430  if (!requested_allprop)
431    {
432      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
433                                          sizeof("</prop>")-1,
434                                          alloc);
435      serf_bucket_aggregate_append(body_bkt, tmp);
436    }
437
438  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
439                                      sizeof(PROPFIND_TRAILER)-1,
440                                      alloc);
441  serf_bucket_aggregate_append(body_bkt, tmp);
442
443  *bkt = body_bkt;
444  return SVN_NO_ERROR;
445}
446
447
448svn_error_t *
449svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler,
450                                     svn_ra_serf__session_t *sess,
451                                     const char *path,
452                                     svn_revnum_t rev,
453                                     const char *depth,
454                                     const svn_ra_serf__dav_props_t *find_props,
455                                     svn_ra_serf__prop_func_t prop_func,
456                                     void *prop_func_baton,
457                                     apr_pool_t *pool)
458{
459  propfind_context_t *new_prop_ctx;
460  svn_ra_serf__handler_t *handler;
461  svn_ra_serf__xml_context_t *xmlctx;
462
463  new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
464
465  new_prop_ctx->path = path;
466  new_prop_ctx->find_props = find_props;
467  new_prop_ctx->prop_func = prop_func;
468  new_prop_ctx->prop_func_baton = prop_func_baton;
469  new_prop_ctx->depth = depth;
470
471  if (SVN_IS_VALID_REVNUM(rev))
472    {
473      new_prop_ctx->label = apr_ltoa(pool, rev);
474    }
475  else
476    {
477      new_prop_ctx->label = NULL;
478    }
479
480  xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
481                                           propfind_opened,
482                                           propfind_closed,
483                                           NULL,
484                                           new_prop_ctx,
485                                           pool);
486  handler = svn_ra_serf__create_expat_handler(sess, xmlctx,
487                                              propfind_expected_status,
488                                              pool);
489
490  handler->method = "PROPFIND";
491  handler->path = path;
492  handler->body_delegate = create_propfind_body;
493  handler->body_type = "text/xml";
494  handler->body_delegate_baton = new_prop_ctx;
495  handler->header_delegate = setup_propfind_headers;
496  handler->header_delegate_baton = new_prop_ctx;
497
498  handler->no_dav_headers = TRUE;
499
500  new_prop_ctx->handler = handler;
501
502  *propfind_handler = handler;
503
504  return SVN_NO_ERROR;
505}
506
507svn_error_t *
508svn_ra_serf__deliver_svn_props(void *baton,
509                               const char *path,
510                               const char *ns,
511                               const char *name,
512                               const svn_string_t *value,
513                               apr_pool_t *scratch_pool)
514{
515  apr_hash_t *props = baton;
516  apr_pool_t *result_pool = apr_hash_pool_get(props);
517  const char *prop_name;
518
519  prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
520  if (prop_name == NULL)
521    return SVN_NO_ERROR;
522
523  svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool));
524
525  return SVN_NO_ERROR;
526}
527
528/*
529 * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties
530 * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash
531 *    (const char * -> svn_string_t *) to the values.
532 */
533static svn_error_t *
534deliver_node_props(void *baton,
535                  const char *path,
536                  const char *ns,
537                  const char *name,
538                  const svn_string_t *value,
539                  apr_pool_t *scratch_pool)
540{
541  apr_hash_t *nss = baton;
542  apr_hash_t *props;
543  apr_pool_t *result_pool = apr_hash_pool_get(nss);
544
545  props = svn_hash_gets(nss, ns);
546
547  if (!props)
548    {
549      props = apr_hash_make(result_pool);
550
551      ns = apr_pstrdup(result_pool, ns);
552      svn_hash_sets(nss, ns, props);
553    }
554
555  name = apr_pstrdup(result_pool, name);
556  svn_hash_sets(props, name, svn_string_dup(value, result_pool));
557
558  return SVN_NO_ERROR;
559}
560
561svn_error_t *
562svn_ra_serf__fetch_node_props(apr_hash_t **results,
563                              svn_ra_serf__session_t *session,
564                              const char *url,
565                              svn_revnum_t revision,
566                              const svn_ra_serf__dav_props_t *which_props,
567                              apr_pool_t *result_pool,
568                              apr_pool_t *scratch_pool)
569{
570  apr_hash_t *props;
571  svn_ra_serf__handler_t *handler;
572
573  props = apr_hash_make(result_pool);
574
575  SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
576                                               url, revision, "0", which_props,
577                                               deliver_node_props,
578                                               props, scratch_pool));
579
580  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
581
582  *results = props;
583  return SVN_NO_ERROR;
584}
585
586const char *
587svn_ra_serf__svnname_from_wirename(const char *ns,
588                                   const char *name,
589                                   apr_pool_t *result_pool)
590{
591  if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
592    return apr_pstrdup(result_pool, name);
593
594  if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
595    return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
596
597  if (strcmp(ns, SVN_PROP_PREFIX) == 0)
598    return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
599
600  if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
601    return SVN_PROP_ENTRY_COMMITTED_REV;
602
603  if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
604    return SVN_PROP_ENTRY_COMMITTED_DATE;
605
606  if (strcmp(name, "creator-displayname") == 0)
607    return SVN_PROP_ENTRY_LAST_AUTHOR;
608
609  if (strcmp(name, "repository-uuid") == 0)
610    return SVN_PROP_ENTRY_UUID;
611
612  if (strcmp(name, "lock-token") == 0)
613    return SVN_PROP_ENTRY_LOCK_TOKEN;
614
615  if (strcmp(name, "checked-in") == 0)
616    return SVN_RA_SERF__WC_CHECKED_IN_URL;
617
618  if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
619    {
620      /* Here DAV: properties not yet converted to svn: properties should be
621         ignored. */
622      return NULL;
623    }
624
625  /* An unknown namespace, must be a custom property. */
626  return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL);
627}
628
629/*
630 * Contact the server (using CONN) to calculate baseline
631 * information for BASELINE_URL at REVISION (which may be
632 * SVN_INVALID_REVNUM to query the HEAD revision).
633 *
634 * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
635 * retrieved from the server as part of this process (which should
636 * match REVISION when REVISION is valid).  Set *BASECOLL_URL_P to the
637 * baseline collection URL.
638 */
639static svn_error_t *
640retrieve_baseline_info(svn_revnum_t *actual_revision,
641                       const char **basecoll_url_p,
642                       svn_ra_serf__session_t *session,
643                       const char *baseline_url,
644                       svn_revnum_t revision,
645                       apr_pool_t *result_pool,
646                       apr_pool_t *scratch_pool)
647{
648  apr_hash_t *props;
649  apr_hash_t *dav_props;
650  const char *basecoll_url;
651
652  SVN_ERR(svn_ra_serf__fetch_node_props(&props, session,
653                                        baseline_url, revision,
654                                        baseline_props,
655                                        scratch_pool, scratch_pool));
656  dav_props = apr_hash_get(props, "DAV:", 4);
657  /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL.  */
658
659  basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
660  if (!basecoll_url)
661    {
662      return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
663                              _("The PROPFIND response did not include "
664                                "the requested baseline-collection value"));
665    }
666  *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
667
668  if (actual_revision)
669    {
670      const char *version_name;
671
672      version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
673      if (version_name)
674        {
675          apr_int64_t rev;
676
677          SVN_ERR(svn_cstring_atoi64(&rev, version_name));
678          *actual_revision = (svn_revnum_t)rev;
679        }
680
681      if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision))
682        return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
683                                _("The PROPFIND response did not include "
684                                  "the requested version-name value"));
685    }
686
687  return SVN_NO_ERROR;
688}
689
690
691/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
692   revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
693   collection URL is also returned.
694
695   Do the work over CONN.
696
697   *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
698   temporary allocations will be made in SCRATCH_POOL.  */
699static svn_error_t *
700v1_get_youngest_revnum(svn_revnum_t *youngest,
701                       const char **basecoll_url,
702                       svn_ra_serf__session_t *session,
703                       const char *vcc_url,
704                       apr_pool_t *result_pool,
705                       apr_pool_t *scratch_pool)
706{
707  const char *baseline_url;
708  const char *bc_url;
709
710  /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
711     revision) will return the latest Baseline resource's URL.  */
712  SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url,
713                                      SVN_INVALID_REVNUM,
714                                      "checked-in",
715                                      scratch_pool, scratch_pool));
716  if (!baseline_url)
717    {
718      return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
719                              _("The OPTIONS response did not include "
720                                "the requested checked-in value"));
721    }
722  baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
723
724  /* From the Baseline resource, we can fetch the DAV:baseline-collection
725     and DAV:version-name properties. The latter is the revision number,
726     which is formally the name used in Label: headers.  */
727
728  /* First check baseline information cache. */
729  SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
730                                                  youngest,
731                                                  session->blncache,
732                                                  baseline_url,
733                                                  scratch_pool));
734  if (!bc_url)
735    {
736      SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session,
737                                     baseline_url, SVN_INVALID_REVNUM,
738                                     scratch_pool, scratch_pool));
739      SVN_ERR(svn_ra_serf__blncache_set(session->blncache,
740                                        baseline_url, *youngest,
741                                        bc_url, scratch_pool));
742    }
743
744  if (basecoll_url != NULL)
745    *basecoll_url = apr_pstrdup(result_pool, bc_url);
746
747  return SVN_NO_ERROR;
748}
749
750
751svn_error_t *
752svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
753                                 svn_ra_serf__session_t *session,
754                                 apr_pool_t *scratch_pool)
755{
756  const char *vcc_url;
757
758  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
759    return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
760                             youngest, session, scratch_pool));
761
762  SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
763
764  return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
765                                                session, vcc_url,
766                                                scratch_pool, scratch_pool));
767}
768
769
770/* Set *BC_URL to the baseline collection url for REVISION. If REVISION
771   is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
772
773   *REVNUM_USED will be set to the revision used.
774
775   Uses the specified CONN, which is part of SESSION.
776
777   All allocations (results and temporary) are performed in POOL.  */
778static svn_error_t *
779get_baseline_info(const char **bc_url,
780                  svn_revnum_t *revnum_used,
781                  svn_ra_serf__session_t *session,
782                  svn_revnum_t revision,
783                  apr_pool_t *result_pool,
784                  apr_pool_t *scratch_pool)
785{
786  /* If we detected HTTP v2 support on the server, we can construct
787     the baseline collection URL ourselves, and fetch the latest
788     revision (if needed) with an OPTIONS request.  */
789  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
790    {
791      if (SVN_IS_VALID_REVNUM(revision))
792        {
793          *revnum_used = revision;
794        }
795      else
796        {
797          SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
798                    revnum_used, session, scratch_pool));
799        }
800
801      *bc_url = apr_psprintf(result_pool, "%s/%ld",
802                             session->rev_root_stub, *revnum_used);
803    }
804
805  /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt.  */
806  else
807    {
808      const char *vcc_url;
809
810      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
811
812      if (SVN_IS_VALID_REVNUM(revision))
813        {
814          /* First check baseline information cache. */
815          SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
816                                                   session->blncache,
817                                                   revision, result_pool));
818          if (!*bc_url)
819            {
820              SVN_ERR(retrieve_baseline_info(NULL, bc_url, session,
821                                             vcc_url, revision,
822                                             result_pool, scratch_pool));
823              SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
824                                                revision, *bc_url,
825                                                scratch_pool));
826            }
827
828          *revnum_used = revision;
829        }
830      else
831        {
832          SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
833                                         session, vcc_url,
834                                         result_pool, scratch_pool));
835        }
836    }
837
838  return SVN_NO_ERROR;
839}
840
841
842svn_error_t *
843svn_ra_serf__get_stable_url(const char **stable_url,
844                            svn_revnum_t *latest_revnum,
845                            svn_ra_serf__session_t *session,
846                            const char *url,
847                            svn_revnum_t revision,
848                            apr_pool_t *result_pool,
849                            apr_pool_t *scratch_pool)
850{
851  const char *basecoll_url;
852  const char *repos_relpath;
853  svn_revnum_t revnum_used;
854
855  /* No URL? No sweat. We'll use the session URL.  */
856  if (! url)
857    url = session->session_url.path;
858
859  SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
860                            session, revision, scratch_pool, scratch_pool));
861  SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
862                                         session, scratch_pool));
863
864  *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
865                                            result_pool);
866  if (latest_revnum)
867    *latest_revnum = revnum_used;
868
869  return SVN_NO_ERROR;
870}
871
872
873svn_error_t *
874svn_ra_serf__fetch_dav_prop(const char **value,
875                            svn_ra_serf__session_t *session,
876                            const char *url,
877                            svn_revnum_t revision,
878                            const char *propname,
879                            apr_pool_t *result_pool,
880                            apr_pool_t *scratch_pool)
881{
882  apr_hash_t *props;
883  apr_hash_t *dav_props;
884
885  SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision,
886                                        checked_in_props,
887                                        scratch_pool, scratch_pool));
888  dav_props = apr_hash_get(props, "DAV:", 4);
889  if (dav_props == NULL)
890    return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
891                            _("The PROPFIND response did not include "
892                              "the requested 'DAV:' properties"));
893
894  /* We wouldn't get here if the resource was not found (404), so the
895     property should be present.
896
897     Note: it is okay to call apr_pstrdup() with NULL.  */
898  *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
899
900  return SVN_NO_ERROR;
901}
902
903/* Removes all non regular properties from PROPS */
904void
905svn_ra_serf__keep_only_regular_props(apr_hash_t *props,
906                                     apr_pool_t *scratch_pool)
907{
908  apr_hash_index_t *hi;
909
910  for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
911    {
912      const char *propname = apr_hash_this_key(hi);
913
914      if (svn_property_kind2(propname) != svn_prop_regular_kind)
915        svn_hash_sets(props, propname, NULL);
916    }
917}
918