serf.c revision 289180
1/*
2 * serf.c :  entry point for ra_serf
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#define APR_WANT_STRFUNC
27#include <apr_want.h>
28
29#include <apr_uri.h>
30#include <serf.h>
31
32#include "svn_pools.h"
33#include "svn_ra.h"
34#include "svn_dav.h"
35#include "svn_xml.h"
36#include "../libsvn_ra/ra_loader.h"
37#include "svn_config.h"
38#include "svn_delta.h"
39#include "svn_dirent_uri.h"
40#include "svn_hash.h"
41#include "svn_path.h"
42#include "svn_props.h"
43#include "svn_time.h"
44#include "svn_version.h"
45
46#include "private/svn_dav_protocol.h"
47#include "private/svn_dep_compat.h"
48#include "private/svn_fspath.h"
49#include "private/svn_subr_private.h"
50#include "svn_private_config.h"
51
52#include "ra_serf.h"
53
54
55/* Implements svn_ra__vtable_t.get_version(). */
56static const svn_version_t *
57ra_serf_version(void)
58{
59  SVN_VERSION_BODY;
60}
61
62#define RA_SERF_DESCRIPTION \
63    N_("Module for accessing a repository via WebDAV protocol using serf.")
64
65#define RA_SERF_DESCRIPTION_VER \
66    N_("Module for accessing a repository via WebDAV protocol using serf.\n" \
67       "  - using serf %d.%d.%d")
68
69/* Implements svn_ra__vtable_t.get_description(). */
70static const char *
71ra_serf_get_description(apr_pool_t *pool)
72{
73  int major, minor, patch;
74
75  serf_lib_version(&major, &minor, &patch);
76  return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), major, minor, patch);
77}
78
79/* Implements svn_ra__vtable_t.get_schemes(). */
80static const char * const *
81ra_serf_get_schemes(apr_pool_t *pool)
82{
83  static const char *serf_ssl[] = { "http", "https", NULL };
84#if 0
85  /* ### Temporary: to shut up a warning. */
86  static const char *serf_no_ssl[] = { "http", NULL };
87#endif
88
89  /* TODO: Runtime detection. */
90  return serf_ssl;
91}
92
93/* Load the setting http-auth-types from the global or server specific
94   section, parse its value and set the types of authentication we should
95   accept from the server. */
96static svn_error_t *
97load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
98                     const char *server_group,
99                     int *authn_types)
100{
101  const char *http_auth_types = NULL;
102  *authn_types = SERF_AUTHN_NONE;
103
104  svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
105               SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
106
107  if (server_group)
108    {
109      svn_config_get(config, &http_auth_types, server_group,
110                     SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types);
111    }
112
113  if (http_auth_types)
114    {
115      char *token;
116      char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
117      apr_collapse_spaces(auth_types_list, http_auth_types);
118      while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL)
119        {
120          if (svn_cstring_casecmp("basic", token) == 0)
121            *authn_types |= SERF_AUTHN_BASIC;
122          else if (svn_cstring_casecmp("digest", token) == 0)
123            *authn_types |= SERF_AUTHN_DIGEST;
124          else if (svn_cstring_casecmp("ntlm", token) == 0)
125            *authn_types |= SERF_AUTHN_NTLM;
126          else if (svn_cstring_casecmp("negotiate", token) == 0)
127            *authn_types |= SERF_AUTHN_NEGOTIATE;
128          else
129            return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
130                                     _("Invalid config: unknown %s "
131                                       "'%s'"),
132                                     SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token);
133      }
134    }
135  else
136    {
137      /* Nothing specified by the user, so accept all types. */
138      *authn_types = SERF_AUTHN_ALL;
139    }
140
141  return SVN_NO_ERROR;
142}
143
144/* Default HTTP timeout (in seconds); overridden by the 'http-timeout'
145   runtime configuration variable. */
146#define DEFAULT_HTTP_TIMEOUT 600
147
148static svn_error_t *
149load_config(svn_ra_serf__session_t *session,
150            apr_hash_t *config_hash,
151            apr_pool_t *pool)
152{
153  svn_config_t *config, *config_client;
154  const char *server_group;
155  const char *proxy_host = NULL;
156  const char *port_str = NULL;
157  const char *timeout_str = NULL;
158  const char *exceptions;
159  apr_port_t proxy_port;
160  svn_tristate_t chunked_requests;
161#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING)
162  apr_int64_t log_components;
163  apr_int64_t log_level;
164#endif
165
166  if (config_hash)
167    {
168      config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS);
169      config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
170    }
171  else
172    {
173      config = NULL;
174      config_client = NULL;
175    }
176
177  SVN_ERR(svn_config_get_bool(config, &session->using_compression,
178                              SVN_CONFIG_SECTION_GLOBAL,
179                              SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
180  svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
181                 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
182
183  if (session->auth_baton)
184    {
185      if (config_client)
186        {
187          svn_auth_set_parameter(session->auth_baton,
188                                 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
189                                 config_client);
190        }
191      if (config)
192        {
193          svn_auth_set_parameter(session->auth_baton,
194                                 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS,
195                                 config);
196        }
197    }
198
199  /* Use the default proxy-specific settings if and only if
200     "http-proxy-exceptions" is not set to exclude this host. */
201  svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
202                 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
203  if (! svn_cstring_match_glob_list(session->session_url.hostname,
204                                    svn_cstring_split(exceptions, ",",
205                                                      TRUE, pool)))
206    {
207      svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
208                     SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
209      svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
210                     SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
211      svn_config_get(config, &session->proxy_username,
212                     SVN_CONFIG_SECTION_GLOBAL,
213                     SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
214      svn_config_get(config, &session->proxy_password,
215                     SVN_CONFIG_SECTION_GLOBAL,
216                     SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
217    }
218
219  /* Load the global ssl settings, if set. */
220  SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
221                              SVN_CONFIG_SECTION_GLOBAL,
222                              SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
223                              TRUE));
224  svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
225                 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
226
227  /* If set, read the flag that tells us to do bulk updates or not. Defaults
228     to skelta updates. */
229  SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
230                                  SVN_CONFIG_SECTION_GLOBAL,
231                                  SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
232                                  "auto",
233                                  svn_tristate_unknown));
234
235  /* Load the maximum number of parallel session connections. */
236  SVN_ERR(svn_config_get_int64(config, &session->max_connections,
237                               SVN_CONFIG_SECTION_GLOBAL,
238                               SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
239                               SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS));
240
241  /* Should we use chunked transfer encoding. */
242  SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
243                                  SVN_CONFIG_SECTION_GLOBAL,
244                                  SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS,
245                                  "auto", svn_tristate_unknown));
246
247#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING)
248  SVN_ERR(svn_config_get_int64(config, &log_components,
249                               SVN_CONFIG_SECTION_GLOBAL,
250                               SVN_CONFIG_OPTION_SERF_LOG_COMPONENTS,
251                               SERF_LOGCOMP_NONE));
252  SVN_ERR(svn_config_get_int64(config, &log_level,
253                               SVN_CONFIG_SECTION_GLOBAL,
254                               SVN_CONFIG_OPTION_SERF_LOG_LEVEL,
255                               SERF_LOG_INFO));
256#endif
257
258  server_group = svn_auth_get_parameter(session->auth_baton,
259                                        SVN_AUTH_PARAM_SERVER_GROUP);
260
261  if (server_group)
262    {
263      SVN_ERR(svn_config_get_bool(config, &session->using_compression,
264                                  server_group,
265                                  SVN_CONFIG_OPTION_HTTP_COMPRESSION,
266                                  session->using_compression));
267      svn_config_get(config, &timeout_str, server_group,
268                     SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
269
270      /* Load the group proxy server settings, overriding global
271         settings.  We intentionally ignore 'http-proxy-exceptions'
272         here because, well, if this site was an exception, why is
273         there a per-server proxy configuration for it?  */
274      svn_config_get(config, &proxy_host, server_group,
275                     SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
276      svn_config_get(config, &port_str, server_group,
277                     SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
278      svn_config_get(config, &session->proxy_username, server_group,
279                     SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
280                     session->proxy_username);
281      svn_config_get(config, &session->proxy_password, server_group,
282                     SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
283                     session->proxy_password);
284
285      /* Load the group ssl settings. */
286      SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
287                                  server_group,
288                                  SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
289                                  session->trust_default_ca));
290      svn_config_get(config, &session->ssl_authorities, server_group,
291                     SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
292                     session->ssl_authorities);
293
294      /* Load the group bulk updates flag. */
295      SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
296                                      server_group,
297                                      SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
298                                      "auto",
299                                      session->bulk_updates));
300
301      /* Load the maximum number of parallel session connections,
302         overriding global values. */
303      SVN_ERR(svn_config_get_int64(config, &session->max_connections,
304                                   server_group,
305                                   SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
306                                   session->max_connections));
307
308      /* Should we use chunked transfer encoding. */
309      SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
310                                      server_group,
311                                      SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS,
312                                      "auto", chunked_requests));
313
314#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING)
315      SVN_ERR(svn_config_get_int64(config, &log_components,
316                                   server_group,
317                                   SVN_CONFIG_OPTION_SERF_LOG_COMPONENTS,
318                                   log_components));
319       SVN_ERR(svn_config_get_int64(config, &log_level,
320                                    server_group,
321                                    SVN_CONFIG_OPTION_SERF_LOG_LEVEL,
322                                    log_level));
323#endif
324    }
325
326#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING)
327  if (log_components != SERF_LOGCOMP_NONE)
328    {
329      serf_log_output_t *output;
330      apr_status_t status;
331
332      status = serf_logging_create_stream_output(&output,
333                                                 session->context,
334                                                 (apr_uint32_t)log_level,
335                                                 (apr_uint32_t)log_components,
336                                                 SERF_LOG_DEFAULT_LAYOUT,
337                                                 stderr,
338                                                 pool);
339
340      if (!status)
341          serf_logging_add_output(session->context, output);
342    }
343#endif
344
345  /* Don't allow the http-max-connections value to be larger than our
346     compiled-in limit, or to be too small to operate.  Broken
347     functionality and angry administrators are equally undesirable. */
348  if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT)
349    session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT;
350  if (session->max_connections < 2)
351    session->max_connections = 2;
352
353  /* Parse the connection timeout value, if any. */
354  session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
355  if (timeout_str)
356    {
357      char *endstr;
358      const long int timeout = strtol(timeout_str, &endstr, 10);
359
360      if (*endstr)
361        return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
362                                _("Invalid config: illegal character in "
363                                  "timeout value"));
364      if (timeout < 0)
365        return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
366                                _("Invalid config: negative timeout value"));
367      session->timeout = apr_time_from_sec(timeout);
368    }
369  SVN_ERR_ASSERT(session->timeout >= 0);
370
371  /* Convert the proxy port value, if any. */
372  if (port_str)
373    {
374      char *endstr;
375      const long int port = strtol(port_str, &endstr, 10);
376
377      if (*endstr)
378        return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
379                                _("Invalid URL: illegal character in proxy "
380                                  "port number"));
381      if (port < 0)
382        return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
383                                _("Invalid URL: negative proxy port number"));
384      if (port > 65535)
385        return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
386                                _("Invalid URL: proxy port number greater "
387                                  "than maximum TCP port number 65535"));
388      proxy_port = (apr_port_t) port;
389    }
390  else
391    {
392      proxy_port = 80;
393    }
394
395  if (proxy_host)
396    {
397      apr_sockaddr_t *proxy_addr;
398      apr_status_t status;
399
400      status = apr_sockaddr_info_get(&proxy_addr, proxy_host,
401                                     APR_UNSPEC, proxy_port, 0,
402                                     session->pool);
403      if (status)
404        {
405          return svn_ra_serf__wrap_err(
406                   status, _("Could not resolve proxy server '%s'"),
407                   proxy_host);
408        }
409      session->using_proxy = TRUE;
410      serf_config_proxy(session->context, proxy_addr);
411    }
412  else
413    {
414      session->using_proxy = FALSE;
415    }
416
417  /* Setup detect_chunking and using_chunked_requests based on
418   * the chunked_requests tristate */
419  if (chunked_requests == svn_tristate_unknown)
420    {
421      session->detect_chunking = TRUE;
422      session->using_chunked_requests = TRUE;
423    }
424  else if (chunked_requests == svn_tristate_true)
425    {
426      session->detect_chunking = FALSE;
427      session->using_chunked_requests = TRUE;
428    }
429  else /* chunked_requests == svn_tristate_false */
430    {
431      session->detect_chunking = FALSE;
432      session->using_chunked_requests = FALSE;
433    }
434
435  /* Setup authentication. */
436  SVN_ERR(load_http_auth_types(pool, config, server_group,
437                               &session->authn_types));
438  serf_config_authn_types(session->context, session->authn_types);
439  serf_config_credentials_callback(session->context,
440                                   svn_ra_serf__credentials_callback);
441
442  return SVN_NO_ERROR;
443}
444#undef DEFAULT_HTTP_TIMEOUT
445
446static void
447svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
448{
449  const svn_ra_serf__session_t *serf_sess = progress_baton;
450  if (serf_sess->progress_func)
451    {
452      serf_sess->progress_func(read + written, -1,
453                               serf_sess->progress_baton,
454                               serf_sess->pool);
455    }
456}
457
458/** Our User-Agent string. */
459static const char *
460get_user_agent_string(apr_pool_t *pool)
461{
462  int major, minor, patch;
463  serf_lib_version(&major, &minor, &patch);
464
465  return apr_psprintf(pool, "SVN/%s (%s) serf/%d.%d.%d",
466                      SVN_VER_NUMBER, SVN_BUILD_TARGET,
467                      major, minor, patch);
468}
469
470/* Implements svn_ra__vtable_t.open_session(). */
471static svn_error_t *
472svn_ra_serf__open(svn_ra_session_t *session,
473                  const char **corrected_url,
474                  const char *session_URL,
475                  const svn_ra_callbacks2_t *callbacks,
476                  void *callback_baton,
477                  svn_auth_baton_t *auth_baton,
478                  apr_hash_t *config,
479                  apr_pool_t *result_pool,
480                  apr_pool_t *scratch_pool)
481{
482  apr_status_t status;
483  svn_ra_serf__session_t *serf_sess;
484  apr_uri_t url;
485  const char *client_string = NULL;
486  svn_error_t *err;
487
488  if (corrected_url)
489    *corrected_url = NULL;
490
491  serf_sess = apr_pcalloc(result_pool, sizeof(*serf_sess));
492  serf_sess->pool = result_pool;
493  if (config)
494    SVN_ERR(svn_config_copy_config(&serf_sess->config, config, result_pool));
495  else
496    serf_sess->config = NULL;
497  serf_sess->wc_callbacks = callbacks;
498  serf_sess->wc_callback_baton = callback_baton;
499  serf_sess->auth_baton = auth_baton;
500  serf_sess->progress_func = callbacks->progress_func;
501  serf_sess->progress_baton = callbacks->progress_baton;
502  serf_sess->cancel_func = callbacks->cancel_func;
503  serf_sess->cancel_baton = callback_baton;
504
505  /* todo: reuse serf context across sessions */
506  serf_sess->context = serf_context_create(serf_sess->pool);
507
508  SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache,
509                                       serf_sess->pool));
510
511
512  status = apr_uri_parse(serf_sess->pool, session_URL, &url);
513  if (status)
514    {
515      return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
516                               _("Illegal URL '%s'"),
517                               session_URL);
518    }
519  /* Depending the version of apr-util in use, for root paths url.path
520     will be NULL or "", where serf requires "/". */
521  if (url.path == NULL || url.path[0] == '\0')
522    {
523      url.path = apr_pstrdup(serf_sess->pool, "/");
524    }
525  if (!url.port)
526    {
527      url.port = apr_uri_port_of_scheme(url.scheme);
528    }
529  serf_sess->session_url = url;
530  serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL);
531  serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
532
533  serf_sess->supports_deadprop_count = svn_tristate_unknown;
534
535  serf_sess->capabilities = apr_hash_make(serf_sess->pool);
536
537  /* We have to assume that the server only supports HTTP/1.0. Once it's clear
538     HTTP/1.1 is supported, we can upgrade. */
539  serf_sess->http10 = TRUE;
540
541  /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable
542     this, if we find an intervening proxy does not support chunked requests.  */
543  serf_sess->using_chunked_requests = TRUE;
544
545  SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
546
547  serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
548                                    sizeof(*serf_sess->conns[0]));
549  serf_sess->conns[0]->bkt_alloc =
550          serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
551  serf_sess->conns[0]->session = serf_sess;
552  serf_sess->conns[0]->last_status_code = -1;
553
554  /* create the user agent string */
555  if (callbacks->get_client_string)
556    SVN_ERR(callbacks->get_client_string(callback_baton, &client_string,
557                                         scratch_pool));
558
559  if (client_string)
560    serf_sess->useragent = apr_pstrcat(result_pool,
561                                       get_user_agent_string(scratch_pool),
562                                       " ",
563                                       client_string, SVN_VA_NULL);
564  else
565    serf_sess->useragent = get_user_agent_string(result_pool);
566
567  /* go ahead and tell serf about the connection. */
568  status =
569    serf_connection_create2(&serf_sess->conns[0]->conn,
570                            serf_sess->context,
571                            url,
572                            svn_ra_serf__conn_setup, serf_sess->conns[0],
573                            svn_ra_serf__conn_closed, serf_sess->conns[0],
574                            serf_sess->pool);
575  if (status)
576    return svn_ra_serf__wrap_err(status, NULL);
577
578  /* Set the progress callback. */
579  serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
580                               serf_sess);
581
582  serf_sess->num_conns = 1;
583
584  session->priv = serf_sess;
585
586  /* The following code explicitly works around a bug in serf <= r2319 / 1.3.8
587     where serf doesn't report the request as failed/cancelled when the
588     authorization request handler fails to handle the request.
589
590     As long as we allocate the request in a subpool of the serf connection
591     pool, we know that the handler is always cleaned before the connection.
592
593     Luckily our caller now passes us two pools which handle this case.
594   */
595#if defined(SVN_DEBUG) && !SERF_VERSION_AT_LEAST(1,4,0)
596  /* Currently ensured by svn_ra_open4().
597     If failing causes segfault in basic_tests.py 48, "basic auth test" */
598  SVN_ERR_ASSERT((serf_sess->pool != scratch_pool)
599                 && apr_pool_is_ancestor(serf_sess->pool, scratch_pool));
600#endif
601
602  err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url,
603                                            result_pool, scratch_pool);
604
605  /* serf should produce a usable error code instead of APR_EGENERAL */
606  if (err && err->apr_err == APR_EGENERAL)
607    err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err,
608                            _("Connection to '%s' failed"), session_URL);
609  SVN_ERR(err);
610
611  /* We have set up a useful connection (that doesn't indication a redirect).
612     If we've been told there is possibly a worrisome proxy in our path to the
613     server AND we switched to HTTP/1.1 (chunked requests), then probe for
614     problems in any proxy.  */
615  if ((corrected_url == NULL || *corrected_url == NULL)
616      && serf_sess->detect_chunking && !serf_sess->http10)
617    SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, scratch_pool));
618
619  return SVN_NO_ERROR;
620}
621
622/* Implements svn_ra__vtable_t.dup_session */
623static svn_error_t *
624ra_serf_dup_session(svn_ra_session_t *new_session,
625                    svn_ra_session_t *old_session,
626                    const char *new_session_url,
627                    apr_pool_t *result_pool,
628                    apr_pool_t *scratch_pool)
629{
630  svn_ra_serf__session_t *old_sess = old_session->priv;
631  svn_ra_serf__session_t *new_sess;
632  apr_status_t status;
633
634  new_sess = apr_pmemdup(result_pool, old_sess, sizeof(*new_sess));
635
636  new_sess->pool = result_pool;
637
638  if (new_sess->config)
639    SVN_ERR(svn_config_copy_config(&new_sess->config, new_sess->config,
640                                   result_pool));
641
642  /* max_connections */
643  /* using_ssl */
644  /* using_compression */
645  /* http10 */
646  /* using_chunked_requests */
647  /* detect_chunking */
648
649  if (new_sess->useragent)
650    new_sess->useragent = apr_pstrdup(result_pool, new_sess->useragent);
651
652  if (new_sess->vcc_url)
653    new_sess->vcc_url = apr_pstrdup(result_pool, new_sess->vcc_url);
654
655  new_sess->auth_state = NULL;
656  new_sess->auth_attempts = 0;
657
658  /* Callback functions to get info from WC */
659  /* wc_callbacks */
660  /* wc_callback_baton */
661
662  /* progress_func */
663  /* progress_baton */
664
665  /* cancel_func */
666  /* cancel_baton */
667
668  /* shim_callbacks */
669
670  new_sess->pending_error = NULL;
671
672  /* authn_types */
673
674  /* Keys and values are static */
675  if (new_sess->capabilities)
676    new_sess->capabilities = apr_hash_copy(result_pool, new_sess->capabilities);
677
678  if (new_sess->activity_collection_url)
679    {
680      new_sess->activity_collection_url
681                = apr_pstrdup(result_pool, new_sess->activity_collection_url);
682    }
683
684   /* using_proxy */
685
686  if (new_sess->proxy_username)
687    {
688      new_sess->proxy_username
689                = apr_pstrdup(result_pool, new_sess->proxy_username);
690    }
691
692  if (new_sess->proxy_password)
693    {
694      new_sess->proxy_username
695                = apr_pstrdup(result_pool, new_sess->proxy_password);
696    }
697
698  new_sess->proxy_auth_attempts = 0;
699
700  /* trust_default_ca */
701
702  if (new_sess->ssl_authorities)
703    {
704      new_sess->ssl_authorities = apr_pstrdup(result_pool,
705                                              new_sess->ssl_authorities);
706    }
707
708  if (new_sess->uuid)
709    new_sess->uuid = apr_pstrdup(result_pool, new_sess->uuid);
710
711  /* timeout */
712  /* supports_deadprop_count */
713
714  if (new_sess->me_resource)
715    new_sess->me_resource = apr_pstrdup(result_pool, new_sess->me_resource);
716  if (new_sess->rev_stub)
717    new_sess->rev_stub = apr_pstrdup(result_pool, new_sess->rev_stub);
718  if (new_sess->txn_stub)
719    new_sess->txn_stub = apr_pstrdup(result_pool, new_sess->txn_stub);
720  if (new_sess->txn_root_stub)
721    new_sess->txn_root_stub = apr_pstrdup(result_pool,
722                                          new_sess->txn_root_stub);
723  if (new_sess->vtxn_stub)
724    new_sess->vtxn_stub = apr_pstrdup(result_pool, new_sess->vtxn_stub);
725  if (new_sess->vtxn_root_stub)
726    new_sess->vtxn_root_stub = apr_pstrdup(result_pool,
727                                           new_sess->vtxn_root_stub);
728
729  /* Keys and values are static */
730  if (new_sess->supported_posts)
731    new_sess->supported_posts = apr_hash_copy(result_pool,
732                                              new_sess->supported_posts);
733
734  /* ### Can we copy this? */
735  SVN_ERR(svn_ra_serf__blncache_create(&new_sess->blncache,
736                                       new_sess->pool));
737
738  if (new_sess->server_allows_bulk)
739    new_sess->server_allows_bulk = apr_pstrdup(result_pool,
740                                               new_sess->server_allows_bulk);
741
742  new_sess->repos_root_str = apr_pstrdup(result_pool,
743                                         new_sess->repos_root_str);
744  status = apr_uri_parse(result_pool, new_sess->repos_root_str,
745                         &new_sess->repos_root);
746  if (status)
747    return svn_ra_serf__wrap_err(status, NULL);
748
749  new_sess->session_url_str = apr_pstrdup(result_pool, new_session_url);
750
751  status = apr_uri_parse(result_pool, new_sess->session_url_str,
752                         &new_sess->session_url);
753
754  if (status)
755    return svn_ra_serf__wrap_err(status, NULL);
756
757  /* svn_boolean_t supports_inline_props */
758  /* supports_rev_rsrc_replay */
759
760  new_sess->context = serf_context_create(result_pool);
761
762  SVN_ERR(load_config(new_sess, old_sess->config, result_pool));
763
764  new_sess->conns[0] = apr_pcalloc(result_pool,
765                                   sizeof(*new_sess->conns[0]));
766  new_sess->conns[0]->bkt_alloc =
767          serf_bucket_allocator_create(result_pool, NULL, NULL);
768  new_sess->conns[0]->session = new_sess;
769  new_sess->conns[0]->last_status_code = -1;
770
771  /* go ahead and tell serf about the connection. */
772  status =
773    serf_connection_create2(&new_sess->conns[0]->conn,
774                            new_sess->context,
775                            new_sess->session_url,
776                            svn_ra_serf__conn_setup, new_sess->conns[0],
777                            svn_ra_serf__conn_closed, new_sess->conns[0],
778                            result_pool);
779  if (status)
780    return svn_ra_serf__wrap_err(status, NULL);
781
782  /* Set the progress callback. */
783  serf_context_set_progress_cb(new_sess->context, svn_ra_serf__progress,
784                               new_sess);
785
786  new_sess->num_conns = 1;
787  new_sess->cur_conn = 0;
788
789  new_session->priv = new_sess;
790
791  return SVN_NO_ERROR;
792}
793
794/* Implements svn_ra__vtable_t.reparent(). */
795svn_error_t *
796svn_ra_serf__reparent(svn_ra_session_t *ra_session,
797                      const char *url,
798                      apr_pool_t *pool)
799{
800  svn_ra_serf__session_t *session = ra_session->priv;
801  apr_uri_t new_url;
802  apr_status_t status;
803
804  /* If it's the URL we already have, wave our hands and do nothing. */
805  if (strcmp(session->session_url_str, url) == 0)
806    {
807      return SVN_NO_ERROR;
808    }
809
810  if (!session->repos_root_str)
811    {
812      const char *vcc_url;
813      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
814    }
815
816  if (!svn_uri__is_ancestor(session->repos_root_str, url))
817    {
818      return svn_error_createf(
819          SVN_ERR_RA_ILLEGAL_URL, NULL,
820          _("URL '%s' is not a child of the session's repository root "
821            "URL '%s'"), url, session->repos_root_str);
822    }
823
824  status = apr_uri_parse(pool, url, &new_url);
825  if (status)
826    {
827      return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
828                               _("Illegal repository URL '%s'"), url);
829    }
830
831  /* Depending the version of apr-util in use, for root paths url.path
832     will be NULL or "", where serf requires "/". */
833  /* ### Maybe we should use a string buffer for these strings so we
834     ### don't allocate memory in the session on every reparent? */
835  if (new_url.path == NULL || new_url.path[0] == '\0')
836    {
837      session->session_url.path = apr_pstrdup(session->pool, "/");
838    }
839  else
840    {
841      session->session_url.path = apr_pstrdup(session->pool, new_url.path);
842    }
843  session->session_url_str = apr_pstrdup(session->pool, url);
844
845  return SVN_NO_ERROR;
846}
847
848/* Implements svn_ra__vtable_t.get_session_url(). */
849static svn_error_t *
850svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
851                             const char **url,
852                             apr_pool_t *pool)
853{
854  svn_ra_serf__session_t *session = ra_session->priv;
855  *url = apr_pstrdup(pool, session->session_url_str);
856  return SVN_NO_ERROR;
857}
858
859/* Implements svn_ra__vtable_t.get_latest_revnum(). */
860static svn_error_t *
861svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
862                               svn_revnum_t *latest_revnum,
863                               apr_pool_t *pool)
864{
865  svn_ra_serf__session_t *session = ra_session->priv;
866
867  return svn_error_trace(svn_ra_serf__get_youngest_revnum(
868                           latest_revnum, session, pool));
869}
870
871/* Implementation of svn_ra_serf__rev_proplist(). */
872static svn_error_t *
873serf__rev_proplist(svn_ra_session_t *ra_session,
874                   svn_revnum_t rev,
875                   const svn_ra_serf__dav_props_t *fetch_props,
876                   apr_hash_t **ret_props,
877                   apr_pool_t *result_pool,
878                   apr_pool_t *scratch_pool)
879{
880  svn_ra_serf__session_t *session = ra_session->priv;
881  apr_hash_t *props;
882  const char *propfind_path;
883  svn_ra_serf__handler_t *handler;
884
885  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
886    {
887      propfind_path = apr_psprintf(scratch_pool, "%s/%ld", session->rev_stub,
888                                   rev);
889
890      /* svn_ra_serf__retrieve_props() wants to added the revision as
891         a Label to the PROPFIND, which isn't really necessary when
892         querying a rev-stub URI.  *Shrug*  Probably okay to leave the
893         Label, but whatever. */
894      rev = SVN_INVALID_REVNUM;
895    }
896  else
897    {
898      /* Use the VCC as the propfind target path. */
899      SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session,
900                                        scratch_pool));
901    }
902
903  props = apr_hash_make(result_pool);
904  SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
905                                               propfind_path, rev, "0",
906                                               fetch_props,
907                                               svn_ra_serf__deliver_svn_props,
908                                               props,
909                                               scratch_pool));
910
911  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
912
913  svn_ra_serf__keep_only_regular_props(props, scratch_pool);
914
915  *ret_props = props;
916
917  return SVN_NO_ERROR;
918}
919
920/* Implements svn_ra__vtable_t.rev_proplist(). */
921static svn_error_t *
922svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
923                          svn_revnum_t rev,
924                          apr_hash_t **ret_props,
925                          apr_pool_t *result_pool)
926{
927  apr_pool_t *scratch_pool = svn_pool_create(result_pool);
928  svn_error_t *err;
929
930  err = serf__rev_proplist(ra_session, rev, all_props, ret_props,
931                           result_pool, scratch_pool);
932
933  svn_pool_destroy(scratch_pool);
934  return svn_error_trace(err);
935}
936
937
938/* Implements svn_ra__vtable_t.rev_prop(). */
939svn_error_t *
940svn_ra_serf__rev_prop(svn_ra_session_t *session,
941                      svn_revnum_t rev,
942                      const char *name,
943                      svn_string_t **value,
944                      apr_pool_t *result_pool)
945{
946  apr_pool_t *scratch_pool = svn_pool_create(result_pool);
947  apr_hash_t *props;
948  svn_ra_serf__dav_props_t specific_props[2];
949  const svn_ra_serf__dav_props_t *fetch_props = all_props;
950
951  /* The DAV propfind doesn't allow property fetches for any property name
952     as there is no defined way to quote values. If we are just fetching a
953     "svn:property" we can safely do this. In other cases we just fetch all
954     revision properties and filter the right one out */
955  if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX)-1) == 0
956      && !strchr(name + sizeof(SVN_PROP_PREFIX)-1, ':'))
957    {
958      specific_props[0].xmlns = SVN_DAV_PROP_NS_SVN;
959      specific_props[0].name = name + sizeof(SVN_PROP_PREFIX)-1;
960      specific_props[1].xmlns = NULL;
961      specific_props[1].name = NULL;
962
963      fetch_props = specific_props;
964    }
965
966  SVN_ERR(serf__rev_proplist(session, rev, fetch_props, &props,
967                             result_pool, scratch_pool));
968
969  *value = svn_hash_gets(props, name);
970
971  svn_pool_destroy(scratch_pool);
972
973  return SVN_NO_ERROR;
974}
975
976svn_error_t *
977svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
978                            const char **url,
979                            apr_pool_t *pool)
980{
981  svn_ra_serf__session_t *session = ra_session->priv;
982
983  if (!session->repos_root_str)
984    {
985      const char *vcc_url;
986      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
987    }
988
989  *url = session->repos_root_str;
990  return SVN_NO_ERROR;
991}
992
993/* TODO: to fetch the uuid from the repository, we need:
994   1. a path that exists in HEAD
995   2. a path that's readable
996
997   get_uuid handles the case where a path doesn't exist in HEAD and also the
998   case where the root of the repository is not readable.
999   However, it does not handle the case where we're fetching path not existing
1000   in HEAD of a repository with unreadable root directory.
1001
1002   Implements svn_ra__vtable_t.get_uuid().
1003 */
1004static svn_error_t *
1005svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
1006                      const char **uuid,
1007                      apr_pool_t *pool)
1008{
1009  svn_ra_serf__session_t *session = ra_session->priv;
1010
1011  if (!session->uuid)
1012    {
1013      const char *vcc_url;
1014
1015      /* We should never get here if we have HTTP v2 support, because
1016         any server with that support should be transmitting the
1017         UUID in the initial OPTIONS response.  */
1018      SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
1019
1020      /* We're not interested in vcc_url and relative_url, but this call also
1021         stores the repository's uuid in the session. */
1022      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
1023      if (!session->uuid)
1024        {
1025          return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
1026                                  _("The UUID property was not found on the "
1027                                    "resource or any of its parents"));
1028        }
1029    }
1030
1031  *uuid = session->uuid;
1032
1033  return SVN_NO_ERROR;
1034}
1035
1036
1037static const svn_ra__vtable_t serf_vtable = {
1038  ra_serf_version,
1039  ra_serf_get_description,
1040  ra_serf_get_schemes,
1041  svn_ra_serf__open,
1042  ra_serf_dup_session,
1043  svn_ra_serf__reparent,
1044  svn_ra_serf__get_session_url,
1045  svn_ra_serf__get_latest_revnum,
1046  svn_ra_serf__get_dated_revision,
1047  svn_ra_serf__change_rev_prop,
1048  svn_ra_serf__rev_proplist,
1049  svn_ra_serf__rev_prop,
1050  svn_ra_serf__get_commit_editor,
1051  svn_ra_serf__get_file,
1052  svn_ra_serf__get_dir,
1053  svn_ra_serf__get_mergeinfo,
1054  svn_ra_serf__do_update,
1055  svn_ra_serf__do_switch,
1056  svn_ra_serf__do_status,
1057  svn_ra_serf__do_diff,
1058  svn_ra_serf__get_log,
1059  svn_ra_serf__check_path,
1060  svn_ra_serf__stat,
1061  svn_ra_serf__get_uuid,
1062  svn_ra_serf__get_repos_root,
1063  svn_ra_serf__get_locations,
1064  svn_ra_serf__get_location_segments,
1065  svn_ra_serf__get_file_revs,
1066  svn_ra_serf__lock,
1067  svn_ra_serf__unlock,
1068  svn_ra_serf__get_lock,
1069  svn_ra_serf__get_locks,
1070  svn_ra_serf__replay,
1071  svn_ra_serf__has_capability,
1072  svn_ra_serf__replay_range,
1073  svn_ra_serf__get_deleted_rev,
1074  svn_ra_serf__register_editor_shim_callbacks,
1075  svn_ra_serf__get_inherited_props
1076};
1077
1078svn_error_t *
1079svn_ra_serf__init(const svn_version_t *loader_version,
1080                  const svn_ra__vtable_t **vtable,
1081                  apr_pool_t *pool)
1082{
1083  static const svn_version_checklist_t checklist[] =
1084    {
1085      { "svn_subr",  svn_subr_version },
1086      { "svn_delta", svn_delta_version },
1087      { NULL, NULL }
1088    };
1089  int serf_major;
1090  int serf_minor;
1091  int serf_patch;
1092
1093  SVN_ERR(svn_ver_check_list2(ra_serf_version(), checklist, svn_ver_equal));
1094
1095  /* Simplified version check to make sure we can safely use the
1096     VTABLE parameter. The RA loader does a more exhaustive check. */
1097  if (loader_version->major != SVN_VER_MAJOR)
1098    {
1099      return svn_error_createf(
1100         SVN_ERR_VERSION_MISMATCH, NULL,
1101         _("Unsupported RA loader version (%d) for ra_serf"),
1102         loader_version->major);
1103    }
1104
1105  /* Make sure that we have loaded a compatible library: the MAJOR must
1106     match, and the minor must be at *least* what we compiled against.
1107     The patch level is simply ignored.  */
1108  serf_lib_version(&serf_major, &serf_minor, &serf_patch);
1109  if (serf_major != SERF_MAJOR_VERSION
1110      || serf_minor < SERF_MINOR_VERSION)
1111    {
1112      return svn_error_createf(
1113         /* ### should return a unique error  */
1114         SVN_ERR_VERSION_MISMATCH, NULL,
1115         _("ra_serf was compiled for serf %d.%d.%d but loaded "
1116           "an incompatible %d.%d.%d library"),
1117         SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION,
1118         serf_major, serf_minor, serf_patch);
1119    }
1120
1121  *vtable = &serf_vtable;
1122
1123  return SVN_NO_ERROR;
1124}
1125
1126/* Compatibility wrapper for pre-1.2 subversions.  Needed? */
1127#define NAME "ra_serf"
1128#define DESCRIPTION RA_SERF_DESCRIPTION
1129#define VTBL serf_vtable
1130#define INITFUNC svn_ra_serf__init
1131#define COMPAT_INITFUNC svn_ra_serf_init
1132#include "../libsvn_ra/wrapper_template.h"
1133