2 * serve.c : Functions for serving the Subversion protocol
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
27 #include <limits.h> /* for UINT_MAX */
30 #define APR_WANT_STRFUNC
32 #include <apr_general.h>
34 #include <apr_strings.h>
36 #include "svn_compat.h"
37 #include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */
39 #include "svn_types.h"
40 #include "svn_string.h"
41 #include "svn_pools.h"
42 #include "svn_error.h"
43 #include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
44 #include "svn_ra_svn.h"
45 #include "svn_repos.h"
46 #include "svn_dirent_uri.h"
49 #include "svn_config.h"
50 #include "svn_props.h"
51 #include "svn_mergeinfo.h"
54 #include "private/svn_log.h"
55 #include "private/svn_mergeinfo_private.h"
56 #include "private/svn_ra_svn_private.h"
57 #include "private/svn_fspath.h"
60 #include <unistd.h> /* For getpid() */
65 typedef struct commit_callback_baton_t {
67 svn_revnum_t *new_rev;
70 const char **post_commit_err;
71 } commit_callback_baton_t;
73 typedef struct report_driver_baton_t {
75 const char *repos_url; /* Decoded repository URL. */
78 /* so update() can distinguish checkout from update in logging */
80 svn_boolean_t only_empty_entries;
81 /* for diff() logging */
82 svn_revnum_t *from_rev;
83 } report_driver_baton_t;
85 typedef struct log_baton_t {
87 svn_ra_svn_conn_t *conn;
91 typedef struct file_revs_baton_t {
92 svn_ra_svn_conn_t *conn;
93 apr_pool_t *pool; /* Pool provided in the handler call. */
96 typedef struct fs_warning_baton_t {
97 server_baton_t *server;
98 svn_ra_svn_conn_t *conn;
100 } fs_warning_baton_t;
102 typedef struct authz_baton_t {
103 server_baton_t *server;
104 svn_ra_svn_conn_t *conn;
107 /* Write LEN bytes of ERRSTR to LOG_FILE with svn_io_file_write(). */
109 log_write(apr_file_t *log_file, const char *errstr, apr_size_t len,
112 return svn_io_file_write(log_file, errstr, &len, pool);
116 log_error(svn_error_t *err, apr_file_t *log_file, const char *remote_host,
117 const char *user, const char *repos, apr_pool_t *pool)
119 const char *timestr, *continuation;
121 /* 8192 from MAX_STRING_LEN in from httpd-2.2.4/include/httpd.h */
124 if (err == SVN_NO_ERROR)
127 if (log_file == NULL)
130 timestr = svn_time_to_cstring(apr_time_now(), pool);
131 remote_host = (remote_host ? remote_host : "-");
132 user = (user ? user : "-");
133 repos = (repos ? repos : "-");
138 const char *message = svn_err_best_message(err, errbuf, sizeof(errbuf));
139 /* based on httpd-2.2.4/server/log.c:log_error_core */
140 apr_size_t len = apr_snprintf(errstr, sizeof(errstr),
142 " %s %s %s %s ERR%s %s %ld %d ",
143 getpid(), timestr, remote_host, user,
145 err->file ? err->file : "-", err->line,
148 len += escape_errorlog_item(errstr + len, message,
149 sizeof(errstr) - len);
150 /* Truncate for the terminator (as apr_snprintf does) */
151 if (len > sizeof(errstr) - sizeof(APR_EOL_STR)) {
152 len = sizeof(errstr) - sizeof(APR_EOL_STR);
154 strcpy(errstr + len, APR_EOL_STR);
155 len += strlen(APR_EOL_STR);
156 svn_error_clear(log_write(log_file, errstr, len, pool));
163 /* Call log_error with log_file, remote_host, user, and repos
164 arguments from SERVER and CONN. */
166 log_server_error(svn_error_t *err, server_baton_t *server,
167 svn_ra_svn_conn_t *conn, apr_pool_t *pool)
169 log_error(err, server->log_file, svn_ra_svn_conn_remote_host(conn),
170 server->user, server->repos_name, pool);
173 /* svn_error_create() a new error, log_server_error() it, and
176 error_create_and_log(apr_status_t apr_err, svn_error_t *child,
177 const char *message, server_baton_t *server,
178 svn_ra_svn_conn_t *conn, apr_pool_t *pool)
180 svn_error_t *err = svn_error_create(apr_err, child, message);
181 log_server_error(err, server, conn, pool);
185 /* Log a failure ERR, transmit ERR back to the client (as part of a
186 "failure" notification), consume ERR, and flush the connection. */
188 log_fail_and_flush(svn_error_t *err, server_baton_t *server,
189 svn_ra_svn_conn_t *conn, apr_pool_t *pool)
193 log_server_error(err, server, conn, pool);
194 io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
195 svn_error_clear(err);
197 return svn_ra_svn__flush(conn, pool);
200 /* Log a client command. */
201 static svn_error_t *log_command(server_baton_t *b,
202 svn_ra_svn_conn_t *conn,
204 const char *fmt, ...)
206 const char *remote_host, *timestr, *log, *line;
210 if (b->log_file == NULL)
213 remote_host = svn_ra_svn_conn_remote_host(conn);
214 timestr = svn_time_to_cstring(apr_time_now(), pool);
217 log = apr_pvsprintf(pool, fmt, ap);
220 line = apr_psprintf(pool, "%" APR_PID_T_FMT
221 " %s %s %s %s %s" APR_EOL_STR,
223 (remote_host ? remote_host : "-"),
224 (b->user ? b->user : "-"), b->repos_name, log);
225 nbytes = strlen(line);
227 return log_write(b->log_file, line, nbytes, pool);
230 /* Log an authz failure */
232 log_authz_denied(const char *path,
233 svn_repos_authz_access_t required,
235 svn_ra_svn_conn_t *conn,
238 const char *timestr, *remote_host, *line;
240 if (b->log_file == NULL)
246 timestr = svn_time_to_cstring(apr_time_now(), pool);
247 remote_host = svn_ra_svn_conn_remote_host(conn);
249 line = apr_psprintf(pool, "%" APR_PID_T_FMT
250 " %s %s %s %s Authorization Failed %s%s %s" APR_EOL_STR,
252 (remote_host ? remote_host : "-"),
253 (b->user ? b->user : "-"),
255 (required & svn_authz_recursive ? "recursive " : ""),
256 (required & svn_authz_write ? "write" : "read"),
257 (path && path[0] ? path : "/"));
259 return log_write(b->log_file, line, strlen(line), pool);
263 svn_error_t *load_pwdb_config(server_baton_t *server,
264 svn_ra_svn_conn_t *conn,
267 const char *pwdb_path;
270 svn_config_get(server->cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL,
271 SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
276 pwdb_path = svn_dirent_internal_style(pwdb_path, pool);
277 pwdb_path = svn_dirent_join(server->base, pwdb_path, pool);
279 err = svn_config_read3(&server->pwdb, pwdb_path, TRUE,
283 log_server_error(err, server, conn, pool);
285 /* Because it may be possible to read the pwdb file with some
286 access methods and not others, ignore errors reading the pwdb
287 file and just don't present password authentication as an
288 option. Also, some authentications (e.g. --tunnel) can
289 proceed without it anyway.
291 ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked
292 ### for here. That seems to have been introduced in r856914,
293 ### and only in r870942 was the APR_EACCES check introduced. */
294 if (err->apr_err != SVN_ERR_BAD_FILENAME
295 && ! APR_STATUS_IS_EACCES(err->apr_err))
297 /* Now that we've logged the error, clear it and return a
298 * nice, generic error to the user:
299 * http://subversion.tigris.org/issues/show_bug.cgi?id=2271 */
300 svn_error_clear(err);
301 return svn_error_create(SVN_ERR_AUTHN_FAILED, NULL, NULL);
304 /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */
305 svn_error_clear(err);
312 /* Canonicalize *ACCESS_FILE based on the type of argument. Results are
313 * placed in *ACCESS_FILE. SERVER baton is used to convert relative paths to
314 * absolute paths rooted at the server root. REPOS_ROOT is used to calculate
315 * an absolute URL for repos-relative URLs. */
317 canonicalize_access_file(const char **access_file, server_baton_t *server,
318 const char *repos_root, apr_pool_t *pool)
320 if (svn_path_is_url(*access_file))
322 *access_file = svn_uri_canonicalize(*access_file, pool);
324 else if (svn_path_is_repos_relative_url(*access_file))
326 const char *repos_root_url;
328 SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_root_url, repos_root,
330 SVN_ERR(svn_path_resolve_repos_relative_url(access_file, *access_file,
331 repos_root_url, pool));
332 *access_file = svn_uri_canonicalize(*access_file, pool);
336 *access_file = svn_dirent_internal_style(*access_file, pool);
337 *access_file = svn_dirent_join(server->base, *access_file, pool);
343 svn_error_t *load_authz_config(server_baton_t *server,
344 svn_ra_svn_conn_t *conn,
345 const char *repos_root,
348 const char *authzdb_path;
349 const char *groupsdb_path;
352 /* Read authz configuration. */
353 svn_config_get(server->cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
354 SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
356 svn_config_get(server->cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL,
357 SVN_CONFIG_OPTION_GROUPS_DB, NULL);
361 const char *case_force_val;
363 /* Canonicalize and add the base onto the authzdb_path (if needed). */
364 err = canonicalize_access_file(&authzdb_path, server,
367 /* Same for the groupsdb_path if it is present. */
368 if (groupsdb_path && !err)
369 err = canonicalize_access_file(&groupsdb_path, server,
373 err = svn_repos_authz_read2(&server->authzdb, authzdb_path,
374 groupsdb_path, TRUE, pool);
378 log_server_error(err, server, conn, pool);
379 svn_error_clear(err);
380 return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, NULL);
383 /* Are we going to be case-normalizing usernames when we consult
384 * this authz file? */
385 svn_config_get(server->cfg, &case_force_val, SVN_CONFIG_SECTION_GENERAL,
386 SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL);
389 if (strcmp(case_force_val, "upper") == 0)
390 server->username_case = CASE_FORCE_UPPER;
391 else if (strcmp(case_force_val, "lower") == 0)
392 server->username_case = CASE_FORCE_LOWER;
394 server->username_case = CASE_ASIS;
399 server->authzdb = NULL;
400 server->username_case = CASE_ASIS;
406 /* Set *FS_PATH to the portion of URL that is the path within the
407 repository, if URL is inside REPOS_URL (if URL is not inside
408 REPOS_URL, then error, with the effect on *FS_PATH undefined).
410 If the resultant fs path would be the empty string (i.e., URL and
411 REPOS_URL are the same), then set *FS_PATH to "/".
413 Assume that REPOS_URL and URL are already URI-decoded. */
414 static svn_error_t *get_fs_path(const char *repos_url, const char *url,
415 const char **fs_path)
419 len = strlen(repos_url);
420 if (strncmp(url, repos_url, len) != 0)
421 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
422 "'%s' is not the same repository as '%s'",
424 *fs_path = url + len;
431 /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
433 /* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else
434 converts it to lower case. */
435 static void convert_case(char *text, svn_boolean_t to_uppercase)
440 *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
445 /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
446 the user described in BATON according to the authz rules in BATON.
447 Use POOL for temporary allocations only. If no authz rules are
448 present in BATON, grant access by default. */
449 static svn_error_t *authz_check_access(svn_boolean_t *allowed,
451 svn_repos_authz_access_t required,
453 svn_ra_svn_conn_t *conn,
456 /* If authz cannot be performed, grant access. This is NOT the same
457 as the default policy when authz is performed on a path with no
458 rules. In the latter case, the default is to deny access, and is
459 set by svn_repos_authz_check_access. */
466 /* If the authz request is for the empty path (ie. ""), replace it
467 with the root path. This happens because of stripping done at
468 various levels in svnserve that remove the leading / on an
469 absolute path. Passing such a malformed path to the authz
470 routines throws them into an infinite loop and makes them miss
473 path = svn_fspath__canonicalize(path, pool);
475 /* If we have a username, and we've not yet used it + any username
476 case normalization that might be requested to determine "the
477 username we used for authz purposes", do so now. */
478 if (b->user && (! b->authz_user))
480 char *authz_user = apr_pstrdup(b->pool, b->user);
481 if (b->username_case == CASE_FORCE_UPPER)
482 convert_case(authz_user, TRUE);
483 else if (b->username_case == CASE_FORCE_LOWER)
484 convert_case(authz_user, FALSE);
485 b->authz_user = authz_user;
488 SVN_ERR(svn_repos_authz_check_access(b->authzdb, b->authz_repos_name,
489 path, b->authz_user, required,
492 SVN_ERR(log_authz_denied(path, required, b, conn, pool));
497 /* Set *ALLOWED to TRUE if PATH is readable by the user described in
498 * BATON. Use POOL for temporary allocations only. ROOT is not used.
499 * Implements the svn_repos_authz_func_t interface.
501 static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed,
507 authz_baton_t *sb = baton;
509 return authz_check_access(allowed, path, svn_authz_read,
510 sb->server, sb->conn, pool);
513 /* If authz is enabled in the specified BATON, return a read authorization
514 function. Otherwise, return NULL. */
515 static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton)
518 return authz_check_access_cb;
522 /* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
523 * according to the state in BATON. Use POOL for temporary
524 * allocations only. ROOT is not used. Implements the
525 * svn_repos_authz_callback_t interface.
527 static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required,
528 svn_boolean_t *allowed,
534 authz_baton_t *sb = baton;
536 return authz_check_access(allowed, path, required,
537 sb->server, sb->conn, pool);
541 enum access_type get_access(server_baton_t *b, enum authn_type auth)
543 const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS :
544 SVN_CONFIG_OPTION_ANON_ACCESS;
545 const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read";
546 enum access_type result;
548 svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def);
549 result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
550 strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
551 return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result;
554 static enum access_type current_access(server_baton_t *b)
556 return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED);
559 /* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME
560 is true, don't send anonymous mech even if that would give the desired
562 static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
563 server_baton_t *b, enum access_type required,
564 svn_boolean_t needs_username)
566 if (!needs_username && get_access(b, UNAUTHENTICATED) >= required)
567 SVN_ERR(svn_ra_svn__write_word(conn, pool, "ANONYMOUS"));
568 if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required)
569 SVN_ERR(svn_ra_svn__write_word(conn, pool, "EXTERNAL"));
570 if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
571 SVN_ERR(svn_ra_svn__write_word(conn, pool, "CRAM-MD5"));
575 /* Context for cleanup handler. */
576 struct cleanup_fs_access_baton
582 /* Pool cleanup handler. Make sure fs's access_t points to NULL when
583 the command pool is destroyed. */
584 static apr_status_t cleanup_fs_access(void *data)
587 struct cleanup_fs_access_baton *baton = data;
589 serr = svn_fs_set_access(baton->fs, NULL);
592 apr_status_t apr_err = serr->apr_err;
593 svn_error_clear(serr);
601 /* Create an svn_fs_access_t in POOL for USER and associate it with
602 B's filesystem. Also, register a cleanup handler with POOL which
603 de-associates the svn_fs_access_t from B's filesystem. */
605 create_fs_access(server_baton_t *b, apr_pool_t *pool)
607 svn_fs_access_t *fs_access;
608 struct cleanup_fs_access_baton *cleanup_baton;
613 SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool));
614 SVN_ERR(svn_fs_set_access(b->fs, fs_access));
616 cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
617 cleanup_baton->pool = pool;
618 cleanup_baton->fs = b->fs;
619 apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
620 apr_pool_cleanup_null);
625 /* Authenticate, once the client has chosen a mechanism and possibly
626 * sent an initial mechanism token. On success, set *success to true
627 * and b->user to the authenticated username (or NULL for anonymous).
628 * On authentication failure, report failure to the client and set
629 * *success to FALSE. On communications failure, return an error.
630 * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
631 static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
632 const char *mech, const char *mecharg,
633 server_baton_t *b, enum access_type required,
634 svn_boolean_t needs_username,
635 svn_boolean_t *success)
640 if (get_access(b, AUTHENTICATED) >= required
641 && b->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
643 if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0)
644 return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure",
645 "Requested username does not match");
646 b->user = b->tunnel_user;
647 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success"));
652 if (get_access(b, UNAUTHENTICATED) >= required
653 && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
655 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success"));
660 if (get_access(b, AUTHENTICATED) >= required
661 && b->pwdb && strcmp(mech, "CRAM-MD5") == 0)
663 SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success));
664 b->user = apr_pstrdup(b->pool, user);
668 return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure",
669 "Must authenticate with listed mechanism");
672 /* Perform an authentication request using the built-in SASL implementation. */
674 internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
675 server_baton_t *b, enum access_type required,
676 svn_boolean_t needs_username)
678 svn_boolean_t success;
679 const char *mech, *mecharg;
681 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
682 SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
683 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)c)", b->realm));
686 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
689 SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username,
696 /* Perform an authentication request in order to get an access level of
697 * REQUIRED or higher. Since the client may escape the authentication
698 * exchange, the caller should check current_access(b) to see if
699 * authentication succeeded. */
700 static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
701 server_baton_t *b, enum access_type required,
702 svn_boolean_t needs_username)
706 return cyrus_auth_request(conn, pool, b, required, needs_username);
709 return internal_auth_request(conn, pool, b, required, needs_username);
712 /* Send a trivial auth notification on CONN which lists no mechanisms,
713 * indicating that authentication is unnecessary. Usually called in
714 * response to invocation of a svnserve command.
716 static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
717 apr_pool_t *pool, server_baton_t *b)
719 return svn_ra_svn__write_cmd_response(conn, pool, "()c", "");
722 /* Ensure that the client has the REQUIRED access by checking the
723 * access directives (both blanket and per-directory) in BATON. If
724 * PATH is NULL, then only the blanket access configuration will
727 * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the
728 * user described in BATON is authenticated and, well, has a username
731 * Use POOL for temporary allocations only.
733 static svn_boolean_t lookup_access(apr_pool_t *pool,
734 server_baton_t *baton,
735 svn_ra_svn_conn_t *conn,
736 svn_repos_authz_access_t required,
738 svn_boolean_t needs_username)
740 enum access_type req = (required & svn_authz_write) ?
741 WRITE_ACCESS : READ_ACCESS;
742 svn_boolean_t authorized;
745 /* Get authz's opinion on the access. */
746 err = authz_check_access(&authorized, path, required, baton, conn, pool);
748 /* If an error made lookup fail, deny access. */
751 log_server_error(err, baton, conn, pool);
752 svn_error_clear(err);
756 /* If the required access is blanket-granted AND granted by authz
757 AND we already have a username if one is required, then the
758 lookup has succeeded. */
759 if (current_access(baton) >= req
761 && (! needs_username || baton->user))
767 /* Check that the client has the REQUIRED access by consulting the
768 * authentication and authorization states stored in BATON. If the
769 * client does not have the required access credentials, attempt to
770 * authenticate the client to get that access, using CONN for
773 * This function is supposed to be called to handle the authentication
774 * half of a standard svn protocol reply. If an error is returned, it
775 * probably means that the server can terminate the client connection
776 * with an apologetic error, as it implies an authentication failure.
778 * PATH and NEEDS_USERNAME are passed along to lookup_access, their
779 * behaviour is documented there.
781 static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn,
784 svn_repos_authz_access_t required,
786 svn_boolean_t needs_username)
788 enum access_type req = (required & svn_authz_write) ?
789 WRITE_ACCESS : READ_ACCESS;
791 /* See whether the user already has the required access. If so,
792 nothing needs to be done. Create the FS access and send a
793 trivial auth request. */
794 if (lookup_access(pool, b, conn, required, path, needs_username))
796 SVN_ERR(create_fs_access(b, pool));
797 return trivial_auth_request(conn, pool, b);
800 /* If the required blanket access can be obtained by authenticating,
801 try that. Unfortunately, we can't tell until after
802 authentication whether authz will work or not. We force
803 requiring a username because we need one to be able to check
804 authz configuration again with a different user credentials than
805 the first time round. */
807 && get_access(b, AUTHENTICATED) >= req
808 && (b->tunnel_user || b->pwdb || b->use_sasl))
809 SVN_ERR(auth_request(conn, pool, b, req, TRUE));
811 /* Now that an authentication has been done get the new take of
812 authz on the request. */
813 if (! lookup_access(pool, b, conn, required, path, needs_username))
814 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
815 error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
816 NULL, NULL, b, conn, pool),
819 /* Else, access is granted, and there is much rejoicing. */
820 SVN_ERR(create_fs_access(b, pool));
825 /* --- REPORTER COMMAND SET --- */
827 /* To allow for pipelining, reporter commands have no reponses. If we
828 * get an error, we ignore all subsequent reporter commands and return
829 * the error finish_report, to be handled by the calling command.
832 static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
833 apr_array_header_t *params, void *baton)
835 report_driver_baton_t *b = baton;
836 const char *path, *lock_token, *depth_word;
838 /* Default to infinity, for old clients that don't send depth. */
839 svn_depth_t depth = svn_depth_infinity;
840 svn_boolean_t start_empty;
842 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crb?(?c)?w",
843 &path, &rev, &start_empty, &lock_token,
846 depth = svn_depth_from_word(depth_word);
847 path = svn_relpath_canonicalize(path, pool);
848 if (b->from_rev && strcmp(path, "") == 0)
851 b->err = svn_repos_set_path3(b->report_baton, path, rev, depth,
852 start_empty, lock_token, pool);
855 b->only_empty_entries = FALSE;
859 static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
860 apr_array_header_t *params, void *baton)
862 report_driver_baton_t *b = baton;
865 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
866 path = svn_relpath_canonicalize(path, pool);
868 b->err = svn_repos_delete_path(b->report_baton, path, pool);
872 static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
873 apr_array_header_t *params, void *baton)
875 report_driver_baton_t *b = baton;
876 const char *path, *url, *lock_token, *fs_path, *depth_word;
878 svn_boolean_t start_empty;
879 /* Default to infinity, for old clients that don't send depth. */
880 svn_depth_t depth = svn_depth_infinity;
882 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccrb?(?c)?w",
883 &path, &url, &rev, &start_empty,
884 &lock_token, &depth_word));
886 /* ### WHAT?! The link path is an absolute URL?! Didn't see that
887 coming... -- cmpilato */
888 path = svn_relpath_canonicalize(path, pool);
889 url = svn_uri_canonicalize(url, pool);
891 depth = svn_depth_from_word(depth_word);
893 b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool),
894 svn_path_uri_decode(url, pool),
897 b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev,
898 depth, start_empty, lock_token, pool);
903 static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
904 apr_array_header_t *params, void *baton)
906 report_driver_baton_t *b = baton;
908 /* No arguments to parse. */
909 SVN_ERR(trivial_auth_request(conn, pool, b->sb));
911 b->err = svn_repos_finish_report(b->report_baton, pool);
915 static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
916 apr_array_header_t *params, void *baton)
918 report_driver_baton_t *b = baton;
920 /* No arguments to parse. */
921 svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
925 static const svn_ra_svn_cmd_entry_t report_commands[] = {
926 { "set-path", set_path },
927 { "delete-path", delete_path },
928 { "link-path", link_path },
929 { "finish-report", finish_report, TRUE },
930 { "abort-report", abort_report, TRUE },
934 /* Accept a report from the client, drive the network editor with the
935 * result, and then write an empty command response. If there is a
936 * non-protocol failure, accept_report will abort the edit and return
937 * a command error to be reported by handle_commands().
939 * If only_empty_entry is not NULL and the report contains only one
940 * item, and that item is empty, set *only_empty_entry to TRUE, else
943 * If from_rev is not NULL, set *from_rev to the revision number from
944 * the set-path on ""; if somehow set-path "" never happens, set
945 * *from_rev to SVN_INVALID_REVNUM.
947 static svn_error_t *accept_report(svn_boolean_t *only_empty_entry,
948 svn_revnum_t *from_rev,
949 svn_ra_svn_conn_t *conn, apr_pool_t *pool,
950 server_baton_t *b, svn_revnum_t rev,
951 const char *target, const char *tgt_path,
952 svn_boolean_t text_deltas,
954 svn_boolean_t send_copyfrom_args,
955 svn_boolean_t ignore_ancestry)
957 const svn_delta_editor_t *editor;
958 void *edit_baton, *report_baton;
959 report_driver_baton_t rb;
966 /* Make an svn_repos report baton. Tell it to drive the network editor
967 * when the report is complete. */
968 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
969 SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev, b->repos,
970 b->fs_path->data, target, tgt_path,
971 text_deltas, depth, ignore_ancestry,
974 authz_check_access_cb_func(b),
975 &ab, svn_ra_svn_zero_copy_limit(conn),
979 rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
980 rb.report_baton = report_baton;
982 rb.entry_counter = 0;
983 rb.only_empty_entries = TRUE;
984 rb.from_rev = from_rev;
986 *from_rev = SVN_INVALID_REVNUM;
987 err = svn_ra_svn__handle_commands2(conn, pool, report_commands, &rb, TRUE);
990 /* Network or protocol error while handling commands. */
991 svn_error_clear(rb.err);
996 /* Some failure during the reporting or editing operations. */
999 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1001 if (only_empty_entry)
1002 *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries;
1004 return SVN_NO_ERROR;
1007 /* --- MAIN COMMAND SET --- */
1009 /* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t
1011 static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
1013 const apr_array_header_t *propdiffs)
1017 for (i = 0; i < propdiffs->nelts; ++i)
1019 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
1021 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "c(?s)",
1022 prop->name, prop->value));
1025 return SVN_NO_ERROR;
1028 /* Write out a lock to the client. */
1029 static svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
1033 const char *cdate, *edate;
1035 cdate = svn_time_to_cstring(lock->creation_date, pool);
1036 edate = lock->expiration_date
1037 ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
1038 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
1039 lock->token, lock->owner, lock->comment,
1042 return SVN_NO_ERROR;
1045 /* ### This really belongs in libsvn_repos. */
1046 /* Get the explicit properties and/or inherited properties for a PATH in
1047 ROOT, with hardcoded committed-info values. */
1048 static svn_error_t *
1049 get_props(apr_hash_t **props,
1050 apr_array_header_t **iprops,
1052 svn_fs_root_t *root,
1056 /* Get the explicit properties. */
1061 const char *cdate, *cauthor, *uuid;
1063 SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
1065 /* Hardcode the values for the committed revision, date, and author. */
1066 SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
1068 str = svn_string_create(apr_psprintf(pool, "%ld", crev),
1070 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, str);
1071 str = (cdate) ? svn_string_create(cdate, pool) : NULL;
1072 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, str);
1073 str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
1074 svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, str);
1076 /* Hardcode the values for the UUID. */
1077 SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
1078 str = (uuid) ? svn_string_create(uuid, pool) : NULL;
1079 svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, str);
1082 /* Get any inherited properties the user is authorized to. */
1085 SVN_ERR(svn_repos_fs_get_inherited_props(
1086 iprops, root, path, NULL,
1087 authz_check_access_cb_func(b->server),
1091 return SVN_NO_ERROR;
1094 /* Set BATON->FS_PATH for the repository URL found in PARAMS. */
1095 static svn_error_t *reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1096 apr_array_header_t *params, void *baton)
1098 server_baton_t *b = baton;
1100 const char *fs_path;
1102 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &url));
1103 url = svn_uri_canonicalize(url, pool);
1104 SVN_ERR(trivial_auth_request(conn, pool, b));
1105 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1106 svn_path_uri_decode(url, pool),
1108 SVN_ERR(log_command(b, conn, pool, "%s", svn_log__reparent(fs_path, pool)));
1109 svn_stringbuf_set(b->fs_path, fs_path);
1110 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1111 return SVN_NO_ERROR;
1114 static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1115 apr_array_header_t *params, void *baton)
1117 server_baton_t *b = baton;
1120 SVN_ERR(log_command(b, conn, pool, "get-latest-rev"));
1122 SVN_ERR(trivial_auth_request(conn, pool, b));
1123 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1124 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
1125 return SVN_NO_ERROR;
1128 static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1129 apr_array_header_t *params, void *baton)
1131 server_baton_t *b = baton;
1134 const char *timestr;
1136 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", ×tr));
1137 SVN_ERR(log_command(b, conn, pool, "get-dated-rev %s", timestr));
1139 SVN_ERR(trivial_auth_request(conn, pool, b));
1140 SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
1141 SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool));
1142 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
1143 return SVN_NO_ERROR;
1146 /* Common logic for change_rev_prop() and change_rev_prop2(). */
1147 static svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn,
1151 const svn_string_t *const *old_value_p,
1152 const svn_string_t *value,
1160 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
1161 SVN_ERR(log_command(b, conn, pool, "%s",
1162 svn_log__change_rev_prop(rev, name, pool)));
1163 SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repos, rev, b->user,
1164 name, old_value_p, value,
1166 authz_check_access_cb_func(b), &ab,
1168 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1170 return SVN_NO_ERROR;
1173 static svn_error_t *change_rev_prop2(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1174 apr_array_header_t *params, void *baton)
1176 server_baton_t *b = baton;
1179 svn_string_t *value;
1180 const svn_string_t *const *old_value_p;
1181 svn_string_t *old_value;
1182 svn_boolean_t dont_care;
1184 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc(?s)(b?s)",
1185 &rev, &name, &value,
1186 &dont_care, &old_value));
1188 /* Argument parsing. */
1192 old_value_p = (const svn_string_t *const *)&old_value;
1194 /* Input validation. */
1195 if (dont_care && old_value)
1198 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1199 "'previous-value' and 'dont-care' cannot both be "
1200 "set in 'change-rev-prop2' request");
1201 return log_fail_and_flush(err, b, conn, pool);
1205 SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool));
1207 return SVN_NO_ERROR;
1210 static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1211 apr_array_header_t *params, void *baton)
1213 server_baton_t *b = baton;
1216 svn_string_t *value;
1218 /* Because the revprop value was at one time mandatory, the usual
1219 optional element pattern "(?s)" isn't used. */
1220 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc?s", &rev, &name, &value));
1222 SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool));
1224 return SVN_NO_ERROR;
1227 static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1228 apr_array_header_t *params, void *baton)
1230 server_baton_t *b = baton;
1238 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
1239 SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool)));
1241 SVN_ERR(trivial_auth_request(conn, pool, b));
1242 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
1243 authz_check_access_cb_func(b), &ab,
1245 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
1246 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1247 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1248 return SVN_NO_ERROR;
1251 static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1252 apr_array_header_t *params, void *baton)
1254 server_baton_t *b = baton;
1257 svn_string_t *value;
1263 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc", &rev, &name));
1264 SVN_ERR(log_command(b, conn, pool, "%s",
1265 svn_log__rev_prop(rev, name, pool)));
1267 SVN_ERR(trivial_auth_request(conn, pool, b));
1268 SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name,
1269 authz_check_access_cb_func(b), &ab,
1271 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(?s)", value));
1272 return SVN_NO_ERROR;
1275 static svn_error_t *commit_done(const svn_commit_info_t *commit_info,
1276 void *baton, apr_pool_t *pool)
1278 commit_callback_baton_t *ccb = baton;
1280 *ccb->new_rev = commit_info->revision;
1281 *ccb->date = commit_info->date
1282 ? apr_pstrdup(ccb->pool, commit_info->date): NULL;
1283 *ccb->author = commit_info->author
1284 ? apr_pstrdup(ccb->pool, commit_info->author) : NULL;
1285 *ccb->post_commit_err = commit_info->post_commit_err
1286 ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL;
1287 return SVN_NO_ERROR;
1290 /* Add the LOCK_TOKENS (if any) to the filesystem access context,
1291 * checking path authorizations using the state in SB as we go.
1292 * LOCK_TOKENS is an array of svn_ra_svn_item_t structs. Return a
1293 * client error if LOCK_TOKENS is not a list of lists. If a lock
1294 * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
1295 * to the client. Use POOL for temporary allocations only.
1297 static svn_error_t *add_lock_tokens(svn_ra_svn_conn_t *conn,
1298 const apr_array_header_t *lock_tokens,
1303 svn_fs_access_t *fs_access;
1305 SVN_ERR(svn_fs_get_access(&fs_access, sb->fs));
1307 /* If there is no access context, nowhere to add the tokens. */
1309 return SVN_NO_ERROR;
1311 for (i = 0; i < lock_tokens->nelts; ++i)
1313 const char *path, *token, *full_path;
1314 svn_ra_svn_item_t *path_item, *token_item;
1315 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i,
1317 if (item->kind != SVN_RA_SVN_LIST)
1318 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1319 "Lock tokens aren't a list of lists");
1321 path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1322 if (path_item->kind != SVN_RA_SVN_STRING)
1323 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1324 "Lock path isn't a string");
1326 token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
1327 if (token_item->kind != SVN_RA_SVN_STRING)
1328 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1329 "Lock token isn't a string");
1331 path = path_item->u.string->data;
1332 full_path = svn_fspath__join(sb->fs_path->data,
1333 svn_relpath_canonicalize(path, pool),
1336 if (! lookup_access(pool, sb, conn, svn_authz_write,
1338 return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
1341 token = token_item->u.string->data;
1342 SVN_ERR(svn_fs_access_add_lock_token2(fs_access, path, token));
1345 return SVN_NO_ERROR;
1348 /* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
1349 LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */
1350 static svn_error_t *unlock_paths(const apr_array_header_t *lock_tokens,
1352 svn_ra_svn_conn_t *conn,
1356 apr_pool_t *iterpool;
1358 iterpool = svn_pool_create(pool);
1360 for (i = 0; i < lock_tokens->nelts; ++i)
1362 svn_ra_svn_item_t *item, *path_item, *token_item;
1363 const char *path, *token, *full_path;
1365 svn_pool_clear(iterpool);
1367 item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
1368 path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1369 token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
1371 path = path_item->u.string->data;
1372 token = token_item->u.string->data;
1374 full_path = svn_fspath__join(sb->fs_path->data,
1375 svn_relpath_canonicalize(path, iterpool),
1378 /* The lock may have become defunct after the commit, so ignore such
1380 err = svn_repos_fs_unlock(sb->repos, full_path, token,
1382 log_server_error(err, sb, conn, iterpool);
1383 svn_error_clear(err);
1386 svn_pool_destroy(iterpool);
1388 return SVN_NO_ERROR;
1391 static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1392 apr_array_header_t *params, void *baton)
1394 server_baton_t *b = baton;
1395 const char *log_msg = NULL,
1398 *post_commit_err = NULL;
1399 apr_array_header_t *lock_tokens;
1400 svn_boolean_t keep_locks;
1401 apr_array_header_t *revprop_list = NULL;
1402 apr_hash_t *revprop_table;
1403 const svn_delta_editor_t *editor;
1405 svn_boolean_t aborted;
1406 commit_callback_baton_t ccb;
1407 svn_revnum_t new_rev;
1413 if (params->nelts == 1)
1415 /* Clients before 1.2 don't send lock-tokens, keep-locks,
1416 and rev-props fields. */
1417 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &log_msg));
1420 revprop_list = NULL;
1424 /* Clients before 1.5 don't send the rev-props field. */
1425 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "clb?l", &log_msg,
1426 &lock_tokens, &keep_locks,
1430 /* The handling for locks is a little problematic, because the
1431 protocol won't let us send several auth requests once one has
1432 succeeded. So we request write access and a username before
1433 adding tokens (if we have any), and subsequently fail if a lock
1435 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
1437 (lock_tokens && lock_tokens->nelts)));
1439 /* Authorize the lock tokens and give them to the FS if we got
1441 if (lock_tokens && lock_tokens->nelts)
1442 SVN_CMD_ERR(add_lock_tokens(conn, lock_tokens, b, pool));
1444 /* Ignore LOG_MSG, per the protocol. See ra_svn_commit(). */
1446 SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table));
1449 revprop_table = apr_hash_make(pool);
1450 svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
1451 svn_string_create(log_msg, pool));
1454 /* Get author from the baton, making sure clients can't circumvent
1455 the authentication via the revision props. */
1456 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
1457 b->user ? svn_string_create(b->user, pool) : NULL);
1460 ccb.new_rev = &new_rev;
1462 ccb.author = &author;
1463 ccb.post_commit_err = &post_commit_err;
1464 /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
1465 SVN_CMD_ERR(svn_repos_get_commit_editor5
1466 (&editor, &edit_baton, b->repos, NULL,
1467 svn_path_uri_decode(b->repos_url, pool),
1468 b->fs_path->data, revprop_table,
1470 authz_commit_cb, &ab, pool));
1471 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1472 SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton,
1476 SVN_ERR(log_command(b, conn, pool, "%s",
1477 svn_log__commit(new_rev, pool)));
1478 SVN_ERR(trivial_auth_request(conn, pool, b));
1480 /* In tunnel mode, deltify before answering the client, because
1481 answering may cause the client to terminate the connection
1482 and thus kill the server. But otherwise, deltify after
1483 answering the client, to avoid user-visible delay. */
1486 SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1488 /* Unlock the paths. */
1489 if (! keep_locks && lock_tokens && lock_tokens->nelts)
1490 SVN_ERR(unlock_paths(lock_tokens, b, conn, pool));
1492 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)",
1493 new_rev, date, author, post_commit_err));
1496 SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1498 return SVN_NO_ERROR;
1501 static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1502 apr_array_header_t *params, void *baton)
1504 server_baton_t *b = baton;
1505 const char *path, *full_path, *hex_digest;
1507 svn_fs_root_t *root;
1508 svn_stream_t *contents;
1509 apr_hash_t *props = NULL;
1510 apr_array_header_t *inherited_props;
1511 svn_string_t write_str;
1514 svn_boolean_t want_props, want_contents;
1515 apr_uint64_t wants_inherited_props;
1516 svn_checksum_t *checksum;
1517 svn_error_t *err, *write_err;
1524 /* Parse arguments. */
1525 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?B", &path, &rev,
1526 &want_props, &want_contents,
1527 &wants_inherited_props));
1529 if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1530 wants_inherited_props = FALSE;
1532 full_path = svn_fspath__join(b->fs_path->data,
1533 svn_relpath_canonicalize(path, pool), pool);
1535 /* Check authorizations */
1536 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1539 if (!SVN_IS_VALID_REVNUM(rev))
1540 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1542 SVN_ERR(log_command(b, conn, pool, "%s",
1543 svn_log__get_file(full_path, rev,
1544 want_contents, want_props, pool)));
1546 /* Fetch the properties and a stream for the contents. */
1547 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1548 SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
1549 full_path, TRUE, pool));
1550 hex_digest = svn_checksum_to_cstring_display(checksum, pool);
1552 /* Fetch the file's explicit and/or inherited properties if
1553 requested. Although the wants-iprops boolean was added to the
1554 protocol in 1.8 a standard 1.8 client never requests iprops. */
1555 if (want_props || wants_inherited_props)
1556 SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1557 wants_inherited_props ? &inherited_props : NULL,
1558 &ab, root, full_path,
1561 SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
1563 /* Send successful command response with revision and props. */
1564 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success",
1566 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1568 if (wants_inherited_props)
1570 apr_pool_t *iterpool = svn_pool_create(pool);
1572 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1573 for (i = 0; i < inherited_props->nelts; i++)
1575 svn_prop_inherited_item_t *iprop =
1576 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1578 svn_pool_clear(iterpool);
1579 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1580 iprop->path_or_url));
1581 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1582 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1583 iprop->path_or_url));
1585 svn_pool_destroy(iterpool);
1588 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1590 /* Now send the file's contents. */
1597 err = svn_stream_read(contents, buf, &len);
1602 write_str.data = buf;
1603 write_str.len = len;
1604 SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str));
1606 if (len < sizeof(buf))
1608 err = svn_stream_close(contents);
1612 write_err = svn_ra_svn__write_cstring(conn, pool, "");
1615 svn_error_clear(err);
1619 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1622 return SVN_NO_ERROR;
1625 static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1626 apr_array_header_t *params, void *baton)
1628 server_baton_t *b = baton;
1629 const char *path, *full_path;
1631 apr_hash_t *entries, *props = NULL;
1632 apr_array_header_t *inherited_props;
1633 apr_hash_index_t *hi;
1634 svn_fs_root_t *root;
1635 apr_pool_t *subpool;
1636 svn_boolean_t want_props, want_contents;
1637 apr_uint64_t wants_inherited_props;
1638 apr_uint64_t dirent_fields;
1639 apr_array_header_t *dirent_fields_list = NULL;
1640 svn_ra_svn_item_t *elt;
1647 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?l?B", &path, &rev,
1648 &want_props, &want_contents,
1649 &dirent_fields_list,
1650 &wants_inherited_props));
1652 if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1653 wants_inherited_props = FALSE;
1655 if (! dirent_fields_list)
1657 dirent_fields = SVN_DIRENT_ALL;
1663 for (i = 0; i < dirent_fields_list->nelts; ++i)
1665 elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t);
1667 if (elt->kind != SVN_RA_SVN_WORD)
1668 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1669 "Dirent field not a string");
1671 if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0)
1672 dirent_fields |= SVN_DIRENT_KIND;
1673 else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0)
1674 dirent_fields |= SVN_DIRENT_SIZE;
1675 else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0)
1676 dirent_fields |= SVN_DIRENT_HAS_PROPS;
1677 else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0)
1678 dirent_fields |= SVN_DIRENT_CREATED_REV;
1679 else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0)
1680 dirent_fields |= SVN_DIRENT_TIME;
1681 else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0)
1682 dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
1686 full_path = svn_fspath__join(b->fs_path->data,
1687 svn_relpath_canonicalize(path, pool), pool);
1689 /* Check authorizations */
1690 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1693 if (!SVN_IS_VALID_REVNUM(rev))
1694 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1696 SVN_ERR(log_command(b, conn, pool, "%s",
1697 svn_log__get_dir(full_path, rev,
1698 want_contents, want_props,
1699 dirent_fields, pool)));
1701 /* Fetch the root of the appropriate revision. */
1702 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1704 /* Fetch the directory's explicit and/or inherited properties if
1705 requested. Although the wants-iprops boolean was added to the
1706 protocol in 1.8 a standard 1.8 client never requests iprops. */
1707 if (want_props || wants_inherited_props)
1708 SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1709 wants_inherited_props ? &inherited_props : NULL,
1710 &ab, root, full_path,
1713 /* Begin response ... */
1714 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev));
1715 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1716 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!"));
1718 /* Fetch the directory entries if requested and send them immediately. */
1721 /* Use epoch for a placeholder for a missing date. */
1722 const char *missing_date = svn_time_to_cstring(0, pool);
1724 SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
1726 /* Transform the hash table's FS entries into dirents. This probably
1727 * belongs in libsvn_repos. */
1728 subpool = svn_pool_create(pool);
1729 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1731 const char *name = svn__apr_hash_index_key(hi);
1732 svn_fs_dirent_t *fsent = svn__apr_hash_index_val(hi);
1733 const char *file_path;
1735 /* The fields in the entry tuple. */
1736 svn_node_kind_t entry_kind = svn_node_none;
1737 svn_filesize_t entry_size = 0;
1738 svn_boolean_t has_props = FALSE;
1739 /* If 'created rev' was not requested, send 0. We can't use
1740 * SVN_INVALID_REVNUM as the tuple field is not optional.
1741 * See the email thread on dev@, 2012-03-28, subject
1742 * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra",
1743 * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */
1744 svn_revnum_t created_rev = 0;
1745 const char *cdate = NULL;
1746 const char *last_author = NULL;
1748 svn_pool_clear(subpool);
1750 file_path = svn_fspath__join(full_path, name, subpool);
1751 if (! lookup_access(subpool, b, conn, svn_authz_read,
1755 if (dirent_fields & SVN_DIRENT_KIND)
1756 entry_kind = fsent->kind;
1758 if (dirent_fields & SVN_DIRENT_SIZE)
1759 if (entry_kind != svn_node_dir)
1760 SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path,
1763 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1765 apr_hash_t *file_props;
1768 SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
1770 has_props = (apr_hash_count(file_props) > 0);
1773 if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1774 || (dirent_fields & SVN_DIRENT_TIME)
1775 || (dirent_fields & SVN_DIRENT_CREATED_REV))
1777 /* created_rev, last_author, time */
1778 SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev,
1786 /* The client does not properly handle a missing CDATE. For
1787 interoperability purposes, we must fill in some junk.
1789 See libsvn_ra_svn/client.c:ra_svn_get_dir() */
1791 cdate = missing_date;
1793 /* Send the entry. */
1794 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
1795 svn_node_kind_to_word(entry_kind),
1796 (apr_uint64_t) entry_size,
1797 has_props, created_rev,
1798 cdate, last_author));
1800 svn_pool_destroy(subpool);
1803 if (wants_inherited_props)
1805 apr_pool_t *iterpool = svn_pool_create(pool);
1807 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1808 for (i = 0; i < inherited_props->nelts; i++)
1810 svn_prop_inherited_item_t *iprop =
1811 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1813 svn_pool_clear(iterpool);
1814 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1815 iprop->path_or_url));
1816 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1817 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1818 iprop->path_or_url));
1820 svn_pool_destroy(iterpool);
1823 /* Finish response. */
1824 return svn_ra_svn__write_tuple(conn, pool, "!))");
1827 static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1828 apr_array_header_t *params, void *baton)
1830 server_baton_t *b = baton;
1832 const char *target, *full_path, *depth_word;
1833 svn_boolean_t recurse;
1834 apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
1835 apr_uint64_t ignore_ancestry; /* Optional; default FALSE */
1836 /* Default to unknown. Old clients won't send depth, but we'll
1837 handle that by converting recurse if necessary. */
1838 svn_depth_t depth = svn_depth_unknown;
1839 svn_boolean_t is_checkout;
1841 /* Parse the arguments. */
1842 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cb?wB?B", &rev, &target,
1843 &recurse, &depth_word,
1844 &send_copyfrom_args, &ignore_ancestry));
1845 target = svn_relpath_canonicalize(target, pool);
1848 depth = svn_depth_from_word(depth_word);
1850 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1852 full_path = svn_fspath__join(b->fs_path->data, target, pool);
1853 /* Check authorization and authenticate the user if necessary. */
1854 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
1856 if (!SVN_IS_VALID_REVNUM(rev))
1857 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1859 SVN_ERR(accept_report(&is_checkout, NULL,
1860 conn, pool, b, rev, target, NULL, TRUE,
1862 (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
1863 (ignore_ancestry == TRUE) /* ignore_ancestry */));
1866 SVN_ERR(log_command(b, conn, pool, "%s",
1867 svn_log__checkout(full_path, rev,
1872 SVN_ERR(log_command(b, conn, pool, "%s",
1873 svn_log__update(full_path, rev, depth,
1874 send_copyfrom_args, pool)));
1877 return SVN_NO_ERROR;
1880 static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1881 apr_array_header_t *params, void *baton)
1883 server_baton_t *b = baton;
1885 const char *target, *depth_word;
1886 const char *switch_url, *switch_path;
1887 svn_boolean_t recurse;
1888 /* Default to unknown. Old clients won't send depth, but we'll
1889 handle that by converting recurse if necessary. */
1890 svn_depth_t depth = svn_depth_unknown;
1891 apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
1892 apr_uint64_t ignore_ancestry; /* Optional; default TRUE */
1894 /* Parse the arguments. */
1895 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbc?w?BB", &rev, &target,
1896 &recurse, &switch_url, &depth_word,
1897 &send_copyfrom_args, &ignore_ancestry));
1898 target = svn_relpath_canonicalize(target, pool);
1899 switch_url = svn_uri_canonicalize(switch_url, pool);
1902 depth = svn_depth_from_word(depth_word);
1904 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1906 SVN_ERR(trivial_auth_request(conn, pool, b));
1907 if (!SVN_IS_VALID_REVNUM(rev))
1908 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1910 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1911 svn_path_uri_decode(switch_url, pool),
1915 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1916 SVN_ERR(log_command(b, conn, pool, "%s",
1917 svn_log__switch(full_path, switch_path, rev,
1921 return accept_report(NULL, NULL,
1922 conn, pool, b, rev, target, switch_path, TRUE,
1924 (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
1925 (ignore_ancestry != FALSE) /* ignore_ancestry */);
1928 static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1929 apr_array_header_t *params, void *baton)
1931 server_baton_t *b = baton;
1933 const char *target, *depth_word;
1934 svn_boolean_t recurse;
1935 /* Default to unknown. Old clients won't send depth, but we'll
1936 handle that by converting recurse if necessary. */
1937 svn_depth_t depth = svn_depth_unknown;
1939 /* Parse the arguments. */
1940 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cb?(?r)?w",
1941 &target, &recurse, &rev, &depth_word));
1942 target = svn_relpath_canonicalize(target, pool);
1945 depth = svn_depth_from_word(depth_word);
1947 depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
1949 SVN_ERR(trivial_auth_request(conn, pool, b));
1950 if (!SVN_IS_VALID_REVNUM(rev))
1951 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1954 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1955 SVN_ERR(log_command(b, conn, pool, "%s",
1956 svn_log__status(full_path, rev, depth, pool)));
1959 return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
1960 depth, FALSE, FALSE);
1963 static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1964 apr_array_header_t *params, void *baton)
1966 server_baton_t *b = baton;
1968 const char *target, *versus_url, *versus_path, *depth_word;
1969 svn_boolean_t recurse, ignore_ancestry;
1970 svn_boolean_t text_deltas;
1971 /* Default to unknown. Old clients won't send depth, but we'll
1972 handle that by converting recurse if necessary. */
1973 svn_depth_t depth = svn_depth_unknown;
1975 /* Parse the arguments. */
1976 if (params->nelts == 5)
1978 /* Clients before 1.4 don't send the text_deltas boolean or depth. */
1979 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
1980 &recurse, &ignore_ancestry, &versus_url));
1986 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbcb?w",
1987 &rev, &target, &recurse,
1988 &ignore_ancestry, &versus_url,
1989 &text_deltas, &depth_word));
1991 target = svn_relpath_canonicalize(target, pool);
1992 versus_url = svn_uri_canonicalize(versus_url, pool);
1995 depth = svn_depth_from_word(depth_word);
1997 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1999 SVN_ERR(trivial_auth_request(conn, pool, b));
2001 if (!SVN_IS_VALID_REVNUM(rev))
2002 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2003 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
2004 svn_path_uri_decode(versus_url, pool),
2008 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
2009 svn_revnum_t from_rev;
2010 SVN_ERR(accept_report(NULL, &from_rev,
2011 conn, pool, b, rev, target, versus_path,
2012 text_deltas, depth, FALSE, ignore_ancestry));
2013 SVN_ERR(log_command(b, conn, pool, "%s",
2014 svn_log__diff(full_path, from_rev, versus_path,
2015 rev, depth, ignore_ancestry,
2018 return SVN_NO_ERROR;
2021 /* Regardless of whether a client's capabilities indicate an
2022 understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
2023 we provide a response.
2025 ASSUMPTION: When performing a 'merge' with two URLs at different
2026 revisions, the client will call this command more than once. */
2027 static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2028 apr_array_header_t *params, void *baton)
2030 server_baton_t *b = baton;
2032 apr_array_header_t *paths, *canonical_paths;
2033 svn_mergeinfo_catalog_t mergeinfo;
2035 apr_hash_index_t *hi;
2036 const char *inherit_word;
2037 svn_mergeinfo_inheritance_t inherit;
2038 svn_boolean_t include_descendants;
2039 apr_pool_t *iterpool;
2045 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)wb", &paths, &rev,
2046 &inherit_word, &include_descendants));
2047 inherit = svn_inheritance_from_word(inherit_word);
2049 /* Canonicalize the paths which mergeinfo has been requested for. */
2050 canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2051 for (i = 0; i < paths->nelts; i++)
2053 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2054 const char *full_path;
2056 if (item->kind != SVN_RA_SVN_STRING)
2057 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2058 _("Path is not a string"));
2059 full_path = svn_relpath_canonicalize(item->u.string->data, pool);
2060 full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
2061 APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
2064 SVN_ERR(log_command(b, conn, pool, "%s",
2065 svn_log__get_mergeinfo(canonical_paths, inherit,
2066 include_descendants,
2069 SVN_ERR(trivial_auth_request(conn, pool, b));
2070 SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos,
2071 canonical_paths, rev,
2073 include_descendants,
2074 authz_check_access_cb_func(b), &ab,
2076 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo,
2077 b->fs_path->data, pool));
2078 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2079 iterpool = svn_pool_create(pool);
2080 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
2082 const char *key = svn__apr_hash_index_key(hi);
2083 svn_mergeinfo_t value = svn__apr_hash_index_val(hi);
2084 svn_string_t *mergeinfo_string;
2086 svn_pool_clear(iterpool);
2088 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, value, iterpool));
2089 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cs", key,
2092 svn_pool_destroy(iterpool);
2093 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2095 return SVN_NO_ERROR;
2098 /* Send a log entry to the client. */
2099 static svn_error_t *log_receiver(void *baton,
2100 svn_log_entry_t *log_entry,
2103 log_baton_t *b = baton;
2104 svn_ra_svn_conn_t *conn = b->conn;
2105 apr_hash_index_t *h;
2106 svn_boolean_t invalid_revnum = FALSE;
2108 const char *author, *date, *message;
2109 apr_uint64_t revprop_count;
2111 if (log_entry->revision == SVN_INVALID_REVNUM)
2113 /* If the stack depth is zero, we've seen the last revision, so don't
2114 send it, just return. */
2115 if (b->stack_depth == 0)
2116 return SVN_NO_ERROR;
2118 /* Because the svn protocol won't let us send an invalid revnum, we have
2119 to fudge here and send an additional flag. */
2120 log_entry->revision = 0;
2121 invalid_revnum = TRUE;
2125 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "(!"));
2126 if (log_entry->changed_paths2)
2128 for (h = apr_hash_first(pool, log_entry->changed_paths2); h;
2129 h = apr_hash_next(h))
2131 const char *path = svn__apr_hash_index_key(h);
2132 svn_log_changed_path2_t *change = svn__apr_hash_index_val(h);
2134 action[0] = change->action;
2136 SVN_ERR(svn_ra_svn__write_tuple(
2137 conn, pool, "cw(?cr)(cbb)",
2140 change->copyfrom_path,
2141 change->copyfrom_rev,
2142 svn_node_kind_to_word(change->node_kind),
2143 /* text_modified and props_modified are never unknown */
2144 change->text_modified == svn_tristate_true,
2145 change->props_modified == svn_tristate_true));
2148 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
2149 svn_compat_log_revprops_clear(log_entry->revprops);
2150 if (log_entry->revprops)
2151 revprop_count = apr_hash_count(log_entry->revprops);
2154 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
2155 log_entry->revision,
2156 author, date, message,
2157 log_entry->has_children,
2158 invalid_revnum, revprop_count));
2159 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, log_entry->revprops));
2160 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b",
2161 log_entry->subtractive_merge));
2163 if (log_entry->has_children)
2166 return SVN_NO_ERROR;
2169 static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2170 apr_array_header_t *params, void *baton)
2172 svn_error_t *err, *write_err;
2173 server_baton_t *b = baton;
2174 svn_revnum_t start_rev, end_rev;
2175 const char *full_path;
2176 svn_boolean_t send_changed_paths, strict_node, include_merged_revisions;
2177 apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
2179 svn_ra_svn_item_t *elt;
2181 apr_uint64_t limit, include_merged_revs_param;
2188 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
2189 &start_rev, &end_rev, &send_changed_paths,
2190 &strict_node, &limit,
2191 &include_merged_revs_param,
2192 &revprop_word, &revprop_items));
2194 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2195 include_merged_revisions = FALSE;
2197 include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2199 if (revprop_word == NULL)
2200 /* pre-1.5 client */
2201 revprops = svn_compat_log_revprops_in(pool);
2202 else if (strcmp(revprop_word, "all-revprops") == 0)
2204 else if (strcmp(revprop_word, "revprops") == 0)
2206 SVN_ERR_ASSERT(revprop_items);
2208 revprops = apr_array_make(pool, revprop_items->nelts,
2210 for (i = 0; i < revprop_items->nelts; i++)
2212 elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
2213 if (elt->kind != SVN_RA_SVN_STRING)
2214 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2215 _("Log revprop entry not a string"));
2216 APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
2220 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2221 _("Unknown revprop word '%s' in log command"),
2224 /* If we got an unspecified number then the user didn't send us anything,
2225 so we assume no limit. If it's larger than INT_MAX then someone is
2226 messing with us, since we know the svn client libraries will never send
2227 us anything that big, so play it safe and default to no limit. */
2228 if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
2231 full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2232 for (i = 0; i < paths->nelts; i++)
2234 elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2235 if (elt->kind != SVN_RA_SVN_STRING)
2236 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2237 _("Log path entry not a string"));
2238 full_path = svn_relpath_canonicalize(elt->u.string->data, pool),
2239 full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
2240 APR_ARRAY_PUSH(full_paths, const char *) = full_path;
2242 SVN_ERR(trivial_auth_request(conn, pool, b));
2244 SVN_ERR(log_command(b, conn, pool, "%s",
2245 svn_log__log(full_paths, start_rev, end_rev,
2246 (int) limit, send_changed_paths,
2247 strict_node, include_merged_revisions,
2250 /* Get logs. (Can't report errors back to the client at this point.) */
2251 lb.fs_path = b->fs_path->data;
2254 err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
2255 (int) limit, send_changed_paths, strict_node,
2256 include_merged_revisions, revprops,
2257 authz_check_access_cb_func(b), &ab, log_receiver,
2260 write_err = svn_ra_svn__write_word(conn, pool, "done");
2263 svn_error_clear(err);
2267 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2268 return SVN_NO_ERROR;
2271 static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2272 apr_array_header_t *params, void *baton)
2274 server_baton_t *b = baton;
2276 const char *path, *full_path;
2277 svn_fs_root_t *root;
2278 svn_node_kind_t kind;
2280 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2281 full_path = svn_fspath__join(b->fs_path->data,
2282 svn_relpath_canonicalize(path, pool), pool);
2284 /* Check authorizations */
2285 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2288 if (!SVN_IS_VALID_REVNUM(rev))
2289 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2291 SVN_ERR(log_command(b, conn, pool, "check-path %s@%d",
2292 svn_path_uri_encode(full_path, pool), rev));
2294 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
2295 SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
2296 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w",
2297 svn_node_kind_to_word(kind)));
2298 return SVN_NO_ERROR;
2301 static svn_error_t *stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2302 apr_array_header_t *params, void *baton)
2304 server_baton_t *b = baton;
2306 const char *path, *full_path, *cdate;
2307 svn_fs_root_t *root;
2308 svn_dirent_t *dirent;
2310 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2311 full_path = svn_fspath__join(b->fs_path->data,
2312 svn_relpath_canonicalize(path, pool), pool);
2314 /* Check authorizations */
2315 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2318 if (!SVN_IS_VALID_REVNUM(rev))
2319 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2321 SVN_ERR(log_command(b, conn, pool, "stat %s@%d",
2322 svn_path_uri_encode(full_path, pool), rev));
2324 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
2325 SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
2327 /* Need to return the equivalent of "(?l)", since that's what the
2328 client is reading. */
2332 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()"));
2333 return SVN_NO_ERROR;
2336 cdate = (dirent->time == (time_t) -1) ? NULL
2337 : svn_time_to_cstring(dirent->time, pool);
2339 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
2340 svn_node_kind_to_word(dirent->kind),
2341 (apr_uint64_t) dirent->size,
2342 dirent->has_props, dirent->created_rev,
2343 cdate, dirent->last_author));
2345 return SVN_NO_ERROR;
2348 static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2349 apr_array_header_t *params, void *baton)
2351 svn_error_t *err, *write_err;
2352 server_baton_t *b = baton;
2353 svn_revnum_t revision;
2354 apr_array_header_t *location_revisions, *loc_revs_proto;
2355 svn_ra_svn_item_t *elt;
2357 const char *relative_path;
2358 svn_revnum_t peg_revision;
2359 apr_hash_t *fs_locations;
2360 const char *abs_path;
2366 /* Parse the arguments. */
2367 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crl", &relative_path,
2370 relative_path = svn_relpath_canonicalize(relative_path, pool);
2372 abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
2374 location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
2375 sizeof(svn_revnum_t));
2376 for (i = 0; i < loc_revs_proto->nelts; i++)
2378 elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
2379 if (elt->kind != SVN_RA_SVN_NUMBER)
2380 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2381 "Get-locations location revisions entry "
2382 "not a revision number");
2383 revision = (svn_revnum_t)(elt->u.number);
2384 APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
2386 SVN_ERR(trivial_auth_request(conn, pool, b));
2387 SVN_ERR(log_command(b, conn, pool, "%s",
2388 svn_log__get_locations(abs_path, peg_revision,
2389 location_revisions, pool)));
2391 /* All the parameters are fine - let's perform the query against the
2394 /* We store both err and write_err here, so the client will get
2395 * the "done" even if there was an error in fetching the results. */
2397 err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
2398 peg_revision, location_revisions,
2399 authz_check_access_cb_func(b), &ab,
2402 /* Now, write the results to the connection. */
2407 apr_hash_index_t *iter;
2409 for (iter = apr_hash_first(pool, fs_locations); iter;
2410 iter = apr_hash_next(iter))
2412 const svn_revnum_t *iter_key = svn__apr_hash_index_key(iter);
2413 const char *iter_value = svn__apr_hash_index_val(iter);
2415 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc",
2416 *iter_key, iter_value));
2421 write_err = svn_ra_svn__write_word(conn, pool, "done");
2424 svn_error_clear(err);
2429 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2431 return SVN_NO_ERROR;
2434 static svn_error_t *gls_receiver(svn_location_segment_t *segment,
2438 svn_ra_svn_conn_t *conn = baton;
2439 return svn_ra_svn__write_tuple(conn, pool, "rr(?c)",
2440 segment->range_start,
2445 static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn,
2447 apr_array_header_t *params,
2450 svn_error_t *err, *write_err;
2451 server_baton_t *b = baton;
2452 svn_revnum_t peg_revision, start_rev, end_rev;
2453 const char *relative_path;
2454 const char *abs_path;
2460 /* Parse the arguments. */
2461 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)(?r)",
2462 &relative_path, &peg_revision,
2463 &start_rev, &end_rev));
2464 relative_path = svn_relpath_canonicalize(relative_path, pool);
2466 abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
2468 if (SVN_IS_VALID_REVNUM(start_rev)
2469 && SVN_IS_VALID_REVNUM(end_rev)
2470 && (end_rev > start_rev))
2472 err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2473 "Get-location-segments end revision must not be "
2474 "younger than start revision");
2475 return log_fail_and_flush(err, b, conn, pool);
2478 if (SVN_IS_VALID_REVNUM(peg_revision)
2479 && SVN_IS_VALID_REVNUM(start_rev)
2480 && (start_rev > peg_revision))
2482 err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2483 "Get-location-segments start revision must not "
2484 "be younger than peg revision");
2485 return log_fail_and_flush(err, b, conn, pool);
2488 SVN_ERR(trivial_auth_request(conn, pool, b));
2489 SVN_ERR(log_command(baton, conn, pool, "%s",
2490 svn_log__get_location_segments(abs_path, peg_revision,
2494 /* All the parameters are fine - let's perform the query against the
2497 /* We store both err and write_err here, so the client will get
2498 * the "done" even if there was an error in fetching the results. */
2500 err = svn_repos_node_location_segments(b->repos, abs_path,
2501 peg_revision, start_rev, end_rev,
2502 gls_receiver, (void *)conn,
2503 authz_check_access_cb_func(b), &ab,
2505 write_err = svn_ra_svn__write_word(conn, pool, "done");
2508 svn_error_clear(err);
2513 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2515 return SVN_NO_ERROR;
2518 /* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the
2519 client as a string. */
2520 static svn_error_t *svndiff_handler(void *baton, const char *data,
2523 file_revs_baton_t *b = baton;
2528 return svn_ra_svn__write_string(b->conn, b->pool, &str);
2531 /* This implements svn_close_fn_t. Mark the end of the data by writing an
2532 empty string to the client. */
2533 static svn_error_t *svndiff_close_handler(void *baton)
2535 file_revs_baton_t *b = baton;
2537 SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, ""));
2538 return SVN_NO_ERROR;
2541 /* This implements the svn_repos_file_rev_handler_t interface. */
2542 static svn_error_t *file_rev_handler(void *baton, const char *path,
2543 svn_revnum_t rev, apr_hash_t *rev_props,
2544 svn_boolean_t merged_revision,
2545 svn_txdelta_window_handler_t *d_handler,
2547 apr_array_header_t *prop_diffs,
2550 file_revs_baton_t *frb = baton;
2551 svn_stream_t *stream;
2553 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!",
2555 SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props));
2556 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!"));
2557 SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
2558 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision));
2560 /* Store the pool for the delta stream. */
2563 /* Prepare for the delta or just write an empty string. */
2566 stream = svn_stream_create(baton, pool);
2567 svn_stream_set_write(stream, svndiff_handler);
2568 svn_stream_set_close(stream, svndiff_close_handler);
2570 /* If the connection does not support SVNDIFF1 or if we don't want to use
2571 * compression, use the non-compressing "version 0" implementation */
2572 if ( svn_ra_svn_compression_level(frb->conn) > 0
2573 && svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1))
2574 svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 1,
2575 svn_ra_svn_compression_level(frb->conn), pool);
2577 svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 0,
2578 svn_ra_svn_compression_level(frb->conn), pool);
2581 SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, ""));
2583 return SVN_NO_ERROR;
2586 static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2587 apr_array_header_t *params, void *baton)
2589 server_baton_t *b = baton;
2590 svn_error_t *err, *write_err;
2591 file_revs_baton_t frb;
2592 svn_revnum_t start_rev, end_rev;
2594 const char *full_path;
2595 apr_uint64_t include_merged_revs_param;
2596 svn_boolean_t include_merged_revisions;
2602 /* Parse arguments. */
2603 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)?B",
2604 &path, &start_rev, &end_rev,
2605 &include_merged_revs_param));
2606 path = svn_relpath_canonicalize(path, pool);
2607 SVN_ERR(trivial_auth_request(conn, pool, b));
2608 full_path = svn_fspath__join(b->fs_path->data, path, pool);
2610 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2611 include_merged_revisions = FALSE;
2613 include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2615 SVN_ERR(log_command(b, conn, pool, "%s",
2616 svn_log__get_file_revs(full_path, start_rev, end_rev,
2617 include_merged_revisions,
2623 err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev,
2624 include_merged_revisions,
2625 authz_check_access_cb_func(b), &ab,
2626 file_rev_handler, &frb, pool);
2627 write_err = svn_ra_svn__write_word(conn, pool, "done");
2630 svn_error_clear(err);
2634 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2636 return SVN_NO_ERROR;
2639 static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2640 apr_array_header_t *params, void *baton)
2642 server_baton_t *b = baton;
2644 const char *comment;
2645 const char *full_path;
2646 svn_boolean_t steal_lock;
2647 svn_revnum_t current_rev;
2650 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
2651 &steal_lock, ¤t_rev));
2652 full_path = svn_fspath__join(b->fs_path->data,
2653 svn_relpath_canonicalize(path, pool), pool);
2655 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2657 SVN_ERR(log_command(b, conn, pool, "%s",
2658 svn_log__lock_one_path(full_path, steal_lock, pool)));
2660 SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
2661 0, /* No expiration time. */
2662 current_rev, steal_lock, pool));
2664 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success"));
2665 SVN_ERR(write_lock(conn, pool, l));
2666 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)"));
2668 return SVN_NO_ERROR;
2671 static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2672 apr_array_header_t *params, void *baton)
2674 server_baton_t *b = baton;
2675 apr_array_header_t *path_revs;
2676 const char *comment;
2677 svn_boolean_t steal_lock;
2679 apr_pool_t *subpool;
2681 const char *full_path;
2682 svn_revnum_t current_rev;
2683 apr_array_header_t *log_paths;
2685 svn_error_t *err = SVN_NO_ERROR, *write_err;
2687 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
2690 subpool = svn_pool_create(pool);
2692 /* Because we can only send a single auth reply per request, we send
2693 a reply before parsing the lock commands. This means an authz
2694 access denial will abort the processing of the locks and return
2696 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
2698 /* Loop through the lock requests. */
2699 log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path));
2700 for (i = 0; i < path_revs->nelts; ++i)
2702 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
2705 svn_pool_clear(subpool);
2707 if (item->kind != SVN_RA_SVN_LIST)
2708 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2709 "Lock requests should be list of lists");
2711 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "c(?r)", &path,
2714 /* Allocate the full_path out of pool so it will survive for use
2715 * by operational logging, after this loop. */
2716 full_path = svn_fspath__join(b->fs_path->data,
2717 svn_relpath_canonicalize(path, subpool),
2719 APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2721 if (! lookup_access(pool, b, conn, svn_authz_write, full_path, TRUE))
2723 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
2728 err = svn_repos_fs_lock(&l, b->repos, full_path,
2729 NULL, comment, FALSE,
2730 0, /* No expiration time. */
2732 steal_lock, subpool);
2736 if (SVN_ERR_IS_LOCK_ERROR(err))
2738 write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
2739 svn_error_clear(err);
2748 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w!", "success"));
2749 SVN_ERR(write_lock(conn, subpool, l));
2750 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "!"));
2754 svn_pool_destroy(subpool);
2756 SVN_ERR(log_command(b, conn, pool, "%s",
2757 svn_log__lock(log_paths, steal_lock, pool)));
2759 /* NOTE: err might contain a fatal locking error from the loop above. */
2760 write_err = svn_ra_svn__write_word(conn, pool, "done");
2763 svn_error_clear(err);
2765 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2767 return SVN_NO_ERROR;
2770 static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2771 apr_array_header_t *params, void *baton)
2773 server_baton_t *b = baton;
2774 const char *path, *token, *full_path;
2775 svn_boolean_t break_lock;
2777 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b", &path, &token,
2780 full_path = svn_fspath__join(b->fs_path->data,
2781 svn_relpath_canonicalize(path, pool), pool);
2783 /* Username required unless break_lock was specified. */
2784 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2785 full_path, ! break_lock));
2786 SVN_ERR(log_command(b, conn, pool, "%s",
2787 svn_log__unlock_one_path(full_path, break_lock, pool)));
2789 SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2792 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2794 return SVN_NO_ERROR;
2797 static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2798 apr_array_header_t *params, void *baton)
2800 server_baton_t *b = baton;
2801 svn_boolean_t break_lock;
2802 apr_array_header_t *unlock_tokens;
2804 apr_pool_t *subpool;
2806 const char *full_path;
2807 apr_array_header_t *log_paths;
2809 svn_error_t *err = SVN_NO_ERROR, *write_err;
2811 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock,
2814 /* Username required unless break_lock was specified. */
2815 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
2817 subpool = svn_pool_create(pool);
2819 /* Loop through the unlock requests. */
2820 log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path));
2821 for (i = 0; i < unlock_tokens->nelts; i++)
2823 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
2826 svn_pool_clear(subpool);
2828 if (item->kind != SVN_RA_SVN_LIST)
2829 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2830 "Unlock request should be a list of lists");
2832 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
2835 /* Allocate the full_path out of pool so it will survive for use
2836 * by operational logging, after this loop. */
2837 full_path = svn_fspath__join(b->fs_path->data,
2838 svn_relpath_canonicalize(path, subpool),
2840 APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2842 if (! lookup_access(subpool, b, conn, svn_authz_write, full_path,
2844 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
2845 error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
2850 err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2854 if (SVN_ERR_IS_UNLOCK_ERROR(err))
2856 write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
2857 svn_error_clear(err);
2865 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
2869 svn_pool_destroy(subpool);
2871 SVN_ERR(log_command(b, conn, pool, "%s",
2872 svn_log__unlock(log_paths, break_lock, pool)));
2874 /* NOTE: err might contain a fatal unlocking error from the loop above. */
2875 write_err = svn_ra_svn__write_word(conn, pool, "done");
2878 svn_error_clear(err);
2879 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2881 return SVN_NO_ERROR;
2884 static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2885 apr_array_header_t *params, void *baton)
2887 server_baton_t *b = baton;
2889 const char *full_path;
2892 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
2894 full_path = svn_fspath__join(b->fs_path->data,
2895 svn_relpath_canonicalize(path, pool), pool);
2897 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2899 SVN_ERR(log_command(b, conn, pool, "get-lock %s",
2900 svn_path_uri_encode(full_path, pool)));
2902 SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
2904 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2906 SVN_ERR(write_lock(conn, pool, l));
2907 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2909 return SVN_NO_ERROR;
2912 static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2913 apr_array_header_t *params, void *baton)
2915 server_baton_t *b = baton;
2917 const char *full_path;
2918 const char *depth_word;
2921 apr_hash_index_t *hi;
2928 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c?(?w)", &path, &depth_word));
2930 depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity;
2931 if ((depth != svn_depth_empty) &&
2932 (depth != svn_depth_files) &&
2933 (depth != svn_depth_immediates) &&
2934 (depth != svn_depth_infinity))
2936 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2937 "Invalid 'depth' specified in get-locks request");
2938 return log_fail_and_flush(err, b, conn, pool);
2941 full_path = svn_fspath__join(b->fs_path->data,
2942 svn_relpath_canonicalize(path, pool), pool);
2944 SVN_ERR(trivial_auth_request(conn, pool, b));
2946 SVN_ERR(log_command(b, conn, pool, "get-locks %s",
2947 svn_path_uri_encode(full_path, pool)));
2948 SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repos, full_path, depth,
2949 authz_check_access_cb_func(b), &ab,
2952 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2953 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2955 svn_lock_t *l = svn__apr_hash_index_val(hi);
2957 SVN_ERR(write_lock(conn, pool, l));
2959 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2961 return SVN_NO_ERROR;
2964 static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
2967 svn_revnum_t low_water_mark,
2968 svn_boolean_t send_deltas,
2971 const svn_delta_editor_t *editor;
2973 svn_fs_root_t *root;
2980 SVN_ERR(log_command(b, conn, pool,
2981 svn_log__replay(b->fs_path->data, rev, pool)));
2983 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
2985 err = svn_fs_revision_root(&root, b->fs, rev, pool);
2988 err = svn_repos_replay2(root, b->fs_path->data, low_water_mark,
2989 send_deltas, editor, edit_baton,
2990 authz_check_access_cb_func(b), &ab, pool);
2993 svn_error_clear(editor->abort_edit(edit_baton, pool));
2996 return svn_ra_svn__write_cmd_finish_replay(conn, pool);
2999 static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3000 apr_array_header_t *params, void *baton)
3002 svn_revnum_t rev, low_water_mark;
3003 svn_boolean_t send_deltas;
3004 server_baton_t *b = baton;
3006 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrb", &rev, &low_water_mark,
3009 SVN_ERR(trivial_auth_request(conn, pool, b));
3011 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3012 send_deltas, pool));
3014 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3016 return SVN_NO_ERROR;
3019 static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3020 apr_array_header_t *params, void *baton)
3022 svn_revnum_t start_rev, end_rev, rev, low_water_mark;
3023 svn_boolean_t send_deltas;
3024 server_baton_t *b = baton;
3025 apr_pool_t *iterpool;
3031 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrrb", &start_rev,
3032 &end_rev, &low_water_mark,
3035 SVN_ERR(trivial_auth_request(conn, pool, b));
3037 iterpool = svn_pool_create(pool);
3038 for (rev = start_rev; rev <= end_rev; rev++)
3042 svn_pool_clear(iterpool);
3044 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
3045 authz_check_access_cb_func(b),
3048 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops"));
3049 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props));
3050 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)"));
3052 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3053 send_deltas, iterpool));
3056 svn_pool_destroy(iterpool);
3058 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3060 return SVN_NO_ERROR;
3063 static svn_error_t *
3064 get_deleted_rev(svn_ra_svn_conn_t *conn,
3066 apr_array_header_t *params,
3069 server_baton_t *b = baton;
3070 const char *path, *full_path;
3071 svn_revnum_t peg_revision;
3072 svn_revnum_t end_revision;
3073 svn_revnum_t revision_deleted;
3075 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crr",
3076 &path, &peg_revision, &end_revision));
3077 full_path = svn_fspath__join(b->fs_path->data,
3078 svn_relpath_canonicalize(path, pool), pool);
3079 SVN_ERR(log_command(b, conn, pool, "get-deleted-rev"));
3080 SVN_ERR(trivial_auth_request(conn, pool, b));
3081 SVN_ERR(svn_repos_deleted_rev(b->fs, full_path, peg_revision, end_revision,
3082 &revision_deleted, pool));
3083 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted));
3084 return SVN_NO_ERROR;
3087 static svn_error_t *
3088 get_inherited_props(svn_ra_svn_conn_t *conn,
3090 apr_array_header_t *params,
3093 server_baton_t *b = baton;
3094 const char *path, *full_path;
3096 svn_fs_root_t *root;
3097 apr_array_header_t *inherited_props;
3099 apr_pool_t *iterpool = svn_pool_create(pool);
3105 /* Parse arguments. */
3106 SVN_ERR(svn_ra_svn__parse_tuple(params, iterpool, "c(?r)", &path, &rev));
3108 full_path = svn_fspath__join(b->fs_path->data,
3109 svn_relpath_canonicalize(path, iterpool),
3112 /* Check authorizations */
3113 SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read,
3116 if (!SVN_IS_VALID_REVNUM(rev))
3117 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
3119 SVN_ERR(log_command(b, conn, pool, "%s",
3120 svn_log__get_inherited_props(full_path, rev,
3123 /* Fetch the properties and a stream for the contents. */
3124 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, iterpool));
3125 SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool));
3127 /* Send successful command response with revision and props. */
3128 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success"));
3130 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!"));
3132 for (i = 0; i < inherited_props->nelts; i++)
3134 svn_prop_inherited_item_t *iprop =
3135 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
3137 svn_pool_clear(iterpool);
3138 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
3139 iprop->path_or_url));
3140 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
3141 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
3142 iprop->path_or_url));
3145 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))"));
3146 svn_pool_destroy(iterpool);
3147 return SVN_NO_ERROR;
3150 static const svn_ra_svn_cmd_entry_t main_commands[] = {
3151 { "reparent", reparent },
3152 { "get-latest-rev", get_latest_rev },
3153 { "get-dated-rev", get_dated_rev },
3154 { "change-rev-prop", change_rev_prop },
3155 { "change-rev-prop2",change_rev_prop2 },
3156 { "rev-proplist", rev_proplist },
3157 { "rev-prop", rev_prop },
3158 { "commit", commit },
3159 { "get-file", get_file },
3160 { "get-dir", get_dir },
3161 { "update", update },
3162 { "switch", switch_cmd },
3163 { "status", status },
3165 { "get-mergeinfo", get_mergeinfo },
3167 { "check-path", check_path },
3168 { "stat", stat_cmd },
3169 { "get-locations", get_locations },
3170 { "get-location-segments", get_location_segments },
3171 { "get-file-revs", get_file_revs },
3173 { "lock-many", lock_many },
3174 { "unlock", unlock },
3175 { "unlock-many", unlock_many },
3176 { "get-lock", get_lock },
3177 { "get-locks", get_locks },
3178 { "replay", replay },
3179 { "replay-range", replay_range },
3180 { "get-deleted-rev", get_deleted_rev },
3181 { "get-iprops", get_inherited_props },
3185 /* Skip past the scheme part of a URL, including the tunnel specification
3186 * if present. Return NULL if the scheme part is invalid for ra_svn. */
3187 static const char *skip_scheme_part(const char *url)
3189 if (strncmp(url, "svn", 3) != 0)
3193 url += strcspn(url, ":");
3194 if (strncmp(url, "://", 3) != 0)
3199 /* Check that PATH is a valid repository path, meaning it doesn't contain any
3201 NOTE: This is similar to svn_path_is_backpath_present, but that function
3202 assumes the path separator is '/'. This function also checks for
3203 segments delimited by the local path separator. */
3204 static svn_boolean_t
3205 repos_path_valid(const char *path)
3207 const char *s = path;
3211 /* Scan for the end of the segment. */
3212 while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
3215 /* Check for '..'. */
3217 /* On Windows, don't allow sequences of more than one character
3218 consisting of just dots and spaces. Win32 functions treat
3219 paths such as ".. " and "......." inconsistently. Make sure
3220 no one can escape out of the root. */
3221 if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s))
3224 if (path - s == 2 && s[0] == '.' && s[1] == '.')
3228 /* Skip all separators. */
3229 while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
3237 /* Look for the repository given by URL, using ROOT as the virtual
3238 * repository root. If we find one, fill in the repos, fs, cfg,
3239 * repos_url, and fs_path fields of B. Set B->repos's client
3240 * capabilities to CAPABILITIES, which must be at least as long-lived
3241 * as POOL, and whose elements are SVN_RA_CAPABILITY_*.
3243 static svn_error_t *find_repos(const char *url, const char *root,
3245 svn_ra_svn_conn_t *conn,
3246 const apr_array_header_t *capabilities,
3249 const char *path, *full_path, *repos_root, *fs_path, *hooks_env;
3250 svn_stringbuf_t *url_buf;
3252 /* Skip past the scheme and authority part. */
3253 path = skip_scheme_part(url);
3255 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
3256 "Non-svn URL passed to svn server: '%s'", url);
3260 path = strchr(path, '/');
3264 path = svn_relpath_canonicalize(path, pool);
3265 path = svn_path_uri_decode(path, pool);
3267 /* Ensure that it isn't possible to escape the root by disallowing
3269 if (!repos_path_valid(path))
3270 return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
3271 "Couldn't determine repository path");
3273 /* Join the server-configured root with the client path. */
3274 full_path = svn_dirent_join(svn_dirent_canonicalize(root, pool),
3277 /* Search for a repository in the full path. */
3278 repos_root = svn_repos_find_root_path(full_path, pool);
3280 return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
3281 "No repository found in '%s'", url);
3283 /* Open the repository and fill in b with the resulting information. */
3284 SVN_ERR(svn_repos_open2(&b->repos, repos_root, b->fs_config, pool));
3285 SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities));
3286 b->fs = svn_repos_fs(b->repos);
3287 fs_path = full_path + strlen(repos_root);
3288 b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool);
3289 url_buf = svn_stringbuf_create(url, pool);
3290 svn_path_remove_components(url_buf,
3291 svn_path_component_count(b->fs_path->data));
3292 b->repos_url = url_buf->data;
3293 b->authz_repos_name = svn_dirent_is_child(root, repos_root, pool);
3294 if (b->authz_repos_name == NULL)
3295 b->repos_name = svn_dirent_basename(repos_root, pool);
3297 b->repos_name = b->authz_repos_name;
3298 b->repos_name = svn_path_uri_encode(b->repos_name, pool);
3300 /* If the svnserve configuration has not been loaded then load it from the
3304 b->base = svn_repos_conf_dir(b->repos, pool);
3306 SVN_ERR(svn_config_read3(&b->cfg, svn_repos_svnserve_conf(b->repos, pool),
3307 FALSE, /* must_exist */
3308 FALSE, /* section_names_case_sensitive */
3309 FALSE, /* option_names_case_sensitive */
3311 SVN_ERR(load_pwdb_config(b, conn, pool));
3312 SVN_ERR(load_authz_config(b, conn, repos_root, pool));
3314 /* svnserve.conf has been loaded via the --config-file option so need
3315 * to load pwdb and authz. */
3318 SVN_ERR(load_pwdb_config(b, conn, pool));
3319 SVN_ERR(load_authz_config(b, conn, repos_root, pool));
3322 #ifdef SVN_HAVE_SASL
3323 /* Should we use Cyrus SASL? */
3324 SVN_ERR(svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
3325 SVN_CONFIG_OPTION_USE_SASL, FALSE));
3328 /* Use the repository UUID as the default realm. */
3329 SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
3330 svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
3331 SVN_CONFIG_OPTION_REALM, b->realm);
3333 /* Make sure it's possible for the client to authenticate. Note
3334 that this doesn't take into account any authz configuration read
3335 above, because we can't know about access it grants until paths
3336 are given by the client. */
3337 if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
3338 && (get_access(b, AUTHENTICATED) == NO_ACCESS
3339 || (!b->tunnel_user && !b->pwdb && !b->use_sasl)))
3340 return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3341 "No access allowed to this repository",
3344 /* Configure hook script environment variables. */
3345 svn_config_get(b->cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL,
3346 SVN_CONFIG_OPTION_HOOKS_ENV, NULL);
3348 hooks_env = svn_dirent_internal_style(hooks_env, pool);
3349 SVN_ERR(svn_repos_hooks_setenv(b->repos, hooks_env, pool));
3351 return SVN_NO_ERROR;
3354 /* Compute the authentication name EXTERNAL should be able to get, if any. */
3355 static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
3357 /* Only offer EXTERNAL for connections tunneled over a login agent. */
3358 if (!params->tunnel)
3361 /* If a tunnel user was provided on the command line, use that. */
3362 if (params->tunnel_user)
3363 return params->tunnel_user;
3365 return svn_user_get_name(pool);
3369 fs_warning_func(void *baton, svn_error_t *err)
3371 fs_warning_baton_t *b = baton;
3372 log_server_error(err, b->server, b->conn, b->pool);
3373 /* TODO: Keep log_pool in the server baton, cleared after every log? */
3374 svn_pool_clear(b->pool);
3377 /* Return the normalized repository-relative path for the given PATH
3378 * (may be a URL, full path or relative path) and fs contained in the
3379 * server baton BATON. Allocate the result in POOL.
3382 get_normalized_repo_rel_path(void *baton,
3386 server_baton_t *sb = baton;
3388 if (svn_path_is_url(path))
3390 /* This is a copyfrom URL. */
3391 path = svn_uri_skip_ancestor(sb->repos_url, path, pool);
3392 path = svn_fspath__canonicalize(path, pool);
3396 /* This is a base-relative path. */
3397 if ((path)[0] != '/')
3398 /* Get an absolute path for use in the FS. */
3399 path = svn_fspath__join(sb->fs_path->data, path, pool);
3405 /* Get the revision root for REVISION in fs given by server baton BATON
3406 * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM.
3407 * Use POOL for allocations.
3409 static svn_error_t *
3410 get_revision_root(svn_fs_root_t **fs_root,
3412 svn_revnum_t revision,
3415 server_baton_t *sb = baton;
3417 if (!SVN_IS_VALID_REVNUM(revision))
3418 SVN_ERR(svn_fs_youngest_rev(&revision, sb->fs, pool));
3420 SVN_ERR(svn_fs_revision_root(fs_root, sb->fs, revision, pool));
3422 return SVN_NO_ERROR;
3425 static svn_error_t *
3426 fetch_props_func(apr_hash_t **props,
3429 svn_revnum_t base_revision,
3430 apr_pool_t *result_pool,
3431 apr_pool_t *scratch_pool)
3433 svn_fs_root_t *fs_root;
3436 path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3437 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3439 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
3440 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3442 svn_error_clear(err);
3443 *props = apr_hash_make(result_pool);
3444 return SVN_NO_ERROR;
3447 return svn_error_trace(err);
3449 return SVN_NO_ERROR;
3452 static svn_error_t *
3453 fetch_kind_func(svn_node_kind_t *kind,
3456 svn_revnum_t base_revision,
3457 apr_pool_t *scratch_pool)
3459 svn_fs_root_t *fs_root;
3461 path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3462 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3464 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
3466 return SVN_NO_ERROR;
3469 static svn_error_t *
3470 fetch_base_func(const char **filename,
3473 svn_revnum_t base_revision,
3474 apr_pool_t *result_pool,
3475 apr_pool_t *scratch_pool)
3477 svn_stream_t *contents;
3478 svn_stream_t *file_stream;
3479 const char *tmp_filename;
3480 svn_fs_root_t *fs_root;
3483 path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3484 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3486 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
3487 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3489 svn_error_clear(err);
3491 return SVN_NO_ERROR;
3494 return svn_error_trace(err);
3495 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
3496 svn_io_file_del_on_pool_cleanup,
3497 scratch_pool, scratch_pool));
3498 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
3500 *filename = apr_pstrdup(result_pool, tmp_filename);
3502 return SVN_NO_ERROR;
3505 svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
3508 svn_error_t *err, *io_err;
3510 const char *uuid, *client_url, *ra_client_string, *client_string;
3511 apr_array_header_t *caplist, *cap_words;
3513 fs_warning_baton_t warn_baton;
3514 svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(pool);
3516 b.tunnel = params->tunnel;
3517 b.tunnel_user = get_tunnel_user(params, pool);
3518 b.read_only = params->read_only;
3520 b.username_case = params->username_case;
3521 b.authz_user = NULL;
3522 b.base = params->base;
3523 b.cfg = params->cfg;
3527 b.log_file = params->log_file;
3530 b.vhost = params->vhost;
3532 /* construct FS configuration parameters */
3533 b.fs_config = apr_hash_make(pool);
3534 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
3535 params->cache_txdeltas ? "1" :"0");
3536 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
3537 params->cache_fulltexts ? "1" :"0");
3538 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
3539 params->cache_revprops ? "1" :"0");
3541 /* Send greeting. We don't support version 1 any more, so we can
3542 * send an empty mechlist. */
3543 if (params->compression_level > 0)
3544 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwwww)",
3545 (apr_uint64_t) 2, (apr_uint64_t) 2,
3546 SVN_RA_SVN_CAP_EDIT_PIPELINE,
3547 SVN_RA_SVN_CAP_SVNDIFF1,
3548 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3549 SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3550 SVN_RA_SVN_CAP_DEPTH,
3551 SVN_RA_SVN_CAP_LOG_REVPROPS,
3552 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3553 SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3554 SVN_RA_SVN_CAP_INHERITED_PROPS,
3555 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3556 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3559 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwww)",
3560 (apr_uint64_t) 2, (apr_uint64_t) 2,
3561 SVN_RA_SVN_CAP_EDIT_PIPELINE,
3562 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3563 SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3564 SVN_RA_SVN_CAP_DEPTH,
3565 SVN_RA_SVN_CAP_LOG_REVPROPS,
3566 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3567 SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3568 SVN_RA_SVN_CAP_INHERITED_PROPS,
3569 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3570 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3573 /* Read client response, which we assume to be in version 2 format:
3574 * version, capability list, and client URL; then we do an auth
3576 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "nlc?c(?c)",
3577 &ver, &caplist, &client_url,
3581 return SVN_NO_ERROR;
3583 client_url = svn_uri_canonicalize(client_url, pool);
3584 SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
3586 /* All released versions of Subversion support edit-pipeline,
3587 * so we do not accept connections from clients that do not. */
3588 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
3589 return SVN_NO_ERROR;
3591 /* find_repos needs the capabilities as a list of words (eventually
3592 they get handed to the start-commit hook). While we could add a
3593 new interface to re-retrieve them from conn and convert the
3594 result to a list, it's simpler to just convert caplist by hand
3595 here, since we already have it and turning 'svn_ra_svn_item_t's
3596 into 'const char *'s is pretty easy.
3598 We only record capabilities we care about. The client may report
3599 more (because it doesn't know what the server cares about). */
3602 svn_ra_svn_item_t *item;
3604 cap_words = apr_array_make(pool, 1, sizeof(const char *));
3605 for (i = 0; i < caplist->nelts; i++)
3607 item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
3608 /* ra_svn_set_capabilities() already type-checked for us */
3609 if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0)
3611 APR_ARRAY_PUSH(cap_words, const char *)
3612 = SVN_RA_CAPABILITY_MERGEINFO;
3614 /* Save for operational log. */
3615 if (cap_log->len > 0)
3616 svn_stringbuf_appendcstr(cap_log, " ");
3617 svn_stringbuf_appendcstr(cap_log, item->u.word);
3621 err = find_repos(client_url, params->root, &b, conn, cap_words, pool);
3624 SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
3625 if (current_access(&b) == NO_ACCESS)
3626 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3627 "Not authorized for access",
3632 log_error(err, b.log_file, svn_ra_svn_conn_remote_host(conn),
3633 b.user, NULL, pool);
3634 io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
3635 svn_error_clear(err);
3637 return svn_ra_svn__flush(conn, pool);
3641 if (ra_client_string == NULL || ra_client_string[0] == '\0')
3642 ra_client_string = "-";
3644 ra_client_string = svn_path_uri_encode(ra_client_string, pool);
3645 if (client_string == NULL || client_string[0] == '\0')
3646 client_string = "-";
3648 client_string = svn_path_uri_encode(client_string, pool);
3649 SVN_ERR(log_command(&b, conn, pool,
3650 "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s",
3652 svn_path_uri_encode(b.fs_path->data, pool),
3653 ra_client_string, client_string));
3655 warn_baton.server = &b;
3656 warn_baton.conn = conn;
3657 warn_baton.pool = svn_pool_create(pool);
3658 svn_fs_set_warning_func(b.fs, fs_warning_func, &warn_baton);
3660 SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
3662 /* We can't claim mergeinfo capability until we know whether the
3663 repository supports mergeinfo (i.e., is not a 1.4 repository),
3664 but we don't get the repository url from the client until after
3665 we've already sent the initial list of server capabilities. So
3666 we list repository capabilities here, in our first response after
3667 the client has sent the url. */
3669 svn_boolean_t supports_mergeinfo;
3670 SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo,
3671 SVN_REPOS_CAPABILITY_MERGEINFO, pool));
3673 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cc(!",
3674 "success", uuid, b.repos_url));
3675 if (supports_mergeinfo)
3676 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO));
3677 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3680 /* Set up editor shims. */
3682 svn_delta_shim_callbacks_t *callbacks =
3683 svn_delta_shim_callbacks_default(pool);
3685 callbacks->fetch_base_func = fetch_base_func;
3686 callbacks->fetch_props_func = fetch_props_func;
3687 callbacks->fetch_kind_func = fetch_kind_func;
3688 callbacks->fetch_baton = &b;
3690 SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks));
3693 return svn_ra_svn__handle_commands2(conn, pool, main_commands, &b, FALSE);