/* * serf.c : entry point for ra_serf * * ==================================================================== * Copyright (c) 2006 CollabNet. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://subversion.tigris.org/license-1.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://subversion.tigris.org/. * ==================================================================== */ #define APR_WANT_STRFUNC #include #include #include #include #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_version.h" #include "svn_path.h" #include "svn_time.h" #include "svn_private_config.h" #include "ra_serf.h" static const svn_version_t * ra_serf_version(void) { SVN_VERSION_BODY; } #define RA_SERF_DESCRIPTION \ N_("Access repository via WebDAV protocol through serf.") static const char * ra_serf_get_description(void) { return _(RA_SERF_DESCRIPTION); } 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; } 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; const char *server_group; config = apr_hash_get(config_hash, SVN_CONFIG_CATEGORY_SERVERS, APR_HASH_KEY_STRING); SVN_ERR(svn_config_get_bool(config, &session->using_compression, SVN_CONFIG_SECTION_GLOBAL, SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE)); server_group = svn_config_find_group(config, session->repos_url.hostname, SVN_CONFIG_SECTION_GROUPS, pool); if (server_group) { SVN_ERR(svn_config_get_bool(config, &session->using_compression, server_group, SVN_CONFIG_OPTION_HTTP_COMPRESSION, session->using_compression)); } return SVN_NO_ERROR; } static svn_error_t * svn_ra_serf__open(svn_ra_session_t *session, const char *repos_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; serf_sess = apr_pcalloc(pool, sizeof(*serf_sess)); apr_pool_create(&serf_sess->pool, pool); serf_sess->bkt_alloc = serf_bucket_allocator_create(serf_sess->pool, NULL, NULL); serf_sess->cached_props = apr_hash_make(pool); serf_sess->wc_callbacks = callbacks; serf_sess->wc_callback_baton = callback_baton; /* todo: reuse serf context across sessions */ serf_sess->context = serf_context_create(pool); apr_uri_parse(serf_sess->pool, repos_URL, &url); serf_sess->repos_url = url; serf_sess->repos_url_str = apr_pstrdup(serf_sess->pool, repos_URL); if (!url.port) { url.port = apr_uri_port_of_scheme(url.scheme); } serf_sess->using_ssl = (strcasecmp(url.scheme, "https") == 0); SVN_ERR(load_config(serf_sess, config, pool)); /* register cleanups */ apr_pool_cleanup_register(serf_sess->pool, serf_sess, svn_ra_serf__cleanup_serf_session, apr_pool_cleanup_null); serf_sess->conns = apr_palloc(pool, sizeof(*serf_sess->conns) * 4); serf_sess->conns[0] = apr_pcalloc(pool, sizeof(*serf_sess->conns[0])); serf_sess->conns[0]->bkt_alloc = serf_bucket_allocator_create(serf_sess->pool, NULL, NULL); /* fetch the DNS record for this host */ status = apr_sockaddr_info_get(&serf_sess->conns[0]->address, url.hostname, APR_UNSPEC, url.port, 0, pool); if (status) { return svn_error_createf(status, NULL, _("Could not lookup hostname: %s://%s"), url.scheme, url.hostname); } serf_sess->conns[0]->using_ssl = serf_sess->using_ssl; serf_sess->conns[0]->using_compression = serf_sess->using_compression; serf_sess->conns[0]->hostinfo = url.hostinfo; /* go ahead and tell serf about the connection. */ serf_sess->conns[0]->conn = serf_connection_create(serf_sess->context, serf_sess->conns[0]->address, svn_ra_serf__conn_setup, serf_sess->conns[0], svn_ra_serf__conn_closed, serf_sess->conns[0], serf_sess->pool); serf_sess->num_conns = 1; session->priv = serf_sess; return SVN_NO_ERROR; } 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; /* If it's the URL we already have, wave our hands and do nothing. */ if (strcmp(session->repos_url_str, url) == 0) { return SVN_NO_ERROR; } /* Do we need to check that it's the same host and port? */ apr_uri_parse(session->pool, url, &new_url); session->repos_url.path = new_url.path; session->repos_url_str = apr_pstrdup(pool, url); return SVN_NO_ERROR; } 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) { apr_hash_t *props; svn_ra_serf__session_t *session = ra_session->priv; const char *vcc_url, *baseline_url, *version_name; props = apr_hash_make(pool); SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL, session, session->conns[0], session->repos_url.path, pool)); if (!vcc_url) { abort(); } /* Using the version-controlled-configuration, fetch the checked-in prop. */ SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0], vcc_url, SVN_INVALID_REVNUM, "0", checked_in_props, pool)); baseline_url = svn_ra_serf__get_prop(props, vcc_url, "DAV:", "checked-in"); if (!baseline_url) { abort(); } /* Using the checked-in property, fetch: * baseline-connection *and* version-name */ SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0], baseline_url, SVN_INVALID_REVNUM, "0", baseline_props, pool)); version_name = svn_ra_serf__get_prop(props, baseline_url, "DAV:", "version-name"); if (!version_name) { abort(); } *latest_revnum = SVN_STR_TO_REV(version_name); return SVN_NO_ERROR; } 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 *vcc_url; props = apr_hash_make(pool); *ret_props = apr_hash_make(pool); SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL, session, session->conns[0], session->repos_url.path, pool)); SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0], vcc_url, rev, "0", all_props, pool)); svn_ra_serf__walk_all_props(props, vcc_url, rev, svn_ra_serf__set_bare_props, *ret_props, pool); return SVN_NO_ERROR; } 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 = apr_hash_get(props, name, APR_HASH_KEY_STRING); return SVN_NO_ERROR; } static svn_error_t * fetch_path_props(svn_ra_serf__propfind_context_t **ret_prop_ctx, apr_hash_t **ret_props, const char **ret_path, svn_revnum_t *ret_revision, svn_ra_serf__session_t *session, const char *rel_path, svn_revnum_t revision, const svn_ra_serf__dav_props_t *desired_props, apr_pool_t *pool) { svn_ra_serf__propfind_context_t *prop_ctx; apr_hash_t *props; const char *path; path = session->repos_url.path; /* If we have a relative path, append it. */ if (rel_path) { path = svn_path_url_add_component(path, rel_path, pool); } props = apr_hash_make(pool); prop_ctx = NULL; /* If we were given a specific revision, we have to fetch the VCC and * do a PROPFIND off of that. */ if (!SVN_IS_VALID_REVNUM(revision)) { svn_ra_serf__deliver_props(&prop_ctx, props, session, session->conns[0], path, revision, "0", desired_props, TRUE, NULL, session->pool); } else { const char *vcc_url, *relative_url, *basecoll_url; SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &relative_url, session, session->conns[0], path, pool)); SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0], vcc_url, revision, "0", baseline_props, pool)); basecoll_url = svn_ra_serf__get_ver_prop(props, vcc_url, revision, "DAV:", "baseline-collection"); if (!basecoll_url) { abort(); } /* We will try again with our new path; however, we're now * technically an unversioned resource because we are accessing * the revision's baseline-collection. */ prop_ctx = NULL; path = svn_path_url_add_component(basecoll_url, relative_url, pool); revision = SVN_INVALID_REVNUM; svn_ra_serf__deliver_props(&prop_ctx, props, session, session->conns[0], path, revision, "0", desired_props, TRUE, NULL, session->pool); } SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, session, pool)); *ret_path = path; *ret_prop_ctx = prop_ctx; *ret_props = props; *ret_revision = revision; return SVN_NO_ERROR; } 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_ra_serf__propfind_context_t *prop_ctx; const char *path, *res_type; svn_revnum_t fetched_rev; SVN_ERR(fetch_path_props(&prop_ctx, &props, &path, &fetched_rev, session, rel_path, revision, check_path_props, pool)); if (svn_ra_serf__propfind_status_code(prop_ctx) == 404) { *kind = svn_node_none; } else { res_type = svn_ra_serf__get_ver_prop(props, path, fetched_rev, "DAV:", "resourcetype"); if (!res_type) { /* How did this happen? */ abort(); } else if (strcmp(res_type, "collection") == 0) { *kind = svn_node_dir; } else { *kind = svn_node_file; } } return SVN_NO_ERROR; } static svn_error_t * dirent_walker(void *baton, 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) { svn_dirent_t *entry = baton; if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) { entry->has_props = TRUE; } else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) { entry->has_props = TRUE; } else if (strcmp(ns, "DAV:") == 0) { if (strcmp(name, "version-name") == 0) { entry->created_rev = SVN_STR_TO_REV(val->data); } else if (strcmp(name, "creator-displayname") == 0) { entry->last_author = val->data; } else if (strcmp(name, "creationdate") == 0) { SVN_ERR(svn_time_from_cstring(&entry->time, val->data, pool)); } else if (strcmp(name, "getcontentlength") == 0) { entry->size = apr_atoi64(val->data); } else if (strcmp(name, "resourcetype") == 0) { if (strcmp(val->data, "collection") == 0) { entry->kind = svn_node_dir; } else { 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; }; 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; 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 = apr_pcalloc(pool, sizeof(*entry)); apr_hash_set(dirents->full_paths, path, path_len, entry); base_name = svn_path_uri_decode(svn_path_basename(path, pool), pool); apr_hash_set(dirents->base_paths, base_name, APR_HASH_KEY_STRING, entry); } return dirent_walker(entry, ns, ns_len, name, name_len, val, pool); } 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_ra_serf__propfind_context_t *prop_ctx; const char *path; svn_revnum_t fetched_rev; svn_dirent_t *entry; SVN_ERR(fetch_path_props(&prop_ctx, &props, &path, &fetched_rev, session, rel_path, revision, all_props, pool)); entry = apr_pcalloc(pool, sizeof(*entry)); svn_ra_serf__walk_all_props(props, path, fetched_rev, dirent_walker, entry, pool); *dirent = entry; return SVN_NO_ERROR; } 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; apr_hash_t *props; const char *path; path = session->repos_url.path; /* If we have a relative path, append it. */ if (rel_path) { path = svn_path_url_add_component(path, rel_path, pool); } props = apr_hash_make(pool); if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) { const char *vcc_url, *relative_url, *basecoll_url; SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &relative_url, session, session->conns[0], path, pool)); SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0], vcc_url, revision, "0", baseline_props, pool)); basecoll_url = svn_ra_serf__get_ver_prop(props, vcc_url, revision, "DAV:", "baseline-collection"); if (!basecoll_url) { abort(); } if (fetched_rev) { *fetched_rev = revision; } path = svn_path_url_add_component(basecoll_url, relative_url, pool); revision = SVN_INVALID_REVNUM; } /* If we're asked for children, fetch them now. */ if (dirents) { svn_ra_serf__propfind_context_t *prop_ctx; struct path_dirent_visitor_t dirent_walk; prop_ctx = NULL; svn_ra_serf__deliver_props(&prop_ctx, props, session, session->conns[0], path, revision, "1", all_props, TRUE, NULL, session->pool); SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, session, pool)); /* 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_path_canonicalize(path, pool); svn_ra_serf__walk_all_paths(props, revision, path_dirent_walker, &dirent_walk, pool); *dirents = dirent_walk.base_paths; } /* If we're asked for the directory properties, fetch them too. */ if (ret_props) { props = apr_hash_make(pool); *ret_props = apr_hash_make(pool); SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0], path, revision, "0", all_props, pool)); svn_ra_serf__walk_all_props(props, path, revision, svn_ra_serf__set_flat_props, *ret_props, pool); } return SVN_NO_ERROR; } static 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_root(&vcc_url, NULL, session, session->conns[0], session->repos_url.path, pool)); } *url = session->repos_root_str; return SVN_NO_ERROR; } 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; apr_hash_t *props; props = apr_hash_make(pool); SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0], session->repos_url.path, SVN_INVALID_REVNUM, "0", uuid_props, pool)); *uuid = svn_ra_serf__get_prop(props, session->repos_url.path, SVN_DAV_PROP_NS_DAV, "repository-uuid"); if (!*uuid) { abort(); } 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_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__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_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_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 } }; 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); } *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"