/*
* 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 <apr_want.h>
#include <apr_uri.h>
#include <expat.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_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"
syntax highlighted by Code2HTML, v. 0.9.1