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"
33289180Speter#include "svn_path.h"
34251881Speter#include "svn_ra.h"
35251881Speter#include "svn_dav.h"
36251881Speter#include "svn_xml.h"
37286506Speter#include "svn_ctype.h"
38251881Speter
39251881Speter#include "../libsvn_ra/ra_loader.h"
40251881Speter#include "svn_private_config.h"
41251881Speter#include "private/svn_fspath.h"
42251881Speter
43251881Speter#include "ra_serf.h"
44251881Speter
45251881Speter
46251881Speter/* In a debug build, setting this environment variable to "yes" will force
47251881Speter   the client to speak v1, even if the server is capable of speaking v2. */
48251881Speter#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
49251881Speter
50251881Speter
51251881Speter/*
52251881Speter * This enum represents the current state of our XML parsing for an OPTIONS.
53251881Speter */
54251881Speterenum options_state_e {
55289180Speter  INITIAL = XML_STATE_INITIAL,
56251881Speter  OPTIONS,
57251881Speter  ACTIVITY_COLLECTION,
58251881Speter  HREF
59251881Speter};
60251881Speter
61251881Spetertypedef struct options_context_t {
62251881Speter  /* pool to allocate memory from */
63251881Speter  apr_pool_t *pool;
64251881Speter
65251881Speter  /* Have we extracted options values from the headers already?  */
66251881Speter  svn_boolean_t headers_processed;
67251881Speter
68251881Speter  svn_ra_serf__session_t *session;
69251881Speter  svn_ra_serf__handler_t *handler;
70251881Speter
71251881Speter  svn_ra_serf__response_handler_t inner_handler;
72251881Speter  void *inner_baton;
73251881Speter
74362181Sdim  /* Have we received any DAV headers at all? */
75362181Sdim  svn_boolean_t received_dav_header;
76362181Sdim
77251881Speter  const char *activity_collection;
78251881Speter  svn_revnum_t youngest_rev;
79251881Speter
80251881Speter} options_context_t;
81251881Speter
82251881Speter#define D_ "DAV:"
83251881Speter#define S_ SVN_XML_NAMESPACE
84251881Speterstatic const svn_ra_serf__xml_transition_t options_ttable[] = {
85251881Speter  { INITIAL, D_, "options-response", OPTIONS,
86251881Speter    FALSE, { NULL }, FALSE },
87251881Speter
88251881Speter  { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
89251881Speter    FALSE, { NULL }, FALSE },
90251881Speter
91251881Speter  { ACTIVITY_COLLECTION, D_, "href", HREF,
92251881Speter    TRUE, { NULL }, TRUE },
93251881Speter
94251881Speter  { 0 }
95251881Speter};
96251881Speter
97251881Speter
98251881Speter/* Conforms to svn_ra_serf__xml_closed_t  */
99251881Speterstatic svn_error_t *
100251881Speteroptions_closed(svn_ra_serf__xml_estate_t *xes,
101251881Speter               void *baton,
102251881Speter               int leaving_state,
103251881Speter               const svn_string_t *cdata,
104251881Speter               apr_hash_t *attrs,
105251881Speter               apr_pool_t *scratch_pool)
106251881Speter{
107251881Speter  options_context_t *opt_ctx = baton;
108251881Speter
109251881Speter  SVN_ERR_ASSERT(leaving_state == HREF);
110251881Speter  SVN_ERR_ASSERT(cdata != NULL);
111251881Speter
112251881Speter  opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
113251881Speter                                                           opt_ctx->pool);
114251881Speter
115251881Speter  return SVN_NO_ERROR;
116251881Speter}
117251881Speter
118289180Speter/* Implements svn_ra_serf__request_body_delegate_t */
119251881Speterstatic svn_error_t *
120251881Spetercreate_options_body(serf_bucket_t **body_bkt,
121251881Speter                    void *baton,
122251881Speter                    serf_bucket_alloc_t *alloc,
123289180Speter                    apr_pool_t *pool /* request pool */,
124289180Speter                    apr_pool_t *scratch_pool)
125251881Speter{
126251881Speter  serf_bucket_t *body;
127251881Speter  body = serf_bucket_aggregate_create(alloc);
128251881Speter  svn_ra_serf__add_xml_header_buckets(body, alloc);
129251881Speter  svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
130251881Speter                                    "xmlns:D", "DAV:",
131289180Speter                                    SVN_VA_NULL);
132251881Speter  svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
133251881Speter  svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
134251881Speter
135251881Speter  *body_bkt = body;
136251881Speter  return SVN_NO_ERROR;
137251881Speter}
138251881Speter
139251881Speter
140251881Speter/* We use these static pointers so we can employ pointer comparison
141251881Speter * of our capabilities hash members instead of strcmp()ing all over
142251881Speter * the place.
143251881Speter */
144251881Speter/* Both server and repository support the capability. */
145251881Speterstatic const char *const capability_yes = "yes";
146251881Speter/* Either server or repository does not support the capability. */
147251881Speterstatic const char *const capability_no = "no";
148251881Speter/* Server supports the capability, but don't yet know if repository does. */
149251881Speterstatic const char *const capability_server_yes = "server-yes";
150251881Speter
151251881Speter
152251881Speter/* This implements serf_bucket_headers_do_callback_fn_t.
153251881Speter */
154251881Speterstatic int
155251881Spetercapabilities_headers_iterator_callback(void *baton,
156251881Speter                                       const char *key,
157251881Speter                                       const char *val)
158251881Speter{
159251881Speter  options_context_t *opt_ctx = baton;
160251881Speter  svn_ra_serf__session_t *session = opt_ctx->session;
161251881Speter
162251881Speter  if (svn_cstring_casecmp(key, "dav") == 0)
163251881Speter    {
164251881Speter      /* Each header may contain multiple values, separated by commas, e.g.:
165251881Speter           DAV: version-control,checkout,working-resource
166251881Speter           DAV: merge,baseline,activity,version-controlled-collection
167251881Speter           DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
168251881Speter      apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
169251881Speter                                                   opt_ctx->pool);
170251881Speter
171362181Sdim      opt_ctx->received_dav_header = TRUE;
172362181Sdim
173251881Speter      /* Right now we only have a few capabilities to detect, so just
174251881Speter         seek for them directly.  This could be written slightly more
175251881Speter         efficiently, but that wouldn't be worth it until we have many
176251881Speter         more capabilities. */
177251881Speter
178251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
179251881Speter        {
180251881Speter          svn_hash_sets(session->capabilities,
181251881Speter                        SVN_RA_CAPABILITY_DEPTH, capability_yes);
182251881Speter        }
183251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
184251881Speter        {
185251881Speter          /* The server doesn't know what repository we're referring
186251881Speter             to, so it can't just say capability_yes. */
187251881Speter          if (!svn_hash_gets(session->capabilities,
188251881Speter                             SVN_RA_CAPABILITY_MERGEINFO))
189251881Speter            {
190251881Speter              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
191251881Speter                            capability_server_yes);
192251881Speter            }
193251881Speter        }
194251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
195251881Speter        {
196251881Speter          svn_hash_sets(session->capabilities,
197251881Speter                        SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
198251881Speter        }
199251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
200251881Speter        {
201251881Speter          svn_hash_sets(session->capabilities,
202251881Speter                        SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
203251881Speter        }
204251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
205251881Speter        {
206251881Speter          svn_hash_sets(session->capabilities,
207251881Speter                        SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
208251881Speter        }
209251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
210251881Speter        {
211251881Speter          svn_hash_sets(session->capabilities,
212251881Speter                        SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
213251881Speter        }
214251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
215251881Speter                                 vals))
216251881Speter        {
217251881Speter          svn_hash_sets(session->capabilities,
218251881Speter                        SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
219251881Speter                        capability_yes);
220251881Speter        }
221251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
222251881Speter        {
223251881Speter          svn_hash_sets(session->capabilities,
224251881Speter                        SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
225251881Speter        }
226251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
227251881Speter        {
228251881Speter          session->supports_inline_props = TRUE;
229251881Speter        }
230251881Speter      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
231251881Speter        {
232251881Speter          session->supports_rev_rsrc_replay = TRUE;
233251881Speter        }
234362181Sdim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF1, vals))
235362181Sdim        {
236362181Sdim          /* Use compressed svndiff1 format for servers that properly
237362181Sdim             advertise this capability (Subversion 1.10 and greater). */
238362181Sdim          session->supports_svndiff1 = TRUE;
239362181Sdim        }
240362181Sdim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LIST, vals))
241362181Sdim        {
242362181Sdim          svn_hash_sets(session->capabilities,
243362181Sdim                        SVN_RA_CAPABILITY_LIST, capability_yes);
244362181Sdim        }
245362181Sdim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF2, vals))
246362181Sdim        {
247362181Sdim          /* Same for svndiff2. */
248362181Sdim          session->supports_svndiff2 = TRUE;
249362181Sdim        }
250362181Sdim      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PUT_RESULT_CHECKSUM, vals))
251362181Sdim        {
252362181Sdim          session->supports_put_result_checksum = TRUE;
253362181Sdim        }
254251881Speter    }
255251881Speter
256251881Speter  /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
257286506Speter  else if (!svn_ctype_casecmp(key[0], 'S')
258286506Speter           && !svn_ctype_casecmp(key[1], 'V')
259286506Speter           && !svn_ctype_casecmp(key[2], 'N'))
260251881Speter    {
261251881Speter      /* If we've not yet seen any information about supported POST
262251881Speter         requests, we'll initialize the list/hash with "create-txn"
263251881Speter         (which we know is supported by virtue of the server speaking
264251881Speter         HTTPv2 at all. */
265251881Speter      if (! session->supported_posts)
266251881Speter        {
267251881Speter          session->supported_posts = apr_hash_make(session->pool);
268251881Speter          apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
269251881Speter        }
270251881Speter
271251881Speter      if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
272251881Speter        {
273251881Speter          session->repos_root = session->session_url;
274251881Speter          session->repos_root.path =
275251881Speter            (char *)svn_fspath__canonicalize(val, session->pool);
276251881Speter          session->repos_root_str =
277251881Speter            svn_urlpath__canonicalize(
278251881Speter                apr_uri_unparse(session->pool, &session->repos_root, 0),
279251881Speter                session->pool);
280251881Speter        }
281251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
282251881Speter        {
283251881Speter#ifdef SVN_DEBUG
284251881Speter          char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
285251881Speter
286251881Speter          if (!(ignore_v2_env_var
287251881Speter                && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
288251881Speter            session->me_resource = apr_pstrdup(session->pool, val);
289251881Speter#else
290251881Speter          session->me_resource = apr_pstrdup(session->pool, val);
291251881Speter#endif
292251881Speter        }
293251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
294251881Speter        {
295251881Speter          session->rev_stub = apr_pstrdup(session->pool, val);
296251881Speter        }
297251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
298251881Speter        {
299251881Speter          session->rev_root_stub = apr_pstrdup(session->pool, val);
300251881Speter        }
301251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
302251881Speter        {
303251881Speter          session->txn_stub = apr_pstrdup(session->pool, val);
304251881Speter        }
305251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
306251881Speter        {
307251881Speter          session->txn_root_stub = apr_pstrdup(session->pool, val);
308251881Speter        }
309251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
310251881Speter        {
311251881Speter          session->vtxn_stub = apr_pstrdup(session->pool, val);
312251881Speter        }
313251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
314251881Speter        {
315251881Speter          session->vtxn_root_stub = apr_pstrdup(session->pool, val);
316251881Speter        }
317251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
318251881Speter        {
319251881Speter          session->uuid = apr_pstrdup(session->pool, val);
320251881Speter        }
321251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
322251881Speter        {
323251881Speter          opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
324251881Speter        }
325251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
326251881Speter        {
327251881Speter          session->server_allows_bulk = apr_pstrdup(session->pool, val);
328251881Speter        }
329251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
330251881Speter        {
331251881Speter          /* May contain multiple values, separated by commas. */
332251881Speter          int i;
333251881Speter          apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
334269833Speter                                                       session->pool);
335251881Speter
336251881Speter          for (i = 0; i < vals->nelts; i++)
337251881Speter            {
338251881Speter              const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
339251881Speter
340251881Speter              svn_hash_sets(session->supported_posts, post_val, (void *)1);
341251881Speter            }
342251881Speter        }
343251881Speter      else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
344251881Speter        {
345251881Speter          if (svn_cstring_casecmp(val, "yes") == 0)
346251881Speter            {
347251881Speter              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
348251881Speter                            capability_yes);
349251881Speter            }
350251881Speter          else if (svn_cstring_casecmp(val, "no") == 0)
351251881Speter            {
352251881Speter              svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
353251881Speter                            capability_no);
354251881Speter            }
355251881Speter        }
356251881Speter    }
357251881Speter
358251881Speter  return 0;
359251881Speter}
360251881Speter
361251881Speter
362251881Speter/* A custom serf_response_handler_t which is mostly a wrapper around
363251881Speter   the expat-based response handler -- it just notices OPTIONS response
364251881Speter   headers first, before handing off to the xml parser.
365251881Speter   Implements svn_ra_serf__response_handler_t */
366251881Speterstatic svn_error_t *
367251881Speteroptions_response_handler(serf_request_t *request,
368251881Speter                         serf_bucket_t *response,
369251881Speter                         void *baton,
370251881Speter                         apr_pool_t *pool)
371251881Speter{
372251881Speter  options_context_t *opt_ctx = baton;
373251881Speter
374251881Speter  if (!opt_ctx->headers_processed)
375251881Speter    {
376251881Speter      svn_ra_serf__session_t *session = opt_ctx->session;
377251881Speter      serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
378362181Sdim      serf_connection_t *conn;
379251881Speter
380251881Speter      /* Start out assuming all capabilities are unsupported. */
381251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
382251881Speter                    capability_no);
383251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
384251881Speter                    capability_no);
385251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
386251881Speter                    NULL);
387251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
388251881Speter                    capability_no);
389251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
390251881Speter                    capability_no);
391251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
392251881Speter                    capability_no);
393251881Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
394251881Speter                    capability_no);
395253734Speter      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
396253734Speter                    capability_no);
397362181Sdim      svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LIST,
398362181Sdim                    capability_no);
399251881Speter
400251881Speter      /* Then see which ones we can discover. */
401251881Speter      serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
402251881Speter                             opt_ctx);
403251881Speter
404362181Sdim      /* Bail out early if we're not talking to a DAV server.
405362181Sdim         Note that this check is only valid if we've received a success
406362181Sdim         response; redirects and errors don't count. */
407362181Sdim      if (opt_ctx->handler->sline.code >= 200
408362181Sdim          && opt_ctx->handler->sline.code < 300
409362181Sdim          && !opt_ctx->received_dav_header)
410362181Sdim        {
411362181Sdim          return svn_error_createf
412362181Sdim            (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
413362181Sdim             _("The server at '%s' does not support the HTTP/DAV protocol"),
414362181Sdim             session->session_url_str);
415362181Sdim        }
416362181Sdim
417289180Speter      /* Assume mergeinfo capability unsupported, if didn't receive information
418251881Speter         about server or repository mergeinfo capability. */
419251881Speter      if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
420251881Speter        svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
421251881Speter                      capability_no);
422251881Speter
423362181Sdim      /* Remember our latency. */
424362181Sdim      conn = serf_request_get_conn(request);
425362181Sdim      session->conn_latency = serf_connection_get_latency(conn);
426362181Sdim
427251881Speter      opt_ctx->headers_processed = TRUE;
428251881Speter    }
429251881Speter
430251881Speter  /* Execute the 'real' response handler to XML-parse the response body. */
431251881Speter  return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
432251881Speter}
433251881Speter
434251881Speter
435251881Speterstatic svn_error_t *
436251881Spetercreate_options_req(options_context_t **opt_ctx,
437251881Speter                   svn_ra_serf__session_t *session,
438251881Speter                   apr_pool_t *pool)
439251881Speter{
440251881Speter  options_context_t *new_ctx;
441251881Speter  svn_ra_serf__xml_context_t *xmlctx;
442251881Speter  svn_ra_serf__handler_t *handler;
443251881Speter
444251881Speter  new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
445251881Speter  new_ctx->pool = pool;
446251881Speter  new_ctx->session = session;
447251881Speter
448251881Speter  new_ctx->youngest_rev = SVN_INVALID_REVNUM;
449251881Speter
450251881Speter  xmlctx = svn_ra_serf__xml_context_create(options_ttable,
451251881Speter                                           NULL, options_closed, NULL,
452251881Speter                                           new_ctx,
453251881Speter                                           pool);
454289180Speter  handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
455251881Speter
456251881Speter  handler->method = "OPTIONS";
457251881Speter  handler->path = session->session_url.path;
458251881Speter  handler->body_delegate = create_options_body;
459251881Speter  handler->body_type = "text/xml";
460251881Speter
461251881Speter  new_ctx->handler = handler;
462251881Speter
463251881Speter  new_ctx->inner_handler = handler->response_handler;
464251881Speter  new_ctx->inner_baton = handler->response_baton;
465251881Speter  handler->response_handler = options_response_handler;
466251881Speter  handler->response_baton = new_ctx;
467251881Speter
468251881Speter  *opt_ctx = new_ctx;
469251881Speter
470251881Speter  return SVN_NO_ERROR;
471251881Speter}
472251881Speter
473251881Speter
474251881Spetersvn_error_t *
475251881Spetersvn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
476289180Speter                                    svn_ra_serf__session_t *session,
477251881Speter                                    apr_pool_t *scratch_pool)
478251881Speter{
479251881Speter  options_context_t *opt_ctx;
480251881Speter
481251881Speter  SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
482251881Speter
483289180Speter  SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
484251881Speter  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
485251881Speter
486289180Speter  if (opt_ctx->handler->sline.code != 200)
487289180Speter    return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
488289180Speter
489289180Speter  if (! SVN_IS_VALID_REVNUM(opt_ctx->youngest_rev))
490289180Speter    return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
491289180Speter                            _("The OPTIONS response did not include "
492289180Speter                              "the youngest revision"));
493289180Speter
494251881Speter  *youngest = opt_ctx->youngest_rev;
495251881Speter
496251881Speter  return SVN_NO_ERROR;
497251881Speter}
498251881Speter
499251881Speter
500251881Spetersvn_error_t *
501251881Spetersvn_ra_serf__v1_get_activity_collection(const char **activity_url,
502289180Speter                                        svn_ra_serf__session_t *session,
503251881Speter                                        apr_pool_t *result_pool,
504251881Speter                                        apr_pool_t *scratch_pool)
505251881Speter{
506251881Speter  options_context_t *opt_ctx;
507251881Speter
508251881Speter  SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
509251881Speter
510289180Speter  if (session->activity_collection_url)
511289180Speter    {
512289180Speter      *activity_url = apr_pstrdup(result_pool,
513289180Speter                                  session->activity_collection_url);
514289180Speter      return SVN_NO_ERROR;
515289180Speter    }
516289180Speter
517289180Speter  SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
518251881Speter  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
519251881Speter
520289180Speter  if (opt_ctx->handler->sline.code != 200)
521289180Speter    return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
522251881Speter
523289180Speter  /* Cache the result. */
524289180Speter  if (opt_ctx->activity_collection)
525289180Speter    {
526289180Speter      session->activity_collection_url =
527289180Speter                    apr_pstrdup(session->pool, opt_ctx->activity_collection);
528289180Speter    }
529289180Speter  else
530289180Speter    {
531289180Speter      return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
532289180Speter                              _("The OPTIONS response did not include the "
533289180Speter                                "requested activity-collection-set value"));
534289180Speter    }
535289180Speter
536251881Speter  *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
537251881Speter
538251881Speter  return SVN_NO_ERROR;
539251881Speter
540251881Speter}
541251881Speter
542251881Speter
543251881Speter
544251881Speter/** Capabilities exchange. */
545251881Speter
546251881Spetersvn_error_t *
547251881Spetersvn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
548251881Speter                                   const char **corrected_url,
549362181Sdim                                   const char **redirect_url,
550289180Speter                                   apr_pool_t *result_pool,
551289180Speter                                   apr_pool_t *scratch_pool)
552251881Speter{
553251881Speter  options_context_t *opt_ctx;
554251881Speter
555289180Speter  if (corrected_url)
556289180Speter    *corrected_url = NULL;
557362181Sdim  if (redirect_url)
558362181Sdim    *redirect_url = NULL;
559289180Speter
560251881Speter  /* This routine automatically fills in serf_sess->capabilities */
561289180Speter  SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_pool));
562251881Speter
563289180Speter  opt_ctx->handler->no_fail_on_http_redirect_status = TRUE;
564251881Speter
565289180Speter  SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
566289180Speter
567251881Speter  /* If our caller cares about server redirections, and our response
568251881Speter     carries such a thing, report as much.  We'll disregard ERR --
569251881Speter     it's most likely just a complaint about the response body not
570251881Speter     successfully parsing as XML or somesuch. */
571251881Speter  if (corrected_url && (opt_ctx->handler->sline.code == 301))
572251881Speter    {
573289180Speter      if (!opt_ctx->handler->location || !*opt_ctx->handler->location)
574289180Speter        {
575289180Speter          return svn_error_create(
576289180Speter                    SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
577289180Speter                    _("Location header not set on redirect response"));
578289180Speter        }
579289180Speter      else if (svn_path_is_url(opt_ctx->handler->location))
580289180Speter        {
581362181Sdim          SVN_ERR(svn_uri_canonicalize_safe(corrected_url, NULL,
582362181Sdim              opt_ctx->handler->location, result_pool, scratch_pool));
583362181Sdim          if (redirect_url)
584362181Sdim            *redirect_url = apr_pstrdup(result_pool,
585362181Sdim                                        opt_ctx->handler->location);
586289180Speter        }
587289180Speter      else
588289180Speter        {
589289180Speter          /* RFC1945 and RFC2616 state that the Location header's value
590289180Speter             (from whence this CORRECTED_URL comes), if present, must be an
591289180Speter             absolute URI.  But some Apache versions (those older than 2.2.11,
592289180Speter             it seems) transmit only the path portion of the URI.
593289180Speter             See issue #3775 for details. */
594289180Speter
595289180Speter          apr_uri_t corrected_URI = serf_sess->session_url;
596362181Sdim          char *absolute_uri;
597289180Speter
598289180Speter          corrected_URI.path = (char *)corrected_url;
599362181Sdim          absolute_uri = apr_uri_unparse(scratch_pool, &corrected_URI, 0);
600362181Sdim          SVN_ERR(svn_uri_canonicalize_safe(corrected_url, NULL,
601362181Sdim              absolute_uri, result_pool, scratch_pool));
602362181Sdim          if (redirect_url)
603362181Sdim            *redirect_url = apr_pstrdup(result_pool, absolute_uri);
604289180Speter        }
605289180Speter
606251881Speter      return SVN_NO_ERROR;
607251881Speter    }
608289180Speter  else if (opt_ctx->handler->sline.code >= 300
609289180Speter           && opt_ctx->handler->sline.code < 399)
610289180Speter    {
611289180Speter      return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL,
612289180Speter                               (opt_ctx->handler->sline.code == 301
613289180Speter                                ? _("Repository moved permanently to '%s'")
614289180Speter                                : _("Repository moved temporarily to '%s'")),
615289180Speter                              opt_ctx->handler->location);
616289180Speter    }
617251881Speter
618289180Speter  if (opt_ctx->handler->sline.code != 200)
619289180Speter    return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
620251881Speter
621251881Speter  /* Opportunistically cache any reported activity URL.  (We don't
622251881Speter     want to have to ask for this again later, potentially against an
623251881Speter     unreadable commit anchor URL.)  */
624251881Speter  if (opt_ctx->activity_collection)
625251881Speter    {
626251881Speter      serf_sess->activity_collection_url =
627251881Speter        apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
628251881Speter    }
629251881Speter
630251881Speter  return SVN_NO_ERROR;
631251881Speter}
632251881Speter
633289180Speter/* Implements svn_ra_serf__request_body_delegate_t */
634253734Speterstatic svn_error_t *
635253734Spetercreate_simple_options_body(serf_bucket_t **body_bkt,
636253734Speter                           void *baton,
637253734Speter                           serf_bucket_alloc_t *alloc,
638289180Speter                           apr_pool_t *pool /* request pool */,
639289180Speter                           apr_pool_t *scratch_pool)
640253734Speter{
641253734Speter  serf_bucket_t *body;
642253734Speter  serf_bucket_t *s;
643253734Speter
644253734Speter  body = serf_bucket_aggregate_create(alloc);
645253734Speter  svn_ra_serf__add_xml_header_buckets(body, alloc);
646253734Speter
647253734Speter  s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
648253734Speter  serf_bucket_aggregate_append(body, s);
649253734Speter
650253734Speter  *body_bkt = body;
651253734Speter  return SVN_NO_ERROR;
652253734Speter}
653253734Speter
654253734Speter
655251881Spetersvn_error_t *
656253734Spetersvn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
657253734Speter                         apr_pool_t *scratch_pool)
658253734Speter{
659253734Speter  svn_ra_serf__handler_t *handler;
660253734Speter
661289180Speter  handler = svn_ra_serf__create_handler(serf_sess, scratch_pool);
662253734Speter  handler->method = "OPTIONS";
663253734Speter  handler->path = serf_sess->session_url.path;
664253734Speter
665253734Speter  /* We don't care about the response body, so discard it.  */
666253734Speter  handler->response_handler = svn_ra_serf__handle_discard_body;
667253734Speter
668253734Speter  /* We need a simple body, in order to send it in chunked format.  */
669253734Speter  handler->body_delegate = create_simple_options_body;
670289180Speter  handler->no_fail_on_http_failure_status = TRUE;
671253734Speter
672253734Speter  /* No special headers.  */
673253734Speter
674253734Speter  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
675253734Speter  /* Some versions of nginx in reverse proxy mode will return 411. They want
676253734Speter     a Content-Length header, rather than chunked requests. We can keep other
677253734Speter     HTTP/1.1 features, but will disable the chunking.  */
678253734Speter  if (handler->sline.code == 411)
679253734Speter    {
680253734Speter      serf_sess->using_chunked_requests = FALSE;
681253734Speter
682253734Speter      return SVN_NO_ERROR;
683253734Speter    }
684289180Speter  if (handler->sline.code != 200)
685289180Speter    SVN_ERR(svn_ra_serf__unexpected_status(handler));
686253734Speter
687253734Speter  return SVN_NO_ERROR;
688253734Speter}
689253734Speter
690253734Speter
691253734Spetersvn_error_t *
692251881Spetersvn_ra_serf__has_capability(svn_ra_session_t *ra_session,
693251881Speter                            svn_boolean_t *has,
694251881Speter                            const char *capability,
695251881Speter                            apr_pool_t *pool)
696251881Speter{
697251881Speter  svn_ra_serf__session_t *serf_sess = ra_session->priv;
698251881Speter  const char *cap_result;
699251881Speter
700251881Speter  /* This capability doesn't rely on anything server side. */
701251881Speter  if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
702251881Speter    {
703251881Speter      *has = TRUE;
704251881Speter      return SVN_NO_ERROR;
705251881Speter    }
706251881Speter
707251881Speter  cap_result = svn_hash_gets(serf_sess->capabilities, capability);
708251881Speter
709251881Speter  /* If any capability is unknown, they're all unknown, so ask. */
710251881Speter  if (cap_result == NULL)
711362181Sdim    SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, NULL,
712362181Sdim                                               pool, pool));
713251881Speter
714251881Speter  /* Try again, now that we've fetched the capabilities. */
715251881Speter  cap_result = svn_hash_gets(serf_sess->capabilities, capability);
716251881Speter
717251881Speter  /* Some capabilities depend on the repository as well as the server. */
718251881Speter  if (cap_result == capability_server_yes)
719251881Speter    {
720251881Speter      if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
721251881Speter        {
722251881Speter          /* Handle mergeinfo specially.  Mergeinfo depends on the
723251881Speter             repository as well as the server, but the server routine
724251881Speter             that answered our svn_ra_serf__exchange_capabilities() call above
725251881Speter             didn't even know which repository we were interested in
726251881Speter             -- it just told us whether the server supports mergeinfo.
727251881Speter             If the answer was 'no', there's no point checking the
728251881Speter             particular repository; but if it was 'yes', we still must
729251881Speter             change it to 'no' iff the repository itself doesn't
730251881Speter             support mergeinfo. */
731251881Speter          svn_mergeinfo_catalog_t ignored;
732251881Speter          svn_error_t *err;
733251881Speter          apr_array_header_t *paths = apr_array_make(pool, 1,
734251881Speter                                                     sizeof(char *));
735251881Speter          APR_ARRAY_PUSH(paths, const char *) = "";
736251881Speter
737251881Speter          err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
738289180Speter                                           svn_mergeinfo_explicit,
739289180Speter                                           FALSE /* include_descendants */,
740289180Speter                                           pool);
741251881Speter
742251881Speter          if (err)
743251881Speter            {
744251881Speter              if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
745251881Speter                {
746251881Speter                  svn_error_clear(err);
747251881Speter                  cap_result = capability_no;
748251881Speter                }
749251881Speter              else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
750251881Speter                {
751251881Speter                  /* Mergeinfo requests use relative paths, and
752251881Speter                     anyway we're in r0, so this is a likely error,
753251881Speter                     but it means the repository supports mergeinfo! */
754251881Speter                  svn_error_clear(err);
755251881Speter                  cap_result = capability_yes;
756251881Speter                }
757251881Speter              else
758289180Speter                return svn_error_trace(err);
759251881Speter            }
760251881Speter          else
761251881Speter            cap_result = capability_yes;
762251881Speter
763251881Speter          svn_hash_sets(serf_sess->capabilities,
764251881Speter                        SVN_RA_CAPABILITY_MERGEINFO,  cap_result);
765251881Speter        }
766251881Speter      else
767251881Speter        {
768251881Speter          return svn_error_createf
769251881Speter            (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
770251881Speter             _("Don't know how to handle '%s' for capability '%s'"),
771251881Speter             capability_server_yes, capability);
772251881Speter        }
773251881Speter    }
774251881Speter
775251881Speter  if (cap_result == capability_yes)
776251881Speter    {
777251881Speter      *has = TRUE;
778251881Speter    }
779251881Speter  else if (cap_result == capability_no)
780251881Speter    {
781251881Speter      *has = FALSE;
782251881Speter    }
783251881Speter  else if (cap_result == NULL)
784251881Speter    {
785251881Speter      return svn_error_createf
786251881Speter        (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
787251881Speter         _("Don't know anything about capability '%s'"), capability);
788251881Speter    }
789251881Speter  else  /* "can't happen" */
790251881Speter    {
791251881Speter      /* Well, let's hope it's a string. */
792251881Speter      return svn_error_createf
793251881Speter        (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
794251881Speter         _("Attempt to fetch capability '%s' resulted in '%s'"),
795251881Speter         capability, cap_result);
796251881Speter    }
797251881Speter
798251881Speter  return SVN_NO_ERROR;
799251881Speter}
800