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