62
61
#define RA_SERF_DESCRIPTION \
63
62
N_("Module for accessing a repository via WebDAV protocol using serf.")
64
#define RA_SERF_DESCRIPTION_VER \
65
N_("Module for accessing a repository via WebDAV protocol using serf.\n" \
66
" - using serf %d.%d.%d")
68
/* Implements svn_ra__vtable_t.get_description(). */
65
69
static const char *
66
ra_serf_get_description(void)
70
ra_serf_get_description(apr_pool_t *pool)
68
return _(RA_SERF_DESCRIPTION);
72
int major, minor, patch;
74
serf_lib_version(&major, &minor, &patch);
75
return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), major, minor, patch);
78
/* Implements svn_ra__vtable_t.get_schemes(). */
71
79
static const char * const *
72
80
ra_serf_get_schemes(apr_pool_t *pool)
184
198
/* Use the default proxy-specific settings if and only if
185
199
"http-proxy-exceptions" is not set to exclude this host. */
186
200
svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
187
SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, NULL);
190
apr_array_header_t *l = svn_cstring_split(exceptions, ",", TRUE, pool);
191
is_exception = svn_cstring_match_glob_list(session->session_url.hostname,
196
/* Load the global proxy server settings, if set. */
201
SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
202
if (! svn_cstring_match_glob_list(session->session_url.hostname,
203
svn_cstring_split(exceptions, ",",
197
206
svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
198
207
SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
199
208
svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
214
223
svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
215
224
SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
226
/* If set, read the flag that tells us to do bulk updates or not. Defaults
227
to skelta updates. */
228
SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
229
SVN_CONFIG_SECTION_GLOBAL,
230
SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
232
svn_tristate_unknown));
234
/* Load the maximum number of parallel session connections. */
235
SVN_ERR(svn_config_get_int64(config, &session->max_connections,
236
SVN_CONFIG_SECTION_GLOBAL,
237
SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
238
SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS));
240
/* Should we use chunked transfer encoding. */
241
SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
242
SVN_CONFIG_SECTION_GLOBAL,
243
OPTION_HTTP_CHUNKED_REQUESTS,
244
"auto", svn_tristate_unknown));
218
247
server_group = svn_config_find_group(config,
219
248
session->session_url.hostname,
233
262
svn_auth_set_parameter(session->wc_callbacks->auth_baton,
234
263
SVN_AUTH_PARAM_SERVER_GROUP, server_group);
236
/* Load the group proxy server settings, overriding global settings. */
265
/* Load the group proxy server settings, overriding global
266
settings. We intentionally ignore 'http-proxy-exceptions'
267
here because, well, if this site was an exception, why is
268
there a per-server proxy configuration for it? */
237
269
svn_config_get(config, &proxy_host, server_group,
238
SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
270
SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
239
271
svn_config_get(config, &port_str, server_group,
240
SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
272
SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
241
273
svn_config_get(config, &session->proxy_username, server_group,
242
SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
274
SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
275
session->proxy_username);
243
276
svn_config_get(config, &session->proxy_password, server_group,
244
SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
277
SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
278
session->proxy_password);
246
280
/* Load the group ssl settings. */
247
281
SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
249
283
SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
284
session->trust_default_ca));
251
285
svn_config_get(config, &session->ssl_authorities, server_group,
252
SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
286
SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
287
session->ssl_authorities);
289
/* Load the group bulk updates flag. */
290
SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
292
SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
294
session->bulk_updates));
296
/* Load the maximum number of parallel session connections,
297
overriding global values. */
298
SVN_ERR(svn_config_get_int64(config, &session->max_connections,
300
SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
301
session->max_connections));
303
/* Should we use chunked transfer encoding. */
304
SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
306
OPTION_HTTP_CHUNKED_REQUESTS,
307
"auto", chunked_requests));
310
/* Don't allow the http-max-connections value to be larger than our
311
compiled-in limit, or to be too small to operate. Broken
312
functionality and angry administrators are equally undesirable. */
313
if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT)
314
session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT;
315
if (session->max_connections < 2)
316
session->max_connections = 2;
255
318
/* Parse the connection timeout value, if any. */
319
session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
305
return svn_error_wrap_apr(status,
306
_("Could not resolve proxy server '%s'"),
370
return svn_ra_serf__wrap_err(
371
status, _("Could not resolve proxy server '%s'"),
309
374
session->using_proxy = TRUE;
310
375
serf_config_proxy(session->context, proxy_addr);
313
session->using_proxy = FALSE;
379
session->using_proxy = FALSE;
382
/* Setup detect_chunking and using_chunked_requests based on
383
* the chunked_requests tristate */
384
if (chunked_requests == svn_tristate_unknown)
386
session->detect_chunking = TRUE;
387
session->using_chunked_requests = TRUE;
389
else if (chunked_requests == svn_tristate_true)
391
session->detect_chunking = FALSE;
392
session->using_chunked_requests = TRUE;
394
else /* chunked_requests == svn_tristate_false */
396
session->detect_chunking = FALSE;
397
session->using_chunked_requests = FALSE;
315
400
/* Setup authentication. */
316
401
SVN_ERR(load_http_auth_types(pool, config, server_group,
423
/** Our User-Agent string. */
425
get_user_agent_string(apr_pool_t *pool)
427
int major, minor, patch;
428
serf_lib_version(&major, &minor, &patch);
430
return apr_psprintf(pool, "SVN/%s (%s) serf/%d.%d.%d",
431
SVN_VER_NUMBER, SVN_BUILD_TARGET,
432
major, minor, patch);
435
/* Implements svn_ra__vtable_t.open_session(). */
338
436
static svn_error_t *
339
437
svn_ra_serf__open(svn_ra_session_t *session,
340
438
const char **corrected_url,
394
493
serf_sess->capabilities = apr_hash_make(serf_sess->pool);
495
/* We have to assume that the server only supports HTTP/1.0. Once it's clear
496
HTTP/1.1 is supported, we can upgrade. */
497
serf_sess->http10 = TRUE;
499
/* If we switch to HTTP/1.1, then we will use chunked requests. We may disable
500
this, if we find an intervening proxy does not support chunked requests. */
501
serf_sess->using_chunked_requests = TRUE;
396
503
SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
399
serf_sess->conns = apr_palloc(serf_sess->pool, sizeof(*serf_sess->conns) * 4);
401
505
serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
402
506
sizeof(*serf_sess->conns[0]));
403
507
serf_sess->conns[0]->bkt_alloc =
405
509
serf_sess->conns[0]->session = serf_sess;
406
510
serf_sess->conns[0]->last_status_code = -1;
408
serf_sess->conns[0]->using_ssl = serf_sess->using_ssl;
409
serf_sess->conns[0]->server_cert_failures = 0;
410
serf_sess->conns[0]->using_compression = serf_sess->using_compression;
411
serf_sess->conns[0]->hostname = url.hostname;
412
serf_sess->conns[0]->useragent = NULL;
414
512
/* create the user agent string */
415
513
if (callbacks->get_client_string)
416
callbacks->get_client_string(callback_baton, &client_string, pool);
514
SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool));
418
516
if (client_string)
419
serf_sess->conns[0]->useragent = apr_pstrcat(pool, USER_AGENT, "/",
420
client_string, (char *)NULL);
517
serf_sess->useragent = apr_pstrcat(pool, get_user_agent_string(pool), " ",
518
client_string, (char *)NULL);
422
serf_sess->conns[0]->useragent = USER_AGENT;
520
serf_sess->useragent = get_user_agent_string(pool);
424
522
/* go ahead and tell serf about the connection. */
441
539
session->priv = serf_sess;
443
return svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool);
541
err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool);
543
/* serf should produce a usable error code instead of APR_EGENERAL */
544
if (err && err->apr_err == APR_EGENERAL)
545
err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err,
546
_("Connection to '%s' failed"), session_URL);
549
/* We have set up a useful connection (that doesn't indication a redirect).
550
If we've been told there is possibly a worrisome proxy in our path to the
551
server AND we switched to HTTP/1.1 (chunked requests), then probe for
552
problems in any proxy. */
553
if ((corrected_url == NULL || *corrected_url == NULL)
554
&& serf_sess->detect_chunking && !serf_sess->http10)
555
SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, pool));
560
/* Implements svn_ra__vtable_t.reparent(). */
446
561
static svn_error_t *
447
562
svn_ra_serf__reparent(svn_ra_session_t *ra_session,
506
622
return SVN_NO_ERROR;
625
/* Implements svn_ra__vtable_t.get_latest_revnum(). */
509
626
static svn_error_t *
510
627
svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
511
628
svn_revnum_t *latest_revnum,
512
629
apr_pool_t *pool)
514
const char *relative_url, *basecoll_url;
515
631
svn_ra_serf__session_t *session = ra_session->priv;
517
return svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
518
NULL, session->session_url.path,
519
SVN_INVALID_REVNUM, latest_revnum,
633
return svn_error_trace(svn_ra_serf__get_youngest_revnum(
634
latest_revnum, session, pool));
637
/* Implements svn_ra__vtable_t.rev_proplist(). */
523
638
static svn_error_t *
524
639
svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
525
640
svn_revnum_t rev,
568
685
SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
570
*value = apr_hash_get(props, name, APR_HASH_KEY_STRING);
687
*value = svn_hash_gets(props, name);
572
689
return SVN_NO_ERROR;
575
692
static svn_error_t *
576
fetch_path_props(svn_ra_serf__propfind_context_t **ret_prop_ctx,
577
apr_hash_t **ret_props,
578
const char **ret_path,
579
svn_revnum_t *ret_revision,
693
fetch_path_props(apr_hash_t **props,
580
694
svn_ra_serf__session_t *session,
581
const char *rel_path,
695
const char *session_relpath,
582
696
svn_revnum_t revision,
583
697
const svn_ra_serf__dav_props_t *desired_props,
698
apr_pool_t *result_pool,
699
apr_pool_t *scratch_pool)
586
svn_ra_serf__propfind_context_t *prop_ctx;
590
path = session->session_url.path;
703
url = session->session_url.path;
592
705
/* If we have a relative path, append it. */
595
path = svn_path_url_add_component2(path, rel_path, pool);
598
props = apr_hash_make(pool);
600
/* If we were given a specific revision, we have to fetch the VCC and
601
* do a PROPFIND off of that.
603
if (!SVN_IS_VALID_REVNUM(revision))
605
SVN_ERR(svn_ra_serf__deliver_props(&prop_ctx, props, session,
606
session->conns[0], path, revision,
607
"0", desired_props, NULL,
612
const char *relative_url, *basecoll_url;
614
SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url,
616
revision, NULL, pool));
618
/* We will try again with our new path; however, we're now
619
* technically an unversioned resource because we are accessing
620
* the revision's baseline-collection.
622
path = svn_path_url_add_component2(basecoll_url, relative_url, pool);
623
revision = SVN_INVALID_REVNUM;
624
SVN_ERR(svn_ra_serf__deliver_props(&prop_ctx, props, session,
625
session->conns[0], path, revision,
626
"0", desired_props, NULL,
630
/* ### switch to svn_ra_serf__retrieve_props? */
631
SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, session, pool));
634
*ret_prop_ctx = prop_ctx;
636
*ret_revision = revision;
707
url = svn_path_url_add_component2(url, session_relpath, scratch_pool);
709
/* If we were given a specific revision, get a URL that refers to that
710
specific revision (rather than floating with HEAD). */
711
if (SVN_IS_VALID_REVNUM(revision))
713
SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
714
session, NULL /* conn */,
716
scratch_pool, scratch_pool));
719
/* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
720
Or we started with SVN_INVALID_REVNUM and URL may be floating. */
721
SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0],
722
url, SVN_INVALID_REVNUM,
724
result_pool, scratch_pool));
638
726
return SVN_NO_ERROR;
729
/* Implements svn_ra__vtable_t.check_path(). */
641
730
static svn_error_t *
642
731
svn_ra_serf__check_path(svn_ra_session_t *ra_session,
643
732
const char *rel_path,
648
737
svn_ra_serf__session_t *session = ra_session->priv;
649
738
apr_hash_t *props;
650
svn_ra_serf__propfind_context_t *prop_ctx;
652
svn_revnum_t fetched_rev;
654
svn_error_t *err = fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
656
revision, check_path_props, pool);
740
svn_error_t *err = fetch_path_props(&props, session, rel_path,
741
revision, check_path_props,
658
744
if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
886
973
svn_ra_serf__session_t *session = ra_session->priv;
887
974
apr_hash_t *props;
888
svn_ra_serf__propfind_context_t *prop_ctx;
890
svn_revnum_t fetched_rev;
891
975
svn_error_t *err;
892
976
struct dirent_walker_baton_t dwb;
893
977
svn_tristate_t deadprop_count = svn_tristate_unknown;
895
err = fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
979
err = fetch_path_props(&props,
896
980
session, rel_path, revision,
897
981
get_dirent_props(SVN_DIRENT_ALL, session, pool),
901
985
if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
924
1006
session->supports_deadprop_count = svn_tristate_false;
926
SVN_ERR(fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
927
session, rel_path, fetched_rev,
1008
SVN_ERR(fetch_path_props(&props,
1009
session, rel_path, SVN_INVALID_REVNUM,
928
1010
get_dirent_props(SVN_DIRENT_ALL, session, pool),
931
SVN_ERR(svn_ra_serf__walk_all_props(props, path, fetched_rev,
1013
SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
936
1016
if (deadprop_count != svn_tristate_unknown)
990
1069
if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
992
const char *relative_url, *basecoll_url;
994
SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url,
995
session, NULL, path, revision,
998
path = svn_path_url_add_component2(basecoll_url, relative_url, pool);
1071
SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
1072
session, NULL /* conn */,
999
1075
revision = SVN_INVALID_REVNUM;
1077
/* REVISION is always SVN_INVALID_REVNUM */
1078
SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
1002
1080
/* If we're asked for children, fetch them now. */
1005
1083
struct path_dirent_visitor_t dirent_walk;
1006
1084
apr_hash_t *props;
1008
1087
/* Always request node kind to check that path is really a
1011
1090
dirent_fields |= SVN_DIRENT_KIND;
1012
1091
SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
1013
path, revision, "1",
1092
path, SVN_INVALID_REVNUM, "1",
1014
1093
get_dirent_props(dirent_fields,
1015
1094
session, pool),
1018
1097
/* Check if the path is really a directory. */
1019
SVN_ERR(resource_is_directory(props, path, revision));
1098
rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype");
1099
if (rtype == NULL || strcmp(rtype, "collection") != 0)
1100
return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1101
_("Can't get entries of non-directory"));
1021
1103
/* We're going to create two hashes to help the walker along.
1022
1104
* We're going to return the 2nd one back to the caller as it
1040
1123
session->supports_deadprop_count = svn_tristate_false;
1041
1124
SVN_ERR(svn_ra_serf__retrieve_props(&props, session,
1042
1125
session->conns[0],
1043
path, revision, "1",
1126
path, SVN_INVALID_REVNUM, "1",
1044
1127
get_dirent_props(dirent_fields,
1045
1128
session, pool),
1048
SVN_ERR(svn_hash__clear(dirent_walk.full_paths, pool));
1049
SVN_ERR(svn_hash__clear(dirent_walk.base_paths, pool));
1131
apr_hash_clear(dirent_walk.full_paths);
1132
apr_hash_clear(dirent_walk.base_paths);
1051
SVN_ERR(svn_ra_serf__walk_all_paths(props, revision,
1134
SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
1052
1135
path_dirent_walker,
1053
1136
&dirent_walk, pool));
1065
1148
apr_hash_t *props;
1067
SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
1068
path, revision, "0", all_props,
1150
SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0],
1151
path, SVN_INVALID_REVNUM,
1070
1155
/* Check if the path is really a directory. */
1071
SVN_ERR(resource_is_directory(props, path, revision));
1156
SVN_ERR(resource_is_directory(props));
1073
SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, path, revision,
1158
/* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
1159
### put them into POOL, so we're okay. */
1160
SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool));
1077
1163
return SVN_NO_ERROR;
1080
static svn_error_t *
1081
1167
svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
1082
1168
const char **url,
1083
1169
apr_pool_t *pool)