| /* |
| * 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_props.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.") |
| |
| #define RA_SERF_DESCRIPTION_VER \ |
| N_("Module for accessing a repository via WebDAV protocol using serf.\n" \ |
| " - using serf %d.%d.%d (compiled with %d.%d.%d)") |
| |
| /* Implements svn_ra__vtable_t.get_description(). */ |
| static const char * |
| ra_serf_get_description(apr_pool_t *pool) |
| { |
| int major, minor, patch; |
| |
| serf_lib_version(&major, &minor, &patch); |
| return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), |
| major, minor, patch, |
| SERF_MAJOR_VERSION, |
| SERF_MINOR_VERSION, |
| SERF_PATCH_VERSION |
| ); |
| } |
| |
| /* 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 *result_pool, |
| apr_pool_t *scratch_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; |
| svn_tristate_t chunked_requests; |
| #if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) |
| apr_int64_t log_components; |
| apr_int64_t log_level; |
| #endif |
| |
| 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_tristate(config, &session->using_compression, |
| SVN_CONFIG_SECTION_GLOBAL, |
| SVN_CONFIG_OPTION_HTTP_COMPRESSION, |
| "auto", svn_tristate_unknown)); |
| svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL, |
| SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL); |
| |
| if (session->auth_baton) |
| { |
| if (config_client) |
| { |
| svn_auth_set_parameter(session->auth_baton, |
| SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, |
| config_client); |
| } |
| if (config) |
| { |
| svn_auth_set_parameter(session->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, scratch_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)); |
| |
| /* Should we use chunked transfer encoding. */ |
| SVN_ERR(svn_config_get_tristate(config, &chunked_requests, |
| SVN_CONFIG_SECTION_GLOBAL, |
| SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS, |
| "auto", svn_tristate_unknown)); |
| |
| #if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) |
| SVN_ERR(svn_config_get_int64(config, &log_components, |
| SVN_CONFIG_SECTION_GLOBAL, |
| SVN_CONFIG_OPTION_SERF_LOG_COMPONENTS, |
| SERF_LOGCOMP_NONE)); |
| SVN_ERR(svn_config_get_int64(config, &log_level, |
| SVN_CONFIG_SECTION_GLOBAL, |
| SVN_CONFIG_OPTION_SERF_LOG_LEVEL, |
| SERF_LOG_INFO)); |
| #endif |
| |
| server_group = svn_auth_get_parameter(session->auth_baton, |
| SVN_AUTH_PARAM_SERVER_GROUP); |
| |
| if (server_group) |
| { |
| SVN_ERR(svn_config_get_tristate(config, &session->using_compression, |
| server_group, |
| SVN_CONFIG_OPTION_HTTP_COMPRESSION, |
| "auto", session->using_compression)); |
| svn_config_get(config, &timeout_str, server_group, |
| SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str); |
| |
| /* 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)); |
| |
| /* Should we use chunked transfer encoding. */ |
| SVN_ERR(svn_config_get_tristate(config, &chunked_requests, |
| server_group, |
| SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS, |
| "auto", chunked_requests)); |
| |
| #if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) |
| SVN_ERR(svn_config_get_int64(config, &log_components, |
| server_group, |
| SVN_CONFIG_OPTION_SERF_LOG_COMPONENTS, |
| log_components)); |
| SVN_ERR(svn_config_get_int64(config, &log_level, |
| server_group, |
| SVN_CONFIG_OPTION_SERF_LOG_LEVEL, |
| log_level)); |
| #endif |
| } |
| |
| #if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) |
| if (log_components != SERF_LOGCOMP_NONE) |
| { |
| serf_log_output_t *output; |
| apr_status_t status; |
| |
| status = serf_logging_create_stream_output(&output, |
| session->context, |
| (apr_uint32_t)log_level, |
| (apr_uint32_t)log_components, |
| SERF_LOG_DEFAULT_LAYOUT, |
| stderr, |
| result_pool); |
| |
| if (!status) |
| serf_logging_add_output(session->context, output); |
| } |
| #endif |
| |
| /* 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) |
| { |
| apr_int64_t timeout; |
| svn_error_t *err; |
| |
| err = svn_cstring_strtoi64(&timeout, timeout_str, 0, APR_INT64_MAX, 10); |
| if (err) |
| return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, err, |
| _("invalid config: bad value for '%s' option"), |
| SVN_CONFIG_OPTION_HTTP_TIMEOUT); |
| session->timeout = apr_time_from_sec(timeout); |
| } |
| |
| /* 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 detect_chunking and using_chunked_requests based on |
| * the chunked_requests tristate */ |
| if (chunked_requests == svn_tristate_unknown) |
| { |
| session->detect_chunking = TRUE; |
| session->using_chunked_requests = TRUE; |
| } |
| else if (chunked_requests == svn_tristate_true) |
| { |
| session->detect_chunking = FALSE; |
| session->using_chunked_requests = TRUE; |
| } |
| else /* chunked_requests == svn_tristate_false */ |
| { |
| session->detect_chunking = FALSE; |
| session->using_chunked_requests = FALSE; |
| } |
| |
| /* Setup authentication. */ |
| SVN_ERR(load_http_auth_types(result_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 bytes_read, |
| apr_off_t bytes_written) |
| { |
| const svn_ra_serf__session_t *serf_sess = progress_baton; |
| if (serf_sess->progress_func) |
| { |
| serf_sess->progress_func(bytes_read + bytes_written, -1, |
| serf_sess->progress_baton, |
| serf_sess->pool); |
| } |
| } |
| |
| /** Our User-Agent string. */ |
| static const char * |
| get_user_agent_string(apr_pool_t *pool) |
| { |
| int major, minor, patch; |
| serf_lib_version(&major, &minor, &patch); |
| |
| return apr_psprintf(pool, "SVN/%s (%s) serf/%d.%d.%d", |
| SVN_VER_NUMBER, SVN_BUILD_TARGET, |
| major, minor, patch); |
| } |
| |
| /* 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 **redirect_url, |
| const char *session_URL, |
| const svn_ra_callbacks2_t *callbacks, |
| void *callback_baton, |
| svn_auth_baton_t *auth_baton, |
| apr_hash_t *config, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_status_t status; |
| svn_ra_serf__session_t *serf_sess; |
| apr_uri_t url; |
| const char *client_string = NULL; |
| svn_error_t *err; |
| |
| if (corrected_url) |
| *corrected_url = NULL; |
| if (redirect_url) |
| *redirect_url = NULL; |
| |
| serf_sess = apr_pcalloc(result_pool, sizeof(*serf_sess)); |
| serf_sess->pool = result_pool; |
| if (config) |
| SVN_ERR(svn_config_copy_config(&serf_sess->config, config, result_pool)); |
| else |
| serf_sess->config = NULL; |
| serf_sess->wc_callbacks = callbacks; |
| serf_sess->wc_callback_baton = callback_baton; |
| serf_sess->auth_baton = auth_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)); |
| |
| |
| SVN_ERR(svn_ra_serf__uri_parse(&url, session_URL, 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; |
| serf_sess->http20 = FALSE; |
| |
| /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable |
| this, if we find an intervening proxy does not support chunked requests. */ |
| serf_sess->using_chunked_requests = TRUE; |
| |
| SVN_ERR(load_config(serf_sess, config, serf_sess->pool, scratch_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, |
| scratch_pool)); |
| |
| if (client_string) |
| serf_sess->useragent = apr_pstrcat(result_pool, |
| get_user_agent_string(scratch_pool), |
| " ", |
| client_string, SVN_VA_NULL); |
| else |
| serf_sess->useragent = get_user_agent_string(result_pool); |
| |
| /* 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; |
| |
| /* The following code explicitly works around a bug in serf <= r2319 / 1.3.8 |
| where serf doesn't report the request as failed/cancelled when the |
| authorization request handler fails to handle the request. |
| |
| As long as we allocate the request in a subpool of the serf connection |
| pool, we know that the handler is always cleaned before the connection. |
| |
| Luckily our caller now passes us two pools which handle this case. |
| */ |
| #if defined(SVN_DEBUG) && !SERF_VERSION_AT_LEAST(1,4,0) |
| /* Currently ensured by svn_ra_open5(). |
| If failing causes segfault in basic_tests.py 48, "basic auth test" */ |
| SVN_ERR_ASSERT((serf_sess->pool != scratch_pool) |
| && apr_pool_is_ancestor(serf_sess->pool, scratch_pool)); |
| #endif |
| |
| /* The actual latency will be determined as a part of the initial |
| OPTIONS request. */ |
| serf_sess->conn_latency = -1; |
| |
| err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, |
| redirect_url, |
| result_pool, scratch_pool); |
| |
| /* serf should produce a usable error code instead of APR_EGENERAL */ |
| if (err && err->apr_err == APR_EGENERAL) |
| err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err, |
| _("Connection to '%s' failed"), session_URL); |
| SVN_ERR(err); |
| |
| /* We have set up a useful connection (that doesn't indication a redirect). |
| If we've been told there is possibly a worrisome proxy in our path to the |
| server AND we switched to HTTP/1.1 (chunked requests), then probe for |
| problems in any proxy. */ |
| if ((corrected_url == NULL || *corrected_url == NULL) |
| && serf_sess->detect_chunking && !serf_sess->http10) |
| SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Implements svn_ra__vtable_t.dup_session */ |
| static svn_error_t * |
| ra_serf_dup_session(svn_ra_session_t *new_session, |
| svn_ra_session_t *old_session, |
| const char *new_session_url, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_ra_serf__session_t *old_sess = old_session->priv; |
| svn_ra_serf__session_t *new_sess; |
| apr_status_t status; |
| |
| new_sess = apr_pmemdup(result_pool, old_sess, sizeof(*new_sess)); |
| |
| new_sess->pool = result_pool; |
| |
| if (new_sess->config) |
| SVN_ERR(svn_config_copy_config(&new_sess->config, new_sess->config, |
| result_pool)); |
| |
| /* max_connections */ |
| /* using_ssl */ |
| /* using_compression */ |
| /* http10 */ |
| /* http20 */ |
| /* using_chunked_requests */ |
| /* detect_chunking */ |
| |
| if (new_sess->useragent) |
| new_sess->useragent = apr_pstrdup(result_pool, new_sess->useragent); |
| |
| if (new_sess->vcc_url) |
| new_sess->vcc_url = apr_pstrdup(result_pool, new_sess->vcc_url); |
| |
| new_sess->auth_state = NULL; |
| new_sess->auth_attempts = 0; |
| |
| /* Callback functions to get info from WC */ |
| /* wc_callbacks */ |
| /* wc_callback_baton */ |
| |
| /* progress_func */ |
| /* progress_baton */ |
| |
| /* cancel_func */ |
| /* cancel_baton */ |
| |
| /* shim_callbacks */ |
| |
| new_sess->pending_error = NULL; |
| |
| /* authn_types */ |
| |
| /* Keys and values are static */ |
| if (new_sess->capabilities) |
| new_sess->capabilities = apr_hash_copy(result_pool, new_sess->capabilities); |
| |
| if (new_sess->activity_collection_url) |
| { |
| new_sess->activity_collection_url |
| = apr_pstrdup(result_pool, new_sess->activity_collection_url); |
| } |
| |
| /* using_proxy */ |
| |
| if (new_sess->proxy_username) |
| { |
| new_sess->proxy_username |
| = apr_pstrdup(result_pool, new_sess->proxy_username); |
| } |
| |
| if (new_sess->proxy_password) |
| { |
| new_sess->proxy_password |
| = apr_pstrdup(result_pool, new_sess->proxy_password); |
| } |
| |
| new_sess->proxy_auth_attempts = 0; |
| |
| /* trust_default_ca */ |
| |
| if (new_sess->ssl_authorities) |
| { |
| new_sess->ssl_authorities = apr_pstrdup(result_pool, |
| new_sess->ssl_authorities); |
| } |
| |
| if (new_sess->uuid) |
| new_sess->uuid = apr_pstrdup(result_pool, new_sess->uuid); |
| |
| /* timeout */ |
| /* supports_deadprop_count */ |
| |
| if (new_sess->me_resource) |
| new_sess->me_resource = apr_pstrdup(result_pool, new_sess->me_resource); |
| if (new_sess->rev_stub) |
| new_sess->rev_stub = apr_pstrdup(result_pool, new_sess->rev_stub); |
| if (new_sess->txn_stub) |
| new_sess->txn_stub = apr_pstrdup(result_pool, new_sess->txn_stub); |
| if (new_sess->txn_root_stub) |
| new_sess->txn_root_stub = apr_pstrdup(result_pool, |
| new_sess->txn_root_stub); |
| if (new_sess->vtxn_stub) |
| new_sess->vtxn_stub = apr_pstrdup(result_pool, new_sess->vtxn_stub); |
| if (new_sess->vtxn_root_stub) |
| new_sess->vtxn_root_stub = apr_pstrdup(result_pool, |
| new_sess->vtxn_root_stub); |
| |
| /* Keys and values are static */ |
| if (new_sess->supported_posts) |
| new_sess->supported_posts = apr_hash_copy(result_pool, |
| new_sess->supported_posts); |
| |
| /* ### Can we copy this? */ |
| SVN_ERR(svn_ra_serf__blncache_create(&new_sess->blncache, |
| new_sess->pool)); |
| |
| if (new_sess->server_allows_bulk) |
| new_sess->server_allows_bulk = apr_pstrdup(result_pool, |
| new_sess->server_allows_bulk); |
| |
| if (new_sess->repos_root_str) |
| { |
| new_sess->repos_root_str = apr_pstrdup(result_pool, |
| new_sess->repos_root_str); |
| SVN_ERR(svn_ra_serf__uri_parse(&new_sess->repos_root, |
| new_sess->repos_root_str, |
| result_pool)); |
| } |
| |
| new_sess->session_url_str = apr_pstrdup(result_pool, new_session_url); |
| |
| SVN_ERR(svn_ra_serf__uri_parse(&new_sess->session_url, |
| new_sess->session_url_str, |
| result_pool)); |
| |
| /* svn_boolean_t supports_inline_props */ |
| /* supports_rev_rsrc_replay */ |
| /* supports_svndiff1 */ |
| /* supports_svndiff2 */ |
| /* supports_put_result_checksum */ |
| /* conn_latency */ |
| |
| new_sess->context = serf_context_create(result_pool); |
| |
| SVN_ERR(load_config(new_sess, old_sess->config, |
| result_pool, scratch_pool)); |
| |
| new_sess->conns[0] = apr_pcalloc(result_pool, |
| sizeof(*new_sess->conns[0])); |
| new_sess->conns[0]->bkt_alloc = |
| serf_bucket_allocator_create(result_pool, NULL, NULL); |
| new_sess->conns[0]->session = new_sess; |
| new_sess->conns[0]->last_status_code = -1; |
| |
| /* go ahead and tell serf about the connection. */ |
| status = |
| serf_connection_create2(&new_sess->conns[0]->conn, |
| new_sess->context, |
| new_sess->session_url, |
| svn_ra_serf__conn_setup, new_sess->conns[0], |
| svn_ra_serf__conn_closed, new_sess->conns[0], |
| result_pool); |
| if (status) |
| return svn_ra_serf__wrap_err(status, NULL); |
| |
| /* Set the progress callback. */ |
| serf_context_set_progress_cb(new_sess->context, svn_ra_serf__progress, |
| new_sess); |
| |
| new_sess->num_conns = 1; |
| new_sess->cur_conn = 0; |
| |
| new_session->priv = new_sess; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Implements svn_ra__vtable_t.reparent(). */ |
| 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; |
| |
| /* 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, 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); |
| } |
| |
| SVN_ERR(svn_ra_serf__uri_parse(&new_url, url, pool)); |
| |
| /* ### Maybe we should use a string buffer for these strings so we |
| ### don't allocate memory in the session on every reparent? */ |
| 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)); |
| } |
| |
| /* Implementation of svn_ra_serf__rev_proplist(). */ |
| static svn_error_t * |
| serf__rev_proplist(svn_ra_session_t *ra_session, |
| svn_revnum_t rev, |
| const svn_ra_serf__dav_props_t *fetch_props, |
| apr_hash_t **ret_props, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_ra_serf__session_t *session = ra_session->priv; |
| apr_hash_t *props; |
| const char *propfind_path; |
| svn_ra_serf__handler_t *handler; |
| |
| if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) |
| { |
| propfind_path = apr_psprintf(scratch_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, |
| scratch_pool)); |
| } |
| |
| props = apr_hash_make(result_pool); |
| SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, |
| propfind_path, rev, "0", |
| fetch_props, |
| svn_ra_serf__deliver_svn_props, |
| props, |
| scratch_pool)); |
| |
| SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); |
| |
| svn_ra_serf__keep_only_regular_props(props, scratch_pool); |
| |
| *ret_props = props; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* 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 *result_pool) |
| { |
| apr_pool_t *scratch_pool = svn_pool_create(result_pool); |
| svn_error_t *err; |
| |
| err = serf__rev_proplist(ra_session, rev, all_props, ret_props, |
| result_pool, scratch_pool); |
| |
| svn_pool_destroy(scratch_pool); |
| return svn_error_trace(err); |
| } |
| |
| |
| /* Implements svn_ra__vtable_t.rev_prop(). */ |
| svn_error_t * |
| svn_ra_serf__rev_prop(svn_ra_session_t *session, |
| svn_revnum_t rev, |
| const char *name, |
| svn_string_t **value, |
| apr_pool_t *result_pool) |
| { |
| apr_pool_t *scratch_pool = svn_pool_create(result_pool); |
| apr_hash_t *props; |
| svn_ra_serf__dav_props_t specific_props[2]; |
| const svn_ra_serf__dav_props_t *fetch_props = all_props; |
| |
| /* The DAV propfind doesn't allow property fetches for any property name |
| as there is no defined way to quote values. If we are just fetching a |
| "svn:property" we can safely do this. In other cases we just fetch all |
| revision properties and filter the right one out */ |
| if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX)-1) == 0 |
| && !strchr(name + sizeof(SVN_PROP_PREFIX)-1, ':')) |
| { |
| specific_props[0].xmlns = SVN_DAV_PROP_NS_SVN; |
| specific_props[0].name = name + sizeof(SVN_PROP_PREFIX)-1; |
| specific_props[1].xmlns = NULL; |
| specific_props[1].name = NULL; |
| |
| fetch_props = specific_props; |
| } |
| |
| SVN_ERR(serf__rev_proplist(session, rev, fetch_props, &props, |
| result_pool, scratch_pool)); |
| |
| *value = svn_hash_gets(props, name); |
| |
| svn_pool_destroy(scratch_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, 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, 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, |
| ra_serf_dup_session, |
| 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__get_inherited_props, |
| NULL /* set_svn_ra_open */, |
| svn_ra_serf__list, |
| svn_ra_serf__register_editor_shim_callbacks, |
| NULL /* commit_ev2 */, |
| NULL /* replay_range_ev2 */ |
| }; |
| |
| 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_list2(ra_serf_version(), checklist, svn_ver_equal)); |
| |
| /* 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" |