Mark many merge tests as skip-against-old-server.
[svn.git] / subversion / svnserve / serve.c
blobcde8fa312bc59f826257c78a3e7d0a7b01c70592
1 /*
2 * serve.c : Functions for serving the Subversion protocol
4 * ====================================================================
5 * Copyright (c) 2000-2007 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 * ====================================================================
22 #include <limits.h> /* for UINT_MAX */
24 #define APR_WANT_STRFUNC
25 #include <apr_want.h>
26 #include <apr_general.h>
27 #include <apr_strings.h>
28 #include <apr_md5.h>
30 #include "svn_compat.h"
31 #include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */
32 #include "svn_types.h"
33 #include "svn_string.h"
34 #include "svn_pools.h"
35 #include "svn_error.h"
36 #include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
37 #include "svn_ra_svn.h"
38 #include "svn_repos.h"
39 #include "svn_path.h"
40 #include "svn_time.h"
41 #include "svn_md5.h"
42 #include "svn_config.h"
43 #include "svn_props.h"
44 #include "svn_mergeinfo.h"
45 #include "svn_user.h"
47 #include "private/svn_mergeinfo_private.h"
49 #include "server.h"
51 typedef struct {
52 apr_pool_t *pool;
53 svn_revnum_t *new_rev;
54 const char **date;
55 const char **author;
56 const char **post_commit_err;
57 } commit_callback_baton_t;
59 typedef struct {
60 server_baton_t *sb;
61 const char *repos_url; /* Decoded repository URL. */
62 void *report_baton;
63 svn_error_t *err;
64 } report_driver_baton_t;
66 typedef struct {
67 const char *fs_path;
68 svn_ra_svn_conn_t *conn;
69 int stack_depth;
70 } log_baton_t;
72 typedef struct {
73 svn_ra_svn_conn_t *conn;
74 apr_pool_t *pool; /* Pool provided in the handler call. */
75 } file_revs_baton_t;
77 svn_error_t *load_configs(svn_config_t **cfg,
78 svn_config_t **pwdb,
79 svn_authz_t **authzdb,
80 const char *filename,
81 svn_boolean_t must_exist,
82 const char *base,
83 apr_pool_t *pool)
85 const char *pwdb_path, *authzdb_path;
86 svn_error_t *err;
88 SVN_ERR(svn_config_read(cfg, filename, must_exist, pool));
90 svn_config_get(*cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL,
91 SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
93 *pwdb = NULL;
94 if (pwdb_path)
96 pwdb_path = svn_path_join(base, pwdb_path, pool);
98 /* Because it may be possible to read the pwdb file with some
99 * access methods and not others, ignore errors reading the pwdb
100 * file and just don't present password authentication as an
101 * option. TODO: Log a warning in this case, when we have a way
102 * of doing logging. */
103 err = svn_config_read(pwdb, pwdb_path, TRUE, pool);
104 if (err && err->apr_err == SVN_ERR_BAD_FILENAME)
105 svn_error_clear(err);
106 else if (err)
107 return err;
110 /* Read authz configuration. */
111 svn_config_get(*cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
112 SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
113 if (authzdb_path)
115 authzdb_path = svn_path_join(base, authzdb_path, pool);
116 SVN_ERR(svn_repos_authz_read(authzdb, authzdb_path, TRUE, pool));
118 else
120 *authzdb = NULL;
123 return SVN_NO_ERROR;
126 /* Set *FS_PATH to the portion of URL that is the path within the
127 repository, if URL is inside REPOS_URL (if URL is not inside
128 REPOS_URL, then error, with the effect on *FS_PATH undefined).
130 If the resultant fs path would be the empty string (i.e., URL and
131 REPOS_URL are the same), then set *FS_PATH to "/".
133 Assume that REPOS_URL and URL are already URI-decoded. */
134 static svn_error_t *get_fs_path(const char *repos_url, const char *url,
135 const char **fs_path)
137 apr_size_t len;
139 len = strlen(repos_url);
140 if (strncmp(url, repos_url, len) != 0)
141 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
142 "'%s' is not the same repository as '%s'",
143 url, repos_url);
144 *fs_path = url + len;
145 if (! **fs_path)
146 *fs_path = "/";
148 return SVN_NO_ERROR;
151 /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
153 /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
154 the user described in BATON according to the authz rules in BATON.
155 Use POOL for temporary allocations only. If no authz rules are
156 present in BATON, grant access by default. */
157 static svn_error_t *authz_check_access(svn_boolean_t *allowed,
158 const char *path,
159 svn_repos_authz_access_t required,
160 server_baton_t *b,
161 apr_pool_t *pool)
163 /* If authz cannot be performed, grant access. This is NOT the same
164 as the default policy when authz is performed on a path with no
165 rules. In the latter case, the default is to deny access, and is
166 set by svn_repos_authz_check_access. */
167 if (!b->authzdb)
169 *allowed = TRUE;
170 return SVN_NO_ERROR;
173 /* If the authz request is for the empty path (ie. ""), replace it
174 with the root path. This happens because of stripping done at
175 various levels in svnserve that remove the leading / on an
176 absolute path. Passing such a malformed path to the authz
177 routines throws them into an infinite loop and makes them miss
178 ACLs. */
179 if (path && *path != '/')
180 path = svn_path_join("/", path, pool);
182 return svn_repos_authz_check_access(b->authzdb, b->authz_repos_name,
183 path, b->user, required,
184 allowed, pool);
187 /* Set *ALLOWED to TRUE if PATH is readable by the user described in
188 * BATON. Use POOL for temporary allocations only. ROOT is not used.
189 * Implements the svn_repos_authz_func_t interface.
191 static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed,
192 svn_fs_root_t *root,
193 const char *path,
194 void *baton,
195 apr_pool_t *pool)
197 server_baton_t *sb = baton;
199 return authz_check_access(allowed, path, svn_authz_read, sb, pool);
202 /* If authz is enabled in the specified BATON, return a read authorization
203 function. Otherwise, return NULL. */
204 static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton)
206 if (baton->authzdb)
207 return authz_check_access_cb;
208 return NULL;
211 /* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
212 * according to the state in BATON. Use POOL for temporary
213 * allocations only. ROOT is not used. Implements the
214 * svn_repos_authz_callback_t interface.
216 static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required,
217 svn_boolean_t *allowed,
218 svn_fs_root_t *root,
219 const char *path,
220 void *baton,
221 apr_pool_t *pool)
223 server_baton_t *sb = baton;
225 return authz_check_access(allowed, path, required, sb, pool);
229 enum access_type get_access(server_baton_t *b, enum authn_type auth)
231 const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS :
232 SVN_CONFIG_OPTION_ANON_ACCESS;
233 const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read";
234 enum access_type result;
236 svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def);
237 result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
238 strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
239 return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result;
242 static enum access_type current_access(server_baton_t *b)
244 return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED);
247 /* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME
248 is true, don't send anonymous mech even if that would give the desired
249 access. */
250 static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
251 server_baton_t *b, enum access_type required,
252 svn_boolean_t needs_username)
254 if (!needs_username && get_access(b, UNAUTHENTICATED) >= required)
255 SVN_ERR(svn_ra_svn_write_word(conn, pool, "ANONYMOUS"));
256 if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required)
257 SVN_ERR(svn_ra_svn_write_word(conn, pool, "EXTERNAL"));
258 if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
259 SVN_ERR(svn_ra_svn_write_word(conn, pool, "CRAM-MD5"));
260 return SVN_NO_ERROR;
263 /* Context for cleanup handler. */
264 struct cleanup_fs_access_baton
266 svn_fs_t *fs;
267 apr_pool_t *pool;
270 /* Pool cleanup handler. Make sure fs's access_t points to NULL when
271 the command pool is destroyed. */
272 static apr_status_t cleanup_fs_access(void *data)
274 svn_error_t *serr;
275 struct cleanup_fs_access_baton *baton = data;
277 serr = svn_fs_set_access(baton->fs, NULL);
278 if (serr)
280 apr_status_t apr_err = serr->apr_err;
281 svn_error_clear(serr);
282 return apr_err;
285 return APR_SUCCESS;
289 /* Create an svn_fs_access_t in POOL for USER and associate it with
290 B's filesystem. Also, register a cleanup handler with POOL which
291 de-associates the svn_fs_access_t from B's filesystem. */
292 static svn_error_t *
293 create_fs_access(server_baton_t *b, apr_pool_t *pool)
295 svn_fs_access_t *fs_access;
296 struct cleanup_fs_access_baton *cleanup_baton;
298 if (!b->user)
299 return SVN_NO_ERROR;
301 SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool));
302 SVN_ERR(svn_fs_set_access(b->fs, fs_access));
304 cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
305 cleanup_baton->pool = pool;
306 cleanup_baton->fs = b->fs;
307 apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
308 apr_pool_cleanup_null);
310 return SVN_NO_ERROR;
313 /* Authenticate, once the client has chosen a mechanism and possibly
314 * sent an initial mechanism token. On success, set *success to true
315 * and b->user to the authenticated username (or NULL for anonymous).
316 * On authentication failure, report failure to the client and set
317 * *success to FALSE. On communications failure, return an error.
318 * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
319 static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
320 const char *mech, const char *mecharg,
321 server_baton_t *b, enum access_type required,
322 svn_boolean_t needs_username,
323 svn_boolean_t *success)
325 const char *user;
326 *success = FALSE;
328 if (get_access(b, AUTHENTICATED) >= required
329 && b->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
331 if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0)
332 return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
333 "Requested username does not match");
334 b->user = b->tunnel_user;
335 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
336 *success = TRUE;
337 return SVN_NO_ERROR;
340 if (get_access(b, UNAUTHENTICATED) >= required
341 && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
343 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
344 *success = TRUE;
345 return SVN_NO_ERROR;
348 if (get_access(b, AUTHENTICATED) >= required
349 && b->pwdb && strcmp(mech, "CRAM-MD5") == 0)
351 SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success));
352 b->user = apr_pstrdup(b->pool, user);
353 return SVN_NO_ERROR;
356 return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
357 "Must authenticate with listed mechanism");
360 /* Perform an authentication request using the built-in SASL implementation. */
361 static svn_error_t *
362 internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
363 server_baton_t *b, enum access_type required,
364 svn_boolean_t needs_username)
366 svn_boolean_t success;
367 const char *mech, *mecharg;
369 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
370 SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
371 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)c)", b->realm));
374 SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
375 if (!*mech)
376 break;
377 SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username,
378 &success));
380 while (!success);
381 return SVN_NO_ERROR;
384 /* Perform an authentication request in order to get an access level of
385 * REQUIRED or higher. Since the client may escape the authentication
386 * exchange, the caller should check current_access(b) to see if
387 * authentication succeeded. */
388 static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
389 server_baton_t *b, enum access_type required,
390 svn_boolean_t needs_username)
392 #ifdef SVN_HAVE_SASL
393 if (b->use_sasl)
394 return cyrus_auth_request(conn, pool, b, required, needs_username);
395 #endif
397 return internal_auth_request(conn, pool, b, required, needs_username);
400 /* Send a trivial auth notification on CONN which lists no mechanisms,
401 * indicating that authentication is unnecessary. Usually called in
402 * response to invocation of a svnserve command.
404 static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
405 apr_pool_t *pool, server_baton_t *b)
407 return svn_ra_svn_write_cmd_response(conn, pool, "()c", "");
410 /* Ensure that the client has the REQUIRED access by checking the
411 * access directives (both blanket and per-directory) in BATON. If
412 * PATH is NULL, then only the blanket access configuration will
413 * impact the result.
415 * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the
416 * user described in BATON is authenticated and, well, has a username
417 * assigned to him.
419 * Use POOL for temporary allocations only.
421 static svn_boolean_t lookup_access(apr_pool_t *pool,
422 server_baton_t *baton,
423 svn_repos_authz_access_t required,
424 const char *path,
425 svn_boolean_t needs_username)
427 enum access_type req = (required & svn_authz_write) ?
428 WRITE_ACCESS : READ_ACCESS;
429 svn_boolean_t authorized;
430 svn_error_t *err;
432 /* Get authz's opinion on the access. */
433 err = authz_check_access(&authorized, path, required, baton, pool);
435 /* If an error made lookup fail, deny access. ### TODO: Once
436 logging is implemented, this is a perfect place to log the
437 problem. */
438 if (err)
440 svn_error_clear(err);
441 return FALSE;
444 /* If the required access is blanket-granted AND granted by authz
445 AND we already have a username if one is required, then the
446 lookup has succeeded. */
447 if (current_access(baton) >= req
448 && authorized
449 && (! needs_username || baton->user))
450 return TRUE;
452 return FALSE;
455 /* Check that the client has the REQUIRED access by consulting the
456 * authentication and authorization states stored in BATON. If the
457 * client does not have the required access credentials, attempt to
458 * authenticate the client to get that access, using CONN for
459 * communication.
461 * This function is supposed to be called to handle the authentication
462 * half of a standard svn protocol reply. If an error is returned, it
463 * probably means that the server can terminate the client connection
464 * with an apologetic error, as it implies an authentication failure.
466 * PATH and NEEDS_USERNAME are passed along to lookup_access, their
467 * behaviour is documented there.
469 static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn,
470 apr_pool_t *pool,
471 server_baton_t *b,
472 svn_repos_authz_access_t required,
473 const char *path,
474 svn_boolean_t needs_username)
476 enum access_type req = (required & svn_authz_write) ?
477 WRITE_ACCESS : READ_ACCESS;
479 /* See whether the user already has the required access. If so,
480 nothing needs to be done. Create the FS access and send a
481 trivial auth request. */
482 if (lookup_access(pool, b, required, path, needs_username))
484 SVN_ERR(create_fs_access(b, pool));
485 return trivial_auth_request(conn, pool, b);
488 /* If the required blanket access can be obtained by authenticating,
489 try that. Unfortunately, we can't tell until after
490 authentication whether authz will work or not. We force
491 requiring a username because we need one to be able to check
492 authz configuration again with a different user credentials than
493 the first time round. */
494 if (b->user == NULL
495 && get_access(b, AUTHENTICATED) >= req
496 && (b->tunnel_user || b->pwdb || b->use_sasl))
497 SVN_ERR(auth_request(conn, pool, b, req, TRUE));
499 /* Now that an authentication has been done get the new take of
500 authz on the request. */
501 if (! lookup_access(pool, b, required, path, needs_username))
502 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
503 svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED,
504 NULL, NULL), NULL);
506 /* Else, access is granted, and there is much rejoicing. */
507 SVN_ERR(create_fs_access(b, pool));
509 return SVN_NO_ERROR;
512 /* --- REPORTER COMMAND SET --- */
514 /* To allow for pipelining, reporter commands have no reponses. If we
515 * get an error, we ignore all subsequent reporter commands and return
516 * the error finish_report, to be handled by the calling command.
519 static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
520 apr_array_header_t *params, void *baton)
522 report_driver_baton_t *b = baton;
523 const char *path, *lock_token, *depth_word;
524 svn_revnum_t rev;
525 /* Default to infinity, for old clients that don't send depth. */
526 svn_depth_t depth = svn_depth_infinity;
527 svn_boolean_t start_empty;
529 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crb?(?c)?w",
530 &path, &rev, &start_empty, &lock_token,
531 &depth_word));
532 if (depth_word)
533 depth = svn_depth_from_word(depth_word);
534 path = svn_path_canonicalize(path, pool);
535 if (!b->err)
536 b->err = svn_repos_set_path3(b->report_baton, path, rev, depth,
537 start_empty, lock_token, pool);
538 return SVN_NO_ERROR;
541 static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
542 apr_array_header_t *params, void *baton)
544 report_driver_baton_t *b = baton;
545 const char *path;
547 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
548 path = svn_path_canonicalize(path, pool);
549 if (!b->err)
550 b->err = svn_repos_delete_path(b->report_baton, path, pool);
551 return SVN_NO_ERROR;
554 static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
555 apr_array_header_t *params, void *baton)
557 report_driver_baton_t *b = baton;
558 const char *path, *url, *lock_token, *fs_path, *depth_word;
559 svn_revnum_t rev;
560 svn_boolean_t start_empty;
561 /* Default to infinity, for old clients that don't send depth. */
562 svn_depth_t depth = svn_depth_infinity;
564 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccrb?(?c)?w",
565 &path, &url, &rev, &start_empty,
566 &lock_token, &depth_word));
567 path = svn_path_canonicalize(path, pool);
568 url = svn_path_uri_decode(svn_path_canonicalize(url, pool), pool);
569 if (depth_word)
570 depth = svn_depth_from_word(depth_word);
571 if (!b->err)
572 b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool),
573 url, &fs_path);
574 if (!b->err)
575 b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev,
576 depth, start_empty, lock_token, pool);
577 return SVN_NO_ERROR;
580 static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
581 apr_array_header_t *params, void *baton)
583 report_driver_baton_t *b = baton;
585 /* No arguments to parse. */
586 SVN_ERR(trivial_auth_request(conn, pool, b->sb));
587 if (!b->err)
588 b->err = svn_repos_finish_report(b->report_baton, pool);
589 return SVN_NO_ERROR;
592 static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
593 apr_array_header_t *params, void *baton)
595 report_driver_baton_t *b = baton;
597 /* No arguments to parse. */
598 svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
599 return SVN_NO_ERROR;
602 static const svn_ra_svn_cmd_entry_t report_commands[] = {
603 { "set-path", set_path },
604 { "delete-path", delete_path },
605 { "link-path", link_path },
606 { "finish-report", finish_report, TRUE },
607 { "abort-report", abort_report, TRUE },
608 { NULL }
611 /* Accept a report from the client, drive the network editor with the
612 * result, and then write an empty command response. If there is a
613 * non-protocol failure, accept_report will abort the edit and return
614 * a command error to be reported by handle_commands(). */
615 static svn_error_t *accept_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
616 server_baton_t *b, svn_revnum_t rev,
617 const char *target, const char *tgt_path,
618 svn_boolean_t text_deltas,
619 svn_depth_t depth,
620 svn_boolean_t send_copyfrom_args,
621 svn_boolean_t ignore_ancestry)
623 const svn_delta_editor_t *editor;
624 void *edit_baton, *report_baton;
625 report_driver_baton_t rb;
626 svn_error_t *err;
628 /* Make an svn_repos report baton. Tell it to drive the network editor
629 * when the report is complete. */
630 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
631 SVN_CMD_ERR(svn_repos_begin_report2(&report_baton, rev, b->repos,
632 b->fs_path->data, target, tgt_path,
633 text_deltas, depth, ignore_ancestry,
634 send_copyfrom_args,
635 editor, edit_baton,
636 authz_check_access_cb_func(b),
637 b, pool));
639 rb.sb = b;
640 rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
641 rb.report_baton = report_baton;
642 rb.err = NULL;
643 err = svn_ra_svn_handle_commands(conn, pool, report_commands, &rb);
644 if (err)
646 /* Network or protocol error while handling commands. */
647 svn_error_clear(rb.err);
648 return err;
650 else if (rb.err)
652 /* Some failure during the reporting or editing operations. */
653 svn_error_clear(editor->abort_edit(edit_baton, pool));
654 SVN_CMD_ERR(rb.err);
656 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
657 return SVN_NO_ERROR;
660 /* --- MAIN COMMAND SET --- */
662 /* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t
663 * values. */
664 static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
665 apr_pool_t *pool,
666 apr_array_header_t *propdiffs)
668 int i;
670 for (i = 0; i < propdiffs->nelts; ++i)
672 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
674 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "c(?s)",
675 prop->name, prop->value));
678 return SVN_NO_ERROR;
681 /* Write out a lock to the client. */
682 static svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
683 apr_pool_t *pool,
684 svn_lock_t *lock)
686 const char *cdate, *edate;
688 cdate = svn_time_to_cstring(lock->creation_date, pool);
689 edate = lock->expiration_date
690 ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
691 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
692 lock->token, lock->owner, lock->comment,
693 cdate, edate));
695 return SVN_NO_ERROR;
698 static const char *kind_word(svn_node_kind_t kind)
700 switch (kind)
702 case svn_node_none:
703 return "none";
704 case svn_node_file:
705 return "file";
706 case svn_node_dir:
707 return "dir";
708 case svn_node_unknown:
709 return "unknown";
710 default:
711 abort();
714 /* Make the compiler happy */
715 return NULL;
718 /* ### This really belongs in libsvn_repos. */
719 /* Get the properties for a path, with hardcoded committed-info values. */
720 static svn_error_t *get_props(apr_hash_t **props, svn_fs_root_t *root,
721 const char *path, apr_pool_t *pool)
723 svn_string_t *str;
724 svn_revnum_t crev;
725 const char *cdate, *cauthor, *uuid;
727 /* Get the properties. */
728 SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
730 /* Hardcode the values for the committed revision, date, and author. */
731 SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
732 path, pool));
733 str = svn_string_create(apr_psprintf(pool, "%ld", crev),
734 pool);
735 apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING, str);
736 str = (cdate) ? svn_string_create(cdate, pool) : NULL;
737 apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING,
738 str);
739 str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
740 apr_hash_set(*props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING, str);
742 /* Hardcode the values for the UUID. */
743 SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
744 str = (uuid) ? svn_string_create(uuid, pool) : NULL;
745 apr_hash_set(*props, SVN_PROP_ENTRY_UUID, APR_HASH_KEY_STRING, str);
747 return SVN_NO_ERROR;
750 /* Set BATON->FS_PATH for the repository URL found in PARAMS. */
751 static svn_error_t *reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
752 apr_array_header_t *params, void *baton)
754 server_baton_t *b = baton;
755 const char *url;
756 const char *fs_path;
758 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &url));
759 url = svn_path_uri_decode(svn_path_canonicalize(url, pool), pool);
760 SVN_ERR(trivial_auth_request(conn, pool, b));
761 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
762 url, &fs_path));
763 svn_stringbuf_set(b->fs_path, fs_path);
764 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
765 return SVN_NO_ERROR;
768 static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
769 apr_array_header_t *params, void *baton)
771 server_baton_t *b = baton;
772 svn_revnum_t rev;
774 SVN_ERR(trivial_auth_request(conn, pool, b));
775 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
776 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev));
777 return SVN_NO_ERROR;
780 static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
781 apr_array_header_t *params, void *baton)
783 server_baton_t *b = baton;
784 svn_revnum_t rev;
785 apr_time_t tm;
786 const char *timestr;
788 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &timestr));
789 SVN_ERR(trivial_auth_request(conn, pool, b));
790 SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
791 SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool));
792 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev));
793 return SVN_NO_ERROR;
796 static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
797 apr_array_header_t *params, void *baton)
799 server_baton_t *b = baton;
800 svn_revnum_t rev;
801 const char *name;
802 svn_string_t *value;
804 /* Because the revprop value was at one time mandatory, the usual
805 optional element pattern "(?s)" isn't used. */
806 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc?s", &rev, &name, &value));
807 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
808 SVN_CMD_ERR(svn_repos_fs_change_rev_prop3(b->repos, rev, b->user,
809 name, value, TRUE, TRUE,
810 authz_check_access_cb_func(b), b,
811 pool));
812 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
813 return SVN_NO_ERROR;
816 static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
817 apr_array_header_t *params, void *baton)
819 server_baton_t *b = baton;
820 svn_revnum_t rev;
821 apr_hash_t *props;
823 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev));
824 SVN_ERR(trivial_auth_request(conn, pool, b));
825 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
826 authz_check_access_cb_func(b), b,
827 pool));
828 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
829 SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props));
830 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
831 return SVN_NO_ERROR;
834 static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
835 apr_array_header_t *params, void *baton)
837 server_baton_t *b = baton;
838 svn_revnum_t rev;
839 const char *name;
840 svn_string_t *value;
842 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc", &rev, &name));
843 SVN_ERR(trivial_auth_request(conn, pool, b));
844 SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name,
845 authz_check_access_cb_func(b), b,
846 pool));
847 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "(?s)", value));
848 return SVN_NO_ERROR;
851 static svn_error_t *commit_done(const svn_commit_info_t *commit_info,
852 void *baton, apr_pool_t *pool)
854 commit_callback_baton_t *ccb = baton;
856 *ccb->new_rev = commit_info->revision;
857 *ccb->date = commit_info->date
858 ? apr_pstrdup(ccb->pool, commit_info->date): NULL;
859 *ccb->author = commit_info->author
860 ? apr_pstrdup(ccb->pool, commit_info->author) : NULL;
861 *ccb->post_commit_err = commit_info->post_commit_err
862 ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL;
863 return SVN_NO_ERROR;
866 /* Add the LOCK_TOKENS (if any) to the filesystem access context,
867 * checking path authorizations using the state in SB as we go.
868 * LOCK_TOKENS is an array of svn_ra_svn_item_t structs. Return a
869 * client error if LOCK_TOKENS is not a list of lists. If a lock
870 * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
871 * to the client. Use POOL for temporary allocations only.
873 static svn_error_t *add_lock_tokens(svn_ra_svn_conn_t *conn,
874 apr_array_header_t *lock_tokens,
875 server_baton_t *sb,
876 apr_pool_t *pool)
878 int i;
879 svn_fs_access_t *fs_access;
881 SVN_ERR(svn_fs_get_access(&fs_access, sb->fs));
883 /* If there is no access context, nowhere to add the tokens. */
884 if (! fs_access)
885 return SVN_NO_ERROR;
887 for (i = 0; i < lock_tokens->nelts; ++i)
889 const char *path, *token, *full_path;
890 svn_ra_svn_item_t *path_item, *token_item;
891 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i,
892 svn_ra_svn_item_t);
893 if (item->kind != SVN_RA_SVN_LIST)
894 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
895 "Lock tokens aren't a list of lists");
897 path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
898 if (path_item->kind != SVN_RA_SVN_STRING)
899 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
900 "Lock path isn't a string");
902 token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
903 if (token_item->kind != SVN_RA_SVN_STRING)
904 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
905 "Lock token isn't a string");
907 path = path_item->u.string->data;
908 full_path = svn_path_join(sb->fs_path->data,
909 svn_path_canonicalize(path, pool),
910 pool);
912 if (! lookup_access(pool, sb, svn_authz_write,
913 full_path, TRUE))
914 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED,
915 NULL, NULL);
917 token = token_item->u.string->data;
918 SVN_ERR(svn_fs_access_add_lock_token(fs_access, token));
921 return SVN_NO_ERROR;
924 /* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
925 LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */
926 static svn_error_t *unlock_paths(apr_array_header_t *lock_tokens,
927 server_baton_t *sb,
928 apr_pool_t *pool)
930 int i;
931 apr_pool_t *iterpool;
933 iterpool = svn_pool_create(pool);
935 for (i = 0; i < lock_tokens->nelts; ++i)
937 svn_ra_svn_item_t *item, *path_item, *token_item;
938 const char *path, *token, *full_path;
939 svn_pool_clear(iterpool);
941 item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
942 path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
943 token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
945 path = path_item->u.string->data;
946 token = token_item->u.string->data;
948 full_path = svn_path_join(sb->fs_path->data,
949 svn_path_canonicalize(path, iterpool),
950 iterpool);
952 /* The lock may have become defunct after the commit, so ignore such
953 errors.
955 ### If we ever write a logging facility for svnserve, this
956 would be a good place to log an error before clearing
957 it. */
958 svn_error_clear(svn_repos_fs_unlock(sb->repos, full_path, token,
959 FALSE, pool));
962 svn_pool_destroy(iterpool);
964 return SVN_NO_ERROR;
967 static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
968 apr_array_header_t *params, void *baton)
970 server_baton_t *b = baton;
971 const char *log_msg = NULL,
972 *date = NULL,
973 *author = NULL,
974 *post_commit_err = NULL;
975 apr_array_header_t *lock_tokens;
976 svn_boolean_t keep_locks;
977 apr_array_header_t *revprop_list = NULL;
978 apr_hash_t *revprop_table;
979 const svn_delta_editor_t *editor;
980 void *edit_baton;
981 svn_boolean_t aborted;
982 commit_callback_baton_t ccb;
983 svn_revnum_t new_rev;
985 if (params->nelts == 1)
987 /* Clients before 1.2 don't send lock-tokens, keep-locks,
988 and rev-props fields. */
989 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &log_msg));
990 lock_tokens = NULL;
991 keep_locks = TRUE;
992 revprop_list = NULL;
994 else
996 /* Clients before 1.5 don't send the rev-props field. */
997 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "clb?l", &log_msg,
998 &lock_tokens, &keep_locks,
999 &revprop_list));
1002 /* The handling for locks is a little problematic, because the
1003 protocol won't let us send several auth requests once one has
1004 succeeded. So we request write access and a username before
1005 adding tokens (if we have any), and subsequently fail if a lock
1006 violates authz. */
1007 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
1008 NULL,
1009 (lock_tokens && lock_tokens->nelts) ? TRUE : FALSE));
1011 /* Authorize the lock tokens and give them to the FS if we got
1012 any. */
1013 if (lock_tokens && lock_tokens->nelts)
1014 SVN_CMD_ERR(add_lock_tokens(conn, lock_tokens, b, pool));
1016 if (revprop_list)
1017 SVN_ERR(svn_ra_svn_parse_proplist(revprop_list, pool, &revprop_table));
1018 else
1020 revprop_table = apr_hash_make(pool);
1021 apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
1022 svn_string_create(log_msg, pool));
1025 /* Get author from the baton, making sure clients can't circumvent
1026 the authentication via the revision props. */
1027 apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
1028 b->user ? svn_string_create(b->user, pool) : NULL);
1030 ccb.pool = pool;
1031 ccb.new_rev = &new_rev;
1032 ccb.date = &date;
1033 ccb.author = &author;
1034 ccb.post_commit_err = &post_commit_err;
1035 /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
1036 SVN_CMD_ERR(svn_repos_get_commit_editor5
1037 (&editor, &edit_baton, b->repos, NULL,
1038 svn_path_uri_decode(b->repos_url, pool),
1039 b->fs_path->data, revprop_table,
1040 commit_done, &ccb,
1041 authz_commit_cb, baton, pool));
1042 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1043 SVN_ERR(svn_ra_svn_drive_editor(conn, pool, editor, edit_baton, &aborted));
1044 if (!aborted)
1046 SVN_ERR(trivial_auth_request(conn, pool, b));
1048 /* In tunnel mode, deltify before answering the client, because
1049 answering may cause the client to terminate the connection
1050 and thus kill the server. But otherwise, deltify after
1051 answering the client, to avoid user-visible delay. */
1053 if (b->tunnel)
1054 SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1056 /* Unlock the paths. */
1057 if (! keep_locks && lock_tokens && lock_tokens->nelts)
1058 SVN_ERR(unlock_paths(lock_tokens, b, pool));
1060 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(?c)(?c)(?c)",
1061 new_rev, date, author, post_commit_err));
1063 if (! b->tunnel)
1064 SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1066 return SVN_NO_ERROR;
1069 static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1070 apr_array_header_t *params, void *baton)
1072 server_baton_t *b = baton;
1073 const char *path, *full_path, *hex_digest;
1074 svn_revnum_t rev;
1075 svn_fs_root_t *root;
1076 svn_stream_t *contents;
1077 apr_hash_t *props = NULL;
1078 svn_string_t write_str;
1079 char buf[4096];
1080 apr_size_t len;
1081 svn_boolean_t want_props, want_contents;
1082 unsigned char digest[APR_MD5_DIGESTSIZE];
1083 svn_error_t *err, *write_err;
1085 /* Parse arguments. */
1086 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev,
1087 &want_props, &want_contents));
1089 full_path = svn_path_join(b->fs_path->data,
1090 svn_path_canonicalize(path, pool), pool);
1092 /* Check authorizations */
1093 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1094 full_path, FALSE));
1096 if (!SVN_IS_VALID_REVNUM(rev))
1097 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1099 /* Fetch the properties and a stream for the contents. */
1100 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1101 SVN_CMD_ERR(svn_fs_file_md5_checksum(digest, root, full_path, pool));
1102 hex_digest = svn_md5_digest_to_cstring_display(digest, pool);
1103 if (want_props)
1104 SVN_CMD_ERR(get_props(&props, root, full_path, pool));
1105 if (want_contents)
1106 SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
1108 /* Send successful command response with revision and props. */
1109 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)r(!", "success",
1110 hex_digest, rev));
1111 SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props));
1112 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1114 /* Now send the file's contents. */
1115 if (want_contents)
1117 err = SVN_NO_ERROR;
1118 while (1)
1120 len = sizeof(buf);
1121 err = svn_stream_read(contents, buf, &len);
1122 if (err)
1123 break;
1124 if (len > 0)
1126 write_str.data = buf;
1127 write_str.len = len;
1128 SVN_ERR(svn_ra_svn_write_string(conn, pool, &write_str));
1130 if (len < sizeof(buf))
1132 err = svn_stream_close(contents);
1133 break;
1136 write_err = svn_ra_svn_write_cstring(conn, pool, "");
1137 if (write_err)
1139 svn_error_clear(err);
1140 return write_err;
1142 SVN_CMD_ERR(err);
1143 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1146 return SVN_NO_ERROR;
1149 static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1150 apr_array_header_t *params, void *baton)
1152 server_baton_t *b = baton;
1153 const char *path, *full_path, *file_path, *name, *cauthor, *cdate;
1154 svn_revnum_t rev;
1155 apr_hash_t *entries, *props = NULL, *file_props;
1156 apr_hash_index_t *hi;
1157 svn_fs_dirent_t *fsent;
1158 svn_dirent_t *entry;
1159 const void *key;
1160 void *val;
1161 svn_fs_root_t *root;
1162 apr_pool_t *subpool;
1163 svn_boolean_t want_props, want_contents;
1164 apr_uint64_t dirent_fields;
1165 apr_array_header_t *dirent_fields_list = NULL;
1166 svn_ra_svn_item_t *elt;
1167 int i;
1169 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb?l", &path, &rev,
1170 &want_props, &want_contents,
1171 &dirent_fields_list));
1173 if (! dirent_fields_list)
1175 dirent_fields = SVN_DIRENT_ALL;
1177 else
1179 dirent_fields = 0;
1181 for (i = 0; i < dirent_fields_list->nelts; ++i)
1183 elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t);
1185 if (elt->kind != SVN_RA_SVN_WORD)
1186 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1187 "Dirent field not a string");
1189 if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0)
1190 dirent_fields |= SVN_DIRENT_KIND;
1191 else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0)
1192 dirent_fields |= SVN_DIRENT_SIZE;
1193 else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0)
1194 dirent_fields |= SVN_DIRENT_HAS_PROPS;
1195 else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0)
1196 dirent_fields |= SVN_DIRENT_CREATED_REV;
1197 else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0)
1198 dirent_fields |= SVN_DIRENT_TIME;
1199 else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0)
1200 dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
1204 full_path = svn_path_join(b->fs_path->data,
1205 svn_path_canonicalize(path, pool), pool);
1207 /* Check authorizations */
1208 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1209 full_path, FALSE));
1211 if (!SVN_IS_VALID_REVNUM(rev))
1212 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1214 /* Fetch the root of the appropriate revision. */
1215 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1217 /* Fetch the directory properties if requested. */
1218 if (want_props)
1219 SVN_CMD_ERR(get_props(&props, root, full_path, pool));
1221 /* Fetch the directory entries if requested. */
1222 if (want_contents)
1224 SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
1226 /* Transform the hash table's FS entries into dirents. This probably
1227 * belongs in libsvn_repos. */
1228 subpool = svn_pool_create(pool);
1229 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1231 apr_hash_this(hi, &key, NULL, &val);
1232 name = key;
1233 fsent = val;
1235 svn_pool_clear(subpool);
1237 file_path = svn_path_join(full_path, name, subpool);
1238 entry = apr_pcalloc(pool, sizeof(*entry));
1240 if (dirent_fields & SVN_DIRENT_KIND)
1242 /* kind */
1243 entry->kind = fsent->kind;
1246 if (dirent_fields & SVN_DIRENT_SIZE)
1248 /* size */
1249 if (entry->kind == svn_node_dir)
1250 entry->size = 0;
1251 else
1252 SVN_CMD_ERR(svn_fs_file_length(&entry->size, root, file_path,
1253 subpool));
1256 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1258 /* has_props */
1259 SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
1260 subpool));
1261 entry->has_props = (apr_hash_count(file_props) > 0) ? TRUE
1262 : FALSE;
1265 if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1266 || (dirent_fields & SVN_DIRENT_TIME)
1267 || (dirent_fields & SVN_DIRENT_CREATED_REV))
1269 /* created_rev, last_author, time */
1270 SVN_CMD_ERR(svn_repos_get_committed_info(&entry->created_rev,
1271 &cdate,
1272 &cauthor, root,
1273 file_path,
1274 subpool));
1275 entry->last_author = apr_pstrdup(pool, cauthor);
1276 if (cdate)
1277 SVN_CMD_ERR(svn_time_from_cstring(&entry->time, cdate,
1278 subpool));
1279 else
1280 entry->time = (time_t) -1;
1283 /* Store the entry. */
1284 apr_hash_set(entries, name, APR_HASH_KEY_STRING, entry);
1286 svn_pool_destroy(subpool);
1289 /* Write out response. */
1290 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(r(!", "success", rev));
1291 SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props));
1292 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(!"));
1293 if (want_contents)
1295 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1297 apr_hash_this(hi, &key, NULL, &val);
1298 name = key;
1299 entry = val;
1300 cdate = (entry->time == (time_t) -1) ? NULL
1301 : svn_time_to_cstring(entry->time, pool);
1302 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
1303 kind_word(entry->kind),
1304 (apr_uint64_t) entry->size,
1305 entry->has_props, entry->created_rev,
1306 cdate, entry->last_author));
1309 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1310 return SVN_NO_ERROR;
1314 static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1315 apr_array_header_t *params, void *baton)
1317 server_baton_t *b = baton;
1318 svn_revnum_t rev;
1319 const char *target, *full_path, *depth_word;
1320 svn_boolean_t recurse;
1321 svn_boolean_t send_copyfrom_args;
1322 apr_uint64_t send_copyfrom_param;
1323 /* Default to unknown. Old clients won't send depth, but we'll
1324 handle that by converting recurse if necessary. */
1325 svn_depth_t depth = svn_depth_unknown;
1327 /* Parse the arguments. */
1328 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cb?wB", &rev, &target,
1329 &recurse, &depth_word, &send_copyfrom_param));
1330 target = svn_path_canonicalize(target, pool);
1332 if (depth_word)
1333 depth = svn_depth_from_word(depth_word);
1334 else
1335 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1337 send_copyfrom_args = (send_copyfrom_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) ?
1338 FALSE : (svn_boolean_t) send_copyfrom_param;
1340 full_path = svn_path_join(b->fs_path->data, target, pool);
1341 /* Check authorization and authenticate the user if necessary. */
1342 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
1344 if (!SVN_IS_VALID_REVNUM(rev))
1345 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1347 return accept_report(conn, pool, b, rev, target, NULL, TRUE,
1348 depth, send_copyfrom_args, FALSE);
1351 static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1352 apr_array_header_t *params, void *baton)
1354 server_baton_t *b = baton;
1355 svn_revnum_t rev;
1356 const char *target, *depth_word;
1357 const char *switch_url, *switch_path;
1358 svn_boolean_t recurse;
1359 /* Default to unknown. Old clients won't send depth, but we'll
1360 handle that by converting recurse if necessary. */
1361 svn_depth_t depth = svn_depth_unknown;
1363 /* Parse the arguments. */
1364 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbc?w", &rev, &target,
1365 &recurse, &switch_url, &depth_word));
1366 target = svn_path_canonicalize(target, pool);
1367 switch_url = svn_path_canonicalize(switch_url, pool);
1369 if (depth_word)
1370 depth = svn_depth_from_word(depth_word);
1371 else
1372 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1374 SVN_ERR(trivial_auth_request(conn, pool, b));
1375 if (!SVN_IS_VALID_REVNUM(rev))
1376 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1377 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1378 svn_path_uri_decode(switch_url, pool),
1379 &switch_path));
1381 return accept_report(conn, pool, b, rev, target, switch_path, TRUE,
1382 depth,
1383 FALSE /* TODO(sussman): no copyfrom args for now */,
1384 TRUE);
1387 static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1388 apr_array_header_t *params, void *baton)
1390 server_baton_t *b = baton;
1391 svn_revnum_t rev;
1392 const char *target, *depth_word;
1393 svn_boolean_t recurse;
1394 /* Default to unknown. Old clients won't send depth, but we'll
1395 handle that by converting recurse if necessary. */
1396 svn_depth_t depth = svn_depth_unknown;
1398 /* Parse the arguments. */
1399 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cb?(?r)?w",
1400 &target, &recurse, &rev, &depth_word));
1401 target = svn_path_canonicalize(target, pool);
1403 if (depth_word)
1404 depth = svn_depth_from_word(depth_word);
1405 else
1406 depth = recurse ? svn_depth_infinity : svn_depth_empty;
1408 SVN_ERR(trivial_auth_request(conn, pool, b));
1409 if (!SVN_IS_VALID_REVNUM(rev))
1410 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1412 return accept_report(conn, pool, b, rev, target, NULL, FALSE,
1413 depth, FALSE, FALSE);
1416 static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1417 apr_array_header_t *params, void *baton)
1419 server_baton_t *b = baton;
1420 svn_revnum_t rev;
1421 const char *target, *versus_url, *versus_path, *depth_word;
1422 svn_boolean_t recurse, ignore_ancestry;
1423 svn_boolean_t text_deltas;
1424 /* Default to unknown. Old clients won't send depth, but we'll
1425 handle that by converting recurse if necessary. */
1426 svn_depth_t depth = svn_depth_unknown;
1428 /* Parse the arguments. */
1429 if (params->nelts == 5)
1431 /* Clients before 1.4 don't send the text_deltas boolean or depth. */
1432 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
1433 &recurse, &ignore_ancestry, &versus_url));
1434 text_deltas = TRUE;
1435 depth_word = NULL;
1437 else
1439 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbcb?w",
1440 &rev, &target, &recurse,
1441 &ignore_ancestry, &versus_url,
1442 &text_deltas, &depth_word));
1444 target = svn_path_canonicalize(target, pool);
1445 versus_url = svn_path_canonicalize(versus_url, pool);
1447 if (depth_word)
1448 depth = svn_depth_from_word(depth_word);
1449 else
1450 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1452 SVN_ERR(trivial_auth_request(conn, pool, b));
1454 if (!SVN_IS_VALID_REVNUM(rev))
1455 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1456 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1457 svn_path_uri_decode(versus_url, pool),
1458 &versus_path));
1460 return accept_report(conn, pool, b, rev, target, versus_path,
1461 text_deltas, depth, FALSE, ignore_ancestry);
1464 /* Regardless of whether a client's capabilities indicate an
1465 understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
1466 we provide a response.
1468 ASSUMPTION: When performing a 'merge' with two URLs at different
1469 revisions, the client will call this command more than once. */
1470 static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1471 apr_array_header_t *params, void *baton)
1473 server_baton_t *b = baton;
1474 svn_revnum_t rev;
1475 apr_array_header_t *paths, *canonical_paths;
1476 svn_mergeinfo_catalog_t mergeinfo;
1477 int i;
1478 apr_hash_index_t *hi;
1479 const char *inherit_word;
1480 svn_mergeinfo_inheritance_t inherit;
1481 svn_boolean_t include_descendants;
1482 apr_pool_t *iterpool;
1484 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)wb", &paths, &rev,
1485 &inherit_word, &include_descendants));
1486 inherit = svn_inheritance_from_word(inherit_word);
1488 /* Canonicalize the paths which mergeinfo has been requested for. */
1489 canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
1490 for (i = 0; i < paths->nelts; i++)
1492 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
1493 const char *full_path;
1495 if (item->kind != SVN_RA_SVN_STRING)
1496 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1497 _("Path is not a string"));
1498 full_path = svn_path_join(b->fs_path->data,
1499 svn_path_canonicalize(item->u.string->data,
1500 pool),
1501 pool);
1502 APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
1505 SVN_ERR(trivial_auth_request(conn, pool, b));
1506 SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos,
1507 canonical_paths, rev,
1508 inherit,
1509 include_descendants,
1510 authz_check_access_cb_func(b), b,
1511 pool));
1512 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo,
1513 b->fs_path->data, pool));
1514 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
1515 iterpool = svn_pool_create(pool);
1516 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1518 const void *key;
1519 void *value;
1520 svn_string_t *mergeinfo_string;
1522 svn_pool_clear(iterpool);
1524 apr_hash_this(hi, &key, NULL, &value);
1525 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string,
1526 (svn_mergeinfo_t) value,
1527 iterpool));
1528 SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "cs", (const char *) key,
1529 mergeinfo_string));
1531 svn_pool_destroy(iterpool);
1532 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1534 return SVN_NO_ERROR;
1537 /* Send a log entry to the client. */
1538 static svn_error_t *log_receiver(void *baton,
1539 svn_log_entry_t *log_entry,
1540 apr_pool_t *pool)
1542 log_baton_t *b = baton;
1543 svn_ra_svn_conn_t *conn = b->conn;
1544 apr_hash_index_t *h;
1545 const void *key;
1546 void *val;
1547 const char *path;
1548 svn_log_changed_path_t *change;
1549 svn_boolean_t invalid_revnum = FALSE;
1550 char action[2];
1551 const char *author, *date, *message;
1552 apr_uint64_t revprop_count;
1554 if (log_entry->revision == SVN_INVALID_REVNUM)
1556 /* If the stack depth is zero, we've seen the last revision, so don't
1557 send it, just return. */
1558 if (b->stack_depth == 0)
1559 return SVN_NO_ERROR;
1561 /* Because the svn protocol won't let us send an invalid revnum, we have
1562 to fudge here and send an additional flag. */
1563 log_entry->revision = 0;
1564 invalid_revnum = TRUE;
1565 b->stack_depth--;
1568 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "(!"));
1569 if (log_entry->changed_paths)
1571 for (h = apr_hash_first(pool, log_entry->changed_paths); h;
1572 h = apr_hash_next(h))
1574 apr_hash_this(h, &key, NULL, &val);
1575 path = key;
1576 change = val;
1577 action[0] = change->action;
1578 action[1] = '\0';
1579 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cw(?cr)", path, action,
1580 change->copyfrom_path,
1581 change->copyfrom_rev));
1584 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
1585 svn_compat_log_revprops_clear(log_entry->revprops);
1586 if (log_entry->revprops)
1587 revprop_count = apr_hash_count(log_entry->revprops);
1588 else
1589 revprop_count = 0;
1590 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
1591 log_entry->revision,
1592 author, date, message,
1593 log_entry->has_children,
1594 invalid_revnum, revprop_count));
1595 SVN_ERR(svn_ra_svn_write_proplist(conn, pool, log_entry->revprops));
1596 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
1598 if (log_entry->has_children)
1599 b->stack_depth++;
1601 return SVN_NO_ERROR;
1604 static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1605 apr_array_header_t *params, void *baton)
1607 svn_error_t *err, *write_err;
1608 server_baton_t *b = baton;
1609 svn_revnum_t start_rev, end_rev;
1610 const char *full_path;
1611 svn_boolean_t changed_paths, strict_node, include_merged_revisions;
1612 apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
1613 char *revprop_word;
1614 svn_ra_svn_item_t *elt;
1615 int i;
1616 apr_uint64_t limit, include_merged_revs_param;
1617 log_baton_t lb;
1619 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
1620 &start_rev, &end_rev, &changed_paths,
1621 &strict_node, &limit,
1622 &include_merged_revs_param,
1623 &revprop_word, &revprop_items));
1625 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1626 include_merged_revisions = FALSE;
1627 else
1628 include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
1630 if (revprop_word == NULL)
1631 /* pre-1.5 client */
1632 revprops = svn_compat_log_revprops_in(pool);
1633 else if (strcmp(revprop_word, "all-revprops") == 0)
1634 revprops = NULL;
1635 else if (strcmp(revprop_word, "revprops") == 0)
1637 revprops = apr_array_make(pool, revprop_items->nelts,
1638 sizeof(char *));
1639 if (revprop_items)
1641 for (i = 0; i < revprop_items->nelts; i++)
1643 elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
1644 if (elt->kind != SVN_RA_SVN_STRING)
1645 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1646 _("Log revprop entry not a string"));
1647 APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
1651 else
1652 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1653 _("Unknown revprop word '%s' in log command"),
1654 revprop_word);
1656 /* If we got an unspecified number then the user didn't send us anything,
1657 so we assume no limit. If it's larger than INT_MAX then someone is
1658 messing with us, since we know the svn client libraries will never send
1659 us anything that big, so play it safe and default to no limit. */
1660 if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
1661 limit = 0;
1663 full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
1664 for (i = 0; i < paths->nelts; i++)
1666 elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
1667 if (elt->kind != SVN_RA_SVN_STRING)
1668 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1669 _("Log path entry not a string"));
1670 full_path = svn_path_join(b->fs_path->data,
1671 svn_path_canonicalize(elt->u.string->data,
1672 pool),
1673 pool);
1674 APR_ARRAY_PUSH(full_paths, const char *) = full_path;
1676 SVN_ERR(trivial_auth_request(conn, pool, b));
1678 /* Get logs. (Can't report errors back to the client at this point.) */
1679 lb.fs_path = b->fs_path->data;
1680 lb.conn = conn;
1681 lb.stack_depth = 0;
1682 err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
1683 (int) limit, changed_paths, strict_node,
1684 include_merged_revisions, revprops,
1685 authz_check_access_cb_func(b), b, log_receiver,
1686 &lb, pool);
1688 write_err = svn_ra_svn_write_word(conn, pool, "done");
1689 if (write_err)
1691 svn_error_clear(err);
1692 return write_err;
1694 SVN_CMD_ERR(err);
1695 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1696 return SVN_NO_ERROR;
1699 static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1700 apr_array_header_t *params, void *baton)
1702 server_baton_t *b = baton;
1703 svn_revnum_t rev;
1704 const char *path, *full_path;
1705 svn_fs_root_t *root;
1706 svn_node_kind_t kind;
1708 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev));
1709 full_path = svn_path_join(b->fs_path->data,
1710 svn_path_canonicalize(path, pool), pool);
1712 /* Check authorizations */
1713 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1714 full_path, FALSE));
1716 if (!SVN_IS_VALID_REVNUM(rev))
1717 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1719 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1720 SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
1721 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "w", kind_word(kind)));
1722 return SVN_NO_ERROR;
1725 static svn_error_t *stat(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1726 apr_array_header_t *params, void *baton)
1728 server_baton_t *b = baton;
1729 svn_revnum_t rev;
1730 const char *path, *full_path, *cdate;
1731 svn_fs_root_t *root;
1732 svn_dirent_t *dirent;
1734 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev));
1735 full_path = svn_path_join(b->fs_path->data,
1736 svn_path_canonicalize(path, pool), pool);
1738 /* Check authorizations */
1739 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1740 full_path, FALSE));
1742 if (!SVN_IS_VALID_REVNUM(rev))
1743 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1744 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1745 SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
1747 /* Need to return the equivalent of "(?l)", since that's what the
1748 client is reading. */
1750 if (dirent == NULL)
1752 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "()"));
1753 return SVN_NO_ERROR;
1756 cdate = (dirent->time == (time_t) -1) ? NULL
1757 : svn_time_to_cstring(dirent->time, pool);
1759 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
1760 kind_word(dirent->kind),
1761 (apr_uint64_t) dirent->size,
1762 dirent->has_props, dirent->created_rev,
1763 cdate, dirent->last_author));
1765 return SVN_NO_ERROR;
1768 static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1769 apr_array_header_t *params, void *baton)
1771 svn_error_t *err, *write_err;
1772 server_baton_t *b = baton;
1773 svn_revnum_t revision;
1774 apr_array_header_t *location_revisions, *loc_revs_proto;
1775 svn_ra_svn_item_t *elt;
1776 int i;
1777 const char *relative_path;
1778 svn_revnum_t peg_revision;
1779 apr_hash_t *fs_locations;
1780 apr_hash_index_t *iter;
1781 const char *abs_path;
1782 const void *iter_key;
1783 void *iter_value;
1785 /* Parse the arguments. */
1786 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crl", &relative_path,
1787 &peg_revision,
1788 &loc_revs_proto));
1789 relative_path = svn_path_canonicalize(relative_path, pool);
1791 abs_path = svn_path_join(b->fs_path->data, relative_path, pool);
1793 location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
1794 sizeof(svn_revnum_t));
1795 for (i = 0; i < loc_revs_proto->nelts; i++)
1797 elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
1798 if (elt->kind != SVN_RA_SVN_NUMBER)
1799 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1800 "Get-locations location revisions entry "
1801 "not a revision number");
1802 revision = (svn_revnum_t)(elt->u.number);
1803 APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
1805 SVN_ERR(trivial_auth_request(conn, pool, b));
1807 /* All the parameters are fine - let's perform the query against the
1808 * repository. */
1810 /* We store both err and write_err here, so the client will get
1811 * the "done" even if there was an error in fetching the results. */
1813 err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
1814 peg_revision, location_revisions,
1815 authz_check_access_cb_func(b), b, pool);
1817 /* Now, write the results to the connection. */
1818 if (!err)
1820 if (fs_locations)
1822 for (iter = apr_hash_first(pool, fs_locations); iter;
1823 iter = apr_hash_next(iter))
1825 apr_hash_this(iter, &iter_key, NULL, &iter_value);
1826 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "rc",
1827 *(const svn_revnum_t *)iter_key,
1828 (const char *)iter_value));
1833 write_err = svn_ra_svn_write_word(conn, pool, "done");
1834 if (write_err)
1836 svn_error_clear(err);
1837 return write_err;
1839 SVN_CMD_ERR(err);
1841 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1843 return SVN_NO_ERROR;
1846 static svn_error_t *gls_receiver(svn_location_segment_t *segment,
1847 void *baton,
1848 apr_pool_t *pool)
1850 svn_ra_svn_conn_t *conn = baton;
1851 return svn_ra_svn_write_tuple(conn, pool, "rr(?c)",
1852 segment->range_start,
1853 segment->range_end,
1854 segment->path);
1857 static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn,
1858 apr_pool_t *pool,
1859 apr_array_header_t *params,
1860 void *baton)
1862 svn_error_t *err, *write_err;
1863 server_baton_t *b = baton;
1864 svn_revnum_t peg_revision, start_rev, end_rev;
1865 const char *relative_path;
1866 const char *abs_path;
1868 /* Parse the arguments. */
1869 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)(?r)",
1870 &relative_path, &peg_revision,
1871 &start_rev, &end_rev));
1872 relative_path = svn_path_canonicalize(relative_path, pool);
1874 abs_path = svn_path_join(b->fs_path->data, relative_path, pool);
1876 if (SVN_IS_VALID_REVNUM(start_rev)
1877 && SVN_IS_VALID_REVNUM(end_rev)
1878 && (end_rev > start_rev))
1879 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1880 "Get-location-segments end revision must not be "
1881 "younger than start revision");
1883 if (SVN_IS_VALID_REVNUM(peg_revision)
1884 && SVN_IS_VALID_REVNUM(start_rev)
1885 && (start_rev > peg_revision))
1886 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1887 "Get-location-segments start revision must not "
1888 "be younger than peg revision");
1890 SVN_ERR(trivial_auth_request(conn, pool, b));
1892 /* All the parameters are fine - let's perform the query against the
1893 * repository. */
1895 /* We store both err and write_err here, so the client will get
1896 * the "done" even if there was an error in fetching the results. */
1898 err = svn_repos_node_location_segments(b->repos, abs_path,
1899 peg_revision, start_rev, end_rev,
1900 gls_receiver, (void *)conn,
1901 authz_check_access_cb_func(b), b,
1902 pool);
1903 write_err = svn_ra_svn_write_word(conn, pool, "done");
1904 if (write_err)
1906 svn_error_clear(err);
1907 return write_err;
1909 SVN_CMD_ERR(err);
1911 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1913 return SVN_NO_ERROR;
1916 /* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the
1917 client as a string. */
1918 static svn_error_t *svndiff_handler(void *baton, const char *data,
1919 apr_size_t *len)
1921 file_revs_baton_t *b = baton;
1922 svn_string_t str;
1924 str.data = data;
1925 str.len = *len;
1926 return svn_ra_svn_write_string(b->conn, b->pool, &str);
1929 /* This implements svn_close_fn_t. Mark the end of the data by writing an
1930 empty string to the client. */
1931 static svn_error_t *svndiff_close_handler(void *baton)
1933 file_revs_baton_t *b = baton;
1935 SVN_ERR(svn_ra_svn_write_cstring(b->conn, b->pool, ""));
1936 return SVN_NO_ERROR;
1939 /* This implements the svn_repos_file_rev_handler_t interface. */
1940 static svn_error_t *file_rev_handler(void *baton, const char *path,
1941 svn_revnum_t rev, apr_hash_t *rev_props,
1942 svn_boolean_t merged_revision,
1943 svn_txdelta_window_handler_t *d_handler,
1944 void **d_baton,
1945 apr_array_header_t *prop_diffs,
1946 apr_pool_t *pool)
1948 file_revs_baton_t *frb = baton;
1949 svn_stream_t *stream;
1951 SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "cr(!",
1952 path, rev));
1953 SVN_ERR(svn_ra_svn_write_proplist(frb->conn, pool, rev_props));
1954 SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)(!"));
1955 SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
1956 SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)b", merged_revision));
1958 /* Store the pool for the delta stream. */
1959 frb->pool = pool;
1961 /* Prepare for the delta or just write an empty string. */
1962 if (d_handler)
1964 stream = svn_stream_create(baton, pool);
1965 svn_stream_set_write(stream, svndiff_handler);
1966 svn_stream_set_close(stream, svndiff_close_handler);
1968 if (svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1))
1969 svn_txdelta_to_svndiff2(d_handler, d_baton, stream, 1, pool);
1970 else
1971 svn_txdelta_to_svndiff2(d_handler, d_baton, stream, 0, pool);
1973 else
1974 SVN_ERR(svn_ra_svn_write_cstring(frb->conn, pool, ""));
1976 return SVN_NO_ERROR;
1979 static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1980 apr_array_header_t *params, void *baton)
1982 server_baton_t *b = baton;
1983 svn_error_t *err, *write_err;
1984 file_revs_baton_t frb;
1985 svn_revnum_t start_rev, end_rev;
1986 const char *path;
1987 const char *full_path;
1988 apr_uint64_t include_merged_revs_param;
1989 svn_boolean_t include_merged_revisions;
1991 /* Parse arguments. */
1992 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)?B",
1993 &path, &start_rev, &end_rev,
1994 &include_merged_revs_param));
1995 path = svn_path_canonicalize(path, pool);
1996 SVN_ERR(trivial_auth_request(conn, pool, b));
1997 full_path = svn_path_join(b->fs_path->data, path, pool);
1999 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2000 include_merged_revisions = FALSE;
2001 else
2002 include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2004 frb.conn = conn;
2005 frb.pool = NULL;
2007 err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev,
2008 include_merged_revisions,
2009 authz_check_access_cb_func(b), b,
2010 file_rev_handler, &frb, pool);
2011 write_err = svn_ra_svn_write_word(conn, pool, "done");
2012 if (write_err)
2014 svn_error_clear(err);
2015 return write_err;
2017 SVN_CMD_ERR(err);
2018 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2020 return SVN_NO_ERROR;
2023 static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2024 apr_array_header_t *params, void *baton)
2026 server_baton_t *b = baton;
2027 const char *path;
2028 const char *comment;
2029 const char *full_path;
2030 svn_boolean_t steal_lock;
2031 svn_revnum_t current_rev;
2032 svn_lock_t *l;
2034 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
2035 &steal_lock, &current_rev));
2036 full_path = svn_path_join(b->fs_path->data,
2037 svn_path_canonicalize(path, pool), pool);
2039 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2040 full_path, TRUE));
2042 SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
2043 0, /* No expiration time. */
2044 current_rev, steal_lock, pool));
2046 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(!", "success"));
2047 SVN_ERR(write_lock(conn, pool, l));
2048 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
2050 return SVN_NO_ERROR;
2053 static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2054 apr_array_header_t *params, void *baton)
2056 server_baton_t *b = baton;
2057 apr_array_header_t *path_revs;
2058 const char *comment;
2059 svn_boolean_t steal_lock;
2060 int i;
2061 apr_pool_t *subpool;
2062 const char *path;
2063 const char *full_path;
2064 svn_revnum_t current_rev;
2065 svn_lock_t *l;
2066 svn_error_t *err = SVN_NO_ERROR, *write_err;
2068 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
2069 &path_revs));
2071 subpool = svn_pool_create(pool);
2073 /* Because we can only send a single auth reply per request, we send
2074 a reply before parsing the lock commands. This means an authz
2075 access denial will abort the processing of the locks and return
2076 an error. */
2077 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
2079 /* Loop through the lock requests. */
2080 for (i = 0; i < path_revs->nelts; ++i)
2082 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
2083 svn_ra_svn_item_t);
2085 svn_pool_clear(subpool);
2087 if (item->kind != SVN_RA_SVN_LIST)
2088 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2089 "Lock requests should be list of lists");
2091 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "c(?r)", &path,
2092 &current_rev));
2094 full_path = svn_path_join(b->fs_path->data,
2095 svn_path_canonicalize(path, subpool),
2096 subpool);
2098 if (! lookup_access(pool, b, svn_authz_write, full_path, TRUE))
2100 err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED,
2101 NULL, NULL);
2102 break;
2105 err = svn_repos_fs_lock(&l, b->repos, full_path,
2106 NULL, comment, FALSE,
2107 0, /* No expiration time. */
2108 current_rev,
2109 steal_lock, subpool);
2111 if (err)
2113 if (SVN_ERR_IS_LOCK_ERROR(err))
2115 write_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
2116 svn_error_clear(err);
2117 err = NULL;
2118 SVN_ERR(write_err);
2120 else
2121 break;
2123 else
2125 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "w!", "success"));
2126 SVN_ERR(write_lock(conn, subpool, l));
2127 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "!"));
2131 svn_pool_destroy(subpool);
2133 /* NOTE: err might contain a fatal locking error from the loop above. */
2134 write_err = svn_ra_svn_write_word(conn, pool, "done");
2135 if (!write_err)
2136 SVN_CMD_ERR(err);
2137 svn_error_clear(err);
2138 SVN_ERR(write_err);
2139 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2141 return SVN_NO_ERROR;
2144 static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2145 apr_array_header_t *params, void *baton)
2147 server_baton_t *b = baton;
2148 const char *path, *token, *full_path;
2149 svn_boolean_t break_lock;
2151 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b", &path, &token,
2152 &break_lock));
2154 full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, pool),
2155 pool);
2157 /* Username required unless break_lock was specified. */
2158 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2159 full_path, ! break_lock));
2161 SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2162 pool));
2164 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2166 return SVN_NO_ERROR;
2169 static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2170 apr_array_header_t *params, void *baton)
2172 server_baton_t *b = baton;
2173 svn_boolean_t break_lock;
2174 apr_array_header_t *unlock_tokens;
2175 int i;
2176 apr_pool_t *subpool;
2177 const char *path;
2178 const char *full_path;
2179 const char *token;
2180 svn_error_t *err = SVN_NO_ERROR, *write_err;
2182 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "bl", &break_lock,
2183 &unlock_tokens));
2185 /* Username required unless break_lock was specified. */
2186 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
2188 subpool = svn_pool_create(pool);
2190 /* Loop through the unlock requests. */
2191 for (i = 0; i < unlock_tokens->nelts; i++)
2193 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
2194 svn_ra_svn_item_t);
2196 svn_pool_clear(subpool);
2198 if (item->kind != SVN_RA_SVN_LIST)
2199 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2200 "Unlock request should be a list of lists");
2202 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "c(?c)", &path,
2203 &token));
2205 full_path = svn_path_join(b->fs_path->data,
2206 svn_path_canonicalize(path, subpool),
2207 subpool);
2209 if (! lookup_access(subpool, b, svn_authz_write, full_path,
2210 ! break_lock))
2211 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
2212 svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED,
2213 NULL, NULL), NULL);
2215 err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2216 subpool);
2217 if (err)
2219 if (SVN_ERR_IS_UNLOCK_ERROR(err))
2221 write_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
2222 svn_error_clear(err);
2223 err = NULL;
2224 SVN_ERR(write_err);
2226 else
2227 break;
2229 else
2230 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "w(c)", "success",
2231 path));
2234 svn_pool_destroy(subpool);
2236 /* NOTE: err might contain a fatal unlocking error from the loop above. */
2237 write_err = svn_ra_svn_write_word(conn, pool, "done");
2238 if (! write_err)
2239 SVN_CMD_ERR(err);
2240 svn_error_clear(err);
2241 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2243 return SVN_NO_ERROR;
2246 static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2247 apr_array_header_t *params, void *baton)
2249 server_baton_t *b = baton;
2250 const char *path;
2251 const char *full_path;
2252 svn_lock_t *l;
2254 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
2256 full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path,
2257 pool),
2258 pool);
2260 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2261 full_path, FALSE));
2263 SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
2265 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
2266 if (l)
2267 SVN_ERR(write_lock(conn, pool, l));
2268 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
2270 return SVN_NO_ERROR;
2273 static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2274 apr_array_header_t *params, void *baton)
2276 server_baton_t *b = baton;
2277 const char *path;
2278 const char *full_path;
2279 apr_hash_t *locks;
2280 apr_hash_index_t *hi;
2281 void *val;
2282 svn_lock_t *l;
2284 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
2286 full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path,
2287 pool),
2288 pool);
2290 SVN_ERR(trivial_auth_request(conn, pool, b));
2292 SVN_CMD_ERR(svn_repos_fs_get_locks(&locks, b->repos, full_path,
2293 authz_check_access_cb_func(b), b, pool));
2295 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
2296 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2298 apr_hash_this(hi, NULL, NULL, &val);
2299 l = val;
2300 SVN_ERR(write_lock(conn, pool, l));
2302 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
2304 return SVN_NO_ERROR;
2307 static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
2308 server_baton_t *b,
2309 svn_revnum_t rev,
2310 svn_revnum_t low_water_mark,
2311 svn_boolean_t send_deltas,
2312 apr_pool_t *pool)
2314 const svn_delta_editor_t *editor;
2315 void *edit_baton;
2316 svn_fs_root_t *root;
2317 svn_error_t *err;
2319 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
2321 err = svn_fs_revision_root(&root, b->fs, rev, pool);
2323 if (! err)
2324 err = svn_repos_replay2(root, b->fs_path->data, low_water_mark,
2325 send_deltas, editor, edit_baton,
2326 authz_check_access_cb_func(b), b, pool);
2328 if (err)
2329 svn_error_clear(editor->abort_edit(edit_baton, pool));
2330 SVN_CMD_ERR(err);
2332 return svn_ra_svn_write_cmd(conn, pool, "finish-replay", "");
2335 static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2336 apr_array_header_t *params, void *baton)
2338 svn_revnum_t rev, low_water_mark;
2339 svn_boolean_t send_deltas;
2340 server_baton_t *b = baton;
2342 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrb", &rev, &low_water_mark,
2343 &send_deltas));
2345 SVN_ERR(trivial_auth_request(conn, pool, b));
2347 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
2348 send_deltas, pool));
2350 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2352 return SVN_NO_ERROR;
2355 static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2356 apr_array_header_t *params, void *baton)
2358 svn_revnum_t start_rev, end_rev, rev, low_water_mark;
2359 svn_boolean_t send_deltas;
2360 server_baton_t *b = baton;
2361 apr_pool_t *iterpool;
2363 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrrb", &start_rev,
2364 &end_rev, &low_water_mark,
2365 &send_deltas));
2367 SVN_ERR(trivial_auth_request(conn, pool, b));
2369 iterpool = svn_pool_create(pool);
2370 for (rev = start_rev; rev <= end_rev; rev++)
2372 apr_hash_t *props;
2374 svn_pool_clear(iterpool);
2376 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
2377 authz_check_access_cb_func(b),
2379 iterpool));
2380 SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "w(!", "revprops"));
2381 SVN_ERR(svn_ra_svn_write_proplist(conn, iterpool, props));
2382 SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!)"));
2384 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
2385 send_deltas, iterpool));
2388 svn_pool_destroy(iterpool);
2390 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2392 return SVN_NO_ERROR;
2396 static const svn_ra_svn_cmd_entry_t main_commands[] = {
2397 { "reparent", reparent },
2398 { "get-latest-rev", get_latest_rev },
2399 { "get-dated-rev", get_dated_rev },
2400 { "change-rev-prop", change_rev_prop },
2401 { "rev-proplist", rev_proplist },
2402 { "rev-prop", rev_prop },
2403 { "commit", commit },
2404 { "get-file", get_file },
2405 { "get-dir", get_dir },
2406 { "update", update },
2407 { "switch", switch_cmd },
2408 { "status", status },
2409 { "diff", diff },
2410 { "get-mergeinfo", get_mergeinfo },
2411 { "log", log_cmd },
2412 { "check-path", check_path },
2413 { "stat", stat },
2414 { "get-locations", get_locations },
2415 { "get-location-segments", get_location_segments },
2416 { "get-file-revs", get_file_revs },
2417 { "lock", lock },
2418 { "lock-many", lock_many },
2419 { "unlock", unlock },
2420 { "unlock-many", unlock_many },
2421 { "get-lock", get_lock },
2422 { "get-locks", get_locks },
2423 { "replay", replay },
2424 { "replay-range", replay_range },
2425 { NULL }
2428 /* Skip past the scheme part of a URL, including the tunnel specification
2429 * if present. Return NULL if the scheme part is invalid for ra_svn. */
2430 static const char *skip_scheme_part(const char *url)
2432 if (strncmp(url, "svn", 3) != 0)
2433 return NULL;
2434 url += 3;
2435 if (*url == '+')
2436 url += strcspn(url, ":");
2437 if (strncmp(url, "://", 3) != 0)
2438 return NULL;
2439 return url + 3;
2442 /* Check that PATH is a valid repository path, meaning it doesn't contain any
2443 '..' path segments.
2444 NOTE: This is similar to svn_path_is_backpath_present, but that function
2445 assumes the path separator is '/'. This function also checks for
2446 segments delimited by the local path separator. */
2447 static svn_boolean_t
2448 repos_path_valid(const char *path)
2450 const char *s = path;
2452 while (*s)
2454 /* Scan for the end of the segment. */
2455 while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
2456 ++path;
2458 /* Check for '..'. */
2459 #ifdef WIN32
2460 /* On Windows, don't allow sequences of more than one character
2461 consisting of just dots and spaces. Win32 functions treat
2462 paths such as ".. " and "......." inconsistently. Make sure
2463 no one can escape out of the root. */
2464 if (path - s >= 2 && strspn(s, ". ") == path - s)
2465 return FALSE;
2466 #else /* ! WIN32 */
2467 if (path - s == 2 && s[0] == '.' && s[1] == '.')
2468 return FALSE;
2469 #endif
2471 /* Skip all separators. */
2472 while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
2473 ++path;
2474 s = path;
2477 return TRUE;
2480 /* Look for the repository given by URL, using ROOT as the virtual
2481 * repository root. If we find one, fill in the repos, fs, cfg,
2482 * repos_url, and fs_path fields of B. Set B->repos's client
2483 * capabilities to CAPABILITIES, which must be at least as long-lived
2484 * as POOL, and whose elements are SVN_RA_CAPABILITY_*.
2486 static svn_error_t *find_repos(const char *url, const char *root,
2487 server_baton_t *b,
2488 apr_array_header_t *capabilities,
2489 apr_pool_t *pool)
2491 const char *path, *full_path, *repos_root, *fs_path;
2492 svn_stringbuf_t *url_buf;
2494 /* Skip past the scheme and authority part. */
2495 path = skip_scheme_part(url);
2496 if (path == NULL)
2497 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
2498 "Non-svn URL passed to svn server: '%s'", url);
2499 path = strchr(path, '/');
2500 path = (path == NULL) ? "" : path + 1;
2502 /* Decode URI escapes from the path. */
2503 path = svn_path_uri_decode(path, pool);
2505 /* Ensure that it isn't possible to escape the root by skipping leading
2506 slashes and not allowing '..' segments. */
2507 while (*path == '/')
2508 ++path;
2509 if (!repos_path_valid(path))
2510 return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
2511 "Couldn't determine repository path");
2513 /* Join the server-configured root with the client path. */
2514 full_path = svn_path_join(svn_path_canonicalize(root, pool),
2515 svn_path_canonicalize(path, pool), pool);
2517 /* Search for a repository in the full path. */
2518 repos_root = svn_repos_find_root_path(full_path, pool);
2519 if (!repos_root)
2520 return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
2521 "No repository found in '%s'", url);
2523 /* Open the repository and fill in b with the resulting information. */
2524 SVN_ERR(svn_repos_open(&b->repos, repos_root, pool));
2525 SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities));
2526 b->fs = svn_repos_fs(b->repos);
2527 fs_path = full_path + strlen(repos_root);
2528 b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool);
2529 url_buf = svn_stringbuf_create(url, pool);
2530 svn_path_remove_components(url_buf,
2531 svn_path_component_count(b->fs_path->data));
2532 b->repos_url = url_buf->data;
2533 b->authz_repos_name = svn_path_is_child(root, repos_root, pool);
2535 /* If the svnserve configuration files have not been loaded then
2536 load them from the repository. */
2537 if (NULL == b->cfg)
2538 SVN_ERR(load_configs(&b->cfg, &b->pwdb, &b->authzdb,
2539 svn_repos_svnserve_conf(b->repos, pool), FALSE,
2540 svn_repos_conf_dir(b->repos, pool),
2541 pool));
2543 #ifdef SVN_HAVE_SASL
2544 /* Should we use Cyrus SASL? */
2545 svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
2546 SVN_CONFIG_OPTION_USE_SASL, FALSE);
2547 #endif
2549 /* Use the repository UUID as the default realm. */
2550 SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
2551 svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
2552 SVN_CONFIG_OPTION_REALM, b->realm);
2554 /* Make sure it's possible for the client to authenticate. Note
2555 that this doesn't take into account any authz configuration read
2556 above, because we can't know about access it grants until paths
2557 are given by the client. */
2558 if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
2559 && (get_access(b, AUTHENTICATED) == NO_ACCESS
2560 || (!b->tunnel_user && !b->pwdb && !b->use_sasl)))
2561 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
2562 "No access allowed to this repository");
2563 return SVN_NO_ERROR;
2566 /* Compute the authentication name EXTERNAL should be able to get, if any. */
2567 static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
2569 /* Only offer EXTERNAL for connections tunneled over a login agent. */
2570 if (!params->tunnel)
2571 return NULL;
2573 /* If a tunnel user was provided on the command line, use that. */
2574 if (params->tunnel_user)
2575 return params->tunnel_user;
2577 return svn_user_get_name(pool);
2580 svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
2581 apr_pool_t *pool)
2583 svn_error_t *err, *io_err;
2584 apr_uint64_t ver;
2585 const char *uuid, *client_url;
2586 apr_array_header_t *caplist, *cap_words;
2587 server_baton_t b;
2589 b.tunnel = params->tunnel;
2590 b.tunnel_user = get_tunnel_user(params, pool);
2591 b.read_only = params->read_only;
2592 b.user = NULL;
2593 b.cfg = params->cfg; /* Ugly; can drop when we remove v1 support. */
2594 b.pwdb = params->pwdb; /* Likewise. */
2595 b.authzdb = params->authzdb;
2596 b.realm = NULL;
2597 b.pool = pool;
2598 b.use_sasl = FALSE;
2600 /* Send greeting. We don't support version 1 any more, so we can
2601 * send an empty mechlist. */
2602 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwww)",
2603 (apr_uint64_t) 2, (apr_uint64_t) 2,
2604 SVN_RA_SVN_CAP_EDIT_PIPELINE,
2605 SVN_RA_SVN_CAP_SVNDIFF1,
2606 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
2607 SVN_RA_SVN_CAP_COMMIT_REVPROPS,
2608 SVN_RA_SVN_CAP_DEPTH,
2609 SVN_RA_SVN_CAP_LOG_REVPROPS,
2610 SVN_RA_SVN_CAP_PARTIAL_REPLAY));
2612 /* Read client response, which we assume to be in version 2 format:
2613 * version, capability list, and client URL; then we do an auth
2614 * request. */
2615 SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "nlc",
2616 &ver, &caplist, &client_url));
2617 if (ver != 2)
2618 return SVN_NO_ERROR;
2620 client_url = svn_path_canonicalize(client_url, pool);
2621 SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
2623 /* All released versions of Subversion support edit-pipeline,
2624 * so we do not accept connections from clients that do not. */
2625 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
2626 return SVN_NO_ERROR;
2628 /* find_repos needs the capabilities as a list of words (eventually
2629 they get handed to the start-commit hook). While we could add a
2630 new interface to re-retrieve them from conn and convert the
2631 result to a list, it's simpler to just convert caplist by hand
2632 here, since we already have it and turning 'svn_ra_svn_item_t's
2633 into 'const char *'s is pretty easy.
2635 We only record capabilities we care about. The client may report
2636 more (because it doesn't know what the server cares about). */
2638 int i;
2639 svn_ra_svn_item_t *item;
2641 cap_words = apr_array_make(pool, 1, sizeof(const char *));
2642 for (i = 0; i < caplist->nelts; i++)
2644 item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
2645 /* ra_svn_set_capabilities() already type-checked for us */
2646 if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0)
2648 APR_ARRAY_PUSH(cap_words, const char *)
2649 = SVN_RA_CAPABILITY_MERGEINFO;
2654 err = find_repos(client_url, params->root, &b, cap_words, pool);
2655 if (!err)
2657 SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
2658 if (current_access(&b) == NO_ACCESS)
2659 err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
2660 "Not authorized for access");
2662 if (err)
2664 io_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
2665 svn_error_clear(err);
2666 SVN_ERR(io_err);
2667 return svn_ra_svn_flush(conn, pool);
2670 SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
2672 /* We can't claim mergeinfo capability until we know whether the
2673 repository supports mergeinfo (i.e., is not a 1.4 repository),
2674 but we don't get the repository url from the client until after
2675 we've already sent the initial list of server capabilities. So
2676 we list repository capabilities here, in our first response after
2677 the client has sent the url. */
2679 svn_boolean_t supports_mergeinfo;
2680 SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo,
2681 SVN_REPOS_CAPABILITY_MERGEINFO, pool));
2683 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(cc(!",
2684 "success", uuid, b.repos_url));
2685 if (supports_mergeinfo)
2686 SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO));
2687 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
2690 return svn_ra_svn_handle_commands(conn, pool, main_commands, &b);