2 * serf.c : entry point for ra_serf
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
26 #define APR_WANT_STRFUNC
32 #include "svn_pools.h"
36 #include "../libsvn_ra/ra_loader.h"
37 #include "svn_config.h"
38 #include "svn_delta.h"
39 #include "svn_dirent_uri.h"
43 #include "svn_version.h"
45 #include "private/svn_dav_protocol.h"
46 #include "private/svn_dep_compat.h"
47 #include "private/svn_fspath.h"
48 #include "private/svn_subr_private.h"
49 #include "svn_private_config.h"
54 /* Implements svn_ra__vtable_t.get_version(). */
55 static const svn_version_t *
61 #define RA_SERF_DESCRIPTION \
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(). */
70 ra_serf_get_description(apr_pool_t *pool)
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(). */
79 static const char * const *
80 ra_serf_get_schemes(apr_pool_t *pool)
82 static const char *serf_ssl[] = { "http", "https", NULL };
84 /* ### Temporary: to shut up a warning. */
85 static const char *serf_no_ssl[] = { "http", NULL };
88 /* TODO: Runtime detection. */
92 /* Load the setting http-auth-types from the global or server specific
93 section, parse its value and set the types of authentication we should
94 accept from the server. */
96 load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
97 const char *server_group,
100 const char *http_auth_types = NULL;
101 *authn_types = SERF_AUTHN_NONE;
103 svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
104 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
108 svn_config_get(config, &http_auth_types, server_group,
109 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types);
115 char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
116 apr_collapse_spaces(auth_types_list, http_auth_types);
117 while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL)
119 if (svn_cstring_casecmp("basic", token) == 0)
120 *authn_types |= SERF_AUTHN_BASIC;
121 else if (svn_cstring_casecmp("digest", token) == 0)
122 *authn_types |= SERF_AUTHN_DIGEST;
123 else if (svn_cstring_casecmp("ntlm", token) == 0)
124 *authn_types |= SERF_AUTHN_NTLM;
125 else if (svn_cstring_casecmp("negotiate", token) == 0)
126 *authn_types |= SERF_AUTHN_NEGOTIATE;
128 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
129 _("Invalid config: unknown %s "
131 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token);
136 /* Nothing specified by the user, so accept all types. */
137 *authn_types = SERF_AUTHN_ALL;
143 /* Default HTTP timeout (in seconds); overridden by the 'http-timeout'
144 runtime configuration variable. */
145 #define DEFAULT_HTTP_TIMEOUT 600
147 /* Private symbol for the 1.9-public SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS */
148 #define OPTION_HTTP_CHUNKED_REQUESTS "http-chunked-requests"
152 load_config(svn_ra_serf__session_t *session,
153 apr_hash_t *config_hash,
156 svn_config_t *config, *config_client;
157 const char *server_group;
158 const char *proxy_host = NULL;
159 const char *port_str = NULL;
160 const char *timeout_str = NULL;
161 const char *exceptions;
162 apr_port_t proxy_port;
163 svn_tristate_t chunked_requests;
167 config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS);
168 config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
173 config_client = NULL;
176 SVN_ERR(svn_config_get_bool(config, &session->using_compression,
177 SVN_CONFIG_SECTION_GLOBAL,
178 SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
179 svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
180 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
182 if (session->wc_callbacks->auth_baton)
186 svn_auth_set_parameter(session->wc_callbacks->auth_baton,
187 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
192 svn_auth_set_parameter(session->wc_callbacks->auth_baton,
193 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS,
198 /* Use the default proxy-specific settings if and only if
199 "http-proxy-exceptions" is not set to exclude this host. */
200 svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
201 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
202 if (! svn_cstring_match_glob_list(session->session_url.hostname,
203 svn_cstring_split(exceptions, ",",
206 svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
207 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
208 svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
209 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
210 svn_config_get(config, &session->proxy_username,
211 SVN_CONFIG_SECTION_GLOBAL,
212 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
213 svn_config_get(config, &session->proxy_password,
214 SVN_CONFIG_SECTION_GLOBAL,
215 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
218 /* Load the global ssl settings, if set. */
219 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
220 SVN_CONFIG_SECTION_GLOBAL,
221 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
223 svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
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));
247 server_group = svn_config_find_group(config,
248 session->session_url.hostname,
249 SVN_CONFIG_SECTION_GROUPS, pool);
255 SVN_ERR(svn_config_get_bool(config, &session->using_compression,
257 SVN_CONFIG_OPTION_HTTP_COMPRESSION,
258 session->using_compression));
259 svn_config_get(config, &timeout_str, server_group,
260 SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
262 svn_auth_set_parameter(session->wc_callbacks->auth_baton,
263 SVN_AUTH_PARAM_SERVER_GROUP, server_group);
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? */
269 svn_config_get(config, &proxy_host, server_group,
270 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
271 svn_config_get(config, &port_str, server_group,
272 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
273 svn_config_get(config, &session->proxy_username, server_group,
274 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
275 session->proxy_username);
276 svn_config_get(config, &session->proxy_password, server_group,
277 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
278 session->proxy_password);
280 /* Load the group ssl settings. */
281 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
283 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
284 session->trust_default_ca));
285 svn_config_get(config, &session->ssl_authorities, server_group,
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;
318 /* Parse the connection timeout value, if any. */
319 session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
323 const long int timeout = strtol(timeout_str, &endstr, 10);
326 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
327 _("Invalid config: illegal character in "
330 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
331 _("Invalid config: negative timeout value"));
332 session->timeout = apr_time_from_sec(timeout);
334 SVN_ERR_ASSERT(session->timeout >= 0);
336 /* Convert the proxy port value, if any. */
340 const long int port = strtol(port_str, &endstr, 10);
343 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
344 _("Invalid URL: illegal character in proxy "
347 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
348 _("Invalid URL: negative proxy port number"));
350 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
351 _("Invalid URL: proxy port number greater "
352 "than maximum TCP port number 65535"));
353 proxy_port = (apr_port_t) port;
362 apr_sockaddr_t *proxy_addr;
365 status = apr_sockaddr_info_get(&proxy_addr, proxy_host,
366 APR_UNSPEC, proxy_port, 0,
370 return svn_ra_serf__wrap_err(
371 status, _("Could not resolve proxy server '%s'"),
374 session->using_proxy = TRUE;
375 serf_config_proxy(session->context, proxy_addr);
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;
400 /* Setup authentication. */
401 SVN_ERR(load_http_auth_types(pool, config, server_group,
402 &session->authn_types));
403 serf_config_authn_types(session->context, session->authn_types);
404 serf_config_credentials_callback(session->context,
405 svn_ra_serf__credentials_callback);
409 #undef DEFAULT_HTTP_TIMEOUT
412 svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
414 const svn_ra_serf__session_t *serf_sess = progress_baton;
415 if (serf_sess->progress_func)
417 serf_sess->progress_func(read + written, -1,
418 serf_sess->progress_baton,
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(). */
437 svn_ra_serf__open(svn_ra_session_t *session,
438 const char **corrected_url,
439 const char *session_URL,
440 const svn_ra_callbacks2_t *callbacks,
441 void *callback_baton,
446 svn_ra_serf__session_t *serf_sess;
448 const char *client_string = NULL;
452 *corrected_url = NULL;
454 serf_sess = apr_pcalloc(pool, sizeof(*serf_sess));
455 serf_sess->pool = svn_pool_create(pool);
456 serf_sess->wc_callbacks = callbacks;
457 serf_sess->wc_callback_baton = callback_baton;
458 serf_sess->progress_func = callbacks->progress_func;
459 serf_sess->progress_baton = callbacks->progress_baton;
460 serf_sess->cancel_func = callbacks->cancel_func;
461 serf_sess->cancel_baton = callback_baton;
463 /* todo: reuse serf context across sessions */
464 serf_sess->context = serf_context_create(serf_sess->pool);
466 SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache,
470 status = apr_uri_parse(serf_sess->pool, session_URL, &url);
473 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
474 _("Illegal URL '%s'"),
477 /* Depending the version of apr-util in use, for root paths url.path
478 will be NULL or "", where serf requires "/". */
479 if (url.path == NULL || url.path[0] == '\0')
481 url.path = apr_pstrdup(serf_sess->pool, "/");
485 url.port = apr_uri_port_of_scheme(url.scheme);
487 serf_sess->session_url = url;
488 serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL);
489 serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
491 serf_sess->supports_deadprop_count = svn_tristate_unknown;
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;
503 SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
505 serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
506 sizeof(*serf_sess->conns[0]));
507 serf_sess->conns[0]->bkt_alloc =
508 serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
509 serf_sess->conns[0]->session = serf_sess;
510 serf_sess->conns[0]->last_status_code = -1;
512 /* create the user agent string */
513 if (callbacks->get_client_string)
514 SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool));
517 serf_sess->useragent = apr_pstrcat(pool, get_user_agent_string(pool), " ",
518 client_string, (char *)NULL);
520 serf_sess->useragent = get_user_agent_string(pool);
522 /* go ahead and tell serf about the connection. */
524 serf_connection_create2(&serf_sess->conns[0]->conn,
527 svn_ra_serf__conn_setup, serf_sess->conns[0],
528 svn_ra_serf__conn_closed, serf_sess->conns[0],
531 return svn_ra_serf__wrap_err(status, NULL);
533 /* Set the progress callback. */
534 serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
537 serf_sess->num_conns = 1;
539 session->priv = serf_sess;
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(). */
562 svn_ra_serf__reparent(svn_ra_session_t *ra_session,
566 svn_ra_serf__session_t *session = ra_session->priv;
570 /* If it's the URL we already have, wave our hands and do nothing. */
571 if (strcmp(session->session_url_str, url) == 0)
576 if (!session->repos_root_str)
579 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
582 if (!svn_uri__is_ancestor(session->repos_root_str, url))
584 return svn_error_createf(
585 SVN_ERR_RA_ILLEGAL_URL, NULL,
586 _("URL '%s' is not a child of the session's repository root "
587 "URL '%s'"), url, session->repos_root_str);
590 status = apr_uri_parse(pool, url, &new_url);
593 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
594 _("Illegal repository URL '%s'"), url);
597 /* Depending the version of apr-util in use, for root paths url.path
598 will be NULL or "", where serf requires "/". */
599 /* ### Maybe we should use a string buffer for these strings so we
600 ### don't allocate memory in the session on every reparent? */
601 if (new_url.path == NULL || new_url.path[0] == '\0')
603 session->session_url.path = apr_pstrdup(session->pool, "/");
607 session->session_url.path = apr_pstrdup(session->pool, new_url.path);
609 session->session_url_str = apr_pstrdup(session->pool, url);
614 /* Implements svn_ra__vtable_t.get_session_url(). */
616 svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
620 svn_ra_serf__session_t *session = ra_session->priv;
621 *url = apr_pstrdup(pool, session->session_url_str);
625 /* Implements svn_ra__vtable_t.get_latest_revnum(). */
627 svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
628 svn_revnum_t *latest_revnum,
631 svn_ra_serf__session_t *session = ra_session->priv;
633 return svn_error_trace(svn_ra_serf__get_youngest_revnum(
634 latest_revnum, session, pool));
637 /* Implements svn_ra__vtable_t.rev_proplist(). */
639 svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
641 apr_hash_t **ret_props,
644 svn_ra_serf__session_t *session = ra_session->priv;
646 const char *propfind_path;
648 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
650 propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
652 /* svn_ra_serf__retrieve_props() wants to added the revision as
653 a Label to the PROPFIND, which isn't really necessary when
654 querying a rev-stub URI. *Shrug* Probably okay to leave the
655 Label, but whatever. */
656 rev = SVN_INVALID_REVNUM;
660 /* Use the VCC as the propfind target path. */
661 SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool));
664 /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash. */
665 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
666 propfind_path, rev, "0", all_props,
669 SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props,
675 /* Implements svn_ra__vtable_t.rev_prop(). */
677 svn_ra_serf__rev_prop(svn_ra_session_t *session,
680 svn_string_t **value,
685 SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
687 *value = svn_hash_gets(props, name);
693 fetch_path_props(apr_hash_t **props,
694 svn_ra_serf__session_t *session,
695 const char *session_relpath,
696 svn_revnum_t revision,
697 const svn_ra_serf__dav_props_t *desired_props,
698 apr_pool_t *result_pool,
699 apr_pool_t *scratch_pool)
703 url = session->session_url.path;
705 /* If we have a relative path, append it. */
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));
729 /* Implements svn_ra__vtable_t.check_path(). */
731 svn_ra_serf__check_path(svn_ra_session_t *ra_session,
732 const char *rel_path,
733 svn_revnum_t revision,
734 svn_node_kind_t *kind,
737 svn_ra_serf__session_t *session = ra_session->priv;
740 svn_error_t *err = fetch_path_props(&props, session, rel_path,
741 revision, check_path_props,
744 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
746 svn_error_clear(err);
747 *kind = svn_node_none;
751 /* Any other error, raise to caller. */
753 return svn_error_trace(err);
755 SVN_ERR(svn_ra_serf__get_resource_type(kind, props));
762 struct dirent_walker_baton_t {
763 /* Update the fields in this entry. */
766 svn_tristate_t *supports_deadprop_count;
768 /* If allocations are necessary, then use this pool. */
769 apr_pool_t *result_pool;
773 dirent_walker(void *baton,
776 const svn_string_t *val,
777 apr_pool_t *scratch_pool)
779 struct dirent_walker_baton_t *dwb = baton;
781 if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
783 dwb->entry->has_props = TRUE;
785 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
787 dwb->entry->has_props = TRUE;
789 else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
791 if(strcmp(name, "deadprop-count") == 0)
795 apr_int64_t deadprop_count;
796 SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
797 dwb->entry->has_props = deadprop_count > 0;
798 if (dwb->supports_deadprop_count)
799 *dwb->supports_deadprop_count = svn_tristate_true;
801 else if (dwb->supports_deadprop_count)
802 *dwb->supports_deadprop_count = svn_tristate_false;
805 else if (strcmp(ns, "DAV:") == 0)
807 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
809 dwb->entry->created_rev = SVN_STR_TO_REV(val->data);
811 else if (strcmp(name, "creator-displayname") == 0)
813 dwb->entry->last_author = val->data;
815 else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
817 SVN_ERR(svn_time_from_cstring(&dwb->entry->time,
821 else if (strcmp(name, "getcontentlength") == 0)
823 /* 'getcontentlength' property is empty for directories. */
826 SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data));
829 else if (strcmp(name, "resourcetype") == 0)
831 if (strcmp(val->data, "collection") == 0)
833 dwb->entry->kind = svn_node_dir;
837 dwb->entry->kind = svn_node_file;
845 struct path_dirent_visitor_t {
846 apr_hash_t *full_paths;
847 apr_hash_t *base_paths;
848 const char *orig_path;
849 svn_tristate_t supports_deadprop_count;
850 apr_pool_t *result_pool;
854 path_dirent_walker(void *baton,
855 const char *path, apr_ssize_t path_len,
856 const char *ns, apr_ssize_t ns_len,
857 const char *name, apr_ssize_t name_len,
858 const svn_string_t *val,
861 struct path_dirent_visitor_t *dirents = baton;
862 struct dirent_walker_baton_t dwb;
865 /* Skip our original path. */
866 if (strcmp(path, dirents->orig_path) == 0)
871 entry = apr_hash_get(dirents->full_paths, path, path_len);
875 const char *base_name;
877 entry = svn_dirent_create(pool);
879 apr_hash_set(dirents->full_paths, path, path_len, entry);
881 base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool),
884 svn_hash_sets(dirents->base_paths, base_name, entry);
888 dwb.supports_deadprop_count = &dirents->supports_deadprop_count;
889 dwb.result_pool = dirents->result_pool;
890 return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool));
893 static const svn_ra_serf__dav_props_t *
894 get_dirent_props(apr_uint32_t dirent_fields,
895 svn_ra_serf__session_t *session,
898 svn_ra_serf__dav_props_t *prop;
899 apr_array_header_t *props = apr_array_make
900 (pool, 7, sizeof(svn_ra_serf__dav_props_t));
902 if (session->supports_deadprop_count != svn_tristate_false
903 || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
905 if (dirent_fields & SVN_DIRENT_KIND)
907 prop = apr_array_push(props);
908 prop->namespace = "DAV:";
909 prop->name = "resourcetype";
912 if (dirent_fields & SVN_DIRENT_SIZE)
914 prop = apr_array_push(props);
915 prop->namespace = "DAV:";
916 prop->name = "getcontentlength";
919 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
921 prop = apr_array_push(props);
922 prop->namespace = SVN_DAV_PROP_NS_DAV;
923 prop->name = "deadprop-count";
926 if (dirent_fields & SVN_DIRENT_CREATED_REV)
928 svn_ra_serf__dav_props_t *p = apr_array_push(props);
929 p->namespace = "DAV:";
930 p->name = SVN_DAV__VERSION_NAME;
933 if (dirent_fields & SVN_DIRENT_TIME)
935 prop = apr_array_push(props);
936 prop->namespace = "DAV:";
937 prop->name = SVN_DAV__CREATIONDATE;
940 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
942 prop = apr_array_push(props);
943 prop->namespace = "DAV:";
944 prop->name = "creator-displayname";
949 /* We found an old subversion server that can't handle
950 the deadprop-count property in the way we expect.
952 The neon behavior is to retrieve all properties in this case */
953 prop = apr_array_push(props);
954 prop->namespace = "DAV:";
955 prop->name = "allprop";
958 prop = apr_array_push(props);
959 prop->namespace = NULL;
962 return (svn_ra_serf__dav_props_t *) props->elts;
965 /* Implements svn_ra__vtable_t.stat(). */
967 svn_ra_serf__stat(svn_ra_session_t *ra_session,
968 const char *rel_path,
969 svn_revnum_t revision,
970 svn_dirent_t **dirent,
973 svn_ra_serf__session_t *session = ra_session->priv;
976 struct dirent_walker_baton_t dwb;
977 svn_tristate_t deadprop_count = svn_tristate_unknown;
979 err = fetch_path_props(&props,
980 session, rel_path, revision,
981 get_dirent_props(SVN_DIRENT_ALL, session, pool),
985 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
987 svn_error_clear(err);
992 return svn_error_trace(err);
995 dwb.entry = svn_dirent_create(pool);
996 dwb.supports_deadprop_count = &deadprop_count;
997 dwb.result_pool = pool;
998 SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
1000 if (deadprop_count == svn_tristate_false
1001 && session->supports_deadprop_count == svn_tristate_unknown
1002 && !dwb.entry->has_props)
1004 /* We have to requery as the server didn't give us the right
1006 session->supports_deadprop_count = svn_tristate_false;
1008 SVN_ERR(fetch_path_props(&props,
1009 session, rel_path, SVN_INVALID_REVNUM,
1010 get_dirent_props(SVN_DIRENT_ALL, session, pool),
1013 SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
1016 if (deadprop_count != svn_tristate_unknown)
1017 session->supports_deadprop_count = deadprop_count;
1019 *dirent = dwb.entry;
1021 return SVN_NO_ERROR;
1024 /* Reads the 'resourcetype' property from the list PROPS and checks if the
1025 * resource at PATH@REVISION really is a directory. Returns
1026 * SVN_ERR_FS_NOT_DIRECTORY if not.
1028 static svn_error_t *
1029 resource_is_directory(apr_hash_t *props)
1031 svn_node_kind_t kind;
1033 SVN_ERR(svn_ra_serf__get_resource_type(&kind, props));
1035 if (kind != svn_node_dir)
1037 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1038 _("Can't get entries of non-directory"));
1041 return SVN_NO_ERROR;
1044 /* Implements svn_ra__vtable_t.get_dir(). */
1045 static svn_error_t *
1046 svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
1047 apr_hash_t **dirents,
1048 svn_revnum_t *fetched_rev,
1049 apr_hash_t **ret_props,
1050 const char *rel_path,
1051 svn_revnum_t revision,
1052 apr_uint32_t dirent_fields,
1055 svn_ra_serf__session_t *session = ra_session->priv;
1058 path = session->session_url.path;
1060 /* If we have a relative path, URI encode and append it. */
1063 path = svn_path_url_add_component2(path, rel_path, pool);
1066 /* If the user specified a peg revision other than HEAD, we have to fetch
1067 the baseline collection url for that revision. If not, we can use the
1069 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
1071 SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
1072 session, NULL /* conn */,
1075 revision = SVN_INVALID_REVNUM;
1077 /* REVISION is always SVN_INVALID_REVNUM */
1078 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
1080 /* If we're asked for children, fetch them now. */
1083 struct path_dirent_visitor_t dirent_walk;
1087 /* Always request node kind to check that path is really a
1090 dirent_fields |= SVN_DIRENT_KIND;
1091 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
1092 path, SVN_INVALID_REVNUM, "1",
1093 get_dirent_props(dirent_fields,
1097 /* Check if the path is really a directory. */
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"));
1103 /* We're going to create two hashes to help the walker along.
1104 * We're going to return the 2nd one back to the caller as it
1105 * will have the basenames it expects.
1107 dirent_walk.full_paths = apr_hash_make(pool);
1108 dirent_walk.base_paths = apr_hash_make(pool);
1109 dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool);
1110 dirent_walk.supports_deadprop_count = svn_tristate_unknown;
1111 dirent_walk.result_pool = pool;
1113 SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
1114 path_dirent_walker, &dirent_walk,
1117 if (dirent_walk.supports_deadprop_count == svn_tristate_false
1118 && session->supports_deadprop_count == svn_tristate_unknown
1119 && dirent_fields & SVN_DIRENT_HAS_PROPS)
1121 /* We have to requery as the server didn't give us the right
1123 session->supports_deadprop_count = svn_tristate_false;
1124 SVN_ERR(svn_ra_serf__retrieve_props(&props, session,
1126 path, SVN_INVALID_REVNUM, "1",
1127 get_dirent_props(dirent_fields,
1131 apr_hash_clear(dirent_walk.full_paths);
1132 apr_hash_clear(dirent_walk.base_paths);
1134 SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
1136 &dirent_walk, pool));
1139 *dirents = dirent_walk.base_paths;
1141 if (dirent_walk.supports_deadprop_count != svn_tristate_unknown)
1142 session->supports_deadprop_count = dirent_walk.supports_deadprop_count;
1145 /* If we're asked for the directory properties, fetch them too. */
1150 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0],
1151 path, SVN_INVALID_REVNUM,
1155 /* Check if the path is really a directory. */
1156 SVN_ERR(resource_is_directory(props));
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));
1163 return SVN_NO_ERROR;
1167 svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
1171 svn_ra_serf__session_t *session = ra_session->priv;
1173 if (!session->repos_root_str)
1175 const char *vcc_url;
1176 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
1179 *url = session->repos_root_str;
1180 return SVN_NO_ERROR;
1183 /* TODO: to fetch the uuid from the repository, we need:
1184 1. a path that exists in HEAD
1185 2. a path that's readable
1187 get_uuid handles the case where a path doesn't exist in HEAD and also the
1188 case where the root of the repository is not readable.
1189 However, it does not handle the case where we're fetching path not existing
1190 in HEAD of a repository with unreadable root directory.
1192 Implements svn_ra__vtable_t.get_uuid().
1194 static svn_error_t *
1195 svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
1199 svn_ra_serf__session_t *session = ra_session->priv;
1203 const char *vcc_url;
1205 /* We should never get here if we have HTTP v2 support, because
1206 any server with that support should be transmitting the
1207 UUID in the initial OPTIONS response. */
1208 SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
1210 /* We're not interested in vcc_url and relative_url, but this call also
1211 stores the repository's uuid in the session. */
1212 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
1215 return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
1216 _("The UUID property was not found on the "
1217 "resource or any of its parents"));
1221 *uuid = session->uuid;
1223 return SVN_NO_ERROR;
1227 static const svn_ra__vtable_t serf_vtable = {
1229 ra_serf_get_description,
1230 ra_serf_get_schemes,
1232 svn_ra_serf__reparent,
1233 svn_ra_serf__get_session_url,
1234 svn_ra_serf__get_latest_revnum,
1235 svn_ra_serf__get_dated_revision,
1236 svn_ra_serf__change_rev_prop,
1237 svn_ra_serf__rev_proplist,
1238 svn_ra_serf__rev_prop,
1239 svn_ra_serf__get_commit_editor,
1240 svn_ra_serf__get_file,
1241 svn_ra_serf__get_dir,
1242 svn_ra_serf__get_mergeinfo,
1243 svn_ra_serf__do_update,
1244 svn_ra_serf__do_switch,
1245 svn_ra_serf__do_status,
1246 svn_ra_serf__do_diff,
1247 svn_ra_serf__get_log,
1248 svn_ra_serf__check_path,
1250 svn_ra_serf__get_uuid,
1251 svn_ra_serf__get_repos_root,
1252 svn_ra_serf__get_locations,
1253 svn_ra_serf__get_location_segments,
1254 svn_ra_serf__get_file_revs,
1256 svn_ra_serf__unlock,
1257 svn_ra_serf__get_lock,
1258 svn_ra_serf__get_locks,
1259 svn_ra_serf__replay,
1260 svn_ra_serf__has_capability,
1261 svn_ra_serf__replay_range,
1262 svn_ra_serf__get_deleted_rev,
1263 svn_ra_serf__register_editor_shim_callbacks,
1264 svn_ra_serf__get_inherited_props
1268 svn_ra_serf__init(const svn_version_t *loader_version,
1269 const svn_ra__vtable_t **vtable,
1272 static const svn_version_checklist_t checklist[] =
1274 { "svn_subr", svn_subr_version },
1275 { "svn_delta", svn_delta_version },
1282 SVN_ERR(svn_ver_check_list2(ra_serf_version(), checklist, svn_ver_equal));
1284 /* Simplified version check to make sure we can safely use the
1285 VTABLE parameter. The RA loader does a more exhaustive check. */
1286 if (loader_version->major != SVN_VER_MAJOR)
1288 return svn_error_createf(
1289 SVN_ERR_VERSION_MISMATCH, NULL,
1290 _("Unsupported RA loader version (%d) for ra_serf"),
1291 loader_version->major);
1294 /* Make sure that we have loaded a compatible library: the MAJOR must
1295 match, and the minor must be at *least* what we compiled against.
1296 The patch level is simply ignored. */
1297 serf_lib_version(&serf_major, &serf_minor, &serf_patch);
1298 if (serf_major != SERF_MAJOR_VERSION
1299 || serf_minor < SERF_MINOR_VERSION)
1301 return svn_error_createf(
1302 /* ### should return a unique error */
1303 SVN_ERR_VERSION_MISMATCH, NULL,
1304 _("ra_serf was compiled for serf %d.%d.%d but loaded "
1305 "an incompatible %d.%d.%d library"),
1306 SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION,
1307 serf_major, serf_minor, serf_patch);
1310 *vtable = &serf_vtable;
1312 return SVN_NO_ERROR;
1315 /* Compatibility wrapper for pre-1.2 subversions. Needed? */
1316 #define NAME "ra_serf"
1317 #define DESCRIPTION RA_SERF_DESCRIPTION
1318 #define VTBL serf_vtable
1319 #define INITFUNC svn_ra_serf__init
1320 #define COMPAT_INITFUNC svn_ra_serf_init
1321 #include "../libsvn_ra/wrapper_template.h"