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(). */
56 static const svn_version_t *
ra_serf_version(void)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 *
ra_serf_get_description(apr_pool_t * pool)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 *
ra_serf_get_schemes(apr_pool_t * pool)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 *
load_http_auth_types(apr_pool_t * pool,svn_config_t * config,const char * server_group,int * authn_types)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 *
load_config(svn_ra_serf__session_t * session,apr_hash_t * config_hash,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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
svn_ra_serf__progress(void * progress_baton,apr_off_t bytes_read,apr_off_t bytes_written)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 *
get_user_agent_string(apr_pool_t * pool)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 *
svn_ra_serf__open(svn_ra_session_t * session,const char ** corrected_url,const char * session_URL,const svn_ra_callbacks2_t * callbacks,void * callback_baton,svn_auth_baton_t * auth_baton,apr_hash_t * config,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 serf_sess->http20 = FALSE;
535
536 /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable
537 this, if we find an intervening proxy does not support chunked requests. */
538 serf_sess->using_chunked_requests = TRUE;
539
540 SVN_ERR(load_config(serf_sess, config, serf_sess->pool, scratch_pool));
541
542 serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
543 sizeof(*serf_sess->conns[0]));
544 serf_sess->conns[0]->bkt_alloc =
545 serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
546 serf_sess->conns[0]->session = serf_sess;
547 serf_sess->conns[0]->last_status_code = -1;
548
549 /* create the user agent string */
550 if (callbacks->get_client_string)
551 SVN_ERR(callbacks->get_client_string(callback_baton, &client_string,
552 scratch_pool));
553
554 if (client_string)
555 serf_sess->useragent = apr_pstrcat(result_pool,
556 get_user_agent_string(scratch_pool),
557 " ",
558 client_string, SVN_VA_NULL);
559 else
560 serf_sess->useragent = get_user_agent_string(result_pool);
561
562 /* go ahead and tell serf about the connection. */
563 status =
564 serf_connection_create2(&serf_sess->conns[0]->conn,
565 serf_sess->context,
566 url,
567 svn_ra_serf__conn_setup, serf_sess->conns[0],
568 svn_ra_serf__conn_closed, serf_sess->conns[0],
569 serf_sess->pool);
570 if (status)
571 return svn_ra_serf__wrap_err(status, NULL);
572
573 /* Set the progress callback. */
574 serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
575 serf_sess);
576
577 serf_sess->num_conns = 1;
578
579 session->priv = serf_sess;
580
581 /* The following code explicitly works around a bug in serf <= r2319 / 1.3.8
582 where serf doesn't report the request as failed/cancelled when the
583 authorization request handler fails to handle the request.
584
585 As long as we allocate the request in a subpool of the serf connection
586 pool, we know that the handler is always cleaned before the connection.
587
588 Luckily our caller now passes us two pools which handle this case.
589 */
590 #if defined(SVN_DEBUG) && !SERF_VERSION_AT_LEAST(1,4,0)
591 /* Currently ensured by svn_ra_open4().
592 If failing causes segfault in basic_tests.py 48, "basic auth test" */
593 SVN_ERR_ASSERT((serf_sess->pool != scratch_pool)
594 && apr_pool_is_ancestor(serf_sess->pool, scratch_pool));
595 #endif
596
597 /* The actual latency will be determined as a part of the initial
598 OPTIONS request. */
599 serf_sess->conn_latency = -1;
600
601 err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url,
602 result_pool, scratch_pool);
603
604 /* serf should produce a usable error code instead of APR_EGENERAL */
605 if (err && err->apr_err == APR_EGENERAL)
606 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err,
607 _("Connection to '%s' failed"), session_URL);
608 SVN_ERR(err);
609
610 /* We have set up a useful connection (that doesn't indication a redirect).
611 If we've been told there is possibly a worrisome proxy in our path to the
612 server AND we switched to HTTP/1.1 (chunked requests), then probe for
613 problems in any proxy. */
614 if ((corrected_url == NULL || *corrected_url == NULL)
615 && serf_sess->detect_chunking && !serf_sess->http10)
616 SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, scratch_pool));
617
618 return SVN_NO_ERROR;
619 }
620
621 /* Implements svn_ra__vtable_t.dup_session */
622 static svn_error_t *
ra_serf_dup_session(svn_ra_session_t * new_session,svn_ra_session_t * old_session,const char * new_session_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)623 ra_serf_dup_session(svn_ra_session_t *new_session,
624 svn_ra_session_t *old_session,
625 const char *new_session_url,
626 apr_pool_t *result_pool,
627 apr_pool_t *scratch_pool)
628 {
629 svn_ra_serf__session_t *old_sess = old_session->priv;
630 svn_ra_serf__session_t *new_sess;
631 apr_status_t status;
632
633 new_sess = apr_pmemdup(result_pool, old_sess, sizeof(*new_sess));
634
635 new_sess->pool = result_pool;
636
637 if (new_sess->config)
638 SVN_ERR(svn_config_copy_config(&new_sess->config, new_sess->config,
639 result_pool));
640
641 /* max_connections */
642 /* using_ssl */
643 /* using_compression */
644 /* http10 */
645 /* http20 */
646 /* using_chunked_requests */
647 /* detect_chunking */
648
649 if (new_sess->useragent)
650 new_sess->useragent = apr_pstrdup(result_pool, new_sess->useragent);
651
652 if (new_sess->vcc_url)
653 new_sess->vcc_url = apr_pstrdup(result_pool, new_sess->vcc_url);
654
655 new_sess->auth_state = NULL;
656 new_sess->auth_attempts = 0;
657
658 /* Callback functions to get info from WC */
659 /* wc_callbacks */
660 /* wc_callback_baton */
661
662 /* progress_func */
663 /* progress_baton */
664
665 /* cancel_func */
666 /* cancel_baton */
667
668 /* shim_callbacks */
669
670 new_sess->pending_error = NULL;
671
672 /* authn_types */
673
674 /* Keys and values are static */
675 if (new_sess->capabilities)
676 new_sess->capabilities = apr_hash_copy(result_pool, new_sess->capabilities);
677
678 if (new_sess->activity_collection_url)
679 {
680 new_sess->activity_collection_url
681 = apr_pstrdup(result_pool, new_sess->activity_collection_url);
682 }
683
684 /* using_proxy */
685
686 if (new_sess->proxy_username)
687 {
688 new_sess->proxy_username
689 = apr_pstrdup(result_pool, new_sess->proxy_username);
690 }
691
692 if (new_sess->proxy_password)
693 {
694 new_sess->proxy_password
695 = apr_pstrdup(result_pool, new_sess->proxy_password);
696 }
697
698 new_sess->proxy_auth_attempts = 0;
699
700 /* trust_default_ca */
701
702 if (new_sess->ssl_authorities)
703 {
704 new_sess->ssl_authorities = apr_pstrdup(result_pool,
705 new_sess->ssl_authorities);
706 }
707
708 if (new_sess->uuid)
709 new_sess->uuid = apr_pstrdup(result_pool, new_sess->uuid);
710
711 /* timeout */
712 /* supports_deadprop_count */
713
714 if (new_sess->me_resource)
715 new_sess->me_resource = apr_pstrdup(result_pool, new_sess->me_resource);
716 if (new_sess->rev_stub)
717 new_sess->rev_stub = apr_pstrdup(result_pool, new_sess->rev_stub);
718 if (new_sess->txn_stub)
719 new_sess->txn_stub = apr_pstrdup(result_pool, new_sess->txn_stub);
720 if (new_sess->txn_root_stub)
721 new_sess->txn_root_stub = apr_pstrdup(result_pool,
722 new_sess->txn_root_stub);
723 if (new_sess->vtxn_stub)
724 new_sess->vtxn_stub = apr_pstrdup(result_pool, new_sess->vtxn_stub);
725 if (new_sess->vtxn_root_stub)
726 new_sess->vtxn_root_stub = apr_pstrdup(result_pool,
727 new_sess->vtxn_root_stub);
728
729 /* Keys and values are static */
730 if (new_sess->supported_posts)
731 new_sess->supported_posts = apr_hash_copy(result_pool,
732 new_sess->supported_posts);
733
734 /* ### Can we copy this? */
735 SVN_ERR(svn_ra_serf__blncache_create(&new_sess->blncache,
736 new_sess->pool));
737
738 if (new_sess->server_allows_bulk)
739 new_sess->server_allows_bulk = apr_pstrdup(result_pool,
740 new_sess->server_allows_bulk);
741
742 if (new_sess->repos_root_str)
743 {
744 new_sess->repos_root_str = apr_pstrdup(result_pool,
745 new_sess->repos_root_str);
746 SVN_ERR(svn_ra_serf__uri_parse(&new_sess->repos_root,
747 new_sess->repos_root_str,
748 result_pool));
749 }
750
751 new_sess->session_url_str = apr_pstrdup(result_pool, new_session_url);
752
753 SVN_ERR(svn_ra_serf__uri_parse(&new_sess->session_url,
754 new_sess->session_url_str,
755 result_pool));
756
757 /* svn_boolean_t supports_inline_props */
758 /* supports_rev_rsrc_replay */
759 /* supports_svndiff1 */
760 /* supports_svndiff2 */
761 /* supports_put_result_checksum */
762 /* conn_latency */
763
764 new_sess->context = serf_context_create(result_pool);
765
766 SVN_ERR(load_config(new_sess, old_sess->config,
767 result_pool, scratch_pool));
768
769 new_sess->conns[0] = apr_pcalloc(result_pool,
770 sizeof(*new_sess->conns[0]));
771 new_sess->conns[0]->bkt_alloc =
772 serf_bucket_allocator_create(result_pool, NULL, NULL);
773 new_sess->conns[0]->session = new_sess;
774 new_sess->conns[0]->last_status_code = -1;
775
776 /* go ahead and tell serf about the connection. */
777 status =
778 serf_connection_create2(&new_sess->conns[0]->conn,
779 new_sess->context,
780 new_sess->session_url,
781 svn_ra_serf__conn_setup, new_sess->conns[0],
782 svn_ra_serf__conn_closed, new_sess->conns[0],
783 result_pool);
784 if (status)
785 return svn_ra_serf__wrap_err(status, NULL);
786
787 /* Set the progress callback. */
788 serf_context_set_progress_cb(new_sess->context, svn_ra_serf__progress,
789 new_sess);
790
791 new_sess->num_conns = 1;
792 new_sess->cur_conn = 0;
793
794 new_session->priv = new_sess;
795
796 return SVN_NO_ERROR;
797 }
798
799 /* Implements svn_ra__vtable_t.reparent(). */
800 svn_error_t *
svn_ra_serf__reparent(svn_ra_session_t * ra_session,const char * url,apr_pool_t * pool)801 svn_ra_serf__reparent(svn_ra_session_t *ra_session,
802 const char *url,
803 apr_pool_t *pool)
804 {
805 svn_ra_serf__session_t *session = ra_session->priv;
806 apr_uri_t new_url;
807
808 /* If it's the URL we already have, wave our hands and do nothing. */
809 if (strcmp(session->session_url_str, url) == 0)
810 {
811 return SVN_NO_ERROR;
812 }
813
814 if (!session->repos_root_str)
815 {
816 const char *vcc_url;
817 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
818 }
819
820 if (!svn_uri__is_ancestor(session->repos_root_str, url))
821 {
822 return svn_error_createf(
823 SVN_ERR_RA_ILLEGAL_URL, NULL,
824 _("URL '%s' is not a child of the session's repository root "
825 "URL '%s'"), url, session->repos_root_str);
826 }
827
828 SVN_ERR(svn_ra_serf__uri_parse(&new_url, url, pool));
829
830 /* ### Maybe we should use a string buffer for these strings so we
831 ### don't allocate memory in the session on every reparent? */
832 session->session_url.path = apr_pstrdup(session->pool, new_url.path);
833 session->session_url_str = apr_pstrdup(session->pool, url);
834
835 return SVN_NO_ERROR;
836 }
837
838 /* Implements svn_ra__vtable_t.get_session_url(). */
839 static svn_error_t *
svn_ra_serf__get_session_url(svn_ra_session_t * ra_session,const char ** url,apr_pool_t * pool)840 svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
841 const char **url,
842 apr_pool_t *pool)
843 {
844 svn_ra_serf__session_t *session = ra_session->priv;
845 *url = apr_pstrdup(pool, session->session_url_str);
846 return SVN_NO_ERROR;
847 }
848
849 /* Implements svn_ra__vtable_t.get_latest_revnum(). */
850 static svn_error_t *
svn_ra_serf__get_latest_revnum(svn_ra_session_t * ra_session,svn_revnum_t * latest_revnum,apr_pool_t * pool)851 svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
852 svn_revnum_t *latest_revnum,
853 apr_pool_t *pool)
854 {
855 svn_ra_serf__session_t *session = ra_session->priv;
856
857 return svn_error_trace(svn_ra_serf__get_youngest_revnum(
858 latest_revnum, session, pool));
859 }
860
861 /* Implementation of svn_ra_serf__rev_proplist(). */
862 static svn_error_t *
serf__rev_proplist(svn_ra_session_t * ra_session,svn_revnum_t rev,const svn_ra_serf__dav_props_t * fetch_props,apr_hash_t ** ret_props,apr_pool_t * result_pool,apr_pool_t * scratch_pool)863 serf__rev_proplist(svn_ra_session_t *ra_session,
864 svn_revnum_t rev,
865 const svn_ra_serf__dav_props_t *fetch_props,
866 apr_hash_t **ret_props,
867 apr_pool_t *result_pool,
868 apr_pool_t *scratch_pool)
869 {
870 svn_ra_serf__session_t *session = ra_session->priv;
871 apr_hash_t *props;
872 const char *propfind_path;
873 svn_ra_serf__handler_t *handler;
874
875 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
876 {
877 propfind_path = apr_psprintf(scratch_pool, "%s/%ld", session->rev_stub,
878 rev);
879
880 /* svn_ra_serf__retrieve_props() wants to added the revision as
881 a Label to the PROPFIND, which isn't really necessary when
882 querying a rev-stub URI. *Shrug* Probably okay to leave the
883 Label, but whatever. */
884 rev = SVN_INVALID_REVNUM;
885 }
886 else
887 {
888 /* Use the VCC as the propfind target path. */
889 SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session,
890 scratch_pool));
891 }
892
893 props = apr_hash_make(result_pool);
894 SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
895 propfind_path, rev, "0",
896 fetch_props,
897 svn_ra_serf__deliver_svn_props,
898 props,
899 scratch_pool));
900
901 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
902
903 svn_ra_serf__keep_only_regular_props(props, scratch_pool);
904
905 *ret_props = props;
906
907 return SVN_NO_ERROR;
908 }
909
910 /* Implements svn_ra__vtable_t.rev_proplist(). */
911 static svn_error_t *
svn_ra_serf__rev_proplist(svn_ra_session_t * ra_session,svn_revnum_t rev,apr_hash_t ** ret_props,apr_pool_t * result_pool)912 svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
913 svn_revnum_t rev,
914 apr_hash_t **ret_props,
915 apr_pool_t *result_pool)
916 {
917 apr_pool_t *scratch_pool = svn_pool_create(result_pool);
918 svn_error_t *err;
919
920 err = serf__rev_proplist(ra_session, rev, all_props, ret_props,
921 result_pool, scratch_pool);
922
923 svn_pool_destroy(scratch_pool);
924 return svn_error_trace(err);
925 }
926
927
928 /* Implements svn_ra__vtable_t.rev_prop(). */
929 svn_error_t *
svn_ra_serf__rev_prop(svn_ra_session_t * session,svn_revnum_t rev,const char * name,svn_string_t ** value,apr_pool_t * result_pool)930 svn_ra_serf__rev_prop(svn_ra_session_t *session,
931 svn_revnum_t rev,
932 const char *name,
933 svn_string_t **value,
934 apr_pool_t *result_pool)
935 {
936 apr_pool_t *scratch_pool = svn_pool_create(result_pool);
937 apr_hash_t *props;
938 svn_ra_serf__dav_props_t specific_props[2];
939 const svn_ra_serf__dav_props_t *fetch_props = all_props;
940
941 /* The DAV propfind doesn't allow property fetches for any property name
942 as there is no defined way to quote values. If we are just fetching a
943 "svn:property" we can safely do this. In other cases we just fetch all
944 revision properties and filter the right one out */
945 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX)-1) == 0
946 && !strchr(name + sizeof(SVN_PROP_PREFIX)-1, ':'))
947 {
948 specific_props[0].xmlns = SVN_DAV_PROP_NS_SVN;
949 specific_props[0].name = name + sizeof(SVN_PROP_PREFIX)-1;
950 specific_props[1].xmlns = NULL;
951 specific_props[1].name = NULL;
952
953 fetch_props = specific_props;
954 }
955
956 SVN_ERR(serf__rev_proplist(session, rev, fetch_props, &props,
957 result_pool, scratch_pool));
958
959 *value = svn_hash_gets(props, name);
960
961 svn_pool_destroy(scratch_pool);
962
963 return SVN_NO_ERROR;
964 }
965
966 svn_error_t *
svn_ra_serf__get_repos_root(svn_ra_session_t * ra_session,const char ** url,apr_pool_t * pool)967 svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
968 const char **url,
969 apr_pool_t *pool)
970 {
971 svn_ra_serf__session_t *session = ra_session->priv;
972
973 if (!session->repos_root_str)
974 {
975 const char *vcc_url;
976 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
977 }
978
979 *url = session->repos_root_str;
980 return SVN_NO_ERROR;
981 }
982
983 /* TODO: to fetch the uuid from the repository, we need:
984 1. a path that exists in HEAD
985 2. a path that's readable
986
987 get_uuid handles the case where a path doesn't exist in HEAD and also the
988 case where the root of the repository is not readable.
989 However, it does not handle the case where we're fetching path not existing
990 in HEAD of a repository with unreadable root directory.
991
992 Implements svn_ra__vtable_t.get_uuid().
993 */
994 static svn_error_t *
svn_ra_serf__get_uuid(svn_ra_session_t * ra_session,const char ** uuid,apr_pool_t * pool)995 svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
996 const char **uuid,
997 apr_pool_t *pool)
998 {
999 svn_ra_serf__session_t *session = ra_session->priv;
1000
1001 if (!session->uuid)
1002 {
1003 const char *vcc_url;
1004
1005 /* We should never get here if we have HTTP v2 support, because
1006 any server with that support should be transmitting the
1007 UUID in the initial OPTIONS response. */
1008 SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
1009
1010 /* We're not interested in vcc_url and relative_url, but this call also
1011 stores the repository's uuid in the session. */
1012 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
1013 if (!session->uuid)
1014 {
1015 return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
1016 _("The UUID property was not found on the "
1017 "resource or any of its parents"));
1018 }
1019 }
1020
1021 *uuid = session->uuid;
1022
1023 return SVN_NO_ERROR;
1024 }
1025
1026
1027 static const svn_ra__vtable_t serf_vtable = {
1028 ra_serf_version,
1029 ra_serf_get_description,
1030 ra_serf_get_schemes,
1031 svn_ra_serf__open,
1032 ra_serf_dup_session,
1033 svn_ra_serf__reparent,
1034 svn_ra_serf__get_session_url,
1035 svn_ra_serf__get_latest_revnum,
1036 svn_ra_serf__get_dated_revision,
1037 svn_ra_serf__change_rev_prop,
1038 svn_ra_serf__rev_proplist,
1039 svn_ra_serf__rev_prop,
1040 svn_ra_serf__get_commit_editor,
1041 svn_ra_serf__get_file,
1042 svn_ra_serf__get_dir,
1043 svn_ra_serf__get_mergeinfo,
1044 svn_ra_serf__do_update,
1045 svn_ra_serf__do_switch,
1046 svn_ra_serf__do_status,
1047 svn_ra_serf__do_diff,
1048 svn_ra_serf__get_log,
1049 svn_ra_serf__check_path,
1050 svn_ra_serf__stat,
1051 svn_ra_serf__get_uuid,
1052 svn_ra_serf__get_repos_root,
1053 svn_ra_serf__get_locations,
1054 svn_ra_serf__get_location_segments,
1055 svn_ra_serf__get_file_revs,
1056 svn_ra_serf__lock,
1057 svn_ra_serf__unlock,
1058 svn_ra_serf__get_lock,
1059 svn_ra_serf__get_locks,
1060 svn_ra_serf__replay,
1061 svn_ra_serf__has_capability,
1062 svn_ra_serf__replay_range,
1063 svn_ra_serf__get_deleted_rev,
1064 svn_ra_serf__get_inherited_props,
1065 NULL /* set_svn_ra_open */,
1066 svn_ra_serf__list,
1067 svn_ra_serf__register_editor_shim_callbacks,
1068 NULL /* commit_ev2 */,
1069 NULL /* replay_range_ev2 */
1070 };
1071
1072 svn_error_t *
svn_ra_serf__init(const svn_version_t * loader_version,const svn_ra__vtable_t ** vtable,apr_pool_t * pool)1073 svn_ra_serf__init(const svn_version_t *loader_version,
1074 const svn_ra__vtable_t **vtable,
1075 apr_pool_t *pool)
1076 {
1077 static const svn_version_checklist_t checklist[] =
1078 {
1079 { "svn_subr", svn_subr_version },
1080 { "svn_delta", svn_delta_version },
1081 { NULL, NULL }
1082 };
1083 int serf_major;
1084 int serf_minor;
1085 int serf_patch;
1086
1087 SVN_ERR(svn_ver_check_list2(ra_serf_version(), checklist, svn_ver_equal));
1088
1089 /* Simplified version check to make sure we can safely use the
1090 VTABLE parameter. The RA loader does a more exhaustive check. */
1091 if (loader_version->major != SVN_VER_MAJOR)
1092 {
1093 return svn_error_createf(
1094 SVN_ERR_VERSION_MISMATCH, NULL,
1095 _("Unsupported RA loader version (%d) for ra_serf"),
1096 loader_version->major);
1097 }
1098
1099 /* Make sure that we have loaded a compatible library: the MAJOR must
1100 match, and the minor must be at *least* what we compiled against.
1101 The patch level is simply ignored. */
1102 serf_lib_version(&serf_major, &serf_minor, &serf_patch);
1103 if (serf_major != SERF_MAJOR_VERSION
1104 || serf_minor < SERF_MINOR_VERSION)
1105 {
1106 return svn_error_createf(
1107 /* ### should return a unique error */
1108 SVN_ERR_VERSION_MISMATCH, NULL,
1109 _("ra_serf was compiled for serf %d.%d.%d but loaded "
1110 "an incompatible %d.%d.%d library"),
1111 SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION,
1112 serf_major, serf_minor, serf_patch);
1113 }
1114
1115 *vtable = &serf_vtable;
1116
1117 return SVN_NO_ERROR;
1118 }
1119
1120 /* Compatibility wrapper for pre-1.2 subversions. Needed? */
1121 #define NAME "ra_serf"
1122 #define DESCRIPTION RA_SERF_DESCRIPTION
1123 #define VTBL serf_vtable
1124 #define INITFUNC svn_ra_serf__init
1125 #define COMPAT_INITFUNC svn_ra_serf_init
1126 #include "../libsvn_ra/wrapper_template.h"
1127