1251881Speter/*
2251881Speter * options.c :  entry point for OPTIONS RA functions for ra_serf
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#include <apr_uri.h>
27251881Speter
28251881Speter#include <serf.h>
29251881Speter
30251881Speter#include "svn_dirent_uri.h"
31251881Speter#include "svn_hash.h"
32251881Speter#include "svn_pools.h"
33251881Speter#include "svn_ra.h"
34251881Speter#include "svn_dav.h"
35251881Speter#include "svn_xml.h"
36251881Speter
37251881Speter#include "../libsvn_ra/ra_loader.h"
38251881Speter#include "svn_private_config.h"
39251881Speter#include "private/svn_fspath.h"
40251881Speter
41251881Speter#include "ra_serf.h"
42251881Speter
43251881Speter
44251881Speter/* In a debug build, setting this environment variable to "yes" will force
45251881Speter   the client to speak v1, even if the server is capable of speaking v2. */
46251881Speter#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
47251881Speter
48251881Speter
49251881Speter/*
50251881Speter * This enum represents the current state of our XML parsing for an OPTIONS.
51251881Speter */
52251881Speterenum options_state_e {
53251881Speter  INITIAL = 0,
54251881Speter  OPTIONS,
55251881Speter  ACTIVITY_COLLECTION,
56251881Speter  HREF
57251881Speter};
58251881Speter
59251881Spetertypedef struct options_context_t {
60251881Speter  /* pool to allocate memory from */
61251881Speter  apr_pool_t *pool;
62251881Speter
63251881Speter  /* Have we extracted options values from the headers already?  */
64251881Speter  svn_boolean_t headers_processed;
65251881Speter
66251881Speter  svn_ra_serf__session_t *session;
67251881Speter  svn_ra_serf__connection_t *conn;
68251881Speter  svn_ra_serf__handler_t *handler;
69251881Speter
70251881Speter  svn_ra_serf__response_handler_t inner_handler;
71251881Speter  void *inner_baton;
72251881Speter
73251881Speter  const char *activity_collection;
74251881Speter  svn_revnum_t youngest_rev;
75251881Speter
76251881Speter} options_context_t;
77251881Speter
78251881Speter#define D_ "DAV:"
79251881Speter#define S_ SVN_XML_NAMESPACE
80251881Speterstatic const svn_ra_serf__xml_transition_t options_ttable[] = {
81251881Speter  { INITIAL, D_, "options-response", OPTIONS,
82251881Speter    FALSE, { NULL }, FALSE },
83251881Speter
84251881Speter  { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
85251881Speter    FALSE, { NULL }, FALSE },
86251881Speter
87251881Speter  { ACTIVITY_COLLECTION, D_, "href", HREF,
88251881Speter    TRUE, { NULL }, TRUE },
89251881Speter
90251881Speter  { 0 }
91251881Speter};
92251881Speter
93251881Speter
94251881Speter/* Conforms to svn_ra_serf__xml_closed_t  */
95251881Speterstatic svn_error_t *
96251881Speteroptions_closed(svn_ra_serf__xml_estate_t *xes,
97251881Speter               void *baton,
98251881Speter               int leaving_state,
99251881Speter               const svn_string_t *cdata,
100251881Speter               apr_hash_t *attrs,
101251881Speter               apr_pool_t *scratch_pool)
102251881Speter{
103251881Speter  options_context_t *opt_ctx = baton;
104251881Speter
105251881Speter  SVN_ERR_ASSERT(leaving_state == HREF);
106251881Speter  SVN_ERR_ASSERT(cdata != NULL);
107251881Speter
108251881Speter  opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
109251881Speter                                                           opt_ctx->pool);
110251881Speter
111251881Speter  return SVN_NO_ERROR;
112251881Speter}
113251881Speter
114251881Speter
115251881Speterstatic svn_error_t *
116251881Spetercreate_options_body(serf_bucket_t **body_bkt,
117251881Speter                    void *baton,
118251881Speter                    serf_bucket_alloc_t *alloc,
119251881Speter                    apr_pool_t *pool)
120251881Speter{
121251881Speter  serf_bucket_t *body;
122251881Speter  body = serf_bucket_aggregate_create(alloc);
123251881Speter  svn_ra_serf__add_xml_header_buckets(body, alloc);
124251881Speter  svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
125251881Speter                                    "xmlns:D", "DAV:",
126251881Speter                                    NULL);
127251881Speter  svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
128251881Speter  svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
129251881Speter
130251881Speter  *body_bkt = body;
131251881Speter  return SVN_NO_ERROR;
132251881Speter}
133251881Speter
134251881Speter
135251881Speter/* We use these static pointers so we can employ pointer comparison
136251881Speter * of our capabilities hash members instead of strcmp()ing all over
137251881Speter * the place.
138251881Speter */
139251881Speter/* Both server and repository support the capability. */
140251881Speterstatic const char *const capability_yes = "yes";
141251881Speter/* Either server or repository does not support the capability. */
142251881Speterstatic const char *const capability_no = "no";
143251881Speter/* Server supports the capability, but don't yet know if repository does. */
144251881Speterstatic const char *const capability_server_yes = "server-yes";
145251881Speter
146251881Speter
147251881Speter/* This implements serf_bucket_headers_do_callback_fn_t.
148251881Speter */
149251881Speterstatic int
150251881Spetercapabilities_headers_iterator_callback(void *baton,
151251881Speter                                       const char *key,
152251881Speter                                       const char *val)
153251881Speter{
154251881Speter  options_context_t *opt_ctx = baton;
155251881Speter  svn_ra_serf__session_t *session = opt_ctx->session;
156251881Speter
157251881Speter  if (svn_cstring_casecmp(key, "dav") == 0)
158251881Speter    {
159251881Speter      /* Each header may contain multiple values, separated by commas, e.g.:
160251881Speter           DAV: version-control,checkout,working-resource
161251881Speter           DAV: merge,baseline,activity,version-controlled-collection
162251881Speter           DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
163251881Speter      apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
164251881Speter                                                   opt_ctx->pool);
165251881Speter
166251881Speter      /* Right now we only have a few capabilities to detect, so just
167251881Speter         seek for them directly.  This could be written slightly more
168251881Speter         efficiently, but that wouldn't be worth it until we have many
169251881Speter         more capabilities. */
170251881Speter
171251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
172251881Speter        {
173251881Speter          svn_hash_sets(session->capabilities,
174251881Speter                        SVN_RA_CAPABILITY_DEPTH, capability_yes);
175251881Speter        }
176251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
177251881Speter        {
178251881Speter          /* The server doesn't know what repository we're referring
179251881Speter             to, so it can't just say capability_yes. */
180251881Speter          if (!svn_hash_gets(session->capabilities,
181251881Speter                             SVN_RA_CAPABILITY_MERGEINFO))
182251881Speter            {
183251881Speter              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
184251881Speter                            capability_server_yes);
185251881Speter            }
186251881Speter        }
187251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
188251881Speter        {
189251881Speter          svn_hash_sets(session->capabilities,
190251881Speter                        SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
191251881Speter        }
192251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
193251881Speter        {
194251881Speter          svn_hash_sets(session->capabilities,
195251881Speter                        SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
196251881Speter        }
197251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
198251881Speter        {
199251881Speter          svn_hash_sets(session->capabilities,
200251881Speter                        SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
201251881Speter        }
202251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
203251881Speter        {
204251881Speter          svn_hash_sets(session->capabilities,
205251881Speter                        SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
206251881Speter        }
207251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
208251881Speter                                 vals))
209251881Speter        {
210251881Speter          svn_hash_sets(session->capabilities,
211251881Speter                        SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
212251881Speter                        capability_yes);
213251881Speter        }
214251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
215251881Speter        {
216251881Speter          svn_hash_sets(session->capabilities,
217251881Speter                        SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
218251881Speter        }
219251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
220251881Speter        {
221251881Speter          session->supports_inline_props = TRUE;
222251881Speter        }
223251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
224251881Speter        {
225251881Speter          session->supports_rev_rsrc_replay = TRUE;
226251881Speter        }
227251881Speter    }
228251881Speter
229251881Speter  /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
230251881Speter  else if (strncmp(key, "SVN", 3) == 0)
231251881Speter    {
232251881Speter      /* If we've not yet seen any information about supported POST
233251881Speter         requests, we'll initialize the list/hash with "create-txn"
234251881Speter         (which we know is supported by virtue of the server speaking
235251881Speter         HTTPv2 at all. */
236251881Speter      if (! session->supported_posts)
237251881Speter        {
238251881Speter          session->supported_posts = apr_hash_make(session->pool);
239251881Speter          apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
240251881Speter        }
241251881Speter
242251881Speter      if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
243251881Speter        {
244251881Speter          session->repos_root = session->session_url;
245251881Speter          session->repos_root.path =
246251881Speter            (char *)svn_fspath__canonicalize(val, session->pool);
247251881Speter          session->repos_root_str =
248251881Speter            svn_urlpath__canonicalize(
249251881Speter                apr_uri_unparse(session->pool, &session->repos_root, 0),
250251881Speter                session->pool);
251251881Speter        }
252251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
253251881Speter        {
254251881Speter#ifdef SVN_DEBUG
255251881Speter          char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
256251881Speter
257251881Speter          if (!(ignore_v2_env_var
258251881Speter                && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
259251881Speter            session->me_resource = apr_pstrdup(session->pool, val);
260251881Speter#else
261251881Speter          session->me_resource = apr_pstrdup(session->pool, val);
262251881Speter#endif
263251881Speter        }
264251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
265251881Speter        {
266251881Speter          session->rev_stub = apr_pstrdup(session->pool, val);
267251881Speter        }
268251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
269251881Speter        {
270251881Speter          session->rev_root_stub = apr_pstrdup(session->pool, val);
271251881Speter        }
272251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
273251881Speter        {
274251881Speter          session->txn_stub = apr_pstrdup(session->pool, val);
275251881Speter        }
276251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
277251881Speter        {
278251881Speter          session->txn_root_stub = apr_pstrdup(session->pool, val);
279251881Speter        }
280251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
281251881Speter        {
282251881Speter          session->vtxn_stub = apr_pstrdup(session->pool, val);
283251881Speter        }
284251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
285251881Speter        {
286251881Speter          session->vtxn_root_stub = apr_pstrdup(session->pool, val);
287251881Speter        }
288251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
289251881Speter        {
290251881Speter          session->uuid = apr_pstrdup(session->pool, val);
291251881Speter        }
292251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
293251881Speter        {
294251881Speter          opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
295251881Speter        }
296251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
297251881Speter        {
298251881Speter          session->server_allows_bulk = apr_pstrdup(session->pool, val);
299251881Speter        }
300251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
301251881Speter        {
302251881Speter          /* May contain multiple values, separated by commas. */
303251881Speter          int i;
304251881Speter          apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
305251881Speter                                                       opt_ctx->pool);
306251881Speter
307251881Speter          for (i = 0; i < vals->nelts; i++)
308251881Speter            {
309251881Speter              const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
310251881Speter
311251881Speter              svn_hash_sets(session->supported_posts, post_val, (void *)1);
312251881Speter            }
313251881Speter        }
314251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
315251881Speter        {
316251881Speter          if (svn_cstring_casecmp(val, "yes") == 0)
317251881Speter            {
318251881Speter              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
319251881Speter                            capability_yes);
320251881Speter            }
321251881Speter          else if (svn_cstring_casecmp(val, "no") == 0)
322251881Speter            {
323251881Speter              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
324251881Speter                            capability_no);
325251881Speter            }
326251881Speter        }
327251881Speter    }
328251881Speter
329251881Speter  return 0;
330251881Speter}
331251881Speter
332251881Speter
333251881Speter/* A custom serf_response_handler_t which is mostly a wrapper around
334251881Speter   the expat-based response handler -- it just notices OPTIONS response
335251881Speter   headers first, before handing off to the xml parser.
336251881Speter   Implements svn_ra_serf__response_handler_t */
337251881Speterstatic svn_error_t *
338251881Speteroptions_response_handler(serf_request_t *request,
339251881Speter                         serf_bucket_t *response,
340251881Speter                         void *baton,
341251881Speter                         apr_pool_t *pool)
342251881Speter{
343251881Speter  options_context_t *opt_ctx = baton;
344251881Speter
345251881Speter  if (!opt_ctx->headers_processed)
346251881Speter    {
347251881Speter      svn_ra_serf__session_t *session = opt_ctx->session;
348251881Speter      serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
349251881Speter
350251881Speter      /* Start out assuming all capabilities are unsupported. */
351251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
352251881Speter                    capability_no);
353251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
354251881Speter                    capability_no);
355251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
356251881Speter                    NULL);
357251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
358251881Speter                    capability_no);
359251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
360251881Speter                    capability_no);
361251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
362251881Speter                    capability_no);
363251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
364251881Speter                    capability_no);
365253734Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
366253734Speter                    capability_no);
367251881Speter
368251881Speter      /* Then see which ones we can discover. */
369251881Speter      serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
370251881Speter                             opt_ctx);
371251881Speter
372251881Speter      /* Assume mergeinfo capability unsupported, if didn't recieve information
373251881Speter         about server or repository mergeinfo capability. */
374251881Speter      if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
375251881Speter        svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
376251881Speter                      capability_no);
377251881Speter
378251881Speter      opt_ctx->headers_processed = TRUE;
379251881Speter    }
380251881Speter
381251881Speter  /* Execute the 'real' response handler to XML-parse the response body. */
382251881Speter  return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
383251881Speter}
384251881Speter
385251881Speter
386251881Speterstatic svn_error_t *
387251881Spetercreate_options_req(options_context_t **opt_ctx,
388251881Speter                   svn_ra_serf__session_t *session,
389251881Speter                   svn_ra_serf__connection_t *conn,
390251881Speter                   apr_pool_t *pool)
391251881Speter{
392251881Speter  options_context_t *new_ctx;
393251881Speter  svn_ra_serf__xml_context_t *xmlctx;
394251881Speter  svn_ra_serf__handler_t *handler;
395251881Speter
396251881Speter  new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
397251881Speter  new_ctx->pool = pool;
398251881Speter  new_ctx->session = session;
399251881Speter  new_ctx->conn = conn;
400251881Speter
401251881Speter  new_ctx->youngest_rev = SVN_INVALID_REVNUM;
402251881Speter
403251881Speter  xmlctx = svn_ra_serf__xml_context_create(options_ttable,
404251881Speter                                           NULL, options_closed, NULL,
405251881Speter                                           new_ctx,
406251881Speter                                           pool);
407251881Speter  handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
408251881Speter
409251881Speter  handler->method = "OPTIONS";
410251881Speter  handler->path = session->session_url.path;
411251881Speter  handler->body_delegate = create_options_body;
412251881Speter  handler->body_type = "text/xml";
413251881Speter  handler->conn = conn;
414251881Speter  handler->session = session;
415251881Speter
416251881Speter  new_ctx->handler = handler;
417251881Speter
418251881Speter  new_ctx->inner_handler = handler->response_handler;
419251881Speter  new_ctx->inner_baton = handler->response_baton;
420251881Speter  handler->response_handler = options_response_handler;
421251881Speter  handler->response_baton = new_ctx;
422251881Speter
423251881Speter  *opt_ctx = new_ctx;
424251881Speter
425251881Speter  return SVN_NO_ERROR;
426251881Speter}
427251881Speter
428251881Speter
429251881Spetersvn_error_t *
430251881Spetersvn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
431251881Speter                                    svn_ra_serf__connection_t *conn,
432251881Speter                                    apr_pool_t *scratch_pool)
433251881Speter{
434251881Speter  svn_ra_serf__session_t *session = conn->session;
435251881Speter  options_context_t *opt_ctx;
436251881Speter
437251881Speter  SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
438251881Speter
439251881Speter  SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
440251881Speter  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
441253734Speter  SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
442251881Speter                                       opt_ctx->handler->path,
443251881Speter                                       opt_ctx->handler->location));
444251881Speter
445251881Speter  *youngest = opt_ctx->youngest_rev;
446253734Speter  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*youngest));
447251881Speter
448251881Speter  return SVN_NO_ERROR;
449251881Speter}
450251881Speter
451251881Speter
452251881Spetersvn_error_t *
453251881Spetersvn_ra_serf__v1_get_activity_collection(const char **activity_url,
454251881Speter                                        svn_ra_serf__connection_t *conn,
455251881Speter                                        apr_pool_t *result_pool,
456251881Speter                                        apr_pool_t *scratch_pool)
457251881Speter{
458251881Speter  svn_ra_serf__session_t *session = conn->session;
459251881Speter  options_context_t *opt_ctx;
460251881Speter
461251881Speter  SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
462251881Speter
463251881Speter  SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
464251881Speter  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
465251881Speter
466253734Speter  SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
467251881Speter                                       opt_ctx->handler->path,
468251881Speter                                       opt_ctx->handler->location));
469251881Speter
470251881Speter  *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
471251881Speter
472251881Speter  return SVN_NO_ERROR;
473251881Speter
474251881Speter}
475251881Speter
476251881Speter
477251881Speter
478251881Speter/** Capabilities exchange. */
479251881Speter
480251881Spetersvn_error_t *
481251881Spetersvn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
482251881Speter                                   const char **corrected_url,
483251881Speter                                   apr_pool_t *pool)
484251881Speter{
485251881Speter  options_context_t *opt_ctx;
486251881Speter  svn_error_t *err;
487251881Speter
488251881Speter  /* This routine automatically fills in serf_sess->capabilities */
489251881Speter  SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool));
490251881Speter
491251881Speter  err = svn_ra_serf__context_run_one(opt_ctx->handler, pool);
492251881Speter
493251881Speter  /* If our caller cares about server redirections, and our response
494251881Speter     carries such a thing, report as much.  We'll disregard ERR --
495251881Speter     it's most likely just a complaint about the response body not
496251881Speter     successfully parsing as XML or somesuch. */
497251881Speter  if (corrected_url && (opt_ctx->handler->sline.code == 301))
498251881Speter    {
499251881Speter      svn_error_clear(err);
500251881Speter      *corrected_url = opt_ctx->handler->location;
501251881Speter      return SVN_NO_ERROR;
502251881Speter    }
503251881Speter
504251881Speter  SVN_ERR(svn_error_compose_create(
505253734Speter              svn_ra_serf__error_on_status(opt_ctx->handler->sline,
506251881Speter                                           serf_sess->session_url.path,
507251881Speter                                           opt_ctx->handler->location),
508251881Speter              err));
509251881Speter
510251881Speter  /* Opportunistically cache any reported activity URL.  (We don't
511251881Speter     want to have to ask for this again later, potentially against an
512251881Speter     unreadable commit anchor URL.)  */
513251881Speter  if (opt_ctx->activity_collection)
514251881Speter    {
515251881Speter      serf_sess->activity_collection_url =
516251881Speter        apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
517251881Speter    }
518251881Speter
519251881Speter  return SVN_NO_ERROR;
520251881Speter}
521251881Speter
522251881Speter
523253734Speterstatic svn_error_t *
524253734Spetercreate_simple_options_body(serf_bucket_t **body_bkt,
525253734Speter                           void *baton,
526253734Speter                           serf_bucket_alloc_t *alloc,
527253734Speter                           apr_pool_t *pool)
528253734Speter{
529253734Speter  serf_bucket_t *body;
530253734Speter  serf_bucket_t *s;
531253734Speter
532253734Speter  body = serf_bucket_aggregate_create(alloc);
533253734Speter  svn_ra_serf__add_xml_header_buckets(body, alloc);
534253734Speter
535253734Speter  s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
536253734Speter  serf_bucket_aggregate_append(body, s);
537253734Speter
538253734Speter  *body_bkt = body;
539253734Speter  return SVN_NO_ERROR;
540253734Speter}
541253734Speter
542253734Speter
543251881Spetersvn_error_t *
544253734Spetersvn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
545253734Speter                         apr_pool_t *scratch_pool)
546253734Speter{
547253734Speter  svn_ra_serf__handler_t *handler;
548253734Speter
549253734Speter  handler = apr_pcalloc(scratch_pool, sizeof(*handler));
550253734Speter  handler->handler_pool = scratch_pool;
551253734Speter  handler->method = "OPTIONS";
552253734Speter  handler->path = serf_sess->session_url.path;
553253734Speter  handler->conn = serf_sess->conns[0];
554253734Speter  handler->session = serf_sess;
555253734Speter
556253734Speter  /* We don't care about the response body, so discard it.  */
557253734Speter  handler->response_handler = svn_ra_serf__handle_discard_body;
558253734Speter
559253734Speter  /* We need a simple body, in order to send it in chunked format.  */
560253734Speter  handler->body_delegate = create_simple_options_body;
561253734Speter
562253734Speter  /* No special headers.  */
563253734Speter
564253734Speter  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
565253734Speter  /* Some versions of nginx in reverse proxy mode will return 411. They want
566253734Speter     a Content-Length header, rather than chunked requests. We can keep other
567253734Speter     HTTP/1.1 features, but will disable the chunking.  */
568253734Speter  if (handler->sline.code == 411)
569253734Speter    {
570253734Speter      serf_sess->using_chunked_requests = FALSE;
571253734Speter
572253734Speter      return SVN_NO_ERROR;
573253734Speter    }
574253734Speter  SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
575253734Speter                                       handler->path,
576253734Speter                                       handler->location));
577253734Speter
578253734Speter  return SVN_NO_ERROR;
579253734Speter}
580253734Speter
581253734Speter
582253734Spetersvn_error_t *
583251881Spetersvn_ra_serf__has_capability(svn_ra_session_t *ra_session,
584251881Speter                            svn_boolean_t *has,
585251881Speter                            const char *capability,
586251881Speter                            apr_pool_t *pool)
587251881Speter{
588251881Speter  svn_ra_serf__session_t *serf_sess = ra_session->priv;
589251881Speter  const char *cap_result;
590251881Speter
591251881Speter  /* This capability doesn't rely on anything server side. */
592251881Speter  if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
593251881Speter    {
594251881Speter      *has = TRUE;
595251881Speter      return SVN_NO_ERROR;
596251881Speter    }
597251881Speter
598251881Speter  cap_result = svn_hash_gets(serf_sess->capabilities, capability);
599251881Speter
600251881Speter  /* If any capability is unknown, they're all unknown, so ask. */
601251881Speter  if (cap_result == NULL)
602251881Speter    SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool));
603251881Speter
604251881Speter  /* Try again, now that we've fetched the capabilities. */
605251881Speter  cap_result = svn_hash_gets(serf_sess->capabilities, capability);
606251881Speter
607251881Speter  /* Some capabilities depend on the repository as well as the server. */
608251881Speter  if (cap_result == capability_server_yes)
609251881Speter    {
610251881Speter      if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
611251881Speter        {
612251881Speter          /* Handle mergeinfo specially.  Mergeinfo depends on the
613251881Speter             repository as well as the server, but the server routine
614251881Speter             that answered our svn_ra_serf__exchange_capabilities() call above
615251881Speter             didn't even know which repository we were interested in
616251881Speter             -- it just told us whether the server supports mergeinfo.
617251881Speter             If the answer was 'no', there's no point checking the
618251881Speter             particular repository; but if it was 'yes', we still must
619251881Speter             change it to 'no' iff the repository itself doesn't
620251881Speter             support mergeinfo. */
621251881Speter          svn_mergeinfo_catalog_t ignored;
622251881Speter          svn_error_t *err;
623251881Speter          apr_array_header_t *paths = apr_array_make(pool, 1,
624251881Speter                                                     sizeof(char *));
625251881Speter          APR_ARRAY_PUSH(paths, const char *) = "";
626251881Speter
627251881Speter          err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
628251881Speter                                           FALSE, FALSE, pool);
629251881Speter
630251881Speter          if (err)
631251881Speter            {
632251881Speter              if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
633251881Speter                {
634251881Speter                  svn_error_clear(err);
635251881Speter                  cap_result = capability_no;
636251881Speter                }
637251881Speter              else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
638251881Speter                {
639251881Speter                  /* Mergeinfo requests use relative paths, and
640251881Speter                     anyway we're in r0, so this is a likely error,
641251881Speter                     but it means the repository supports mergeinfo! */
642251881Speter                  svn_error_clear(err);
643251881Speter                  cap_result = capability_yes;
644251881Speter                }
645251881Speter              else
646251881Speter                return err;
647251881Speter            }
648251881Speter          else
649251881Speter            cap_result = capability_yes;
650251881Speter
651251881Speter          svn_hash_sets(serf_sess->capabilities,
652251881Speter                        SVN_RA_CAPABILITY_MERGEINFO,  cap_result);
653251881Speter        }
654251881Speter      else
655251881Speter        {
656251881Speter          return svn_error_createf
657251881Speter            (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
658251881Speter             _("Don't know how to handle '%s' for capability '%s'"),
659251881Speter             capability_server_yes, capability);
660251881Speter        }
661251881Speter    }
662251881Speter
663251881Speter  if (cap_result == capability_yes)
664251881Speter    {
665251881Speter      *has = TRUE;
666251881Speter    }
667251881Speter  else if (cap_result == capability_no)
668251881Speter    {
669251881Speter      *has = FALSE;
670251881Speter    }
671251881Speter  else if (cap_result == NULL)
672251881Speter    {
673251881Speter      return svn_error_createf
674251881Speter        (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
675251881Speter         _("Don't know anything about capability '%s'"), capability);
676251881Speter    }
677251881Speter  else  /* "can't happen" */
678251881Speter    {
679251881Speter      /* Well, let's hope it's a string. */
680251881Speter      return svn_error_createf
681251881Speter        (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
682251881Speter         _("Attempt to fetch capability '%s' resulted in '%s'"),
683251881Speter         capability, cap_result);
684251881Speter    }
685251881Speter
686251881Speter  return SVN_NO_ERROR;
687251881Speter}
688