diff options
Diffstat (limited to 'subversion/libsvn_ra_serf/serf.c')
-rw-r--r-- | subversion/libsvn_ra_serf/serf.c | 1246 |
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 000000000000..601615738043 --- /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" |