| /* |
| * serve.c : Functions for serving the Subversion protocol |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2007 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| |
| |
| |
| #include <limits.h> /* for UINT_MAX */ |
| |
| #define APR_WANT_STRFUNC |
| #include <apr_want.h> |
| #include <apr_general.h> |
| #include <apr_strings.h> |
| #include <apr_md5.h> |
| |
| #include "svn_compat.h" |
| #include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */ |
| #include "svn_types.h" |
| #include "svn_string.h" |
| #include "svn_pools.h" |
| #include "svn_error.h" |
| #include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */ |
| #include "svn_ra_svn.h" |
| #include "svn_repos.h" |
| #include "svn_path.h" |
| #include "svn_time.h" |
| #include "svn_md5.h" |
| #include "svn_config.h" |
| #include "svn_props.h" |
| #include "svn_mergeinfo.h" |
| #include "svn_user.h" |
| |
| #include "private/svn_mergeinfo_private.h" |
| |
| #include "server.h" |
| |
| typedef struct { |
| apr_pool_t *pool; |
| svn_revnum_t *new_rev; |
| const char **date; |
| const char **author; |
| const char **post_commit_err; |
| } commit_callback_baton_t; |
| |
| typedef struct { |
| server_baton_t *sb; |
| const char *repos_url; /* Decoded repository URL. */ |
| void *report_baton; |
| svn_error_t *err; |
| } report_driver_baton_t; |
| |
| typedef struct { |
| const char *fs_path; |
| svn_ra_svn_conn_t *conn; |
| int stack_depth; |
| } log_baton_t; |
| |
| typedef struct { |
| svn_ra_svn_conn_t *conn; |
| apr_pool_t *pool; /* Pool provided in the handler call. */ |
| } file_revs_baton_t; |
| |
| svn_error_t *load_configs(svn_config_t **cfg, |
| svn_config_t **pwdb, |
| svn_authz_t **authzdb, |
| const char *filename, |
| svn_boolean_t must_exist, |
| const char *base, |
| apr_pool_t *pool) |
| { |
| const char *pwdb_path, *authzdb_path; |
| svn_error_t *err; |
| |
| SVN_ERR(svn_config_read(cfg, filename, must_exist, pool)); |
| |
| svn_config_get(*cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL, |
| SVN_CONFIG_OPTION_PASSWORD_DB, NULL); |
| |
| *pwdb = NULL; |
| if (pwdb_path) |
| { |
| pwdb_path = svn_path_join(base, pwdb_path, pool); |
| |
| /* Because it may be possible to read the pwdb file with some |
| * access methods and not others, ignore errors reading the pwdb |
| * file and just don't present password authentication as an |
| * option. Also, some authentications (e.g. --tunnel) can |
| * proceed without it anyway. |
| * |
| * ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked |
| * ### for here. That seems to have been introduced in r16840, |
| * ### and only in r30868 was the APR_EACCES check introduced. |
| * |
| * TODO: Log a warning in this case, when we have a way of doing logging. |
| */ |
| err = svn_config_read(pwdb, pwdb_path, TRUE, pool); |
| if (err |
| && (err->apr_err == SVN_ERR_BAD_FILENAME |
| || APR_STATUS_IS_EACCES(err->apr_err))) |
| { |
| svn_error_clear(err); |
| } |
| else if (err) |
| return err; |
| } |
| |
| /* Read authz configuration. */ |
| svn_config_get(*cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL, |
| SVN_CONFIG_OPTION_AUTHZ_DB, NULL); |
| if (authzdb_path) |
| { |
| authzdb_path = svn_path_join(base, authzdb_path, pool); |
| SVN_ERR(svn_repos_authz_read(authzdb, authzdb_path, TRUE, pool)); |
| } |
| else |
| { |
| *authzdb = NULL; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Set *FS_PATH to the portion of URL that is the path within the |
| repository, if URL is inside REPOS_URL (if URL is not inside |
| REPOS_URL, then error, with the effect on *FS_PATH undefined). |
| |
| If the resultant fs path would be the empty string (i.e., URL and |
| REPOS_URL are the same), then set *FS_PATH to "/". |
| |
| Assume that REPOS_URL and URL are already URI-decoded. */ |
| static svn_error_t *get_fs_path(const char *repos_url, const char *url, |
| const char **fs_path) |
| { |
| apr_size_t len; |
| |
| len = strlen(repos_url); |
| if (strncmp(url, repos_url, len) != 0) |
| return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, |
| "'%s' is not the same repository as '%s'", |
| url, repos_url); |
| *fs_path = url + len; |
| if (! **fs_path) |
| *fs_path = "/"; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */ |
| |
| /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to |
| the user described in BATON according to the authz rules in BATON. |
| Use POOL for temporary allocations only. If no authz rules are |
| present in BATON, grant access by default. */ |
| static svn_error_t *authz_check_access(svn_boolean_t *allowed, |
| const char *path, |
| svn_repos_authz_access_t required, |
| server_baton_t *b, |
| apr_pool_t *pool) |
| { |
| /* If authz cannot be performed, grant access. This is NOT the same |
| as the default policy when authz is performed on a path with no |
| rules. In the latter case, the default is to deny access, and is |
| set by svn_repos_authz_check_access. */ |
| if (!b->authzdb) |
| { |
| *allowed = TRUE; |
| return SVN_NO_ERROR; |
| } |
| |
| /* If the authz request is for the empty path (ie. ""), replace it |
| with the root path. This happens because of stripping done at |
| various levels in svnserve that remove the leading / on an |
| absolute path. Passing such a malformed path to the authz |
| routines throws them into an infinite loop and makes them miss |
| ACLs. */ |
| if (path && *path != '/') |
| path = svn_path_join("/", path, pool); |
| |
| return svn_repos_authz_check_access(b->authzdb, b->authz_repos_name, |
| path, b->user, required, |
| allowed, pool); |
| } |
| |
| /* Set *ALLOWED to TRUE if PATH is readable by the user described in |
| * BATON. Use POOL for temporary allocations only. ROOT is not used. |
| * Implements the svn_repos_authz_func_t interface. |
| */ |
| static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed, |
| svn_fs_root_t *root, |
| const char *path, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| server_baton_t *sb = baton; |
| |
| return authz_check_access(allowed, path, svn_authz_read, sb, pool); |
| } |
| |
| /* If authz is enabled in the specified BATON, return a read authorization |
| function. Otherwise, return NULL. */ |
| static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton) |
| { |
| if (baton->authzdb) |
| return authz_check_access_cb; |
| return NULL; |
| } |
| |
| /* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted, |
| * according to the state in BATON. Use POOL for temporary |
| * allocations only. ROOT is not used. Implements the |
| * svn_repos_authz_callback_t interface. |
| */ |
| static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required, |
| svn_boolean_t *allowed, |
| svn_fs_root_t *root, |
| const char *path, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| server_baton_t *sb = baton; |
| |
| return authz_check_access(allowed, path, required, sb, pool); |
| } |
| |
| |
| enum access_type get_access(server_baton_t *b, enum authn_type auth) |
| { |
| const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS : |
| SVN_CONFIG_OPTION_ANON_ACCESS; |
| const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read"; |
| enum access_type result; |
| |
| svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def); |
| result = (strcmp(val, "write") == 0 ? WRITE_ACCESS : |
| strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS); |
| return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result; |
| } |
| |
| static enum access_type current_access(server_baton_t *b) |
| { |
| return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED); |
| } |
| |
| /* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME |
| is true, don't send anonymous mech even if that would give the desired |
| access. */ |
| static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| server_baton_t *b, enum access_type required, |
| svn_boolean_t needs_username) |
| { |
| if (!needs_username && get_access(b, UNAUTHENTICATED) >= required) |
| SVN_ERR(svn_ra_svn_write_word(conn, pool, "ANONYMOUS")); |
| if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required) |
| SVN_ERR(svn_ra_svn_write_word(conn, pool, "EXTERNAL")); |
| if (b->pwdb && get_access(b, AUTHENTICATED) >= required) |
| SVN_ERR(svn_ra_svn_write_word(conn, pool, "CRAM-MD5")); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Context for cleanup handler. */ |
| struct cleanup_fs_access_baton |
| { |
| svn_fs_t *fs; |
| apr_pool_t *pool; |
| }; |
| |
| /* Pool cleanup handler. Make sure fs's access_t points to NULL when |
| the command pool is destroyed. */ |
| static apr_status_t cleanup_fs_access(void *data) |
| { |
| svn_error_t *serr; |
| struct cleanup_fs_access_baton *baton = data; |
| |
| serr = svn_fs_set_access(baton->fs, NULL); |
| if (serr) |
| { |
| apr_status_t apr_err = serr->apr_err; |
| svn_error_clear(serr); |
| return apr_err; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| |
| /* Create an svn_fs_access_t in POOL for USER and associate it with |
| B's filesystem. Also, register a cleanup handler with POOL which |
| de-associates the svn_fs_access_t from B's filesystem. */ |
| static svn_error_t * |
| create_fs_access(server_baton_t *b, apr_pool_t *pool) |
| { |
| svn_fs_access_t *fs_access; |
| struct cleanup_fs_access_baton *cleanup_baton; |
| |
| if (!b->user) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool)); |
| SVN_ERR(svn_fs_set_access(b->fs, fs_access)); |
| |
| cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton)); |
| cleanup_baton->pool = pool; |
| cleanup_baton->fs = b->fs; |
| apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access, |
| apr_pool_cleanup_null); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Authenticate, once the client has chosen a mechanism and possibly |
| * sent an initial mechanism token. On success, set *success to true |
| * and b->user to the authenticated username (or NULL for anonymous). |
| * On authentication failure, report failure to the client and set |
| * *success to FALSE. On communications failure, return an error. |
| * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */ |
| static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| const char *mech, const char *mecharg, |
| server_baton_t *b, enum access_type required, |
| svn_boolean_t needs_username, |
| svn_boolean_t *success) |
| { |
| const char *user; |
| *success = FALSE; |
| |
| if (get_access(b, AUTHENTICATED) >= required |
| && b->tunnel_user && strcmp(mech, "EXTERNAL") == 0) |
| { |
| if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0) |
| return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", |
| "Requested username does not match"); |
| b->user = b->tunnel_user; |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success")); |
| *success = TRUE; |
| return SVN_NO_ERROR; |
| } |
| |
| if (get_access(b, UNAUTHENTICATED) >= required |
| && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username) |
| { |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success")); |
| *success = TRUE; |
| return SVN_NO_ERROR; |
| } |
| |
| if (get_access(b, AUTHENTICATED) >= required |
| && b->pwdb && strcmp(mech, "CRAM-MD5") == 0) |
| { |
| SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success)); |
| b->user = apr_pstrdup(b->pool, user); |
| return SVN_NO_ERROR; |
| } |
| |
| return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", |
| "Must authenticate with listed mechanism"); |
| } |
| |
| /* Perform an authentication request using the built-in SASL implementation. */ |
| static svn_error_t * |
| internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| server_baton_t *b, enum access_type required, |
| svn_boolean_t needs_username) |
| { |
| svn_boolean_t success; |
| const char *mech, *mecharg; |
| |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success")); |
| SVN_ERR(send_mechs(conn, pool, b, required, needs_username)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)c)", b->realm)); |
| do |
| { |
| SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &mech, &mecharg)); |
| if (!*mech) |
| break; |
| SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username, |
| &success)); |
| } |
| while (!success); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Perform an authentication request in order to get an access level of |
| * REQUIRED or higher. Since the client may escape the authentication |
| * exchange, the caller should check current_access(b) to see if |
| * authentication succeeded. */ |
| static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| server_baton_t *b, enum access_type required, |
| svn_boolean_t needs_username) |
| { |
| #ifdef SVN_HAVE_SASL |
| if (b->use_sasl) |
| SVN_ERR(cyrus_auth_request(conn, pool, b, required, needs_username)); |
| else |
| #endif |
| SVN_ERR(internal_auth_request(conn, pool, b, required, needs_username)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Send a trivial auth notification on CONN which lists no mechanisms, |
| * indicating that authentication is unnecessary. Usually called in |
| * response to invocation of a svnserve command. |
| */ |
| static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn, |
| apr_pool_t *pool, server_baton_t *b) |
| { |
| return svn_ra_svn_write_cmd_response(conn, pool, "()c", ""); |
| } |
| |
| /* Ensure that the client has the REQUIRED access by checking the |
| * access directives (both blanket and per-directory) in BATON. If |
| * PATH is NULL, then only the blanket access configuration will |
| * impact the result. |
| * |
| * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the |
| * user described in BATON is authenticated and, well, has a username |
| * assigned to him. |
| * |
| * Use POOL for temporary allocations only. |
| */ |
| static svn_boolean_t lookup_access(apr_pool_t *pool, |
| server_baton_t *baton, |
| svn_repos_authz_access_t required, |
| const char *path, |
| svn_boolean_t needs_username) |
| { |
| enum access_type req = (required & svn_authz_write) ? |
| WRITE_ACCESS : READ_ACCESS; |
| svn_boolean_t authorized; |
| svn_error_t *err; |
| |
| /* Get authz's opinion on the access. */ |
| err = authz_check_access(&authorized, path, required, baton, pool); |
| |
| /* If an error made lookup fail, deny access. ### TODO: Once |
| logging is implemented, this is a perfect place to log the |
| problem. */ |
| if (err) |
| { |
| svn_error_clear(err); |
| return FALSE; |
| } |
| |
| /* If the required access is blanket-granted AND granted by authz |
| AND we already have a username if one is required, then the |
| lookup has succeeded. */ |
| if (current_access(baton) >= req |
| && authorized |
| && (! needs_username || baton->user)) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| /* Check that the client has the REQUIRED access by consulting the |
| * authentication and authorization states stored in BATON. If the |
| * client does not have the required access credentials, attempt to |
| * authenticate the client to get that access, using CONN for |
| * communication. |
| * |
| * This function is supposed to be called to handle the authentication |
| * half of a standard svn protocol reply. If an error is returned, it |
| * probably means that the server can terminate the client connection |
| * with an apologetic error, as it implies an authentication failure. |
| * |
| * PATH and NEEDS_USERNAME are passed along to lookup_access, their |
| * behaviour is documented there. |
| */ |
| static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn, |
| apr_pool_t *pool, |
| server_baton_t *b, |
| svn_repos_authz_access_t required, |
| const char *path, |
| svn_boolean_t needs_username) |
| { |
| enum access_type req = (required & svn_authz_write) ? |
| WRITE_ACCESS : READ_ACCESS; |
| |
| /* See whether the user already has the required access. If so, |
| nothing needs to be done. Create the FS access and send a |
| trivial auth request. */ |
| if (lookup_access(pool, b, required, path, needs_username)) |
| { |
| SVN_ERR(create_fs_access(b, pool)); |
| return trivial_auth_request(conn, pool, b); |
| } |
| |
| /* If the required blanket access can be obtained by authenticating, |
| try that. Unfortunately, we can't tell until after |
| authentication whether authz will work or not. We force |
| requiring a username because we need one to be able to check |
| authz configuration again with a different user credentials than |
| the first time round. */ |
| if (b->user == NULL |
| && get_access(b, AUTHENTICATED) >= req |
| && (b->tunnel_user || b->pwdb |
| #ifdef SVN_HAVE_SASL |
| || b->use_sasl |
| #endif |
| )) |
| SVN_ERR(auth_request(conn, pool, b, req, TRUE)); |
| |
| /* Now that an authentication has been done get the new take of |
| authz on the request. */ |
| if (! lookup_access(pool, b, required, path, needs_username)) |
| return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, |
| svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, |
| NULL, NULL), NULL); |
| |
| /* Else, access is granted, and there is much rejoicing. */ |
| SVN_ERR(create_fs_access(b, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* --- REPORTER COMMAND SET --- */ |
| |
| /* To allow for pipelining, reporter commands have no reponses. If we |
| * get an error, we ignore all subsequent reporter commands and return |
| * the error finish_report, to be handled by the calling command. |
| */ |
| |
| static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| report_driver_baton_t *b = baton; |
| const char *path, *lock_token, *depth_word; |
| svn_revnum_t rev; |
| /* Default to infinity, for old clients that don't send depth. */ |
| svn_depth_t depth = svn_depth_infinity; |
| svn_boolean_t start_empty; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crb?(?c)?w", |
| &path, &rev, &start_empty, &lock_token, |
| &depth_word)); |
| if (depth_word) |
| depth = svn_depth_from_word(depth_word); |
| path = svn_path_canonicalize(path, pool); |
| if (!b->err) |
| b->err = svn_repos_set_path3(b->report_baton, path, rev, depth, |
| start_empty, lock_token, pool); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| report_driver_baton_t *b = baton; |
| const char *path; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path)); |
| path = svn_path_canonicalize(path, pool); |
| if (!b->err) |
| b->err = svn_repos_delete_path(b->report_baton, path, pool); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| report_driver_baton_t *b = baton; |
| const char *path, *url, *lock_token, *fs_path, *depth_word; |
| svn_revnum_t rev; |
| svn_boolean_t start_empty; |
| /* Default to infinity, for old clients that don't send depth. */ |
| svn_depth_t depth = svn_depth_infinity; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccrb?(?c)?w", |
| &path, &url, &rev, &start_empty, |
| &lock_token, &depth_word)); |
| path = svn_path_canonicalize(path, pool); |
| url = svn_path_uri_decode(svn_path_canonicalize(url, pool), pool); |
| if (depth_word) |
| depth = svn_depth_from_word(depth_word); |
| if (!b->err) |
| b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool), |
| url, &fs_path); |
| if (!b->err) |
| b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev, |
| depth, start_empty, lock_token, pool); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| report_driver_baton_t *b = baton; |
| |
| /* No arguments to parse. */ |
| SVN_ERR(trivial_auth_request(conn, pool, b->sb)); |
| if (!b->err) |
| b->err = svn_repos_finish_report(b->report_baton, pool); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| report_driver_baton_t *b = baton; |
| |
| /* No arguments to parse. */ |
| svn_error_clear(svn_repos_abort_report(b->report_baton, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| static const svn_ra_svn_cmd_entry_t report_commands[] = { |
| { "set-path", set_path }, |
| { "delete-path", delete_path }, |
| { "link-path", link_path }, |
| { "finish-report", finish_report, TRUE }, |
| { "abort-report", abort_report, TRUE }, |
| { NULL } |
| }; |
| |
| /* Accept a report from the client, drive the network editor with the |
| * result, and then write an empty command response. If there is a |
| * non-protocol failure, accept_report will abort the edit and return |
| * a command error to be reported by handle_commands(). */ |
| static svn_error_t *accept_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| server_baton_t *b, svn_revnum_t rev, |
| const char *target, const char *tgt_path, |
| svn_boolean_t text_deltas, |
| svn_depth_t depth, |
| svn_boolean_t send_copyfrom_args, |
| svn_boolean_t ignore_ancestry) |
| { |
| const svn_delta_editor_t *editor; |
| void *edit_baton, *report_baton; |
| report_driver_baton_t rb; |
| svn_error_t *err; |
| |
| /* Make an svn_repos report baton. Tell it to drive the network editor |
| * when the report is complete. */ |
| svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); |
| SVN_CMD_ERR(svn_repos_begin_report2(&report_baton, rev, b->repos, |
| b->fs_path->data, target, tgt_path, |
| text_deltas, depth, ignore_ancestry, |
| send_copyfrom_args, |
| editor, edit_baton, |
| authz_check_access_cb_func(b), |
| b, pool)); |
| |
| rb.sb = b; |
| rb.repos_url = svn_path_uri_decode(b->repos_url, pool); |
| rb.report_baton = report_baton; |
| rb.err = NULL; |
| err = svn_ra_svn_handle_commands(conn, pool, report_commands, &rb); |
| if (err) |
| { |
| /* Network or protocol error while handling commands. */ |
| svn_error_clear(rb.err); |
| return err; |
| } |
| else if (rb.err) |
| { |
| /* Some failure during the reporting or editing operations. */ |
| svn_error_clear(editor->abort_edit(edit_baton, pool)); |
| SVN_CMD_ERR(rb.err); |
| } |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| return SVN_NO_ERROR; |
| } |
| |
| /* --- MAIN COMMAND SET --- */ |
| |
| /* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t |
| * values. */ |
| static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn, |
| apr_pool_t *pool, |
| apr_array_header_t *propdiffs) |
| { |
| int i; |
| |
| for (i = 0; i < propdiffs->nelts; ++i) |
| { |
| const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t); |
| |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "c(?s)", |
| prop->name, prop->value)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Write out a lock to the client. */ |
| static svn_error_t *write_lock(svn_ra_svn_conn_t *conn, |
| apr_pool_t *pool, |
| svn_lock_t *lock) |
| { |
| const char *cdate, *edate; |
| |
| cdate = svn_time_to_cstring(lock->creation_date, pool); |
| edate = lock->expiration_date |
| ? svn_time_to_cstring(lock->expiration_date, pool) : NULL; |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path, |
| lock->token, lock->owner, lock->comment, |
| cdate, edate)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static const char *kind_word(svn_node_kind_t kind) |
| { |
| switch (kind) |
| { |
| case svn_node_none: |
| return "none"; |
| case svn_node_file: |
| return "file"; |
| case svn_node_dir: |
| return "dir"; |
| case svn_node_unknown: |
| return "unknown"; |
| default: |
| abort(); |
| } |
| |
| /* Make the compiler happy */ |
| return NULL; |
| } |
| |
| /* ### This really belongs in libsvn_repos. */ |
| /* Get the properties for a path, with hardcoded committed-info values. */ |
| static svn_error_t *get_props(apr_hash_t **props, svn_fs_root_t *root, |
| const char *path, apr_pool_t *pool) |
| { |
| svn_string_t *str; |
| svn_revnum_t crev; |
| const char *cdate, *cauthor, *uuid; |
| |
| /* Get the properties. */ |
| SVN_ERR(svn_fs_node_proplist(props, root, path, pool)); |
| |
| /* Hardcode the values for the committed revision, date, and author. */ |
| SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root, |
| path, pool)); |
| str = svn_string_create(apr_psprintf(pool, "%ld", crev), |
| pool); |
| apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING, str); |
| str = (cdate) ? svn_string_create(cdate, pool) : NULL; |
| apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING, |
| str); |
| str = (cauthor) ? svn_string_create(cauthor, pool) : NULL; |
| apr_hash_set(*props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING, str); |
| |
| /* Hardcode the values for the UUID. */ |
| SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool)); |
| str = (uuid) ? svn_string_create(uuid, pool) : NULL; |
| apr_hash_set(*props, SVN_PROP_ENTRY_UUID, APR_HASH_KEY_STRING, str); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Set BATON->FS_PATH for the repository URL found in PARAMS. */ |
| static svn_error_t *reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| const char *url; |
| const char *fs_path; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &url)); |
| url = svn_path_uri_decode(svn_path_canonicalize(url, pool), pool); |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool), |
| url, &fs_path)); |
| svn_stringbuf_set(b->fs_path, fs_path); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| apr_time_t tm; |
| const char *timestr; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", ×tr)); |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool)); |
| SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool)); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| const char *name; |
| svn_string_t *value; |
| |
| /* Because the revprop value was at one time mandatory, the usual |
| optional element pattern "(?s)" isn't used. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc?s", &rev, &name, &value)); |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE)); |
| SVN_CMD_ERR(svn_repos_fs_change_rev_prop3(b->repos, rev, b->user, |
| name, value, TRUE, TRUE, |
| authz_check_access_cb_func(b), b, |
| pool)); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| apr_hash_t *props; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev)); |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev, |
| authz_check_access_cb_func(b), b, |
| pool)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success")); |
| SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| const char *name; |
| svn_string_t *value; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc", &rev, &name)); |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name, |
| authz_check_access_cb_func(b), b, |
| pool)); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "(?s)", value)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *commit_done(const svn_commit_info_t *commit_info, |
| void *baton, apr_pool_t *pool) |
| { |
| commit_callback_baton_t *ccb = baton; |
| |
| *ccb->new_rev = commit_info->revision; |
| *ccb->date = commit_info->date |
| ? apr_pstrdup(ccb->pool, commit_info->date): NULL; |
| *ccb->author = commit_info->author |
| ? apr_pstrdup(ccb->pool, commit_info->author) : NULL; |
| *ccb->post_commit_err = commit_info->post_commit_err |
| ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL; |
| return SVN_NO_ERROR; |
| } |
| |
| /* Add the LOCK_TOKENS (if any) to the filesystem access context, |
| * checking path authorizations using the state in SB as we go. |
| * LOCK_TOKENS is an array of svn_ra_svn_item_t structs. Return a |
| * client error if LOCK_TOKENS is not a list of lists. If a lock |
| * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED |
| * to the client. Use POOL for temporary allocations only. |
| */ |
| static svn_error_t *add_lock_tokens(svn_ra_svn_conn_t *conn, |
| apr_array_header_t *lock_tokens, |
| server_baton_t *sb, |
| apr_pool_t *pool) |
| { |
| int i; |
| svn_fs_access_t *fs_access; |
| |
| SVN_ERR(svn_fs_get_access(&fs_access, sb->fs)); |
| |
| /* If there is no access context, nowhere to add the tokens. */ |
| if (! fs_access) |
| return SVN_NO_ERROR; |
| |
| for (i = 0; i < lock_tokens->nelts; ++i) |
| { |
| const char *path, *token, *full_path; |
| svn_ra_svn_item_t *path_item, *token_item; |
| svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i, |
| svn_ra_svn_item_t); |
| if (item->kind != SVN_RA_SVN_LIST) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| "Lock tokens aren't a list of lists"); |
| |
| path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t); |
| if (path_item->kind != SVN_RA_SVN_STRING) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| "Lock path isn't a string"); |
| |
| token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t); |
| if (token_item->kind != SVN_RA_SVN_STRING) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| "Lock token isn't a string"); |
| |
| path = path_item->u.string->data; |
| full_path = svn_path_join(sb->fs_path->data, |
| svn_path_canonicalize(path, pool), |
| pool); |
| |
| if (! lookup_access(pool, sb, svn_authz_write, |
| full_path, TRUE)) |
| return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, |
| NULL, NULL); |
| |
| token = token_item->u.string->data; |
| SVN_ERR(svn_fs_access_add_lock_token(fs_access, token)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors. |
| LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */ |
| static svn_error_t *unlock_paths(apr_array_header_t *lock_tokens, |
| server_baton_t *sb, |
| apr_pool_t *pool) |
| { |
| int i; |
| apr_pool_t *iterpool; |
| |
| iterpool = svn_pool_create(pool); |
| |
| for (i = 0; i < lock_tokens->nelts; ++i) |
| { |
| svn_ra_svn_item_t *item, *path_item, *token_item; |
| const char *path, *token, *full_path; |
| svn_pool_clear(iterpool); |
| |
| item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t); |
| path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t); |
| token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t); |
| |
| path = path_item->u.string->data; |
| token = token_item->u.string->data; |
| |
| full_path = svn_path_join(sb->fs_path->data, |
| svn_path_canonicalize(path, iterpool), |
| iterpool); |
| |
| /* The lock may have become defunct after the commit, so ignore such |
| errors. |
| |
| ### If we ever write a logging facility for svnserve, this |
| would be a good place to log an error before clearing |
| it. */ |
| svn_error_clear(svn_repos_fs_unlock(sb->repos, full_path, token, |
| FALSE, pool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| const char *log_msg = NULL, |
| *date = NULL, |
| *author = NULL, |
| *post_commit_err = NULL; |
| apr_array_header_t *lock_tokens; |
| svn_boolean_t keep_locks; |
| apr_array_header_t *revprop_list = NULL; |
| apr_hash_t *revprop_table; |
| const svn_delta_editor_t *editor; |
| void *edit_baton; |
| svn_boolean_t aborted; |
| commit_callback_baton_t ccb; |
| svn_revnum_t new_rev; |
| |
| if (params->nelts == 1) |
| { |
| /* Clients before 1.2 don't send lock-tokens, keep-locks, |
| and rev-props fields. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &log_msg)); |
| lock_tokens = NULL; |
| keep_locks = TRUE; |
| revprop_list = NULL; |
| } |
| else |
| { |
| /* Clients before 1.5 don't send the rev-props field. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "clb?l", &log_msg, |
| &lock_tokens, &keep_locks, |
| &revprop_list)); |
| } |
| |
| /* The handling for locks is a little problematic, because the |
| protocol won't let us send several auth requests once one has |
| succeeded. So we request write access and a username before |
| adding tokens (if we have any), and subsequently fail if a lock |
| violates authz. */ |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, |
| NULL, |
| (lock_tokens && lock_tokens->nelts) ? TRUE : FALSE)); |
| |
| /* Authorize the lock tokens and give them to the FS if we got |
| any. */ |
| if (lock_tokens && lock_tokens->nelts) |
| SVN_CMD_ERR(add_lock_tokens(conn, lock_tokens, b, pool)); |
| |
| if (revprop_list) |
| SVN_ERR(svn_ra_svn_parse_proplist(revprop_list, pool, &revprop_table)); |
| else |
| { |
| revprop_table = apr_hash_make(pool); |
| apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING, |
| svn_string_create(log_msg, pool)); |
| } |
| |
| /* Get author from the baton, making sure clients can't circumvent |
| the authentication via the revision props. */ |
| apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING, |
| b->user ? svn_string_create(b->user, pool) : NULL); |
| |
| ccb.pool = pool; |
| ccb.new_rev = &new_rev; |
| ccb.date = &date; |
| ccb.author = &author; |
| ccb.post_commit_err = &post_commit_err; |
| /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */ |
| SVN_CMD_ERR(svn_repos_get_commit_editor5 |
| (&editor, &edit_baton, b->repos, NULL, |
| svn_path_uri_decode(b->repos_url, pool), |
| b->fs_path->data, revprop_table, |
| commit_done, &ccb, |
| authz_commit_cb, baton, pool)); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| SVN_ERR(svn_ra_svn_drive_editor(conn, pool, editor, edit_baton, &aborted)); |
| if (!aborted) |
| { |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| |
| /* In tunnel mode, deltify before answering the client, because |
| answering may cause the client to terminate the connection |
| and thus kill the server. But otherwise, deltify after |
| answering the client, to avoid user-visible delay. */ |
| |
| if (b->tunnel) |
| SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool)); |
| |
| /* Unlock the paths. */ |
| if (! keep_locks && lock_tokens && lock_tokens->nelts) |
| SVN_ERR(unlock_paths(lock_tokens, b, pool)); |
| |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(?c)(?c)(?c)", |
| new_rev, date, author, post_commit_err)); |
| |
| if (! b->tunnel) |
| SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool)); |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| const char *path, *full_path, *hex_digest; |
| svn_revnum_t rev; |
| svn_fs_root_t *root; |
| svn_stream_t *contents; |
| apr_hash_t *props = NULL; |
| svn_string_t write_str; |
| char buf[4096]; |
| apr_size_t len; |
| svn_boolean_t want_props, want_contents; |
| unsigned char digest[APR_MD5_DIGESTSIZE]; |
| svn_error_t *err, *write_err; |
| |
| /* Parse arguments. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev, |
| &want_props, &want_contents)); |
| |
| full_path = svn_path_join(b->fs_path->data, |
| svn_path_canonicalize(path, pool), pool); |
| |
| /* Check authorizations */ |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, |
| full_path, FALSE)); |
| |
| if (!SVN_IS_VALID_REVNUM(rev)) |
| SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); |
| |
| /* Fetch the properties and a stream for the contents. */ |
| SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); |
| SVN_CMD_ERR(svn_fs_file_md5_checksum(digest, root, full_path, pool)); |
| hex_digest = svn_md5_digest_to_cstring_display(digest, pool); |
| if (want_props) |
| SVN_CMD_ERR(get_props(&props, root, full_path, pool)); |
| if (want_contents) |
| SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool)); |
| |
| /* Send successful command response with revision and props. */ |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)r(!", "success", |
| hex_digest, rev)); |
| SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); |
| |
| /* Now send the file's contents. */ |
| if (want_contents) |
| { |
| err = SVN_NO_ERROR; |
| while (1) |
| { |
| len = sizeof(buf); |
| err = svn_stream_read(contents, buf, &len); |
| if (err) |
| break; |
| if (len > 0) |
| { |
| write_str.data = buf; |
| write_str.len = len; |
| SVN_ERR(svn_ra_svn_write_string(conn, pool, &write_str)); |
| } |
| if (len < sizeof(buf)) |
| { |
| err = svn_stream_close(contents); |
| break; |
| } |
| } |
| write_err = svn_ra_svn_write_cstring(conn, pool, ""); |
| if (write_err) |
| { |
| svn_error_clear(err); |
| return write_err; |
| } |
| SVN_CMD_ERR(err); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| const char *path, *full_path, *file_path, *name, *cauthor, *cdate; |
| svn_revnum_t rev; |
| apr_hash_t *entries, *props = NULL, *file_props; |
| apr_hash_index_t *hi; |
| svn_fs_dirent_t *fsent; |
| svn_dirent_t *entry; |
| const void *key; |
| void *val; |
| svn_fs_root_t *root; |
| apr_pool_t *subpool; |
| svn_boolean_t want_props, want_contents; |
| apr_uint64_t dirent_fields; |
| apr_array_header_t *dirent_fields_list = NULL; |
| svn_ra_svn_item_t *elt; |
| int i; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb?l", &path, &rev, |
| &want_props, &want_contents, |
| &dirent_fields_list)); |
| |
| if (! dirent_fields_list) |
| { |
| dirent_fields = SVN_DIRENT_ALL; |
| } |
| else |
| { |
| dirent_fields = 0; |
| |
| for (i = 0; i < dirent_fields_list->nelts; ++i) |
| { |
| elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t); |
| |
| if (elt->kind != SVN_RA_SVN_WORD) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| "Dirent field not a string"); |
| |
| if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0) |
| dirent_fields |= SVN_DIRENT_KIND; |
| else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0) |
| dirent_fields |= SVN_DIRENT_SIZE; |
| else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0) |
| dirent_fields |= SVN_DIRENT_HAS_PROPS; |
| else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0) |
| dirent_fields |= SVN_DIRENT_CREATED_REV; |
| else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0) |
| dirent_fields |= SVN_DIRENT_TIME; |
| else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0) |
| dirent_fields |= SVN_DIRENT_LAST_AUTHOR; |
| } |
| } |
| |
| full_path = svn_path_join(b->fs_path->data, |
| svn_path_canonicalize(path, pool), pool); |
| |
| /* Check authorizations */ |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, |
| full_path, FALSE)); |
| |
| if (!SVN_IS_VALID_REVNUM(rev)) |
| SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); |
| |
| /* Fetch the root of the appropriate revision. */ |
| SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); |
| |
| /* Fetch the directory properties if requested. */ |
| if (want_props) |
| SVN_CMD_ERR(get_props(&props, root, full_path, pool)); |
| |
| /* Fetch the directory entries if requested. */ |
| if (want_contents) |
| { |
| SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool)); |
| |
| /* Transform the hash table's FS entries into dirents. This probably |
| * belongs in libsvn_repos. */ |
| subpool = svn_pool_create(pool); |
| for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) |
| { |
| apr_hash_this(hi, &key, NULL, &val); |
| name = key; |
| fsent = val; |
| |
| svn_pool_clear(subpool); |
| |
| file_path = svn_path_join(full_path, name, subpool); |
| entry = apr_pcalloc(pool, sizeof(*entry)); |
| |
| if (dirent_fields & SVN_DIRENT_KIND) |
| { |
| /* kind */ |
| entry->kind = fsent->kind; |
| } |
| |
| if (dirent_fields & SVN_DIRENT_SIZE) |
| { |
| /* size */ |
| if (entry->kind == svn_node_dir) |
| entry->size = 0; |
| else |
| SVN_CMD_ERR(svn_fs_file_length(&entry->size, root, file_path, |
| subpool)); |
| } |
| |
| if (dirent_fields & SVN_DIRENT_HAS_PROPS) |
| { |
| /* has_props */ |
| SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path, |
| subpool)); |
| entry->has_props = (apr_hash_count(file_props) > 0) ? TRUE |
| : FALSE; |
| } |
| |
| if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR) |
| || (dirent_fields & SVN_DIRENT_TIME) |
| || (dirent_fields & SVN_DIRENT_CREATED_REV)) |
| { |
| /* created_rev, last_author, time */ |
| SVN_CMD_ERR(svn_repos_get_committed_info(&entry->created_rev, |
| &cdate, |
| &cauthor, root, |
| file_path, |
| subpool)); |
| entry->last_author = apr_pstrdup(pool, cauthor); |
| if (cdate) |
| SVN_CMD_ERR(svn_time_from_cstring(&entry->time, cdate, |
| subpool)); |
| else |
| entry->time = (time_t) -1; |
| } |
| |
| /* Store the entry. */ |
| apr_hash_set(entries, name, APR_HASH_KEY_STRING, entry); |
| } |
| svn_pool_destroy(subpool); |
| } |
| |
| /* Write out response. */ |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(r(!", "success", rev)); |
| SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(!")); |
| if (want_contents) |
| { |
| for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) |
| { |
| apr_hash_this(hi, &key, NULL, &val); |
| name = key; |
| entry = val; |
| cdate = (entry->time == (time_t) -1) ? NULL |
| : svn_time_to_cstring(entry->time, pool); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cwnbr(?c)(?c)", name, |
| kind_word(entry->kind), |
| (apr_uint64_t) entry->size, |
| entry->has_props, entry->created_rev, |
| cdate, entry->last_author)); |
| } |
| } |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| const char *target, *full_path, *depth_word; |
| svn_boolean_t recurse; |
| svn_boolean_t send_copyfrom_args; |
| apr_uint64_t send_copyfrom_param; |
| /* Default to unknown. Old clients won't send depth, but we'll |
| handle that by converting recurse if necessary. */ |
| svn_depth_t depth = svn_depth_unknown; |
| |
| /* Parse the arguments. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cb?wB", &rev, &target, |
| &recurse, &depth_word, &send_copyfrom_param)); |
| target = svn_path_canonicalize(target, pool); |
| |
| if (depth_word) |
| depth = svn_depth_from_word(depth_word); |
| else |
| depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); |
| |
| send_copyfrom_args = (send_copyfrom_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) ? |
| FALSE : (svn_boolean_t) send_copyfrom_param; |
| |
| full_path = svn_path_join(b->fs_path->data, target, pool); |
| /* Check authorization and authenticate the user if necessary. */ |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); |
| |
| if (!SVN_IS_VALID_REVNUM(rev)) |
| SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); |
| |
| return accept_report(conn, pool, b, rev, target, NULL, TRUE, |
| depth, send_copyfrom_args, FALSE); |
| } |
| |
| static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| const char *target, *depth_word; |
| const char *switch_url, *switch_path; |
| svn_boolean_t recurse; |
| /* Default to unknown. Old clients won't send depth, but we'll |
| handle that by converting recurse if necessary. */ |
| svn_depth_t depth = svn_depth_unknown; |
| |
| /* Parse the arguments. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbc?w", &rev, &target, |
| &recurse, &switch_url, &depth_word)); |
| target = svn_path_canonicalize(target, pool); |
| switch_url = svn_path_canonicalize(switch_url, pool); |
| |
| if (depth_word) |
| depth = svn_depth_from_word(depth_word); |
| else |
| depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); |
| |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| if (!SVN_IS_VALID_REVNUM(rev)) |
| SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); |
| SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool), |
| svn_path_uri_decode(switch_url, pool), |
| &switch_path)); |
| |
| return accept_report(conn, pool, b, rev, target, switch_path, TRUE, |
| depth, |
| FALSE /* TODO(sussman): no copyfrom args for now */, |
| TRUE); |
| } |
| |
| static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| const char *target, *depth_word; |
| svn_boolean_t recurse; |
| /* Default to unknown. Old clients won't send depth, but we'll |
| handle that by converting recurse if necessary. */ |
| svn_depth_t depth = svn_depth_unknown; |
| |
| /* Parse the arguments. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cb?(?r)?w", |
| &target, &recurse, &rev, &depth_word)); |
| target = svn_path_canonicalize(target, pool); |
| |
| if (depth_word) |
| depth = svn_depth_from_word(depth_word); |
| else |
| depth = recurse ? svn_depth_infinity : svn_depth_empty; |
| |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| if (!SVN_IS_VALID_REVNUM(rev)) |
| SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); |
| |
| return accept_report(conn, pool, b, rev, target, NULL, FALSE, |
| depth, FALSE, FALSE); |
| } |
| |
| static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| const char *target, *versus_url, *versus_path, *depth_word; |
| svn_boolean_t recurse, ignore_ancestry; |
| svn_boolean_t text_deltas; |
| /* Default to unknown. Old clients won't send depth, but we'll |
| handle that by converting recurse if necessary. */ |
| svn_depth_t depth = svn_depth_unknown; |
| |
| /* Parse the arguments. */ |
| if (params->nelts == 5) |
| { |
| /* Clients before 1.4 don't send the text_deltas boolean or depth. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbc", &rev, &target, |
| &recurse, &ignore_ancestry, &versus_url)); |
| text_deltas = TRUE; |
| depth_word = NULL; |
| } |
| else |
| { |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbcb?w", |
| &rev, &target, &recurse, |
| &ignore_ancestry, &versus_url, |
| &text_deltas, &depth_word)); |
| } |
| target = svn_path_canonicalize(target, pool); |
| versus_url = svn_path_canonicalize(versus_url, pool); |
| |
| if (depth_word) |
| depth = svn_depth_from_word(depth_word); |
| else |
| depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); |
| |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| |
| if (!SVN_IS_VALID_REVNUM(rev)) |
| SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); |
| SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool), |
| svn_path_uri_decode(versus_url, pool), |
| &versus_path)); |
| |
| return accept_report(conn, pool, b, rev, target, versus_path, |
| text_deltas, depth, FALSE, ignore_ancestry); |
| } |
| |
| /* Regardless of whether a client's capabilities indicate an |
| understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO), |
| we provide a response. |
| |
| ASSUMPTION: When performing a 'merge' with two URLs at different |
| revisions, the client will call this command more than once. */ |
| static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| apr_array_header_t *paths, *canonical_paths; |
| svn_mergeinfo_catalog_t mergeinfo; |
| int i; |
| apr_hash_index_t *hi; |
| const char *inherit_word; |
| svn_mergeinfo_inheritance_t inherit; |
| svn_boolean_t include_descendants; |
| apr_pool_t *iterpool; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)wb", &paths, &rev, |
| &inherit_word, &include_descendants)); |
| inherit = svn_inheritance_from_word(inherit_word); |
| |
| /* Canonicalize the paths which mergeinfo has been requested for. */ |
| canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); |
| for (i = 0; i < paths->nelts; i++) |
| { |
| svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t); |
| const char *full_path; |
| |
| if (item->kind != SVN_RA_SVN_STRING) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Path is not a string")); |
| full_path = svn_path_join(b->fs_path->data, |
| svn_path_canonicalize(item->u.string->data, |
| pool), |
| pool); |
| APR_ARRAY_PUSH(canonical_paths, const char *) = full_path; |
| } |
| |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos, |
| canonical_paths, rev, |
| inherit, |
| include_descendants, |
| authz_check_access_cb_func(b), b, |
| pool)); |
| SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo, |
| b->fs_path->data, pool)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success")); |
| iterpool = svn_pool_create(pool); |
| for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *value; |
| svn_string_t *mergeinfo_string; |
| |
| svn_pool_clear(iterpool); |
| |
| apr_hash_this(hi, &key, NULL, &value); |
| SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, |
| (svn_mergeinfo_t) value, |
| iterpool)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "cs", (const char *) key, |
| mergeinfo_string)); |
| } |
| svn_pool_destroy(iterpool); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Send a log entry to the client. */ |
| static svn_error_t *log_receiver(void *baton, |
| svn_log_entry_t *log_entry, |
| apr_pool_t *pool) |
| { |
| log_baton_t *b = baton; |
| svn_ra_svn_conn_t *conn = b->conn; |
| apr_hash_index_t *h; |
| const void *key; |
| void *val; |
| const char *path; |
| svn_log_changed_path_t *change; |
| svn_boolean_t invalid_revnum = FALSE; |
| char action[2]; |
| const char *author, *date, *message; |
| apr_uint64_t revprop_count; |
| |
| if (log_entry->revision == SVN_INVALID_REVNUM) |
| { |
| /* If the stack depth is zero, we've seen the last revision, so don't |
| send it, just return. */ |
| if (b->stack_depth == 0) |
| return SVN_NO_ERROR; |
| |
| /* Because the svn protocol won't let us send an invalid revnum, we have |
| to fudge here and send an additional flag. */ |
| log_entry->revision = 0; |
| invalid_revnum = TRUE; |
| b->stack_depth--; |
| } |
| |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "(!")); |
| if (log_entry->changed_paths) |
| { |
| for (h = apr_hash_first(pool, log_entry->changed_paths); h; |
| h = apr_hash_next(h)) |
| { |
| apr_hash_this(h, &key, NULL, &val); |
| path = key; |
| change = val; |
| action[0] = change->action; |
| action[1] = '\0'; |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cw(?cr)", path, action, |
| change->copyfrom_path, |
| change->copyfrom_rev)); |
| } |
| } |
| svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); |
| svn_compat_log_revprops_clear(log_entry->revprops); |
| if (log_entry->revprops) |
| revprop_count = apr_hash_count(log_entry->revprops); |
| else |
| revprop_count = 0; |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!", |
| log_entry->revision, |
| author, date, message, |
| log_entry->has_children, |
| invalid_revnum, revprop_count)); |
| SVN_ERR(svn_ra_svn_write_proplist(conn, pool, log_entry->revprops)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)")); |
| |
| if (log_entry->has_children) |
| b->stack_depth++; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| svn_error_t *err, *write_err; |
| server_baton_t *b = baton; |
| svn_revnum_t start_rev, end_rev; |
| const char *full_path; |
| svn_boolean_t changed_paths, strict_node, include_merged_revisions; |
| apr_array_header_t *paths, *full_paths, *revprop_items, *revprops; |
| char *revprop_word; |
| svn_ra_svn_item_t *elt; |
| int i; |
| apr_uint64_t limit, include_merged_revs_param; |
| log_baton_t lb; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths, |
| &start_rev, &end_rev, &changed_paths, |
| &strict_node, &limit, |
| &include_merged_revs_param, |
| &revprop_word, &revprop_items)); |
| |
| if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) |
| include_merged_revisions = FALSE; |
| else |
| include_merged_revisions = (svn_boolean_t) include_merged_revs_param; |
| |
| if (revprop_word == NULL) |
| /* pre-1.5 client */ |
| revprops = svn_compat_log_revprops_in(pool); |
| else if (strcmp(revprop_word, "all-revprops") == 0) |
| revprops = NULL; |
| else if (strcmp(revprop_word, "revprops") == 0) |
| { |
| revprops = apr_array_make(pool, revprop_items->nelts, |
| sizeof(char *)); |
| if (revprop_items) |
| { |
| for (i = 0; i < revprop_items->nelts; i++) |
| { |
| elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t); |
| if (elt->kind != SVN_RA_SVN_STRING) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Log revprop entry not a string")); |
| APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data; |
| } |
| } |
| } |
| else |
| return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Unknown revprop word '%s' in log command"), |
| revprop_word); |
| |
| /* If we got an unspecified number then the user didn't send us anything, |
| so we assume no limit. If it's larger than INT_MAX then someone is |
| messing with us, since we know the svn client libraries will never send |
| us anything that big, so play it safe and default to no limit. */ |
| if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX) |
| limit = 0; |
| |
| full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); |
| for (i = 0; i < paths->nelts; i++) |
| { |
| elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t); |
| if (elt->kind != SVN_RA_SVN_STRING) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Log path entry not a string")); |
| full_path = svn_path_join(b->fs_path->data, |
| svn_path_canonicalize(elt->u.string->data, |
| pool), |
| pool); |
| APR_ARRAY_PUSH(full_paths, const char *) = full_path; |
| } |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| |
| /* Get logs. (Can't report errors back to the client at this point.) */ |
| lb.fs_path = b->fs_path->data; |
| lb.conn = conn; |
| lb.stack_depth = 0; |
| err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev, |
| (int) limit, changed_paths, strict_node, |
| include_merged_revisions, revprops, |
| authz_check_access_cb_func(b), b, log_receiver, |
| &lb, pool); |
| |
| write_err = svn_ra_svn_write_word(conn, pool, "done"); |
| if (write_err) |
| { |
| svn_error_clear(err); |
| return write_err; |
| } |
| SVN_CMD_ERR(err); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| const char *path, *full_path; |
| svn_fs_root_t *root; |
| svn_node_kind_t kind; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev)); |
| full_path = svn_path_join(b->fs_path->data, |
| svn_path_canonicalize(path, pool), pool); |
| |
| /* Check authorizations */ |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, |
| full_path, FALSE)); |
| |
| if (!SVN_IS_VALID_REVNUM(rev)) |
| SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); |
| |
| SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); |
| SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool)); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "w", kind_word(kind))); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *stat(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_revnum_t rev; |
| const char *path, *full_path, *cdate; |
| svn_fs_root_t *root; |
| svn_dirent_t *dirent; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev)); |
| full_path = svn_path_join(b->fs_path->data, |
| svn_path_canonicalize(path, pool), pool); |
| |
| /* Check authorizations */ |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, |
| full_path, FALSE)); |
| |
| if (!SVN_IS_VALID_REVNUM(rev)) |
| SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); |
| SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); |
| SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool)); |
| |
| /* Need to return the equivalent of "(?l)", since that's what the |
| client is reading. */ |
| |
| if (dirent == NULL) |
| { |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "()")); |
| return SVN_NO_ERROR; |
| } |
| |
| cdate = (dirent->time == (time_t) -1) ? NULL |
| : svn_time_to_cstring(dirent->time, pool); |
| |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "((wnbr(?c)(?c)))", |
| kind_word(dirent->kind), |
| (apr_uint64_t) dirent->size, |
| dirent->has_props, dirent->created_rev, |
| cdate, dirent->last_author)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| svn_error_t *err, *write_err; |
| server_baton_t *b = baton; |
| svn_revnum_t revision; |
| apr_array_header_t *location_revisions, *loc_revs_proto; |
| svn_ra_svn_item_t *elt; |
| int i; |
| const char *relative_path; |
| svn_revnum_t peg_revision; |
| apr_hash_t *fs_locations; |
| apr_hash_index_t *iter; |
| const char *abs_path; |
| const void *iter_key; |
| void *iter_value; |
| |
| /* Parse the arguments. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crl", &relative_path, |
| &peg_revision, |
| &loc_revs_proto)); |
| relative_path = svn_path_canonicalize(relative_path, pool); |
| |
| abs_path = svn_path_join(b->fs_path->data, relative_path, pool); |
| |
| location_revisions = apr_array_make(pool, loc_revs_proto->nelts, |
| sizeof(svn_revnum_t)); |
| for (i = 0; i < loc_revs_proto->nelts; i++) |
| { |
| elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t); |
| if (elt->kind != SVN_RA_SVN_NUMBER) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| "Get-locations location revisions entry " |
| "not a revision number"); |
| revision = (svn_revnum_t)(elt->u.number); |
| APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision; |
| } |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| |
| /* All the parameters are fine - let's perform the query against the |
| * repository. */ |
| |
| /* We store both err and write_err here, so the client will get |
| * the "done" even if there was an error in fetching the results. */ |
| |
| err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path, |
| peg_revision, location_revisions, |
| authz_check_access_cb_func(b), b, pool); |
| |
| /* Now, write the results to the connection. */ |
| if (!err) |
| { |
| if (fs_locations) |
| { |
| for (iter = apr_hash_first(pool, fs_locations); iter; |
| iter = apr_hash_next(iter)) |
| { |
| apr_hash_this(iter, &iter_key, NULL, &iter_value); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "rc", |
| *(const svn_revnum_t *)iter_key, |
| (const char *)iter_value)); |
| } |
| } |
| } |
| |
| write_err = svn_ra_svn_write_word(conn, pool, "done"); |
| if (write_err) |
| { |
| svn_error_clear(err); |
| return write_err; |
| } |
| SVN_CMD_ERR(err); |
| |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *gls_receiver(svn_location_segment_t *segment, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn_conn_t *conn = baton; |
| return svn_ra_svn_write_tuple(conn, pool, "rr(?c)", |
| segment->range_start, |
| segment->range_end, |
| segment->path); |
| } |
| |
| static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn, |
| apr_pool_t *pool, |
| apr_array_header_t *params, |
| void *baton) |
| { |
| svn_error_t *err, *write_err; |
| server_baton_t *b = baton; |
| svn_revnum_t peg_revision, start_rev, end_rev; |
| const char *relative_path; |
| const char *abs_path; |
| |
| /* Parse the arguments. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)(?r)", |
| &relative_path, &peg_revision, |
| &start_rev, &end_rev)); |
| relative_path = svn_path_canonicalize(relative_path, pool); |
| |
| abs_path = svn_path_join(b->fs_path->data, relative_path, pool); |
| |
| if (SVN_IS_VALID_REVNUM(start_rev) |
| && SVN_IS_VALID_REVNUM(end_rev) |
| && (end_rev > start_rev)) |
| return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, |
| "Get-location-segments end revision must not be " |
| "younger than start revision"); |
| |
| if (SVN_IS_VALID_REVNUM(peg_revision) |
| && SVN_IS_VALID_REVNUM(start_rev) |
| && (start_rev > peg_revision)) |
| return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, |
| "Get-location-segments start revision must not " |
| "be younger than peg revision"); |
| |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| |
| /* All the parameters are fine - let's perform the query against the |
| * repository. */ |
| |
| /* We store both err and write_err here, so the client will get |
| * the "done" even if there was an error in fetching the results. */ |
| |
| err = svn_repos_node_location_segments(b->repos, abs_path, |
| peg_revision, start_rev, end_rev, |
| gls_receiver, (void *)conn, |
| authz_check_access_cb_func(b), b, |
| pool); |
| write_err = svn_ra_svn_write_word(conn, pool, "done"); |
| if (write_err) |
| { |
| svn_error_clear(err); |
| return write_err; |
| } |
| SVN_CMD_ERR(err); |
| |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the |
| client as a string. */ |
| static svn_error_t *svndiff_handler(void *baton, const char *data, |
| apr_size_t *len) |
| { |
| file_revs_baton_t *b = baton; |
| svn_string_t str; |
| |
| str.data = data; |
| str.len = *len; |
| return svn_ra_svn_write_string(b->conn, b->pool, &str); |
| } |
| |
| /* This implements svn_close_fn_t. Mark the end of the data by writing an |
| empty string to the client. */ |
| static svn_error_t *svndiff_close_handler(void *baton) |
| { |
| file_revs_baton_t *b = baton; |
| |
| SVN_ERR(svn_ra_svn_write_cstring(b->conn, b->pool, "")); |
| return SVN_NO_ERROR; |
| } |
| |
| /* This implements the svn_repos_file_rev_handler_t interface. */ |
| static svn_error_t *file_rev_handler(void *baton, const char *path, |
| svn_revnum_t rev, apr_hash_t *rev_props, |
| svn_boolean_t merged_revision, |
| svn_txdelta_window_handler_t *d_handler, |
| void **d_baton, |
| apr_array_header_t *prop_diffs, |
| apr_pool_t *pool) |
| { |
| file_revs_baton_t *frb = baton; |
| svn_stream_t *stream; |
| |
| SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "cr(!", |
| path, rev)); |
| SVN_ERR(svn_ra_svn_write_proplist(frb->conn, pool, rev_props)); |
| SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)(!")); |
| SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs)); |
| SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)b", merged_revision)); |
| |
| /* Store the pool for the delta stream. */ |
| frb->pool = pool; |
| |
| /* Prepare for the delta or just write an empty string. */ |
| if (d_handler) |
| { |
| stream = svn_stream_create(baton, pool); |
| svn_stream_set_write(stream, svndiff_handler); |
| svn_stream_set_close(stream, svndiff_close_handler); |
| |
| if (svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1)) |
| svn_txdelta_to_svndiff2(d_handler, d_baton, stream, 1, pool); |
| else |
| svn_txdelta_to_svndiff2(d_handler, d_baton, stream, 0, pool); |
| } |
| else |
| SVN_ERR(svn_ra_svn_write_cstring(frb->conn, pool, "")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_error_t *err, *write_err; |
| file_revs_baton_t frb; |
| svn_revnum_t start_rev, end_rev; |
| const char *path; |
| const char *full_path; |
| apr_uint64_t include_merged_revs_param; |
| svn_boolean_t include_merged_revisions; |
| |
| /* Parse arguments. */ |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)?B", |
| &path, &start_rev, &end_rev, |
| &include_merged_revs_param)); |
| path = svn_path_canonicalize(path, pool); |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| full_path = svn_path_join(b->fs_path->data, path, pool); |
| |
| if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) |
| include_merged_revisions = FALSE; |
| else |
| include_merged_revisions = (svn_boolean_t) include_merged_revs_param; |
| |
| frb.conn = conn; |
| frb.pool = NULL; |
| |
| err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev, |
| include_merged_revisions, |
| authz_check_access_cb_func(b), b, |
| file_rev_handler, &frb, pool); |
| write_err = svn_ra_svn_write_word(conn, pool, "done"); |
| if (write_err) |
| { |
| svn_error_clear(err); |
| return write_err; |
| } |
| SVN_CMD_ERR(err); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| const char *path; |
| const char *comment; |
| const char *full_path; |
| svn_boolean_t steal_lock; |
| svn_revnum_t current_rev; |
| svn_lock_t *l; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment, |
| &steal_lock, ¤t_rev)); |
| full_path = svn_path_join(b->fs_path->data, |
| svn_path_canonicalize(path, pool), pool); |
| |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, |
| full_path, TRUE)); |
| |
| SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0, |
| 0, /* No expiration time. */ |
| current_rev, steal_lock, pool)); |
| |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(!", "success")); |
| SVN_ERR(write_lock(conn, pool, l)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| apr_array_header_t *path_revs; |
| const char *comment; |
| svn_boolean_t steal_lock; |
| int i; |
| apr_pool_t *subpool; |
| const char *path; |
| const char *full_path; |
| svn_revnum_t current_rev; |
| svn_lock_t *l; |
| svn_error_t *err = SVN_NO_ERROR, *write_err; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock, |
| &path_revs)); |
| |
| subpool = svn_pool_create(pool); |
| |
| /* Because we can only send a single auth reply per request, we send |
| a reply before parsing the lock commands. This means an authz |
| access denial will abort the processing of the locks and return |
| an error. */ |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE)); |
| |
| /* Loop through the lock requests. */ |
| for (i = 0; i < path_revs->nelts; ++i) |
| { |
| svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i, |
| svn_ra_svn_item_t); |
| |
| svn_pool_clear(subpool); |
| |
| if (item->kind != SVN_RA_SVN_LIST) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| "Lock requests should be list of lists"); |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "c(?r)", &path, |
| ¤t_rev)); |
| |
| full_path = svn_path_join(b->fs_path->data, |
| svn_path_canonicalize(path, subpool), |
| subpool); |
| |
| if (! lookup_access(pool, b, svn_authz_write, full_path, TRUE)) |
| { |
| err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, |
| NULL, NULL); |
| break; |
| } |
| |
| err = svn_repos_fs_lock(&l, b->repos, full_path, |
| NULL, comment, FALSE, |
| 0, /* No expiration time. */ |
| current_rev, |
| steal_lock, subpool); |
| |
| if (err) |
| { |
| if (SVN_ERR_IS_LOCK_ERROR(err)) |
| { |
| write_err = svn_ra_svn_write_cmd_failure(conn, pool, err); |
| svn_error_clear(err); |
| err = NULL; |
| SVN_ERR(write_err); |
| } |
| else |
| break; |
| } |
| else |
| { |
| SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "w!", "success")); |
| SVN_ERR(write_lock(conn, subpool, l)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "!")); |
| } |
| } |
| |
| svn_pool_destroy(subpool); |
| |
| /* NOTE: err might contain a fatal locking error from the loop above. */ |
| write_err = svn_ra_svn_write_word(conn, pool, "done"); |
| if (!write_err) |
| SVN_CMD_ERR(err); |
| svn_error_clear(err); |
| SVN_ERR(write_err); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| const char *path, *token, *full_path; |
| svn_boolean_t break_lock; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b", &path, &token, |
| &break_lock)); |
| |
| full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, pool), |
| pool); |
| |
| /* Username required unless break_lock was specified. */ |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, |
| full_path, ! break_lock)); |
| |
| SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock, |
| pool)); |
| |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| svn_boolean_t break_lock; |
| apr_array_header_t *unlock_tokens; |
| int i; |
| apr_pool_t *subpool; |
| const char *path; |
| const char *full_path; |
| const char *token; |
| svn_error_t *err = SVN_NO_ERROR, *write_err; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "bl", &break_lock, |
| &unlock_tokens)); |
| |
| /* Username required unless break_lock was specified. */ |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock)); |
| |
| subpool = svn_pool_create(pool); |
| |
| /* Loop through the unlock requests. */ |
| for (i = 0; i < unlock_tokens->nelts; i++) |
| { |
| svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i, |
| svn_ra_svn_item_t); |
| |
| svn_pool_clear(subpool); |
| |
| if (item->kind != SVN_RA_SVN_LIST) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| "Unlock request should be a list of lists"); |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "c(?c)", &path, |
| &token)); |
| |
| full_path = svn_path_join(b->fs_path->data, |
| svn_path_canonicalize(path, subpool), |
| subpool); |
| |
| if (! lookup_access(subpool, b, svn_authz_write, full_path, |
| ! break_lock)) |
| return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, |
| svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, |
| NULL, NULL), NULL); |
| |
| err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock, |
| subpool); |
| if (err) |
| { |
| if (SVN_ERR_IS_UNLOCK_ERROR(err)) |
| { |
| write_err = svn_ra_svn_write_cmd_failure(conn, pool, err); |
| svn_error_clear(err); |
| err = NULL; |
| SVN_ERR(write_err); |
| } |
| else |
| break; |
| } |
| else |
| SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "w(c)", "success", |
| path)); |
| } |
| |
| svn_pool_destroy(subpool); |
| |
| /* NOTE: err might contain a fatal unlocking error from the loop above. */ |
| write_err = svn_ra_svn_write_word(conn, pool, "done"); |
| if (! write_err) |
| SVN_CMD_ERR(err); |
| svn_error_clear(err); |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| const char *path; |
| const char *full_path; |
| svn_lock_t *l; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path)); |
| |
| full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, |
| pool), |
| pool); |
| |
| SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, |
| full_path, FALSE)); |
| |
| SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool)); |
| |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success")); |
| if (l) |
| SVN_ERR(write_lock(conn, pool, l)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| server_baton_t *b = baton; |
| const char *path; |
| const char *full_path; |
| apr_hash_t *locks; |
| apr_hash_index_t *hi; |
| void *val; |
| svn_lock_t *l; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path)); |
| |
| full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, |
| pool), |
| pool); |
| |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| |
| SVN_CMD_ERR(svn_repos_fs_get_locks(&locks, b->repos, full_path, |
| authz_check_access_cb_func(b), b, pool)); |
| |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success")); |
| for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi)) |
| { |
| apr_hash_this(hi, NULL, NULL, &val); |
| l = val; |
| SVN_ERR(write_lock(conn, pool, l)); |
| } |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn, |
| server_baton_t *b, |
| svn_revnum_t rev, |
| svn_revnum_t low_water_mark, |
| svn_boolean_t send_deltas, |
| apr_pool_t *pool) |
| { |
| const svn_delta_editor_t *editor; |
| void *edit_baton; |
| svn_fs_root_t *root; |
| svn_error_t *err; |
| |
| svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); |
| |
| err = svn_fs_revision_root(&root, b->fs, rev, pool); |
| |
| if (! err) |
| err = svn_repos_replay2(root, b->fs_path->data, low_water_mark, |
| send_deltas, editor, edit_baton, |
| authz_check_access_cb_func(b), b, pool); |
| |
| if (err) |
| svn_error_clear(editor->abort_edit(edit_baton, pool)); |
| SVN_CMD_ERR(err); |
| |
| return svn_ra_svn_write_cmd(conn, pool, "finish-replay", ""); |
| } |
| |
| static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| svn_revnum_t rev, low_water_mark; |
| svn_boolean_t send_deltas; |
| server_baton_t *b = baton; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrb", &rev, &low_water_mark, |
| &send_deltas)); |
| |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| |
| SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark, |
| send_deltas, pool)); |
| |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| apr_array_header_t *params, void *baton) |
| { |
| svn_revnum_t start_rev, end_rev, rev, low_water_mark; |
| svn_boolean_t send_deltas; |
| server_baton_t *b = baton; |
| apr_pool_t *iterpool; |
| |
| SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrrb", &start_rev, |
| &end_rev, &low_water_mark, |
| &send_deltas)); |
| |
| SVN_ERR(trivial_auth_request(conn, pool, b)); |
| |
| iterpool = svn_pool_create(pool); |
| for (rev = start_rev; rev <= end_rev; rev++) |
| { |
| apr_hash_t *props; |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev, |
| authz_check_access_cb_func(b), |
| b, |
| iterpool)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "w(!", "revprops")); |
| SVN_ERR(svn_ra_svn_write_proplist(conn, iterpool, props)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!)")); |
| |
| SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark, |
| send_deltas, iterpool)); |
| |
| } |
| svn_pool_destroy(iterpool); |
| |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static const svn_ra_svn_cmd_entry_t main_commands[] = { |
| { "reparent", reparent }, |
| { "get-latest-rev", get_latest_rev }, |
| { "get-dated-rev", get_dated_rev }, |
| { "change-rev-prop", change_rev_prop }, |
| { "rev-proplist", rev_proplist }, |
| { "rev-prop", rev_prop }, |
| { "commit", commit }, |
| { "get-file", get_file }, |
| { "get-dir", get_dir }, |
| { "update", update }, |
| { "switch", switch_cmd }, |
| { "status", status }, |
| { "diff", diff }, |
| { "get-mergeinfo", get_mergeinfo }, |
| { "log", log_cmd }, |
| { "check-path", check_path }, |
| { "stat", stat }, |
| { "get-locations", get_locations }, |
| { "get-location-segments", get_location_segments }, |
| { "get-file-revs", get_file_revs }, |
| { "lock", lock }, |
| { "lock-many", lock_many }, |
| { "unlock", unlock }, |
| { "unlock-many", unlock_many }, |
| { "get-lock", get_lock }, |
| { "get-locks", get_locks }, |
| { "replay", replay }, |
| { "replay-range", replay_range }, |
| { NULL } |
| }; |
| |
| /* Skip past the scheme part of a URL, including the tunnel specification |
| * if present. Return NULL if the scheme part is invalid for ra_svn. */ |
| static const char *skip_scheme_part(const char *url) |
| { |
| if (strncmp(url, "svn", 3) != 0) |
| return NULL; |
| url += 3; |
| if (*url == '+') |
| url += strcspn(url, ":"); |
| if (strncmp(url, "://", 3) != 0) |
| return NULL; |
| return url + 3; |
| } |
| |
| /* Check that PATH is a valid repository path, meaning it doesn't contain any |
| '..' path segments. |
| NOTE: This is similar to svn_path_is_backpath_present, but that function |
| assumes the path separator is '/'. This function also checks for |
| segments delimited by the local path separator. */ |
| static svn_boolean_t |
| repos_path_valid(const char *path) |
| { |
| const char *s = path; |
| |
| while (*s) |
| { |
| /* Scan for the end of the segment. */ |
| while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR) |
| ++path; |
| |
| /* Check for '..'. */ |
| #ifdef WIN32 |
| /* On Windows, don't allow sequences of more than one character |
| consisting of just dots and spaces. Win32 functions treat |
| paths such as ".. " and "......." inconsistently. Make sure |
| no one can escape out of the root. */ |
| if (path - s >= 2 && strspn(s, ". ") == path - s) |
| return FALSE; |
| #else /* ! WIN32 */ |
| if (path - s == 2 && s[0] == '.' && s[1] == '.') |
| return FALSE; |
| #endif |
| |
| /* Skip all separators. */ |
| while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR)) |
| ++path; |
| s = path; |
| } |
| |
| return TRUE; |
| } |
| |
| /* Look for the repository given by URL, using ROOT as the virtual |
| * repository root. If we find one, fill in the repos, fs, cfg, |
| * repos_url, and fs_path fields of B. Set B->repos's client |
| * capabilities to CAPABILITIES, which must be at least as long-lived |
| * as POOL, and whose elements are SVN_RA_CAPABILITY_*. |
| */ |
| static svn_error_t *find_repos(const char *url, const char *root, |
| server_baton_t *b, |
| apr_array_header_t *capabilities, |
| apr_pool_t *pool) |
| { |
| const char *path, *full_path, *repos_root, *fs_path; |
| svn_stringbuf_t *url_buf; |
| |
| /* Skip past the scheme and authority part. */ |
| path = skip_scheme_part(url); |
| if (path == NULL) |
| return svn_error_createf(SVN_ERR_BAD_URL, NULL, |
| "Non-svn URL passed to svn server: '%s'", url); |
| path = strchr(path, '/'); |
| path = (path == NULL) ? "" : path + 1; |
| |
| /* Decode URI escapes from the path. */ |
| path = svn_path_uri_decode(path, pool); |
| |
| /* Ensure that it isn't possible to escape the root by skipping leading |
| slashes and not allowing '..' segments. */ |
| while (*path == '/') |
| ++path; |
| if (!repos_path_valid(path)) |
| return svn_error_create(SVN_ERR_BAD_FILENAME, NULL, |
| "Couldn't determine repository path"); |
| |
| /* Join the server-configured root with the client path. */ |
| full_path = svn_path_join(svn_path_canonicalize(root, pool), |
| svn_path_canonicalize(path, pool), pool); |
| |
| /* Search for a repository in the full path. */ |
| repos_root = svn_repos_find_root_path(full_path, pool); |
| if (!repos_root) |
| return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL, |
| "No repository found in '%s'", url); |
| |
| /* Open the repository and fill in b with the resulting information. */ |
| SVN_ERR(svn_repos_open(&b->repos, repos_root, pool)); |
| SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities)); |
| b->fs = svn_repos_fs(b->repos); |
| fs_path = full_path + strlen(repos_root); |
| b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool); |
| url_buf = svn_stringbuf_create(url, pool); |
| svn_path_remove_components(url_buf, |
| svn_path_component_count(b->fs_path->data)); |
| b->repos_url = url_buf->data; |
| b->authz_repos_name = svn_path_is_child(root, repos_root, pool); |
| |
| /* If the svnserve configuration files have not been loaded then |
| load them from the repository. */ |
| if (NULL == b->cfg) |
| SVN_ERR(load_configs(&b->cfg, &b->pwdb, &b->authzdb, |
| svn_repos_svnserve_conf(b->repos, pool), FALSE, |
| svn_repos_conf_dir(b->repos, pool), |
| pool)); |
| |
| #ifdef SVN_HAVE_SASL |
| /* Should we use Cyrus SASL? */ |
| svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL, |
| SVN_CONFIG_OPTION_USE_SASL, FALSE); |
| #endif |
| |
| /* Use the repository UUID as the default realm. */ |
| SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool)); |
| svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL, |
| SVN_CONFIG_OPTION_REALM, b->realm); |
| |
| /* Make sure it's possible for the client to authenticate. Note |
| that this doesn't take into account any authz configuration read |
| above, because we can't know about access it grants until paths |
| are given by the client. */ |
| if (get_access(b, UNAUTHENTICATED) == NO_ACCESS |
| && (get_access(b, AUTHENTICATED) == NO_ACCESS |
| || (!b->tunnel_user && !b->pwdb |
| #ifdef SVN_HAVE_SASL |
| && !b->use_sasl |
| #endif |
| ))) |
| return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, |
| "No access allowed to this repository"); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Compute the authentication name EXTERNAL should be able to get, if any. */ |
| static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool) |
| { |
| /* Only offer EXTERNAL for connections tunneled over a login agent. */ |
| if (!params->tunnel) |
| return NULL; |
| |
| /* If a tunnel user was provided on the command line, use that. */ |
| if (params->tunnel_user) |
| return params->tunnel_user; |
| |
| return svn_user_get_name(pool); |
| } |
| |
| svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params, |
| apr_pool_t *pool) |
| { |
| svn_error_t *err, *io_err; |
| apr_uint64_t ver; |
| const char *uuid, *client_url; |
| apr_array_header_t *caplist, *cap_words; |
| server_baton_t b; |
| |
| b.tunnel = params->tunnel; |
| b.tunnel_user = get_tunnel_user(params, pool); |
| b.read_only = params->read_only; |
| b.user = NULL; |
| b.cfg = params->cfg; /* Ugly; can drop when we remove v1 support. */ |
| b.pwdb = params->pwdb; /* Likewise. */ |
| b.authzdb = params->authzdb; |
| b.realm = NULL; |
| b.pool = pool; |
| |
| /* Send greeting. We don't support version 1 any more, so we can |
| * send an empty mechlist. */ |
| SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwww)", |
| (apr_uint64_t) 2, (apr_uint64_t) 2, |
| SVN_RA_SVN_CAP_EDIT_PIPELINE, |
| SVN_RA_SVN_CAP_SVNDIFF1, |
| SVN_RA_SVN_CAP_ABSENT_ENTRIES, |
| SVN_RA_SVN_CAP_COMMIT_REVPROPS, |
| SVN_RA_SVN_CAP_DEPTH, |
| SVN_RA_SVN_CAP_LOG_REVPROPS, |
| SVN_RA_SVN_CAP_PARTIAL_REPLAY)); |
| |
| /* Read client response, which we assume to be in version 2 format: |
| * version, capability list, and client URL; then we do an auth |
| * request. */ |
| SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "nlc", |
| &ver, &caplist, &client_url)); |
| if (ver != 2) |
| return SVN_NO_ERROR; |
| |
| client_url = svn_path_canonicalize(client_url, pool); |
| SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist)); |
| |
| /* All released versions of Subversion support edit-pipeline, |
| * so we do not accept connections from clients that do not. */ |
| if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) |
| return SVN_NO_ERROR; |
| |
| /* find_repos needs the capabilities as a list of words (eventually |
| they get handed to the start-commit hook). While we could add a |
| new interface to re-retrieve them from conn and convert the |
| result to a list, it's simpler to just convert caplist by hand |
| here, since we already have it and turning 'svn_ra_svn_item_t's |
| into 'const char *'s is pretty easy. |
| |
| We only record capabilities we care about. The client may report |
| more (because it doesn't know what the server cares about). */ |
| { |
| int i; |
| svn_ra_svn_item_t *item; |
| |
| cap_words = apr_array_make(pool, 1, sizeof(const char *)); |
| for (i = 0; i < caplist->nelts; i++) |
| { |
| item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t); |
| /* ra_svn_set_capabilities() already type-checked for us */ |
| if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0) |
| { |
| APR_ARRAY_PUSH(cap_words, const char *) |
| = SVN_RA_CAPABILITY_MERGEINFO; |
| } |
| } |
| } |
| |
| err = find_repos(client_url, params->root, &b, cap_words, pool); |
| if (!err) |
| { |
| SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE)); |
| if (current_access(&b) == NO_ACCESS) |
| err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, |
| "Not authorized for access"); |
| } |
| if (err) |
| { |
| io_err = svn_ra_svn_write_cmd_failure(conn, pool, err); |
| svn_error_clear(err); |
| SVN_ERR(io_err); |
| return svn_ra_svn_flush(conn, pool); |
| } |
| |
| SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool)); |
| |
| /* We can't claim mergeinfo capability until we know whether the |
| repository supports mergeinfo (i.e., is not a 1.4 repository), |
| but we don't get the repository url from the client until after |
| we've already sent the initial list of server capabilities. So |
| we list repository capabilities here, in our first response after |
| the client has sent the url. */ |
| { |
| svn_boolean_t supports_mergeinfo; |
| SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo, |
| SVN_REPOS_CAPABILITY_MERGEINFO, pool)); |
| |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(cc(!", |
| "success", uuid, b.repos_url)); |
| if (supports_mergeinfo) |
| SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); |
| } |
| |
| return svn_ra_svn_handle_commands(conn, pool, main_commands, &b); |
| } |