2
* serve.c : Functions for serving the Subversion protocol
4
* ====================================================================
5
* Copyright (c) 2000-2004 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
#include <limits.h> /* for UINT_MAX */
23
#define APR_WANT_STRFUNC
25
#include <apr_general.h>
26
#include <apr_strings.h>
30
#include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */
31
#include <svn_types.h>
32
#include <svn_string.h>
33
#include <svn_pools.h>
34
#include <svn_error.h>
35
#include <svn_ra_svn.h>
36
#include <svn_repos.h>
40
#include <svn_config.h>
41
#include <svn_props.h>
47
svn_fs_t *fs; /* For convenience; same as svn_repos_fs(repos) */
48
svn_config_t *cfg; /* Parsed repository svnserve.conf */
49
svn_config_t *pwdb; /* Parsed password database */
50
const char *realm; /* Authentication realm */
51
const char *repos_url; /* URL to base of repository */
52
const char *fs_path; /* Decoded base path inside repository */
54
svn_boolean_t tunnel; /* Tunneled through login agent */
55
const char *tunnel_user; /* Allow EXTERNAL to authenticate as this */
56
svn_boolean_t read_only; /* Disallow write access (global flag) */
62
svn_revnum_t *new_rev;
65
} commit_callback_baton_t;
69
const char *repos_url; /* Decoded repository URL. */
72
} report_driver_baton_t;
76
svn_ra_svn_conn_t *conn;
80
svn_ra_svn_conn_t *conn;
81
apr_pool_t *pool; /* Pool provided in the handler call. */
84
enum authn_type { UNAUTHENTICATED, AUTHENTICATED };
85
enum access_type { NO_ACCESS, READ_ACCESS, WRITE_ACCESS };
87
/* Verify that URL is inside REPOS_URL and get its fs path. Assume that
88
REPOS_URL and URL are already URI-decoded. */
89
static svn_error_t *get_fs_path(const char *repos_url, const char *url,
90
const char **fs_path, apr_pool_t *pool)
94
len = strlen(repos_url);
95
if (strncmp(url, repos_url, len) != 0)
96
return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
97
"'%s' is not the same repository as '%s'",
103
/* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
105
static enum access_type get_access(server_baton_t *b, enum authn_type auth)
107
const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS :
108
SVN_CONFIG_OPTION_ANON_ACCESS;
109
const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read";
110
enum access_type result;
112
svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def);
113
result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
114
strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
115
return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result;
118
static enum access_type current_access(server_baton_t *b)
120
return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED);
123
/* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME
124
is true, don't send anonymous mech even if that would give the desired
126
static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
127
server_baton_t *b, enum access_type required,
128
svn_boolean_t needs_username)
130
if (get_access(b, UNAUTHENTICATED) >= required)
131
SVN_ERR(svn_ra_svn_write_word(conn, pool, "ANONYMOUS"));
132
if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required)
133
SVN_ERR(svn_ra_svn_write_word(conn, pool, "EXTERNAL"));
134
if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
135
SVN_ERR(svn_ra_svn_write_word(conn, pool, "CRAM-MD5"));
139
/* Context for cleanup handler. */
140
struct cleanup_fs_access_baton
146
/* Pool cleanup handler. Make sure fs's access_t points to NULL when
147
the command pool is destroyed. */
148
static apr_status_t cleanup_fs_access(void *data)
151
struct cleanup_fs_access_baton *baton = data;
153
serr = svn_fs_set_access (baton->fs, NULL);
156
apr_status_t apr_err = serr->apr_err;
157
svn_error_clear(serr);
165
/* Create an svn_fs_access_t in POOL for USER and associate it with
166
B's filesystem. Also, register a cleanup handler with POOL which
167
de-associates the svn_fs_access_t from B's filesystem. */
169
create_fs_access(server_baton_t *b, apr_pool_t *pool)
171
svn_fs_access_t *fs_access;
172
struct cleanup_fs_access_baton *cleanup_baton;
177
SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool));
178
SVN_ERR(svn_fs_set_access(b->fs, fs_access));
180
cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
181
cleanup_baton->pool = pool;
182
cleanup_baton->fs = b->fs;
183
apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
184
apr_pool_cleanup_null);
189
/* Authenticate, once the client has chosen a mechanism and possibly
190
* sent an initial mechanism token. On success, set *success to true
191
* and b->user to the authenticated username (or NULL for anonymous).
192
* On authentication failure, report failure to the client and set
193
* *success to FALSE. On communications failure, return an error.
194
* If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
195
static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
196
const char *mech, const char *mecharg,
197
server_baton_t *b, enum access_type required,
198
svn_boolean_t needs_username,
199
svn_boolean_t *success)
204
if (get_access(b, AUTHENTICATED) >= required
205
&& b->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
207
if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0)
208
return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
209
"Requested username does not match");
210
b->user = b->tunnel_user;
211
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
216
if (get_access(b, UNAUTHENTICATED) >= required
217
&& strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
219
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
224
if (get_access(b, AUTHENTICATED) >= required
225
&& b->pwdb && strcmp(mech, "CRAM-MD5") == 0)
227
SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success));
228
b->user = apr_pstrdup (b->pool, user);
232
return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
233
"Must authenticate with listed mechanism");
236
/* Perform an authentication request in order to get an access level of
237
* REQUIRED or higher. Since the client may escape the authentication
238
* exchange, the caller should check current_access(b) to see if
239
* authentication succeeded. */
240
static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
241
server_baton_t *b, enum access_type required,
242
svn_boolean_t needs_username)
244
svn_boolean_t success;
245
const char *mech, *mecharg;
247
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
248
SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
249
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)c)", b->realm));
252
SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
255
SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username,
262
/* Send a trivial auth request, listing no mechanisms. */
263
static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
264
apr_pool_t *pool, server_baton_t *b)
266
if (b->protocol_version < 2)
268
return svn_ra_svn_write_cmd_response(conn, pool, "()c", "");
271
/* Ensure that the client has write access. If the client already has
272
write access, just send a trivial auth request. Else, try to authenticate
273
the client. If NEEDS_USERNAME is TRUE, only use auth mechs that will yield
274
a username. Return an error if write access couldn't be achieved. */
275
static svn_error_t *must_have_write_access(svn_ra_svn_conn_t *conn,
276
apr_pool_t *pool, server_baton_t *b,
277
svn_boolean_t needs_username)
279
if (current_access(b) == WRITE_ACCESS
280
&& (! needs_username || b->user))
282
SVN_ERR(create_fs_access(b, pool));
283
return trivial_auth_request(conn, pool, b);
286
/* If we can get write access by authenticating, try that. */
287
if (b->user == NULL && get_access(b, AUTHENTICATED) == WRITE_ACCESS
288
&& (b->tunnel_user || b->pwdb) && b->protocol_version >= 2)
289
SVN_ERR(auth_request(conn, pool, b, WRITE_ACCESS, needs_username));
291
if (current_access(b) != WRITE_ACCESS)
292
return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
293
svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
294
"Connection is read-only"), NULL);
296
SVN_ERR(create_fs_access(b, pool));
301
/* --- REPORTER COMMAND SET --- */
303
/* To allow for pipelining, reporter commands have no reponses. If we
304
* get an error, we ignore all subsequent reporter commands and return
305
* the error finish_report, to be handled by the calling command.
308
static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
309
apr_array_header_t *params, void *baton)
311
report_driver_baton_t *b = baton;
312
const char *path, *lock_token;
314
svn_boolean_t start_empty;
316
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crb?(?c)",
317
&path, &rev, &start_empty, &lock_token));
318
path = svn_path_canonicalize(path, pool);
320
b->err = svn_repos_set_path2(b->report_baton, path, rev, start_empty,
325
static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
326
apr_array_header_t *params, void *baton)
328
report_driver_baton_t *b = baton;
331
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
332
path = svn_path_canonicalize(path, pool);
334
b->err = svn_repos_delete_path(b->report_baton, path, pool);
338
static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
339
apr_array_header_t *params, void *baton)
341
report_driver_baton_t *b = baton;
342
const char *path, *url, *lock_token, *fs_path;
344
svn_boolean_t start_empty;
346
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccrb?(?c)",
347
&path, &url, &rev, &start_empty,
349
path = svn_path_canonicalize(path, pool);
350
url = svn_path_uri_decode(svn_path_canonicalize(url, pool), pool);
352
b->err = get_fs_path(b->repos_url, url, &fs_path, pool);
354
b->err = svn_repos_link_path2(b->report_baton, path, fs_path, rev,
355
start_empty, lock_token, pool);
359
static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
360
apr_array_header_t *params, void *baton)
362
report_driver_baton_t *b = baton;
364
/* No arguments to parse. */
365
SVN_ERR(trivial_auth_request(conn, pool, b->sb));
367
b->err = svn_repos_finish_report(b->report_baton, pool);
371
static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
372
apr_array_header_t *params, void *baton)
374
report_driver_baton_t *b = baton;
376
/* No arguments to parse. */
377
svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
381
static const svn_ra_svn_cmd_entry_t report_commands[] = {
382
{ "set-path", set_path },
383
{ "delete-path", delete_path },
384
{ "link-path", link_path },
385
{ "finish-report", finish_report, TRUE },
386
{ "abort-report", abort_report, TRUE },
390
/* Accept a report from the client, drive the network editor with the
391
* result, and then write an empty command response. If there is a
392
* non-protocol failure, accept_report will abort the edit and return
393
* a command error to be reported by handle_commands(). */
394
static svn_error_t *accept_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
395
server_baton_t *b, svn_revnum_t rev,
396
const char *target, const char *tgt_path,
397
svn_boolean_t text_deltas,
398
svn_boolean_t recurse,
399
svn_boolean_t ignore_ancestry)
401
const svn_delta_editor_t *editor;
402
void *edit_baton, *report_baton;
403
report_driver_baton_t rb;
406
/* Make an svn_repos report baton. Tell it to drive the network editor
407
* when the report is complete. */
408
svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
409
SVN_CMD_ERR(svn_repos_begin_report(&report_baton, rev, b->user, b->repos,
410
b->fs_path, target, tgt_path, text_deltas,
411
recurse, ignore_ancestry, editor,
412
edit_baton, NULL, NULL, pool));
415
rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
416
rb.report_baton = report_baton;
418
err = svn_ra_svn_handle_commands(conn, pool, report_commands, &rb);
421
/* Network or protocol error while handling commands. */
422
svn_error_clear(rb.err);
427
/* Some failure during the reporting or editing operations. */
428
svn_error_clear(editor->abort_edit(edit_baton, pool));
431
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
435
/* --- MAIN COMMAND SET --- */
437
/* Write out a property list. PROPS is allowed to be NULL, in which case
438
* an empty list will be written out; this happens if the client could
439
* have asked for props but didn't. */
440
static svn_error_t *write_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
443
apr_hash_index_t *hi;
451
for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
453
apr_hash_this(hi, &namevar, NULL, &valuevar);
456
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cs", name, value));
462
/* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t
464
static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
466
apr_array_header_t *propdiffs)
470
for (i = 0; i < propdiffs->nelts; ++i)
472
const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
474
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "c(?s)",
475
prop->name, prop->value));
481
/* Write out a lock to the client. */
482
static svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
486
const char *cdate, *edate;
488
cdate = svn_time_to_cstring(lock->creation_date, pool);
489
edate = lock->expiration_date
490
? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
491
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
492
lock->token, lock->owner, lock->comment,
498
static const char *kind_word(svn_node_kind_t kind)
508
case svn_node_unknown:
515
/* ### This really belongs in libsvn_repos. */
516
/* Get the properties for a path, with hardcoded committed-info values. */
517
static svn_error_t *get_props(apr_hash_t **props, svn_fs_root_t *root,
518
const char *path, apr_pool_t *pool)
522
const char *cdate, *cauthor, *uuid;
524
/* Get the properties. */
525
SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
527
/* Hardcode the values for the committed revision, date, and author. */
528
SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
530
str = svn_string_create(apr_psprintf(pool, "%ld", crev),
532
apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING, str);
533
str = (cdate) ? svn_string_create(cdate, pool) : NULL;
534
apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING,
536
str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
537
apr_hash_set(*props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING, str);
539
/* Hardcode the values for the UUID. */
540
SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
541
str = (uuid) ? svn_string_create(uuid, pool) : NULL;
542
apr_hash_set(*props, SVN_PROP_ENTRY_UUID, APR_HASH_KEY_STRING, str);
547
static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
548
apr_array_header_t *params, void *baton)
550
server_baton_t *b = baton;
553
SVN_ERR(trivial_auth_request(conn, pool, b));
554
SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
555
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev));
559
static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
560
apr_array_header_t *params, void *baton)
562
server_baton_t *b = baton;
567
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", ×tr));
568
SVN_ERR(trivial_auth_request(conn, pool, b));
569
SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
570
SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool));
571
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev));
575
static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
576
apr_array_header_t *params, void *baton)
578
server_baton_t *b = baton;
583
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc?s", &rev, &name, &value));
584
SVN_ERR(must_have_write_access(conn, pool, b, FALSE));
585
SVN_CMD_ERR(svn_repos_fs_change_rev_prop2(b->repos, rev, b->user,
586
name, value, NULL, NULL, pool));
587
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
591
static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
592
apr_array_header_t *params, void *baton)
594
server_baton_t *b = baton;
598
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev));
599
SVN_ERR(trivial_auth_request(conn, pool, b));
600
SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
602
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
603
SVN_ERR(write_proplist(conn, pool, props));
604
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
608
static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
609
apr_array_header_t *params, void *baton)
611
server_baton_t *b = baton;
616
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc", &rev, &name));
617
SVN_ERR(trivial_auth_request(conn, pool, b));
618
SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name,
620
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "(?s)", value));
624
static svn_error_t *commit_done(svn_revnum_t new_rev, const char *date,
625
const char *author, void *baton)
627
commit_callback_baton_t *ccb = baton;
629
*ccb->new_rev = new_rev;
631
*ccb->author = author;
635
/* Add the LOCK_TOKENS to the filesystem access context if any. LOCK_TOKENS is
636
an array of svn_ra_svn_item_t structs. Return an error if they are
637
not a list of lists. */
638
static svn_error_t *add_lock_tokens(apr_array_header_t *lock_tokens,
643
svn_fs_access_t *fs_access;
645
SVN_ERR(svn_fs_get_access(&fs_access, sb->fs));
647
/* If there is no access context, nowhere to add the tokens. */
651
for (i = 0; i < lock_tokens->nelts; ++i)
654
svn_ra_svn_item_t *path_item, *token_item;
655
svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i,
657
if (item->kind != SVN_RA_SVN_LIST)
658
return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
659
"Lock tokens aren't a list of lists");
661
path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
662
if (path_item->kind != SVN_RA_SVN_STRING)
663
return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
664
"Lock path isn't a string.");
666
token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
667
if (token_item->kind != SVN_RA_SVN_STRING)
668
return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
669
"Lock token isn't a string");
671
token = token_item->u.string->data;
672
SVN_ERR(svn_fs_access_add_lock_token(fs_access, token));
678
/* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
679
LOCK_TOKENS contais svn_ra_svn_item_t elements, assumed to be lists. */
680
static svn_error_t *unlock_paths(apr_array_header_t *lock_tokens,
685
apr_pool_t *iterpool;
687
iterpool = svn_pool_create(pool);
689
for (i = 0; i < lock_tokens->nelts; ++i)
691
svn_ra_svn_item_t *item, *path_item, *token_item;
692
const char *path, *token, *full_path;
693
svn_pool_clear(iterpool);
695
item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
696
path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
697
token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
699
path = path_item->u.string->data;
700
token = token_item->u.string->data;
702
full_path = svn_path_join(sb->fs_path,
703
svn_path_canonicalize(path, iterpool),
706
/* The lock may have become defunct after the commit, so ignore such
709
### If we ever write a logging facility for svnserve, this
710
would be a good place to log an error before clearing
712
svn_error_clear(svn_repos_fs_unlock(sb->repos, full_path, token,
716
svn_pool_destroy(iterpool);
721
static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
722
apr_array_header_t *params, void *baton)
724
server_baton_t *b = baton;
725
const char *log_msg, *date, *author;
726
apr_array_header_t *lock_tokens;
727
svn_boolean_t keep_locks;
728
const svn_delta_editor_t *editor;
730
svn_boolean_t aborted;
731
commit_callback_baton_t ccb;
732
svn_revnum_t new_rev;
734
if (params->nelts == 1)
736
/* Clients before 1.2 don't send lock-tokens and keep-locks fields. */
737
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &log_msg));
742
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "clb", &log_msg,
743
&lock_tokens, &keep_locks));
744
/* Require a username if the client gave us any lock tokens. */
745
SVN_ERR(must_have_write_access(conn, pool, b,
746
lock_tokens && lock_tokens->nelts > 0));
748
/* Give the lock tokens to the FS if we got any. */
750
SVN_CMD_ERR(add_lock_tokens(lock_tokens, b, pool));
751
ccb.new_rev = &new_rev;
753
ccb.author = &author;
754
/* ### Note that svn_repos_get_commit_editor actually wants a decoded URL. */
755
SVN_CMD_ERR(svn_repos_get_commit_editor2
756
(&editor, &edit_baton, b->repos, NULL,
757
svn_path_uri_decode(b->repos_url, pool),
759
log_msg, commit_done, &ccb, pool));
760
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
761
SVN_ERR(svn_ra_svn_drive_editor(conn, pool, editor, edit_baton, &aborted));
764
SVN_ERR(trivial_auth_request(conn, pool, b));
766
/* In tunnel mode, deltify before answering the client, because
767
answering may cause the client to terminate the connection
768
and thus kill the server. But otherwise, deltify after
769
answering the client, to avoid user-visible delay. */
772
SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
774
/* Unlock the paths. */
775
if (! keep_locks && lock_tokens)
776
SVN_ERR(unlock_paths(lock_tokens, b, pool));
778
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(?c)(?c)",
779
new_rev, date, author));
782
SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
787
static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
788
apr_array_header_t *params, void *baton)
790
server_baton_t *b = baton;
791
const char *path, *full_path, *hex_digest;
794
svn_stream_t *contents;
795
apr_hash_t *props = NULL;
796
svn_string_t write_str;
799
svn_boolean_t want_props, want_contents;
800
unsigned char digest[APR_MD5_DIGESTSIZE];
801
svn_error_t *err, *write_err;
803
/* Parse arguments. */
804
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev,
805
&want_props, &want_contents));
806
path = svn_path_canonicalize(path, pool);
807
SVN_ERR(trivial_auth_request(conn, pool, b));
808
if (!SVN_IS_VALID_REVNUM(rev))
809
SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
810
full_path = svn_path_join(b->fs_path, path, pool);
812
/* Fetch the properties and a stream for the contents. */
813
SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
814
SVN_CMD_ERR(svn_fs_file_md5_checksum(digest, root, full_path, pool));
815
hex_digest = svn_md5_digest_to_cstring_display(digest, pool);
817
SVN_CMD_ERR(get_props(&props, root, full_path, pool));
819
SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
821
/* Send successful command response with revision and props. */
822
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)r(!", "success",
824
SVN_ERR(write_proplist(conn, pool, props));
825
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
827
/* Now send the file's contents. */
834
err = svn_stream_read(contents, buf, &len);
839
write_str.data = buf;
841
SVN_ERR(svn_ra_svn_write_string(conn, pool, &write_str));
843
if (len < sizeof(buf))
845
err = svn_stream_close(contents);
849
write_err = svn_ra_svn_write_cstring(conn, pool, "");
852
svn_error_clear(err);
856
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
862
static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
863
apr_array_header_t *params, void *baton)
865
server_baton_t *b = baton;
866
const char *path, *full_path, *file_path, *name, *cauthor, *cdate;
868
apr_hash_t *entries, *props = NULL, *file_props;
869
apr_hash_index_t *hi;
870
svn_fs_dirent_t *fsent;
876
svn_boolean_t want_props, want_contents;
878
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev,
879
&want_props, &want_contents));
880
path = svn_path_canonicalize(path, pool);
881
SVN_ERR(trivial_auth_request(conn, pool, b));
882
if (!SVN_IS_VALID_REVNUM(rev))
883
SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
884
full_path = svn_path_join(b->fs_path, path, pool);
886
/* Fetch the root of the appropriate revision. */
887
SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
889
/* Fetch the directory properties if requested. */
891
SVN_CMD_ERR(get_props(&props, root, full_path, pool));
893
/* Fetch the directory entries if requested. */
896
SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
898
/* Transform the hash table's FS entries into dirents. This probably
899
* belongs in libsvn_repos. */
900
subpool = svn_pool_create(pool);
901
for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
903
apr_hash_this(hi, &key, NULL, &val);
907
svn_pool_clear(subpool);
909
file_path = svn_path_join(full_path, name, subpool);
910
entry = apr_pcalloc(pool, sizeof(*entry));
913
entry->kind = fsent->kind;
916
if (entry->kind == svn_node_dir)
919
SVN_CMD_ERR(svn_fs_file_length(&entry->size, root, file_path,
923
SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
925
entry->has_props = (apr_hash_count(file_props) > 0) ? TRUE : FALSE;
927
/* created_rev, last_author, time */
928
SVN_CMD_ERR(svn_repos_get_committed_info(&entry->created_rev, &cdate,
929
&cauthor, root, file_path,
931
entry->last_author = apr_pstrdup(pool, cauthor);
933
SVN_CMD_ERR(svn_time_from_cstring(&entry->time, cdate, subpool));
935
entry->time = (time_t) -1;
937
/* Store the entry. */
938
apr_hash_set(entries, name, APR_HASH_KEY_STRING, entry);
940
svn_pool_destroy(subpool);
943
/* Write out response. */
944
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(r(!", "success", rev));
945
SVN_ERR(write_proplist(conn, pool, props));
946
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(!"));
949
for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
951
apr_hash_this(hi, &key, NULL, &val);
954
cdate = (entry->time == (time_t) -1) ? NULL
955
: svn_time_to_cstring(entry->time, pool);
956
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
957
kind_word(entry->kind),
958
(apr_uint64_t) entry->size,
959
entry->has_props, entry->created_rev,
960
cdate, entry->last_author));
963
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
968
static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
969
apr_array_header_t *params, void *baton)
971
server_baton_t *b = baton;
974
svn_boolean_t recurse;
976
/* Parse the arguments. */
977
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cb", &rev, &target,
979
target = svn_path_canonicalize(target, pool);
980
SVN_ERR(trivial_auth_request(conn, pool, b));
981
if (!SVN_IS_VALID_REVNUM(rev))
982
SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
984
return accept_report(conn, pool, b, rev, target, NULL, TRUE, recurse, FALSE);
987
static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
988
apr_array_header_t *params, void *baton)
990
server_baton_t *b = baton;
993
const char *switch_url, *switch_path;
994
svn_boolean_t recurse;
996
/* Parse the arguments. */
997
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbc", &rev, &target,
998
&recurse, &switch_url));
999
target = svn_path_canonicalize(target, pool);
1000
switch_url = svn_path_canonicalize(switch_url, pool);
1001
SVN_ERR(trivial_auth_request(conn, pool, b));
1002
if (!SVN_IS_VALID_REVNUM(rev))
1003
SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1004
SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1005
svn_path_uri_decode(switch_url, pool),
1006
&switch_path, pool));
1008
return accept_report(conn, pool, b, rev, target, switch_path, TRUE, recurse,
1012
static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1013
apr_array_header_t *params, void *baton)
1015
server_baton_t *b = baton;
1018
svn_boolean_t recurse;
1020
/* Parse the arguments. */
1021
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cb?(?r)",
1022
&target, &recurse, &rev));
1023
target = svn_path_canonicalize(target, pool);
1024
SVN_ERR(trivial_auth_request(conn, pool, b));
1025
if (!SVN_IS_VALID_REVNUM(rev))
1026
SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1027
return accept_report(conn, pool, b, rev, target, NULL, FALSE, recurse,
1031
static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1032
apr_array_header_t *params, void *baton)
1034
server_baton_t *b = baton;
1036
const char *target, *versus_url, *versus_path;
1037
svn_boolean_t recurse, ignore_ancestry;
1039
/* Parse the arguments. */
1040
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
1041
&recurse, &ignore_ancestry, &versus_url));
1042
target = svn_path_canonicalize(target, pool);
1043
versus_url = svn_path_canonicalize(versus_url, pool);
1044
SVN_ERR(trivial_auth_request(conn, pool, b));
1045
if (!SVN_IS_VALID_REVNUM(rev))
1046
SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1047
SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1048
svn_path_uri_decode(versus_url, pool),
1049
&versus_path, pool));
1051
return accept_report(conn, pool, b, rev, target, versus_path, TRUE, recurse,
1055
/* Send a log entry to the client. */
1056
static svn_error_t *log_receiver(void *baton, apr_hash_t *changed_paths,
1057
svn_revnum_t rev, const char *author,
1058
const char *date, const char *message,
1061
log_baton_t *b = baton;
1062
svn_ra_svn_conn_t *conn = b->conn;
1063
apr_hash_index_t *h;
1067
svn_log_changed_path_t *change;
1070
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "(!"));
1073
for (h = apr_hash_first(pool, changed_paths); h; h = apr_hash_next(h))
1075
apr_hash_this(h, &key, NULL, &val);
1078
action[0] = change->action;
1080
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cw(?cr)", path, action,
1081
change->copyfrom_path,
1082
change->copyfrom_rev));
1085
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)", rev, author,
1087
return SVN_NO_ERROR;
1090
static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1091
apr_array_header_t *params, void *baton)
1093
svn_error_t *err, *write_err;
1094
server_baton_t *b = baton;
1095
svn_revnum_t start_rev, end_rev;
1096
const char *full_path;
1097
svn_boolean_t changed_paths, strict_node;
1098
apr_array_header_t *paths, *full_paths;
1099
svn_ra_svn_item_t *elt;
1104
/* Parse the arguments. */
1105
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n", &paths,
1106
&start_rev, &end_rev, &changed_paths,
1107
&strict_node, &limit));
1109
/* If we got an unspecified number then the user didn't send us anything,
1110
so we assume no limit. If it's larger than INT_MAX then someone is
1111
messing with us, since we know the svn client libraries will never send
1112
us anything that big, so play it safe and default to no limit. */
1113
if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
1116
full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
1117
for (i = 0; i < paths->nelts; i++)
1119
elt = &((svn_ra_svn_item_t *) paths->elts)[i];
1120
if (elt->kind != SVN_RA_SVN_STRING)
1121
return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1122
"Log path entry not a string");
1123
full_path = svn_path_join(b->fs_path,
1124
svn_path_canonicalize(elt->u.string->data,
1127
*((const char **) apr_array_push(full_paths)) = full_path;
1129
SVN_ERR(trivial_auth_request(conn, pool, b));
1131
/* Get logs. (Can't report errors back to the client at this point.) */
1132
lb.fs_path = b->fs_path;
1134
err = svn_repos_get_logs3(b->repos, full_paths, start_rev, end_rev,
1135
(int) limit, changed_paths, strict_node,
1136
NULL, NULL, log_receiver, &lb, pool);
1138
write_err = svn_ra_svn_write_word(conn, pool, "done");
1141
svn_error_clear(err);
1145
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1146
return SVN_NO_ERROR;
1149
static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1150
apr_array_header_t *params, void *baton)
1152
server_baton_t *b = baton;
1154
const char *path, *full_path;
1155
svn_fs_root_t *root;
1156
svn_node_kind_t kind;
1158
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev));
1159
path = svn_path_canonicalize(path, pool);
1160
SVN_ERR(trivial_auth_request(conn, pool, b));
1161
if (!SVN_IS_VALID_REVNUM(rev))
1162
SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1163
full_path = svn_path_join(b->fs_path, path, pool);
1164
SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1165
SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
1166
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "w", kind_word(kind)));
1167
return SVN_NO_ERROR;
1170
static svn_error_t *stat(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1171
apr_array_header_t *params, void *baton)
1173
server_baton_t *b = baton;
1175
const char *path, *full_path, *cdate;
1176
svn_fs_root_t *root;
1177
svn_dirent_t *dirent;
1179
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev));
1180
path = svn_path_canonicalize(path, pool);
1181
SVN_ERR(trivial_auth_request(conn, pool, b));
1182
if (!SVN_IS_VALID_REVNUM(rev))
1183
SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1184
full_path = svn_path_join(b->fs_path, path, pool);
1185
SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1186
SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
1188
/* Need to return the equivalent of "(?l)", since that's what the
1189
client is reading. */
1193
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "()"));
1194
return SVN_NO_ERROR;
1197
cdate = (dirent->time == (time_t) -1) ? NULL
1198
: svn_time_to_cstring(dirent->time, pool);
1200
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
1201
kind_word(dirent->kind),
1202
(apr_uint64_t) dirent->size,
1203
dirent->has_props, dirent->created_rev,
1204
cdate, dirent->last_author));
1206
return SVN_NO_ERROR;
1209
static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1210
apr_array_header_t *params, void *baton)
1212
svn_error_t *err, *write_err;
1213
server_baton_t *b = baton;
1214
svn_revnum_t revision;
1215
apr_array_header_t *location_revisions, *loc_revs_proto;
1216
svn_ra_svn_item_t *elt;
1218
const char *relative_path;
1219
svn_revnum_t peg_revision;
1220
apr_hash_t *fs_locations;
1221
apr_hash_index_t *iter;
1222
const char *abs_path;
1223
const void *iter_key;
1226
/* Parse the arguments. */
1227
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crl", &relative_path,
1230
relative_path = svn_path_canonicalize(relative_path, pool);
1232
abs_path = svn_path_join(b->fs_path, relative_path, pool);
1234
location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
1235
sizeof(svn_revnum_t));
1236
for (i = 0; i < loc_revs_proto->nelts; i++)
1238
elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
1239
if (elt->kind != SVN_RA_SVN_NUMBER)
1240
return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1241
"Get-locations location revisions entry "
1242
"not a revision number");
1243
revision = (svn_revnum_t)(elt->u.number);
1244
APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
1246
SVN_ERR(trivial_auth_request(conn, pool, b));
1248
/* All the parameters are fine - let's perform the query against the
1251
/* We store both err and write_err here, so the client will get
1252
* the "done" even if there was an error in fetching the results. */
1254
err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
1255
peg_revision, location_revisions,
1258
/* Now, write the results to the connection. */
1263
for (iter = apr_hash_first(pool, fs_locations); iter;
1264
iter = apr_hash_next(iter))
1266
apr_hash_this(iter, &iter_key, NULL, &iter_value);
1267
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "rc",
1268
*(const svn_revnum_t *)iter_key,
1269
(const char *)iter_value));
1274
write_err = svn_ra_svn_write_word(conn, pool, "done");
1277
svn_error_clear(err);
1282
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1284
return SVN_NO_ERROR;
1287
/* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the
1288
client as a string. */
1289
static svn_error_t *svndiff_handler(void *baton, const char *data,
1292
file_revs_baton_t *b = baton;
1297
return svn_ra_svn_write_string(b->conn, b->pool, &str);
1300
/* This implements svn_close_fn_t. Mark the end of the data by writing an
1301
empty string to the client. */
1302
static svn_error_t *svndiff_close_handler(void *baton)
1304
file_revs_baton_t *b = baton;
1306
SVN_ERR(svn_ra_svn_write_cstring(b->conn, b->pool, ""));
1307
return SVN_NO_ERROR;
1310
/* This implements the svn_repos_file_rev_handler_t interface. */
1311
static svn_error_t *file_rev_handler(void *baton, const char *path,
1312
svn_revnum_t rev, apr_hash_t *rev_props,
1313
svn_txdelta_window_handler_t *d_handler,
1315
apr_array_header_t *prop_diffs,
1318
file_revs_baton_t *frb = baton;
1319
svn_stream_t *stream;
1321
SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "cr(!",
1323
SVN_ERR(write_proplist(frb->conn, pool, rev_props));
1324
SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)(!"));
1325
SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
1326
SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)"));
1328
/* Store the pool for the delta stream. */
1331
/* Prepare for the delta or just write an empty string. */
1334
stream = svn_stream_create(baton, pool);
1335
svn_stream_set_write(stream, svndiff_handler);
1336
svn_stream_set_close(stream, svndiff_close_handler);
1338
svn_txdelta_to_svndiff(stream, pool, d_handler, d_baton);
1341
SVN_ERR(svn_ra_svn_write_cstring(frb->conn, pool, ""));
1343
return SVN_NO_ERROR;
1346
static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1347
apr_array_header_t *params, void *baton)
1349
server_baton_t *b = baton;
1350
svn_error_t *err, *write_err;
1351
file_revs_baton_t frb;
1352
svn_revnum_t start_rev, end_rev;
1354
const char *full_path;
1356
/* Parse arguments. */
1357
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)",
1358
&path, &start_rev, &end_rev));
1359
path = svn_path_canonicalize(path, pool);
1360
SVN_ERR(trivial_auth_request(conn, pool, b));
1361
full_path = svn_path_join(b->fs_path, path, pool);
1366
err = svn_repos_get_file_revs(b->repos, full_path, start_rev, end_rev, NULL,
1367
NULL, file_rev_handler, &frb, pool);
1368
write_err = svn_ra_svn_write_word(conn, pool, "done");
1371
svn_error_clear(err);
1375
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1377
return SVN_NO_ERROR;
1380
static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1381
apr_array_header_t *params, void *baton)
1383
server_baton_t *b = baton;
1385
const char *comment;
1386
const char *full_path;
1387
svn_boolean_t force;
1388
svn_revnum_t current_rev;
1391
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
1392
&force, ¤t_rev));
1394
full_path = svn_path_join(b->fs_path, svn_path_canonicalize(path, pool),
1397
SVN_ERR(must_have_write_access(conn, pool, b, TRUE));
1399
SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
1400
0, /* No expiration time. */
1401
current_rev, force, pool));
1403
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(!", "success"));
1404
SVN_ERR(write_lock(conn, pool, l));
1405
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
1407
return SVN_NO_ERROR;
1410
static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1411
apr_array_header_t *params, void *baton)
1413
server_baton_t *b = baton;
1414
const char *path, *token, *full_path;
1415
svn_boolean_t force;
1417
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b", &path, &token,
1420
full_path = svn_path_join(b->fs_path, svn_path_canonicalize(path, pool),
1423
/* Username required unless force was specified. */
1424
SVN_ERR(must_have_write_access(conn, pool, b, ! force));
1426
SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, force, pool));
1428
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1430
return SVN_NO_ERROR;
1433
static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1434
apr_array_header_t *params, void *baton)
1436
server_baton_t *b = baton;
1438
const char *full_path;
1441
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
1443
full_path = svn_path_join(b->fs_path, svn_path_canonicalize(path, pool),
1446
SVN_ERR(trivial_auth_request(conn, pool, b));
1448
SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
1450
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
1452
SVN_ERR(write_lock(conn, pool, l));
1453
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1455
return SVN_NO_ERROR;
1458
static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1459
apr_array_header_t *params, void *baton)
1461
server_baton_t *b = baton;
1463
const char *full_path;
1465
apr_hash_index_t *hi;
1470
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
1472
full_path = svn_path_join(b->fs_path, svn_path_canonicalize(path, pool),
1475
SVN_ERR(trivial_auth_request(conn, pool, b));
1477
SVN_CMD_ERR(svn_repos_fs_get_locks(&locks, b->repos, full_path,
1480
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
1481
for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1483
apr_hash_this(hi, &key, NULL, &val);
1485
SVN_ERR(write_lock(conn, pool, l));
1487
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1489
return SVN_NO_ERROR;
1493
static const svn_ra_svn_cmd_entry_t main_commands[] = {
1494
{ "get-latest-rev", get_latest_rev },
1495
{ "get-dated-rev", get_dated_rev },
1496
{ "change-rev-prop", change_rev_prop },
1497
{ "rev-proplist", rev_proplist },
1498
{ "rev-prop", rev_prop },
1499
{ "commit", commit },
1500
{ "get-file", get_file },
1501
{ "get-dir", get_dir },
1502
{ "update", update },
1503
{ "switch", switch_cmd },
1504
{ "status", status },
1507
{ "check-path", check_path },
1509
{ "get-locations", get_locations },
1510
{ "get-file-revs", get_file_revs },
1512
{ "unlock", unlock },
1513
{ "get-lock", get_lock },
1514
{ "get-locks", get_locks },
1518
/* Skip past the scheme part of a URL, including the tunnel specification
1519
* if present. Return NULL if the scheme part is invalid for ra_svn. */
1520
static const char *skip_scheme_part(const char *url)
1522
if (strncmp(url, "svn", 3) != 0)
1526
url += strcspn(url, ":");
1527
if (strncmp(url, "://", 3) != 0)
1532
/* Check that PATH is a valid repository path, meaning it doesn't contain any
1534
NOTE: This is similar to svn_path_is_backpath_present, but that function
1535
assumes the path separator is '/'. This function also checks for
1536
segments delimited by the local path separator. */
1537
static svn_boolean_t
1538
repos_path_valid(const char *path)
1540
const char *s = path;
1544
/* Scan for the end of the segment. */
1545
while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
1548
/* Check for '..'. */
1550
/* On Windows, don't allow sequences of more than one character
1551
consisting of just dots and spaces. Win32 functions treat
1552
paths such as ".. " and "......." inconsistently. Make sure
1553
no one can escape out of the root. */
1554
if (path - s >= 2 && strspn(s, ". ") == path - s)
1557
if (path - s == 2 && s[0] == '.' && s[1] == '.')
1561
/* Skip all separators. */
1562
while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
1570
/* Look for the repository given by URL, using ROOT as the virtual
1571
* repository root. If we find one, fill in the repos, fs, cfg,
1572
* repos_url, and fs_path fields of B. */
1573
static svn_error_t *find_repos(const char *url, const char *root,
1574
server_baton_t *b, apr_pool_t *pool)
1576
const char *path, *full_path, *repos_root, *pwdb_path;
1577
svn_stringbuf_t *url_buf;
1579
/* Skip past the scheme and authority part. */
1580
path = skip_scheme_part(url);
1582
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
1583
"Non-svn URL passed to svn server: '%s'", url);
1584
path = strchr(path, '/');
1585
path = (path == NULL) ? "" : path + 1;
1587
/* Decode URI escapes from the path. */
1588
path = svn_path_uri_decode(path, pool);
1590
/* Ensure that it isn't possible to escape the root by skipping leading
1591
slashes and not allowing '..' segments. */
1592
while (*path == '/')
1594
if (!repos_path_valid(path))
1595
return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
1596
"Couldn't determine repository path");
1598
/* Join the server-configured root with the client path. */
1599
full_path = svn_path_join(svn_path_canonicalize(root, pool),
1600
svn_path_canonicalize(path, pool), pool);
1602
/* Search for a repository in the full path. */
1603
repos_root = svn_repos_find_root_path(full_path, pool);
1605
return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
1606
"No repository found in '%s'", url);
1608
/* Open the repository and fill in b with the resulting information. */
1609
SVN_ERR(svn_repos_open(&b->repos, repos_root, pool));
1610
b->fs = svn_repos_fs(b->repos);
1611
b->fs_path = apr_pstrdup(pool, full_path + strlen(repos_root));
1612
url_buf = svn_stringbuf_create(url, pool);
1613
svn_path_remove_components(url_buf, svn_path_component_count(b->fs_path));
1614
b->repos_url = url_buf->data;
1616
/* Read repository configuration. */
1617
SVN_ERR(svn_config_read(&b->cfg, svn_repos_svnserve_conf(b->repos, pool),
1619
svn_config_get(b->cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL,
1620
SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
1623
pwdb_path = svn_path_join(svn_repos_conf_dir(b->repos, pool),
1625
SVN_ERR(svn_config_read(&b->pwdb, pwdb_path, TRUE, pool));
1627
/* Use the repository UUID as the default realm. */
1628
SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
1629
svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
1630
SVN_CONFIG_OPTION_REALM, b->realm);
1638
/* Make sure it's possible for the client to authenticate. */
1639
if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
1640
&& (get_access(b, AUTHENTICATED) == NO_ACCESS
1641
|| (!b->tunnel_user && !b->pwdb)))
1642
return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
1643
"No access allowed to this repository");
1644
return SVN_NO_ERROR;
1647
/* Compute the authentication name EXTERNAL should be able to get, if any. */
1648
static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
1654
/* Only offer EXTERNAL for connections tunneled over a login agent. */
1655
if (!params->tunnel)
1658
/* If a tunnel user was provided on the command line, use that. */
1659
if (params->tunnel_user)
1660
return params->tunnel_user;
1663
/* Use the current uid's name, if we can. */
1664
if (apr_uid_current(&uid, &gid, pool) == APR_SUCCESS
1665
&& apr_uid_name_get(&user, uid, pool) == APR_SUCCESS)
1669
/* Give up and don't offer EXTERNAL. */
1673
svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
1676
svn_error_t *err, *io_err;
1678
const char *mech, *mecharg, *uuid, *client_url;
1679
apr_array_header_t *caplist;
1681
svn_boolean_t success;
1682
svn_ra_svn_item_t *item, *first;
1684
b.tunnel = params->tunnel;
1685
b.tunnel_user = get_tunnel_user(params, pool);
1686
b.read_only = params->read_only;
1688
b.cfg = NULL; /* Ugly; can drop when we remove v1 support. */
1689
b.pwdb = NULL; /* Likewise */
1692
/* Send greeting. When we drop support for version 1, we can
1693
* start sending an empty mechlist. */
1694
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(nn(!", "success",
1695
(apr_uint64_t) 1, (apr_uint64_t) 2));
1696
SVN_ERR(send_mechs(conn, pool, &b, READ_ACCESS, FALSE));
1697
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(w))",
1698
SVN_RA_SVN_CAP_EDIT_PIPELINE));
1700
/* Read client response. Because the client response form changed
1701
* between version 1 and version 2, we have to do some of this by
1702
* hand until we punt support for version 1. */
1703
SVN_ERR(svn_ra_svn_read_item(conn, pool, &item));
1704
if (item->kind != SVN_RA_SVN_LIST || item->u.list->nelts < 2)
1705
return SVN_NO_ERROR;
1706
first = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1707
if (first->kind != SVN_RA_SVN_NUMBER)
1708
return SVN_NO_ERROR;
1709
b.protocol_version = (int) first->u.number;
1710
if (b.protocol_version == 1)
1712
/* Version 1: auth exchange is mixed with client version and
1713
* capability list, and happens before the client URL is received. */
1714
SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "nw(?c)l",
1715
&ver, &mech, &mecharg, &caplist));
1716
SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
1717
SVN_ERR(auth(conn, pool, mech, mecharg, &b, READ_ACCESS, FALSE,
1720
return svn_ra_svn_flush(conn, pool);
1721
SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "c", &client_url));
1722
client_url = svn_path_canonicalize(client_url, pool);
1723
err = find_repos(client_url, params->root, &b, pool);
1724
if (!err && current_access(&b) == NO_ACCESS)
1725
err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
1726
"Not authorized for access");
1729
io_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
1730
svn_error_clear(err);
1732
return svn_ra_svn_flush(conn, pool);
1735
else if (b.protocol_version == 2)
1737
/* Version 2: client sends version, capability list, and client
1738
* URL, and then we do an auth request. */
1739
SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "nlc", &ver,
1740
&caplist, &client_url));
1741
client_url = svn_path_canonicalize(client_url, pool);
1742
SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
1743
err = find_repos(client_url, params->root, &b, pool);
1746
SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
1747
if (current_access(&b) == NO_ACCESS)
1748
err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
1749
"Not authorized for access");
1753
io_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
1754
svn_error_clear(err);
1756
return svn_ra_svn_flush(conn, pool);
1760
return SVN_NO_ERROR;
1762
SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
1763
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "cc", uuid, b.repos_url));
1765
return svn_ra_svn_handle_commands(conn, pool, main_commands, &b);