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