diff options
author | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 14:29:52 +0100 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 14:29:52 +0100 |
commit | f1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch) | |
tree | 4223b2035bf2240d681a53822808b3c7f687b905 /subversion/libsvn_ra_svn | |
download | subversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz |
Tarball conversion
Diffstat (limited to 'subversion/libsvn_ra_svn')
-rw-r--r-- | subversion/libsvn_ra_svn/client.c | 2599 | ||||
-rw-r--r-- | subversion/libsvn_ra_svn/cram.c | 221 | ||||
-rw-r--r-- | subversion/libsvn_ra_svn/cyrus_auth.c | 941 | ||||
-rw-r--r-- | subversion/libsvn_ra_svn/editorp.c | 1005 | ||||
-rw-r--r-- | subversion/libsvn_ra_svn/internal_auth.c | 121 | ||||
-rw-r--r-- | subversion/libsvn_ra_svn/marshal.c | 1112 | ||||
-rw-r--r-- | subversion/libsvn_ra_svn/protocol | 612 | ||||
-rw-r--r-- | subversion/libsvn_ra_svn/ra_svn.h | 212 | ||||
-rw-r--r-- | subversion/libsvn_ra_svn/streams.c | 255 | ||||
-rw-r--r-- | subversion/libsvn_ra_svn/version.c | 33 |
10 files changed, 7111 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_svn/client.c b/subversion/libsvn_ra_svn/client.c new file mode 100644 index 0000000..c5c7dd2 --- /dev/null +++ b/subversion/libsvn_ra_svn/client.c @@ -0,0 +1,2599 @@ +/* + * client.c : Functions for repository access via the Subversion protocol + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include "svn_private_config.h" + +#define APR_WANT_STRFUNC +#include <apr_want.h> +#include <apr_general.h> +#include <apr_strings.h> +#include <apr_network_io.h> +#include <apr_uri.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_time.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_config.h" +#include "svn_ra.h" +#include "svn_ra_svn.h" +#include "svn_props.h" +#include "svn_mergeinfo.h" +#include "svn_version.h" + +#include "svn_private_config.h" + +#include "private/svn_fspath.h" + +#include "../libsvn_ra/ra_loader.h" + +#include "ra_svn.h" + +#ifdef SVN_HAVE_SASL +#define DO_AUTH svn_ra_svn__do_cyrus_auth +#else +#define DO_AUTH svn_ra_svn__do_internal_auth +#endif + +/* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for + whatever reason) deems svn_depth_immediates as non-recursive, which + is ... kinda true, but not true enough for our purposes. We need + our requested recursion level to be *at least* as recursive as the + real depth we're looking for. + */ +#define DEPTH_TO_RECURSE(d) \ + ((d) == svn_depth_unknown || (d) > svn_depth_files) + +typedef struct ra_svn_commit_callback_baton_t { + svn_ra_svn__session_baton_t *sess_baton; + apr_pool_t *pool; + svn_revnum_t *new_rev; + svn_commit_callback2_t callback; + void *callback_baton; +} ra_svn_commit_callback_baton_t; + +typedef struct ra_svn_reporter_baton_t { + svn_ra_svn__session_baton_t *sess_baton; + svn_ra_svn_conn_t *conn; + apr_pool_t *pool; + const svn_delta_editor_t *editor; + void *edit_baton; +} ra_svn_reporter_baton_t; + +/* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel + portion. */ +static void parse_tunnel(const char *url, const char **tunnel, + apr_pool_t *pool) +{ + *tunnel = NULL; + + if (strncasecmp(url, "svn", 3) != 0) + return; + url += 3; + + /* Get the tunnel specification, if any. */ + if (*url == '+') + { + const char *p; + + url++; + p = strchr(url, ':'); + if (!p) + return; + *tunnel = apr_pstrmemdup(pool, url, p - url); + } +} + +static svn_error_t *make_connection(const char *hostname, unsigned short port, + apr_socket_t **sock, apr_pool_t *pool) +{ + apr_sockaddr_t *sa; + apr_status_t status; + int family = APR_INET; + + /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get + APR_UNSPEC, because it may give us back an IPV6 address even if we can't + create IPV6 sockets. */ + +#if APR_HAVE_IPV6 +#ifdef MAX_SECS_TO_LINGER + status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool); +#else + status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, + APR_PROTO_TCP, pool); +#endif + if (status == 0) + { + apr_socket_close(*sock); + family = APR_UNSPEC; + } +#endif + + /* Resolve the hostname. */ + status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool); + if (status) + return svn_error_createf(status, NULL, _("Unknown hostname '%s'"), + hostname); + /* Iterate through the returned list of addresses attempting to + * connect to each in turn. */ + do + { + /* Create the socket. */ +#ifdef MAX_SECS_TO_LINGER + /* ### old APR interface */ + status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool); +#else + status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP, + pool); +#endif + if (status == APR_SUCCESS) + { + status = apr_socket_connect(*sock, sa); + if (status != APR_SUCCESS) + apr_socket_close(*sock); + } + sa = sa->next; + } + while (status != APR_SUCCESS && sa); + + if (status) + return svn_error_wrap_apr(status, _("Can't connect to host '%s'"), + hostname); + + /* Enable TCP keep-alives on the socket so we time out when + * the connection breaks due to network-layer problems. + * If the peer has dropped the connection due to a network partition + * or a crash, or if the peer no longer considers the connection + * valid because we are behind a NAT and our public IP has changed, + * it will respond to the keep-alive probe with a RST instead of an + * acknowledgment segment, which will cause svn to abort the session + * even while it is currently blocked waiting for data from the peer. + * See issue #3347. */ + status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1); + if (status) + { + /* It's not a fatal error if we cannot enable keep-alives. */ + } + + return SVN_NO_ERROR; +} + +/* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the + property diffs in LIST, received from the server. */ +static svn_error_t *parse_prop_diffs(const apr_array_header_t *list, + apr_pool_t *pool, + apr_array_header_t **diffs) +{ + int i; + + *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t)); + + for (i = 0; i < list->nelts; i++) + { + svn_prop_t *prop; + svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); + + if (elt->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Prop diffs element not a list")); + prop = apr_array_push(*diffs); + SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "c(?s)", &prop->name, + &prop->value)); + } + return SVN_NO_ERROR; +} + +/* Parse a lockdesc, provided in LIST as specified by the protocol into + LOCK, allocated in POOL. */ +static svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool, + svn_lock_t **lock) +{ + const char *cdate, *edate; + *lock = svn_lock_create(pool); + SVN_ERR(svn_ra_svn_parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path, + &(*lock)->token, &(*lock)->owner, + &(*lock)->comment, &cdate, &edate)); + (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool); + SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool)); + if (edate) + SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool)); + return SVN_NO_ERROR; +} + +/* --- AUTHENTICATION ROUTINES --- */ + +svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const char *mech, const char *mech_arg) +{ + return svn_ra_svn_write_tuple(conn, pool, "w(?c)", mech, mech_arg); +} + +static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess, + apr_pool_t *pool) +{ + svn_ra_svn_conn_t *conn = sess->conn; + apr_array_header_t *mechlist; + const char *realm; + + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "lc", &mechlist, &realm)); + if (mechlist->nelts == 0) + return SVN_NO_ERROR; + return DO_AUTH(sess, mechlist, realm, pool); +} + +/* --- REPORTER IMPLEMENTATION --- */ + +static svn_error_t *ra_svn_set_path(void *baton, const char *path, + svn_revnum_t rev, + svn_depth_t depth, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + ra_svn_reporter_baton_t *b = baton; + + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "set-path", "crb(?c)w", + path, rev, start_empty, lock_token, + svn_depth_to_word(depth))); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_delete_path(void *baton, const char *path, + apr_pool_t *pool) +{ + ra_svn_reporter_baton_t *b = baton; + + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "delete-path", "c", path)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_link_path(void *baton, const char *path, + const char *url, + svn_revnum_t rev, + svn_depth_t depth, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + ra_svn_reporter_baton_t *b = baton; + + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "link-path", "ccrb(?c)w", + path, url, rev, start_empty, lock_token, + svn_depth_to_word(depth))); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_finish_report(void *baton, + apr_pool_t *pool) +{ + ra_svn_reporter_baton_t *b = baton; + + SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "finish-report", "")); + SVN_ERR(handle_auth_request(b->sess_baton, b->pool)); + SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton, + NULL, FALSE)); + SVN_ERR(svn_ra_svn_read_cmd_response(b->conn, b->pool, "")); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_abort_report(void *baton, + apr_pool_t *pool) +{ + ra_svn_reporter_baton_t *b = baton; + + SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "abort-report", "")); + return SVN_NO_ERROR; +} + +static svn_ra_reporter3_t ra_svn_reporter = { + ra_svn_set_path, + ra_svn_delete_path, + ra_svn_link_path, + ra_svn_finish_report, + ra_svn_abort_report +}; + +static svn_error_t * +ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton, + apr_pool_t *pool, + const svn_delta_editor_t *editor, + void *edit_baton, + const char *target, + svn_depth_t depth, + const svn_ra_reporter3_t **reporter, + void **report_baton) +{ + ra_svn_reporter_baton_t *b; + const svn_delta_editor_t *filter_editor; + void *filter_baton; + + /* We can skip the depth filtering when the user requested + depth_files or depth_infinity because the server will + transmit the right stuff anyway. */ + if ((depth != svn_depth_files) && (depth != svn_depth_infinity) + && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH)) + { + SVN_ERR(svn_delta_depth_filter_editor(&filter_editor, + &filter_baton, + editor, edit_baton, depth, + *target != '\0', + pool)); + editor = filter_editor; + edit_baton = filter_baton; + } + + b = apr_palloc(pool, sizeof(*b)); + b->sess_baton = sess_baton; + b->conn = sess_baton->conn; + b->pool = pool; + b->editor = editor; + b->edit_baton = edit_baton; + + *reporter = &ra_svn_reporter; + *report_baton = b; + + return SVN_NO_ERROR; +} + +/* --- RA LAYER IMPLEMENTATION --- */ + +/* (Note: *ARGV is an output parameter.) */ +static svn_error_t *find_tunnel_agent(const char *tunnel, + const char *hostinfo, + const char ***argv, + apr_hash_t *config, apr_pool_t *pool) +{ + svn_config_t *cfg; + const char *val, *var, *cmd; + char **cmd_argv; + apr_size_t len; + apr_status_t status; + int n; + + /* Look up the tunnel specification in config. */ + cfg = config ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG, + APR_HASH_KEY_STRING) : NULL; + svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL); + + /* We have one predefined tunnel scheme, if it isn't overridden by config. */ + if (!val && strcmp(tunnel, "ssh") == 0) + { + /* Killing the tunnel agent with SIGTERM leads to unsightly + * stderr output from ssh, unless we pass -q. + * The "-q" option to ssh is widely supported: all versions of + * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com + * versions have it too. If the user is using some other ssh + * implementation that doesn't accept it, they can override it + * in the [tunnels] section of the config. */ + val = "$SVN_SSH ssh -q"; + } + + if (!val || !*val) + return svn_error_createf(SVN_ERR_BAD_URL, NULL, + _("Undefined tunnel scheme '%s'"), tunnel); + + /* If the scheme definition begins with "$varname", it means there + * is an environment variable which can override the command. */ + if (*val == '$') + { + val++; + len = strcspn(val, " "); + var = apr_pstrmemdup(pool, val, len); + cmd = getenv(var); + if (!cmd) + { + cmd = val + len; + while (*cmd == ' ') + cmd++; + if (!*cmd) + return svn_error_createf(SVN_ERR_BAD_URL, NULL, + _("Tunnel scheme %s requires environment " + "variable %s to be defined"), tunnel, + var); + } + } + else + cmd = val; + + /* Tokenize the command into a list of arguments. */ + status = apr_tokenize_to_argv(cmd, &cmd_argv, pool); + if (status != APR_SUCCESS) + return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd); + + /* Append the fixed arguments to the result. */ + for (n = 0; cmd_argv[n] != NULL; n++) + ; + *argv = apr_palloc(pool, (n + 4) * sizeof(char *)); + memcpy((void *) *argv, cmd_argv, n * sizeof(char *)); + (*argv)[n++] = svn_path_uri_decode(hostinfo, pool); + (*argv)[n++] = "svnserve"; + (*argv)[n++] = "-t"; + (*argv)[n] = NULL; + + return SVN_NO_ERROR; +} + +/* This function handles any errors which occur in the child process + * created for a tunnel agent. We write the error out as a command + * failure; the code in ra_svn_open() to read the server's greeting + * will see the error and return it to the caller. */ +static void handle_child_process_error(apr_pool_t *pool, apr_status_t status, + const char *desc) +{ + svn_ra_svn_conn_t *conn; + apr_file_t *in_file, *out_file; + svn_error_t *err; + + if (apr_file_open_stdin(&in_file, pool) + || apr_file_open_stdout(&out_file, pool)) + return; + + conn = svn_ra_svn_create_conn2(NULL, in_file, out_file, + SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); + err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc); + svn_error_clear(svn_ra_svn_write_cmd_failure(conn, pool, err)); + svn_error_clear(err); + svn_error_clear(svn_ra_svn_flush(conn, pool)); +} + +/* (Note: *CONN is an output parameter.) */ +static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn, + apr_pool_t *pool) +{ + apr_status_t status; + apr_proc_t *proc; + apr_procattr_t *attr; + svn_error_t *err; + + status = apr_procattr_create(&attr, pool); + if (status == APR_SUCCESS) + status = apr_procattr_io_set(attr, 1, 1, 0); + if (status == APR_SUCCESS) + status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); + if (status == APR_SUCCESS) + status = apr_procattr_child_errfn_set(attr, handle_child_process_error); + proc = apr_palloc(pool, sizeof(*proc)); + if (status == APR_SUCCESS) + status = apr_proc_create(proc, *args, args, NULL, attr, pool); + if (status != APR_SUCCESS) + return svn_error_wrap_apr(status, _("Can't create tunnel")); + + /* Arrange for the tunnel agent to get a SIGTERM on pool + * cleanup. This is a little extreme, but the alternatives + * weren't working out. + * + * Closing the pipes and waiting for the process to die + * was prone to mysterious hangs which are difficult to + * diagnose (e.g. svnserve dumps core due to unrelated bug; + * sshd goes into zombie state; ssh connection is never + * closed; ssh never terminates). + * See also the long dicussion in issue #2580 if you really + * want to know various reasons for these problems and + * the different opinions on this issue. + * + * On Win32, APR does not support KILL_ONLY_ONCE. It only has + * KILL_ALWAYS and KILL_NEVER. Other modes are converted to + * KILL_ALWAYS, which immediately calls TerminateProcess(). + * This instantly kills the tunnel, leaving sshd and svnserve + * on a remote machine running indefinitely. These processes + * accumulate. The problem is most often seen with a fast client + * machine and a modest internet connection, as the tunnel + * is killed before being able to gracefully complete the + * session. In that case, svn is unusable 100% of the time on + * the windows machine. Thus, on Win32, we use KILL_NEVER and + * take the lesser of two evils. + */ +#ifdef WIN32 + apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER); +#else + apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE); +#endif + + /* APR pipe objects inherit by default. But we don't want the + * tunnel agent's pipes held open by future child processes + * (such as other ra_svn sessions), so turn that off. */ + apr_file_inherit_unset(proc->in); + apr_file_inherit_unset(proc->out); + + /* Guard against dotfile output to stdout on the server. */ + *conn = svn_ra_svn_create_conn2(NULL, proc->out, proc->in, + SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); + err = svn_ra_svn_skip_leading_garbage(*conn, pool); + if (err) + return svn_error_quick_wrap( + err, + _("To better debug SSH connection problems, remove the -q " + "option from 'ssh' in the [tunnels] section of your " + "Subversion configuration file.")); + + return SVN_NO_ERROR; +} + +/* Parse URL inot URI, validating it and setting the default port if none + was given. Allocate the URI fileds out of POOL. */ +static svn_error_t *parse_url(const char *url, apr_uri_t *uri, + apr_pool_t *pool) +{ + apr_status_t apr_err; + + apr_err = apr_uri_parse(pool, url, uri); + + if (apr_err != 0) + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("Illegal svn repository URL '%s'"), url); + + if (! uri->port) + uri->port = SVN_RA_SVN_PORT; + + return SVN_NO_ERROR; +} + +/* Open a session to URL, returning it in *SESS_P, allocating it in POOL. + URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON + are provided by the caller of ra_svn_open. If tunnel_argv is non-null, + it points to a program argument list to use when invoking the tunnel agent. +*/ +static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, + const char *url, + const apr_uri_t *uri, + const char **tunnel_argv, + const svn_ra_callbacks2_t *callbacks, + void *callbacks_baton, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess; + svn_ra_svn_conn_t *conn; + apr_socket_t *sock; + apr_uint64_t minver, maxver; + apr_array_header_t *mechlist, *server_caplist, *repos_caplist; + const char *client_string = NULL; + + sess = apr_palloc(pool, sizeof(*sess)); + sess->pool = pool; + sess->is_tunneled = (tunnel_argv != NULL); + sess->url = apr_pstrdup(pool, url); + sess->user = uri->user; + sess->hostname = uri->hostname; + sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname, + uri->port); + sess->tunnel_argv = tunnel_argv; + sess->callbacks = callbacks; + sess->callbacks_baton = callbacks_baton; + sess->bytes_read = sess->bytes_written = 0; + + if (tunnel_argv) + SVN_ERR(make_tunnel(tunnel_argv, &conn, pool)); + else + { + SVN_ERR(make_connection(uri->hostname, uri->port, &sock, pool)); + conn = svn_ra_svn_create_conn2(sock, NULL, NULL, + SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, + pool); + } + + /* Make sure we set conn->session before reading from it, + * because the reader and writer functions expect a non-NULL value. */ + sess->conn = conn; + conn->session = sess; + + /* Read server's greeting. */ + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "nnll", &minver, &maxver, + &mechlist, &server_caplist)); + + /* We support protocol version 2. */ + if (minver > 2) + return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, + _("Server requires minimum version %d"), + (int) minver); + if (maxver < 2) + return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, + _("Server only supports versions up to %d"), + (int) maxver); + SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist)); + + /* All released versions of Subversion support edit-pipeline, + * so we do not support servers that do not. */ + if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) + return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, + _("Server does not support edit pipelining")); + + if (sess->callbacks->get_client_string != NULL) + SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton, + &client_string, pool)); + + /* In protocol version 2, we send back our protocol version, our + * capability list, and the URL, and subsequently there is an auth + * request. */ + /* Client-side capabilities list: */ + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "n(wwwwww)cc(?c)", + (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_DEPTH, + SVN_RA_SVN_CAP_MERGEINFO, + SVN_RA_SVN_CAP_LOG_REVPROPS, + url, "SVN/" SVN_VER_NUMBER, client_string)); + SVN_ERR(handle_auth_request(sess, pool)); + + /* This is where the security layer would go into effect if we + * supported security layers, which is a ways off. */ + + /* Read the repository's uuid and root URL, and perhaps learn more + capabilities that weren't available before now. */ + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "c?c?l", &conn->uuid, + &conn->repos_root, &repos_caplist)); + if (repos_caplist) + SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist)); + + if (conn->repos_root) + { + conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool); + /* We should check that the returned string is a prefix of url, since + that's the API guarantee, but this isn't true for 1.0 servers. + Checking the length prevents client crashes. */ + if (strlen(conn->repos_root) > strlen(url)) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Impossibly long repository root from " + "server")); + } + + *sess_p = sess; + + return SVN_NO_ERROR; +} + + +#ifdef SVN_HAVE_SASL +#define RA_SVN_DESCRIPTION \ + N_("Module for accessing a repository using the svn network protocol.\n" \ + " - with Cyrus SASL authentication") +#else +#define RA_SVN_DESCRIPTION \ + N_("Module for accessing a repository using the svn network protocol.") +#endif + +static const char *ra_svn_get_description(void) +{ + return _(RA_SVN_DESCRIPTION); +} + +static const char * const * +ra_svn_get_schemes(apr_pool_t *pool) +{ + static const char *schemes[] = { "svn", NULL }; + + return schemes; +} + + + +static svn_error_t *ra_svn_open(svn_ra_session_t *session, + const char **corrected_url, + const char *url, + const svn_ra_callbacks2_t *callbacks, + void *callback_baton, + apr_hash_t *config, + apr_pool_t *pool) +{ + apr_pool_t *sess_pool = svn_pool_create(pool); + svn_ra_svn__session_baton_t *sess; + const char *tunnel, **tunnel_argv; + apr_uri_t uri; + svn_config_t *cfg, *cfg_client; + + /* We don't support server-prescribed redirections in ra-svn. */ + if (corrected_url) + *corrected_url = NULL; + + SVN_ERR(parse_url(url, &uri, sess_pool)); + + parse_tunnel(url, &tunnel, pool); + + if (tunnel) + SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config, + pool)); + else + tunnel_argv = NULL; + + cfg_client = config ? apr_hash_get(config, + SVN_CONFIG_CATEGORY_CONFIG, + APR_HASH_KEY_STRING) : NULL; + cfg = config ? apr_hash_get(config, + SVN_CONFIG_CATEGORY_SERVERS, + APR_HASH_KEY_STRING) : NULL; + svn_auth_set_parameter(callbacks->auth_baton, + SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client); + svn_auth_set_parameter(callbacks->auth_baton, + SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg); + + /* We open the session in a subpool so we can get rid of it if we + reparent with a server that doesn't support reparenting. */ + SVN_ERR(open_session(&sess, url, &uri, tunnel_argv, + callbacks, callback_baton, sess_pool)); + session->priv = sess; + + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session, + const char *url, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = ra_session->priv; + svn_ra_svn_conn_t *conn = sess->conn; + svn_error_t *err; + apr_pool_t *sess_pool; + svn_ra_svn__session_baton_t *new_sess; + apr_uri_t uri; + + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "reparent", "c", url)); + err = handle_auth_request(sess, pool); + if (! err) + { + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); + sess->url = apr_pstrdup(sess->pool, url); + return SVN_NO_ERROR; + } + else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD) + return err; + + /* Servers before 1.4 doesn't support this command; try to reconnect + instead. */ + svn_error_clear(err); + /* Create a new subpool of the RA session pool. */ + sess_pool = svn_pool_create(ra_session->pool); + err = parse_url(url, &uri, sess_pool); + if (! err) + err = open_session(&new_sess, url, &uri, sess->tunnel_argv, + sess->callbacks, sess->callbacks_baton, sess_pool); + /* We destroy the new session pool on error, since it is allocated in + the main session pool. */ + if (err) + { + svn_pool_destroy(sess_pool); + return err; + } + + /* We have a new connection, assign it and destroy the old. */ + ra_session->priv = new_sess; + svn_pool_destroy(sess->pool); + + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session, + const char **url, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = session->priv; + *url = apr_pstrdup(pool, sess->url); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session, + svn_revnum_t *rev, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-latest-rev", "")); + SVN_ERR(handle_auth_request(sess_baton, pool)); + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session, + svn_revnum_t *rev, apr_time_t tm, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-dated-rev", "c", + svn_time_to_cstring(tm, pool))); + SVN_ERR(handle_auth_request(sess_baton, pool)); + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev)); + return SVN_NO_ERROR; +} + +/* Forward declaration. */ +static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session, + svn_boolean_t *has, + const char *capability, + apr_pool_t *pool); + +static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, + const char *name, + const svn_string_t *const *old_value_p, + const svn_string_t *value, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + svn_boolean_t dont_care; + const svn_string_t *old_value; + svn_boolean_t has_atomic_revprops; + + SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops, + SVN_RA_SVN_CAP_ATOMIC_REVPROPS, + pool)); + + if (old_value_p) + { + /* How did you get past the same check in svn_ra_change_rev_prop2()? */ + SVN_ERR_ASSERT(has_atomic_revprops); + + dont_care = FALSE; + old_value = *old_value_p; + } + else + { + dont_care = TRUE; + old_value = NULL; + } + + if (has_atomic_revprops) + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "change-rev-prop2", "rc(?s)(b?s)", + rev, name, value, dont_care, old_value)); + else + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "change-rev-prop", "rc?s", + rev, name, value)); + + SVN_ERR(handle_auth_request(sess_baton, pool)); + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + + *uuid = conn->uuid; + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + + if (!conn->repos_root) + return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, + _("Server did not send repository root")); + *url = conn->repos_root; + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev, + apr_hash_t **props, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + apr_array_header_t *proplist; + + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "rev-proplist", "r", rev)); + SVN_ERR(handle_auth_request(sess_baton, pool)); + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &proplist)); + SVN_ERR(svn_ra_svn_parse_proplist(proplist, pool, props)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, + const char *name, + svn_string_t **value, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "rev-prop", "rc", rev, name)); + SVN_ERR(handle_auth_request(sess_baton, pool)); + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?s)", value)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_end_commit(void *baton) +{ + ra_svn_commit_callback_baton_t *ccb = baton; + svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool); + + SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool)); + SVN_ERR(svn_ra_svn_read_tuple(ccb->sess_baton->conn, ccb->pool, + "r(?c)(?c)?(?c)", + &(commit_info->revision), + &(commit_info->date), + &(commit_info->author), + &(commit_info->post_commit_err))); + + if (ccb->callback) + SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_commit(svn_ra_session_t *session, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_hash_t *revprop_table, + svn_commit_callback2_t callback, + void *callback_baton, + apr_hash_t *lock_tokens, + svn_boolean_t keep_locks, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + ra_svn_commit_callback_baton_t *ccb; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + const svn_string_t *log_msg = apr_hash_get(revprop_table, + SVN_PROP_REVISION_LOG, + APR_HASH_KEY_STRING); + + /* If we're sending revprops other than svn:log, make sure the server won't + silently ignore them. */ + if (apr_hash_count(revprop_table) > 1 && + ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, + _("Server doesn't support setting arbitrary " + "revision properties during commit")); + + /* Tell the server we're starting the commit. + Send log message here for backwards compatibility with servers + before 1.5. */ + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(!", "commit", + log_msg->data)); + if (lock_tokens) + { + iterpool = svn_pool_create(pool); + for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi)) + { + const void *key; + void *val; + const char *path, *token; + + svn_pool_clear(iterpool); + apr_hash_this(hi, &key, NULL, &val); + path = key; + token = val; + SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "cc", path, token)); + } + svn_pool_destroy(iterpool); + } + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)b(!", keep_locks)); + SVN_ERR(svn_ra_svn_write_proplist(conn, pool, revprop_table)); + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); + SVN_ERR(handle_auth_request(sess_baton, pool)); + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); + + /* Remember a few arguments for when the commit is over. */ + ccb = apr_palloc(pool, sizeof(*ccb)); + ccb->sess_baton = sess_baton; + ccb->pool = pool; + ccb->callback = callback; + ccb->callback_baton = callback_baton; + + /* Fetch an editor for the caller to drive. The editor will call + * ra_svn_end_commit() upon close_edit(), at which point we'll fill + * in the new_rev, committed_date, and committed_author values. */ + svn_ra_svn_get_editor(editor, edit_baton, conn, pool, + ra_svn_end_commit, ccb); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path, + svn_revnum_t rev, svn_stream_t *stream, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + apr_array_header_t *proplist; + const char *expected_digest; + svn_checksum_t *expected_checksum = NULL; + svn_checksum_ctx_t *checksum_ctx; + apr_pool_t *iterpool; + + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-file", "c(?r)bb", path, + rev, (props != NULL), (stream != NULL))); + SVN_ERR(handle_auth_request(sess_baton, pool)); + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?c)rl", + &expected_digest, + &rev, &proplist)); + + if (fetched_rev) + *fetched_rev = rev; + if (props) + SVN_ERR(svn_ra_svn_parse_proplist(proplist, pool, props)); + + /* We're done if the contents weren't wanted. */ + if (!stream) + return SVN_NO_ERROR; + + if (expected_digest) + { + SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, + expected_digest, pool)); + checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); + } + + /* Read the file's contents. */ + iterpool = svn_pool_create(pool); + while (1) + { + svn_ra_svn_item_t *item; + + svn_pool_clear(iterpool); + SVN_ERR(svn_ra_svn_read_item(conn, iterpool, &item)); + if (item->kind != SVN_RA_SVN_STRING) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Non-string as part of file contents")); + if (item->u.string->len == 0) + break; + + if (expected_checksum) + SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data, + item->u.string->len)); + + SVN_ERR(svn_stream_write(stream, item->u.string->data, + &item->u.string->len)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); + + if (expected_checksum) + { + svn_checksum_t *checksum; + + SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool)); + if (!svn_checksum_match(checksum, expected_checksum)) + return svn_checksum_mismatch_err(expected_checksum, checksum, pool, + _("Checksum mismatch for '%s'"), + path); + } + + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session, + apr_hash_t **dirents, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + const char *path, + svn_revnum_t rev, + apr_uint32_t dirent_fields, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + apr_array_header_t *proplist, *dirlist; + int i; + + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path, + rev, (props != NULL), (dirents != NULL))); + if (dirent_fields & SVN_DIRENT_KIND) + SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND)); + if (dirent_fields & SVN_DIRENT_SIZE) + SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE)); + if (dirent_fields & SVN_DIRENT_HAS_PROPS) + SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS)); + if (dirent_fields & SVN_DIRENT_CREATED_REV) + SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV)); + if (dirent_fields & SVN_DIRENT_TIME) + SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME)); + if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) + SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR)); + + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); + + SVN_ERR(handle_auth_request(sess_baton, pool)); + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "rll", &rev, &proplist, + &dirlist)); + + if (fetched_rev) + *fetched_rev = rev; + if (props) + SVN_ERR(svn_ra_svn_parse_proplist(proplist, pool, props)); + + /* We're done if dirents aren't wanted. */ + if (!dirents) + return SVN_NO_ERROR; + + /* Interpret the directory list. */ + *dirents = apr_hash_make(pool); + for (i = 0; i < dirlist->nelts; i++) + { + const char *name, *kind, *cdate, *cauthor; + svn_boolean_t has_props; + svn_dirent_t *dirent; + apr_uint64_t size; + svn_revnum_t crev; + svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t); + + if (elt->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Dirlist element not a list")); + SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)", + &name, &kind, &size, &has_props, + &crev, &cdate, &cauthor)); + name = svn_relpath_canonicalize(name, pool); + dirent = apr_palloc(pool, sizeof(*dirent)); + dirent->kind = svn_node_kind_from_word(kind); + dirent->size = size;/* FIXME: svn_filesize_t */ + dirent->has_props = has_props; + dirent->created_rev = crev; + /* NOTE: the tuple's format string says CDATE may be NULL. But this + function does not allow that. The server has always sent us some + random date, however, so this just happens to work. But let's + be wary of servers that are (improperly) fixed to send NULL. + + Note: they should NOT be "fixed" to send NULL, as that would break + any older clients which received that NULL. But we may as well + be defensive against a malicous server. */ + if (cdate == NULL) + dirent->time = 0; + else + SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool)); + dirent->last_author = cauthor; + apr_hash_set(*dirents, name, APR_HASH_KEY_STRING, dirent); + } + + return SVN_NO_ERROR; +} + +/* Converts a apr_uint64_t with values TRUE, FALSE or + SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn_parse_tuple + to a svn_tristate_t */ +static svn_tristate_t +optbool_to_tristate(apr_uint64_t v) +{ + switch (v) + { + case TRUE: + return svn_tristate_true; + case FALSE: + return svn_tristate_false; + default: /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */ + return svn_tristate_unknown; + } +} + +/* If REVISION is SVN_INVALID_REVNUM, no value is sent to the + server, which defaults to youngest. */ +static svn_error_t *ra_svn_get_mergeinfo( + svn_ra_session_t *session, + svn_mergeinfo_catalog_t *catalog, + const apr_array_header_t *paths, + svn_revnum_t revision, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t include_descendants, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + int i; + apr_array_header_t *mergeinfo_tuple; + svn_ra_svn_item_t *elt; + const char *path; + + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "get-mergeinfo")); + for (i = 0; i < paths->nelts; i++) + { + path = APR_ARRAY_IDX(paths, i, const char *); + SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path)); + } + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)wb)", revision, + svn_inheritance_to_word(inherit), + include_descendants)); + + SVN_ERR(handle_auth_request(sess_baton, pool)); + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &mergeinfo_tuple)); + + *catalog = NULL; + if (mergeinfo_tuple->nelts > 0) + { + *catalog = apr_hash_make(pool); + for (i = 0; i < mergeinfo_tuple->nelts; i++) + { + svn_mergeinfo_t for_path; + const char *to_parse; + + elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i]; + if (elt->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Mergeinfo element is not a list")); + SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "cc", + &path, &to_parse)); + SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool)); + /* Correct for naughty servers that send "relative" paths + with leading slashes! */ + apr_hash_set(*catalog, path[0] == '/' ? path + 1 : path, + APR_HASH_KEY_STRING, for_path); + } + } + + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_update(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, svn_revnum_t rev, + const char *target, svn_depth_t depth, + svn_boolean_t send_copyfrom_args, + const svn_delta_editor_t *update_editor, + void *update_baton, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); + + /* Tell the server we want to start an update. */ + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "update", "(?r)cbwb", rev, target, + recurse, svn_depth_to_word(depth), + send_copyfrom_args)); + SVN_ERR(handle_auth_request(sess_baton, pool)); + + /* Fetch a reporter for the caller to drive. The reporter will drive + * update_editor upon finish_report(). */ + SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, + target, depth, reporter, report_baton)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_switch(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, svn_revnum_t rev, + const char *target, svn_depth_t depth, + const char *switch_url, + const svn_delta_editor_t *update_editor, + void *update_baton, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); + + /* Tell the server we want to start a switch. */ + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "switch", "(?r)cbcw", rev, + target, recurse, switch_url, + svn_depth_to_word(depth))); + SVN_ERR(handle_auth_request(sess_baton, pool)); + + /* Fetch a reporter for the caller to drive. The reporter will drive + * update_editor upon finish_report(). */ + SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, + target, depth, reporter, report_baton)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_status(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + const char *target, svn_revnum_t rev, + svn_depth_t depth, + const svn_delta_editor_t *status_editor, + void *status_baton, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); + + /* Tell the server we want to start a status operation. */ + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "status", "cb(?r)w", + target, recurse, rev, + svn_depth_to_word(depth))); + SVN_ERR(handle_auth_request(sess_baton, pool)); + + /* Fetch a reporter for the caller to drive. The reporter will drive + * status_editor upon finish_report(). */ + SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton, + target, depth, reporter, report_baton)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_diff(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + svn_revnum_t rev, const char *target, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t text_deltas, + const char *versus_url, + const svn_delta_editor_t *diff_editor, + void *diff_baton, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); + + /* Tell the server we want to start a diff. */ + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "diff", "(?r)cbbcbw", rev, + target, recurse, ignore_ancestry, + versus_url, text_deltas, + svn_depth_to_word(depth))); + SVN_ERR(handle_auth_request(sess_baton, pool)); + + /* Fetch a reporter for the caller to drive. The reporter will drive + * diff_editor upon finish_report(). */ + SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton, + target, depth, reporter, report_baton)); + return SVN_NO_ERROR; +} + + +static svn_error_t *ra_svn_log(svn_ra_session_t *session, + const apr_array_header_t *paths, + svn_revnum_t start, svn_revnum_t end, + int limit, + svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_boolean_t include_merged_revisions, + const apr_array_header_t *revprops, + svn_log_entry_receiver_t receiver, + void *receiver_baton, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + apr_pool_t *iterpool; + int i; + int nest_level = 0; + const char *path; + char *name; + svn_boolean_t want_custom_revprops; + + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "log")); + if (paths) + { + for (i = 0; i < paths->nelts; i++) + { + path = APR_ARRAY_IDX(paths, i, const char *); + SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path)); + } + } + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end, + discover_changed_paths, strict_node_history, + (apr_uint64_t) limit, + include_merged_revisions)); + if (revprops) + { + want_custom_revprops = FALSE; + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w(!", "revprops")); + for (i = 0; i < revprops->nelts; i++) + { + name = APR_ARRAY_IDX(revprops, i, char *); + SVN_ERR(svn_ra_svn_write_cstring(conn, pool, name)); + if (!want_custom_revprops + && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0 + && strcmp(name, SVN_PROP_REVISION_DATE) != 0 + && strcmp(name, SVN_PROP_REVISION_LOG) != 0) + want_custom_revprops = TRUE; + } + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); + } + else + { + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w())", "all-revprops")); + want_custom_revprops = TRUE; + } + + SVN_ERR(handle_auth_request(sess_baton, pool)); + + /* Read the log messages. */ + iterpool = svn_pool_create(pool); + while (1) + { + apr_uint64_t has_children_param, invalid_revnum_param; + apr_uint64_t has_subtractive_merge_param; + svn_string_t *author, *date, *message; + apr_array_header_t *cplist, *rplist; + svn_log_entry_t *log_entry; + svn_boolean_t has_children; + svn_boolean_t subtractive_merge = FALSE; + apr_uint64_t revprop_count; + svn_ra_svn_item_t *item; + apr_hash_t *cphash; + svn_revnum_t rev; + int nreceived; + + svn_pool_clear(iterpool); + SVN_ERR(svn_ra_svn_read_item(conn, iterpool, &item)); + if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) + break; + if (item->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Log entry not a list")); + SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, iterpool, + "lr(?s)(?s)(?s)?BBnl?B", + &cplist, &rev, &author, &date, + &message, &has_children_param, + &invalid_revnum_param, + &revprop_count, &rplist, + &has_subtractive_merge_param)); + if (want_custom_revprops && rplist == NULL) + { + /* Caller asked for custom revprops, but server is too old. */ + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, + _("Server does not support custom revprops" + " via log")); + } + + if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) + has_children = FALSE; + else + has_children = (svn_boolean_t) has_children_param; + + if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) + subtractive_merge = FALSE; + else + subtractive_merge = (svn_boolean_t) has_subtractive_merge_param; + + /* Because the svn protocol won't let us send an invalid revnum, we have + to recover that fact using the extra parameter. */ + if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER + && invalid_revnum_param) + rev = SVN_INVALID_REVNUM; + + if (cplist->nelts > 0) + { + /* Interpret the changed-paths list. */ + cphash = apr_hash_make(iterpool); + for (i = 0; i < cplist->nelts; i++) + { + svn_log_changed_path2_t *change; + const char *copy_path, *action, *cpath, *kind_str; + apr_uint64_t text_mods, prop_mods; + svn_revnum_t copy_rev; + svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i, + svn_ra_svn_item_t); + + if (elt->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Changed-path entry not a list")); + SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, iterpool, + "cw(?cr)?(?c?BB)", + &cpath, &action, ©_path, + ©_rev, &kind_str, + &text_mods, &prop_mods)); + cpath = svn_fspath__canonicalize(cpath, iterpool); + if (copy_path) + copy_path = svn_fspath__canonicalize(copy_path, iterpool); + change = svn_log_changed_path2_create(iterpool); + change->action = *action; + change->copyfrom_path = copy_path; + change->copyfrom_rev = copy_rev; + change->node_kind = svn_node_kind_from_word(kind_str); + change->text_modified = optbool_to_tristate(text_mods); + change->props_modified = optbool_to_tristate(prop_mods); + apr_hash_set(cphash, cpath, APR_HASH_KEY_STRING, change); + } + } + else + cphash = NULL; + + nreceived = 0; + if (! (limit && (nest_level == 0) && (++nreceived > limit))) + { + log_entry = svn_log_entry_create(iterpool); + + log_entry->changed_paths = cphash; + log_entry->changed_paths2 = cphash; + log_entry->revision = rev; + log_entry->has_children = has_children; + log_entry->subtractive_merge = subtractive_merge; + if (rplist) + SVN_ERR(svn_ra_svn_parse_proplist(rplist, pool, + &log_entry->revprops)); + if (log_entry->revprops == NULL) + log_entry->revprops = apr_hash_make(pool); + if (revprops == NULL) + { + /* Caller requested all revprops; set author/date/log. */ + if (author) + apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, + APR_HASH_KEY_STRING, author); + if (date) + apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE, + APR_HASH_KEY_STRING, date); + if (message) + apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG, + APR_HASH_KEY_STRING, message); + } + else + { + /* Caller requested some; maybe set author/date/log. */ + for (i = 0; i < revprops->nelts; i++) + { + name = APR_ARRAY_IDX(revprops, i, char *); + if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0) + apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, + APR_HASH_KEY_STRING, author); + if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0) + apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE, + APR_HASH_KEY_STRING, date); + if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0) + apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG, + APR_HASH_KEY_STRING, message); + } + } + SVN_ERR(receiver(receiver_baton, log_entry, iterpool)); + if (log_entry->has_children) + { + nest_level++; + } + if (! SVN_IS_VALID_REVNUM(log_entry->revision)) + { + SVN_ERR_ASSERT(nest_level); + nest_level--; + } + } + } + svn_pool_destroy(iterpool); + + /* Read the response. */ + return svn_ra_svn_read_cmd_response(conn, pool, ""); +} + + +static svn_error_t *ra_svn_check_path(svn_ra_session_t *session, + const char *path, svn_revnum_t rev, + svn_node_kind_t *kind, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + const char *kind_word; + + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "check-path", "c(?r)", path, rev)); + SVN_ERR(handle_auth_request(sess_baton, pool)); + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "w", &kind_word)); + *kind = svn_node_kind_from_word(kind_word); + return SVN_NO_ERROR; +} + + +/* If ERR is a command not supported error, wrap it in a + SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */ +static svn_error_t *handle_unsupported_cmd(svn_error_t *err, + const char *msg) +{ + if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, + msg); + return err; +} + + +static svn_error_t *ra_svn_stat(svn_ra_session_t *session, + const char *path, svn_revnum_t rev, + svn_dirent_t **dirent, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + apr_array_header_t *list = NULL; + svn_dirent_t *the_dirent; + + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "stat", "c(?r)", path, rev)); + + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), + _("'stat' not implemented"))); + + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &list)); + + if (! list) + { + *dirent = NULL; + } + else + { + const char *kind, *cdate, *cauthor; + svn_boolean_t has_props; + svn_revnum_t crev; + apr_uint64_t size; + + SVN_ERR(svn_ra_svn_parse_tuple(list, pool, "wnbr(?c)(?c)", + &kind, &size, &has_props, + &crev, &cdate, &cauthor)); + + the_dirent = apr_palloc(pool, sizeof(*the_dirent)); + the_dirent->kind = svn_node_kind_from_word(kind); + the_dirent->size = size;/* FIXME: svn_filesize_t */ + the_dirent->has_props = has_props; + the_dirent->created_rev = crev; + SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool)); + the_dirent->last_author = cauthor; + + *dirent = the_dirent; + } + + return SVN_NO_ERROR; +} + + +static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session, + apr_hash_t **locations, + const char *path, + svn_revnum_t peg_revision, + const apr_array_header_t *location_revisions, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + svn_revnum_t revision; + svn_boolean_t is_done; + int i; + + /* Transmit the parameters. */ + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(cr(!", + "get-locations", path, peg_revision)); + for (i = 0; i < location_revisions->nelts; i++) + { + revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t); + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!r!", revision)); + } + + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); + + /* Servers before 1.1 don't support this command. Check for this here. */ + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), + _("'get-locations' not implemented"))); + + /* Read the hash items. */ + is_done = FALSE; + *locations = apr_hash_make(pool); + while (!is_done) + { + svn_ra_svn_item_t *item; + const char *ret_path; + + SVN_ERR(svn_ra_svn_read_item(conn, pool, &item)); + if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) + is_done = 1; + else if (item->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Location entry not a list")); + else + { + SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "rc", + &revision, &ret_path)); + ret_path = svn_fspath__canonicalize(ret_path, pool); + apr_hash_set(*locations, apr_pmemdup(pool, &revision, + sizeof(revision)), + sizeof(revision), ret_path); + } + } + + /* Read the response. This is so the server would have a chance to + * report an error. */ + return svn_ra_svn_read_cmd_response(conn, pool, ""); +} + +static svn_error_t * +ra_svn_get_location_segments(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_location_segment_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + svn_boolean_t is_done; + apr_pool_t *iterpool = svn_pool_create(pool); + + /* Transmit the parameters. */ + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(?r)(?r)(?r))", + "get-location-segments", + path, peg_revision, start_rev, end_rev)); + + /* Servers before 1.5 don't support this command. Check for this here. */ + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), + _("'get-location-segments' not implemented"))); + + /* Parse the response. */ + is_done = FALSE; + while (!is_done) + { + svn_revnum_t range_start, range_end; + svn_ra_svn_item_t *item; + const char *ret_path; + + svn_pool_clear(iterpool); + SVN_ERR(svn_ra_svn_read_item(conn, iterpool, &item)); + if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) + is_done = 1; + else if (item->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Location segment entry not a list")); + else + { + svn_location_segment_t *segment = apr_pcalloc(iterpool, + sizeof(*segment)); + SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, iterpool, "rr(?c)", + &range_start, &range_end, &ret_path)); + if (! (SVN_IS_VALID_REVNUM(range_start) + && SVN_IS_VALID_REVNUM(range_end))) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Expected valid revision range")); + if (ret_path) + ret_path = svn_relpath_canonicalize(ret_path, iterpool); + segment->path = ret_path; + segment->range_start = range_start; + segment->range_end = range_end; + SVN_ERR(receiver(segment, receiver_baton, iterpool)); + } + } + svn_pool_destroy(iterpool); + + /* Read the response. This is so the server would have a chance to + * report an error. */ + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); + + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, + const char *path, + svn_revnum_t start, svn_revnum_t end, + svn_boolean_t include_merged_revisions, + svn_file_rev_handler_t handler, + void *handler_baton, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + apr_pool_t *rev_pool, *chunk_pool; + svn_boolean_t has_txdelta; + svn_boolean_t had_revision = FALSE; + + /* One sub-pool for each revision and one for each txdelta chunk. + Note that the rev_pool must live during the following txdelta. */ + rev_pool = svn_pool_create(pool); + chunk_pool = svn_pool_create(pool); + + SVN_ERR(svn_ra_svn_write_cmd(sess_baton->conn, pool, "get-file-revs", + "c(?r)(?r)b", path, start, end, + include_merged_revisions)); + + /* Servers before 1.1 don't support this command. Check for this here. */ + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), + _("'get-file-revs' not implemented"))); + + while (1) + { + apr_array_header_t *rev_proplist, *proplist; + apr_uint64_t merged_rev_param; + apr_array_header_t *props; + svn_ra_svn_item_t *item; + apr_hash_t *rev_props; + svn_revnum_t rev; + const char *p; + svn_boolean_t merged_rev; + svn_txdelta_window_handler_t d_handler; + void *d_baton; + + svn_pool_clear(rev_pool); + svn_pool_clear(chunk_pool); + SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, rev_pool, &item)); + if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) + break; + /* Either we've got a correct revision or we will error out below. */ + had_revision = TRUE; + if (item->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Revision entry not a list")); + + SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, rev_pool, + "crll?B", &p, &rev, &rev_proplist, + &proplist, &merged_rev_param)); + p = svn_fspath__canonicalize(p, rev_pool); + SVN_ERR(svn_ra_svn_parse_proplist(rev_proplist, rev_pool, &rev_props)); + SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props)); + if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) + merged_rev = FALSE; + else + merged_rev = (svn_boolean_t) merged_rev_param; + + /* Get the first delta chunk so we know if there is a delta. */ + SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, chunk_pool, &item)); + if (item->kind != SVN_RA_SVN_STRING) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Text delta chunk not a string")); + has_txdelta = item->u.string->len > 0; + + SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev, + has_txdelta ? &d_handler : NULL, &d_baton, + props, rev_pool)); + + /* Process the text delta if any. */ + if (has_txdelta) + { + svn_stream_t *stream; + + if (d_handler) + stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE, + rev_pool); + else + stream = NULL; + while (item->u.string->len > 0) + { + apr_size_t size; + + size = item->u.string->len; + if (stream) + SVN_ERR(svn_stream_write(stream, item->u.string->data, &size)); + svn_pool_clear(chunk_pool); + + SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, chunk_pool, + &item)); + if (item->kind != SVN_RA_SVN_STRING) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Text delta chunk not a string")); + } + if (stream) + SVN_ERR(svn_stream_close(stream)); + } + } + + SVN_ERR(svn_ra_svn_read_cmd_response(sess_baton->conn, pool, "")); + + /* Return error if we didn't get any revisions. */ + if (!had_revision) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("The get-file-revs command didn't return " + "any revisions")); + + svn_pool_destroy(chunk_pool); + svn_pool_destroy(rev_pool); + + return SVN_NO_ERROR; +} + +/* For each path in PATH_REVS, send a 'lock' command to the server. + Used with 1.2.x series servers which support locking, but of only + one path at a time. ra_svn_lock(), which supports 'lock-many' + is now the default. See svn_ra_lock() docstring for interface details. */ +static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session, + apr_hash_t *path_revs, + const char *comment, + svn_boolean_t steal_lock, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = session->priv; + svn_ra_svn_conn_t* conn = sess->conn; + apr_array_header_t *list; + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(pool); + + for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) + { + svn_lock_t *lock; + const void *key; + const char *path; + void *val; + svn_revnum_t *revnum; + svn_error_t *err, *callback_err = NULL; + + svn_pool_clear(iterpool); + + apr_hash_this(hi, &key, NULL, &val); + path = key; + revnum = val; + + SVN_ERR(svn_ra_svn_write_cmd(conn, iterpool, "lock", "c(?c)b(?r)", + path, comment, + steal_lock, *revnum)); + + /* Servers before 1.2 doesn't support locking. Check this here. */ + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), + _("Server doesn't support " + "the lock command"))); + + err = svn_ra_svn_read_cmd_response(conn, iterpool, "l", &list); + + if (!err) + SVN_ERR(parse_lock(list, iterpool, &lock)); + + if (err && !SVN_ERR_IS_LOCK_ERROR(err)) + return err; + + if (lock_func) + callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock, + err, iterpool); + + svn_error_clear(err); + + if (callback_err) + return callback_err; + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* For each path in PATH_TOKENS, send an 'unlock' command to the server. + Used with 1.2.x series servers which support unlocking, but of only + one path at a time. ra_svn_unlock(), which supports 'unlock-many' is + now the default. See svn_ra_unlock() docstring for interface details. */ +static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session, + apr_hash_t *path_tokens, + svn_boolean_t break_lock, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = session->priv; + svn_ra_svn_conn_t* conn = sess->conn; + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(pool); + + for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) + { + const void *key; + const char *path; + void *val; + const char *token; + svn_error_t *err, *callback_err = NULL; + + svn_pool_clear(iterpool); + + apr_hash_this(hi, &key, NULL, &val); + path = key; + if (strcmp(val, "") != 0) + token = val; + else + token = NULL; + + SVN_ERR(svn_ra_svn_write_cmd(conn, iterpool, "unlock", "c(?c)b", + path, token, break_lock)); + + /* Servers before 1.2 don't support locking. Check this here. */ + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool), + _("Server doesn't support the unlock " + "command"))); + + err = svn_ra_svn_read_cmd_response(conn, iterpool, ""); + + if (err && !SVN_ERR_IS_UNLOCK_ERROR(err)) + return err; + + if (lock_func) + callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool); + + svn_error_clear(err); + + if (callback_err) + return callback_err; + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Tell the server to lock all paths in PATH_REVS. + See svn_ra_lock() for interface details. */ +static svn_error_t *ra_svn_lock(svn_ra_session_t *session, + apr_hash_t *path_revs, + const char *comment, + svn_boolean_t steal_lock, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = session->priv; + svn_ra_svn_conn_t *conn = sess->conn; + apr_hash_index_t *hi; + svn_error_t *err; + apr_pool_t *iterpool = svn_pool_create(pool); + + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)b(!", "lock-many", + comment, steal_lock)); + + for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) + { + const void *key; + const char *path; + void *val; + svn_revnum_t *revnum; + + svn_pool_clear(iterpool); + apr_hash_this(hi, &key, NULL, &val); + path = key; + revnum = val; + + SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "c(?r)", path, *revnum)); + } + + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); + + err = handle_auth_request(sess, pool); + + /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back + * to 'lock'. */ + if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) + { + svn_error_clear(err); + return ra_svn_lock_compat(session, path_revs, comment, steal_lock, + lock_func, lock_baton, pool); + } + + if (err) + return err; + + /* Loop over responses to get lock information. */ + for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) + { + svn_ra_svn_item_t *elt; + const void *key; + const char *path; + svn_error_t *callback_err; + const char *status; + svn_lock_t *lock; + apr_array_header_t *list; + + apr_hash_this(hi, &key, NULL, NULL); + path = key; + + svn_pool_clear(iterpool); + SVN_ERR(svn_ra_svn_read_item(conn, iterpool, &elt)); + + /* The server might have encountered some sort of fatal error in + the middle of the request list. If this happens, it will + transmit "done" to end the lock-info early, and then the + overall command response will talk about the fatal error. */ + if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0) + break; + + if (elt->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Lock response not a list")); + + SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, iterpool, "wl", &status, + &list)); + + if (strcmp(status, "failure") == 0) + err = svn_ra_svn__handle_failure_status(list, iterpool); + else if (strcmp(status, "success") == 0) + { + SVN_ERR(parse_lock(list, iterpool, &lock)); + err = NULL; + } + else + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Unknown status for lock command")); + + if (lock_func) + callback_err = lock_func(lock_baton, path, TRUE, + err ? NULL : lock, + err, iterpool); + else + callback_err = SVN_NO_ERROR; + + svn_error_clear(err); + + if (callback_err) + return callback_err; + } + + /* If we didn't break early above, and the whole hash was traversed, + read the final "done" from the server. */ + if (!hi) + { + svn_ra_svn_item_t *elt; + + SVN_ERR(svn_ra_svn_read_item(conn, pool, &elt)); + if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Didn't receive end marker for lock " + "responses")); + } + + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Tell the server to unlock all paths in PATH_TOKENS. + See svn_ra_unlock() for interface details. */ +static svn_error_t *ra_svn_unlock(svn_ra_session_t *session, + apr_hash_t *path_tokens, + svn_boolean_t break_lock, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = session->priv; + svn_ra_svn_conn_t *conn = sess->conn; + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_error_t *err; + const char *path; + + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(b(!", "unlock-many", + break_lock)); + + for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) + { + void *val; + const void *key; + const char *token; + + svn_pool_clear(iterpool); + apr_hash_this(hi, &key, NULL, &val); + path = key; + + if (strcmp(val, "") != 0) + token = val; + else + token = NULL; + + SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "c(?c)", path, token)); + } + + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))")); + + err = handle_auth_request(sess, pool); + + /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back + * to 'unlock'. + */ + if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) + { + svn_error_clear(err); + return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func, + lock_baton, pool); + } + + if (err) + return err; + + /* Loop over responses to unlock files. */ + for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) + { + svn_ra_svn_item_t *elt; + const void *key; + svn_error_t *callback_err; + const char *status; + apr_array_header_t *list; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_ra_svn_read_item(conn, iterpool, &elt)); + + /* The server might have encountered some sort of fatal error in + the middle of the request list. If this happens, it will + transmit "done" to end the lock-info early, and then the + overall command response will talk about the fatal error. */ + if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0)) + break; + + apr_hash_this(hi, &key, NULL, NULL); + path = key; + + if (elt->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Unlock response not a list")); + + SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, iterpool, "wl", &status, + &list)); + + if (strcmp(status, "failure") == 0) + err = svn_ra_svn__handle_failure_status(list, iterpool); + else if (strcmp(status, "success") == 0) + { + SVN_ERR(svn_ra_svn_parse_tuple(list, iterpool, "c", &path)); + err = SVN_NO_ERROR; + } + else + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Unknown status for unlock command")); + + if (lock_func) + callback_err = lock_func(lock_baton, path, FALSE, NULL, err, + iterpool); + else + callback_err = SVN_NO_ERROR; + + svn_error_clear(err); + + if (callback_err) + return callback_err; + } + + /* If we didn't break early above, and the whole hash was traversed, + read the final "done" from the server. */ + if (!hi) + { + svn_ra_svn_item_t *elt; + + SVN_ERR(svn_ra_svn_read_item(conn, pool, &elt)); + if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Didn't receive end marker for unlock " + "responses")); + } + + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session, + svn_lock_t **lock, + const char *path, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = session->priv; + svn_ra_svn_conn_t* conn = sess->conn; + apr_array_header_t *list; + + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-lock", "c", path)); + + /* Servers before 1.2 doesn't support locking. Check this here. */ + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), + _("Server doesn't support the get-lock " + "command"))); + + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &list)); + if (list) + SVN_ERR(parse_lock(list, pool, lock)); + else + *lock = NULL; + + return SVN_NO_ERROR; +} + +/* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized + to prevent a dependency cycle. */ +static svn_error_t *path_relative_to_root(svn_ra_session_t *session, + const char **rel_path, + const char *url, + apr_pool_t *pool) +{ + const char *root_url; + + SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool)); + if (strcmp(root_url, url) == 0) + { + *rel_path = ""; + } + else + { + *rel_path = svn_uri__is_child(root_url, url, pool); + if (! *rel_path) + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("'%s' isn't a child of repository root " + "URL '%s'"), + url, root_url); + } + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session, + apr_hash_t **locks, + const char *path, + svn_depth_t depth, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = session->priv; + svn_ra_svn_conn_t* conn = sess->conn; + apr_array_header_t *list; + const char *full_url, *abs_path; + int i; + + /* Figure out the repository abspath from PATH. */ + full_url = svn_path_url_add_component2(sess->url, path, pool); + SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool)); + abs_path = svn_fspath__canonicalize(abs_path, pool); + + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-locks", "c(w)", path, + svn_depth_to_word(depth))); + + /* Servers before 1.2 doesn't support locking. Check this here. */ + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), + _("Server doesn't support the get-lock " + "command"))); + + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &list)); + + *locks = apr_hash_make(pool); + + for (i = 0; i < list->nelts; ++i) + { + svn_lock_t *lock; + svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); + + if (elt->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Lock element not a list")); + SVN_ERR(parse_lock(elt->u.list, pool, &lock)); + + /* Filter out unwanted paths. Since Subversion only allows + locks on files, we can treat depth=immediates the same as + depth=files for filtering purposes. Meaning, we'll keep + this lock if: + + a) its path is the very path we queried, or + b) we've asked for a fully recursive answer, or + c) we've asked for depth=files or depth=immediates, and this + lock is on an immediate child of our query path. + */ + if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity)) + { + apr_hash_set(*locks, lock->path, APR_HASH_KEY_STRING, lock); + } + else if ((depth == svn_depth_files) || (depth == svn_depth_immediates)) + { + const char *relpath = svn_fspath__is_child(abs_path, lock->path, pool); + if (relpath && (svn_path_component_count(relpath) == 1)) + apr_hash_set(*locks, lock->path, APR_HASH_KEY_STRING, lock); + } + } + + return SVN_NO_ERROR; +} + + +static svn_error_t *ra_svn_replay(svn_ra_session_t *session, + svn_revnum_t revision, + svn_revnum_t low_water_mark, + svn_boolean_t send_deltas, + const svn_delta_editor_t *editor, + void *edit_baton, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = session->priv; + + SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay", "rrb", revision, + low_water_mark, send_deltas)); + + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), + _("Server doesn't support the replay " + "command"))); + + SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton, + NULL, TRUE)); + + return svn_ra_svn_read_cmd_response(sess->conn, pool, ""); +} + + +static svn_error_t * +ra_svn_replay_range(svn_ra_session_t *session, + svn_revnum_t start_revision, + svn_revnum_t end_revision, + svn_revnum_t low_water_mark, + svn_boolean_t send_deltas, + svn_ra_replay_revstart_callback_t revstart_func, + svn_ra_replay_revfinish_callback_t revfinish_func, + void *replay_baton, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = session->priv; + apr_pool_t *iterpool; + svn_revnum_t rev; + svn_boolean_t drive_aborted = FALSE; + + SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay-range", "rrrb", + start_revision, end_revision, + low_water_mark, send_deltas)); + + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), + _("Server doesn't support the replay-range " + "command"))); + + iterpool = svn_pool_create(pool); + for (rev = start_revision; rev <= end_revision; rev++) + { + const svn_delta_editor_t *editor; + void *edit_baton; + apr_hash_t *rev_props; + const char *word; + apr_array_header_t *list; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_ra_svn_read_tuple(sess->conn, iterpool, + "wl", &word, &list)); + if (strcmp(word, "revprops") != 0) + return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Expected 'revprops', found '%s'"), + word); + + SVN_ERR(svn_ra_svn_parse_proplist(list, iterpool, &rev_props)); + + SVN_ERR(revstart_func(rev, replay_baton, + &editor, &edit_baton, + rev_props, + iterpool)); + SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool, + editor, edit_baton, + &drive_aborted, TRUE)); + /* If drive_editor2() aborted the commit, do NOT try to call + revfinish_func and commit the transaction! */ + if (drive_aborted) { + svn_pool_destroy(iterpool); + return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL, + _("Error while replaying commit")); + } + SVN_ERR(revfinish_func(rev, replay_baton, + editor, edit_baton, + rev_props, + iterpool)); + } + svn_pool_destroy(iterpool); + + return svn_ra_svn_read_cmd_response(sess->conn, pool, ""); +} + + +static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session, + svn_boolean_t *has, + const char *capability, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = session->priv; + + *has = FALSE; + + if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0) + *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_DEPTH); + else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0) + *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_MERGEINFO); + else if (strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0) + *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_LOG_REVPROPS); + else if (strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0) + *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_PARTIAL_REPLAY); + else if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0) + *has = svn_ra_svn_has_capability(sess->conn, + SVN_RA_SVN_CAP_COMMIT_REVPROPS); + else if (strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0) + *has = svn_ra_svn_has_capability(sess->conn, + SVN_RA_SVN_CAP_ATOMIC_REVPROPS); + else /* Don't know any other capabilities, so error. */ + { + return svn_error_createf + (SVN_ERR_UNKNOWN_CAPABILITY, NULL, + _("Don't know anything about capability '%s'"), capability); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +ra_svn_get_deleted_rev(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool) + +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + + /* Transmit the parameters. */ + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-deleted-rev", "crr", + path, peg_revision, end_revision)); + + /* Servers before 1.6 don't support this command. Check for this here. */ + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), + _("'get-deleted-rev' not implemented"))); + + return svn_ra_svn_read_cmd_response(conn, pool, "r", revision_deleted); +} + + +static const svn_ra__vtable_t ra_svn_vtable = { + svn_ra_svn_version, + ra_svn_get_description, + ra_svn_get_schemes, + ra_svn_open, + ra_svn_reparent, + ra_svn_get_session_url, + ra_svn_get_latest_rev, + ra_svn_get_dated_rev, + ra_svn_change_rev_prop, + ra_svn_rev_proplist, + ra_svn_rev_prop, + ra_svn_commit, + ra_svn_get_file, + ra_svn_get_dir, + ra_svn_get_mergeinfo, + ra_svn_update, + ra_svn_switch, + ra_svn_status, + ra_svn_diff, + ra_svn_log, + ra_svn_check_path, + ra_svn_stat, + ra_svn_get_uuid, + ra_svn_get_repos_root, + ra_svn_get_locations, + ra_svn_get_location_segments, + ra_svn_get_file_revs, + ra_svn_lock, + ra_svn_unlock, + ra_svn_get_lock, + ra_svn_get_locks, + ra_svn_replay, + ra_svn_has_capability, + ra_svn_replay_range, + ra_svn_get_deleted_rev +}; + +svn_error_t * +svn_ra_svn__init(const svn_version_t *loader_version, + const svn_ra__vtable_t **vtable, + apr_pool_t *pool) +{ + static const svn_version_checklist_t checklist[] = + { + { "svn_subr", svn_subr_version }, + { "svn_delta", svn_delta_version }, + { NULL, NULL } + }; + + SVN_ERR(svn_ver_check_list(svn_ra_svn_version(), checklist)); + + /* Simplified version check to make sure we can safely use the + VTABLE parameter. The RA loader does a more exhaustive check. */ + if (loader_version->major != SVN_VER_MAJOR) + { + return svn_error_createf + (SVN_ERR_VERSION_MISMATCH, NULL, + _("Unsupported RA loader version (%d) for ra_svn"), + loader_version->major); + } + + *vtable = &ra_svn_vtable; + +#ifdef SVN_HAVE_SASL + SVN_ERR(svn_ra_svn__sasl_init()); +#endif + + return SVN_NO_ERROR; +} + +/* Compatibility wrapper for the 1.1 and before API. */ +#define NAME "ra_svn" +#define DESCRIPTION RA_SVN_DESCRIPTION +#define VTBL ra_svn_vtable +#define INITFUNC svn_ra_svn__init +#define COMPAT_INITFUNC svn_ra_svn_init +#include "../libsvn_ra/wrapper_template.h" diff --git a/subversion/libsvn_ra_svn/cram.c b/subversion/libsvn_ra_svn/cram.c new file mode 100644 index 0000000..0de85e2 --- /dev/null +++ b/subversion/libsvn_ra_svn/cram.c @@ -0,0 +1,221 @@ +/* + * cram.c : Minimal standalone CRAM-MD5 implementation + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC +#define APR_WANT_STDIO +#include <apr_want.h> +#include <apr_general.h> +#include <apr_strings.h> +#include <apr_network_io.h> +#include <apr_time.h> +#include <apr_md5.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_ra_svn.h" +#include "svn_config.h" +#include "svn_private_config.h" + +#include "ra_svn.h" + +static int hex_to_int(char c) +{ + return (c >= '0' && c <= '9') ? c - '0' + : (c >= 'a' && c <= 'f') ? c - 'a' + 10 + : -1; +} + +static char int_to_hex(int v) +{ + return (char)((v < 10) ? '0' + v : 'a' + (v - 10)); +} + +static svn_boolean_t hex_decode(unsigned char *hashval, const char *hexval) +{ + int i, h1, h2; + + for (i = 0; i < APR_MD5_DIGESTSIZE; i++) + { + h1 = hex_to_int(hexval[2 * i]); + h2 = hex_to_int(hexval[2 * i + 1]); + if (h1 == -1 || h2 == -1) + return FALSE; + hashval[i] = (unsigned char)((h1 << 4) | h2); + } + return TRUE; +} + +static void hex_encode(char *hexval, const unsigned char *hashval) +{ + int i; + + for (i = 0; i < APR_MD5_DIGESTSIZE; i++) + { + hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf); + hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf); + } +} + +static void compute_digest(unsigned char *digest, const char *challenge, + const char *password) +{ + unsigned char secret[64]; + apr_size_t len = strlen(password), i; + apr_md5_ctx_t ctx; + + /* Munge the password into a 64-byte secret. */ + memset(secret, 0, sizeof(secret)); + if (len <= sizeof(secret)) + memcpy(secret, password, len); + else + apr_md5(secret, password, len); + + /* Compute MD5(secret XOR opad, MD5(secret XOR ipad, challenge)), + * where ipad is a string of 0x36 and opad is a string of 0x5c. */ + for (i = 0; i < sizeof(secret); i++) + secret[i] ^= 0x36; + apr_md5_init(&ctx); + apr_md5_update(&ctx, secret, sizeof(secret)); + apr_md5_update(&ctx, challenge, strlen(challenge)); + apr_md5_final(digest, &ctx); + for (i = 0; i < sizeof(secret); i++) + secret[i] ^= (0x36 ^ 0x5c); + apr_md5_init(&ctx); + apr_md5_update(&ctx, secret, sizeof(secret)); + apr_md5_update(&ctx, digest, APR_MD5_DIGESTSIZE); + apr_md5_final(digest, &ctx); +} + +/* Fail the authentication, from the server's perspective. */ +static svn_error_t *fail(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *msg) +{ + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", msg)); + return svn_ra_svn_flush(conn, pool); +} + +/* If we can, make the nonce with random bytes. If we can't... well, + * it just has to be different each time. The current time isn't + * absolutely guaranteed to be different for each connection, but it + * should prevent replay attacks in practice. */ +static apr_status_t make_nonce(apr_uint64_t *nonce) +{ +#if APR_HAS_RANDOM + return apr_generate_random_bytes((unsigned char *) nonce, sizeof(*nonce)); +#else + *nonce = apr_time_now(); + return APR_SUCCESS; +#endif +} + +svn_error_t *svn_ra_svn_cram_server(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + svn_config_t *pwdb, const char **user, + svn_boolean_t *success) +{ + apr_status_t status; + apr_uint64_t nonce; + char hostbuf[APRMAXHOSTLEN + 1]; + unsigned char cdigest[APR_MD5_DIGESTSIZE], sdigest[APR_MD5_DIGESTSIZE]; + const char *challenge, *sep, *password; + svn_ra_svn_item_t *item; + svn_string_t *resp; + + *success = FALSE; + + /* Send a challenge. */ + status = make_nonce(&nonce); + if (!status) + status = apr_gethostname(hostbuf, sizeof(hostbuf), pool); + if (status) + return fail(conn, pool, "Internal server error in authentication"); + challenge = apr_psprintf(pool, + "<%" APR_UINT64_T_FMT ".%" APR_TIME_T_FMT "@%s>", + nonce, apr_time_now(), hostbuf); + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "step", challenge)); + + /* Read the client's response and decode it into *user and cdigest. */ + SVN_ERR(svn_ra_svn_read_item(conn, pool, &item)); + if (item->kind != SVN_RA_SVN_STRING) /* Very wrong; don't report failure */ + return SVN_NO_ERROR; + resp = item->u.string; + sep = strrchr(resp->data, ' '); + if (!sep || resp->len - (sep + 1 - resp->data) != APR_MD5_DIGESTSIZE * 2 + || !hex_decode(cdigest, sep + 1)) + return fail(conn, pool, "Malformed client response in authentication"); + *user = apr_pstrmemdup(pool, resp->data, sep - resp->data); + + /* Verify the digest against the password in pwfile. */ + svn_config_get(pwdb, &password, SVN_CONFIG_SECTION_USERS, *user, NULL); + if (!password) + return fail(conn, pool, "Username not found"); + compute_digest(sdigest, challenge, password); + if (memcmp(cdigest, sdigest, sizeof(sdigest)) != 0) + return fail(conn, pool, "Password incorrect"); + + *success = TRUE; + return svn_ra_svn_write_tuple(conn, pool, "w()", "success"); +} + +svn_error_t *svn_ra_svn__cram_client(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *user, const char *password, + const char **message) +{ + const char *status, *str, *reply; + unsigned char digest[APR_MD5_DIGESTSIZE]; + char hex[2 * APR_MD5_DIGESTSIZE + 1]; + + /* Read the server challenge. */ + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &status, &str)); + if (strcmp(status, "failure") == 0 && str) + { + *message = str; + return SVN_NO_ERROR; + } + else if (strcmp(status, "step") != 0 || !str) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Unexpected server response to authentication")); + + /* Write our response. */ + compute_digest(digest, str, password); + hex_encode(hex, digest); + hex[sizeof(hex) - 1] = '\0'; + reply = apr_psprintf(pool, "%s %s", user, hex); + SVN_ERR(svn_ra_svn_write_cstring(conn, pool, reply)); + + /* Read the success or failure response from the server. */ + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &status, &str)); + if (strcmp(status, "failure") == 0 && str) + { + *message = str; + return SVN_NO_ERROR; + } + else if (strcmp(status, "success") != 0 || str) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Unexpected server response to authentication")); + + *message = NULL; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_ra_svn/cyrus_auth.c b/subversion/libsvn_ra_svn/cyrus_auth.c new file mode 100644 index 0000000..96e3935 --- /dev/null +++ b/subversion/libsvn_ra_svn/cyrus_auth.c @@ -0,0 +1,941 @@ +/* + * cyrus_auth.c : functions for Cyrus SASL-based authentication + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_private_config.h" +#ifdef SVN_HAVE_SASL + +#define APR_WANT_STRFUNC +#include <apr_want.h> +#include <apr_general.h> +#include <apr_strings.h> +#include <apr_atomic.h> +#include <apr_thread_mutex.h> +#include <apr_version.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_ra.h" +#include "svn_ra_svn.h" +#include "svn_base64.h" + +#include "private/svn_atomic.h" +#include "private/ra_svn_sasl.h" + +#include "ra_svn.h" + +/* Note: In addition to being used via svn_atomic__init_once to control + * initialization of the SASL code this will also be referenced in + * the various functions that work with sasl mutexes to determine + * if the sasl pool has been destroyed. This should be safe, since + * it is only set back to zero in the sasl pool's cleanups, which + * only happens during apr_terminate, which we assume is occurring + * in atexit processing, at which point we are already running in + * single threaded mode. + */ +volatile svn_atomic_t svn_ra_svn__sasl_status = 0; + +/* Initialized by svn_ra_svn__sasl_common_init(). */ +static volatile svn_atomic_t sasl_ctx_count; + +static apr_pool_t *sasl_pool = NULL; + + +/* Pool cleanup called when sasl_pool is destroyed. */ +static apr_status_t sasl_done_cb(void *data) +{ + /* Reset svn_ra_svn__sasl_status, in case the client calls + apr_initialize()/apr_terminate() more than once. */ + svn_ra_svn__sasl_status = 0; + if (svn_atomic_dec(&sasl_ctx_count) == 0) + sasl_done(); + return APR_SUCCESS; +} + +#if APR_HAS_THREADS +/* Cyrus SASL is thread-safe only if we supply it with mutex functions + * (with sasl_set_mutex()). To make this work with APR, we need to use the + * global sasl_pool for the mutex allocations. Freeing a mutex actually + * returns it to a global array. We allocate mutexes from this + * array if it is non-empty, or directly from the pool otherwise. + * We also need a mutex to serialize accesses to the array itself. + */ + +/* An array of allocated, but unused, apr_thread_mutex_t's. */ +static apr_array_header_t *free_mutexes = NULL; + +/* A mutex to serialize access to the array. */ +static apr_thread_mutex_t *array_mutex = NULL; + +/* Callbacks we pass to sasl_set_mutex(). */ + +static void *sasl_mutex_alloc_cb(void) +{ + apr_thread_mutex_t *mutex; + apr_status_t apr_err; + + if (!svn_ra_svn__sasl_status) + return NULL; + + apr_err = apr_thread_mutex_lock(array_mutex); + if (apr_err != APR_SUCCESS) + return NULL; + + if (apr_is_empty_array(free_mutexes)) + { + apr_err = apr_thread_mutex_create(&mutex, + APR_THREAD_MUTEX_DEFAULT, + sasl_pool); + if (apr_err != APR_SUCCESS) + mutex = NULL; + } + else + mutex = *((apr_thread_mutex_t**)apr_array_pop(free_mutexes)); + + apr_err = apr_thread_mutex_unlock(array_mutex); + if (apr_err != APR_SUCCESS) + return NULL; + + return mutex; +} + +static int sasl_mutex_lock_cb(void *mutex) +{ + if (!svn_ra_svn__sasl_status) + return 0; + return (apr_thread_mutex_lock(mutex) == APR_SUCCESS) ? 0 : -1; +} + +static int sasl_mutex_unlock_cb(void *mutex) +{ + if (!svn_ra_svn__sasl_status) + return 0; + return (apr_thread_mutex_unlock(mutex) == APR_SUCCESS) ? 0 : -1; +} + +static void sasl_mutex_free_cb(void *mutex) +{ + if (svn_ra_svn__sasl_status) + { + apr_status_t apr_err = apr_thread_mutex_lock(array_mutex); + if (apr_err == APR_SUCCESS) + { + APR_ARRAY_PUSH(free_mutexes, apr_thread_mutex_t*) = mutex; + apr_thread_mutex_unlock(array_mutex); + } + } +} +#endif /* APR_HAS_THREADS */ + +apr_status_t svn_ra_svn__sasl_common_init(apr_pool_t *pool) +{ + apr_status_t apr_err = APR_SUCCESS; + + sasl_pool = svn_pool_create(pool); + sasl_ctx_count = 1; + apr_pool_cleanup_register(sasl_pool, NULL, sasl_done_cb, + apr_pool_cleanup_null); +#if APR_HAS_THREADS + sasl_set_mutex(sasl_mutex_alloc_cb, + sasl_mutex_lock_cb, + sasl_mutex_unlock_cb, + sasl_mutex_free_cb); + free_mutexes = apr_array_make(sasl_pool, 0, sizeof(apr_thread_mutex_t *)); + apr_err = apr_thread_mutex_create(&array_mutex, + APR_THREAD_MUTEX_DEFAULT, + sasl_pool); +#endif /* APR_HAS_THREADS */ + return apr_err; +} + +/* We are going to look at errno when we get SASL_FAIL but we don't + know for sure whether SASL always sets errno. Clearing errno + before calling SASL functions helps in cases where SASL does + nothing to set errno. */ +#ifdef apr_set_os_error +#define clear_sasl_errno() apr_set_os_error(APR_SUCCESS) +#else +#define clear_sasl_errno() (void)0 +#endif + +/* Sometimes SASL returns SASL_FAIL as RESULT and sets errno. + * SASL_FAIL translates to "generic error" which is quite unhelpful. + * Try to append a more informative error message based on errno so + * should be called before doing anything that may change errno. */ +static const char * +get_sasl_errno_msg(int result, apr_pool_t *result_pool) +{ +#ifdef apr_get_os_error + char buf[1024]; + + if (result == SASL_FAIL && apr_get_os_error() != 0) + return apr_psprintf(result_pool, ": %s", + svn_strerror(apr_get_os_error(), buf, sizeof(buf))); +#endif + return ""; +} + +/* Wrap an error message from SASL with a prefix that allows users + * to tell that the error message came from SASL. Queries errno and + * so should be called before doing anything that may change errno. */ +static const char * +get_sasl_error(sasl_conn_t *sasl_ctx, int result, apr_pool_t *result_pool) +{ + const char *sasl_errno_msg = get_sasl_errno_msg(result, result_pool); + + return apr_psprintf(result_pool, + _("SASL authentication error: %s%s"), + sasl_errdetail(sasl_ctx), sasl_errno_msg); +} + +static svn_error_t *sasl_init_cb(void *baton, apr_pool_t *pool) +{ + int result; + + if (svn_ra_svn__sasl_common_init(pool) != APR_SUCCESS) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Could not initialize the SASL library")); + clear_sasl_errno(); + result = sasl_client_init(NULL); + if (result != SASL_OK) + { + const char *sasl_errno_msg = get_sasl_errno_msg(result, pool); + + return svn_error_createf + (SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Could not initialized the SASL library: %s%s"), + sasl_errstring(result, NULL, NULL), + sasl_errno_msg); + } + + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn__sasl_init(void) +{ + SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status, + sasl_init_cb, NULL, NULL)); + return SVN_NO_ERROR; +} + +static apr_status_t sasl_dispose_cb(void *data) +{ + sasl_conn_t *sasl_ctx = data; + sasl_dispose(&sasl_ctx); + if (svn_atomic_dec(&sasl_ctx_count) == 0) + sasl_done(); + return APR_SUCCESS; +} + +void svn_ra_svn__default_secprops(sasl_security_properties_t *secprops) +{ + /* The minimum and maximum security strength factors that the chosen + SASL mechanism should provide. 0 means 'no encryption', 256 means + '256-bit encryption', which is about the best that any SASL + mechanism can provide. Using these values effectively means 'use + whatever encryption the other side wants'. Note that SASL will try + to use better encryption whenever possible, so if both the server and + the client use these values the highest possible encryption strength + will be used. */ + secprops->min_ssf = 0; + secprops->max_ssf = 256; + + /* Set maxbufsize to the maximum amount of data we can read at any one time. + This value needs to be commmunicated to the peer if a security layer + is negotiated. */ + secprops->maxbufsize = SVN_RA_SVN__READBUF_SIZE; + + secprops->security_flags = 0; + secprops->property_names = secprops->property_values = NULL; +} + +/* A baton type used by the SASL username and password callbacks. */ +typedef struct cred_baton { + svn_auth_baton_t *auth_baton; + svn_auth_iterstate_t *iterstate; + const char *realmstring; + + /* Unfortunately SASL uses two separate callbacks for the username and + password, but we must fetch both of them at the same time. So we cache + their values in the baton, set them to NULL individually when SASL + demands them, and fetch the next pair when both are NULL. */ + const char *username; + const char *password; + + /* Any errors we receive from svn_auth_{first,next}_credentials + are saved here. */ + svn_error_t *err; + + /* This flag is set when we run out of credential providers. */ + svn_boolean_t no_more_creds; + + /* Were the auth callbacks ever called? */ + svn_boolean_t was_used; + + apr_pool_t *pool; +} cred_baton_t; + +/* Call svn_auth_{first,next}_credentials. If successful, set BATON->username + and BATON->password to the new username and password and return TRUE, + otherwise return FALSE. If there are no more credentials, set + BATON->no_more_creds to TRUE. Any errors are saved in BATON->err. */ +static svn_boolean_t +get_credentials(cred_baton_t *baton) +{ + void *creds; + + if (baton->iterstate) + baton->err = svn_auth_next_credentials(&creds, baton->iterstate, + baton->pool); + else + baton->err = svn_auth_first_credentials(&creds, &baton->iterstate, + SVN_AUTH_CRED_SIMPLE, + baton->realmstring, + baton->auth_baton, baton->pool); + if (baton->err) + return FALSE; + + if (! creds) + { + baton->no_more_creds = TRUE; + return FALSE; + } + + baton->username = ((svn_auth_cred_simple_t *)creds)->username; + baton->password = ((svn_auth_cred_simple_t *)creds)->password; + baton->was_used = TRUE; + + return TRUE; +} + +/* The username callback. Implements the sasl_getsimple_t interface. */ +static int +get_username_cb(void *b, int id, const char **username, size_t *len) +{ + cred_baton_t *baton = b; + + if (baton->username || get_credentials(baton)) + { + *username = baton->username; + if (len) + *len = strlen(baton->username); + baton->username = NULL; + + return SASL_OK; + } + + return SASL_FAIL; +} + +/* The password callback. Implements the sasl_getsecret_t interface. */ +static int +get_password_cb(sasl_conn_t *conn, void *b, int id, sasl_secret_t **psecret) +{ + cred_baton_t *baton = b; + + if (baton->password || get_credentials(baton)) + { + sasl_secret_t *secret; + size_t len = strlen(baton->password); + + /* sasl_secret_t is a struct with a variable-sized array as a final + member, which means we need to allocate len-1 supplementary bytes + (one byte is part of sasl_secret_t, and we don't need a NULL + terminator). */ + secret = apr_palloc(baton->pool, sizeof(*secret) + len - 1); + secret->len = len; + memcpy(secret->data, baton->password, len); + baton->password = NULL; + *psecret = secret; + + return SASL_OK; + } + + return SASL_FAIL; +} + +/* Create a new SASL context. */ +static svn_error_t *new_sasl_ctx(sasl_conn_t **sasl_ctx, + svn_boolean_t is_tunneled, + const char *hostname, + const char *local_addrport, + const char *remote_addrport, + sasl_callback_t *callbacks, + apr_pool_t *pool) +{ + sasl_security_properties_t secprops; + int result; + + clear_sasl_errno(); + result = sasl_client_new(SVN_RA_SVN_SASL_NAME, + hostname, local_addrport, remote_addrport, + callbacks, SASL_SUCCESS_DATA, + sasl_ctx); + if (result != SASL_OK) + { + const char *sasl_errno_msg = get_sasl_errno_msg(result, pool); + + return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Could not create SASL context: %s%s"), + sasl_errstring(result, NULL, NULL), + sasl_errno_msg); + } + svn_atomic_inc(&sasl_ctx_count); + apr_pool_cleanup_register(pool, *sasl_ctx, sasl_dispose_cb, + apr_pool_cleanup_null); + + if (is_tunneled) + { + /* We need to tell SASL that this connection is tunneled, + otherwise it will ignore EXTERNAL. The third parameter + should be the username, but since SASL doesn't seem + to use it on the client side, any non-empty string will do. */ + clear_sasl_errno(); + result = sasl_setprop(*sasl_ctx, + SASL_AUTH_EXTERNAL, " "); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(*sasl_ctx, result, pool)); + } + + /* Set security properties. */ + svn_ra_svn__default_secprops(&secprops); + sasl_setprop(*sasl_ctx, SASL_SEC_PROPS, &secprops); + + return SVN_NO_ERROR; +} + +/* Perform an authentication exchange */ +static svn_error_t *try_auth(svn_ra_svn__session_baton_t *sess, + sasl_conn_t *sasl_ctx, + svn_boolean_t *success, + const char **last_err, + const char *mechstring, + apr_pool_t *pool) +{ + sasl_interact_t *client_interact = NULL; + const char *out, *mech, *status = NULL; + const svn_string_t *arg = NULL, *in; + int result; + unsigned int outlen; + svn_boolean_t again; + + do + { + again = FALSE; + clear_sasl_errno(); + result = sasl_client_start(sasl_ctx, + mechstring, + &client_interact, + &out, + &outlen, + &mech); + switch (result) + { + case SASL_OK: + case SASL_CONTINUE: + /* Success. */ + break; + case SASL_NOMECH: + return svn_error_create(SVN_ERR_RA_SVN_NO_MECHANISMS, NULL, NULL); + case SASL_BADPARAM: + case SASL_NOMEM: + /* Fatal error. Fail the authentication. */ + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_ctx, result, pool)); + default: + /* For anything else, delete the mech from the list + and try again. */ + { + const char *pmech = strstr(mechstring, mech); + const char *head = apr_pstrndup(pool, mechstring, + pmech - mechstring); + const char *tail = pmech + strlen(mech); + + mechstring = apr_pstrcat(pool, head, tail, (char *)NULL); + again = TRUE; + } + } + } + while (again); + + /* Prepare the initial authentication token. */ + if (outlen > 0 || strcmp(mech, "EXTERNAL") == 0) + arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool), + TRUE, pool); + + /* Send the initial client response */ + SVN_ERR(svn_ra_svn__auth_response(sess->conn, pool, mech, + arg ? arg->data : NULL)); + + while (result == SASL_CONTINUE) + { + /* Read the server response */ + SVN_ERR(svn_ra_svn_read_tuple(sess->conn, pool, "w(?s)", + &status, &in)); + + if (strcmp(status, "failure") == 0) + { + /* Authentication failed. Use the next set of credentials */ + *success = FALSE; + /* Remember the message sent by the server because we'll want to + return a meaningful error if we run out of auth providers. */ + *last_err = in ? in->data : ""; + return SVN_NO_ERROR; + } + + if ((strcmp(status, "success") != 0 && strcmp(status, "step") != 0) + || in == NULL) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Unexpected server response" + " to authentication")); + + /* If the mech is CRAM-MD5 we don't base64-decode the server response. */ + if (strcmp(mech, "CRAM-MD5") != 0) + in = svn_base64_decode_string(in, pool); + + clear_sasl_errno(); + result = sasl_client_step(sasl_ctx, + in->data, + in->len, + &client_interact, + &out, /* Filled in by SASL. */ + &outlen); + + if (result != SASL_OK && result != SASL_CONTINUE) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_ctx, result, pool)); + + /* If the server thinks we're done, then don't send any response. */ + if (strcmp(status, "success") == 0) + break; + + if (outlen > 0) + { + arg = svn_string_ncreate(out, outlen, pool); + /* Write our response. */ + /* For CRAM-MD5, we don't use base64-encoding. */ + if (strcmp(mech, "CRAM-MD5") != 0) + arg = svn_base64_encode_string2(arg, TRUE, pool); + SVN_ERR(svn_ra_svn_write_cstring(sess->conn, pool, arg->data)); + } + else + { + SVN_ERR(svn_ra_svn_write_cstring(sess->conn, pool, "")); + } + } + + if (!status || strcmp(status, "step") == 0) + { + /* This is a client-send-last mech. Read the last server response. */ + SVN_ERR(svn_ra_svn_read_tuple(sess->conn, pool, "w(?s)", + &status, &in)); + + if (strcmp(status, "failure") == 0) + { + *success = FALSE; + *last_err = in ? in->data : ""; + } + else if (strcmp(status, "success") == 0) + { + /* We're done */ + *success = TRUE; + } + else + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Unexpected server response" + " to authentication")); + } + else + *success = TRUE; + return SVN_NO_ERROR; +} + +/* Baton for a SASL encrypted svn_ra_svn__stream_t. */ +typedef struct sasl_baton { + svn_ra_svn__stream_t *stream; /* Inherited stream. */ + sasl_conn_t *ctx; /* The SASL context for this connection. */ + unsigned int maxsize; /* The maximum amount of data we can encode. */ + const char *read_buf; /* The buffer returned by sasl_decode. */ + unsigned int read_len; /* Its current length. */ + const char *write_buf; /* The buffer returned by sasl_encode. */ + unsigned int write_len; /* Its length. */ + apr_pool_t *scratch_pool; +} sasl_baton_t; + +/* Functions to implement a SASL encrypted svn_ra_svn__stream_t. */ + +/* Implements svn_read_fn_t. */ +static svn_error_t *sasl_read_cb(void *baton, char *buffer, apr_size_t *len) +{ + sasl_baton_t *sasl_baton = baton; + int result; + /* A copy of *len, used by the wrapped stream. */ + apr_size_t len2 = *len; + + /* sasl_decode might need more data than a single read can provide, + hence the need to put a loop around the decoding. */ + while (! sasl_baton->read_buf || sasl_baton->read_len == 0) + { + SVN_ERR(svn_ra_svn__stream_read(sasl_baton->stream, buffer, &len2)); + if (len2 == 0) + { + *len = 0; + return SVN_NO_ERROR; + } + clear_sasl_errno(); + result = sasl_decode(sasl_baton->ctx, buffer, len2, + &sasl_baton->read_buf, + &sasl_baton->read_len); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_baton->ctx, result, + sasl_baton->scratch_pool)); + } + + /* The buffer returned by sasl_decode might be larger than what the + caller wants. If this is the case, we only copy back *len bytes now + (the rest will be returned by subsequent calls to this function). + If not, we just copy back the whole thing. */ + if (*len >= sasl_baton->read_len) + { + memcpy(buffer, sasl_baton->read_buf, sasl_baton->read_len); + *len = sasl_baton->read_len; + sasl_baton->read_buf = NULL; + sasl_baton->read_len = 0; + } + else + { + memcpy(buffer, sasl_baton->read_buf, *len); + sasl_baton->read_len -= *len; + sasl_baton->read_buf += *len; + } + + return SVN_NO_ERROR; +} + +/* Implements svn_write_fn_t. */ +static svn_error_t * +sasl_write_cb(void *baton, const char *buffer, apr_size_t *len) +{ + sasl_baton_t *sasl_baton = baton; + int result; + + if (! sasl_baton->write_buf || sasl_baton->write_len == 0) + { + /* Make sure we don't write too much. */ + *len = (*len > sasl_baton->maxsize) ? sasl_baton->maxsize : *len; + clear_sasl_errno(); + result = sasl_encode(sasl_baton->ctx, buffer, *len, + &sasl_baton->write_buf, + &sasl_baton->write_len); + + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_baton->ctx, result, + sasl_baton->scratch_pool)); + } + + do + { + apr_size_t tmplen = sasl_baton->write_len; + SVN_ERR(svn_ra_svn__stream_write(sasl_baton->stream, + sasl_baton->write_buf, + &tmplen)); + if (tmplen == 0) + { + /* The output buffer and its length will be preserved in sasl_baton + and will be written out during the next call to this function + (which will have the same arguments). */ + *len = 0; + return SVN_NO_ERROR; + } + sasl_baton->write_len -= (unsigned int) tmplen; + sasl_baton->write_buf += tmplen; + } + while (sasl_baton->write_len > 0); + + sasl_baton->write_buf = NULL; + sasl_baton->write_len = 0; + + return SVN_NO_ERROR; +} + +/* Implements ra_svn_timeout_fn_t. */ +static void sasl_timeout_cb(void *baton, apr_interval_time_t interval) +{ + sasl_baton_t *sasl_baton = baton; + svn_ra_svn__stream_timeout(sasl_baton->stream, interval); +} + +/* Implements ra_svn_pending_fn_t. */ +static svn_boolean_t sasl_pending_cb(void *baton) +{ + sasl_baton_t *sasl_baton = baton; + return svn_ra_svn__stream_pending(sasl_baton->stream); +} + +svn_error_t *svn_ra_svn__enable_sasl_encryption(svn_ra_svn_conn_t *conn, + sasl_conn_t *sasl_ctx, + apr_pool_t *pool) +{ + const sasl_ssf_t *ssfp; + + if (! conn->encrypted) + { + int result; + + /* Get the strength of the security layer. */ + clear_sasl_errno(); + result = sasl_getprop(sasl_ctx, SASL_SSF, (void*) &ssfp); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_ctx, result, pool)); + + if (*ssfp > 0) + { + sasl_baton_t *sasl_baton; + const void *maxsize; + + /* Flush the connection, as we're about to replace its stream. */ + SVN_ERR(svn_ra_svn_flush(conn, pool)); + + /* Create and initialize the stream baton. */ + sasl_baton = apr_pcalloc(conn->pool, sizeof(*sasl_baton)); + sasl_baton->ctx = sasl_ctx; + sasl_baton->scratch_pool = conn->pool; + + /* Find out the maximum input size for sasl_encode. */ + clear_sasl_errno(); + result = sasl_getprop(sasl_ctx, SASL_MAXOUTBUF, &maxsize); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_ctx, result, pool)); + sasl_baton->maxsize = *((const unsigned int *) maxsize); + + /* If there is any data left in the read buffer at this point, + we need to decrypt it. */ + if (conn->read_end > conn->read_ptr) + { + clear_sasl_errno(); + result = sasl_decode(sasl_ctx, conn->read_ptr, + conn->read_end - conn->read_ptr, + &sasl_baton->read_buf, + &sasl_baton->read_len); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_ctx, result, pool)); + conn->read_end = conn->read_ptr; + } + + /* Wrap the existing stream. */ + sasl_baton->stream = conn->stream; + + conn->stream = svn_ra_svn__stream_create(sasl_baton, sasl_read_cb, + sasl_write_cb, + sasl_timeout_cb, + sasl_pending_cb, conn->pool); + /* Yay, we have a security layer! */ + conn->encrypted = TRUE; + } + } + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn__get_addresses(const char **local_addrport, + const char **remote_addrport, + svn_ra_svn_conn_t *conn, + apr_pool_t *pool) +{ + if (conn->sock) + { + apr_status_t apr_err; + apr_sockaddr_t *local_sa, *remote_sa; + char *local_addr, *remote_addr; + + apr_err = apr_socket_addr_get(&local_sa, APR_LOCAL, conn->sock); + if (apr_err) + return svn_error_wrap_apr(apr_err, NULL); + + apr_err = apr_socket_addr_get(&remote_sa, APR_REMOTE, conn->sock); + if (apr_err) + return svn_error_wrap_apr(apr_err, NULL); + + apr_err = apr_sockaddr_ip_get(&local_addr, local_sa); + if (apr_err) + return svn_error_wrap_apr(apr_err, NULL); + + apr_err = apr_sockaddr_ip_get(&remote_addr, remote_sa); + if (apr_err) + return svn_error_wrap_apr(apr_err, NULL); + + /* Format the IP address and port number like this: a.b.c.d;port */ + *local_addrport = apr_pstrcat(pool, local_addr, ";", + apr_itoa(pool, (int)local_sa->port), + (char *)NULL); + *remote_addrport = apr_pstrcat(pool, remote_addr, ";", + apr_itoa(pool, (int)remote_sa->port), + (char *)NULL); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess, + const apr_array_header_t *mechlist, + const char *realm, apr_pool_t *pool) +{ + apr_pool_t *subpool; + sasl_conn_t *sasl_ctx; + const char *mechstring = "", *last_err = "", *realmstring; + const char *local_addrport = NULL, *remote_addrport = NULL; + svn_boolean_t success; + sasl_callback_t *callbacks; + cred_baton_t cred_baton; + int i; + + if (!sess->is_tunneled) + { + SVN_ERR(svn_ra_svn__get_addresses(&local_addrport, &remote_addrport, + sess->conn, pool)); + } + + /* Prefer EXTERNAL, then ANONYMOUS, then let SASL decide. */ + if (svn_ra_svn__find_mech(mechlist, "EXTERNAL")) + mechstring = "EXTERNAL"; + else if (svn_ra_svn__find_mech(mechlist, "ANONYMOUS")) + mechstring = "ANONYMOUS"; + else + { + /* Create a string containing the list of mechanisms, separated by spaces. */ + for (i = 0; i < mechlist->nelts; i++) + { + svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(mechlist, i, svn_ra_svn_item_t); + mechstring = apr_pstrcat(pool, + mechstring, + i == 0 ? "" : " ", + elt->u.word, (char *)NULL); + } + } + + realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm); + + /* Initialize the credential baton. */ + memset(&cred_baton, 0, sizeof(cred_baton)); + cred_baton.auth_baton = sess->callbacks->auth_baton; + cred_baton.realmstring = realmstring; + cred_baton.pool = pool; + + /* Reserve space for 3 callbacks (for the username, password and the + array terminator). These structures must persist until the + disposal of the SASL context at pool cleanup, however the + callback functions will not be invoked outside this function so + other structures can have a shorter lifetime. */ + callbacks = apr_palloc(sess->conn->pool, sizeof(*callbacks) * 3); + + /* Initialize the callbacks array. */ + + /* The username callback. */ + callbacks[0].id = SASL_CB_AUTHNAME; + callbacks[0].proc = get_username_cb; + callbacks[0].context = &cred_baton; + + /* The password callback. */ + callbacks[1].id = SASL_CB_PASS; + callbacks[1].proc = get_password_cb; + callbacks[1].context = &cred_baton; + + /* Mark the end of the array. */ + callbacks[2].id = SASL_CB_LIST_END; + callbacks[2].proc = NULL; + callbacks[2].context = NULL; + + subpool = svn_pool_create(pool); + do + { + svn_error_t *err; + + /* If last_err was set to a non-empty string, it needs to be duplicated + to the parent pool before the subpool is cleared. */ + if (*last_err) + last_err = apr_pstrdup(pool, last_err); + svn_pool_clear(subpool); + + SVN_ERR(new_sasl_ctx(&sasl_ctx, sess->is_tunneled, + sess->hostname, local_addrport, remote_addrport, + callbacks, sess->conn->pool)); + err = try_auth(sess, sasl_ctx, &success, &last_err, mechstring, + subpool); + + /* If we encountered an error while fetching credentials, that error + has priority. */ + if (cred_baton.err) + { + svn_error_clear(err); + return cred_baton.err; + } + if (cred_baton.no_more_creds + || (! success && ! err && ! cred_baton.was_used)) + { + svn_error_clear(err); + /* If we ran out of authentication providers, or if we got a server + error and our callbacks were never called, there's no point in + retrying authentication. Return the last error sent by the + server. */ + if (*last_err) + return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Authentication error from server: %s"), + last_err); + /* Hmm, we don't have a server error. Return a generic error. */ + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Can't get username or password")); + } + if (err) + { + if (err->apr_err == SVN_ERR_RA_SVN_NO_MECHANISMS) + { + svn_error_clear(err); + + /* We could not find a supported mechanism in the list sent by the + server. In many cases this happens because the client is missing + the CRAM-MD5 or ANONYMOUS plugins, in which case we can simply use + the built-in implementation. In all other cases this call will be + useless, but hey, at least we'll get consistent error messages. */ + return svn_ra_svn__do_internal_auth(sess, mechlist, + realm, pool); + } + return err; + } + } + while (!success); + svn_pool_destroy(subpool); + + SVN_ERR(svn_ra_svn__enable_sasl_encryption(sess->conn, sasl_ctx, pool)); + + SVN_ERR(svn_auth_save_credentials(cred_baton.iterstate, pool)); + + return SVN_NO_ERROR; +} + +#endif /* SVN_HAVE_SASL */ diff --git a/subversion/libsvn_ra_svn/editorp.c b/subversion/libsvn_ra_svn/editorp.c new file mode 100644 index 0000000..c7fc69b --- /dev/null +++ b/subversion/libsvn_ra_svn/editorp.c @@ -0,0 +1,1005 @@ +/* + * editorp.c : Driving and consuming an editor across an svn connection + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC +#include <apr_want.h> +#include <apr_general.h> +#include <apr_strings.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_delta.h" +#include "svn_dirent_uri.h" +#include "svn_ra_svn.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_private_config.h" + +#include "private/svn_fspath.h" + +#include "ra_svn.h" + +/* + * Both the client and server in the svn protocol need to drive and + * consume editors. For a commit, the client drives and the server + * consumes; for an update/switch/status/diff, the server drives and + * the client consumes. This file provides a generic framework for + * marshalling and unmarshalling editor operations over an svn + * connection; both ends are useful for both server and client. + */ + +typedef struct ra_svn_edit_baton_t { + svn_ra_svn_conn_t *conn; + svn_ra_svn_edit_callback callback; /* Called on successful completion. */ + void *callback_baton; + int next_token; + svn_boolean_t got_status; +} ra_svn_edit_baton_t; + +/* Works for both directories and files. */ +typedef struct ra_svn_baton_t { + svn_ra_svn_conn_t *conn; + apr_pool_t *pool; + ra_svn_edit_baton_t *eb; + const char *token; +} ra_svn_baton_t; + +typedef struct ra_svn_driver_state_t { + const svn_delta_editor_t *editor; + void *edit_baton; + apr_hash_t *tokens; + svn_boolean_t *aborted; + svn_boolean_t done; + apr_pool_t *pool; + apr_pool_t *file_pool; + int file_refs; + svn_boolean_t for_replay; +} ra_svn_driver_state_t; + +/* Works for both directories and files; however, the pool handling is + different for files. To save space during commits (where file + batons generally last until the end of the commit), token entries + for files are all created in a single reference-counted pool (the + file_pool member of the driver state structure), which is cleared + at close_file time when the reference count hits zero. So the pool + field in this structure is vestigial for files, and we use it for a + different purpose instead: at apply-textdelta time, we set it to a + subpool of the file pool, which is destroyed in textdelta-end. */ +typedef struct ra_svn_token_entry_t { + const char *token; + void *baton; + svn_boolean_t is_file; + svn_stream_t *dstream; /* svndiff stream for apply_textdelta */ + apr_pool_t *pool; +} ra_svn_token_entry_t; + +/* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */ + +static const char *make_token(char type, ra_svn_edit_baton_t *eb, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "%c%d", type, eb->next_token++); +} + +static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + ra_svn_edit_baton_t *eb, + const char *token) +{ + ra_svn_baton_t *b; + + b = apr_palloc(pool, sizeof(*b)); + b->conn = conn; + b->pool = pool; + b->eb = eb; + b->token = token; + return b; +} + +/* Check for an early error status report from the consumer. If we + * get one, abort the edit and return the error. */ +static svn_error_t *check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool) +{ + SVN_ERR_ASSERT(!eb->got_status); + if (svn_ra_svn__input_waiting(eb->conn, pool)) + { + eb->got_status = TRUE; + SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "abort-edit", "")); + SVN_ERR(svn_ra_svn_read_cmd_response(eb->conn, pool, "")); + /* We shouldn't get here if the consumer is doing its job. */ + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Successful edit status returned too soon")); + } + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev, + apr_pool_t *pool) +{ + ra_svn_edit_baton_t *eb = edit_baton; + + SVN_ERR(check_for_error(eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "target-rev", "r", rev)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev, + apr_pool_t *pool, void **root_baton) +{ + ra_svn_edit_baton_t *eb = edit_baton; + const char *token = make_token('d', eb, pool); + + SVN_ERR(check_for_error(eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "open-root", "(?r)c", rev, + token)); + *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev, + void *parent_baton, apr_pool_t *pool) +{ + ra_svn_baton_t *b = parent_baton; + + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "delete-entry", "c(?r)c", + path, rev, b->token)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton, + const char *copy_path, + svn_revnum_t copy_rev, + apr_pool_t *pool, void **child_baton) +{ + ra_svn_baton_t *b = parent_baton; + const char *token = make_token('d', b->eb, pool); + + SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev)) + || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev))); + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "add-dir", "ccc(?cr)", path, + b->token, token, copy_path, copy_rev)); + *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton, + svn_revnum_t rev, apr_pool_t *pool, + void **child_baton) +{ + ra_svn_baton_t *b = parent_baton; + const char *token = make_token('d', b->eb, pool); + + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "open-dir", "ccc(?r)", + path, b->token, token, rev)); + *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + ra_svn_baton_t *b = dir_baton; + + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "change-dir-prop", "cc(?s)", + b->token, name, value)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool) +{ + ra_svn_baton_t *b = dir_baton; + + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "close-dir", "c", b->token)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_absent_dir(const char *path, + void *parent_baton, apr_pool_t *pool) +{ + ra_svn_baton_t *b = parent_baton; + + /* Avoid sending an unknown command if the other end doesn't support + absent-dir. */ + if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES)) + return SVN_NO_ERROR; + + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "absent-dir", "cc", path, + b->token)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_add_file(const char *path, + void *parent_baton, + const char *copy_path, + svn_revnum_t copy_rev, + apr_pool_t *pool, + void **file_baton) +{ + ra_svn_baton_t *b = parent_baton; + const char *token = make_token('c', b->eb, pool); + + SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev)) + || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev))); + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "add-file", "ccc(?cr)", path, + b->token, token, copy_path, copy_rev)); + *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_open_file(const char *path, + void *parent_baton, + svn_revnum_t rev, + apr_pool_t *pool, + void **file_baton) +{ + ra_svn_baton_t *b = parent_baton; + const char *token = make_token('c', b->eb, pool); + + SVN_ERR(check_for_error(b->eb, b->pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "open-file", "ccc(?r)", + path, b->token, token, rev)); + *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data, + apr_size_t *len) +{ + ra_svn_baton_t *b = baton; + svn_string_t str; + + SVN_ERR(check_for_error(b->eb, b->pool)); + str.data = data; + str.len = *len; + return svn_ra_svn_write_cmd(b->conn, b->pool, "textdelta-chunk", "cs", + b->token, &str); +} + +static svn_error_t *ra_svn_svndiff_close_handler(void *baton) +{ + ra_svn_baton_t *b = baton; + + SVN_ERR(check_for_error(b->eb, b->pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "textdelta-end", "c", + b->token)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_apply_textdelta(void *file_baton, + const char *base_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *wh, + void **wh_baton) +{ + ra_svn_baton_t *b = file_baton; + svn_stream_t *diff_stream; + + /* Tell the other side we're starting a text delta. */ + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "apply-textdelta", "c(?c)", + b->token, base_checksum)); + + /* Transform the window stream to an svndiff stream. Reuse the + * file baton for the stream handler, since it has all the + * needed information. */ + diff_stream = svn_stream_create(b, pool); + svn_stream_set_write(diff_stream, ra_svn_svndiff_handler); + svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler); + + /* If the connection does not support SVNDIFF1 or if we don't want to use + * compression, use the non-compressing "version 0" implementation */ + if ( svn_ra_svn_compression_level(b->conn) > 0 + && svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_SVNDIFF1)) + svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 1, + b->conn->compression_level, pool); + else + svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 0, + b->conn->compression_level, pool); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + ra_svn_baton_t *b = file_baton; + + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "change-file-prop", "cc(?s)", + b->token, name, value)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_close_file(void *file_baton, + const char *text_checksum, + apr_pool_t *pool) +{ + ra_svn_baton_t *b = file_baton; + + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "close-file", "c(?c)", + b->token, text_checksum)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_absent_file(const char *path, + void *parent_baton, apr_pool_t *pool) +{ + ra_svn_baton_t *b = parent_baton; + + /* Avoid sending an unknown command if the other end doesn't support + absent-file. */ + if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES)) + return SVN_NO_ERROR; + + SVN_ERR(check_for_error(b->eb, pool)); + SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "absent-file", "cc", path, + b->token)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool) +{ + ra_svn_edit_baton_t *eb = edit_baton; + svn_error_t *err; + + SVN_ERR_ASSERT(!eb->got_status); + eb->got_status = TRUE; + SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "close-edit", "")); + err = svn_ra_svn_read_cmd_response(eb->conn, pool, ""); + if (err) + { + svn_error_clear(svn_ra_svn_write_cmd(eb->conn, pool, "abort-edit", "")); + return err; + } + if (eb->callback) + SVN_ERR(eb->callback(eb->callback_baton)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool) +{ + ra_svn_edit_baton_t *eb = edit_baton; + + if (eb->got_status) + return SVN_NO_ERROR; + SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "abort-edit", "")); + SVN_ERR(svn_ra_svn_read_cmd_response(eb->conn, pool, "")); + return SVN_NO_ERROR; +} + +void svn_ra_svn_get_editor(const svn_delta_editor_t **editor, + void **edit_baton, svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + svn_ra_svn_edit_callback callback, + void *callback_baton) +{ + svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool); + ra_svn_edit_baton_t *eb; + + eb = apr_palloc(pool, sizeof(*eb)); + eb->conn = conn; + eb->callback = callback; + eb->callback_baton = callback_baton; + eb->next_token = 0; + eb->got_status = FALSE; + + ra_svn_editor->set_target_revision = ra_svn_target_rev; + ra_svn_editor->open_root = ra_svn_open_root; + ra_svn_editor->delete_entry = ra_svn_delete_entry; + ra_svn_editor->add_directory = ra_svn_add_dir; + ra_svn_editor->open_directory = ra_svn_open_dir; + ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop; + ra_svn_editor->close_directory = ra_svn_close_dir; + ra_svn_editor->absent_directory = ra_svn_absent_dir; + ra_svn_editor->add_file = ra_svn_add_file; + ra_svn_editor->open_file = ra_svn_open_file; + ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta; + ra_svn_editor->change_file_prop = ra_svn_change_file_prop; + ra_svn_editor->close_file = ra_svn_close_file; + ra_svn_editor->absent_file = ra_svn_absent_file; + ra_svn_editor->close_edit = ra_svn_close_edit; + ra_svn_editor->abort_edit = ra_svn_abort_edit; + + *editor = ra_svn_editor; + *edit_baton = eb; +} + +/* --- DRIVING AN EDITOR --- */ + +/* Store a token entry. The token string will be copied into pool. */ +static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds, + void *baton, const char *token, + svn_boolean_t is_file, + apr_pool_t *pool) +{ + ra_svn_token_entry_t *entry; + + entry = apr_palloc(pool, sizeof(*entry)); + entry->token = apr_pstrdup(pool, token); + entry->baton = baton; + entry->is_file = is_file; + entry->dstream = NULL; + entry->pool = pool; + apr_hash_set(ds->tokens, entry->token, APR_HASH_KEY_STRING, entry); + return entry; +} + +static svn_error_t *lookup_token(ra_svn_driver_state_t *ds, const char *token, + svn_boolean_t is_file, + ra_svn_token_entry_t **entry) +{ + *entry = apr_hash_get(ds->tokens, token, APR_HASH_KEY_STRING); + if (!*entry || (*entry)->is_file != is_file) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Invalid file or dir token during edit")); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + svn_revnum_t rev; + + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev)); + SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_open_root(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + svn_revnum_t rev; + apr_pool_t *subpool; + const char *token; + void *root_baton; + + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)c", &rev, &token)); + subpool = svn_pool_create(ds->pool); + SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool, + &root_baton)); + store_token(ds, root_baton, token, FALSE, subpool); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *path, *token; + svn_revnum_t rev; + ra_svn_token_entry_t *entry; + + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)c", &path, &rev, &token)); + SVN_ERR(lookup_token(ds, token, FALSE, &entry)); + path = svn_relpath_canonicalize(path, pool); + SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *path, *token, *child_token, *copy_path; + svn_revnum_t copy_rev; + ra_svn_token_entry_t *entry; + apr_pool_t *subpool; + void *child_baton; + + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?cr)", &path, &token, + &child_token, ©_path, ©_rev)); + SVN_ERR(lookup_token(ds, token, FALSE, &entry)); + subpool = svn_pool_create(entry->pool); + path = svn_relpath_canonicalize(path, pool); + + /* Some operations pass COPY_PATH as a full URL (commits, etc.). + Others (replay, e.g.) deliver an fspath. That's ... annoying. */ + if (copy_path) + { + if (svn_path_is_url(copy_path)) + copy_path = svn_uri_canonicalize(copy_path, pool); + else + copy_path = svn_fspath__canonicalize(copy_path, pool); + } + + SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path, + copy_rev, subpool, &child_baton)); + store_token(ds, child_baton, child_token, FALSE, subpool); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *path, *token, *child_token; + svn_revnum_t rev; + ra_svn_token_entry_t *entry; + apr_pool_t *subpool; + void *child_baton; + + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?r)", &path, &token, + &child_token, &rev)); + SVN_ERR(lookup_token(ds, token, FALSE, &entry)); + subpool = svn_pool_create(entry->pool); + path = svn_relpath_canonicalize(path, pool); + SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool, + &child_baton)); + store_token(ds, child_baton, child_token, FALSE, subpool); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *token, *name; + svn_string_t *value; + ra_svn_token_entry_t *entry; + + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cc(?s)", &token, &name, + &value)); + SVN_ERR(lookup_token(ds, token, FALSE, &entry)); + SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value, + entry->pool)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *token; + ra_svn_token_entry_t *entry; + + /* Parse and look up the directory token. */ + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &token)); + SVN_ERR(lookup_token(ds, token, FALSE, &entry)); + + /* Close the directory and destroy the baton. */ + SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool)); + apr_hash_set(ds->tokens, token, APR_HASH_KEY_STRING, NULL); + svn_pool_destroy(entry->pool); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *path; + const char *token; + ra_svn_token_entry_t *entry; + + /* Parse parameters and look up the directory token. */ + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cc", &path, &token)); + SVN_ERR(lookup_token(ds, token, FALSE, &entry)); + + /* Call the editor. */ + SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_add_file(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *path, *token, *file_token, *copy_path; + svn_revnum_t copy_rev; + ra_svn_token_entry_t *entry, *file_entry; + + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?cr)", &path, &token, + &file_token, ©_path, ©_rev)); + SVN_ERR(lookup_token(ds, token, FALSE, &entry)); + ds->file_refs++; + path = svn_relpath_canonicalize(path, pool); + + /* Some operations pass COPY_PATH as a full URL (commits, etc.). + Others (replay, e.g.) deliver an fspath. That's ... annoying. */ + if (copy_path) + { + if (svn_path_is_url(copy_path)) + copy_path = svn_uri_canonicalize(copy_path, pool); + else + copy_path = svn_fspath__canonicalize(copy_path, pool); + } + + file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool); + SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path, copy_rev, + ds->file_pool, &file_entry->baton)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_open_file(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *path, *token, *file_token; + svn_revnum_t rev; + ra_svn_token_entry_t *entry, *file_entry; + + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?r)", &path, &token, + &file_token, &rev)); + SVN_ERR(lookup_token(ds, token, FALSE, &entry)); + ds->file_refs++; + path = svn_relpath_canonicalize(path, pool); + file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool); + SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool, + &file_entry->baton)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *token; + ra_svn_token_entry_t *entry; + svn_txdelta_window_handler_t wh; + void *wh_baton; + char *base_checksum; + + /* Parse arguments and look up the token. */ + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)", + &token, &base_checksum)); + SVN_ERR(lookup_token(ds, token, TRUE, &entry)); + if (entry->dstream) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Apply-textdelta already active")); + entry->pool = svn_pool_create(ds->file_pool); + SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum, + entry->pool, &wh, &wh_baton)); + entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *token; + ra_svn_token_entry_t *entry; + svn_string_t *str; + + /* Parse arguments and look up the token. */ + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cs", &token, &str)); + SVN_ERR(lookup_token(ds, token, TRUE, &entry)); + if (!entry->dstream) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Apply-textdelta not active")); + SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *token; + ra_svn_token_entry_t *entry; + + /* Parse arguments and look up the token. */ + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &token)); + SVN_ERR(lookup_token(ds, token, TRUE, &entry)); + if (!entry->dstream) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Apply-textdelta not active")); + SVN_CMD_ERR(svn_stream_close(entry->dstream)); + entry->dstream = NULL; + svn_pool_destroy(entry->pool); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *token, *name; + svn_string_t *value; + ra_svn_token_entry_t *entry; + + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cc(?s)", &token, &name, + &value)); + SVN_ERR(lookup_token(ds, token, TRUE, &entry)); + SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_close_file(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *token; + ra_svn_token_entry_t *entry; + const char *text_checksum; + + /* Parse arguments and look up the file token. */ + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)", + &token, &text_checksum)); + SVN_ERR(lookup_token(ds, token, TRUE, &entry)); + + /* Close the file and destroy the baton. */ + SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool)); + apr_hash_set(ds->tokens, token, APR_HASH_KEY_STRING, NULL); + if (--ds->file_refs == 0) + svn_pool_clear(ds->file_pool); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + const char *path; + const char *token; + ra_svn_token_entry_t *entry; + + /* Parse parameters and look up the parent directory token. */ + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cc", &path, &token)); + SVN_ERR(lookup_token(ds, token, FALSE, &entry)); + + /* Call the editor. */ + SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool)); + ds->done = TRUE; + if (ds->aborted) + *ds->aborted = FALSE; + return svn_ra_svn_write_cmd_response(conn, pool, ""); +} + +static svn_error_t *ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + ds->done = TRUE; + if (ds->aborted) + *ds->aborted = TRUE; + SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool)); + return svn_ra_svn_write_cmd_response(conn, pool, ""); +} + +static svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds) +{ + if (!ds->for_replay) + return svn_error_createf + (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL, + _("Command 'finish-replay' invalid outside of replays")); + ds->done = TRUE; + if (ds->aborted) + *ds->aborted = FALSE; + return SVN_NO_ERROR; +} + +static const struct { + const char *cmd; + svn_error_t *(*handler)(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const apr_array_header_t *params, + ra_svn_driver_state_t *ds); +} ra_svn_edit_cmds[] = { + { "target-rev", ra_svn_handle_target_rev }, + { "open-root", ra_svn_handle_open_root }, + { "delete-entry", ra_svn_handle_delete_entry }, + { "add-dir", ra_svn_handle_add_dir }, + { "open-dir", ra_svn_handle_open_dir }, + { "change-dir-prop", ra_svn_handle_change_dir_prop }, + { "close-dir", ra_svn_handle_close_dir }, + { "absent-dir", ra_svn_handle_absent_dir }, + { "add-file", ra_svn_handle_add_file }, + { "open-file", ra_svn_handle_open_file }, + { "apply-textdelta", ra_svn_handle_apply_textdelta }, + { "textdelta-chunk", ra_svn_handle_textdelta_chunk }, + { "textdelta-end", ra_svn_handle_textdelta_end }, + { "change-file-prop", ra_svn_handle_change_file_prop }, + { "close-file", ra_svn_handle_close_file }, + { "absent-file", ra_svn_handle_absent_file }, + { "close-edit", ra_svn_handle_close_edit }, + { "abort-edit", ra_svn_handle_abort_edit }, + { "finish-replay", ra_svn_handle_finish_replay }, + { NULL } +}; + +static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + void *baton) +{ + ra_svn_driver_state_t *ds = baton; + const char *cmd; + apr_array_header_t *params; + + /* We blocked trying to send an error. Read and discard an editing + * command in order to avoid deadlock. */ + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "wl", &cmd, ¶ms)); + if (strcmp(cmd, "abort-edit") == 0) + { + ds->done = TRUE; + svn_ra_svn__set_block_handler(conn, NULL, NULL); + } + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const svn_delta_editor_t *editor, + void *edit_baton, + svn_boolean_t *aborted, + svn_boolean_t for_replay) +{ + ra_svn_driver_state_t state; + apr_pool_t *subpool = svn_pool_create(pool); + const char *cmd; + int i; + svn_error_t *err, *write_err; + apr_array_header_t *params; + + state.editor = editor; + state.edit_baton = edit_baton; + state.tokens = apr_hash_make(pool); + state.aborted = aborted; + state.done = FALSE; + state.pool = pool; + state.file_pool = svn_pool_create(pool); + state.file_refs = 0; + state.for_replay = for_replay; + + while (!state.done) + { + svn_pool_clear(subpool); + SVN_ERR(svn_ra_svn_read_tuple(conn, subpool, "wl", &cmd, ¶ms)); + for (i = 0; ra_svn_edit_cmds[i].cmd; i++) + { + if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0) + break; + } + if (ra_svn_edit_cmds[i].cmd) + err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state); + else if (strcmp(cmd, "failure") == 0) + { + /* While not really an editor command this can occur when + reporter->finish_report() fails before the first editor command */ + if (aborted) + *aborted = TRUE; + err = svn_ra_svn__handle_failure_status(params, pool); + return svn_error_compose_create( + err, + editor->abort_edit(edit_baton, subpool)); + } + else + { + err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL, + _("Unknown command '%s'"), cmd); + err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL); + } + + if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR) + { + if (aborted) + *aborted = TRUE; + if (!state.done) + { + /* Abort the edit and use non-blocking I/O to write the error. */ + svn_error_clear(editor->abort_edit(edit_baton, subpool)); + svn_ra_svn__set_block_handler(conn, blocked_write, &state); + } + write_err = svn_ra_svn_write_cmd_failure( + conn, subpool, + svn_ra_svn__locate_real_error_child(err)); + if (!write_err) + write_err = svn_ra_svn_flush(conn, subpool); + svn_ra_svn__set_block_handler(conn, NULL, NULL); + svn_error_clear(err); + SVN_ERR(write_err); + break; + } + SVN_ERR(err); + } + + /* Read and discard editing commands until the edit is complete. + Hopefully, the other side will call another editor command, run + check_for_error, notice the error, write "abort-edit" at us, and + throw the error up a few levels on its side (possibly even + tossing it right back at us, which is why we can return + SVN_NO_ERROR below). + + However, if the other side is way ahead of us, it might + completely finish the edit (or sequence of edit/revprops, for + "replay-range") before we send over our "failure". So we should + also stop if we see "success". (Then the other side will try to + interpret our "failure" as a command, which will itself fail... + The net effect is that whatever error we wrote to the other side + will be replaced with SVN_ERR_RA_SVN_UNKNOWN_CMD.) + */ + while (!state.done) + { + svn_pool_clear(subpool); + err = svn_ra_svn_read_tuple(conn, subpool, "wl", &cmd, ¶ms); + if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED) + { + /* Other side disconnected; that's no error. */ + svn_error_clear(err); + svn_pool_destroy(subpool); + return SVN_NO_ERROR; + } + svn_error_clear(err); + if (strcmp(cmd, "abort-edit") == 0 + || strcmp(cmd, "success") == 0) + state.done = TRUE; + } + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const svn_delta_editor_t *editor, + void *edit_baton, + svn_boolean_t *aborted) +{ + return svn_ra_svn_drive_editor2(conn, + pool, + editor, + edit_baton, + aborted, + FALSE); +} diff --git a/subversion/libsvn_ra_svn/internal_auth.c b/subversion/libsvn_ra_svn/internal_auth.c new file mode 100644 index 0000000..e1ac6b9 --- /dev/null +++ b/subversion/libsvn_ra_svn/internal_auth.c @@ -0,0 +1,121 @@ +/* + * simple_auth.c : Simple SASL-based authentication, used in case + * Cyrus SASL isn't available. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_private_config.h" + +#define APR_WANT_STRFUNC +#include <apr_want.h> +#include <apr_general.h> +#include <apr_strings.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_ra.h" +#include "svn_ra_svn.h" + +#include "ra_svn.h" + +svn_boolean_t svn_ra_svn__find_mech(const apr_array_header_t *mechlist, + const char *mech) +{ + int i; + svn_ra_svn_item_t *elt; + + for (i = 0; i < mechlist->nelts; i++) + { + elt = &APR_ARRAY_IDX(mechlist, i, svn_ra_svn_item_t); + if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, mech) == 0) + return TRUE; + } + return FALSE; +} + +/* Read the "success" response to ANONYMOUS or EXTERNAL authentication. */ +static svn_error_t *read_success(svn_ra_svn_conn_t *conn, apr_pool_t *pool) +{ + const char *status, *arg; + + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &status, &arg)); + if (strcmp(status, "failure") == 0 && arg) + return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Authentication error from server: %s"), arg); + else if (strcmp(status, "success") != 0 || arg) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Unexpected server response to authentication")); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_svn__do_internal_auth(svn_ra_svn__session_baton_t *sess, + const apr_array_header_t *mechlist, + const char *realm, apr_pool_t *pool) +{ + svn_ra_svn_conn_t *conn = sess->conn; + const char *realmstring, *user, *password, *msg; + svn_auth_iterstate_t *iterstate; + void *creds; + + realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm); + + if (sess->is_tunneled && svn_ra_svn__find_mech(mechlist, "EXTERNAL")) + { + /* Ask the server to use the tunnel connection environment (on + * Unix, that means uid) to determine the authentication name. */ + SVN_ERR(svn_ra_svn__auth_response(conn, pool, "EXTERNAL", "")); + return read_success(conn, pool); + } + else if (svn_ra_svn__find_mech(mechlist, "ANONYMOUS")) + { + SVN_ERR(svn_ra_svn__auth_response(conn, pool, "ANONYMOUS", "")); + return read_success(conn, pool); + } + else if (svn_ra_svn__find_mech(mechlist, "CRAM-MD5")) + { + SVN_ERR(svn_auth_first_credentials(&creds, &iterstate, + SVN_AUTH_CRED_SIMPLE, realmstring, + sess->callbacks->auth_baton, pool)); + if (!creds) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Can't get password")); + while (creds) + { + user = ((svn_auth_cred_simple_t *) creds)->username; + password = ((svn_auth_cred_simple_t *) creds)->password; + SVN_ERR(svn_ra_svn__auth_response(conn, pool, "CRAM-MD5", NULL)); + SVN_ERR(svn_ra_svn__cram_client(conn, pool, user, password, &msg)); + if (!msg) + break; + SVN_ERR(svn_auth_next_credentials(&creds, iterstate, pool)); + } + if (!creds) + return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Authentication error from server: %s"), + msg); + SVN_ERR(svn_auth_save_credentials(iterstate, pool)); + return SVN_NO_ERROR; + } + else + return svn_error_create(SVN_ERR_RA_SVN_NO_MECHANISMS, NULL, NULL); +} diff --git a/subversion/libsvn_ra_svn/marshal.c b/subversion/libsvn_ra_svn/marshal.c new file mode 100644 index 0000000..588ccb4 --- /dev/null +++ b/subversion/libsvn_ra_svn/marshal.c @@ -0,0 +1,1112 @@ +/* + * marshal.c : Marshalling routines for Subversion protocol + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <assert.h> +#include <stdlib.h> + +#define APR_WANT_STRFUNC +#include <apr_want.h> +#include <apr_general.h> +#include <apr_lib.h> +#include <apr_strings.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_ra_svn.h" +#include "svn_private_config.h" +#include "svn_ctype.h" + +#include "ra_svn.h" + +#include "private/svn_string_private.h" +#include "private/svn_dep_compat.h" + +#define svn_iswhitespace(c) ((c) == ' ' || (c) == '\n') + +/* If we receive data that *claims* to be followed by a very long string, + * we should not trust that claim right away. But everything up to 1 MB + * should be too small to be instrumental for a DOS attack. */ + +#define SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD (0x100000) + +/* --- CONNECTION INITIALIZATION --- */ + +svn_ra_svn_conn_t *svn_ra_svn_create_conn2(apr_socket_t *sock, + apr_file_t *in_file, + apr_file_t *out_file, + int compression_level, + apr_pool_t *pool) +{ + svn_ra_svn_conn_t *conn = apr_palloc(pool, sizeof(*conn)); + + assert((sock && !in_file && !out_file) || (!sock && in_file && out_file)); +#ifdef SVN_HAVE_SASL + conn->sock = sock; + conn->encrypted = FALSE; +#endif + conn->session = NULL; + conn->read_ptr = conn->read_buf; + conn->read_end = conn->read_buf; + conn->write_pos = 0; + conn->block_handler = NULL; + conn->block_baton = NULL; + conn->capabilities = apr_hash_make(pool); + conn->compression_level = compression_level; + conn->pool = pool; + + if (sock != NULL) + { + apr_sockaddr_t *sa; + conn->stream = svn_ra_svn__stream_from_sock(sock, pool); + if (!(apr_socket_addr_get(&sa, APR_REMOTE, sock) == APR_SUCCESS + && apr_sockaddr_ip_get(&conn->remote_ip, sa) == APR_SUCCESS)) + conn->remote_ip = NULL; + } + else + { + conn->stream = svn_ra_svn__stream_from_files(in_file, out_file, pool); + conn->remote_ip = NULL; + } + + return conn; +} + +/* backward-compatible implementation using the default compression level */ +svn_ra_svn_conn_t *svn_ra_svn_create_conn(apr_socket_t *sock, + apr_file_t *in_file, + apr_file_t *out_file, + apr_pool_t *pool) +{ + return svn_ra_svn_create_conn2(sock, in_file, out_file, + SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); +} + +svn_error_t *svn_ra_svn_set_capabilities(svn_ra_svn_conn_t *conn, + const apr_array_header_t *list) +{ + int i; + svn_ra_svn_item_t *item; + const char *word; + + for (i = 0; i < list->nelts; i++) + { + item = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); + if (item->kind != SVN_RA_SVN_WORD) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Capability entry is not a word")); + word = apr_pstrdup(conn->pool, item->u.word); + apr_hash_set(conn->capabilities, word, APR_HASH_KEY_STRING, word); + } + return SVN_NO_ERROR; +} + +svn_boolean_t svn_ra_svn_has_capability(svn_ra_svn_conn_t *conn, + const char *capability) +{ + return (apr_hash_get(conn->capabilities, capability, + APR_HASH_KEY_STRING) != NULL); +} + +int +svn_ra_svn_compression_level(svn_ra_svn_conn_t *conn) +{ + return conn->compression_level; +} + +const char *svn_ra_svn_conn_remote_host(svn_ra_svn_conn_t *conn) +{ + return conn->remote_ip; +} + +void +svn_ra_svn__set_block_handler(svn_ra_svn_conn_t *conn, + ra_svn_block_handler_t handler, + void *baton) +{ + apr_interval_time_t interval = (handler) ? 0 : -1; + + conn->block_handler = handler; + conn->block_baton = baton; + svn_ra_svn__stream_timeout(conn->stream, interval); +} + +svn_boolean_t svn_ra_svn__input_waiting(svn_ra_svn_conn_t *conn, + apr_pool_t *pool) +{ + return svn_ra_svn__stream_pending(conn->stream); +} + +/* --- WRITE BUFFER MANAGEMENT --- */ + +/* Write bytes into the write buffer until either the write buffer is + * full or we reach END. */ +static const char *writebuf_push(svn_ra_svn_conn_t *conn, const char *data, + const char *end) +{ + apr_ssize_t buflen, copylen; + + buflen = sizeof(conn->write_buf) - conn->write_pos; + copylen = (buflen < end - data) ? buflen : end - data; + memcpy(conn->write_buf + conn->write_pos, data, copylen); + conn->write_pos += copylen; + return data + copylen; +} + +/* Write data to socket or output file as appropriate. */ +static svn_error_t *writebuf_output(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *data, apr_size_t len) +{ + const char *end = data + len; + apr_size_t count; + apr_pool_t *subpool = NULL; + svn_ra_svn__session_baton_t *session = conn->session; + + while (data < end) + { + count = end - data; + + if (session && session->callbacks && session->callbacks->cancel_func) + SVN_ERR((session->callbacks->cancel_func)(session->callbacks_baton)); + + SVN_ERR(svn_ra_svn__stream_write(conn->stream, data, &count)); + if (count == 0) + { + if (!subpool) + subpool = svn_pool_create(pool); + else + svn_pool_clear(subpool); + SVN_ERR(conn->block_handler(conn, subpool, conn->block_baton)); + } + data += count; + + if (session) + { + const svn_ra_callbacks2_t *cb = session->callbacks; + session->bytes_written += count; + + if (cb && cb->progress_func) + (cb->progress_func)(session->bytes_written + session->bytes_read, + -1, cb->progress_baton, subpool); + } + } + + if (subpool) + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + +/* Write data from the write buffer out to the socket. */ +static svn_error_t *writebuf_flush(svn_ra_svn_conn_t *conn, apr_pool_t *pool) +{ + int write_pos = conn->write_pos; + + /* Clear conn->write_pos first in case the block handler does a read. */ + conn->write_pos = 0; + SVN_ERR(writebuf_output(conn, pool, conn->write_buf, write_pos)); + return SVN_NO_ERROR; +} + +static svn_error_t *writebuf_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *data, apr_size_t len) +{ + const char *end = data + len; + + if (conn->write_pos > 0 && conn->write_pos + len > sizeof(conn->write_buf)) + { + /* Fill and then empty the write buffer. */ + data = writebuf_push(conn, data, end); + SVN_ERR(writebuf_flush(conn, pool)); + } + + if (end - data > (apr_ssize_t)sizeof(conn->write_buf)) + SVN_ERR(writebuf_output(conn, pool, data, end - data)); + else + writebuf_push(conn, data, end); + return SVN_NO_ERROR; +} + +static svn_error_t *writebuf_printf(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *fmt, ...) + __attribute__ ((format(printf, 3, 4))); +static svn_error_t *writebuf_printf(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *fmt, ...) +{ + va_list ap; + char *str; + + va_start(ap, fmt); + str = apr_pvsprintf(pool, fmt, ap); + va_end(ap); + return writebuf_write(conn, pool, str, strlen(str)); +} + +/* --- READ BUFFER MANAGEMENT --- */ + +/* Read bytes into DATA until either the read buffer is empty or + * we reach END. */ +static char *readbuf_drain(svn_ra_svn_conn_t *conn, char *data, char *end) +{ + apr_ssize_t buflen, copylen; + + buflen = conn->read_end - conn->read_ptr; + copylen = (buflen < end - data) ? buflen : end - data; + memcpy(data, conn->read_ptr, copylen); + conn->read_ptr += copylen; + return data + copylen; +} + +/* Read data from socket or input file as appropriate. */ +static svn_error_t *readbuf_input(svn_ra_svn_conn_t *conn, char *data, + apr_size_t *len, apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *session = conn->session; + + if (session && session->callbacks && session->callbacks->cancel_func) + SVN_ERR((session->callbacks->cancel_func)(session->callbacks_baton)); + + SVN_ERR(svn_ra_svn__stream_read(conn->stream, data, len)); + if (*len == 0) + return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, NULL); + + if (session) + { + const svn_ra_callbacks2_t *cb = session->callbacks; + session->bytes_read += *len; + + if (cb && cb->progress_func) + (cb->progress_func)(session->bytes_read + session->bytes_written, + -1, cb->progress_baton, pool); + } + + return SVN_NO_ERROR; +} + +/* Read data from the socket into the read buffer, which must be empty. */ +static svn_error_t *readbuf_fill(svn_ra_svn_conn_t *conn, apr_pool_t *pool) +{ + apr_size_t len; + + SVN_ERR_ASSERT(conn->read_ptr == conn->read_end); + SVN_ERR(writebuf_flush(conn, pool)); + len = sizeof(conn->read_buf); + SVN_ERR(readbuf_input(conn, conn->read_buf, &len, pool)); + conn->read_ptr = conn->read_buf; + conn->read_end = conn->read_buf + len; + return SVN_NO_ERROR; +} + +static APR_INLINE svn_error_t * +readbuf_getchar(svn_ra_svn_conn_t *conn, apr_pool_t *pool, char *result) +{ + if (conn->read_ptr == conn->read_end) + SVN_ERR(readbuf_fill(conn, pool)); + *result = *conn->read_ptr++; + return SVN_NO_ERROR; +} + +static svn_error_t *readbuf_getchar_skip_whitespace(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + char *result) +{ + do + SVN_ERR(readbuf_getchar(conn, pool, result)); + while (svn_iswhitespace(*result)); + return SVN_NO_ERROR; +} + +/* Read the next LEN bytes from CONN and copy them to *DATA. */ +static svn_error_t *readbuf_read(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + char *data, apr_size_t len) +{ + char *end = data + len; + apr_size_t count; + + /* Copy in an appropriate amount of data from the buffer. */ + data = readbuf_drain(conn, data, end); + + /* Read large chunks directly into buffer. */ + while (end - data > (apr_ssize_t)sizeof(conn->read_buf)) + { + SVN_ERR(writebuf_flush(conn, pool)); + count = end - data; + SVN_ERR(readbuf_input(conn, data, &count, pool)); + data += count; + } + + while (end > data) + { + /* The remaining amount to read is small; fill the buffer and + * copy from that. */ + SVN_ERR(readbuf_fill(conn, pool)); + data = readbuf_drain(conn, data, end); + } + + return SVN_NO_ERROR; +} + +static svn_error_t *readbuf_skip_leading_garbage(svn_ra_svn_conn_t *conn, + apr_pool_t *pool) +{ + char buf[256]; /* Must be smaller than sizeof(conn->read_buf) - 1. */ + const char *p, *end; + apr_size_t len; + svn_boolean_t lparen = FALSE; + + SVN_ERR_ASSERT(conn->read_ptr == conn->read_end); + while (1) + { + /* Read some data directly from the connection input source. */ + len = sizeof(buf); + SVN_ERR(readbuf_input(conn, buf, &len, pool)); + end = buf + len; + + /* Scan the data for '(' WS with a very simple state machine. */ + for (p = buf; p < end; p++) + { + if (lparen && svn_iswhitespace(*p)) + break; + else + lparen = (*p == '('); + } + if (p < end) + break; + } + + /* p now points to the whitespace just after the left paren. Fake + * up the left paren and then copy what we have into the read + * buffer. */ + conn->read_buf[0] = '('; + memcpy(conn->read_buf + 1, p, end - p); + conn->read_ptr = conn->read_buf; + conn->read_end = conn->read_buf + 1 + (end - p); + return SVN_NO_ERROR; +} + +/* --- WRITING DATA ITEMS --- */ + +svn_error_t *svn_ra_svn_write_number(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + apr_uint64_t number) +{ + return writebuf_printf(conn, pool, "%" APR_UINT64_T_FMT " ", number); +} + +svn_error_t *svn_ra_svn_write_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const svn_string_t *str) +{ + SVN_ERR(writebuf_printf(conn, pool, "%" APR_SIZE_T_FMT ":", str->len)); + SVN_ERR(writebuf_write(conn, pool, str->data, str->len)); + SVN_ERR(writebuf_write(conn, pool, " ", 1)); + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn_write_cstring(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, const char *s) +{ + return writebuf_printf(conn, pool, "%" APR_SIZE_T_FMT ":%s ", strlen(s), s); +} + +svn_error_t *svn_ra_svn_write_word(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *word) +{ + return writebuf_printf(conn, pool, "%s ", word); +} + +svn_error_t *svn_ra_svn_write_proplist(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, apr_hash_t *props) +{ + apr_pool_t *iterpool; + apr_hash_index_t *hi; + const void *key; + void *val; + const char *propname; + svn_string_t *propval; + + if (props) + { + iterpool = svn_pool_create(pool); + for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) + { + svn_pool_clear(iterpool); + apr_hash_this(hi, &key, NULL, &val); + propname = key; + propval = val; + SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "cs", + propname, propval)); + } + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn_start_list(svn_ra_svn_conn_t *conn, apr_pool_t *pool) +{ + return writebuf_write(conn, pool, "( ", 2); +} + +svn_error_t *svn_ra_svn_end_list(svn_ra_svn_conn_t *conn, apr_pool_t *pool) +{ + return writebuf_write(conn, pool, ") ", 2); +} + +svn_error_t *svn_ra_svn_flush(svn_ra_svn_conn_t *conn, apr_pool_t *pool) +{ + return writebuf_flush(conn, pool); +} + +/* --- WRITING TUPLES --- */ + +static svn_error_t *vwrite_tuple(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *fmt, va_list ap) +{ + svn_boolean_t opt = FALSE; + svn_revnum_t rev; + const char *cstr; + const svn_string_t *str; + + if (*fmt == '!') + fmt++; + else + SVN_ERR(svn_ra_svn_start_list(conn, pool)); + for (; *fmt; fmt++) + { + if (*fmt == 'n' && !opt) + SVN_ERR(svn_ra_svn_write_number(conn, pool, va_arg(ap, apr_uint64_t))); + else if (*fmt == 'r') + { + rev = va_arg(ap, svn_revnum_t); + SVN_ERR_ASSERT(opt || SVN_IS_VALID_REVNUM(rev)); + if (SVN_IS_VALID_REVNUM(rev)) + SVN_ERR(svn_ra_svn_write_number(conn, pool, rev)); + } + else if (*fmt == 's') + { + str = va_arg(ap, const svn_string_t *); + SVN_ERR_ASSERT(opt || str); + if (str) + SVN_ERR(svn_ra_svn_write_string(conn, pool, str)); + } + else if (*fmt == 'c') + { + cstr = va_arg(ap, const char *); + SVN_ERR_ASSERT(opt || cstr); + if (cstr) + SVN_ERR(svn_ra_svn_write_cstring(conn, pool, cstr)); + } + else if (*fmt == 'w') + { + cstr = va_arg(ap, const char *); + SVN_ERR_ASSERT(opt || cstr); + if (cstr) + SVN_ERR(svn_ra_svn_write_word(conn, pool, cstr)); + } + else if (*fmt == 'b' && !opt) + { + cstr = va_arg(ap, svn_boolean_t) ? "true" : "false"; + SVN_ERR(svn_ra_svn_write_word(conn, pool, cstr)); + } + else if (*fmt == '?') + opt = TRUE; + else if (*fmt == '(' && !opt) + SVN_ERR(svn_ra_svn_start_list(conn, pool)); + else if (*fmt == ')') + { + SVN_ERR(svn_ra_svn_end_list(conn, pool)); + opt = FALSE; + } + else if (*fmt == '!' && !*(fmt + 1)) + return SVN_NO_ERROR; + else + SVN_ERR_MALFUNCTION(); + } + SVN_ERR(svn_ra_svn_end_list(conn, pool)); + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn_write_tuple(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *fmt, ...) +{ + svn_error_t *err; + va_list ap; + + va_start(ap, fmt); + err = vwrite_tuple(conn, pool, fmt, ap); + va_end(ap); + return err; +} + +/* --- READING DATA ITEMS --- */ + +/* Read LEN bytes from CONN into already-allocated structure ITEM. + * Afterwards, *ITEM is of type 'SVN_RA_SVN_STRING', and its string + * data is allocated in POOL. */ +static svn_error_t *read_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + svn_ra_svn_item_t *item, apr_uint64_t len64) +{ + svn_stringbuf_t *stringbuf; + apr_size_t len = (apr_size_t)len64; + apr_size_t readbuf_len; + char *dest; + + /* We can't store strings longer than the maximum size of apr_size_t, + * so check for wrapping */ + if (len64 > APR_SIZE_MAX) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("String length larger than maximum")); + + /* Read the string in chunks. The chunk size is large enough to avoid + * re-allocation in typical cases, and small enough to ensure we do not + * pre-allocate an unreasonable amount of memory if (perhaps due to + * network data corruption or a DOS attack), we receive a bogus claim that + * a very long string is going to follow. In that case, we start small + * and wait for all that data to actually show up. This does not fully + * prevent DOS attacks but makes them harder (you have to actually send + * gigabytes of data). */ + readbuf_len = len < SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD + ? len + : SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD; + stringbuf = svn_stringbuf_create_ensure(readbuf_len, pool); + dest = stringbuf->data; + + /* Read remaining string data directly into the string structure. + * Do it iteratively, if necessary. */ + while (readbuf_len) + { + SVN_ERR(readbuf_read(conn, pool, dest, readbuf_len)); + + stringbuf->len += readbuf_len; + len -= readbuf_len; + + /* Early exit. In most cases, strings can be read in the first + * iteration. */ + if (len == 0) + break; + + /* Prepare next iteration: determine length of chunk to read + * and re-alloc the string buffer. */ + readbuf_len + = len < SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD + ? len + : SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD; + + svn_stringbuf_ensure(stringbuf, stringbuf->len + readbuf_len + 1); + dest = stringbuf->data + stringbuf->len; + } + + /* zero-terminate the string */ + stringbuf->data[stringbuf->len] = '\0'; + + /* Return the string properly wrapped into an RA_SVN item. */ + item->kind = SVN_RA_SVN_STRING; + item->u.string = svn_stringbuf__morph_into_string(stringbuf); + + return SVN_NO_ERROR; +} + +/* Given the first non-whitespace character FIRST_CHAR, read an item + * into the already allocated structure ITEM. LEVEL should be set + * to 0 for the first call and is used to enforce a recurssion limit + * on the parser. */ +static svn_error_t *read_item(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + svn_ra_svn_item_t *item, char first_char, + int level) +{ + char c = first_char; + apr_uint64_t val; + svn_stringbuf_t *str; + svn_ra_svn_item_t *listitem; + + if (++level >= 64) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Too many nested items")); + + + /* Determine the item type and read it in. Make sure that c is the + * first character at the end of the item so we can test to make + * sure it's whitespace. */ + if (svn_ctype_isdigit(c)) + { + /* It's a number or a string. Read the number part, either way. */ + val = c - '0'; + while (1) + { + apr_uint64_t prev_val = val; + SVN_ERR(readbuf_getchar(conn, pool, &c)); + if (!svn_ctype_isdigit(c)) + break; + val = val * 10 + (c - '0'); + if ((val / 10) != prev_val) /* val wrapped past maximum value */ + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Number is larger than maximum")); + } + if (c == ':') + { + /* It's a string. */ + SVN_ERR(read_string(conn, pool, item, val)); + SVN_ERR(readbuf_getchar(conn, pool, &c)); + } + else + { + /* It's a number. */ + item->kind = SVN_RA_SVN_NUMBER; + item->u.number = val; + } + } + else if (svn_ctype_isalpha(c)) + { + /* It's a word. */ + str = svn_stringbuf_create_ensure(16, pool); + svn_stringbuf_appendbyte(str, c); + while (1) + { + SVN_ERR(readbuf_getchar(conn, pool, &c)); + if (!svn_ctype_isalnum(c) && c != '-') + break; + svn_stringbuf_appendbyte(str, c); + } + item->kind = SVN_RA_SVN_WORD; + item->u.word = str->data; + } + else if (c == '(') + { + /* Read in the list items. */ + item->kind = SVN_RA_SVN_LIST; + item->u.list = apr_array_make(pool, 4, sizeof(svn_ra_svn_item_t)); + while (1) + { + SVN_ERR(readbuf_getchar_skip_whitespace(conn, pool, &c)); + if (c == ')') + break; + listitem = apr_array_push(item->u.list); + SVN_ERR(read_item(conn, pool, listitem, c, level)); + } + SVN_ERR(readbuf_getchar(conn, pool, &c)); + } + + if (!svn_iswhitespace(c)) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Malformed network data")); + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn_read_item(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + svn_ra_svn_item_t **item) +{ + char c; + + /* Allocate space, read the first character, and then do the rest of + * the work. This makes sense because of the way lists are read. */ + *item = apr_palloc(pool, sizeof(**item)); + SVN_ERR(readbuf_getchar_skip_whitespace(conn, pool, &c)); + return read_item(conn, pool, *item, c, 0); +} + +svn_error_t *svn_ra_svn_skip_leading_garbage(svn_ra_svn_conn_t *conn, + apr_pool_t *pool) +{ + return readbuf_skip_leading_garbage(conn, pool); +} + +/* --- READING AND PARSING TUPLES --- */ + +/* Parse a tuple of svn_ra_svn_item_t *'s. Advance *FMT to the end of the + * tuple specification and advance AP by the corresponding arguments. */ +static svn_error_t *vparse_tuple(const apr_array_header_t *items, apr_pool_t *pool, + const char **fmt, va_list *ap) +{ + int count, nesting_level; + svn_ra_svn_item_t *elt; + + for (count = 0; **fmt && count < items->nelts; (*fmt)++, count++) + { + /* '?' just means the tuple may stop; skip past it. */ + if (**fmt == '?') + (*fmt)++; + elt = &APR_ARRAY_IDX(items, count, svn_ra_svn_item_t); + if (**fmt == 'n' && elt->kind == SVN_RA_SVN_NUMBER) + *va_arg(*ap, apr_uint64_t *) = elt->u.number; + else if (**fmt == 'r' && elt->kind == SVN_RA_SVN_NUMBER) + *va_arg(*ap, svn_revnum_t *) = (svn_revnum_t) elt->u.number; + else if (**fmt == 's' && elt->kind == SVN_RA_SVN_STRING) + *va_arg(*ap, svn_string_t **) = elt->u.string; + else if (**fmt == 'c' && elt->kind == SVN_RA_SVN_STRING) + *va_arg(*ap, const char **) = elt->u.string->data; + else if (**fmt == 'w' && elt->kind == SVN_RA_SVN_WORD) + *va_arg(*ap, const char **) = elt->u.word; + else if (**fmt == 'b' && elt->kind == SVN_RA_SVN_WORD) + { + if (strcmp(elt->u.word, "true") == 0) + *va_arg(*ap, svn_boolean_t *) = TRUE; + else if (strcmp(elt->u.word, "false") == 0) + *va_arg(*ap, svn_boolean_t *) = FALSE; + else + break; + } + else if (**fmt == 'B' && elt->kind == SVN_RA_SVN_WORD) + { + if (strcmp(elt->u.word, "true") == 0) + *va_arg(*ap, apr_uint64_t *) = TRUE; + else if (strcmp(elt->u.word, "false") == 0) + *va_arg(*ap, apr_uint64_t *) = FALSE; + else + break; + } + else if (**fmt == 'l' && elt->kind == SVN_RA_SVN_LIST) + *va_arg(*ap, apr_array_header_t **) = elt->u.list; + else if (**fmt == '(' && elt->kind == SVN_RA_SVN_LIST) + { + (*fmt)++; + SVN_ERR(vparse_tuple(elt->u.list, pool, fmt, ap)); + } + else if (**fmt == ')') + return SVN_NO_ERROR; + else + break; + } + if (**fmt == '?') + { + nesting_level = 0; + for (; **fmt; (*fmt)++) + { + switch (**fmt) + { + case '?': + break; + case 'r': + *va_arg(*ap, svn_revnum_t *) = SVN_INVALID_REVNUM; + break; + case 's': + *va_arg(*ap, svn_string_t **) = NULL; + break; + case 'c': + case 'w': + *va_arg(*ap, const char **) = NULL; + break; + case 'l': + *va_arg(*ap, apr_array_header_t **) = NULL; + break; + case 'B': + case 'n': + *va_arg(*ap, apr_uint64_t *) = SVN_RA_SVN_UNSPECIFIED_NUMBER; + break; + case '(': + nesting_level++; + break; + case ')': + if (--nesting_level < 0) + return SVN_NO_ERROR; + break; + default: + SVN_ERR_MALFUNCTION(); + } + } + } + if (**fmt && **fmt != ')') + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Malformed network data")); + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn_parse_tuple(const apr_array_header_t *list, + apr_pool_t *pool, + const char *fmt, ...) +{ + svn_error_t *err; + va_list ap; + + va_start(ap, fmt); + err = vparse_tuple(list, pool, &fmt, &ap); + va_end(ap); + return err; +} + +svn_error_t *svn_ra_svn_read_tuple(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *fmt, ...) +{ + va_list ap; + svn_ra_svn_item_t *item; + svn_error_t *err; + + SVN_ERR(svn_ra_svn_read_item(conn, pool, &item)); + if (item->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Malformed network data")); + va_start(ap, fmt); + err = vparse_tuple(item->u.list, pool, &fmt, &ap); + va_end(ap); + return err; +} + +svn_error_t *svn_ra_svn_parse_proplist(const apr_array_header_t *list, + apr_pool_t *pool, + apr_hash_t **props) +{ + char *name; + svn_string_t *value; + svn_ra_svn_item_t *elt; + int i; + + *props = apr_hash_make(pool); + for (i = 0; i < list->nelts; i++) + { + elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); + if (elt->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Proplist element not a list")); + SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "cs", &name, &value)); + apr_hash_set(*props, name, APR_HASH_KEY_STRING, value); + } + + return SVN_NO_ERROR; +} + + +/* --- READING AND WRITING COMMANDS AND RESPONSES --- */ + +svn_error_t *svn_ra_svn__locate_real_error_child(svn_error_t *err) +{ + svn_error_t *this_link; + + SVN_ERR_ASSERT(err); + + for (this_link = err; + this_link && (this_link->apr_err == SVN_ERR_RA_SVN_CMD_ERR); + this_link = this_link->child) + ; + + SVN_ERR_ASSERT(this_link); + return this_link; +} + +svn_error_t *svn_ra_svn__handle_failure_status(const apr_array_header_t *params, + apr_pool_t *pool) +{ + const char *message, *file; + svn_error_t *err = NULL; + svn_ra_svn_item_t *elt; + int i; + apr_uint64_t apr_err, line; + apr_pool_t *subpool = svn_pool_create(pool); + + if (params->nelts == 0) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Empty error list")); + + /* Rebuild the error list from the end, to avoid reversing the order. */ + for (i = params->nelts - 1; i >= 0; i--) + { + svn_pool_clear(subpool); + elt = &APR_ARRAY_IDX(params, i, svn_ra_svn_item_t); + if (elt->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Malformed error list")); + SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "nccn", &apr_err, + &message, &file, &line)); + /* The message field should have been optional, but we can't + easily change that, so "" means a nonexistent message. */ + if (!*message) + message = NULL; + + /* Skip over links in the error chain that were intended only to + exist on the server (to wrap real errors intended for the + client) but accidentally got included in the server's actual + response. */ + if ((apr_status_t)apr_err != SVN_ERR_RA_SVN_CMD_ERR) + { + err = svn_error_create((apr_status_t)apr_err, err, message); + err->file = apr_pstrdup(err->pool, file); + err->line = (long)line; + } + } + + svn_pool_destroy(subpool); + + /* If we get here, then we failed to find a real error in the error + chain that the server proported to be sending us. That's bad. */ + if (! err) + err = svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Malformed error list")); + + return err; +} + +svn_error_t *svn_ra_svn_read_cmd_response(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const char *fmt, ...) +{ + va_list ap; + const char *status; + apr_array_header_t *params; + svn_error_t *err; + + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "wl", &status, ¶ms)); + if (strcmp(status, "success") == 0) + { + va_start(ap, fmt); + err = vparse_tuple(params, pool, &fmt, &ap); + va_end(ap); + return err; + } + else if (strcmp(status, "failure") == 0) + { + return svn_ra_svn__handle_failure_status(params, pool); + } + + return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Unknown status '%s' in command response"), + status); +} + +svn_error_t *svn_ra_svn_handle_commands2(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const svn_ra_svn_cmd_entry_t *commands, + void *baton, + svn_boolean_t error_on_disconnect) +{ + apr_pool_t *subpool = svn_pool_create(pool); + apr_pool_t *iterpool = svn_pool_create(subpool); + const char *cmdname; + const svn_ra_svn_cmd_entry_t *command; + svn_error_t *err, *write_err; + apr_array_header_t *params; + apr_hash_t *cmd_hash = apr_hash_make(subpool); + + for (command = commands; command->cmdname; command++) + apr_hash_set(cmd_hash, command->cmdname, APR_HASH_KEY_STRING, command); + + while (1) + { + svn_pool_clear(iterpool); + err = svn_ra_svn_read_tuple(conn, iterpool, "wl", &cmdname, ¶ms); + if (err) + { + if (!error_on_disconnect + && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED) + { + svn_error_clear(err); + svn_pool_destroy(subpool); + return SVN_NO_ERROR; + } + return err; + } + command = apr_hash_get(cmd_hash, cmdname, APR_HASH_KEY_STRING); + + if (command) + err = (*command->handler)(conn, iterpool, params, baton); + else + { + err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL, + _("Unknown command '%s'"), cmdname); + err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL); + } + + if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR) + { + write_err = svn_ra_svn_write_cmd_failure( + conn, iterpool, + svn_ra_svn__locate_real_error_child(err)); + svn_error_clear(err); + if (write_err) + return write_err; + } + else if (err) + return err; + + if (command && command->terminate) + break; + } + svn_pool_destroy(iterpool); + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn_handle_commands(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const svn_ra_svn_cmd_entry_t *commands, + void *baton) +{ + return svn_ra_svn_handle_commands2(conn, pool, commands, baton, TRUE); +} + +svn_error_t *svn_ra_svn_write_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *cmdname, const char *fmt, ...) +{ + va_list ap; + svn_error_t *err; + + SVN_ERR(svn_ra_svn_start_list(conn, pool)); + SVN_ERR(svn_ra_svn_write_word(conn, pool, cmdname)); + va_start(ap, fmt); + err = vwrite_tuple(conn, pool, fmt, ap); + va_end(ap); + if (err) + return err; + SVN_ERR(svn_ra_svn_end_list(conn, pool)); + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn_write_cmd_response(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const char *fmt, ...) +{ + va_list ap; + svn_error_t *err; + + SVN_ERR(svn_ra_svn_start_list(conn, pool)); + SVN_ERR(svn_ra_svn_write_word(conn, pool, "success")); + va_start(ap, fmt); + err = vwrite_tuple(conn, pool, fmt, ap); + va_end(ap); + if (err) + return err; + SVN_ERR(svn_ra_svn_end_list(conn, pool)); + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn_write_cmd_failure(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, svn_error_t *err) +{ + char buffer[128]; + SVN_ERR(svn_ra_svn_start_list(conn, pool)); + SVN_ERR(svn_ra_svn_write_word(conn, pool, "failure")); + SVN_ERR(svn_ra_svn_start_list(conn, pool)); + for (; err; err = err->child) + { + const char *msg = svn_err_best_message(err, buffer, sizeof(buffer)); + + /* The message string should have been optional, but we can't + easily change that, so marshal nonexistent messages as "". */ + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "nccn", + (apr_uint64_t) err->apr_err, + msg ? msg : "", + err->file ? err->file : "", + (apr_uint64_t) err->line)); + } + SVN_ERR(svn_ra_svn_end_list(conn, pool)); + SVN_ERR(svn_ra_svn_end_list(conn, pool)); + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_ra_svn/protocol b/subversion/libsvn_ra_svn/protocol new file mode 100644 index 0000000..3839ebe --- /dev/null +++ b/subversion/libsvn_ra_svn/protocol @@ -0,0 +1,612 @@ +This file documents version 2 of the svn protocol. + +1. Syntactic structure +---------------------- + +The Subversion protocol is specified in terms of the following +syntactic elements, specified using ABNF [RFC 2234]: + + item = word / number / string / list + word = ALPHA *(ALPHA / DIGIT / "-") space + number = 1*DIGIT space + string = 1*DIGIT ":" *OCTET space + ; digits give the byte count of the *OCTET portion + list = "(" space *item ")" space + space = 1*(SP / LF) + +Here is an example item showing each of the syntactic elements: + + ( word 22 6:string ( sublist ) ) + +All items end with mandatory whitespace. (In the above example, a +newline provides the terminating whitespace for the outer list.) It +is possible to parse an item without knowing its type in advance. + +Lists are not constrained to contain items of the same type. Lists +can be used for tuples, optional tuples, or arrays. A tuple is a list +expected to contain a fixed number of items, generally of differing +types. An optional tuple is a list containing either zero or a fixed +number of items (thus "optional" here does not refer to the list's +presence or absence, but to the presence or absence of its contents). +An array is a list containing zero or more items of the same type. + +Words are used for enumerated protocol values, while strings are used +for text or binary data of interest to the Subversion client or +server. Words are case-sensitive. + +For convenience, this specification will define prototypes for data +items using a syntax like: + + example: ( literal ( data:string ... ) ) + +A simple word such as "literal", with no colon, denotes a literal +word. A choice of words may be given with "|" separating the choices. +"name:type" specifies a parameter with the given type. + +A type is "word", "number", "string", "list", or the name of another +prototype. Parentheses denote a tuple, unless the parentheses contain +ellipses, in which case the parentheses denote an array containing +zero or more elements matching the prototype preceding the ellipses. + +If a tuple has an optional part after the fixed part, a '?' marks +places where the tuple is allowed to end. The following tuple could +contain one, three, or four or more items: + + example: ( fixed:string ? opt1:number opt2:string ? opt3:number ) + +Brackets denote an optional tuple; they are equivalent to parentheses +and a leading '?'. For example, this: + + example: ( literal (? rev:number ) ( data:string ... ) ) + +can be written more compactly like this: + + example: ( literal [ rev:number ] ( data:string ... ) ) + +For extensibility, implementations must treat a list as matching a +prototype's tuple even if the list contains extra elements. The extra +elements must be ignored. + +In some cases, a prototype may need to match two different kinds of +data items. This case will be written using "|" to separate the +alternatives; for example: + + example: ( first-kind rev:number ) + | second-kind + +The "command response" prototype is used in several contexts of this +specification to indicate the success or failure of an operation. It +is defined as follows: + + command-response: ( success params:list ) + | ( failure ( err:error ... ) ) + error: ( apr-err:number message:string file:string line:number ) + +The interpretation of parameters in a successful command response is +context-dependent. + +URLs and repository paths are represented as strings. They should be in +canonical form when sent over the protocol. However, as a matter of input +validation, an implementation should always canonicalize received paths if it +needs them in canonicalized form. + +2. Connection establishment and protocol setup +---------------------------------------------- + +By default, the client connects to the server on port 3690. + +Upon receiving a connection, the server sends a greeting, using a +command response whose parameters match the prototype: + + greeting: ( minver:number maxver:number mechs:list ( cap:word ... ) ) + +minver and maxver give the minimum and maximum Subversion protocol +versions supported by the server. mechs is present for historical +reasons, and is ignored by the client. The cap values give a list of +server capabilities (see section 2.1). + +If the client does not support a protocol version within the specified +range, it closes the connection. Otherwise, the client responds to +the greeting with an item matching the prototype: + + response: ( version:number ( cap:word ... ) url:string + ? ra-client:string ( ? client:string ) ) + +version gives the protocol version selected by the client. The cap +values give a list of client capabilities (see section 2.1). url +gives the URL the client is accessing. ra-client is a string +identifying the RA implementation, e.g. "SVN/1.6.0" or "SVNKit 1.1.4". +client is the string returned by svn_ra_callbacks2_t.get_client_string; +that callback may not be implemented, so this is optional. + +Upon receiving the client's response to the greeting, the server sends +an authentication request, which is a command response whose arguments +match the prototype: + + auth-request: ( ( mech:word ... ) realm:string ) + +The mech values give a list of SASL mechanisms supported by the +server. The realm string is similar to an HTTP authentication realm +as defined in [RFC 2617]; it allows the server to indicate which of +several protection spaces the server wishes to authenticate in. If +the mechanism list is empty, then no authentication is required and no +further action takes place as part of the authentication challenge; +otherwise, the client responds with a tuple matching the prototype: + + auth-response: ( mech:word [ token:string ] ) + +mech specifies the SASL mechanism and token, if present, gives the +"initial response" of the authentication exchange. The client may +specify an empty mechanism to decline authentication; otherwise, upon +receiving the client's auth-response, the server sends a series of +challenges, each a tuple matching the prototype: + + challenge: ( step ( token:string ) ) + | ( failure ( message:string ) ) + | ( success [ token:string ] ) + +If the first word of the challenge is "step", then the token is +interpreted by the authentication mechanism, and the response token +transmitted to the server as a string. The server then proceeds with +another challenge. If the client wishes to abort the authentication +exchange, it may do so by closing the connection. + +If the first word of the challenge is "success", the authentication is +successful. If a token is provided, it should be interpreted by the +authentication mechanism, but there is no response. + +If the first word of the challenge is "failure", the authentication +exchange is unsuccessful. The client may then give up, or make +another auth-response and restart the authentication process. + +RFC 2222 requires that a protocol profile define a service name for +the sake of the GSSAPI mechanism. The service name for this protocol +is "svn". + +After a successful authentication exchange, the server sends a command +response whose parameters match the prototype: + + repos-info: ( uuid:string repos-url:string ( cap:word ... ) ) + +uuid gives the universal unique identifier of the repository, +repos-url gives the URL of the repository's root directory, and the +cap values list the repository capabilities (that is, capabilities +that require both server and repository support before the server can +claim them as capabilities, e.g., SVN_RA_SVN_CAP_MERGEINFO). + +The client can now begin sending commands from the main command set. + +2.1 Capabilities + +The following capabilities are currently defined (S indicates a server +capability and C indicates a client capability): + +[CS] edit-pipeline Every released version of Subversion since 1.0 + announces the edit-pipeline capability; starting + in Subversion 1.5, both client and server + *require* the other side to announce edit-pipeline. +[CS] svndiff1 If both the client and server support svndiff version + 1, this will be used as the on-the-wire format for + svndiff instead of svndiff version 0. +[CS] absent-entries If the remote end announces support for this capability, + it will accept the absent-dir and absent-file editor + commands. +[S] commit-revprops If the server presents this capability, it supports the + rev-props parameter of the commit command. + See section 3.1.1. +[S] mergeinfo If the server presents this capability, it supports the + get-mergeinfo command. See section 3.1.1. +[S] depth If the server presents this capability, it understands + requested operational depth (see section 3.1.1) and + per-path ambient depth (see section 3.1.3). +[S] atomic-revprops If the server presents this capability, it + supports the change-rev-prop2 command. + See section 3.1.1. + +3. Commands +----------- + +Commands match the prototypes: + + command: ( command-name:word params:list ) + +The interpretation of command parameters is different from command to +command. + +Initially, the client initiates commands from the main command set, +and the server responds. Some commands in the main command set can +temporarily change the set of commands which may be issued, or change +the flow of control so that the server issues commands and the client +responds. + +Here are some miscellaneous prototypes used by the command sets: + + proplist: ( ( name:string value:string ) ... ) + propdelta: ( ( name:string [ value:string ] ) ... ) + node-kind: none|file|dir|unknown + bool: true|false + lockdesc: ( path:string token:string owner:string [ comment:string ] + created:string [ expires:string ] ) + +3.1. Command Sets + +There are three command sets: the main command set, the editor command +set, and the report command set. Initially, the protocol begins in +the main command set with the client sending commands; some commands +can change the command set and possibly the direction of control. + +3.1.1. Main Command Set + +The main command set corresponds to the svn_ra interfaces. After each +main command is issued by the client, the server sends an auth-request +as described in section 2. (If no new authentication is required, the +auth-request contains an empty mechanism list, and the server proceeds +immediately to sending the command response.) Some commands include a +second place for auth-request point as noted below. + + reparent + params: ( url:string ) + response: ( ) + + get-latest-rev + params: ( ) + response: ( rev:number ) + + get-dated-rev + params: ( date:string ) + response: ( rev:number ) + + change-rev-prop + params: ( rev:number name:string ? value:string ) + response: ( ) + If value is not specified, the rev-prop is removed. + (Originally the value was required; for minimum impact, it was + changed to be optional without creating an optional tuple for + that one parameter as we normally do.) + + change-rev-prop2 + params: ( rev:number name:string [ value:string ] + ( dont-care:bool ? previous-value:string ) ) + response: ( ) + If value is not specified, the rev-prop is removed. If dont-care is false, + then the rev-prop is changed only if it is currently set as previous-value + indicates. (If dont-care is false and previous-value is unspecified, then + the revision property must be previously unset.) If dont-care is true, + then previous-value must not be specified. + + rev-proplist + params: ( rev:number ) + response: ( props:proplist ) + + rev-prop + params: ( rev:number name:string ) + response: ( [ value:string ] ) + + commit + params: ( logmsg:string ? ( ( lock-path:string lock-token:string ) ... ) + keep-locks:bool ? rev-props:proplist ) + response: ( ) + Upon receiving response, client switches to editor command set. + Upon successful completion of edit, server sends auth-request. + After auth exchange completes, server sends commit-info. + commit-info: ( new-rev:number date:string author:string + ? ( post-commit-err:string ) ) + + get-file + params: ( path:string [ rev:number ] want-props:bool want-contents:bool ) + response: ( [ checksum:string ] rev:number props:proplist ) + If want-contents is specified, then after sending response, server + sends file contents as a series of strings, terminated by the empty + string, followed by a second empty command response to indicate + whether an error occurred during the sending of the file. + + get-dir + params: ( path:string [ rev:number ] want-props:bool want-contents:bool + ? ( field:dirent-field ... ) ) + response: ( rev:number props:proplist ( entry:dirent ... ) )] + dirent: ( name:string kind:node-kind size:number has-props:bool + created-rev:number [ created-date:string ] + [ last-author:string ] ) + dirent-field: kind | size | has-props | created-rev | time | last-author + | word + + check-path + params: ( path:string [ rev:number ] ) + response: ( kind:node-kind ) + If path is non-existent, 'svn_node_none' kind is returned. + + stat + params: ( path:string [ rev:number ] ) + response: ( ? entry:dirent ) + dirent: ( name:string kind:node-kind size:number has-props:bool + created-rev:number [ created-date:string ] + [ last-author:string ] ) + New in svn 1.2. If path is non-existent, an empty response is returned. + + get-mergeinfo + params: ( ( path:string ... ) [ rev:number ] inherit:word + descendents:bool) + response: ( ( ( path:string merge-info:string ) ... ) ) + New in svn 1.5. If no paths are specified, an empty response is + returned. If rev is not specified, the youngest revision is used. + + update + params: ( [ rev:number ] target:string recurse:bool + ? depth:word send_copyfrom_param:bool ) + Client switches to report command set. + Upon finish-report, server sends auth-request. + After auth exchange completes, server switches to editor command set. + After edit completes, server sends response. + response: ( ) + + switch + params: ( [ rev:number ] target:string recurse:bool url:string + ? depth:word) + Client switches to report command set. + Upon finish-report, server sends auth-request. + After auth exchange completes, server switches to editor command set. + After edit completes, server sends response. + response: ( ) + + status + params: ( target:string recurse:bool ? [ rev:number ] ? depth:word ) + Client switches to report command set. + Upon finish-report, server sends auth-request. + After auth exchange completes, server switches to editor command set. + After edit completes, server sends response. + response: ( ) + + diff + params: ( [ rev:number ] target:string recurse:bool ignore-ancestry:bool + url:string ? text-deltas:bool ? depth:word ) + Client switches to report command set. + Upon finish-report, server sends auth-request. + After auth exchange completes, server switches to editor command set. + After edit completes, server sends response. + response: ( ) + + log + params: ( ( target-path:string ... ) [ start-rev:number ] + [ end-rev:number ] changed-paths:bool strict-node:bool + ? limit:number + ? include-merged-revisions:bool + all-revprops | revprops ( revprop:string ... ) ) + Before sending response, server sends log entries, ending with "done". + If a client does not want to specify a limit, it should send 0 as the + limit parameter. rev-props excludes author, date, and log; they are + sent separately for backwards-compatibility. + log-entry: ( ( change:changed-path-entry ... ) rev:number + [ author:string ] [ date:string ] [ message:string ] + ? has-children:bool invalid-revnum:bool + revprop-count:number rev-props:proplist + ? subtractive-merge:bool ) + | done + changed-path-entry: ( path:string A|D|R|M + ? ( ? copy-path:string copy-rev:number ) + ? ( ? node-kind:string ? text-mods:bool prop-mods:bool ) ) + response: ( ) + + get-locations + params: ( path:string peg-rev:number ( rev:number ... ) ) + Before sending response, server sends location entries, ending with "done". + location-entry: ( rev:number abs-path:number ) | done + response: ( ) + + get-location-segments + params: ( path:string [ start-rev:number ] [ end-rev:number ] ) + Before sending response, server sends location entries, ending with "done". + location-entry: ( range-start:number range-end:number [ abs-path:string ] ) | done + response: ( ) + + get-file-revs + params: ( path:string [ start-rev:number ] [ end-rev:number ] + ? include-merged-revisions:bool ) + Before sending response, server sends file-rev entries, ending with "done". + file-rev: ( path:string rev:number rev-props:proplist + file-props:propdelta ? merged-revision:bool ) + | done + After each file-rev, the file delta is sent as one or more strings, + terminated by the empty string. If there is no delta, server just sends + the terminator. + response: ( ) + + lock + params: ( path:string [ comment:string ] steal-lock:bool + [ current-rev:number ] ) + response: ( lock:lockdesc ) + + lock-many + params: ( [ comment:string ] steal-lock:bool ( ( path:string + [ current-rev:number ] ) ... ) ) + Before sending response, server sends lock cmd status and descriptions, + ending with "done". + lock-info: ( success ( lock:lockdesc ) ) | ( failure ( err:error ) ) + | done + response: ( ) + + unlock + params: ( path:string [ token:string ] break-lock:bool ) + response: ( ) + + unlock-many + params: ( break-lock:bool ( ( path:string [ token:string ] ) ... ) ) + Before sending response, server sends unlocked paths, ending with "done". + pre-response: ( success ( path:string ) ) | ( failure ( err:error ) ) + | done + response: ( ) + + get-lock + params: ( path:string ) + response: ( [ lock:lockdesc ] ) + + get-locks + params: ( path:string ? [ depth:word ] ) + response ( ( lock:lockdesc ... ) ) + + replay + params: ( revision:number low-water-mark:number send-deltas:bool ) + After auth exchange completes, server switches to editor command set. + After edit completes, server sends response. + response ( ) + + replay-range + params: ( start-rev:number end-rev:number low-water-mark:number + send-deltas:bool ) + After auth exchange completes, server sends each revision + from start-rev to end-rev, alternating between sending 'revprops' + entries and sending the revision in the editor command set. + After all revisions are complete, server sends response. + revprops: ( revprops:word props:proplist ) + (revprops here is the literal word "revprops".) + response ( ) + + get-deleted-rev + params: ( path:string peg-rev:number end-rev:number ) + response: ( deleted-rev:number ) + +3.1.2. Editor Command Set + +An edit operation produces only one response, at close-edit or +abort-edit time. However, the consumer may write an error response at +any time during the edit in order to terminate the edit operation +early; the driver must notice that input is waiting on the connection, +read the error, and send an abort-edit operation. After an error is +returned, the consumer must read and discard editing operations until +the abort-edit. In order to prevent TCP deadlock, the consumer must +use non-blocking I/O to send an early error response; if writing +blocks, the consumer must read and discard edit operations until +writing unblocks or it reads an abort-edit. + + target-rev + params: ( rev:number ) + + open-root + params: ( [ rev:number ] root-token:string ) + + delete-entry + params: ( path:string rev:number dir-token:string ) + + add-dir + params: ( path:string parent-token:string child-token:string + [ copy-path:string copy-rev:number ] ) + + open-dir + params: ( path:string parent-token:string child-token:string rev:number ) + + change-dir-prop + params: ( dir-token:string name:string [ value:string ] ) + + close-dir + params: ( dir-token:string ) + + absent-dir + params: ( path:string parent-token:string ) + + add-file + params: ( path:string dir-token:string file-token:string + [ copy-path:string copy-rev:number ] ) + + open-file + params: ( path:string dir-token:string file-token:string rev:number ) + + apply-textdelta + params: ( file-token:string [ base-checksum:string ] ) + + textdelta-chunk + params: ( file-token:string chunk:string ) + + textdelta-end + params: ( file-token:string ) + + change-file-prop + params: ( file-token:string name:string [ value:string ] ) + + close-file + params: ( file-token:string [ text-checksum:string ] ) + + absent-file + params: ( path:string parent-token:string ) + + close-edit + params: ( ) + response: ( ) + + abort-edit + params: ( ) + response: ( ) + + finish-replay + params: ( ) + Only delivered from server to client, at the end of a replay. + +3.1.3. Report Command Set + +To reduce round-trip delays, report commands do not return responses. +Any errors resulting from a report call will be returned to the client +by the command which invoked the report (following an abort-edit +call). Errors resulting from an abort-report call are ignored. + + set-path: + params: ( path:string rev:number start-empty:bool + ? [ lock-token:string ] ? depth:word ) + + delete-path: + params: ( path:string ) + + link-path: + params: ( path:string url:string rev:number start-empty:bool + ? [ lock-token:string ] ? depth:word ) + + finish-report: + params: ( ) + + abort-report + params: ( ) + +4. Extensibility +---------------- + +This protocol may be extended in three ways, in decreasing order of +desirability: + + * Items may be added to any tuple. An old implementation will + ignore the extra items. + + * Named extensions may be expressed at connection initiation time + by the client or server. + + * The protocol version may be bumped. Clients and servers can then + choose to any range of protocol versions. + +4.1. Extending existing commands + +Extending an existing command is normally done by indicating that its +tuple is allowed to end where it currently ends, for backwards +compatibility, and then tacking on a new, possibly optional, item. + +For example, diff was extended to include a new mandatory text-deltas +parameter like this: + + /* OLD */ diff: + params: ( [ rev:number ] target:string recurse:bool ignore-ancestry:bool + url:string ) + /* NEW */ diff: + params: ( [ rev:number ] target:string recurse:bool ignore-ancestry:bool + url:string ? text-deltas:bool ) + +The "?" says that the tuple is allowed to end here, because an old +client or server wouldn't know to send the new item. + +For optional parameters, a slightly different approach must be used. +set-path was extended to include lock-tokens like this: + + /* OLD */ set-path: + params: ( path:string rev:number start-empty:bool ) + + /* NEW */ set-path: + params: ( path:string rev:number start-empty:bool ? [ lock-token:string ] ) + +The new item appears in brackets because, even in the new protocol, +the lock-token is still optional. However, if there's no lock-token +to send, an empty tuple must still be transmitted so that future +extensions to this command remain possible. diff --git a/subversion/libsvn_ra_svn/ra_svn.h b/subversion/libsvn_ra_svn/ra_svn.h new file mode 100644 index 0000000..1b1924d --- /dev/null +++ b/subversion/libsvn_ra_svn/ra_svn.h @@ -0,0 +1,212 @@ +/* + * ra_svn.h : private declarations for the ra_svn module + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#ifndef RA_SVN_H +#define RA_SVN_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include <apr_network_io.h> +#include <apr_file_io.h> +#include <apr_thread_proc.h> +#include "svn_ra.h" +#include "svn_ra_svn.h" + +/* Callback function that indicates if a svn_ra_svn__stream_t has pending + * data. + */ +typedef svn_boolean_t (*ra_svn_pending_fn_t)(void *baton); + +/* Callback function that sets the timeout value for a svn_ra_svn__stream_t. */ +typedef void (*ra_svn_timeout_fn_t)(void *baton, apr_interval_time_t timeout); + +/* A stream abstraction for ra_svn. + * + * This is different from svn_stream_t in that it provides timeouts and + * the ability to check for pending data. + */ +typedef struct svn_ra_svn__stream_st svn_ra_svn__stream_t; + +/* Handler for blocked writes. */ +typedef svn_error_t *(*ra_svn_block_handler_t)(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + void *baton); + +/* The size of our per-connection read and write buffers. */ +#define SVN_RA_SVN__READBUF_SIZE 4096 +#define SVN_RA_SVN__WRITEBUF_SIZE 4096 + +/* Create forward reference */ +typedef struct svn_ra_svn__session_baton_t svn_ra_svn__session_baton_t; + +/* This structure is opaque to the server. The client pokes at the + * first few fields during setup and cleanup. */ +struct svn_ra_svn_conn_st { + svn_ra_svn__stream_t *stream; + svn_ra_svn__session_baton_t *session; +#ifdef SVN_HAVE_SASL + /* Although all reads and writes go through the svn_ra_svn__stream_t + interface, SASL still needs direct access to the underlying socket + for stuff like IP addresses and port numbers. */ + apr_socket_t *sock; + svn_boolean_t encrypted; +#endif + char read_buf[SVN_RA_SVN__READBUF_SIZE]; + char *read_ptr; + char *read_end; + char write_buf[SVN_RA_SVN__WRITEBUF_SIZE]; + int write_pos; + const char *uuid; + const char *repos_root; + ra_svn_block_handler_t block_handler; + void *block_baton; + apr_hash_t *capabilities; + int compression_level; + char *remote_ip; + apr_pool_t *pool; +}; + +struct svn_ra_svn__session_baton_t { + apr_pool_t *pool; + svn_ra_svn_conn_t *conn; + svn_boolean_t is_tunneled; + const char *url; + const char *user; + const char *hostname; /* The remote hostname. */ + const char *realm_prefix; + const char **tunnel_argv; + const svn_ra_callbacks2_t *callbacks; + void *callbacks_baton; + apr_off_t bytes_read, bytes_written; /* apr_off_t's because that's what + the callback interface uses */ +}; + +/* Set a callback for blocked writes on conn. This handler may + * perform reads on the connection in order to prevent deadlock due to + * pipelining. If callback is NULL, the connection goes back to + * normal blocking I/O for writes. + */ +void svn_ra_svn__set_block_handler(svn_ra_svn_conn_t *conn, + ra_svn_block_handler_t callback, + void *baton); + +/* Return true if there is input waiting on conn. */ +svn_boolean_t svn_ra_svn__input_waiting(svn_ra_svn_conn_t *conn, + apr_pool_t *pool); + +/* CRAM-MD5 client implementation. */ +svn_error_t *svn_ra_svn__cram_client(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *user, const char *password, + const char **message); + +/* Return a pointer to the error chain child of ERR which contains the + * first "real" error message, not merely one of the + * SVN_ERR_RA_SVN_CMD_ERR wrapper errors. */ +svn_error_t *svn_ra_svn__locate_real_error_child(svn_error_t *err); + +/* Return an error chain based on @a params (which contains a + * command response indicating failure). The error chain will be + * in the same order as the errors indicated in @a params. Use + * @a pool for temporary allocations. */ +svn_error_t *svn_ra_svn__handle_failure_status(const apr_array_header_t *params, + apr_pool_t *pool); + +/* Returns a stream that reads/writes from/to SOCK. */ +svn_ra_svn__stream_t *svn_ra_svn__stream_from_sock(apr_socket_t *sock, + apr_pool_t *pool); + +/* Returns a stream that reads from IN_FILE and writes to OUT_FILE. */ +svn_ra_svn__stream_t *svn_ra_svn__stream_from_files(apr_file_t *in_file, + apr_file_t *out_file, + apr_pool_t *pool); + +/* Create an svn_ra_svn__stream_t using READ_CB, WRITE_CB, TIMEOUT_CB, + * PENDING_CB, and BATON. + */ +svn_ra_svn__stream_t *svn_ra_svn__stream_create(void *baton, + svn_read_fn_t read_cb, + svn_write_fn_t write_cb, + ra_svn_timeout_fn_t timeout_cb, + ra_svn_pending_fn_t pending_cb, + apr_pool_t *pool); + +/* Write *LEN bytes from DATA to STREAM, returning the number of bytes + * written in *LEN. + */ +svn_error_t *svn_ra_svn__stream_write(svn_ra_svn__stream_t *stream, + const char *data, apr_size_t *len); + +/* Read *LEN bytes from STREAM into DATA, returning the number of bytes + * read in *LEN. + */ +svn_error_t *svn_ra_svn__stream_read(svn_ra_svn__stream_t *stream, + char *data, apr_size_t *len); + +/* Set the timeout for operations on STREAM to INTERVAL. */ +void svn_ra_svn__stream_timeout(svn_ra_svn__stream_t *stream, + apr_interval_time_t interval); + +/* Return whether or not there is data pending on STREAM. */ +svn_boolean_t svn_ra_svn__stream_pending(svn_ra_svn__stream_t *stream); + +/* Respond to an auth request and perform authentication. Use the Cyrus + * SASL library for mechanism negotiation and for creating authentication + * tokens. */ +svn_error_t * +svn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess, + const apr_array_header_t *mechlist, + const char *realm, apr_pool_t *pool); + +/* Same as svn_ra_svn__do_cyrus_auth, but uses the built-in implementation of + * the CRAM-MD5, ANONYMOUS and EXTERNAL mechanisms. Return the error + * SVN_ERR_RA_SVN_NO_MECHANSIMS if we cannot negotiate an authentication + * mechanism with the server. */ +svn_error_t * +svn_ra_svn__do_internal_auth(svn_ra_svn__session_baton_t *sess, + const apr_array_header_t *mechlist, + const char *realm, apr_pool_t *pool); + +/* Having picked a mechanism, start authentication by writing out an + * auth response. MECH_ARG may be NULL for mechanisms with no + * initial client response. */ +svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const char *mech, const char *mech_arg); + +/* Looks for MECH as a word in MECHLIST (an array of svn_ra_svn_item_t). */ +svn_boolean_t svn_ra_svn__find_mech(const apr_array_header_t *mechlist, + const char *mech); + +/* Initialize the SASL library. */ +svn_error_t *svn_ra_svn__sasl_init(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* RA_SVN_H */ diff --git a/subversion/libsvn_ra_svn/streams.c b/subversion/libsvn_ra_svn/streams.c new file mode 100644 index 0000000..4ae93d5 --- /dev/null +++ b/subversion/libsvn_ra_svn/streams.c @@ -0,0 +1,255 @@ +/* + * streams.c : stream encapsulation routines for the ra_svn protocol + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <apr_general.h> +#include <apr_network_io.h> +#include <apr_poll.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_io.h" +#include "svn_private_config.h" + +#include "ra_svn.h" + +struct svn_ra_svn__stream_st { + svn_stream_t *stream; + void *baton; + ra_svn_pending_fn_t pending_fn; + ra_svn_timeout_fn_t timeout_fn; +}; + +typedef struct sock_baton_t { + apr_socket_t *sock; + apr_pool_t *pool; +} sock_baton_t; + +typedef struct file_baton_t { + apr_file_t *in_file; + apr_file_t *out_file; + apr_pool_t *pool; +} file_baton_t; + +/* Returns TRUE if PFD has pending data, FALSE otherwise. */ +static svn_boolean_t pending(apr_pollfd_t *pfd, apr_pool_t *pool) +{ + apr_status_t status; + int n; + + pfd->p = pool; + pfd->reqevents = APR_POLLIN; + status = apr_poll(pfd, 1, &n, 0); + return (status == APR_SUCCESS && n); +} + +/* Functions to implement a file backed svn_ra_svn__stream_t. */ + +/* Implements svn_read_fn_t */ +static svn_error_t * +file_read_cb(void *baton, char *buffer, apr_size_t *len) +{ + file_baton_t *b = baton; + apr_status_t status = apr_file_read(b->in_file, buffer, len); + + if (status && !APR_STATUS_IS_EOF(status)) + return svn_error_wrap_apr(status, _("Can't read from connection")); + if (*len == 0) + return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, NULL); + return SVN_NO_ERROR; +} + +/* Implements svn_write_fn_t */ +static svn_error_t * +file_write_cb(void *baton, const char *buffer, apr_size_t *len) +{ + file_baton_t *b = baton; + apr_status_t status = apr_file_write(b->out_file, buffer, len); + if (status) + return svn_error_wrap_apr(status, _("Can't write to connection")); + return SVN_NO_ERROR; +} + +/* Implements ra_svn_timeout_fn_t */ +static void +file_timeout_cb(void *baton, apr_interval_time_t interval) +{ + file_baton_t *b = baton; + apr_file_pipe_timeout_set(b->out_file, interval); +} + +/* Implements ra_svn_pending_fn_t */ +static svn_boolean_t +file_pending_cb(void *baton) +{ + file_baton_t *b = baton; + apr_pollfd_t pfd; + + pfd.desc_type = APR_POLL_FILE; + pfd.desc.f = b->in_file; + + return pending(&pfd, b->pool); +} + +svn_ra_svn__stream_t * +svn_ra_svn__stream_from_files(apr_file_t *in_file, + apr_file_t *out_file, + apr_pool_t *pool) +{ + file_baton_t *b = apr_palloc(pool, sizeof(*b)); + + b->in_file = in_file; + b->out_file = out_file; + b->pool = pool; + + return svn_ra_svn__stream_create(b, file_read_cb, file_write_cb, + file_timeout_cb, file_pending_cb, + pool); +} + +/* Functions to implement a socket backed svn_ra_svn__stream_t. */ + +/* Implements svn_read_fn_t */ +static svn_error_t * +sock_read_cb(void *baton, char *buffer, apr_size_t *len) +{ + sock_baton_t *b = baton; + apr_status_t status; + apr_interval_time_t interval; + + status = apr_socket_timeout_get(b->sock, &interval); + if (status) + return svn_error_wrap_apr(status, _("Can't get socket timeout")); + + /* Always block on read. + * During pipelining, we set the timeout to 0 for some write + * operations so that we can try them without blocking. If APR had + * separate timeouts for read and write, we would only set the + * write timeout, but it doesn't. So here, we revert back to blocking. + */ + apr_socket_timeout_set(b->sock, -1); + status = apr_socket_recv(b->sock, buffer, len); + apr_socket_timeout_set(b->sock, interval); + + if (status && !APR_STATUS_IS_EOF(status)) + return svn_error_wrap_apr(status, _("Can't read from connection")); + if (*len == 0) + return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, NULL); + return SVN_NO_ERROR; +} + +/* Implements svn_write_fn_t */ +static svn_error_t * +sock_write_cb(void *baton, const char *buffer, apr_size_t *len) +{ + sock_baton_t *b = baton; + apr_status_t status = apr_socket_send(b->sock, buffer, len); + if (status) + return svn_error_wrap_apr(status, _("Can't write to connection")); + return SVN_NO_ERROR; +} + +/* Implements ra_svn_timeout_fn_t */ +static void +sock_timeout_cb(void *baton, apr_interval_time_t interval) +{ + sock_baton_t *b = baton; + apr_socket_timeout_set(b->sock, interval); +} + +/* Implements ra_svn_pending_fn_t */ +static svn_boolean_t +sock_pending_cb(void *baton) +{ + sock_baton_t *b = baton; + apr_pollfd_t pfd; + + pfd.desc_type = APR_POLL_SOCKET; + pfd.desc.s = b->sock; + + return pending(&pfd, b->pool); +} + +svn_ra_svn__stream_t * +svn_ra_svn__stream_from_sock(apr_socket_t *sock, + apr_pool_t *pool) +{ + sock_baton_t *b = apr_palloc(pool, sizeof(*b)); + + b->sock = sock; + b->pool = pool; + + return svn_ra_svn__stream_create(b, sock_read_cb, sock_write_cb, + sock_timeout_cb, sock_pending_cb, + pool); +} + +svn_ra_svn__stream_t * +svn_ra_svn__stream_create(void *baton, + svn_read_fn_t read_cb, + svn_write_fn_t write_cb, + ra_svn_timeout_fn_t timeout_cb, + ra_svn_pending_fn_t pending_cb, + apr_pool_t *pool) +{ + svn_ra_svn__stream_t *s = apr_palloc(pool, sizeof(*s)); + s->stream = svn_stream_empty(pool); + svn_stream_set_baton(s->stream, baton); + if (read_cb) + svn_stream_set_read(s->stream, read_cb); + if (write_cb) + svn_stream_set_write(s->stream, write_cb); + s->baton = baton; + s->timeout_fn = timeout_cb; + s->pending_fn = pending_cb; + return s; +} + +svn_error_t * +svn_ra_svn__stream_write(svn_ra_svn__stream_t *stream, + const char *data, apr_size_t *len) +{ + return svn_stream_write(stream->stream, data, len); +} + +svn_error_t * +svn_ra_svn__stream_read(svn_ra_svn__stream_t *stream, char *data, + apr_size_t *len) +{ + return svn_stream_read(stream->stream, data, len); +} + +void +svn_ra_svn__stream_timeout(svn_ra_svn__stream_t *stream, + apr_interval_time_t interval) +{ + stream->timeout_fn(stream->baton, interval); +} + +svn_boolean_t +svn_ra_svn__stream_pending(svn_ra_svn__stream_t *stream) +{ + return stream->pending_fn(stream->baton); +} diff --git a/subversion/libsvn_ra_svn/version.c b/subversion/libsvn_ra_svn/version.c new file mode 100644 index 0000000..251a4c1 --- /dev/null +++ b/subversion/libsvn_ra_svn/version.c @@ -0,0 +1,33 @@ +/* + * version.c: library version number + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include "svn_version.h" +#include "svn_ra_svn.h" + +const svn_version_t * +svn_ra_svn_version(void) +{ + SVN_VERSION_BODY; +} |