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      prop++;
394    }
395
396  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
397                                      sizeof(PROPFIND_HEADER)-1,
398                                      alloc);
399  serf_bucket_aggregate_append(body_bkt, tmp);
400
401  /* If we're not doing an allprop, add <prop> tags. */
402  if (!requested_allprop)
403    {
404      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
405                                          sizeof("<prop>")-1,
406                                          alloc);
407      serf_bucket_aggregate_append(body_bkt, tmp);
408    }
409
410  prop = ctx->find_props;
411  while (prop && prop->xmlns)
412    {
413      /* <*propname* xmlns="*propns*" /> */
414      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
415      serf_bucket_aggregate_append(body_bkt, tmp);
416
417      tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
418      serf_bucket_aggregate_append(body_bkt, tmp);
419
420      tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
421                                          sizeof(" xmlns=\"")-1,
422                                          alloc);
423      serf_bucket_aggregate_append(body_bkt, tmp);
424
425      tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc);
426      serf_bucket_aggregate_append(body_bkt, tmp);
427
428      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
429                                          alloc);
430      serf_bucket_aggregate_append(body_bkt, tmp);
431
432      prop++;
433    }
434
435  if (!requested_allprop)
436    {
437      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
438                                          sizeof("</prop>")-1,
439                                          alloc);
440      serf_bucket_aggregate_append(body_bkt, tmp);
441    }
442
443  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
444                                      sizeof(PROPFIND_TRAILER)-1,
445                                      alloc);
446  serf_bucket_aggregate_append(body_bkt, tmp);
447
448  *bkt = body_bkt;
449  return SVN_NO_ERROR;
450}
451
452
453svn_error_t *
454svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler,
455                                     svn_ra_serf__session_t *sess,
456                                     const char *path,
457                                     svn_revnum_t rev,
458                                     const char *depth,
459                                     const svn_ra_serf__dav_props_t *find_props,
460                                     svn_ra_serf__prop_func_t prop_func,
461                                     void *prop_func_baton,
462                                     apr_pool_t *pool)
463{
464  propfind_context_t *new_prop_ctx;
465  svn_ra_serf__handler_t *handler;
466  svn_ra_serf__xml_context_t *xmlctx;
467
468  new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
469
470  new_prop_ctx->path = path;
471  new_prop_ctx->find_props = find_props;
472  new_prop_ctx->prop_func = prop_func;
473  new_prop_ctx->prop_func_baton = prop_func_baton;
474  new_prop_ctx->depth = depth;
475
476  if (SVN_IS_VALID_REVNUM(rev))
477    {
478      new_prop_ctx->label = apr_ltoa(pool, rev);
479    }
480  else
481    {
482      new_prop_ctx->label = NULL;
483    }
484
485  xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
486                                           propfind_opened,
487                                           propfind_closed,
488                                           NULL,
489                                           new_prop_ctx,
490                                           pool);
491  handler = svn_ra_serf__create_expat_handler(sess, xmlctx,
492                                              propfind_expected_status,
493                                              pool);
494
495  handler->method = "PROPFIND";
496  handler->path = path;
497  handler->body_delegate = create_propfind_body;
498  handler->body_type = "text/xml";
499  handler->body_delegate_baton = new_prop_ctx;
500  handler->header_delegate = setup_propfind_headers;
501  handler->header_delegate_baton = new_prop_ctx;
502
503  handler->no_dav_headers = TRUE;
504
505  new_prop_ctx->handler = handler;
506
507  *propfind_handler = handler;
508
509  return SVN_NO_ERROR;
510}
511
512svn_error_t *
513svn_ra_serf__deliver_svn_props(void *baton,
514                               const char *path,
515                               const char *ns,
516                               const char *name,
517                               const svn_string_t *value,
518                               apr_pool_t *scratch_pool)
519{
520  apr_hash_t *props = baton;
521  apr_pool_t *result_pool = apr_hash_pool_get(props);
522  const char *prop_name;
523
524  prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
525  if (prop_name == NULL)
526    return SVN_NO_ERROR;
527
528  svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool));
529
530  return SVN_NO_ERROR;
531}
532
533/*
534 * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties
535 * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash
536 *    (const char * -> svn_string_t *) to the values.
537 */
538static svn_error_t *
539deliver_node_props(void *baton,
540                  const char *path,
541                  const char *ns,
542                  const char *name,
543                  const svn_string_t *value,
544                  apr_pool_t *scratch_pool)
545{
546  apr_hash_t *nss = baton;
547  apr_hash_t *props;
548  apr_pool_t *result_pool = apr_hash_pool_get(nss);
549
550  props = svn_hash_gets(nss, ns);
551
552  if (!props)
553    {
554      props = apr_hash_make(result_pool);
555
556      ns = apr_pstrdup(result_pool, ns);
557      svn_hash_sets(nss, ns, props);
558    }
559
560  name = apr_pstrdup(result_pool, name);
561  svn_hash_sets(props, name, svn_string_dup(value, result_pool));
562
563  return SVN_NO_ERROR;
564}
565
566svn_error_t *
567svn_ra_serf__fetch_node_props(apr_hash_t **results,
568                              svn_ra_serf__session_t *session,
569                              const char *url,
570                              svn_revnum_t revision,
571                              const svn_ra_serf__dav_props_t *which_props,
572                              apr_pool_t *result_pool,
573                              apr_pool_t *scratch_pool)
574{
575  apr_hash_t *props;
576  svn_ra_serf__handler_t *handler;
577
578  props = apr_hash_make(result_pool);
579
580  SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
581                                               url, revision, "0", which_props,
582                                               deliver_node_props,
583                                               props, scratch_pool));
584
585  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
586
587  *results = props;
588  return SVN_NO_ERROR;
589}
590
591const char *
592svn_ra_serf__svnname_from_wirename(const char *ns,
593                                   const char *name,
594                                   apr_pool_t *result_pool)
595{
596  if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
597    return apr_pstrdup(result_pool, name);
598
599  if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
600    return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
601
602  if (strcmp(ns, SVN_PROP_PREFIX) == 0)
603    return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
604
605  if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
606    return SVN_PROP_ENTRY_COMMITTED_REV;
607
608  if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
609    return SVN_PROP_ENTRY_COMMITTED_DATE;
610
611  if (strcmp(name, "creator-displayname") == 0)
612    return SVN_PROP_ENTRY_LAST_AUTHOR;
613
614  if (strcmp(name, "repository-uuid") == 0)
615    return SVN_PROP_ENTRY_UUID;
616
617  if (strcmp(name, "lock-token") == 0)
618    return SVN_PROP_ENTRY_LOCK_TOKEN;
619
620  if (strcmp(name, "checked-in") == 0)
621    return SVN_RA_SERF__WC_CHECKED_IN_URL;
622
623  if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
624    {
625      /* Here DAV: properties not yet converted to svn: properties should be
626         ignored. */
627      return NULL;
628    }
629
630  /* An unknown namespace, must be a custom property. */
631  return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL);
632}
633
634/*
635 * Contact the server (using CONN) to calculate baseline
636 * information for BASELINE_URL at REVISION (which may be
637 * SVN_INVALID_REVNUM to query the HEAD revision).
638 *
639 * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
640 * retrieved from the server as part of this process (which should
641 * match REVISION when REVISION is valid).  Set *BASECOLL_URL_P to the
642 * baseline collection URL.
643 */
644static svn_error_t *
645retrieve_baseline_info(svn_revnum_t *actual_revision,
646                       const char **basecoll_url_p,
647                       svn_ra_serf__session_t *session,
648                       const char *baseline_url,
649                       svn_revnum_t revision,
650                       apr_pool_t *result_pool,
651                       apr_pool_t *scratch_pool)
652{
653  apr_hash_t *props;
654  apr_hash_t *dav_props;
655  const char *basecoll_url;
656
657  SVN_ERR(svn_ra_serf__fetch_node_props(&props, session,
658                                        baseline_url, revision,
659                                        baseline_props,
660                                        scratch_pool, scratch_pool));
661  dav_props = apr_hash_get(props, "DAV:", 4);
662  /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL.  */
663
664  basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
665  if (!basecoll_url)
666    {
667      return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
668                              _("The PROPFIND response did not include "
669                                "the requested baseline-collection value"));
670    }
671  *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
672
673  if (actual_revision)
674    {
675      const char *version_name;
676
677      version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
678      if (version_name)
679        {
680          apr_int64_t rev;
681
682          SVN_ERR(svn_cstring_atoi64(&rev, version_name));
683          *actual_revision = (svn_revnum_t)rev;
684        }
685
686      if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision))
687        return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
688                                _("The PROPFIND response did not include "
689                                  "the requested version-name value"));
690    }
691
692  return SVN_NO_ERROR;
693}
694
695
696/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
697   revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
698   collection URL is also returned.
699
700   Do the work over CONN.
701
702   *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
703   temporary allocations will be made in SCRATCH_POOL.  */
704static svn_error_t *
705v1_get_youngest_revnum(svn_revnum_t *youngest,
706                       const char **basecoll_url,
707                       svn_ra_serf__session_t *session,
708                       const char *vcc_url,
709                       apr_pool_t *result_pool,
710                       apr_pool_t *scratch_pool)
711{
712  const char *baseline_url;
713  const char *bc_url;
714
715  /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
716     revision) will return the latest Baseline resource's URL.  */
717  SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url,
718                                      SVN_INVALID_REVNUM,
719                                      "checked-in",
720                                      scratch_pool, scratch_pool));
721  if (!baseline_url)
722    {
723      return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
724                              _("The OPTIONS response did not include "
725                                "the requested checked-in value"));
726    }
727  baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
728
729  /* From the Baseline resource, we can fetch the DAV:baseline-collection
730     and DAV:version-name properties. The latter is the revision number,
731     which is formally the name used in Label: headers.  */
732
733  /* First check baseline information cache. */
734  SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
735                                                  youngest,
736                                                  session->blncache,
737                                                  baseline_url,
738                                                  scratch_pool));
739  if (!bc_url)
740    {
741      SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session,
742                                     baseline_url, SVN_INVALID_REVNUM,
743                                     scratch_pool, scratch_pool));
744      SVN_ERR(svn_ra_serf__blncache_set(session->blncache,
745                                        baseline_url, *youngest,
746                                        bc_url, scratch_pool));
747    }
748
749  if (basecoll_url != NULL)
750    *basecoll_url = apr_pstrdup(result_pool, bc_url);
751
752  return SVN_NO_ERROR;
753}
754
755
756svn_error_t *
757svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
758                                 svn_ra_serf__session_t *session,
759                                 apr_pool_t *scratch_pool)
760{
761  const char *vcc_url;
762
763  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
764    return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
765                             youngest, session, scratch_pool));
766
767  SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
768
769  return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
770                                                session, vcc_url,
771                                                scratch_pool, scratch_pool));
772}
773
774
775/* Set *BC_URL to the baseline collection url for REVISION. If REVISION
776   is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
777
778   *REVNUM_USED will be set to the revision used.
779
780   Uses the specified CONN, which is part of SESSION.
781
782   All allocations (results and temporary) are performed in POOL.  */
783static svn_error_t *
784get_baseline_info(const char **bc_url,
785                  svn_revnum_t *revnum_used,
786                  svn_ra_serf__session_t *session,
787                  svn_revnum_t revision,
788                  apr_pool_t *result_pool,
789                  apr_pool_t *scratch_pool)
790{
791  /* If we detected HTTP v2 support on the server, we can construct
792     the baseline collection URL ourselves, and fetch the latest
793     revision (if needed) with an OPTIONS request.  */
794  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
795    {
796      if (SVN_IS_VALID_REVNUM(revision))
797        {
798          *revnum_used = revision;
799        }
800      else
801        {
802          SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
803                    revnum_used, session, scratch_pool));
804        }
805
806      *bc_url = apr_psprintf(result_pool, "%s/%ld",
807                             session->rev_root_stub, *revnum_used);
808    }
809
810  /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt.  */
811  else
812    {
813      const char *vcc_url;
814
815      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
816
817      if (SVN_IS_VALID_REVNUM(revision))
818        {
819          /* First check baseline information cache. */
820          SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
821                                                   session->blncache,
822                                                   revision, result_pool));
823          if (!*bc_url)
824            {
825              SVN_ERR(retrieve_baseline_info(NULL, bc_url, session,
826                                             vcc_url, revision,
827                                             result_pool, scratch_pool));
828              SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
829                                                revision, *bc_url,
830                                                scratch_pool));
831            }
832
833          *revnum_used = revision;
834        }
835      else
836        {
837          SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
838                                         session, vcc_url,
839                                         result_pool, scratch_pool));
840        }
841    }
842
843  return SVN_NO_ERROR;
844}
845
846
847svn_error_t *
848svn_ra_serf__get_stable_url(const char **stable_url,
849                            svn_revnum_t *latest_revnum,
850                            svn_ra_serf__session_t *session,
851                            const char *url,
852                            svn_revnum_t revision,
853                            apr_pool_t *result_pool,
854                            apr_pool_t *scratch_pool)
855{
856  const char *basecoll_url;
857  const char *repos_relpath;
858  svn_revnum_t revnum_used;
859
860  /* No URL? No sweat. We'll use the session URL.  */
861  if (! url)
862    url = session->session_url.path;
863
864  SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
865                            session, revision, scratch_pool, scratch_pool));
866  SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
867                                         session, scratch_pool));
868
869  *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
870                                            result_pool);
871  if (latest_revnum)
872    *latest_revnum = revnum_used;
873
874  return SVN_NO_ERROR;
875}
876
877
878svn_error_t *
879svn_ra_serf__fetch_dav_prop(const char **value,
880                            svn_ra_serf__session_t *session,
881                            const char *url,
882                            svn_revnum_t revision,
883                            const char *propname,
884                            apr_pool_t *result_pool,
885                            apr_pool_t *scratch_pool)
886{
887  apr_hash_t *props;
888  apr_hash_t *dav_props;
889
890  SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision,
891                                        checked_in_props,
892                                        scratch_pool, scratch_pool));
893  dav_props = apr_hash_get(props, "DAV:", 4);
894  if (dav_props == NULL)
895    return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
896                            _("The PROPFIND response did not include "
897                              "the requested 'DAV:' properties"));
898
899  /* We wouldn't get here if the resource was not found (404), so the
900     property should be present.
901
902     Note: it is okay to call apr_pstrdup() with NULL.  */
903  *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
904
905  return SVN_NO_ERROR;
906}
907
908/* Removes all non regular properties from PROPS */
909void
910svn_ra_serf__keep_only_regular_props(apr_hash_t *props,
911                                     apr_pool_t *scratch_pool)
912{
913  apr_hash_index_t *hi;
914
915  for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
916    {
917      const char *propname = apr_hash_this_key(hi);
918
919      if (svn_property_kind2(propname) != svn_prop_regular_kind)
920        svn_hash_sets(props, propname, NULL);
921    }
922}
923