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