options.c revision 299742
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_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
51/*
52 * This enum represents the current state of our XML parsing for an OPTIONS.
53 */
54enum options_state_e {
55  INITIAL = XML_STATE_INITIAL,
56  OPTIONS,
57  ACTIVITY_COLLECTION,
58  HREF
59};
60
61typedef 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
81static 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  */
96static svn_error_t *
97options_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 */
116static svn_error_t *
117create_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. */
142static const char *const capability_yes = "yes";
143/* Either server or repository does not support the capability. */
144static const char *const capability_no = "no";
145/* Server supports the capability, but don't yet know if repository does. */
146static const char *const capability_server_yes = "server-yes";
147
148
149/* This implements serf_bucket_headers_do_callback_fn_t.
150 */
151static int
152capabilities_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 */
341static svn_error_t *
342options_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
390static svn_error_t *
391create_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
429svn_error_t *
430svn_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
455svn_error_t *
456svn_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
499/** Capabilities exchange. */
500
501svn_error_t *
502svn_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 */
580static svn_error_t *
581create_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
601svn_error_t *
602svn_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
637svn_error_t *
638svn_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}
745