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