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