summaryrefslogtreecommitdiff
path: root/subversion/libsvn_ra_svn
diff options
context:
space:
mode:
authorLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
committerLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
commitf1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch)
tree4223b2035bf2240d681a53822808b3c7f687b905 /subversion/libsvn_ra_svn
downloadsubversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz
Tarball conversion
Diffstat (limited to 'subversion/libsvn_ra_svn')
-rw-r--r--subversion/libsvn_ra_svn/client.c2599
-rw-r--r--subversion/libsvn_ra_svn/cram.c221
-rw-r--r--subversion/libsvn_ra_svn/cyrus_auth.c941
-rw-r--r--subversion/libsvn_ra_svn/editorp.c1005
-rw-r--r--subversion/libsvn_ra_svn/internal_auth.c121
-rw-r--r--subversion/libsvn_ra_svn/marshal.c1112
-rw-r--r--subversion/libsvn_ra_svn/protocol612
-rw-r--r--subversion/libsvn_ra_svn/ra_svn.h212
-rw-r--r--subversion/libsvn_ra_svn/streams.c255
-rw-r--r--subversion/libsvn_ra_svn/version.c33
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, &copy_path,
+ &copy_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, &copy_path, &copy_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, &copy_path, &copy_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, &params));
+ 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, &params));
+ 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, &params);
+ 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, &params));
+ 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, &params);
+ 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;
+}