]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_ra_serf/options.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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
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 \f
49 /*
50  * This enum represents the current state of our XML parsing for an OPTIONS.
51  */
52 enum options_state_e {
53   INITIAL = 0,
54   OPTIONS,
55   ACTIVITY_COLLECTION,
56   HREF
57 };
58
59 typedef 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
80 static 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  */
95 static svn_error_t *
96 options_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
115 static svn_error_t *
116 create_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. */
140 static const char *const capability_yes = "yes";
141 /* Either server or repository does not support the capability. */
142 static const char *const capability_no = "no";
143 /* Server supports the capability, but don't yet know if repository does. */
144 static const char *const capability_server_yes = "server-yes";
145
146
147 /* This implements serf_bucket_headers_do_callback_fn_t.
148  */
149 static int
150 capabilities_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                                                        opt_ctx->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 */
337 static svn_error_t *
338 options_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
386 static svn_error_t *
387 create_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
429 svn_error_t *
430 svn_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
452 svn_error_t *
453 svn_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 \f
478 /** Capabilities exchange. */
479
480 svn_error_t *
481 svn_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
523 static svn_error_t *
524 create_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
543 svn_error_t *
544 svn_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
582 svn_error_t *
583 svn_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 }