options.c revision 299742
167754Smsmith/*
267754Smsmith * options.c :  entry point for OPTIONS RA functions for ra_serf
377424Smsmith *
467754Smsmith * ====================================================================
567754Smsmith *    Licensed to the Apache Software Foundation (ASF) under one
667754Smsmith *    or more contributor license agreements.  See the NOTICE file
7217365Sjkim *    distributed with this work for additional information
8306536Sjkim *    regarding copyright ownership.  The ASF licenses this file
970243Smsmith *    to you under the Apache License, Version 2.0 (the
1067754Smsmith *    "License"); you may not use this file except in compliance
11217365Sjkim *    with the License.  You may obtain a copy of the License at
12217365Sjkim *
13217365Sjkim *      http://www.apache.org/licenses/LICENSE-2.0
14217365Sjkim *
15217365Sjkim *    Unless required by applicable law or agreed to in writing,
16217365Sjkim *    software distributed under the License is distributed on an
17217365Sjkim *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18217365Sjkim *    KIND, either express or implied.  See the License for the
19217365Sjkim *    specific language governing permissions and limitations
20217365Sjkim *    under the License.
21217365Sjkim * ====================================================================
22217365Sjkim */
23217365Sjkim
24217365Sjkim
2567754Smsmith
26217365Sjkim#include <apr_uri.h>
27217365Sjkim
28217365Sjkim#include <serf.h>
2967754Smsmith
30217365Sjkim#include "svn_dirent_uri.h"
31217365Sjkim#include "svn_hash.h"
32217365Sjkim#include "svn_pools.h"
33217365Sjkim#include "svn_path.h"
34217365Sjkim#include "svn_ra.h"
35217365Sjkim#include "svn_dav.h"
36217365Sjkim#include "svn_xml.h"
37217365Sjkim#include "svn_ctype.h"
38217365Sjkim
39217365Sjkim#include "../libsvn_ra/ra_loader.h"
40217365Sjkim#include "svn_private_config.h"
41217365Sjkim#include "private/svn_fspath.h"
42217365Sjkim
4367754Smsmith#include "ra_serf.h"
44193341Sjkim
45193341Sjkim
46193341Sjkim/* In a debug build, setting this environment variable to "yes" will force
4767754Smsmith   the client to speak v1, even if the server is capable of speaking v2. */
4877424Smsmith#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
4991116Smsmith
5067754Smsmith
5167754Smsmith/*
52151937Sjkim * This enum represents the current state of our XML parsing for an OPTIONS.
53151937Sjkim */
54151937Sjkimenum options_state_e {
55151937Sjkim  INITIAL = XML_STATE_INITIAL,
56151937Sjkim  OPTIONS,
57151937Sjkim  ACTIVITY_COLLECTION,
58151937Sjkim  HREF
59151937Sjkim};
60151937Sjkim
6167754Smsmithtypedef struct options_context_t {
6267754Smsmith  /* pool to allocate memory from */
63151937Sjkim  apr_pool_t *pool;
6467754Smsmith
65151937Sjkim  /* Have we extracted options values from the headers already?  */
66151937Sjkim  svn_boolean_t headers_processed;
67151937Sjkim
6867754Smsmith  svn_ra_serf__session_t *session;
6977424Smsmith  svn_ra_serf__handler_t *handler;
7067754Smsmith
71151937Sjkim  svn_ra_serf__response_handler_t inner_handler;
72151937Sjkim  void *inner_baton;
7367754Smsmith
7467754Smsmith  const char *activity_collection;
7567754Smsmith  svn_revnum_t youngest_rev;
7667754Smsmith
77151937Sjkim} options_context_t;
78151937Sjkim
79151937Sjkim#define D_ "DAV:"
80151937Sjkim#define S_ SVN_XML_NAMESPACE
8167754Smsmithstatic const svn_ra_serf__xml_transition_t options_ttable[] = {
82151937Sjkim  { INITIAL, D_, "options-response", OPTIONS,
83151937Sjkim    FALSE, { NULL }, FALSE },
84151937Sjkim
85151937Sjkim  { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
86151937Sjkim    FALSE, { NULL }, FALSE },
87151937Sjkim
88151937Sjkim  { ACTIVITY_COLLECTION, D_, "href", HREF,
89151937Sjkim    TRUE, { NULL }, TRUE },
9067754Smsmith
9167754Smsmith  { 0 }
92167802Sjkim};
9367754Smsmith
9477424Smsmith
95228110Sjkim/* Conforms to svn_ra_serf__xml_closed_t  */
96228110Sjkimstatic svn_error_t *
97228110Sjkimoptions_closed(svn_ra_serf__xml_estate_t *xes,
98228110Sjkim               void *baton,
99228110Sjkim               int leaving_state,
100193267Sjkim               const svn_string_t *cdata,
101151937Sjkim               apr_hash_t *attrs,
102167802Sjkim               apr_pool_t *scratch_pool)
103167802Sjkim{
104167802Sjkim  options_context_t *opt_ctx = baton;
105204773Sjkim
106167802Sjkim  SVN_ERR_ASSERT(leaving_state == HREF);
107151937Sjkim  SVN_ERR_ASSERT(cdata != NULL);
10867754Smsmith
109151937Sjkim  opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
11067754Smsmith                                                           opt_ctx->pool);
111151937Sjkim
11267754Smsmith  return SVN_NO_ERROR;
11367754Smsmith}
114151937Sjkim
115151937Sjkim/* Implements svn_ra_serf__request_body_delegate_t */
11667754Smsmithstatic svn_error_t *
117151937Sjkimcreate_options_body(serf_bucket_t **body_bkt,
118151937Sjkim                    void *baton,
119151937Sjkim                    serf_bucket_alloc_t *alloc,
120151937Sjkim                    apr_pool_t *pool /* request pool */,
121151937Sjkim                    apr_pool_t *scratch_pool)
122151937Sjkim{
123151937Sjkim  serf_bucket_t *body;
124306536Sjkim  body = serf_bucket_aggregate_create(alloc);
125167802Sjkim  svn_ra_serf__add_xml_header_buckets(body, alloc);
12667754Smsmith  svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
127151937Sjkim                                    "xmlns:D", "DAV:",
128151937Sjkim                                    SVN_VA_NULL);
129151937Sjkim  svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
130151937Sjkim  svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
131151937Sjkim
132151937Sjkim  *body_bkt = body;
133306536Sjkim  return SVN_NO_ERROR;
134151937Sjkim}
135151937Sjkim
136151937Sjkim
13767754Smsmith/* We use these static pointers so we can employ pointer comparison
138151937Sjkim * of our capabilities hash members instead of strcmp()ing all over
139151937Sjkim * the place.
14067754Smsmith */
141151937Sjkim/* Both server and repository support the capability. */
14277424Smsmithstatic const char *const capability_yes = "yes";
143151937Sjkim/* Either server or repository does not support the capability. */
144151937Sjkimstatic const char *const capability_no = "no";
14567754Smsmith/* Server supports the capability, but don't yet know if repository does. */
146151937Sjkimstatic const char *const capability_server_yes = "server-yes";
147151937Sjkim
148151937Sjkim
149151937Sjkim/* This implements serf_bucket_headers_do_callback_fn_t.
150243347Sjkim */
151243347Sjkimstatic int
152151937Sjkimcapabilities_headers_iterator_callback(void *baton,
15367754Smsmith                                       const char *key,
154151937Sjkim                                       const char *val)
155151937Sjkim{
156151937Sjkim  options_context_t *opt_ctx = baton;
157151937Sjkim  svn_ra_serf__session_t *session = opt_ctx->session;
158243347Sjkim
159243347Sjkim  if (svn_cstring_casecmp(key, "dav") == 0)
160151937Sjkim    {
16167754Smsmith      /* Each header may contain multiple values, separated by commas, e.g.:
162228110Sjkim           DAV: version-control,checkout,working-resource
163228110Sjkim           DAV: merge,baseline,activity,version-controlled-collection
164228110Sjkim           DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
165228110Sjkim      apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
166243347Sjkim                                                   opt_ctx->pool);
167243347Sjkim
168228110Sjkim      /* Right now we only have a few capabilities to detect, so just
169228110Sjkim         seek for them directly.  This could be written slightly more
170151937Sjkim         efficiently, but that wouldn't be worth it until we have many
17167754Smsmith         more capabilities. */
172167802Sjkim
173243347Sjkim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
17467754Smsmith        {
175151937Sjkim          svn_hash_sets(session->capabilities,
176151937Sjkim                        SVN_RA_CAPABILITY_DEPTH, capability_yes);
177151937Sjkim        }
17867754Smsmith      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
179151937Sjkim        {
18067754Smsmith          /* The server doesn't know what repository we're referring
181151937Sjkim             to, so it can't just say capability_yes. */
182243347Sjkim          if (!svn_hash_gets(session->capabilities,
18367754Smsmith                             SVN_RA_CAPABILITY_MERGEINFO))
184151937Sjkim            {
185151937Sjkim              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
186151937Sjkim                            capability_server_yes);
18767754Smsmith            }
188228110Sjkim        }
189228110Sjkim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
190228110Sjkim        {
191228110Sjkim          svn_hash_sets(session->capabilities,
192228110Sjkim                        SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
193228110Sjkim        }
194228110Sjkim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
195243347Sjkim        {
196228110Sjkim          svn_hash_sets(session->capabilities,
197228110Sjkim                        SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
198228110Sjkim        }
199228110Sjkim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
200228110Sjkim        {
201243347Sjkim          svn_hash_sets(session->capabilities,
202228110Sjkim                        SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
203306536Sjkim        }
204228110Sjkim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
205228110Sjkim        {
206228110Sjkim          svn_hash_sets(session->capabilities,
207228110Sjkim                        SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
208228110Sjkim        }
209228110Sjkim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
210228110Sjkim                                 vals))
211228110Sjkim        {
212228110Sjkim          svn_hash_sets(session->capabilities,
213228110Sjkim                        SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
214228110Sjkim                        capability_yes);
215228110Sjkim        }
216228110Sjkim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
217228110Sjkim        {
218228110Sjkim          svn_hash_sets(session->capabilities,
219228110Sjkim                        SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
220228110Sjkim        }
221228110Sjkim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
222228110Sjkim        {
223228110Sjkim          session->supports_inline_props = TRUE;
224228110Sjkim        }
225228110Sjkim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
226228110Sjkim        {
227228110Sjkim          session->supports_rev_rsrc_replay = TRUE;
228228110Sjkim        }
229243347Sjkim    }
230228110Sjkim
231228110Sjkim  /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
232228110Sjkim  else if (!svn_ctype_casecmp(key[0], 'S')
233228110Sjkim           && !svn_ctype_casecmp(key[1], 'V')
234228110Sjkim           && !svn_ctype_casecmp(key[2], 'N'))
235228110Sjkim    {
236228110Sjkim      /* If we've not yet seen any information about supported POST
237243347Sjkim         requests, we'll initialize the list/hash with "create-txn"
238228110Sjkim         (which we know is supported by virtue of the server speaking
239228110Sjkim         HTTPv2 at all. */
240228110Sjkim      if (! session->supported_posts)
241228110Sjkim        {
242228110Sjkim          session->supported_posts = apr_hash_make(session->pool);
243228110Sjkim          apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
244228110Sjkim        }
245228110Sjkim
246228110Sjkim      if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
247243347Sjkim        {
248228110Sjkim          session->repos_root = session->session_url;
249228110Sjkim          session->repos_root.path =
250151937Sjkim            (char *)svn_fspath__canonicalize(val, session->pool);
25167754Smsmith          session->repos_root_str =
252151937Sjkim            svn_urlpath__canonicalize(
253151937Sjkim                apr_uri_unparse(session->pool, &session->repos_root, 0),
25467754Smsmith                session->pool);
255151937Sjkim        }
256151937Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
257151937Sjkim        {
258151937Sjkim#ifdef SVN_DEBUG
259151937Sjkim          char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
260151937Sjkim
261151937Sjkim          if (!(ignore_v2_env_var
262151937Sjkim                && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
263151937Sjkim            session->me_resource = apr_pstrdup(session->pool, val);
264151937Sjkim#else
265151937Sjkim          session->me_resource = apr_pstrdup(session->pool, val);
266151937Sjkim#endif
267167802Sjkim        }
268151937Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
26967754Smsmith        {
270228110Sjkim          session->rev_stub = apr_pstrdup(session->pool, val);
271228110Sjkim        }
272228110Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
273228110Sjkim        {
274228110Sjkim          session->rev_root_stub = apr_pstrdup(session->pool, val);
275306536Sjkim        }
276228110Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
277228110Sjkim        {
278228110Sjkim          session->txn_stub = apr_pstrdup(session->pool, val);
279228110Sjkim        }
280228110Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
281228110Sjkim        {
282228110Sjkim          session->txn_root_stub = apr_pstrdup(session->pool, val);
283228110Sjkim        }
284228110Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
285228110Sjkim        {
286228110Sjkim          session->vtxn_stub = apr_pstrdup(session->pool, val);
287228110Sjkim        }
288228110Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
289306536Sjkim        {
290228110Sjkim          session->vtxn_root_stub = apr_pstrdup(session->pool, val);
291228110Sjkim        }
292228110Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
293228110Sjkim        {
294228110Sjkim          session->uuid = apr_pstrdup(session->pool, val);
295228110Sjkim        }
296228110Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
297228110Sjkim        {
298228110Sjkim          opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
299228110Sjkim        }
300228110Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
301228110Sjkim        {
302228110Sjkim          session->server_allows_bulk = apr_pstrdup(session->pool, val);
303306536Sjkim        }
304228110Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
305228110Sjkim        {
306228110Sjkim          /* May contain multiple values, separated by commas. */
307228110Sjkim          int i;
308228110Sjkim          apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
309228110Sjkim                                                       session->pool);
310228110Sjkim
311228110Sjkim          for (i = 0; i < vals->nelts; i++)
312228110Sjkim            {
313228110Sjkim              const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
314228110Sjkim
315228110Sjkim              svn_hash_sets(session->supported_posts, post_val, (void *)1);
316228110Sjkim            }
317306536Sjkim        }
318228110Sjkim      else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
319228110Sjkim        {
320228110Sjkim          if (svn_cstring_casecmp(val, "yes") == 0)
321228110Sjkim            {
322306536Sjkim              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
323306536Sjkim                            capability_yes);
324228110Sjkim            }
325228110Sjkim          else if (svn_cstring_casecmp(val, "no") == 0)
326228110Sjkim            {
327151937Sjkim              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
32867754Smsmith                            capability_no);
329306536Sjkim            }
330151937Sjkim        }
33167754Smsmith    }
332151937Sjkim
33367754Smsmith  return 0;
334167802Sjkim}
335306536Sjkim
336151937Sjkim
33767754Smsmith/* A custom serf_response_handler_t which is mostly a wrapper around
338151937Sjkim   the expat-based response handler -- it just notices OPTIONS response
339151937Sjkim   headers first, before handing off to the xml parser.
340151937Sjkim   Implements svn_ra_serf__response_handler_t */
341151937Sjkimstatic svn_error_t *
342151937Sjkimoptions_response_handler(serf_request_t *request,
343151937Sjkim                         serf_bucket_t *response,
344151937Sjkim                         void *baton,
345151937Sjkim                         apr_pool_t *pool)
346151937Sjkim{
34767754Smsmith  options_context_t *opt_ctx = baton;
348151937Sjkim
349151937Sjkim  if (!opt_ctx->headers_processed)
350151937Sjkim    {
351151937Sjkim      svn_ra_serf__session_t *session = opt_ctx->session;
352151937Sjkim      serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
353151937Sjkim
354151937Sjkim      /* Start out assuming all capabilities are unsupported. */
355151937Sjkim      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
35667754Smsmith                    capability_no);
357151937Sjkim      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
358151937Sjkim                    capability_no);
359151937Sjkim      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
360151937Sjkim                    NULL);
361151937Sjkim      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
362228110Sjkim                    capability_no);
363228110Sjkim      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
36467754Smsmith                    capability_no);
365151937Sjkim      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
366228110Sjkim                    capability_no);
367228110Sjkim      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
368151937Sjkim                    capability_no);
369151937Sjkim      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
37067754Smsmith                    capability_no);
371151937Sjkim
372151937Sjkim      /* Then see which ones we can discover. */
373151937Sjkim      serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
374151937Sjkim                             opt_ctx);
375167802Sjkim
376151937Sjkim      /* Assume mergeinfo capability unsupported, if didn't receive information
377151937Sjkim         about server or repository mergeinfo capability. */
378152069Sjkim      if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
379151937Sjkim        svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
38077424Smsmith                      capability_no);
381167802Sjkim
382243347Sjkim      opt_ctx->headers_processed = TRUE;
383151937Sjkim    }
38467754Smsmith
385151937Sjkim  /* Execute the 'real' response handler to XML-parse the response body. */
386151937Sjkim  return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
387151937Sjkim}
388151937Sjkim
389151937Sjkim
39067754Smsmithstatic svn_error_t *
391151937Sjkimcreate_options_req(options_context_t **opt_ctx,
392151937Sjkim                   svn_ra_serf__session_t *session,
393151937Sjkim                   apr_pool_t *pool)
394152069Sjkim{
395151937Sjkim  options_context_t *new_ctx;
396151937Sjkim  svn_ra_serf__xml_context_t *xmlctx;
397167802Sjkim  svn_ra_serf__handler_t *handler;
398243347Sjkim
399151937Sjkim  new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
400151937Sjkim  new_ctx->pool = pool;
401151937Sjkim  new_ctx->session = session;
402151937Sjkim
403151937Sjkim  new_ctx->youngest_rev = SVN_INVALID_REVNUM;
404151937Sjkim
405151937Sjkim  xmlctx = svn_ra_serf__xml_context_create(options_ttable,
406151937Sjkim                                           NULL, options_closed, NULL,
407151937Sjkim                                           new_ctx,
408250838Sjkim                                           pool);
409151937Sjkim  handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
410151937Sjkim
411151937Sjkim  handler->method = "OPTIONS";
412151937Sjkim  handler->path = session->session_url.path;
413151937Sjkim  handler->body_delegate = create_options_body;
414151937Sjkim  handler->body_type = "text/xml";
415151937Sjkim
416250838Sjkim  new_ctx->handler = handler;
417167802Sjkim
418151937Sjkim  new_ctx->inner_handler = handler->response_handler;
419151937Sjkim  new_ctx->inner_baton = handler->response_baton;
420151937Sjkim  handler->response_handler = options_response_handler;
421151937Sjkim  handler->response_baton = new_ctx;
422151937Sjkim
423151937Sjkim  *opt_ctx = new_ctx;
424167802Sjkim
425167802Sjkim  return SVN_NO_ERROR;
426151937Sjkim}
427151937Sjkim
428151937Sjkim
429151937Sjkimsvn_error_t *
430151937Sjkimsvn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
431151937Sjkim                                    svn_ra_serf__session_t *session,
432167802Sjkim                                    apr_pool_t *scratch_pool)
433151937Sjkim{
434151937Sjkim  options_context_t *opt_ctx;
435151937Sjkim
436151937Sjkim  SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
437151937Sjkim
43867754Smsmith  SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
43967754Smsmith  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
440151937Sjkim
441151937Sjkim  if (opt_ctx->handler->sline.code != 200)
44267754Smsmith    return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
443167802Sjkim
44467754Smsmith  if (! SVN_IS_VALID_REVNUM(opt_ctx->youngest_rev))
445306536Sjkim    return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
446306536Sjkim                            _("The OPTIONS response did not include "
44767754Smsmith                              "the youngest revision"));
44867754Smsmith
44967754Smsmith  *youngest = opt_ctx->youngest_rev;
45067754Smsmith
45167754Smsmith  return SVN_NO_ERROR;
45267754Smsmith}
45367754Smsmith
454151937Sjkim
45567754Smsmithsvn_error_t *
456151937Sjkimsvn_ra_serf__v1_get_activity_collection(const char **activity_url,
457151937Sjkim                                        svn_ra_serf__session_t *session,
458151937Sjkim                                        apr_pool_t *result_pool,
45967754Smsmith                                        apr_pool_t *scratch_pool)
46077424Smsmith{
46167754Smsmith  options_context_t *opt_ctx;
462151937Sjkim
463151937Sjkim  SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
46467754Smsmith
46567754Smsmith  if (session->activity_collection_url)
46667754Smsmith    {
46767754Smsmith      *activity_url = apr_pstrdup(result_pool,
468151937Sjkim                                  session->activity_collection_url);
469151937Sjkim      return SVN_NO_ERROR;
470151937Sjkim    }
471151937Sjkim
47267754Smsmith  SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
473151937Sjkim  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
474151937Sjkim
475228110Sjkim  if (opt_ctx->handler->sline.code != 200)
476151937Sjkim    return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
477151937Sjkim
478151937Sjkim  /* Cache the result. */
479151937Sjkim  if (opt_ctx->activity_collection)
48067754Smsmith    {
48167754Smsmith      session->activity_collection_url =
482167802Sjkim                    apr_pstrdup(session->pool, opt_ctx->activity_collection);
48367754Smsmith    }
48477424Smsmith  else
485228110Sjkim    {
486228110Sjkim      return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
487228110Sjkim                              _("The OPTIONS response did not include the "
488228110Sjkim                                "requested activity-collection-set value"));
489228110Sjkim    }
49067754Smsmith
491151937Sjkim  *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
492151937Sjkim
49367754Smsmith  return SVN_NO_ERROR;
494151937Sjkim
495151937Sjkim}
496151937Sjkim
49767754Smsmith
49867754Smsmith
499151937Sjkim/** Capabilities exchange. */
500151937Sjkim
50167754Smsmithsvn_error_t *
502306536Sjkimsvn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
503167802Sjkim                                   const char **corrected_url,
50467754Smsmith                                   apr_pool_t *result_pool,
505151937Sjkim                                   apr_pool_t *scratch_pool)
50667754Smsmith{
507151937Sjkim  options_context_t *opt_ctx;
50867754Smsmith
509306536Sjkim  if (corrected_url)
510151937Sjkim    *corrected_url = NULL;
511306536Sjkim
512306536Sjkim  /* This routine automatically fills in serf_sess->capabilities */
513151937Sjkim  SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_pool));
51467754Smsmith
515151937Sjkim  opt_ctx->handler->no_fail_on_http_redirect_status = TRUE;
516151937Sjkim
51767754Smsmith  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
518151937Sjkim
519151937Sjkim  /* If our caller cares about server redirections, and our response
520151937Sjkim     carries such a thing, report as much.  We'll disregard ERR --
521151937Sjkim     it's most likely just a complaint about the response body not
522243347Sjkim     successfully parsing as XML or somesuch. */
523151937Sjkim  if (corrected_url && (opt_ctx->handler->sline.code == 301))
52467754Smsmith    {
525151937Sjkim      if (!opt_ctx->handler->location || !*opt_ctx->handler->location)
526151937Sjkim        {
527151937Sjkim          return svn_error_create(
528151937Sjkim                    SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
529243347Sjkim                    _("Location header not set on redirect response"));
530243347Sjkim        }
531151937Sjkim      else if (svn_path_is_url(opt_ctx->handler->location))
53267754Smsmith        {
533151937Sjkim          *corrected_url = svn_uri_canonicalize(opt_ctx->handler->location,
534151937Sjkim                                                result_pool);
535151937Sjkim        }
536151937Sjkim      else
537243347Sjkim        {
538243347Sjkim          /* RFC1945 and RFC2616 state that the Location header's value
539151937Sjkim             (from whence this CORRECTED_URL comes), if present, must be an
54067754Smsmith             absolute URI.  But some Apache versions (those older than 2.2.11,
541228110Sjkim             it seems) transmit only the path portion of the URI.
542228110Sjkim             See issue #3775 for details. */
543228110Sjkim
544228110Sjkim          apr_uri_t corrected_URI = serf_sess->session_url;
545243347Sjkim
546243347Sjkim          corrected_URI.path = (char *)corrected_url;
547228110Sjkim          *corrected_url = svn_uri_canonicalize(
548228110Sjkim                              apr_uri_unparse(scratch_pool, &corrected_URI, 0),
549151937Sjkim                              result_pool);
55077424Smsmith        }
551167802Sjkim
552243347Sjkim      return SVN_NO_ERROR;
55367754Smsmith    }
554306536Sjkim  else if (opt_ctx->handler->sline.code >= 300
555306536Sjkim           && opt_ctx->handler->sline.code < 399)
556151937Sjkim    {
55767754Smsmith      return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL,
558151937Sjkim                               (opt_ctx->handler->sline.code == 301
55967754Smsmith                                ? _("Repository moved permanently to '%s'")
560167802Sjkim                                : _("Repository moved temporarily to '%s'")),
561151937Sjkim                              opt_ctx->handler->location);
562151937Sjkim    }
563151937Sjkim
56467754Smsmith  if (opt_ctx->handler->sline.code != 200)
565228110Sjkim    return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
566228110Sjkim
567228110Sjkim  /* Opportunistically cache any reported activity URL.  (We don't
568243347Sjkim     want to have to ask for this again later, potentially against an
569228110Sjkim     unreadable commit anchor URL.)  */
570228110Sjkim  if (opt_ctx->activity_collection)
571228110Sjkim    {
572243347Sjkim      serf_sess->activity_collection_url =
573228110Sjkim        apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
574228110Sjkim    }
575228110Sjkim
576228110Sjkim  return SVN_NO_ERROR;
577228110Sjkim}
578228110Sjkim
579243347Sjkim/* Implements svn_ra_serf__request_body_delegate_t */
580228110Sjkimstatic svn_error_t *
581306536Sjkimcreate_simple_options_body(serf_bucket_t **body_bkt,
582306536Sjkim                           void *baton,
583228110Sjkim                           serf_bucket_alloc_t *alloc,
584228110Sjkim                           apr_pool_t *pool /* request pool */,
585228110Sjkim                           apr_pool_t *scratch_pool)
586228110Sjkim{
587228110Sjkim  serf_bucket_t *body;
588228110Sjkim  serf_bucket_t *s;
589228110Sjkim
590228110Sjkim  body = serf_bucket_aggregate_create(alloc);
591243347Sjkim  svn_ra_serf__add_xml_header_buckets(body, alloc);
592228110Sjkim
593228110Sjkim  s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
594228110Sjkim  serf_bucket_aggregate_append(body, s);
595228110Sjkim
596228110Sjkim  *body_bkt = body;
597228110Sjkim  return SVN_NO_ERROR;
598228110Sjkim}
599228110Sjkim
600228110Sjkim
601228110Sjkimsvn_error_t *
602243347Sjkimsvn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
603228110Sjkim                         apr_pool_t *scratch_pool)
604228110Sjkim{
605228110Sjkim  svn_ra_serf__handler_t *handler;
606228110Sjkim
607228110Sjkim  handler = svn_ra_serf__create_handler(serf_sess, scratch_pool);
608228110Sjkim  handler->method = "OPTIONS";
609228110Sjkim  handler->path = serf_sess->session_url.path;
610228110Sjkim
611243347Sjkim  /* We don't care about the response body, so discard it.  */
612228110Sjkim  handler->response_handler = svn_ra_serf__handle_discard_body;
613228110Sjkim
614228110Sjkim  /* We need a simple body, in order to send it in chunked format.  */
615228110Sjkim  handler->body_delegate = create_simple_options_body;
616228110Sjkim  handler->no_fail_on_http_failure_status = TRUE;
617228110Sjkim
618228110Sjkim  /* No special headers.  */
619228110Sjkim
620228110Sjkim  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
621228110Sjkim  /* Some versions of nginx in reverse proxy mode will return 411. They want
622228110Sjkim     a Content-Length header, rather than chunked requests. We can keep other
623151937Sjkim     HTTP/1.1 features, but will disable the chunking.  */
62467754Smsmith  if (handler->sline.code == 411)
625151937Sjkim    {
626151937Sjkim      serf_sess->using_chunked_requests = FALSE;
62767754Smsmith
628151937Sjkim      return SVN_NO_ERROR;
629151937Sjkim    }
630151937Sjkim  if (handler->sline.code != 200)
631151937Sjkim    SVN_ERR(svn_ra_serf__unexpected_status(handler));
63277424Smsmith
633151937Sjkim  return SVN_NO_ERROR;
634151937Sjkim}
635151937Sjkim
636151937Sjkim
637151937Sjkimsvn_error_t *
638151937Sjkimsvn_ra_serf__has_capability(svn_ra_session_t *ra_session,
63967754Smsmith                            svn_boolean_t *has,
640228110Sjkim                            const char *capability,
641228110Sjkim                            apr_pool_t *pool)
642228110Sjkim{
643306536Sjkim  svn_ra_serf__session_t *serf_sess = ra_session->priv;
644228110Sjkim  const char *cap_result;
645228110Sjkim
646228110Sjkim  /* This capability doesn't rely on anything server side. */
647228110Sjkim  if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
648228110Sjkim    {
649228110Sjkim      *has = TRUE;
650228110Sjkim      return SVN_NO_ERROR;
651228110Sjkim    }
652228110Sjkim
653306536Sjkim  cap_result = svn_hash_gets(serf_sess->capabilities, capability);
654228110Sjkim
655228110Sjkim  /* If any capability is unknown, they're all unknown, so ask. */
656228110Sjkim  if (cap_result == NULL)
657228110Sjkim    SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool, pool));
658228110Sjkim
659228110Sjkim  /* Try again, now that we've fetched the capabilities. */
660228110Sjkim  cap_result = svn_hash_gets(serf_sess->capabilities, capability);
661306536Sjkim
662228110Sjkim  /* Some capabilities depend on the repository as well as the server. */
663228110Sjkim  if (cap_result == capability_server_yes)
664228110Sjkim    {
665228110Sjkim      if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
666228110Sjkim        {
667228110Sjkim          /* Handle mergeinfo specially.  Mergeinfo depends on the
668228110Sjkim             repository as well as the server, but the server routine
669306536Sjkim             that answered our svn_ra_serf__exchange_capabilities() call above
670228110Sjkim             didn't even know which repository we were interested in
671228110Sjkim             -- it just told us whether the server supports mergeinfo.
672228110Sjkim             If the answer was 'no', there's no point checking the
673228110Sjkim             particular repository; but if it was 'yes', we still must
674151937Sjkim             change it to 'no' iff the repository itself doesn't
67567754Smsmith             support mergeinfo. */
676151937Sjkim          svn_mergeinfo_catalog_t ignored;
67767754Smsmith          svn_error_t *err;
678151937Sjkim          apr_array_header_t *paths = apr_array_make(pool, 1,
679151937Sjkim                                                     sizeof(char *));
68067754Smsmith          APR_ARRAY_PUSH(paths, const char *) = "";
681151937Sjkim
682151937Sjkim          err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
683151937Sjkim                                           svn_mergeinfo_explicit,
684151937Sjkim                                           FALSE /* include_descendants */,
685151937Sjkim                                           pool);
686306536Sjkim
687151937Sjkim          if (err)
688151937Sjkim            {
68967754Smsmith              if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
690151937Sjkim                {
691151937Sjkim                  svn_error_clear(err);
692151937Sjkim                  cap_result = capability_no;
693151937Sjkim                }
694151937Sjkim              else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
695151937Sjkim                {
696151937Sjkim                  /* Mergeinfo requests use relative paths, and
697151937Sjkim                     anyway we're in r0, so this is a likely error,
69867754Smsmith                     but it means the repository supports mergeinfo! */
699151937Sjkim                  svn_error_clear(err);
700151937Sjkim                  cap_result = capability_yes;
701151937Sjkim                }
702151937Sjkim              else
703243347Sjkim                return svn_error_trace(err);
704167802Sjkim            }
705243347Sjkim          else
706151937Sjkim            cap_result = capability_yes;
70767754Smsmith
708151937Sjkim          svn_hash_sets(serf_sess->capabilities,
709151937Sjkim                        SVN_RA_CAPABILITY_MERGEINFO,  cap_result);
710151937Sjkim        }
711151937Sjkim      else
712306536Sjkim        {
713306536Sjkim          return svn_error_createf
714151937Sjkim            (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
715151937Sjkim             _("Don't know how to handle '%s' for capability '%s'"),
716151937Sjkim             capability_server_yes, capability);
717151937Sjkim        }
718151937Sjkim    }
719151937Sjkim
720151937Sjkim  if (cap_result == capability_yes)
721151937Sjkim    {
722151937Sjkim      *has = TRUE;
723151937Sjkim    }
724151937Sjkim  else if (cap_result == capability_no)
725151937Sjkim    {
726151937Sjkim      *has = FALSE;
727151937Sjkim    }
728151937Sjkim  else if (cap_result == NULL)
729151937Sjkim    {
730151937Sjkim      return svn_error_createf
731151937Sjkim        (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
732151937Sjkim         _("Don't know anything about capability '%s'"), capability);
733151937Sjkim    }
734167802Sjkim  else  /* "can't happen" */
735167802Sjkim    {
736306536Sjkim      /* Well, let's hope it's a string. */
737151937Sjkim      return svn_error_createf
738151937Sjkim        (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
739151937Sjkim         _("Attempt to fetch capability '%s' resulted in '%s'"),
740151937Sjkim         capability, cap_result);
741151937Sjkim    }
742151937Sjkim
743167802Sjkim  return SVN_NO_ERROR;
744167802Sjkim}
745151937Sjkim