]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_serf/serf.c
Update nvi to 2.2.0
[FreeBSD/FreeBSD.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 *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
450 static void
451 svn_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. */
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 **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 */
626 static svn_error_t *
627 ra_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(). */
804 svn_error_t *
805 svn_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(). */
843 static svn_error_t *
844 svn_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(). */
854 static svn_error_t *
855 svn_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(). */
866 static svn_error_t *
867 serf__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(). */
915 static svn_error_t *
916 svn_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(). */
933 svn_error_t *
934 svn_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
970 svn_error_t *
971 svn_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  */
998 static svn_error_t *
999 svn_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
1031 static 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
1076 svn_error_t *
1077 svn_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"