summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_ra_serf/serf.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_ra_serf/serf.c')
-rw-r--r--subversion/libsvn_ra_serf/serf.c1246
1 files changed, 1246 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_serf/serf.c b/subversion/libsvn_ra_serf/serf.c
new file mode 100644
index 0000000..6016157
--- /dev/null
+++ b/subversion/libsvn_ra_serf/serf.c
@@ -0,0 +1,1246 @@
+/*
+ * serf.c : entry point for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_version.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+
+
+/* Implements svn_ra__vtable_t.get_version(). */
+static const svn_version_t *
+ra_serf_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+#define RA_SERF_DESCRIPTION \
+ N_("Module for accessing a repository via WebDAV protocol using serf.")
+
+/* Implements svn_ra__vtable_t.get_description(). */
+static const char *
+ra_serf_get_description(void)
+{
+ return _(RA_SERF_DESCRIPTION);
+}
+
+/* Implements svn_ra__vtable_t.get_schemes(). */
+static const char * const *
+ra_serf_get_schemes(apr_pool_t *pool)
+{
+ static const char *serf_ssl[] = { "http", "https", NULL };
+#if 0
+ /* ### Temporary: to shut up a warning. */
+ static const char *serf_no_ssl[] = { "http", NULL };
+#endif
+
+ /* TODO: Runtime detection. */
+ return serf_ssl;
+}
+
+/* Load the setting http-auth-types from the global or server specific
+ section, parse its value and set the types of authentication we should
+ accept from the server. */
+static svn_error_t *
+load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
+ const char *server_group,
+ int *authn_types)
+{
+ const char *http_auth_types = NULL;
+ *authn_types = SERF_AUTHN_NONE;
+
+ svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
+
+ if (server_group)
+ {
+ svn_config_get(config, &http_auth_types, server_group,
+ SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types);
+ }
+
+ if (http_auth_types)
+ {
+ char *token;
+ char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
+ apr_collapse_spaces(auth_types_list, http_auth_types);
+ while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL)
+ {
+ if (svn_cstring_casecmp("basic", token) == 0)
+ *authn_types |= SERF_AUTHN_BASIC;
+ else if (svn_cstring_casecmp("digest", token) == 0)
+ *authn_types |= SERF_AUTHN_DIGEST;
+ else if (svn_cstring_casecmp("ntlm", token) == 0)
+ *authn_types |= SERF_AUTHN_NTLM;
+ else if (svn_cstring_casecmp("negotiate", token) == 0)
+ *authn_types |= SERF_AUTHN_NEGOTIATE;
+ else
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: unknown %s "
+ "'%s'"),
+ SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token);
+ }
+ }
+ else
+ {
+ /* Nothing specified by the user, so accept all types. */
+ *authn_types = SERF_AUTHN_ALL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Default HTTP timeout (in seconds); overridden by the 'http-timeout'
+ runtime configuration variable. */
+#define DEFAULT_HTTP_TIMEOUT 600
+
+static svn_error_t *
+load_config(svn_ra_serf__session_t *session,
+ apr_hash_t *config_hash,
+ apr_pool_t *pool)
+{
+ svn_config_t *config, *config_client;
+ const char *server_group;
+ const char *proxy_host = NULL;
+ const char *port_str = NULL;
+ const char *timeout_str = NULL;
+ const char *exceptions;
+ apr_port_t proxy_port;
+
+ if (config_hash)
+ {
+ config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS);
+ config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
+ }
+ else
+ {
+ config = NULL;
+ config_client = NULL;
+ }
+
+ SVN_ERR(svn_config_get_bool(config, &session->using_compression,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
+ svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
+
+ if (session->wc_callbacks->auth_baton)
+ {
+ if (config_client)
+ {
+ svn_auth_set_parameter(session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
+ config_client);
+ }
+ if (config)
+ {
+ svn_auth_set_parameter(session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS,
+ config);
+ }
+ }
+
+ /* Use the default proxy-specific settings if and only if
+ "http-proxy-exceptions" is not set to exclude this host. */
+ svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
+ if (! svn_cstring_match_glob_list(session->session_url.hostname,
+ svn_cstring_split(exceptions, ",",
+ TRUE, pool)))
+ {
+ svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
+ svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
+ svn_config_get(config, &session->proxy_username,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
+ svn_config_get(config, &session->proxy_password,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
+ }
+
+ /* Load the global ssl settings, if set. */
+ SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
+ TRUE));
+ svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
+
+ /* If set, read the flag that tells us to do bulk updates or not. Defaults
+ to skelta updates. */
+ SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
+ "auto",
+ svn_tristate_unknown));
+
+ /* Load the maximum number of parallel session connections. */
+ SVN_ERR(svn_config_get_int64(config, &session->max_connections,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
+ SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS));
+
+ if (config)
+ server_group = svn_config_find_group(config,
+ session->session_url.hostname,
+ SVN_CONFIG_SECTION_GROUPS, pool);
+ else
+ server_group = NULL;
+
+ if (server_group)
+ {
+ SVN_ERR(svn_config_get_bool(config, &session->using_compression,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_COMPRESSION,
+ session->using_compression));
+ svn_config_get(config, &timeout_str, server_group,
+ SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
+
+ svn_auth_set_parameter(session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SERVER_GROUP, server_group);
+
+ /* Load the group proxy server settings, overriding global
+ settings. We intentionally ignore 'http-proxy-exceptions'
+ here because, well, if this site was an exception, why is
+ there a per-server proxy configuration for it? */
+ svn_config_get(config, &proxy_host, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
+ svn_config_get(config, &port_str, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
+ svn_config_get(config, &session->proxy_username, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
+ session->proxy_username);
+ svn_config_get(config, &session->proxy_password, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
+ session->proxy_password);
+
+ /* Load the group ssl settings. */
+ SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
+ server_group,
+ SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
+ session->trust_default_ca));
+ svn_config_get(config, &session->ssl_authorities, server_group,
+ SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
+ session->ssl_authorities);
+
+ /* Load the group bulk updates flag. */
+ SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
+ "auto",
+ session->bulk_updates));
+
+ /* Load the maximum number of parallel session connections,
+ overriding global values. */
+ SVN_ERR(svn_config_get_int64(config, &session->max_connections,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
+ session->max_connections));
+ }
+
+ /* Don't allow the http-max-connections value to be larger than our
+ compiled-in limit, or to be too small to operate. Broken
+ functionality and angry administrators are equally undesirable. */
+ if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT)
+ session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT;
+ if (session->max_connections < 2)
+ session->max_connections = 2;
+
+ /* Parse the connection timeout value, if any. */
+ session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
+ if (timeout_str)
+ {
+ char *endstr;
+ const long int timeout = strtol(timeout_str, &endstr, 10);
+
+ if (*endstr)
+ return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: illegal character in "
+ "timeout value"));
+ if (timeout < 0)
+ return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: negative timeout value"));
+ session->timeout = apr_time_from_sec(timeout);
+ }
+ SVN_ERR_ASSERT(session->timeout >= 0);
+
+ /* Convert the proxy port value, if any. */
+ if (port_str)
+ {
+ char *endstr;
+ const long int port = strtol(port_str, &endstr, 10);
+
+ if (*endstr)
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Invalid URL: illegal character in proxy "
+ "port number"));
+ if (port < 0)
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Invalid URL: negative proxy port number"));
+ if (port > 65535)
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Invalid URL: proxy port number greater "
+ "than maximum TCP port number 65535"));
+ proxy_port = (apr_port_t) port;
+ }
+ else
+ {
+ proxy_port = 80;
+ }
+
+ if (proxy_host)
+ {
+ apr_sockaddr_t *proxy_addr;
+ apr_status_t status;
+
+ status = apr_sockaddr_info_get(&proxy_addr, proxy_host,
+ APR_UNSPEC, proxy_port, 0,
+ session->pool);
+ if (status)
+ {
+ return svn_ra_serf__wrap_err(
+ status, _("Could not resolve proxy server '%s'"),
+ proxy_host);
+ }
+ session->using_proxy = TRUE;
+ serf_config_proxy(session->context, proxy_addr);
+ }
+ else
+ {
+ session->using_proxy = FALSE;
+ }
+
+ /* Setup authentication. */
+ SVN_ERR(load_http_auth_types(pool, config, server_group,
+ &session->authn_types));
+ serf_config_authn_types(session->context, session->authn_types);
+ serf_config_credentials_callback(session->context,
+ svn_ra_serf__credentials_callback);
+
+ return SVN_NO_ERROR;
+}
+#undef DEFAULT_HTTP_TIMEOUT
+
+static void
+svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
+{
+ const svn_ra_serf__session_t *serf_sess = progress_baton;
+ if (serf_sess->progress_func)
+ {
+ serf_sess->progress_func(read + written, -1,
+ serf_sess->progress_baton,
+ serf_sess->pool);
+ }
+}
+
+/* Implements svn_ra__vtable_t.open_session(). */
+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,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ svn_ra_serf__session_t *serf_sess;
+ apr_uri_t url;
+ const char *client_string = NULL;
+
+ if (corrected_url)
+ *corrected_url = NULL;
+
+ serf_sess = apr_pcalloc(pool, sizeof(*serf_sess));
+ serf_sess->pool = svn_pool_create(pool);
+ serf_sess->wc_callbacks = callbacks;
+ serf_sess->wc_callback_baton = callback_baton;
+ serf_sess->progress_func = callbacks->progress_func;
+ serf_sess->progress_baton = callbacks->progress_baton;
+ serf_sess->cancel_func = callbacks->cancel_func;
+ serf_sess->cancel_baton = callback_baton;
+
+ /* todo: reuse serf context across sessions */
+ serf_sess->context = serf_context_create(serf_sess->pool);
+
+ SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache,
+ serf_sess->pool));
+
+
+ status = apr_uri_parse(serf_sess->pool, session_URL, &url);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal URL '%s'"),
+ session_URL);
+ }
+ /* Depending the version of apr-util in use, for root paths url.path
+ will be NULL or "", where serf requires "/". */
+ if (url.path == NULL || url.path[0] == '\0')
+ {
+ url.path = apr_pstrdup(serf_sess->pool, "/");
+ }
+ if (!url.port)
+ {
+ url.port = apr_uri_port_of_scheme(url.scheme);
+ }
+ serf_sess->session_url = url;
+ serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL);
+ serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
+
+ serf_sess->supports_deadprop_count = svn_tristate_unknown;
+
+ serf_sess->capabilities = apr_hash_make(serf_sess->pool);
+
+ /* We have to assume that the server only supports HTTP/1.0. Once it's clear
+ HTTP/1.1 is supported, we can upgrade. */
+ serf_sess->http10 = TRUE;
+
+ SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
+
+ serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
+ sizeof(*serf_sess->conns[0]));
+ serf_sess->conns[0]->bkt_alloc =
+ serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
+ serf_sess->conns[0]->session = serf_sess;
+ serf_sess->conns[0]->last_status_code = -1;
+
+ /* create the user agent string */
+ if (callbacks->get_client_string)
+ SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool));
+
+ if (client_string)
+ serf_sess->useragent = apr_pstrcat(pool, USER_AGENT, " ",
+ client_string, (char *)NULL);
+ else
+ serf_sess->useragent = USER_AGENT;
+
+ /* go ahead and tell serf about the connection. */
+ status =
+ serf_connection_create2(&serf_sess->conns[0]->conn,
+ serf_sess->context,
+ url,
+ svn_ra_serf__conn_setup, serf_sess->conns[0],
+ svn_ra_serf__conn_closed, serf_sess->conns[0],
+ serf_sess->pool);
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+
+ /* Set the progress callback. */
+ serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
+ serf_sess);
+
+ serf_sess->num_conns = 1;
+
+ session->priv = serf_sess;
+
+ return svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool);
+}
+
+/* Implements svn_ra__vtable_t.reparent(). */
+static svn_error_t *
+svn_ra_serf__reparent(svn_ra_session_t *ra_session,
+ const char *url,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_uri_t new_url;
+ apr_status_t status;
+
+ /* If it's the URL we already have, wave our hands and do nothing. */
+ if (strcmp(session->session_url_str, url) == 0)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if (!session->repos_root_str)
+ {
+ const char *vcc_url;
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
+ }
+
+ if (!svn_uri__is_ancestor(session->repos_root_str, url))
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' is not a child of the session's repository root "
+ "URL '%s'"), url, session->repos_root_str);
+ }
+
+ status = apr_uri_parse(pool, url, &new_url);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal repository URL '%s'"), url);
+ }
+
+ /* Depending the version of apr-util in use, for root paths url.path
+ will be NULL or "", where serf requires "/". */
+ /* ### Maybe we should use a string buffer for these strings so we
+ ### don't allocate memory in the session on every reparent? */
+ if (new_url.path == NULL || new_url.path[0] == '\0')
+ {
+ session->session_url.path = apr_pstrdup(session->pool, "/");
+ }
+ else
+ {
+ session->session_url.path = apr_pstrdup(session->pool, new_url.path);
+ }
+ session->session_url_str = apr_pstrdup(session->pool, url);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.get_session_url(). */
+static svn_error_t *
+svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ *url = apr_pstrdup(pool, session->session_url_str);
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.get_latest_revnum(). */
+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)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ return svn_error_trace(svn_ra_serf__get_youngest_revnum(
+ latest_revnum, session, pool));
+}
+
+/* Implements svn_ra__vtable_t.rev_proplist(). */
+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 *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_t *props;
+ const char *propfind_path;
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ {
+ propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
+
+ /* svn_ra_serf__retrieve_props() wants to added the revision as
+ a Label to the PROPFIND, which isn't really necessary when
+ querying a rev-stub URI. *Shrug* Probably okay to leave the
+ Label, but whatever. */
+ rev = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ /* Use the VCC as the propfind target path. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool));
+ }
+
+ /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash. */
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
+ propfind_path, rev, "0", all_props,
+ pool, pool));
+
+ SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props,
+ pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.rev_prop(). */
+static 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 *pool)
+{
+ apr_hash_t *props;
+
+ SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
+
+ *value = svn_hash_gets(props, name);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_path_props(apr_hash_t **props,
+ svn_ra_serf__session_t *session,
+ const char *session_relpath,
+ svn_revnum_t revision,
+ const svn_ra_serf__dav_props_t *desired_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *url;
+
+ url = session->session_url.path;
+
+ /* If we have a relative path, append it. */
+ if (session_relpath)
+ url = svn_path_url_add_component2(url, session_relpath, scratch_pool);
+
+ /* If we were given a specific revision, get a URL that refers to that
+ specific revision (rather than floating with HEAD). */
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ url, revision,
+ scratch_pool, scratch_pool));
+ }
+
+ /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
+ Or we started with SVN_INVALID_REVNUM and URL may be floating. */
+ SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0],
+ url, SVN_INVALID_REVNUM,
+ desired_props,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.check_path(). */
+static svn_error_t *
+svn_ra_serf__check_path(svn_ra_session_t *ra_session,
+ const char *rel_path,
+ svn_revnum_t revision,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_t *props;
+
+ svn_error_t *err = fetch_path_props(&props, session, rel_path,
+ revision, check_path_props,
+ pool, pool);
+
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *kind = svn_node_none;
+ }
+ else
+ {
+ /* Any other error, raise to caller. */
+ if (err)
+ return svn_error_trace(err);
+
+ SVN_ERR(svn_ra_serf__get_resource_type(kind, props));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct dirent_walker_baton_t {
+ /* Update the fields in this entry. */
+ svn_dirent_t *entry;
+
+ svn_tristate_t *supports_deadprop_count;
+
+ /* If allocations are necessary, then use this pool. */
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+dirent_walker(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ struct dirent_walker_baton_t *dwb = baton;
+
+ if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
+ {
+ dwb->entry->has_props = TRUE;
+ }
+ else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
+ {
+ dwb->entry->has_props = TRUE;
+ }
+ else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
+ {
+ if(strcmp(name, "deadprop-count") == 0)
+ {
+ if (*val->data)
+ {
+ apr_int64_t deadprop_count;
+ SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
+ dwb->entry->has_props = deadprop_count > 0;
+ if (dwb->supports_deadprop_count)
+ *dwb->supports_deadprop_count = svn_tristate_true;
+ }
+ else if (dwb->supports_deadprop_count)
+ *dwb->supports_deadprop_count = svn_tristate_false;
+ }
+ }
+ else if (strcmp(ns, "DAV:") == 0)
+ {
+ if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
+ {
+ dwb->entry->created_rev = SVN_STR_TO_REV(val->data);
+ }
+ else if (strcmp(name, "creator-displayname") == 0)
+ {
+ dwb->entry->last_author = val->data;
+ }
+ else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
+ {
+ SVN_ERR(svn_time_from_cstring(&dwb->entry->time,
+ val->data,
+ dwb->result_pool));
+ }
+ else if (strcmp(name, "getcontentlength") == 0)
+ {
+ /* 'getcontentlength' property is empty for directories. */
+ if (val->len)
+ {
+ SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data));
+ }
+ }
+ else if (strcmp(name, "resourcetype") == 0)
+ {
+ if (strcmp(val->data, "collection") == 0)
+ {
+ dwb->entry->kind = svn_node_dir;
+ }
+ else
+ {
+ dwb->entry->kind = svn_node_file;
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct path_dirent_visitor_t {
+ apr_hash_t *full_paths;
+ apr_hash_t *base_paths;
+ const char *orig_path;
+ svn_tristate_t supports_deadprop_count;
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+path_dirent_walker(void *baton,
+ const char *path, apr_ssize_t path_len,
+ const char *ns, apr_ssize_t ns_len,
+ const char *name, apr_ssize_t name_len,
+ const svn_string_t *val,
+ apr_pool_t *pool)
+{
+ struct path_dirent_visitor_t *dirents = baton;
+ struct dirent_walker_baton_t dwb;
+ svn_dirent_t *entry;
+
+ /* Skip our original path. */
+ if (strcmp(path, dirents->orig_path) == 0)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ entry = apr_hash_get(dirents->full_paths, path, path_len);
+
+ if (!entry)
+ {
+ const char *base_name;
+
+ entry = svn_dirent_create(pool);
+
+ apr_hash_set(dirents->full_paths, path, path_len, entry);
+
+ base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool),
+ pool);
+
+ svn_hash_sets(dirents->base_paths, base_name, entry);
+ }
+
+ dwb.entry = entry;
+ dwb.supports_deadprop_count = &dirents->supports_deadprop_count;
+ dwb.result_pool = dirents->result_pool;
+ return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool));
+}
+
+static const svn_ra_serf__dav_props_t *
+get_dirent_props(apr_uint32_t dirent_fields,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__dav_props_t *prop;
+ apr_array_header_t *props = apr_array_make
+ (pool, 7, sizeof(svn_ra_serf__dav_props_t));
+
+ if (session->supports_deadprop_count != svn_tristate_false
+ || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
+ {
+ if (dirent_fields & SVN_DIRENT_KIND)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "resourcetype";
+ }
+
+ if (dirent_fields & SVN_DIRENT_SIZE)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "getcontentlength";
+ }
+
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = SVN_DAV_PROP_NS_DAV;
+ prop->name = "deadprop-count";
+ }
+
+ if (dirent_fields & SVN_DIRENT_CREATED_REV)
+ {
+ svn_ra_serf__dav_props_t *p = apr_array_push(props);
+ p->namespace = "DAV:";
+ p->name = SVN_DAV__VERSION_NAME;
+ }
+
+ if (dirent_fields & SVN_DIRENT_TIME)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = SVN_DAV__CREATIONDATE;
+ }
+
+ if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "creator-displayname";
+ }
+ }
+ else
+ {
+ /* We found an old subversion server that can't handle
+ the deadprop-count property in the way we expect.
+
+ The neon behavior is to retrieve all properties in this case */
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "allprop";
+ }
+
+ prop = apr_array_push(props);
+ prop->namespace = NULL;
+ prop->name = NULL;
+
+ return (svn_ra_serf__dav_props_t *) props->elts;
+}
+
+/* Implements svn_ra__vtable_t.stat(). */
+static svn_error_t *
+svn_ra_serf__stat(svn_ra_session_t *ra_session,
+ const char *rel_path,
+ svn_revnum_t revision,
+ svn_dirent_t **dirent,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_t *props;
+ svn_error_t *err;
+ struct dirent_walker_baton_t dwb;
+ svn_tristate_t deadprop_count = svn_tristate_unknown;
+
+ err = fetch_path_props(&props,
+ session, rel_path, revision,
+ get_dirent_props(SVN_DIRENT_ALL, session, pool),
+ pool, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *dirent = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ dwb.entry = svn_dirent_create(pool);
+ dwb.supports_deadprop_count = &deadprop_count;
+ dwb.result_pool = pool;
+ SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
+
+ if (deadprop_count == svn_tristate_false
+ && session->supports_deadprop_count == svn_tristate_unknown
+ && !dwb.entry->has_props)
+ {
+ /* We have to requery as the server didn't give us the right
+ information */
+ session->supports_deadprop_count = svn_tristate_false;
+
+ SVN_ERR(fetch_path_props(&props,
+ session, rel_path, SVN_INVALID_REVNUM,
+ get_dirent_props(SVN_DIRENT_ALL, session, pool),
+ pool, pool));
+
+ SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
+ }
+
+ if (deadprop_count != svn_tristate_unknown)
+ session->supports_deadprop_count = deadprop_count;
+
+ *dirent = dwb.entry;
+
+ return SVN_NO_ERROR;
+}
+
+/* Reads the 'resourcetype' property from the list PROPS and checks if the
+ * resource at PATH@REVISION really is a directory. Returns
+ * SVN_ERR_FS_NOT_DIRECTORY if not.
+ */
+static svn_error_t *
+resource_is_directory(apr_hash_t *props)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_ra_serf__get_resource_type(&kind, props));
+
+ if (kind != svn_node_dir)
+ {
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.get_dir(). */
+static svn_error_t *
+svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **ret_props,
+ const char *rel_path,
+ svn_revnum_t revision,
+ apr_uint32_t dirent_fields,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ const char *path;
+
+ path = session->session_url.path;
+
+ /* If we have a relative path, URI encode and append it. */
+ if (rel_path)
+ {
+ path = svn_path_url_add_component2(path, rel_path, pool);
+ }
+
+ /* If the user specified a peg revision other than HEAD, we have to fetch
+ the baseline collection url for that revision. If not, we can use the
+ public url. */
+ if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
+ {
+ SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
+ session, NULL /* conn */,
+ path, revision,
+ pool, pool));
+ revision = SVN_INVALID_REVNUM;
+ }
+ /* REVISION is always SVN_INVALID_REVNUM */
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
+
+ /* If we're asked for children, fetch them now. */
+ if (dirents)
+ {
+ struct path_dirent_visitor_t dirent_walk;
+ apr_hash_t *props;
+ const char *rtype;
+
+ /* Always request node kind to check that path is really a
+ * directory.
+ */
+ dirent_fields |= SVN_DIRENT_KIND;
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
+ path, SVN_INVALID_REVNUM, "1",
+ get_dirent_props(dirent_fields,
+ session, pool),
+ pool, pool));
+
+ /* Check if the path is really a directory. */
+ rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype");
+ if (rtype == NULL || strcmp(rtype, "collection") != 0)
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
+
+ /* We're going to create two hashes to help the walker along.
+ * We're going to return the 2nd one back to the caller as it
+ * will have the basenames it expects.
+ */
+ dirent_walk.full_paths = apr_hash_make(pool);
+ dirent_walk.base_paths = apr_hash_make(pool);
+ dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool);
+ dirent_walk.supports_deadprop_count = svn_tristate_unknown;
+ dirent_walk.result_pool = pool;
+
+ SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
+ path_dirent_walker, &dirent_walk,
+ pool));
+
+ if (dirent_walk.supports_deadprop_count == svn_tristate_false
+ && session->supports_deadprop_count == svn_tristate_unknown
+ && dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ /* We have to requery as the server didn't give us the right
+ information */
+ session->supports_deadprop_count = svn_tristate_false;
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session,
+ session->conns[0],
+ path, SVN_INVALID_REVNUM, "1",
+ get_dirent_props(dirent_fields,
+ session, pool),
+ pool, pool));
+
+ apr_hash_clear(dirent_walk.full_paths);
+ apr_hash_clear(dirent_walk.base_paths);
+
+ SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
+ path_dirent_walker,
+ &dirent_walk, pool));
+ }
+
+ *dirents = dirent_walk.base_paths;
+
+ if (dirent_walk.supports_deadprop_count != svn_tristate_unknown)
+ session->supports_deadprop_count = dirent_walk.supports_deadprop_count;
+ }
+
+ /* If we're asked for the directory properties, fetch them too. */
+ if (ret_props)
+ {
+ apr_hash_t *props;
+
+ SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0],
+ path, SVN_INVALID_REVNUM,
+ all_props,
+ pool, pool));
+
+ /* Check if the path is really a directory. */
+ SVN_ERR(resource_is_directory(props));
+
+ /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
+ ### put them into POOL, so we're okay. */
+ SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ if (!session->repos_root_str)
+ {
+ const char *vcc_url;
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
+ }
+
+ *url = session->repos_root_str;
+ return SVN_NO_ERROR;
+}
+
+/* TODO: to fetch the uuid from the repository, we need:
+ 1. a path that exists in HEAD
+ 2. a path that's readable
+
+ get_uuid handles the case where a path doesn't exist in HEAD and also the
+ case where the root of the repository is not readable.
+ However, it does not handle the case where we're fetching path not existing
+ in HEAD of a repository with unreadable root directory.
+
+ Implements svn_ra__vtable_t.get_uuid().
+ */
+static svn_error_t *
+svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
+ const char **uuid,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ if (!session->uuid)
+ {
+ const char *vcc_url;
+
+ /* We should never get here if we have HTTP v2 support, because
+ any server with that support should be transmitting the
+ UUID in the initial OPTIONS response. */
+ SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ /* We're not interested in vcc_url and relative_url, but this call also
+ stores the repository's uuid in the session. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
+ if (!session->uuid)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
+ _("The UUID property was not found on the "
+ "resource or any of its parents"));
+ }
+ }
+
+ *uuid = session->uuid;
+
+ return SVN_NO_ERROR;
+}
+
+
+static const svn_ra__vtable_t serf_vtable = {
+ ra_serf_version,
+ ra_serf_get_description,
+ ra_serf_get_schemes,
+ svn_ra_serf__open,
+ svn_ra_serf__reparent,
+ svn_ra_serf__get_session_url,
+ svn_ra_serf__get_latest_revnum,
+ svn_ra_serf__get_dated_revision,
+ svn_ra_serf__change_rev_prop,
+ svn_ra_serf__rev_proplist,
+ svn_ra_serf__rev_prop,
+ svn_ra_serf__get_commit_editor,
+ svn_ra_serf__get_file,
+ svn_ra_serf__get_dir,
+ svn_ra_serf__get_mergeinfo,
+ svn_ra_serf__do_update,
+ svn_ra_serf__do_switch,
+ svn_ra_serf__do_status,
+ svn_ra_serf__do_diff,
+ svn_ra_serf__get_log,
+ svn_ra_serf__check_path,
+ svn_ra_serf__stat,
+ svn_ra_serf__get_uuid,
+ svn_ra_serf__get_repos_root,
+ svn_ra_serf__get_locations,
+ svn_ra_serf__get_location_segments,
+ svn_ra_serf__get_file_revs,
+ svn_ra_serf__lock,
+ svn_ra_serf__unlock,
+ svn_ra_serf__get_lock,
+ svn_ra_serf__get_locks,
+ svn_ra_serf__replay,
+ svn_ra_serf__has_capability,
+ svn_ra_serf__replay_range,
+ svn_ra_serf__get_deleted_rev,
+ svn_ra_serf__register_editor_shim_callbacks,
+ svn_ra_serf__get_inherited_props
+};
+
+svn_error_t *
+svn_ra_serf__init(const svn_version_t *loader_version,
+ const svn_ra__vtable_t **vtable,
+ apr_pool_t *pool)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+ int serf_major;
+ int serf_minor;
+ int serf_patch;
+
+ SVN_ERR(svn_ver_check_list(ra_serf_version(), checklist));
+
+ /* Simplified version check to make sure we can safely use the
+ VTABLE parameter. The RA loader does a more exhaustive check. */
+ if (loader_version->major != SVN_VER_MAJOR)
+ {
+ return svn_error_createf(
+ SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Unsupported RA loader version (%d) for ra_serf"),
+ loader_version->major);
+ }
+
+ /* Make sure that we have loaded a compatible library: the MAJOR must
+ match, and the minor must be at *least* what we compiled against.
+ The patch level is simply ignored. */
+ serf_lib_version(&serf_major, &serf_minor, &serf_patch);
+ if (serf_major != SERF_MAJOR_VERSION
+ || serf_minor < SERF_MINOR_VERSION)
+ {
+ return svn_error_createf(
+ /* ### should return a unique error */
+ SVN_ERR_VERSION_MISMATCH, NULL,
+ _("ra_serf was compiled for serf %d.%d.%d but loaded "
+ "an incompatible %d.%d.%d library"),
+ SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION,
+ serf_major, serf_minor, serf_patch);
+ }
+
+ *vtable = &serf_vtable;
+
+ return SVN_NO_ERROR;
+}
+
+/* Compatibility wrapper for pre-1.2 subversions. Needed? */
+#define NAME "ra_serf"
+#define DESCRIPTION RA_SERF_DESCRIPTION
+#define VTBL serf_vtable
+#define INITFUNC svn_ra_serf__init
+#define COMPAT_INITFUNC svn_ra_serf_init
+#include "../libsvn_ra/wrapper_template.h"
OpenPOWER on IntegriCloud