options.c revision 289166
1177633Sdfr/*
2177633Sdfr * options.c :  entry point for OPTIONS RA functions for ra_serf
3177633Sdfr *
4177633Sdfr * ====================================================================
5177633Sdfr *    Licensed to the Apache Software Foundation (ASF) under one
6177633Sdfr *    or more contributor license agreements.  See the NOTICE file
7177633Sdfr *    distributed with this work for additional information
8177633Sdfr *    regarding copyright ownership.  The ASF licenses this file
9177633Sdfr *    to you under the Apache License, Version 2.0 (the
10177633Sdfr *    "License"); you may not use this file except in compliance
11177633Sdfr *    with the License.  You may obtain a copy of the License at
12177633Sdfr *
13177633Sdfr *      http://www.apache.org/licenses/LICENSE-2.0
14177633Sdfr *
15177633Sdfr *    Unless required by applicable law or agreed to in writing,
16177633Sdfr *    software distributed under the License is distributed on an
17177633Sdfr *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18177633Sdfr *    KIND, either express or implied.  See the License for the
19177633Sdfr *    specific language governing permissions and limitations
20177633Sdfr *    under the License.
21177633Sdfr * ====================================================================
22177633Sdfr */
23177633Sdfr
24177633Sdfr
25177633Sdfr
26177633Sdfr#include <apr_uri.h>
27177633Sdfr
28177633Sdfr#include <serf.h>
29177633Sdfr
30177633Sdfr#include "svn_dirent_uri.h"
31177633Sdfr#include "svn_hash.h"
32177633Sdfr#include "svn_pools.h"
33177633Sdfr#include "svn_ra.h"
34177633Sdfr#include "svn_dav.h"
35177633Sdfr#include "svn_xml.h"
36177633Sdfr#include "svn_ctype.h"
37177633Sdfr
38177633Sdfr#include "../libsvn_ra/ra_loader.h"
39177633Sdfr#include "svn_private_config.h"
40177633Sdfr#include "private/svn_fspath.h"
41177633Sdfr
42177633Sdfr#include "ra_serf.h"
43177633Sdfr
44177633Sdfr
45177633Sdfr/* In a debug build, setting this environment variable to "yes" will force
46177633Sdfr   the client to speak v1, even if the server is capable of speaking v2. */
47177633Sdfr#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
48177633Sdfr
49177633Sdfr
50177633Sdfr/*
51177633Sdfr * This enum represents the current state of our XML parsing for an OPTIONS.
52177633Sdfr */
53177633Sdfrenum options_state_e {
54177633Sdfr  INITIAL = 0,
55177633Sdfr  OPTIONS,
56177633Sdfr  ACTIVITY_COLLECTION,
57177633Sdfr  HREF
58177633Sdfr};
59177633Sdfr
60177633Sdfrtypedef struct options_context_t {
61177633Sdfr  /* pool to allocate memory from */
62177633Sdfr  apr_pool_t *pool;
63177633Sdfr
64177633Sdfr  /* Have we extracted options values from the headers already?  */
65180025Sdfr  svn_boolean_t headers_processed;
66177633Sdfr
67177633Sdfr  svn_ra_serf__session_t *session;
68177633Sdfr  svn_ra_serf__connection_t *conn;
69177633Sdfr  svn_ra_serf__handler_t *handler;
70177633Sdfr
71177633Sdfr  svn_ra_serf__response_handler_t inner_handler;
72177633Sdfr  void *inner_baton;
73177633Sdfr
74177633Sdfr  const char *activity_collection;
75177633Sdfr  svn_revnum_t youngest_rev;
76177633Sdfr
77177633Sdfr} options_context_t;
78177633Sdfr
79177633Sdfr#define D_ "DAV:"
80177633Sdfr#define S_ SVN_XML_NAMESPACE
81177633Sdfrstatic const svn_ra_serf__xml_transition_t options_ttable[] = {
82177633Sdfr  { INITIAL, D_, "options-response", OPTIONS,
83177633Sdfr    FALSE, { NULL }, FALSE },
84177633Sdfr
85177633Sdfr  { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
86177633Sdfr    FALSE, { NULL }, FALSE },
87177633Sdfr
88177633Sdfr  { ACTIVITY_COLLECTION, D_, "href", HREF,
89177633Sdfr    TRUE, { NULL }, TRUE },
90177633Sdfr
91177633Sdfr  { 0 }
92177633Sdfr};
93177633Sdfr
94177633Sdfr
95177633Sdfr/* Conforms to svn_ra_serf__xml_closed_t  */
96177633Sdfrstatic svn_error_t *
97177633Sdfroptions_closed(svn_ra_serf__xml_estate_t *xes,
98177633Sdfr               void *baton,
99177633Sdfr               int leaving_state,
100177633Sdfr               const svn_string_t *cdata,
101177633Sdfr               apr_hash_t *attrs,
102177633Sdfr               apr_pool_t *scratch_pool)
103177633Sdfr{
104177633Sdfr  options_context_t *opt_ctx = baton;
105177633Sdfr
106177633Sdfr  SVN_ERR_ASSERT(leaving_state == HREF);
107177633Sdfr  SVN_ERR_ASSERT(cdata != NULL);
108177633Sdfr
109177633Sdfr  opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
110177633Sdfr                                                           opt_ctx->pool);
111177633Sdfr
112177633Sdfr  return SVN_NO_ERROR;
113180025Sdfr}
114180025Sdfr
115180025Sdfr
116180025Sdfrstatic svn_error_t *
117180025Sdfrcreate_options_body(serf_bucket_t **body_bkt,
118180025Sdfr                    void *baton,
119177633Sdfr                    serf_bucket_alloc_t *alloc,
120177633Sdfr                    apr_pool_t *pool)
121184588Sdfr{
122184588Sdfr  serf_bucket_t *body;
123184588Sdfr  body = serf_bucket_aggregate_create(alloc);
124184588Sdfr  svn_ra_serf__add_xml_header_buckets(body, alloc);
125184588Sdfr  svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
126184588Sdfr                                    "xmlns:D", "DAV:",
127184588Sdfr                                    NULL);
128184588Sdfr  svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
129184588Sdfr  svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
130180025Sdfr
131180025Sdfr  *body_bkt = body;
132180025Sdfr  return SVN_NO_ERROR;
133180025Sdfr}
134180025Sdfr
135180025Sdfr
136180025Sdfr/* We use these static pointers so we can employ pointer comparison
137184588Sdfr * of our capabilities hash members instead of strcmp()ing all over
138184588Sdfr * the place.
139180025Sdfr */
140180025Sdfr/* Both server and repository support the capability. */
141180025Sdfrstatic const char *const capability_yes = "yes";
142180025Sdfr/* Either server or repository does not support the capability. */
143177633Sdfrstatic const char *const capability_no = "no";
144177633Sdfr/* Server supports the capability, but don't yet know if repository does. */
145177633Sdfrstatic const char *const capability_server_yes = "server-yes";
146177633Sdfr
147177633Sdfr
148180025Sdfr/* This implements serf_bucket_headers_do_callback_fn_t.
149180025Sdfr */
150177633Sdfrstatic int
151177633Sdfrcapabilities_headers_iterator_callback(void *baton,
152177633Sdfr                                       const char *key,
153177633Sdfr                                       const char *val)
154184588Sdfr{
155184588Sdfr  options_context_t *opt_ctx = baton;
156177633Sdfr  svn_ra_serf__session_t *session = opt_ctx->session;
157177633Sdfr
158177633Sdfr  if (svn_cstring_casecmp(key, "dav") == 0)
159177633Sdfr    {
160177633Sdfr      /* Each header may contain multiple values, separated by commas, e.g.:
161177633Sdfr           DAV: version-control,checkout,working-resource
162177633Sdfr           DAV: merge,baseline,activity,version-controlled-collection
163177633Sdfr           DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
164184588Sdfr      apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
165184588Sdfr                                                   opt_ctx->pool);
166177633Sdfr
167177633Sdfr      /* Right now we only have a few capabilities to detect, so just
168177633Sdfr         seek for them directly.  This could be written slightly more
169177633Sdfr         efficiently, but that wouldn't be worth it until we have many
170177633Sdfr         more capabilities. */
171177633Sdfr
172180025Sdfr      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
173180025Sdfr        {
174180025Sdfr          svn_hash_sets(session->capabilities,
175180025Sdfr                        SVN_RA_CAPABILITY_DEPTH, capability_yes);
176180025Sdfr        }
177180025Sdfr      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
178180025Sdfr        {
179180025Sdfr          /* The server doesn't know what repository we're referring
180180025Sdfr             to, so it can't just say capability_yes. */
181180025Sdfr          if (!svn_hash_gets(session->capabilities,
182180025Sdfr                             SVN_RA_CAPABILITY_MERGEINFO))
183180025Sdfr            {
184180025Sdfr              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
185180025Sdfr                            capability_server_yes);
186180025Sdfr            }
187180025Sdfr        }
188180025Sdfr      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
189180025Sdfr        {
190180025Sdfr          svn_hash_sets(session->capabilities,
191180025Sdfr                        SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
192180025Sdfr        }
193180025Sdfr      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
194177633Sdfr        {
195177633Sdfr          svn_hash_sets(session->capabilities,
196177633Sdfr                        SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
197177633Sdfr        }
198177633Sdfr      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
199177633Sdfr        {
200177633Sdfr          svn_hash_sets(session->capabilities,
201177633Sdfr                        SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
202180025Sdfr        }
203180025Sdfr      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
204180025Sdfr        {
205180025Sdfr          svn_hash_sets(session->capabilities,
206177633Sdfr                        SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
207177633Sdfr        }
208177633Sdfr      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
209177633Sdfr                                 vals))
210177633Sdfr        {
211177633Sdfr          svn_hash_sets(session->capabilities,
212177633Sdfr                        SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
213177633Sdfr                        capability_yes);
214177633Sdfr        }
215177633Sdfr      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
216177633Sdfr        {
217177633Sdfr          svn_hash_sets(session->capabilities,
218177633Sdfr                        SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
219177633Sdfr        }
220177633Sdfr      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
221180025Sdfr        {
222180025Sdfr          session->supports_inline_props = TRUE;
223180025Sdfr        }
224180025Sdfr      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
225180025Sdfr        {
226180025Sdfr          session->supports_rev_rsrc_replay = TRUE;
227180025Sdfr        }
228177633Sdfr    }
229184588Sdfr
230184588Sdfr  /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
231184588Sdfr  else if (!svn_ctype_casecmp(key[0], 'S')
232184588Sdfr           && !svn_ctype_casecmp(key[1], 'V')
233184588Sdfr           && !svn_ctype_casecmp(key[2], 'N'))
234184588Sdfr    {
235184588Sdfr      /* If we've not yet seen any information about supported POST
236184588Sdfr         requests, we'll initialize the list/hash with "create-txn"
237184588Sdfr         (which we know is supported by virtue of the server speaking
238184588Sdfr         HTTPv2 at all. */
239177633Sdfr      if (! session->supported_posts)
240184588Sdfr        {
241184588Sdfr          session->supported_posts = apr_hash_make(session->pool);
242184588Sdfr          apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
243184588Sdfr        }
244184588Sdfr
245184588Sdfr      if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
246184588Sdfr        {
247184588Sdfr          session->repos_root = session->session_url;
248184588Sdfr          session->repos_root.path =
249184588Sdfr            (char *)svn_fspath__canonicalize(val, session->pool);
250184588Sdfr          session->repos_root_str =
251184588Sdfr            svn_urlpath__canonicalize(
252184588Sdfr                apr_uri_unparse(session->pool, &session->repos_root, 0),
253184588Sdfr                session->pool);
254184588Sdfr        }
255184588Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
256180025Sdfr        {
257180025Sdfr#ifdef SVN_DEBUG
258180025Sdfr          char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
259180025Sdfr
260180025Sdfr          if (!(ignore_v2_env_var
261180025Sdfr                && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
262180025Sdfr            session->me_resource = apr_pstrdup(session->pool, val);
263180025Sdfr#else
264180025Sdfr          session->me_resource = apr_pstrdup(session->pool, val);
265180025Sdfr#endif
266180025Sdfr        }
267184588Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
268184588Sdfr        {
269180025Sdfr          session->rev_stub = apr_pstrdup(session->pool, val);
270180025Sdfr        }
271180025Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
272180025Sdfr        {
273177633Sdfr          session->rev_root_stub = apr_pstrdup(session->pool, val);
274177633Sdfr        }
275177633Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
276177633Sdfr        {
277177633Sdfr          session->txn_stub = apr_pstrdup(session->pool, val);
278177633Sdfr        }
279177633Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
280177633Sdfr        {
281177633Sdfr          session->txn_root_stub = apr_pstrdup(session->pool, val);
282180025Sdfr        }
283184588Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
284184588Sdfr        {
285184588Sdfr          session->vtxn_stub = apr_pstrdup(session->pool, val);
286184588Sdfr        }
287184588Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
288184588Sdfr        {
289180025Sdfr          session->vtxn_root_stub = apr_pstrdup(session->pool, val);
290180025Sdfr        }
291180025Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
292180025Sdfr        {
293180025Sdfr          session->uuid = apr_pstrdup(session->pool, val);
294180025Sdfr        }
295180025Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
296180025Sdfr        {
297177633Sdfr          opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
298177633Sdfr        }
299177633Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
300177633Sdfr        {
301177633Sdfr          session->server_allows_bulk = apr_pstrdup(session->pool, val);
302177633Sdfr        }
303177633Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
304177633Sdfr        {
305177633Sdfr          /* May contain multiple values, separated by commas. */
306177633Sdfr          int i;
307177633Sdfr          apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
308177633Sdfr                                                       session->pool);
309177633Sdfr
310177633Sdfr          for (i = 0; i < vals->nelts; i++)
311177633Sdfr            {
312177633Sdfr              const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
313177633Sdfr
314177633Sdfr              svn_hash_sets(session->supported_posts, post_val, (void *)1);
315177633Sdfr            }
316177633Sdfr        }
317177633Sdfr      else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
318177633Sdfr        {
319177633Sdfr          if (svn_cstring_casecmp(val, "yes") == 0)
320177633Sdfr            {
321177633Sdfr              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
322177633Sdfr                            capability_yes);
323177633Sdfr            }
324177633Sdfr          else if (svn_cstring_casecmp(val, "no") == 0)
325177633Sdfr            {
326177633Sdfr              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
327177633Sdfr                            capability_no);
328177633Sdfr            }
329177633Sdfr        }
330177633Sdfr    }
331177633Sdfr
332177633Sdfr  return 0;
333177633Sdfr}
334177633Sdfr
335177633Sdfr
336177633Sdfr/* A custom serf_response_handler_t which is mostly a wrapper around
337177633Sdfr   the expat-based response handler -- it just notices OPTIONS response
338177633Sdfr   headers first, before handing off to the xml parser.
339177633Sdfr   Implements svn_ra_serf__response_handler_t */
340177633Sdfrstatic svn_error_t *
341177633Sdfroptions_response_handler(serf_request_t *request,
342177633Sdfr                         serf_bucket_t *response,
343177633Sdfr                         void *baton,
344177633Sdfr                         apr_pool_t *pool)
345177633Sdfr{
346177633Sdfr  options_context_t *opt_ctx = baton;
347177633Sdfr
348177633Sdfr  if (!opt_ctx->headers_processed)
349177633Sdfr    {
350177633Sdfr      svn_ra_serf__session_t *session = opt_ctx->session;
351177633Sdfr      serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
352177633Sdfr
353177633Sdfr      /* Start out assuming all capabilities are unsupported. */
354177633Sdfr      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
355177633Sdfr                    capability_no);
356177633Sdfr      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
357177633Sdfr                    capability_no);
358177633Sdfr      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
359177633Sdfr                    NULL);
360177633Sdfr      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
361177633Sdfr                    capability_no);
362177633Sdfr      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
363177633Sdfr                    capability_no);
364177633Sdfr      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
365177633Sdfr                    capability_no);
366177633Sdfr      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
367177633Sdfr                    capability_no);
368177633Sdfr      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
369177633Sdfr                    capability_no);
370177633Sdfr
371180025Sdfr      /* Then see which ones we can discover. */
372180025Sdfr      serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
373184588Sdfr                             opt_ctx);
374184588Sdfr
375244008Srmacklem      /* Assume mergeinfo capability unsupported, if didn't recieve information
376177633Sdfr         about server or repository mergeinfo capability. */
377177633Sdfr      if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
378177633Sdfr        svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
379177633Sdfr                      capability_no);
380177633Sdfr
381177633Sdfr      opt_ctx->headers_processed = TRUE;
382177633Sdfr    }
383177633Sdfr
384177633Sdfr  /* Execute the 'real' response handler to XML-parse the response body. */
385177633Sdfr  return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
386177633Sdfr}
387177633Sdfr
388177633Sdfr
389177633Sdfrstatic svn_error_t *
390177633Sdfrcreate_options_req(options_context_t **opt_ctx,
391177633Sdfr                   svn_ra_serf__session_t *session,
392177633Sdfr                   svn_ra_serf__connection_t *conn,
393177633Sdfr                   apr_pool_t *pool)
394177633Sdfr{
395177633Sdfr  options_context_t *new_ctx;
396177633Sdfr  svn_ra_serf__xml_context_t *xmlctx;
397177633Sdfr  svn_ra_serf__handler_t *handler;
398177633Sdfr
399177633Sdfr  new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
400177633Sdfr  new_ctx->pool = pool;
401177633Sdfr  new_ctx->session = session;
402177633Sdfr  new_ctx->conn = conn;
403177633Sdfr
404177633Sdfr  new_ctx->youngest_rev = SVN_INVALID_REVNUM;
405177633Sdfr
406177633Sdfr  xmlctx = svn_ra_serf__xml_context_create(options_ttable,
407177633Sdfr                                           NULL, options_closed, NULL,
408177633Sdfr                                           new_ctx,
409177633Sdfr                                           pool);
410177633Sdfr  handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
411177633Sdfr
412177633Sdfr  handler->method = "OPTIONS";
413177633Sdfr  handler->path = session->session_url.path;
414177633Sdfr  handler->body_delegate = create_options_body;
415177633Sdfr  handler->body_type = "text/xml";
416177633Sdfr  handler->conn = conn;
417177633Sdfr  handler->session = session;
418177633Sdfr
419177633Sdfr  new_ctx->handler = handler;
420177633Sdfr
421177633Sdfr  new_ctx->inner_handler = handler->response_handler;
422177633Sdfr  new_ctx->inner_baton = handler->response_baton;
423177633Sdfr  handler->response_handler = options_response_handler;
424177633Sdfr  handler->response_baton = new_ctx;
425177633Sdfr
426177633Sdfr  *opt_ctx = new_ctx;
427177633Sdfr
428177633Sdfr  return SVN_NO_ERROR;
429177633Sdfr}
430177633Sdfr
431177633Sdfr
432177633Sdfrsvn_error_t *
433177633Sdfrsvn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
434177633Sdfr                                    svn_ra_serf__connection_t *conn,
435177633Sdfr                                    apr_pool_t *scratch_pool)
436177633Sdfr{
437221127Srmacklem  svn_ra_serf__session_t *session = conn->session;
438177633Sdfr  options_context_t *opt_ctx;
439177633Sdfr
440177633Sdfr  SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
441221127Srmacklem
442177633Sdfr  SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
443177633Sdfr  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
444177633Sdfr  SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
445177633Sdfr                                       opt_ctx->handler->path,
446177633Sdfr                                       opt_ctx->handler->location));
447177633Sdfr
448177633Sdfr  *youngest = opt_ctx->youngest_rev;
449177633Sdfr  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*youngest));
450177633Sdfr
451177633Sdfr  return SVN_NO_ERROR;
452177633Sdfr}
453177633Sdfr
454177633Sdfr
455177633Sdfrsvn_error_t *
456177633Sdfrsvn_ra_serf__v1_get_activity_collection(const char **activity_url,
457177633Sdfr                                        svn_ra_serf__connection_t *conn,
458177633Sdfr                                        apr_pool_t *result_pool,
459177633Sdfr                                        apr_pool_t *scratch_pool)
460177633Sdfr{
461177633Sdfr  svn_ra_serf__session_t *session = conn->session;
462177633Sdfr  options_context_t *opt_ctx;
463177633Sdfr
464177633Sdfr  SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
465177633Sdfr
466177633Sdfr  SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
467177633Sdfr  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
468177633Sdfr
469177633Sdfr  SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
470177633Sdfr                                       opt_ctx->handler->path,
471177633Sdfr                                       opt_ctx->handler->location));
472177633Sdfr
473177633Sdfr  *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
474177633Sdfr
475177633Sdfr  return SVN_NO_ERROR;
476177633Sdfr
477177633Sdfr}
478177633Sdfr
479177633Sdfr
480177633Sdfr
481177633Sdfr/** Capabilities exchange. */
482177633Sdfr
483177633Sdfrsvn_error_t *
484177633Sdfrsvn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
485177633Sdfr                                   const char **corrected_url,
486177633Sdfr                                   apr_pool_t *pool)
487177633Sdfr{
488177633Sdfr  options_context_t *opt_ctx;
489177633Sdfr  svn_error_t *err;
490177633Sdfr
491177633Sdfr  /* This routine automatically fills in serf_sess->capabilities */
492177633Sdfr  SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool));
493177633Sdfr
494177633Sdfr  err = svn_ra_serf__context_run_one(opt_ctx->handler, pool);
495177633Sdfr
496177633Sdfr  /* If our caller cares about server redirections, and our response
497177633Sdfr     carries such a thing, report as much.  We'll disregard ERR --
498177633Sdfr     it's most likely just a complaint about the response body not
499177633Sdfr     successfully parsing as XML or somesuch. */
500177633Sdfr  if (corrected_url && (opt_ctx->handler->sline.code == 301))
501177633Sdfr    {
502177633Sdfr      svn_error_clear(err);
503177633Sdfr      *corrected_url = opt_ctx->handler->location;
504177633Sdfr      return SVN_NO_ERROR;
505177633Sdfr    }
506177633Sdfr
507177633Sdfr  SVN_ERR(svn_error_compose_create(
508177633Sdfr              svn_ra_serf__error_on_status(opt_ctx->handler->sline,
509177633Sdfr                                           serf_sess->session_url.path,
510177633Sdfr                                           opt_ctx->handler->location),
511177633Sdfr              err));
512177633Sdfr
513177633Sdfr  /* Opportunistically cache any reported activity URL.  (We don't
514177633Sdfr     want to have to ask for this again later, potentially against an
515177633Sdfr     unreadable commit anchor URL.)  */
516177633Sdfr  if (opt_ctx->activity_collection)
517177633Sdfr    {
518177633Sdfr      serf_sess->activity_collection_url =
519177633Sdfr        apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
520177633Sdfr    }
521177633Sdfr
522177633Sdfr  return SVN_NO_ERROR;
523177633Sdfr}
524177633Sdfr
525177633Sdfr
526177633Sdfrstatic svn_error_t *
527177633Sdfrcreate_simple_options_body(serf_bucket_t **body_bkt,
528177633Sdfr                           void *baton,
529177633Sdfr                           serf_bucket_alloc_t *alloc,
530177633Sdfr                           apr_pool_t *pool)
531177633Sdfr{
532177633Sdfr  serf_bucket_t *body;
533177633Sdfr  serf_bucket_t *s;
534177633Sdfr
535177633Sdfr  body = serf_bucket_aggregate_create(alloc);
536177633Sdfr  svn_ra_serf__add_xml_header_buckets(body, alloc);
537177633Sdfr
538177633Sdfr  s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
539177633Sdfr  serf_bucket_aggregate_append(body, s);
540177633Sdfr
541177633Sdfr  *body_bkt = body;
542177633Sdfr  return SVN_NO_ERROR;
543177633Sdfr}
544177633Sdfr
545177633Sdfr
546177633Sdfrsvn_error_t *
547177633Sdfrsvn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
548177633Sdfr                         apr_pool_t *scratch_pool)
549177633Sdfr{
550177633Sdfr  svn_ra_serf__handler_t *handler;
551177633Sdfr
552177633Sdfr  handler = apr_pcalloc(scratch_pool, sizeof(*handler));
553177633Sdfr  handler->handler_pool = scratch_pool;
554177633Sdfr  handler->method = "OPTIONS";
555177633Sdfr  handler->path = serf_sess->session_url.path;
556177633Sdfr  handler->conn = serf_sess->conns[0];
557177633Sdfr  handler->session = serf_sess;
558177633Sdfr
559177633Sdfr  /* We don't care about the response body, so discard it.  */
560177633Sdfr  handler->response_handler = svn_ra_serf__handle_discard_body;
561177633Sdfr
562177633Sdfr  /* We need a simple body, in order to send it in chunked format.  */
563177633Sdfr  handler->body_delegate = create_simple_options_body;
564177633Sdfr
565177633Sdfr  /* No special headers.  */
566177633Sdfr
567177633Sdfr  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
568177633Sdfr  /* Some versions of nginx in reverse proxy mode will return 411. They want
569177633Sdfr     a Content-Length header, rather than chunked requests. We can keep other
570177633Sdfr     HTTP/1.1 features, but will disable the chunking.  */
571177633Sdfr  if (handler->sline.code == 411)
572177633Sdfr    {
573177633Sdfr      serf_sess->using_chunked_requests = FALSE;
574177633Sdfr
575177633Sdfr      return SVN_NO_ERROR;
576177633Sdfr    }
577177633Sdfr  SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
578177633Sdfr                                       handler->path,
579177633Sdfr                                       handler->location));
580177633Sdfr
581177633Sdfr  return SVN_NO_ERROR;
582177633Sdfr}
583177633Sdfr
584177633Sdfr
585177633Sdfrsvn_error_t *
586177633Sdfrsvn_ra_serf__has_capability(svn_ra_session_t *ra_session,
587177633Sdfr                            svn_boolean_t *has,
588177633Sdfr                            const char *capability,
589177633Sdfr                            apr_pool_t *pool)
590177633Sdfr{
591177633Sdfr  svn_ra_serf__session_t *serf_sess = ra_session->priv;
592177633Sdfr  const char *cap_result;
593177633Sdfr
594177633Sdfr  /* This capability doesn't rely on anything server side. */
595177633Sdfr  if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
596177633Sdfr    {
597177633Sdfr      *has = TRUE;
598177633Sdfr      return SVN_NO_ERROR;
599177633Sdfr    }
600177633Sdfr
601177633Sdfr  cap_result = svn_hash_gets(serf_sess->capabilities, capability);
602177633Sdfr
603177633Sdfr  /* If any capability is unknown, they're all unknown, so ask. */
604177633Sdfr  if (cap_result == NULL)
605177633Sdfr    SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool));
606177633Sdfr
607177633Sdfr  /* Try again, now that we've fetched the capabilities. */
608177633Sdfr  cap_result = svn_hash_gets(serf_sess->capabilities, capability);
609177633Sdfr
610177633Sdfr  /* Some capabilities depend on the repository as well as the server. */
611177633Sdfr  if (cap_result == capability_server_yes)
612177633Sdfr    {
613177633Sdfr      if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
614177633Sdfr        {
615177633Sdfr          /* Handle mergeinfo specially.  Mergeinfo depends on the
616177633Sdfr             repository as well as the server, but the server routine
617177633Sdfr             that answered our svn_ra_serf__exchange_capabilities() call above
618177633Sdfr             didn't even know which repository we were interested in
619177633Sdfr             -- it just told us whether the server supports mergeinfo.
620177633Sdfr             If the answer was 'no', there's no point checking the
621177633Sdfr             particular repository; but if it was 'yes', we still must
622177633Sdfr             change it to 'no' iff the repository itself doesn't
623177633Sdfr             support mergeinfo. */
624177633Sdfr          svn_mergeinfo_catalog_t ignored;
625177633Sdfr          svn_error_t *err;
626177633Sdfr          apr_array_header_t *paths = apr_array_make(pool, 1,
627177633Sdfr                                                     sizeof(char *));
628177633Sdfr          APR_ARRAY_PUSH(paths, const char *) = "";
629177633Sdfr
630177633Sdfr          err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
631177633Sdfr                                           FALSE, FALSE, pool);
632177633Sdfr
633177633Sdfr          if (err)
634177633Sdfr            {
635177633Sdfr              if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
636177633Sdfr                {
637177633Sdfr                  svn_error_clear(err);
638177633Sdfr                  cap_result = capability_no;
639177633Sdfr                }
640177633Sdfr              else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
641177633Sdfr                {
642177633Sdfr                  /* Mergeinfo requests use relative paths, and
643177633Sdfr                     anyway we're in r0, so this is a likely error,
644177633Sdfr                     but it means the repository supports mergeinfo! */
645177633Sdfr                  svn_error_clear(err);
646177633Sdfr                  cap_result = capability_yes;
647177633Sdfr                }
648177633Sdfr              else
649180025Sdfr                return err;
650177633Sdfr            }
651177633Sdfr          else
652177633Sdfr            cap_result = capability_yes;
653177633Sdfr
654177633Sdfr          svn_hash_sets(serf_sess->capabilities,
655177633Sdfr                        SVN_RA_CAPABILITY_MERGEINFO,  cap_result);
656177633Sdfr        }
657177633Sdfr      else
658177633Sdfr        {
659177633Sdfr          return svn_error_createf
660177633Sdfr            (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
661177633Sdfr             _("Don't know how to handle '%s' for capability '%s'"),
662177633Sdfr             capability_server_yes, capability);
663177633Sdfr        }
664177633Sdfr    }
665177633Sdfr
666177633Sdfr  if (cap_result == capability_yes)
667177633Sdfr    {
668177633Sdfr      *has = TRUE;
669177633Sdfr    }
670177633Sdfr  else if (cap_result == capability_no)
671177633Sdfr    {
672177633Sdfr      *has = FALSE;
673177633Sdfr    }
674177633Sdfr  else if (cap_result == NULL)
675177633Sdfr    {
676177633Sdfr      return svn_error_createf
677177633Sdfr        (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
678177633Sdfr         _("Don't know anything about capability '%s'"), capability);
679177633Sdfr    }
680177633Sdfr  else  /* "can't happen" */
681177633Sdfr    {
682177633Sdfr      /* Well, let's hope it's a string. */
683177633Sdfr      return svn_error_createf
684177633Sdfr        (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
685177633Sdfr         _("Attempt to fetch capability '%s' resulted in '%s'"),
686177633Sdfr         capability, cap_result);
687177633Sdfr    }
688177633Sdfr
689177633Sdfr  return SVN_NO_ERROR;
690177633Sdfr}
691177633Sdfr