2
* serf.c : entry point for ra_serf
4
* ====================================================================
5
* Copyright (c) 2006 CollabNet. All rights reserved.
7
* This software is licensed as described in the file COPYING, which
8
* you should have received as part of this distribution. The terms
9
* are also available at http://subversion.tigris.org/license-1.html.
10
* If newer versions of this license are posted there, you may use a
11
* newer version instead, at your option.
13
* This software consists of voluntary contributions made by many
14
* individuals. For exact contribution history, see the revision
15
* history and logs, available at http://subversion.tigris.org/.
16
* ====================================================================
21
#define APR_WANT_STRFUNC
30
#include "svn_pools.h"
34
#include "../libsvn_ra/ra_loader.h"
35
#include "svn_config.h"
36
#include "svn_delta.h"
37
#include "svn_version.h"
40
#include "svn_private_config.h"
45
static const svn_version_t *
51
#define RA_SERF_DESCRIPTION \
52
N_("Access repository via WebDAV protocol through serf.")
55
ra_serf_get_description(void)
57
return _(RA_SERF_DESCRIPTION);
60
static const char * const *
61
ra_serf_get_schemes(apr_pool_t *pool)
63
static const char *serf_ssl[] = { "http", "https", NULL };
65
/* ### Temporary: to shut up a warning. */
66
static const char *serf_no_ssl[] = { "http", NULL };
69
/* TODO: Runtime detection. */
74
load_config(svn_ra_serf__session_t *session,
75
apr_hash_t *config_hash,
79
const char *server_group;
81
config = apr_hash_get(config_hash, SVN_CONFIG_CATEGORY_SERVERS,
84
SVN_ERR(svn_config_get_bool(config, &session->using_compression,
85
SVN_CONFIG_SECTION_GLOBAL,
86
SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
88
server_group = svn_config_find_group(config,
89
session->repos_url.hostname,
90
SVN_CONFIG_SECTION_GROUPS, pool);
94
SVN_ERR(svn_config_get_bool(config, &session->using_compression,
96
SVN_CONFIG_OPTION_HTTP_COMPRESSION,
97
session->using_compression));
104
svn_ra_serf__open(svn_ra_session_t *session,
105
const char *repos_URL,
106
const svn_ra_callbacks2_t *callbacks,
107
void *callback_baton,
112
svn_ra_serf__session_t *serf_sess;
115
serf_sess = apr_pcalloc(pool, sizeof(*serf_sess));
116
apr_pool_create(&serf_sess->pool, pool);
117
serf_sess->bkt_alloc = serf_bucket_allocator_create(serf_sess->pool, NULL,
119
serf_sess->cached_props = apr_hash_make(pool);
120
serf_sess->wc_callbacks = callbacks;
121
serf_sess->wc_callback_baton = callback_baton;
123
/* todo: reuse serf context across sessions */
124
serf_sess->context = serf_context_create(pool);
126
apr_uri_parse(serf_sess->pool, repos_URL, &url);
127
serf_sess->repos_url = url;
128
serf_sess->repos_url_str = apr_pstrdup(serf_sess->pool, repos_URL);
132
url.port = apr_uri_port_of_scheme(url.scheme);
134
serf_sess->using_ssl = (strcasecmp(url.scheme, "https") == 0);
136
SVN_ERR(load_config(serf_sess, config, pool));
138
/* register cleanups */
139
apr_pool_cleanup_register(serf_sess->pool, serf_sess,
140
svn_ra_serf__cleanup_serf_session,
141
apr_pool_cleanup_null);
143
serf_sess->conns = apr_palloc(pool, sizeof(*serf_sess->conns) * 4);
145
serf_sess->conns[0] = apr_pcalloc(pool, sizeof(*serf_sess->conns[0]));
146
serf_sess->conns[0]->bkt_alloc =
147
serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
149
/* fetch the DNS record for this host */
150
status = apr_sockaddr_info_get(&serf_sess->conns[0]->address, url.hostname,
151
APR_UNSPEC, url.port, 0, pool);
154
return svn_error_createf(status, NULL,
155
_("Could not lookup hostname: %s://%s"),
156
url.scheme, url.hostname);
159
serf_sess->conns[0]->using_ssl = serf_sess->using_ssl;
160
serf_sess->conns[0]->using_compression = serf_sess->using_compression;
161
serf_sess->conns[0]->hostinfo = url.hostinfo;
163
/* go ahead and tell serf about the connection. */
164
serf_sess->conns[0]->conn =
165
serf_connection_create(serf_sess->context, serf_sess->conns[0]->address,
166
svn_ra_serf__conn_setup, serf_sess->conns[0],
167
svn_ra_serf__conn_closed, serf_sess->conns[0],
170
serf_sess->num_conns = 1;
172
session->priv = serf_sess;
178
svn_ra_serf__reparent(svn_ra_session_t *ra_session,
182
svn_ra_serf__session_t *session = ra_session->priv;
185
/* If it's the URL we already have, wave our hands and do nothing. */
186
if (strcmp(session->repos_url_str, url) == 0)
191
/* Do we need to check that it's the same host and port? */
192
apr_uri_parse(session->pool, url, &new_url);
194
session->repos_url.path = new_url.path;
195
session->repos_url_str = apr_pstrdup(pool, url);
201
svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
202
svn_revnum_t *latest_revnum,
206
svn_ra_serf__session_t *session = ra_session->priv;
207
const char *vcc_url, *baseline_url, *version_name;
209
props = apr_hash_make(pool);
211
SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL,
212
session, session->conns[0],
213
session->repos_url.path, pool));
220
/* Using the version-controlled-configuration, fetch the checked-in prop. */
221
SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
222
vcc_url, SVN_INVALID_REVNUM, "0",
223
checked_in_props, pool));
225
baseline_url = svn_ra_serf__get_prop(props, vcc_url,
226
"DAV:", "checked-in");
233
/* Using the checked-in property, fetch:
234
* baseline-connection *and* version-name
236
SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
237
baseline_url, SVN_INVALID_REVNUM,
238
"0", baseline_props, pool));
240
version_name = svn_ra_serf__get_prop(props, baseline_url,
241
"DAV:", "version-name");
248
*latest_revnum = SVN_STR_TO_REV(version_name);
254
svn_ra_serf__get_dated_revision(svn_ra_session_t *session,
255
svn_revnum_t *revision,
263
svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
265
apr_hash_t **ret_props,
268
svn_ra_serf__session_t *session = ra_session->priv;
272
props = apr_hash_make(pool);
273
*ret_props = apr_hash_make(pool);
275
SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL,
276
session, session->conns[0],
277
session->repos_url.path, pool));
279
SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
280
vcc_url, rev, "0", all_props, pool));
282
svn_ra_serf__walk_all_props(props, vcc_url, rev, svn_ra_serf__set_bare_props,
289
svn_ra_serf__rev_prop(svn_ra_session_t *session,
292
svn_string_t **value,
297
SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
299
*value = apr_hash_get(props, name, APR_HASH_KEY_STRING);
305
fetch_path_props(svn_ra_serf__propfind_context_t **ret_prop_ctx,
306
apr_hash_t **ret_props,
307
const char **ret_path,
308
svn_revnum_t *ret_revision,
309
svn_ra_serf__session_t *session,
310
const char *rel_path,
311
svn_revnum_t revision,
312
const svn_ra_serf__dav_props_t *desired_props,
315
svn_ra_serf__propfind_context_t *prop_ctx;
319
path = session->repos_url.path;
321
/* If we have a relative path, append it. */
324
path = svn_path_url_add_component(path, rel_path, pool);
327
props = apr_hash_make(pool);
331
/* If we were given a specific revision, we have to fetch the VCC and
332
* do a PROPFIND off of that.
334
if (!SVN_IS_VALID_REVNUM(revision))
336
svn_ra_serf__deliver_props(&prop_ctx, props, session, session->conns[0],
337
path, revision, "0", desired_props, TRUE,
338
NULL, session->pool);
342
const char *vcc_url, *relative_url, *basecoll_url;
344
SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &relative_url,
345
session, session->conns[0],
348
SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
350
"0", baseline_props, pool));
352
basecoll_url = svn_ra_serf__get_ver_prop(props, vcc_url, revision,
353
"DAV:", "baseline-collection");
360
/* We will try again with our new path; however, we're now
361
* technically an unversioned resource because we are accessing
362
* the revision's baseline-collection.
365
path = svn_path_url_add_component(basecoll_url, relative_url, pool);
366
revision = SVN_INVALID_REVNUM;
367
svn_ra_serf__deliver_props(&prop_ctx, props, session, session->conns[0],
370
NULL, session->pool);
373
SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, session, pool));
376
*ret_prop_ctx = prop_ctx;
378
*ret_revision = revision;
384
svn_ra_serf__check_path(svn_ra_session_t *ra_session,
385
const char *rel_path,
386
svn_revnum_t revision,
387
svn_node_kind_t *kind,
390
svn_ra_serf__session_t *session = ra_session->priv;
392
svn_ra_serf__propfind_context_t *prop_ctx;
393
const char *path, *res_type;
394
svn_revnum_t fetched_rev;
396
SVN_ERR(fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
398
revision, check_path_props, pool));
400
if (svn_ra_serf__propfind_status_code(prop_ctx) == 404)
402
*kind = svn_node_none;
406
res_type = svn_ra_serf__get_ver_prop(props, path, fetched_rev,
407
"DAV:", "resourcetype");
410
/* How did this happen? */
413
else if (strcmp(res_type, "collection") == 0)
415
*kind = svn_node_dir;
419
*kind = svn_node_file;
427
dirent_walker(void *baton,
428
const char *ns, apr_ssize_t ns_len,
429
const char *name, apr_ssize_t name_len,
430
const svn_string_t *val,
433
svn_dirent_t *entry = baton;
435
if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
437
entry->has_props = TRUE;
439
else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
441
entry->has_props = TRUE;
443
else if (strcmp(ns, "DAV:") == 0)
445
if (strcmp(name, "version-name") == 0)
447
entry->created_rev = SVN_STR_TO_REV(val->data);
449
else if (strcmp(name, "creator-displayname") == 0)
451
entry->last_author = val->data;
453
else if (strcmp(name, "creationdate") == 0)
455
SVN_ERR(svn_time_from_cstring(&entry->time, val->data, pool));
457
else if (strcmp(name, "getcontentlength") == 0)
459
entry->size = apr_atoi64(val->data);
461
else if (strcmp(name, "resourcetype") == 0)
463
if (strcmp(val->data, "collection") == 0)
465
entry->kind = svn_node_dir;
469
entry->kind = svn_node_file;
477
struct path_dirent_visitor_t {
478
apr_hash_t *full_paths;
479
apr_hash_t *base_paths;
480
const char *orig_path;
484
path_dirent_walker(void *baton,
485
const char *path, apr_ssize_t path_len,
486
const char *ns, apr_ssize_t ns_len,
487
const char *name, apr_ssize_t name_len,
488
const svn_string_t *val,
491
struct path_dirent_visitor_t *dirents = baton;
494
/* Skip our original path. */
495
if (strcmp(path, dirents->orig_path) == 0)
500
entry = apr_hash_get(dirents->full_paths, path, path_len);
504
const char *base_name;
506
entry = apr_pcalloc(pool, sizeof(*entry));
508
apr_hash_set(dirents->full_paths, path, path_len, entry);
510
base_name = svn_path_uri_decode(svn_path_basename(path, pool), pool);
512
apr_hash_set(dirents->base_paths, base_name, APR_HASH_KEY_STRING, entry);
515
return dirent_walker(entry, ns, ns_len, name, name_len, val, pool);
519
svn_ra_serf__stat(svn_ra_session_t *ra_session,
520
const char *rel_path,
521
svn_revnum_t revision,
522
svn_dirent_t **dirent,
525
svn_ra_serf__session_t *session = ra_session->priv;
527
svn_ra_serf__propfind_context_t *prop_ctx;
529
svn_revnum_t fetched_rev;
532
SVN_ERR(fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
533
session, rel_path, revision, all_props, pool));
535
entry = apr_pcalloc(pool, sizeof(*entry));
537
svn_ra_serf__walk_all_props(props, path, fetched_rev, dirent_walker, entry,
546
svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
547
apr_hash_t **dirents,
548
svn_revnum_t *fetched_rev,
549
apr_hash_t **ret_props,
550
const char *rel_path,
551
svn_revnum_t revision,
552
apr_uint32_t dirent_fields,
555
svn_ra_serf__session_t *session = ra_session->priv;
559
path = session->repos_url.path;
561
/* If we have a relative path, append it. */
564
path = svn_path_url_add_component(path, rel_path, pool);
567
props = apr_hash_make(pool);
569
if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
571
const char *vcc_url, *relative_url, *basecoll_url;
573
SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &relative_url,
574
session, session->conns[0],
577
SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
579
"0", baseline_props, pool));
581
basecoll_url = svn_ra_serf__get_ver_prop(props, vcc_url, revision,
582
"DAV:", "baseline-collection");
591
*fetched_rev = revision;
594
path = svn_path_url_add_component(basecoll_url, relative_url, pool);
595
revision = SVN_INVALID_REVNUM;
598
/* If we're asked for children, fetch them now. */
601
svn_ra_serf__propfind_context_t *prop_ctx;
602
struct path_dirent_visitor_t dirent_walk;
605
svn_ra_serf__deliver_props(&prop_ctx, props, session, session->conns[0],
606
path, revision, "1", all_props, TRUE,
607
NULL, session->pool);
609
SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, session, pool));
611
/* We're going to create two hashes to help the walker along.
612
* We're going to return the 2nd one back to the caller as it
613
* will have the basenames it expects.
615
dirent_walk.full_paths = apr_hash_make(pool);
616
dirent_walk.base_paths = apr_hash_make(pool);
617
dirent_walk.orig_path = svn_path_canonicalize(path, pool);
619
svn_ra_serf__walk_all_paths(props, revision, path_dirent_walker,
622
*dirents = dirent_walk.base_paths;
625
/* If we're asked for the directory properties, fetch them too. */
628
props = apr_hash_make(pool);
629
*ret_props = apr_hash_make(pool);
631
SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
632
path, revision, "0", all_props,
634
svn_ra_serf__walk_all_props(props, path, revision,
635
svn_ra_serf__set_flat_props,
643
svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
647
svn_ra_serf__session_t *session = ra_session->priv;
649
if (!session->repos_root_str)
653
SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL,
654
session, session->conns[0],
655
session->repos_url.path, pool));
658
*url = session->repos_root_str;
663
svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
667
svn_ra_serf__session_t *session = ra_session->priv;
669
const char *root_url;
671
props = apr_hash_make(pool);
673
svn_ra_serf__get_repos_root(ra_session, &root_url, pool);
675
SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
676
root_url, SVN_INVALID_REVNUM, "0",
678
*uuid = svn_ra_serf__get_prop(props, root_url,
679
SVN_DAV_PROP_NS_DAV, "repository-uuid");
689
static const svn_ra__vtable_t serf_vtable = {
691
ra_serf_get_description,
694
svn_ra_serf__reparent,
695
svn_ra_serf__get_latest_revnum,
696
svn_ra_serf__get_dated_revision,
697
svn_ra_serf__change_rev_prop,
698
svn_ra_serf__rev_proplist,
699
svn_ra_serf__rev_prop,
700
svn_ra_serf__get_commit_editor,
701
svn_ra_serf__get_file,
702
svn_ra_serf__get_dir,
703
svn_ra_serf__do_update,
704
svn_ra_serf__do_switch,
705
svn_ra_serf__do_status,
706
svn_ra_serf__do_diff,
707
svn_ra_serf__get_log,
708
svn_ra_serf__check_path,
710
svn_ra_serf__get_uuid,
711
svn_ra_serf__get_repos_root,
712
svn_ra_serf__get_locations,
713
svn_ra_serf__get_file_revs,
716
svn_ra_serf__get_lock,
717
svn_ra_serf__get_locks,
722
svn_ra_serf__init(const svn_version_t *loader_version,
723
const svn_ra__vtable_t **vtable,
726
static const svn_version_checklist_t checklist[] =
728
{ "svn_subr", svn_subr_version },
729
{ "svn_delta", svn_delta_version },
733
SVN_ERR(svn_ver_check_list(ra_serf_version(), checklist));
735
/* Simplified version check to make sure we can safely use the
736
VTABLE parameter. The RA loader does a more exhaustive check. */
737
if (loader_version->major != SVN_VER_MAJOR)
739
return svn_error_createf
740
(SVN_ERR_VERSION_MISMATCH, NULL,
741
_("Unsupported RA loader version (%d) for ra_serf"),
742
loader_version->major);
745
*vtable = &serf_vtable;
750
/* Compatibility wrapper for pre-1.2 subversions. Needed? */
751
#define NAME "ra_serf"
752
#define DESCRIPTION RA_SERF_DESCRIPTION
753
#define VTBL serf_vtable
754
#define INITFUNC svn_ra_serf__init
755
#define COMPAT_INITFUNC svn_ra_serf_init
756
#include "../libsvn_ra/wrapper_template.h"