]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/serf.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra_serf / serf.c
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 \f
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 \f
55 /* Implements svn_ra__vtable_t.get_version(). */
56 static const svn_version_t *
57 ra_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(). */
70 static const char *
71 ra_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(). */
85 static const char * const *
86 ra_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. */
101 static svn_error_t *
102 load_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
153 static svn_error_t *
154 load_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
451 static void
452 svn_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. */
464 static const char *
465 get_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(). */
476 static svn_error_t *
477 svn_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 */
617 static svn_error_t *
618 ra_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(). */
786 svn_error_t *
787 svn_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(). */
825 static svn_error_t *
826 svn_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(). */
836 static svn_error_t *
837 svn_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(). */
848 static svn_error_t *
849 serf__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(). */
897 static svn_error_t *
898 svn_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(). */
915 svn_error_t *
916 svn_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
952 svn_error_t *
953 svn_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  */
980 static svn_error_t *
981 svn_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
1013 static 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
1054 svn_error_t *
1055 svn_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"