options.c revision 269847
1/*
2 * options.c :  entry point for OPTIONS RA functions 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 <apr_uri.h>
27
28#include <serf.h>
29
30#include "svn_dirent_uri.h"
31#include "svn_hash.h"
32#include "svn_pools.h"
33#include "svn_ra.h"
34#include "svn_dav.h"
35#include "svn_xml.h"
36
37#include "../libsvn_ra/ra_loader.h"
38#include "svn_private_config.h"
39#include "private/svn_fspath.h"
40
41#include "ra_serf.h"
42
43
44/* In a debug build, setting this environment variable to "yes" will force
45   the client to speak v1, even if the server is capable of speaking v2. */
46#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
47
48
49/*
50 * This enum represents the current state of our XML parsing for an OPTIONS.
51 */
52enum options_state_e {
53  INITIAL = 0,
54  OPTIONS,
55  ACTIVITY_COLLECTION,
56  HREF
57};
58
59typedef struct options_context_t {
60  /* pool to allocate memory from */
61  apr_pool_t *pool;
62
63  /* Have we extracted options values from the headers already?  */
64  svn_boolean_t headers_processed;
65
66  svn_ra_serf__session_t *session;
67  svn_ra_serf__connection_t *conn;
68  svn_ra_serf__handler_t *handler;
69
70  svn_ra_serf__response_handler_t inner_handler;
71  void *inner_baton;
72
73  const char *activity_collection;
74  svn_revnum_t youngest_rev;
75
76} options_context_t;
77
78#define D_ "DAV:"
79#define S_ SVN_XML_NAMESPACE
80static const svn_ra_serf__xml_transition_t options_ttable[] = {
81  { INITIAL, D_, "options-response", OPTIONS,
82    FALSE, { NULL }, FALSE },
83
84  { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
85    FALSE, { NULL }, FALSE },
86
87  { ACTIVITY_COLLECTION, D_, "href", HREF,
88    TRUE, { NULL }, TRUE },
89
90  { 0 }
91};
92
93
94/* Conforms to svn_ra_serf__xml_closed_t  */
95static svn_error_t *
96options_closed(svn_ra_serf__xml_estate_t *xes,
97               void *baton,
98               int leaving_state,
99               const svn_string_t *cdata,
100               apr_hash_t *attrs,
101               apr_pool_t *scratch_pool)
102{
103  options_context_t *opt_ctx = baton;
104
105  SVN_ERR_ASSERT(leaving_state == HREF);
106  SVN_ERR_ASSERT(cdata != NULL);
107
108  opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
109                                                           opt_ctx->pool);
110
111  return SVN_NO_ERROR;
112}
113
114
115static svn_error_t *
116create_options_body(serf_bucket_t **body_bkt,
117                    void *baton,
118                    serf_bucket_alloc_t *alloc,
119                    apr_pool_t *pool)
120{
121  serf_bucket_t *body;
122  body = serf_bucket_aggregate_create(alloc);
123  svn_ra_serf__add_xml_header_buckets(body, alloc);
124  svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
125                                    "xmlns:D", "DAV:",
126                                    NULL);
127  svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
128  svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
129
130  *body_bkt = body;
131  return SVN_NO_ERROR;
132}
133
134
135/* We use these static pointers so we can employ pointer comparison
136 * of our capabilities hash members instead of strcmp()ing all over
137 * the place.
138 */
139/* Both server and repository support the capability. */
140static const char *const capability_yes = "yes";
141/* Either server or repository does not support the capability. */
142static const char *const capability_no = "no";
143/* Server supports the capability, but don't yet know if repository does. */
144static const char *const capability_server_yes = "server-yes";
145
146
147/* This implements serf_bucket_headers_do_callback_fn_t.
148 */
149static int
150capabilities_headers_iterator_callback(void *baton,
151                                       const char *key,
152                                       const char *val)
153{
154  options_context_t *opt_ctx = baton;
155  svn_ra_serf__session_t *session = opt_ctx->session;
156
157  if (svn_cstring_casecmp(key, "dav") == 0)
158    {
159      /* Each header may contain multiple values, separated by commas, e.g.:
160           DAV: version-control,checkout,working-resource
161           DAV: merge,baseline,activity,version-controlled-collection
162           DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
163      apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
164                                                   opt_ctx->pool);
165
166      /* Right now we only have a few capabilities to detect, so just
167         seek for them directly.  This could be written slightly more
168         efficiently, but that wouldn't be worth it until we have many
169         more capabilities. */
170
171      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
172        {
173          svn_hash_sets(session->capabilities,
174                        SVN_RA_CAPABILITY_DEPTH, capability_yes);
175        }
176      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
177        {
178          /* The server doesn't know what repository we're referring
179             to, so it can't just say capability_yes. */
180          if (!svn_hash_gets(session->capabilities,
181                             SVN_RA_CAPABILITY_MERGEINFO))
182            {
183              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
184                            capability_server_yes);
185            }
186        }
187      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
188        {
189          svn_hash_sets(session->capabilities,
190                        SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
191        }
192      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
193        {
194          svn_hash_sets(session->capabilities,
195                        SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
196        }
197      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
198        {
199          svn_hash_sets(session->capabilities,
200                        SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
201        }
202      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
203        {
204          svn_hash_sets(session->capabilities,
205                        SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
206        }
207      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
208                                 vals))
209        {
210          svn_hash_sets(session->capabilities,
211                        SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
212                        capability_yes);
213        }
214      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
215        {
216          svn_hash_sets(session->capabilities,
217                        SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
218        }
219      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
220        {
221          session->supports_inline_props = TRUE;
222        }
223      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
224        {
225          session->supports_rev_rsrc_replay = TRUE;
226        }
227    }
228
229  /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
230  else if (strncmp(key, "SVN", 3) == 0)
231    {
232      /* If we've not yet seen any information about supported POST
233         requests, we'll initialize the list/hash with "create-txn"
234         (which we know is supported by virtue of the server speaking
235         HTTPv2 at all. */
236      if (! session->supported_posts)
237        {
238          session->supported_posts = apr_hash_make(session->pool);
239          apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
240        }
241
242      if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
243        {
244          session->repos_root = session->session_url;
245          session->repos_root.path =
246            (char *)svn_fspath__canonicalize(val, session->pool);
247          session->repos_root_str =
248            svn_urlpath__canonicalize(
249                apr_uri_unparse(session->pool, &session->repos_root, 0),
250                session->pool);
251        }
252      else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
253        {
254#ifdef SVN_DEBUG
255          char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
256
257          if (!(ignore_v2_env_var
258                && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
259            session->me_resource = apr_pstrdup(session->pool, val);
260#else
261          session->me_resource = apr_pstrdup(session->pool, val);
262#endif
263        }
264      else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
265        {
266          session->rev_stub = apr_pstrdup(session->pool, val);
267        }
268      else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
269        {
270          session->rev_root_stub = apr_pstrdup(session->pool, val);
271        }
272      else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
273        {
274          session->txn_stub = apr_pstrdup(session->pool, val);
275        }
276      else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
277        {
278          session->txn_root_stub = apr_pstrdup(session->pool, val);
279        }
280      else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
281        {
282          session->vtxn_stub = apr_pstrdup(session->pool, val);
283        }
284      else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
285        {
286          session->vtxn_root_stub = apr_pstrdup(session->pool, val);
287        }
288      else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
289        {
290          session->uuid = apr_pstrdup(session->pool, val);
291        }
292      else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
293        {
294          opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
295        }
296      else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
297        {
298          session->server_allows_bulk = apr_pstrdup(session->pool, val);
299        }
300      else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
301        {
302          /* May contain multiple values, separated by commas. */
303          int i;
304          apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
305                                                       session->pool);
306
307          for (i = 0; i < vals->nelts; i++)
308            {
309              const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
310
311              svn_hash_sets(session->supported_posts, post_val, (void *)1);
312            }
313        }
314      else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
315        {
316          if (svn_cstring_casecmp(val, "yes") == 0)
317            {
318              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
319                            capability_yes);
320            }
321          else if (svn_cstring_casecmp(val, "no") == 0)
322            {
323              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
324                            capability_no);
325            }
326        }
327    }
328
329  return 0;
330}
331
332
333/* A custom serf_response_handler_t which is mostly a wrapper around
334   the expat-based response handler -- it just notices OPTIONS response
335   headers first, before handing off to the xml parser.
336   Implements svn_ra_serf__response_handler_t */
337static svn_error_t *
338options_response_handler(serf_request_t *request,
339                         serf_bucket_t *response,
340                         void *baton,
341                         apr_pool_t *pool)
342{
343  options_context_t *opt_ctx = baton;
344
345  if (!opt_ctx->headers_processed)
346    {
347      svn_ra_serf__session_t *session = opt_ctx->session;
348      serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
349
350      /* Start out assuming all capabilities are unsupported. */
351      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
352                    capability_no);
353      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
354                    capability_no);
355      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
356                    NULL);
357      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
358                    capability_no);
359      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
360                    capability_no);
361      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
362                    capability_no);
363      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
364                    capability_no);
365      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
366                    capability_no);
367
368      /* Then see which ones we can discover. */
369      serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
370                             opt_ctx);
371
372      /* Assume mergeinfo capability unsupported, if didn't recieve information
373         about server or repository mergeinfo capability. */
374      if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
375        svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
376                      capability_no);
377
378      opt_ctx->headers_processed = TRUE;
379    }
380
381  /* Execute the 'real' response handler to XML-parse the response body. */
382  return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
383}
384
385
386static svn_error_t *
387create_options_req(options_context_t **opt_ctx,
388                   svn_ra_serf__session_t *session,
389                   svn_ra_serf__connection_t *conn,
390                   apr_pool_t *pool)
391{
392  options_context_t *new_ctx;
393  svn_ra_serf__xml_context_t *xmlctx;
394  svn_ra_serf__handler_t *handler;
395
396  new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
397  new_ctx->pool = pool;
398  new_ctx->session = session;
399  new_ctx->conn = conn;
400
401  new_ctx->youngest_rev = SVN_INVALID_REVNUM;
402
403  xmlctx = svn_ra_serf__xml_context_create(options_ttable,
404                                           NULL, options_closed, NULL,
405                                           new_ctx,
406                                           pool);
407  handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
408
409  handler->method = "OPTIONS";
410  handler->path = session->session_url.path;
411  handler->body_delegate = create_options_body;
412  handler->body_type = "text/xml";
413  handler->conn = conn;
414  handler->session = session;
415
416  new_ctx->handler = handler;
417
418  new_ctx->inner_handler = handler->response_handler;
419  new_ctx->inner_baton = handler->response_baton;
420  handler->response_handler = options_response_handler;
421  handler->response_baton = new_ctx;
422
423  *opt_ctx = new_ctx;
424
425  return SVN_NO_ERROR;
426}
427
428
429svn_error_t *
430svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
431                                    svn_ra_serf__connection_t *conn,
432                                    apr_pool_t *scratch_pool)
433{
434  svn_ra_serf__session_t *session = conn->session;
435  options_context_t *opt_ctx;
436
437  SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
438
439  SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
440  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
441  SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
442                                       opt_ctx->handler->path,
443                                       opt_ctx->handler->location));
444
445  *youngest = opt_ctx->youngest_rev;
446  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*youngest));
447
448  return SVN_NO_ERROR;
449}
450
451
452svn_error_t *
453svn_ra_serf__v1_get_activity_collection(const char **activity_url,
454                                        svn_ra_serf__connection_t *conn,
455                                        apr_pool_t *result_pool,
456                                        apr_pool_t *scratch_pool)
457{
458  svn_ra_serf__session_t *session = conn->session;
459  options_context_t *opt_ctx;
460
461  SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
462
463  SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
464  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
465
466  SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
467                                       opt_ctx->handler->path,
468                                       opt_ctx->handler->location));
469
470  *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
471
472  return SVN_NO_ERROR;
473
474}
475
476
477
478/** Capabilities exchange. */
479
480svn_error_t *
481svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
482                                   const char **corrected_url,
483                                   apr_pool_t *pool)
484{
485  options_context_t *opt_ctx;
486  svn_error_t *err;
487
488  /* This routine automatically fills in serf_sess->capabilities */
489  SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool));
490
491  err = svn_ra_serf__context_run_one(opt_ctx->handler, pool);
492
493  /* If our caller cares about server redirections, and our response
494     carries such a thing, report as much.  We'll disregard ERR --
495     it's most likely just a complaint about the response body not
496     successfully parsing as XML or somesuch. */
497  if (corrected_url && (opt_ctx->handler->sline.code == 301))
498    {
499      svn_error_clear(err);
500      *corrected_url = opt_ctx->handler->location;
501      return SVN_NO_ERROR;
502    }
503
504  SVN_ERR(svn_error_compose_create(
505              svn_ra_serf__error_on_status(opt_ctx->handler->sline,
506                                           serf_sess->session_url.path,
507                                           opt_ctx->handler->location),
508              err));
509
510  /* Opportunistically cache any reported activity URL.  (We don't
511     want to have to ask for this again later, potentially against an
512     unreadable commit anchor URL.)  */
513  if (opt_ctx->activity_collection)
514    {
515      serf_sess->activity_collection_url =
516        apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
517    }
518
519  return SVN_NO_ERROR;
520}
521
522
523static svn_error_t *
524create_simple_options_body(serf_bucket_t **body_bkt,
525                           void *baton,
526                           serf_bucket_alloc_t *alloc,
527                           apr_pool_t *pool)
528{
529  serf_bucket_t *body;
530  serf_bucket_t *s;
531
532  body = serf_bucket_aggregate_create(alloc);
533  svn_ra_serf__add_xml_header_buckets(body, alloc);
534
535  s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
536  serf_bucket_aggregate_append(body, s);
537
538  *body_bkt = body;
539  return SVN_NO_ERROR;
540}
541
542
543svn_error_t *
544svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
545                         apr_pool_t *scratch_pool)
546{
547  svn_ra_serf__handler_t *handler;
548
549  handler = apr_pcalloc(scratch_pool, sizeof(*handler));
550  handler->handler_pool = scratch_pool;
551  handler->method = "OPTIONS";
552  handler->path = serf_sess->session_url.path;
553  handler->conn = serf_sess->conns[0];
554  handler->session = serf_sess;
555
556  /* We don't care about the response body, so discard it.  */
557  handler->response_handler = svn_ra_serf__handle_discard_body;
558
559  /* We need a simple body, in order to send it in chunked format.  */
560  handler->body_delegate = create_simple_options_body;
561
562  /* No special headers.  */
563
564  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
565  /* Some versions of nginx in reverse proxy mode will return 411. They want
566     a Content-Length header, rather than chunked requests. We can keep other
567     HTTP/1.1 features, but will disable the chunking.  */
568  if (handler->sline.code == 411)
569    {
570      serf_sess->using_chunked_requests = FALSE;
571
572      return SVN_NO_ERROR;
573    }
574  SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
575                                       handler->path,
576                                       handler->location));
577
578  return SVN_NO_ERROR;
579}
580
581
582svn_error_t *
583svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
584                            svn_boolean_t *has,
585                            const char *capability,
586                            apr_pool_t *pool)
587{
588  svn_ra_serf__session_t *serf_sess = ra_session->priv;
589  const char *cap_result;
590
591  /* This capability doesn't rely on anything server side. */
592  if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
593    {
594      *has = TRUE;
595      return SVN_NO_ERROR;
596    }
597
598  cap_result = svn_hash_gets(serf_sess->capabilities, capability);
599
600  /* If any capability is unknown, they're all unknown, so ask. */
601  if (cap_result == NULL)
602    SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool));
603
604  /* Try again, now that we've fetched the capabilities. */
605  cap_result = svn_hash_gets(serf_sess->capabilities, capability);
606
607  /* Some capabilities depend on the repository as well as the server. */
608  if (cap_result == capability_server_yes)
609    {
610      if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
611        {
612          /* Handle mergeinfo specially.  Mergeinfo depends on the
613             repository as well as the server, but the server routine
614             that answered our svn_ra_serf__exchange_capabilities() call above
615             didn't even know which repository we were interested in
616             -- it just told us whether the server supports mergeinfo.
617             If the answer was 'no', there's no point checking the
618             particular repository; but if it was 'yes', we still must
619             change it to 'no' iff the repository itself doesn't
620             support mergeinfo. */
621          svn_mergeinfo_catalog_t ignored;
622          svn_error_t *err;
623          apr_array_header_t *paths = apr_array_make(pool, 1,
624                                                     sizeof(char *));
625          APR_ARRAY_PUSH(paths, const char *) = "";
626
627          err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
628                                           FALSE, FALSE, pool);
629
630          if (err)
631            {
632              if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
633                {
634                  svn_error_clear(err);
635                  cap_result = capability_no;
636                }
637              else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
638                {
639                  /* Mergeinfo requests use relative paths, and
640                     anyway we're in r0, so this is a likely error,
641                     but it means the repository supports mergeinfo! */
642                  svn_error_clear(err);
643                  cap_result = capability_yes;
644                }
645              else
646                return err;
647            }
648          else
649            cap_result = capability_yes;
650
651          svn_hash_sets(serf_sess->capabilities,
652                        SVN_RA_CAPABILITY_MERGEINFO,  cap_result);
653        }
654      else
655        {
656          return svn_error_createf
657            (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
658             _("Don't know how to handle '%s' for capability '%s'"),
659             capability_server_yes, capability);
660        }
661    }
662
663  if (cap_result == capability_yes)
664    {
665      *has = TRUE;
666    }
667  else if (cap_result == capability_no)
668    {
669      *has = FALSE;
670    }
671  else if (cap_result == NULL)
672    {
673      return svn_error_createf
674        (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
675         _("Don't know anything about capability '%s'"), capability);
676    }
677  else  /* "can't happen" */
678    {
679      /* Well, let's hope it's a string. */
680      return svn_error_createf
681        (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
682         _("Attempt to fetch capability '%s' resulted in '%s'"),
683         capability, cap_result);
684    }
685
686  return SVN_NO_ERROR;
687}
688