]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_serf/options.c
Import DTS files for riscv from Linux 5.4
[FreeBSD/FreeBSD.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       if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF1, vals))
230         {
231           /* Use compressed svndiff1 format for servers that properly
232              advertise this capability (Subversion 1.10 and greater). */
233           session->supports_svndiff1 = TRUE;
234         }
235       if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LIST, vals))
236         {
237           svn_hash_sets(session->capabilities,
238                         SVN_RA_CAPABILITY_LIST, capability_yes);
239         }
240       if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF2, vals))
241         {
242           /* Same for svndiff2. */
243           session->supports_svndiff2 = TRUE;
244         }
245       if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PUT_RESULT_CHECKSUM, vals))
246         {
247           session->supports_put_result_checksum = TRUE;
248         }
249     }
250
251   /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
252   else if (!svn_ctype_casecmp(key[0], 'S')
253            && !svn_ctype_casecmp(key[1], 'V')
254            && !svn_ctype_casecmp(key[2], 'N'))
255     {
256       /* If we've not yet seen any information about supported POST
257          requests, we'll initialize the list/hash with "create-txn"
258          (which we know is supported by virtue of the server speaking
259          HTTPv2 at all. */
260       if (! session->supported_posts)
261         {
262           session->supported_posts = apr_hash_make(session->pool);
263           apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
264         }
265
266       if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
267         {
268           session->repos_root = session->session_url;
269           session->repos_root.path =
270             (char *)svn_fspath__canonicalize(val, session->pool);
271           session->repos_root_str =
272             svn_urlpath__canonicalize(
273                 apr_uri_unparse(session->pool, &session->repos_root, 0),
274                 session->pool);
275         }
276       else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
277         {
278 #ifdef SVN_DEBUG
279           char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
280
281           if (!(ignore_v2_env_var
282                 && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
283             session->me_resource = apr_pstrdup(session->pool, val);
284 #else
285           session->me_resource = apr_pstrdup(session->pool, val);
286 #endif
287         }
288       else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
289         {
290           session->rev_stub = apr_pstrdup(session->pool, val);
291         }
292       else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
293         {
294           session->rev_root_stub = apr_pstrdup(session->pool, val);
295         }
296       else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
297         {
298           session->txn_stub = apr_pstrdup(session->pool, val);
299         }
300       else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
301         {
302           session->txn_root_stub = apr_pstrdup(session->pool, val);
303         }
304       else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
305         {
306           session->vtxn_stub = apr_pstrdup(session->pool, val);
307         }
308       else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
309         {
310           session->vtxn_root_stub = apr_pstrdup(session->pool, val);
311         }
312       else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
313         {
314           session->uuid = apr_pstrdup(session->pool, val);
315         }
316       else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
317         {
318           opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
319         }
320       else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
321         {
322           session->server_allows_bulk = apr_pstrdup(session->pool, val);
323         }
324       else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
325         {
326           /* May contain multiple values, separated by commas. */
327           int i;
328           apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
329                                                        session->pool);
330
331           for (i = 0; i < vals->nelts; i++)
332             {
333               const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
334
335               svn_hash_sets(session->supported_posts, post_val, (void *)1);
336             }
337         }
338       else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
339         {
340           if (svn_cstring_casecmp(val, "yes") == 0)
341             {
342               svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
343                             capability_yes);
344             }
345           else if (svn_cstring_casecmp(val, "no") == 0)
346             {
347               svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
348                             capability_no);
349             }
350         }
351     }
352
353   return 0;
354 }
355
356
357 /* A custom serf_response_handler_t which is mostly a wrapper around
358    the expat-based response handler -- it just notices OPTIONS response
359    headers first, before handing off to the xml parser.
360    Implements svn_ra_serf__response_handler_t */
361 static svn_error_t *
362 options_response_handler(serf_request_t *request,
363                          serf_bucket_t *response,
364                          void *baton,
365                          apr_pool_t *pool)
366 {
367   options_context_t *opt_ctx = baton;
368
369   if (!opt_ctx->headers_processed)
370     {
371       svn_ra_serf__session_t *session = opt_ctx->session;
372       serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
373       serf_connection_t *conn;
374
375       /* Start out assuming all capabilities are unsupported. */
376       svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
377                     capability_no);
378       svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
379                     capability_no);
380       svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
381                     NULL);
382       svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
383                     capability_no);
384       svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
385                     capability_no);
386       svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
387                     capability_no);
388       svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
389                     capability_no);
390       svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
391                     capability_no);
392       svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LIST,
393                     capability_no);
394
395       /* Then see which ones we can discover. */
396       serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
397                              opt_ctx);
398
399       /* Assume mergeinfo capability unsupported, if didn't receive information
400          about server or repository mergeinfo capability. */
401       if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
402         svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
403                       capability_no);
404
405       /* Remember our latency. */
406       conn = serf_request_get_conn(request);
407       session->conn_latency = serf_connection_get_latency(conn);
408
409       opt_ctx->headers_processed = TRUE;
410     }
411
412   /* Execute the 'real' response handler to XML-parse the response body. */
413   return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
414 }
415
416
417 static svn_error_t *
418 create_options_req(options_context_t **opt_ctx,
419                    svn_ra_serf__session_t *session,
420                    apr_pool_t *pool)
421 {
422   options_context_t *new_ctx;
423   svn_ra_serf__xml_context_t *xmlctx;
424   svn_ra_serf__handler_t *handler;
425
426   new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
427   new_ctx->pool = pool;
428   new_ctx->session = session;
429
430   new_ctx->youngest_rev = SVN_INVALID_REVNUM;
431
432   xmlctx = svn_ra_serf__xml_context_create(options_ttable,
433                                            NULL, options_closed, NULL,
434                                            new_ctx,
435                                            pool);
436   handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
437
438   handler->method = "OPTIONS";
439   handler->path = session->session_url.path;
440   handler->body_delegate = create_options_body;
441   handler->body_type = "text/xml";
442
443   new_ctx->handler = handler;
444
445   new_ctx->inner_handler = handler->response_handler;
446   new_ctx->inner_baton = handler->response_baton;
447   handler->response_handler = options_response_handler;
448   handler->response_baton = new_ctx;
449
450   *opt_ctx = new_ctx;
451
452   return SVN_NO_ERROR;
453 }
454
455
456 svn_error_t *
457 svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
458                                     svn_ra_serf__session_t *session,
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   SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
466   SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
467
468   if (opt_ctx->handler->sline.code != 200)
469     return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
470
471   if (! SVN_IS_VALID_REVNUM(opt_ctx->youngest_rev))
472     return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
473                             _("The OPTIONS response did not include "
474                               "the youngest revision"));
475
476   *youngest = opt_ctx->youngest_rev;
477
478   return SVN_NO_ERROR;
479 }
480
481
482 svn_error_t *
483 svn_ra_serf__v1_get_activity_collection(const char **activity_url,
484                                         svn_ra_serf__session_t *session,
485                                         apr_pool_t *result_pool,
486                                         apr_pool_t *scratch_pool)
487 {
488   options_context_t *opt_ctx;
489
490   SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
491
492   if (session->activity_collection_url)
493     {
494       *activity_url = apr_pstrdup(result_pool,
495                                   session->activity_collection_url);
496       return SVN_NO_ERROR;
497     }
498
499   SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
500   SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
501
502   if (opt_ctx->handler->sline.code != 200)
503     return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
504
505   /* Cache the result. */
506   if (opt_ctx->activity_collection)
507     {
508       session->activity_collection_url =
509                     apr_pstrdup(session->pool, opt_ctx->activity_collection);
510     }
511   else
512     {
513       return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
514                               _("The OPTIONS response did not include the "
515                                 "requested activity-collection-set value"));
516     }
517
518   *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
519
520   return SVN_NO_ERROR;
521
522 }
523
524
525 \f
526 /** Capabilities exchange. */
527
528 svn_error_t *
529 svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
530                                    const char **corrected_url,
531                                    apr_pool_t *result_pool,
532                                    apr_pool_t *scratch_pool)
533 {
534   options_context_t *opt_ctx;
535
536   if (corrected_url)
537     *corrected_url = NULL;
538
539   /* This routine automatically fills in serf_sess->capabilities */
540   SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_pool));
541
542   opt_ctx->handler->no_fail_on_http_redirect_status = TRUE;
543
544   SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
545
546   /* If our caller cares about server redirections, and our response
547      carries such a thing, report as much.  We'll disregard ERR --
548      it's most likely just a complaint about the response body not
549      successfully parsing as XML or somesuch. */
550   if (corrected_url && (opt_ctx->handler->sline.code == 301))
551     {
552       if (!opt_ctx->handler->location || !*opt_ctx->handler->location)
553         {
554           return svn_error_create(
555                     SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
556                     _("Location header not set on redirect response"));
557         }
558       else if (svn_path_is_url(opt_ctx->handler->location))
559         {
560           *corrected_url = svn_uri_canonicalize(opt_ctx->handler->location,
561                                                 result_pool);
562         }
563       else
564         {
565           /* RFC1945 and RFC2616 state that the Location header's value
566              (from whence this CORRECTED_URL comes), if present, must be an
567              absolute URI.  But some Apache versions (those older than 2.2.11,
568              it seems) transmit only the path portion of the URI.
569              See issue #3775 for details. */
570
571           apr_uri_t corrected_URI = serf_sess->session_url;
572
573           corrected_URI.path = (char *)corrected_url;
574           *corrected_url = svn_uri_canonicalize(
575                               apr_uri_unparse(scratch_pool, &corrected_URI, 0),
576                               result_pool);
577         }
578
579       return SVN_NO_ERROR;
580     }
581   else if (opt_ctx->handler->sline.code >= 300
582            && opt_ctx->handler->sline.code < 399)
583     {
584       return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL,
585                                (opt_ctx->handler->sline.code == 301
586                                 ? _("Repository moved permanently to '%s'")
587                                 : _("Repository moved temporarily to '%s'")),
588                               opt_ctx->handler->location);
589     }
590
591   if (opt_ctx->handler->sline.code != 200)
592     return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
593
594   /* Opportunistically cache any reported activity URL.  (We don't
595      want to have to ask for this again later, potentially against an
596      unreadable commit anchor URL.)  */
597   if (opt_ctx->activity_collection)
598     {
599       serf_sess->activity_collection_url =
600         apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
601     }
602
603   return SVN_NO_ERROR;
604 }
605
606 /* Implements svn_ra_serf__request_body_delegate_t */
607 static svn_error_t *
608 create_simple_options_body(serf_bucket_t **body_bkt,
609                            void *baton,
610                            serf_bucket_alloc_t *alloc,
611                            apr_pool_t *pool /* request pool */,
612                            apr_pool_t *scratch_pool)
613 {
614   serf_bucket_t *body;
615   serf_bucket_t *s;
616
617   body = serf_bucket_aggregate_create(alloc);
618   svn_ra_serf__add_xml_header_buckets(body, alloc);
619
620   s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
621   serf_bucket_aggregate_append(body, s);
622
623   *body_bkt = body;
624   return SVN_NO_ERROR;
625 }
626
627
628 svn_error_t *
629 svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
630                          apr_pool_t *scratch_pool)
631 {
632   svn_ra_serf__handler_t *handler;
633
634   handler = svn_ra_serf__create_handler(serf_sess, scratch_pool);
635   handler->method = "OPTIONS";
636   handler->path = serf_sess->session_url.path;
637
638   /* We don't care about the response body, so discard it.  */
639   handler->response_handler = svn_ra_serf__handle_discard_body;
640
641   /* We need a simple body, in order to send it in chunked format.  */
642   handler->body_delegate = create_simple_options_body;
643   handler->no_fail_on_http_failure_status = TRUE;
644
645   /* No special headers.  */
646
647   SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
648   /* Some versions of nginx in reverse proxy mode will return 411. They want
649      a Content-Length header, rather than chunked requests. We can keep other
650      HTTP/1.1 features, but will disable the chunking.  */
651   if (handler->sline.code == 411)
652     {
653       serf_sess->using_chunked_requests = FALSE;
654
655       return SVN_NO_ERROR;
656     }
657   if (handler->sline.code != 200)
658     SVN_ERR(svn_ra_serf__unexpected_status(handler));
659
660   return SVN_NO_ERROR;
661 }
662
663
664 svn_error_t *
665 svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
666                             svn_boolean_t *has,
667                             const char *capability,
668                             apr_pool_t *pool)
669 {
670   svn_ra_serf__session_t *serf_sess = ra_session->priv;
671   const char *cap_result;
672
673   /* This capability doesn't rely on anything server side. */
674   if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
675     {
676       *has = TRUE;
677       return SVN_NO_ERROR;
678     }
679
680   cap_result = svn_hash_gets(serf_sess->capabilities, capability);
681
682   /* If any capability is unknown, they're all unknown, so ask. */
683   if (cap_result == NULL)
684     SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool, pool));
685
686   /* Try again, now that we've fetched the capabilities. */
687   cap_result = svn_hash_gets(serf_sess->capabilities, capability);
688
689   /* Some capabilities depend on the repository as well as the server. */
690   if (cap_result == capability_server_yes)
691     {
692       if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
693         {
694           /* Handle mergeinfo specially.  Mergeinfo depends on the
695              repository as well as the server, but the server routine
696              that answered our svn_ra_serf__exchange_capabilities() call above
697              didn't even know which repository we were interested in
698              -- it just told us whether the server supports mergeinfo.
699              If the answer was 'no', there's no point checking the
700              particular repository; but if it was 'yes', we still must
701              change it to 'no' iff the repository itself doesn't
702              support mergeinfo. */
703           svn_mergeinfo_catalog_t ignored;
704           svn_error_t *err;
705           apr_array_header_t *paths = apr_array_make(pool, 1,
706                                                      sizeof(char *));
707           APR_ARRAY_PUSH(paths, const char *) = "";
708
709           err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
710                                            svn_mergeinfo_explicit,
711                                            FALSE /* include_descendants */,
712                                            pool);
713
714           if (err)
715             {
716               if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
717                 {
718                   svn_error_clear(err);
719                   cap_result = capability_no;
720                 }
721               else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
722                 {
723                   /* Mergeinfo requests use relative paths, and
724                      anyway we're in r0, so this is a likely error,
725                      but it means the repository supports mergeinfo! */
726                   svn_error_clear(err);
727                   cap_result = capability_yes;
728                 }
729               else
730                 return svn_error_trace(err);
731             }
732           else
733             cap_result = capability_yes;
734
735           svn_hash_sets(serf_sess->capabilities,
736                         SVN_RA_CAPABILITY_MERGEINFO,  cap_result);
737         }
738       else
739         {
740           return svn_error_createf
741             (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
742              _("Don't know how to handle '%s' for capability '%s'"),
743              capability_server_yes, capability);
744         }
745     }
746
747   if (cap_result == capability_yes)
748     {
749       *has = TRUE;
750     }
751   else if (cap_result == capability_no)
752     {
753       *has = FALSE;
754     }
755   else if (cap_result == NULL)
756     {
757       return svn_error_createf
758         (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
759          _("Don't know anything about capability '%s'"), capability);
760     }
761   else  /* "can't happen" */
762     {
763       /* Well, let's hope it's a string. */
764       return svn_error_createf
765         (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
766          _("Attempt to fetch capability '%s' resulted in '%s'"),
767          capability, cap_result);
768     }
769
770   return SVN_NO_ERROR;
771 }