]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_svn/client.c
Import tzdata 2017c
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_ra_svn / client.c
1 /*
2  * client.c :  Functions for repository access via the Subversion protocol
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23
24
25 \f
26 #include "svn_private_config.h"
27
28 #define APR_WANT_STRFUNC
29 #include <apr_want.h>
30 #include <apr_general.h>
31 #include <apr_strings.h>
32 #include <apr_network_io.h>
33 #include <apr_uri.h>
34
35 #include "svn_hash.h"
36 #include "svn_types.h"
37 #include "svn_string.h"
38 #include "svn_dirent_uri.h"
39 #include "svn_error.h"
40 #include "svn_time.h"
41 #include "svn_path.h"
42 #include "svn_pools.h"
43 #include "svn_config.h"
44 #include "svn_ra.h"
45 #include "svn_ra_svn.h"
46 #include "svn_props.h"
47 #include "svn_mergeinfo.h"
48 #include "svn_version.h"
49 #include "svn_ctype.h"
50
51 #include "svn_private_config.h"
52
53 #include "private/svn_fspath.h"
54 #include "private/svn_subr_private.h"
55
56 #include "../libsvn_ra/ra_loader.h"
57
58 #include "ra_svn.h"
59
60 #ifdef SVN_HAVE_SASL
61 #define DO_AUTH svn_ra_svn__do_cyrus_auth
62 #else
63 #define DO_AUTH svn_ra_svn__do_internal_auth
64 #endif
65
66 /* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for
67    whatever reason) deems svn_depth_immediates as non-recursive, which
68    is ... kinda true, but not true enough for our purposes.  We need
69    our requested recursion level to be *at least* as recursive as the
70    real depth we're looking for.
71  */
72 #define DEPTH_TO_RECURSE(d)    \
73         ((d) == svn_depth_unknown || (d) > svn_depth_files)
74
75 typedef struct ra_svn_commit_callback_baton_t {
76   svn_ra_svn__session_baton_t *sess_baton;
77   apr_pool_t *pool;
78   svn_revnum_t *new_rev;
79   svn_commit_callback2_t callback;
80   void *callback_baton;
81 } ra_svn_commit_callback_baton_t;
82
83 typedef struct ra_svn_reporter_baton_t {
84   svn_ra_svn__session_baton_t *sess_baton;
85   svn_ra_svn_conn_t *conn;
86   apr_pool_t *pool;
87   const svn_delta_editor_t *editor;
88   void *edit_baton;
89 } ra_svn_reporter_baton_t;
90
91 /* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
92    portion. */
93 static void parse_tunnel(const char *url, const char **tunnel,
94                          apr_pool_t *pool)
95 {
96   *tunnel = NULL;
97
98   if (strncasecmp(url, "svn", 3) != 0)
99     return;
100   url += 3;
101
102   /* Get the tunnel specification, if any. */
103   if (*url == '+')
104     {
105       const char *p;
106
107       url++;
108       p = strchr(url, ':');
109       if (!p)
110         return;
111       *tunnel = apr_pstrmemdup(pool, url, p - url);
112     }
113 }
114
115 static svn_error_t *make_connection(const char *hostname, unsigned short port,
116                                     apr_socket_t **sock, apr_pool_t *pool)
117 {
118   apr_sockaddr_t *sa;
119   apr_status_t status;
120   int family = APR_INET;
121
122   /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
123      APR_UNSPEC, because it may give us back an IPV6 address even if we can't
124      create IPV6 sockets.  */
125
126 #if APR_HAVE_IPV6
127 #ifdef MAX_SECS_TO_LINGER
128   status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool);
129 #else
130   status = apr_socket_create(sock, APR_INET6, SOCK_STREAM,
131                              APR_PROTO_TCP, pool);
132 #endif
133   if (status == 0)
134     {
135       apr_socket_close(*sock);
136       family = APR_UNSPEC;
137     }
138 #endif
139
140   /* Resolve the hostname. */
141   status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool);
142   if (status)
143     return svn_error_createf(status, NULL, _("Unknown hostname '%s'"),
144                              hostname);
145   /* Iterate through the returned list of addresses attempting to
146    * connect to each in turn. */
147   do
148     {
149       /* Create the socket. */
150 #ifdef MAX_SECS_TO_LINGER
151       /* ### old APR interface */
152       status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool);
153 #else
154       status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
155                                  pool);
156 #endif
157       if (status == APR_SUCCESS)
158         {
159           status = apr_socket_connect(*sock, sa);
160           if (status != APR_SUCCESS)
161             apr_socket_close(*sock);
162         }
163       sa = sa->next;
164     }
165   while (status != APR_SUCCESS && sa);
166
167   if (status)
168     return svn_error_wrap_apr(status, _("Can't connect to host '%s'"),
169                               hostname);
170
171   /* Enable TCP keep-alives on the socket so we time out when
172    * the connection breaks due to network-layer problems.
173    * If the peer has dropped the connection due to a network partition
174    * or a crash, or if the peer no longer considers the connection
175    * valid because we are behind a NAT and our public IP has changed,
176    * it will respond to the keep-alive probe with a RST instead of an
177    * acknowledgment segment, which will cause svn to abort the session
178    * even while it is currently blocked waiting for data from the peer.
179    * See issue #3347. */
180   status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1);
181   if (status)
182     {
183       /* It's not a fatal error if we cannot enable keep-alives. */
184     }
185
186   return SVN_NO_ERROR;
187 }
188
189 /* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the
190    property diffs in LIST, received from the server. */
191 static svn_error_t *parse_prop_diffs(const apr_array_header_t *list,
192                                      apr_pool_t *pool,
193                                      apr_array_header_t **diffs)
194 {
195   int i;
196
197   *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t));
198
199   for (i = 0; i < list->nelts; i++)
200     {
201       svn_prop_t *prop;
202       svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
203
204       if (elt->kind != SVN_RA_SVN_LIST)
205         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
206                                 _("Prop diffs element not a list"));
207       prop = apr_array_push(*diffs);
208       SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name,
209                                       &prop->value));
210     }
211   return SVN_NO_ERROR;
212 }
213
214 /* Parse a lockdesc, provided in LIST as specified by the protocol into
215    LOCK, allocated in POOL. */
216 static svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool,
217                                svn_lock_t **lock)
218 {
219   const char *cdate, *edate;
220   *lock = svn_lock_create(pool);
221   SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path,
222                                   &(*lock)->token, &(*lock)->owner,
223                                   &(*lock)->comment, &cdate, &edate));
224   (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool);
225   SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool));
226   if (edate)
227     SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool));
228   return SVN_NO_ERROR;
229 }
230
231 /* --- AUTHENTICATION ROUTINES --- */
232
233 svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
234                                        apr_pool_t *pool,
235                                        const char *mech, const char *mech_arg)
236 {
237   return svn_error_trace(svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg));
238 }
239
240 static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess,
241                                         apr_pool_t *pool)
242 {
243   svn_ra_svn_conn_t *conn = sess->conn;
244   apr_array_header_t *mechlist;
245   const char *realm;
246
247   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm));
248   if (mechlist->nelts == 0)
249     return SVN_NO_ERROR;
250   return DO_AUTH(sess, mechlist, realm, pool);
251 }
252
253 /* --- REPORTER IMPLEMENTATION --- */
254
255 static svn_error_t *ra_svn_set_path(void *baton, const char *path,
256                                     svn_revnum_t rev,
257                                     svn_depth_t depth,
258                                     svn_boolean_t start_empty,
259                                     const char *lock_token,
260                                     apr_pool_t *pool)
261 {
262   ra_svn_reporter_baton_t *b = baton;
263
264   SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev,
265                                          start_empty, lock_token, depth));
266   return SVN_NO_ERROR;
267 }
268
269 static svn_error_t *ra_svn_delete_path(void *baton, const char *path,
270                                        apr_pool_t *pool)
271 {
272   ra_svn_reporter_baton_t *b = baton;
273
274   SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path));
275   return SVN_NO_ERROR;
276 }
277
278 static svn_error_t *ra_svn_link_path(void *baton, const char *path,
279                                      const char *url,
280                                      svn_revnum_t rev,
281                                      svn_depth_t depth,
282                                      svn_boolean_t start_empty,
283                                      const char *lock_token,
284                                      apr_pool_t *pool)
285 {
286   ra_svn_reporter_baton_t *b = baton;
287
288   SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev,
289                                           start_empty, lock_token, depth));
290   return SVN_NO_ERROR;
291 }
292
293 static svn_error_t *ra_svn_finish_report(void *baton,
294                                          apr_pool_t *pool)
295 {
296   ra_svn_reporter_baton_t *b = baton;
297
298   SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool));
299   SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
300   SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
301                                    NULL, FALSE));
302   SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, ""));
303   return SVN_NO_ERROR;
304 }
305
306 static svn_error_t *ra_svn_abort_report(void *baton,
307                                         apr_pool_t *pool)
308 {
309   ra_svn_reporter_baton_t *b = baton;
310
311   SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool));
312   return SVN_NO_ERROR;
313 }
314
315 static svn_ra_reporter3_t ra_svn_reporter = {
316   ra_svn_set_path,
317   ra_svn_delete_path,
318   ra_svn_link_path,
319   ra_svn_finish_report,
320   ra_svn_abort_report
321 };
322
323 /* Set *REPORTER and *REPORT_BATON to a new reporter which will drive
324  * EDITOR/EDIT_BATON when it gets the finish_report() call.
325  *
326  * Allocate the new reporter in POOL.
327  */
328 static svn_error_t *
329 ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton,
330                     apr_pool_t *pool,
331                     const svn_delta_editor_t *editor,
332                     void *edit_baton,
333                     const char *target,
334                     svn_depth_t depth,
335                     const svn_ra_reporter3_t **reporter,
336                     void **report_baton)
337 {
338   ra_svn_reporter_baton_t *b;
339   const svn_delta_editor_t *filter_editor;
340   void *filter_baton;
341
342   /* We can skip the depth filtering when the user requested
343      depth_files or depth_infinity because the server will
344      transmit the right stuff anyway. */
345   if ((depth != svn_depth_files) && (depth != svn_depth_infinity)
346       && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH))
347     {
348       SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
349                                             &filter_baton,
350                                             editor, edit_baton, depth,
351                                             *target != '\0',
352                                             pool));
353       editor = filter_editor;
354       edit_baton = filter_baton;
355     }
356
357   b = apr_palloc(pool, sizeof(*b));
358   b->sess_baton = sess_baton;
359   b->conn = sess_baton->conn;
360   b->pool = pool;
361   b->editor = editor;
362   b->edit_baton = edit_baton;
363
364   *reporter = &ra_svn_reporter;
365   *report_baton = b;
366
367   return SVN_NO_ERROR;
368 }
369
370 /* --- RA LAYER IMPLEMENTATION --- */
371
372 /* (Note: *ARGV_P is an output parameter.) */
373 static svn_error_t *find_tunnel_agent(const char *tunnel,
374                                       const char *hostinfo,
375                                       const char ***argv_p,
376                                       apr_hash_t *config, apr_pool_t *pool)
377 {
378   svn_config_t *cfg;
379   const char *val, *var, *cmd;
380   char **cmd_argv;
381   const char **argv;
382   apr_size_t len;
383   apr_status_t status;
384   int n;
385
386   /* Look up the tunnel specification in config. */
387   cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
388   svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
389
390   /* We have one predefined tunnel scheme, if it isn't overridden by config. */
391   if (!val && strcmp(tunnel, "ssh") == 0)
392     {
393       /* Killing the tunnel agent with SIGTERM leads to unsightly
394        * stderr output from ssh, unless we pass -q.
395        * The "-q" option to ssh is widely supported: all versions of
396        * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
397        * versions have it too. If the user is using some other ssh
398        * implementation that doesn't accept it, they can override it
399        * in the [tunnels] section of the config. */
400       val = "$SVN_SSH ssh -q --";
401     }
402
403   if (!val || !*val)
404     return svn_error_createf(SVN_ERR_BAD_URL, NULL,
405                              _("Undefined tunnel scheme '%s'"), tunnel);
406
407   /* If the scheme definition begins with "$varname", it means there
408    * is an environment variable which can override the command. */
409   if (*val == '$')
410     {
411       val++;
412       len = strcspn(val, " ");
413       var = apr_pstrmemdup(pool, val, len);
414       cmd = getenv(var);
415       if (!cmd)
416         {
417           cmd = val + len;
418           while (*cmd == ' ')
419             cmd++;
420           if (!*cmd)
421             return svn_error_createf(SVN_ERR_BAD_URL, NULL,
422                                      _("Tunnel scheme %s requires environment "
423                                        "variable %s to be defined"), tunnel,
424                                      var);
425         }
426     }
427   else
428     cmd = val;
429
430   /* Tokenize the command into a list of arguments. */
431   status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
432   if (status != APR_SUCCESS)
433     return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
434
435   /* Calc number of the fixed arguments. */
436   for (n = 0; cmd_argv[n] != NULL; n++)
437     ;
438
439   argv = apr_palloc(pool, (n + 4) * sizeof(char *));
440
441   /* Append the fixed arguments to the result. */
442   for (n = 0; cmd_argv[n] != NULL; n++)
443     argv[n] = cmd_argv[n];
444
445   argv[n++] = hostinfo;
446   argv[n++] = "svnserve";
447   argv[n++] = "-t";
448   argv[n] = NULL;
449
450   *argv_p = argv;
451   return SVN_NO_ERROR;
452 }
453
454 /* This function handles any errors which occur in the child process
455  * created for a tunnel agent.  We write the error out as a command
456  * failure; the code in ra_svn_open() to read the server's greeting
457  * will see the error and return it to the caller. */
458 static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
459                                        const char *desc)
460 {
461   svn_ra_svn_conn_t *conn;
462   apr_file_t *in_file, *out_file;
463   svn_stream_t *in_stream, *out_stream;
464   svn_error_t *err;
465
466   if (apr_file_open_stdin(&in_file, pool)
467       || apr_file_open_stdout(&out_file, pool))
468     return;
469
470   in_stream = svn_stream_from_aprfile2(in_file, FALSE, pool);
471   out_stream = svn_stream_from_aprfile2(out_file, FALSE, pool);
472
473   conn = svn_ra_svn_create_conn4(NULL, in_stream, out_stream,
474                                  SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
475                                  0, pool);
476   err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
477   svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
478   svn_error_clear(err);
479   svn_error_clear(svn_ra_svn__flush(conn, pool));
480 }
481
482 /* (Note: *CONN is an output parameter.) */
483 static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
484                                 apr_pool_t *pool)
485 {
486   apr_status_t status;
487   apr_proc_t *proc;
488   apr_procattr_t *attr;
489   svn_error_t *err;
490
491   status = apr_procattr_create(&attr, pool);
492   if (status == APR_SUCCESS)
493     status = apr_procattr_io_set(attr, 1, 1, 0);
494   if (status == APR_SUCCESS)
495     status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
496   if (status == APR_SUCCESS)
497     status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
498   proc = apr_palloc(pool, sizeof(*proc));
499   if (status == APR_SUCCESS)
500     status = apr_proc_create(proc, *args, args, NULL, attr, pool);
501   if (status != APR_SUCCESS)
502     return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
503                             svn_error_wrap_apr(status,
504                                                _("Can't create tunnel")), NULL);
505
506   /* Arrange for the tunnel agent to get a SIGTERM on pool
507    * cleanup.  This is a little extreme, but the alternatives
508    * weren't working out.
509    *
510    * Closing the pipes and waiting for the process to die
511    * was prone to mysterious hangs which are difficult to
512    * diagnose (e.g. svnserve dumps core due to unrelated bug;
513    * sshd goes into zombie state; ssh connection is never
514    * closed; ssh never terminates).
515    * See also the long dicussion in issue #2580 if you really
516    * want to know various reasons for these problems and
517    * the different opinions on this issue.
518    *
519    * On Win32, APR does not support KILL_ONLY_ONCE. It only has
520    * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
521    * KILL_ALWAYS, which immediately calls TerminateProcess().
522    * This instantly kills the tunnel, leaving sshd and svnserve
523    * on a remote machine running indefinitely. These processes
524    * accumulate. The problem is most often seen with a fast client
525    * machine and a modest internet connection, as the tunnel
526    * is killed before being able to gracefully complete the
527    * session. In that case, svn is unusable 100% of the time on
528    * the windows machine. Thus, on Win32, we use KILL_NEVER and
529    * take the lesser of two evils.
530    */
531 #ifdef WIN32
532   apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
533 #else
534   apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
535 #endif
536
537   /* APR pipe objects inherit by default.  But we don't want the
538    * tunnel agent's pipes held open by future child processes
539    * (such as other ra_svn sessions), so turn that off. */
540   apr_file_inherit_unset(proc->in);
541   apr_file_inherit_unset(proc->out);
542
543   /* Guard against dotfile output to stdout on the server. */
544   *conn = svn_ra_svn_create_conn4(NULL,
545                                   svn_stream_from_aprfile2(proc->out, FALSE,
546                                                            pool),
547                                   svn_stream_from_aprfile2(proc->in, FALSE,
548                                                            pool),
549                                   SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
550                                   0, 0, pool);
551   err = svn_ra_svn__skip_leading_garbage(*conn, pool);
552   if (err)
553     return svn_error_quick_wrap(
554              err,
555              _("To better debug SSH connection problems, remove the -q "
556                "option from 'ssh' in the [tunnels] section of your "
557                "Subversion configuration file."));
558
559   return SVN_NO_ERROR;
560 }
561
562 /* Parse URL inot URI, validating it and setting the default port if none
563    was given.  Allocate the URI fileds out of POOL. */
564 static svn_error_t *parse_url(const char *url, apr_uri_t *uri,
565                               apr_pool_t *pool)
566 {
567   apr_status_t apr_err;
568
569   apr_err = apr_uri_parse(pool, url, uri);
570
571   if (apr_err != 0)
572     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
573                              _("Illegal svn repository URL '%s'"), url);
574
575   return SVN_NO_ERROR;
576 }
577
578 /* This structure is used as a baton for the pool cleanup function to
579    store tunnel parameters used by the close-tunnel callback. */
580 struct tunnel_data_t {
581   void *tunnel_context;
582   void *tunnel_baton;
583   svn_ra_close_tunnel_func_t close_tunnel;
584   svn_stream_t *request;
585   svn_stream_t *response;
586 };
587
588 /* Pool cleanup function that invokes the close-tunnel callback. */
589 static apr_status_t close_tunnel_cleanup(void *baton)
590 {
591   const struct tunnel_data_t *const td = baton;
592
593   if (td->close_tunnel)
594     td->close_tunnel(td->tunnel_context, td->tunnel_baton);
595
596   svn_error_clear(svn_stream_close(td->request));
597
598   /* We might have one stream to use for both request and response! */
599   if (td->request != td->response)
600     svn_error_clear(svn_stream_close(td->response));
601
602   return APR_SUCCESS; /* ignored */
603 }
604
605 /* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
606    URI is a parsed version of URL.  CALLBACKS and CALLBACKS_BATON
607    are provided by the caller of ra_svn_open. If TUNNEL_NAME is not NULL,
608    it is the name of the tunnel type parsed from the URL scheme.
609    If TUNNEL_ARGV is not NULL, it points to a program argument list to use
610    when invoking the tunnel agent.
611 */
612 static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
613                                  const char *url,
614                                  const apr_uri_t *uri,
615                                  const char *tunnel_name,
616                                  const char **tunnel_argv,
617                                  apr_hash_t *config,
618                                  const svn_ra_callbacks2_t *callbacks,
619                                  void *callbacks_baton,
620                                  svn_auth_baton_t *auth_baton,
621                                  apr_pool_t *result_pool,
622                                  apr_pool_t *scratch_pool)
623 {
624   svn_ra_svn__session_baton_t *sess;
625   svn_ra_svn_conn_t *conn;
626   apr_socket_t *sock;
627   apr_uint64_t minver, maxver;
628   apr_array_header_t *mechlist, *server_caplist, *repos_caplist;
629   const char *client_string = NULL;
630   apr_pool_t *pool = result_pool;
631
632   sess = apr_palloc(pool, sizeof(*sess));
633   sess->pool = pool;
634   sess->is_tunneled = (tunnel_name != NULL);
635   sess->url = apr_pstrdup(pool, url);
636   sess->user = uri->user;
637   sess->hostname = uri->hostname;
638   sess->tunnel_name = tunnel_name;
639   sess->tunnel_argv = tunnel_argv;
640   sess->callbacks = callbacks;
641   sess->callbacks_baton = callbacks_baton;
642   sess->bytes_read = sess->bytes_written = 0;
643   sess->auth_baton = auth_baton;
644
645   if (config)
646     SVN_ERR(svn_config_copy_config(&sess->config, config, pool));
647   else
648     sess->config = NULL;
649
650   if (tunnel_name)
651     {
652       sess->realm_prefix = apr_psprintf(pool, "<svn+%s://%s:%d>",
653                                         tunnel_name,
654                                         uri->hostname, uri->port);
655
656       if (tunnel_argv)
657         SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
658       else
659         {
660           struct tunnel_data_t *const td = apr_palloc(pool, sizeof(*td));
661
662           td->tunnel_baton = callbacks->tunnel_baton;
663           td->close_tunnel = NULL;
664
665           SVN_ERR(callbacks->open_tunnel_func(
666                       &td->request, &td->response,
667                       &td->close_tunnel, &td->tunnel_context,
668                       callbacks->tunnel_baton, tunnel_name,
669                       uri->user, uri->hostname, uri->port,
670                       callbacks->cancel_func, callbacks_baton,
671                       pool));
672
673           apr_pool_cleanup_register(pool, td, close_tunnel_cleanup,
674                                     apr_pool_cleanup_null);
675
676           conn = svn_ra_svn_create_conn4(NULL, td->response, td->request,
677                                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
678                                          0, 0, pool);
679           SVN_ERR(svn_ra_svn__skip_leading_garbage(conn, pool));
680         }
681     }
682   else
683     {
684       sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
685                                         uri->port ? uri->port : SVN_RA_SVN_PORT);
686
687       SVN_ERR(make_connection(uri->hostname,
688                               uri->port ? uri->port : SVN_RA_SVN_PORT,
689                               &sock, pool));
690       conn = svn_ra_svn_create_conn4(sock, NULL, NULL,
691                                      SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
692                                      0, 0, pool);
693     }
694
695   /* Build the useragent string, querying the client for any
696      customizations it wishes to note.  For historical reasons, we
697      still deliver the hard-coded client version info
698      (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
699      separately in the protocol/capabilities handshake below.  But the
700      commit logic wants the combined form for use with the
701      SVN_PROP_TXN_USER_AGENT ephemeral property because that's
702      consistent with our DAV approach.  */
703   if (sess->callbacks->get_client_string != NULL)
704     SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
705                                                &client_string, pool));
706   if (client_string)
707     sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
708                                   client_string, SVN_VA_NULL);
709   else
710     sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
711
712   /* Make sure we set conn->session before reading from it,
713    * because the reader and writer functions expect a non-NULL value. */
714   sess->conn = conn;
715   conn->session = sess;
716
717   /* Read server's greeting. */
718   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
719                                         &mechlist, &server_caplist));
720
721   /* We support protocol version 2. */
722   if (minver > 2)
723     return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
724                              _("Server requires minimum version %d"),
725                              (int) minver);
726   if (maxver < 2)
727     return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
728                              _("Server only supports versions up to %d"),
729                              (int) maxver);
730   SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
731
732   /* All released versions of Subversion support edit-pipeline,
733    * so we do not support servers that do not. */
734   if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
735     return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
736                             _("Server does not support edit pipelining"));
737
738   /* In protocol version 2, we send back our protocol version, our
739    * capability list, and the URL, and subsequently there is an auth
740    * request. */
741   /* Client-side capabilities list: */
742   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)",
743                                   (apr_uint64_t) 2,
744                                   SVN_RA_SVN_CAP_EDIT_PIPELINE,
745                                   SVN_RA_SVN_CAP_SVNDIFF1,
746                                   SVN_RA_SVN_CAP_ABSENT_ENTRIES,
747                                   SVN_RA_SVN_CAP_DEPTH,
748                                   SVN_RA_SVN_CAP_MERGEINFO,
749                                   SVN_RA_SVN_CAP_LOG_REVPROPS,
750                                   url,
751                                   SVN_RA_SVN__DEFAULT_USERAGENT,
752                                   client_string));
753   SVN_ERR(handle_auth_request(sess, pool));
754
755   /* This is where the security layer would go into effect if we
756    * supported security layers, which is a ways off. */
757
758   /* Read the repository's uuid and root URL, and perhaps learn more
759      capabilities that weren't available before now. */
760   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
761                                         &conn->repos_root, &repos_caplist));
762   if (repos_caplist)
763     SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
764
765   if (conn->repos_root)
766     {
767       conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
768       /* We should check that the returned string is a prefix of url, since
769          that's the API guarantee, but this isn't true for 1.0 servers.
770          Checking the length prevents client crashes. */
771       if (strlen(conn->repos_root) > strlen(url))
772         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
773                                 _("Impossibly long repository root from "
774                                   "server"));
775     }
776
777   *sess_p = sess;
778
779   return SVN_NO_ERROR;
780 }
781
782
783 #ifdef SVN_HAVE_SASL
784 #define RA_SVN_DESCRIPTION \
785   N_("Module for accessing a repository using the svn network protocol.\n" \
786      "  - with Cyrus SASL authentication")
787 #else
788 #define RA_SVN_DESCRIPTION \
789   N_("Module for accessing a repository using the svn network protocol.")
790 #endif
791
792 static const char *ra_svn_get_description(apr_pool_t *pool)
793 {
794   return _(RA_SVN_DESCRIPTION);
795 }
796
797 static const char * const *
798 ra_svn_get_schemes(apr_pool_t *pool)
799 {
800   static const char *schemes[] = { "svn", NULL };
801
802   return schemes;
803 }
804
805
806 /* A simple whitelist to ensure the following are valid:
807  *   user@server
808  *   [::1]:22
809  *   server-name
810  *   server_name
811  *   127.0.0.1
812  * with an extra restriction that a leading '-' is invalid.
813  */
814 static svn_boolean_t
815 is_valid_hostinfo(const char *hostinfo)
816 {
817   const char *p = hostinfo;
818
819   if (p[0] == '-')
820     return FALSE;
821
822   while (*p)
823     {
824       if (!svn_ctype_isalnum(*p) && !strchr(":.-_[]@", *p))
825         return FALSE;
826
827       ++p;
828     }
829
830   return TRUE;
831 }
832
833 static svn_error_t *ra_svn_open(svn_ra_session_t *session,
834                                 const char **corrected_url,
835                                 const char *url,
836                                 const svn_ra_callbacks2_t *callbacks,
837                                 void *callback_baton,
838                                 svn_auth_baton_t *auth_baton,
839                                 apr_hash_t *config,
840                                 apr_pool_t *result_pool,
841                                 apr_pool_t *scratch_pool)
842 {
843   apr_pool_t *sess_pool = svn_pool_create(result_pool);
844   svn_ra_svn__session_baton_t *sess;
845   const char *tunnel, **tunnel_argv;
846   apr_uri_t uri;
847   svn_config_t *cfg, *cfg_client;
848
849   /* We don't support server-prescribed redirections in ra-svn. */
850   if (corrected_url)
851     *corrected_url = NULL;
852
853   SVN_ERR(parse_url(url, &uri, sess_pool));
854
855   parse_tunnel(url, &tunnel, result_pool);
856
857   /* Use the default tunnel implementation if we got a tunnel name,
858      but either do not have tunnel handler callbacks installed, or
859      the handlers don't like the tunnel name. */
860   if (tunnel
861       && (!callbacks->open_tunnel_func
862           || (callbacks->check_tunnel_func && callbacks->open_tunnel_func
863               && !callbacks->check_tunnel_func(callbacks->tunnel_baton,
864                                                tunnel))))
865     {
866       const char *decoded_hostinfo;
867
868       decoded_hostinfo = svn_path_uri_decode(uri.hostinfo, result_pool);
869
870       if (!is_valid_hostinfo(decoded_hostinfo))
871         return svn_error_createf(SVN_ERR_BAD_URL, NULL, _("Invalid host '%s'"),
872                                  uri.hostinfo);
873
874       SVN_ERR(find_tunnel_agent(tunnel, decoded_hostinfo, &tunnel_argv,
875                                 config, result_pool));
876     }
877   else
878     tunnel_argv = NULL;
879
880   cfg_client = config
881                ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
882                : NULL;
883   cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
884   svn_auth_set_parameter(auth_baton,
885                          SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
886   svn_auth_set_parameter(auth_baton,
887                          SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
888
889   /* We open the session in a subpool so we can get rid of it if we
890      reparent with a server that doesn't support reparenting. */
891   SVN_ERR(open_session(&sess, url, &uri, tunnel, tunnel_argv, config,
892                        callbacks, callback_baton,
893                        auth_baton, sess_pool, scratch_pool));
894   session->priv = sess;
895
896   return SVN_NO_ERROR;
897 }
898
899 static svn_error_t *ra_svn_dup_session(svn_ra_session_t *new_session,
900                                        svn_ra_session_t *old_session,
901                                        const char *new_session_url,
902                                        apr_pool_t *result_pool,
903                                        apr_pool_t *scratch_pool)
904 {
905   svn_ra_svn__session_baton_t *old_sess = old_session->priv;
906
907   SVN_ERR(ra_svn_open(new_session, NULL, new_session_url,
908                       old_sess->callbacks, old_sess->callbacks_baton,
909                       old_sess->auth_baton, old_sess->config,
910                       result_pool, scratch_pool));
911
912   return SVN_NO_ERROR;
913 }
914
915 static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
916                                     const char *url,
917                                     apr_pool_t *pool)
918 {
919   svn_ra_svn__session_baton_t *sess = ra_session->priv;
920   svn_ra_svn_conn_t *conn = sess->conn;
921   svn_error_t *err;
922   apr_pool_t *sess_pool;
923   svn_ra_svn__session_baton_t *new_sess;
924   apr_uri_t uri;
925
926   SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url));
927   err = handle_auth_request(sess, pool);
928   if (! err)
929     {
930       SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
931       sess->url = apr_pstrdup(sess->pool, url);
932       return SVN_NO_ERROR;
933     }
934   else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
935     return err;
936
937   /* Servers before 1.4 doesn't support this command; try to reconnect
938      instead. */
939   svn_error_clear(err);
940   /* Create a new subpool of the RA session pool. */
941   sess_pool = svn_pool_create(ra_session->pool);
942   err = parse_url(url, &uri, sess_pool);
943   if (! err)
944     err = open_session(&new_sess, url, &uri, sess->tunnel_name, sess->tunnel_argv,
945                        sess->config, sess->callbacks, sess->callbacks_baton,
946                        sess->auth_baton, sess_pool, sess_pool);
947   /* We destroy the new session pool on error, since it is allocated in
948      the main session pool. */
949   if (err)
950     {
951       svn_pool_destroy(sess_pool);
952       return err;
953     }
954
955   /* We have a new connection, assign it and destroy the old. */
956   ra_session->priv = new_sess;
957   svn_pool_destroy(sess->pool);
958
959   return SVN_NO_ERROR;
960 }
961
962 static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
963                                            const char **url, apr_pool_t *pool)
964 {
965   svn_ra_svn__session_baton_t *sess = session->priv;
966   *url = apr_pstrdup(pool, sess->url);
967   return SVN_NO_ERROR;
968 }
969
970 static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
971                                           svn_revnum_t *rev, apr_pool_t *pool)
972 {
973   svn_ra_svn__session_baton_t *sess_baton = session->priv;
974   svn_ra_svn_conn_t *conn = sess_baton->conn;
975
976   SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
977   SVN_ERR(handle_auth_request(sess_baton, pool));
978   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
979   return SVN_NO_ERROR;
980 }
981
982 static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
983                                          svn_revnum_t *rev, apr_time_t tm,
984                                          apr_pool_t *pool)
985 {
986   svn_ra_svn__session_baton_t *sess_baton = session->priv;
987   svn_ra_svn_conn_t *conn = sess_baton->conn;
988
989   SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
990   SVN_ERR(handle_auth_request(sess_baton, pool));
991   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
992   return SVN_NO_ERROR;
993 }
994
995 /* Forward declaration. */
996 static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
997                                           svn_boolean_t *has,
998                                           const char *capability,
999                                           apr_pool_t *pool);
1000
1001 static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
1002                                            const char *name,
1003                                            const svn_string_t *const *old_value_p,
1004                                            const svn_string_t *value,
1005                                            apr_pool_t *pool)
1006 {
1007   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1008   svn_ra_svn_conn_t *conn = sess_baton->conn;
1009   svn_boolean_t dont_care;
1010   const svn_string_t *old_value;
1011   svn_boolean_t has_atomic_revprops;
1012
1013   SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
1014                                 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
1015                                 pool));
1016
1017   if (old_value_p)
1018     {
1019       /* How did you get past the same check in svn_ra_change_rev_prop2()? */
1020       SVN_ERR_ASSERT(has_atomic_revprops);
1021
1022       dont_care = FALSE;
1023       old_value = *old_value_p;
1024     }
1025   else
1026     {
1027       dont_care = TRUE;
1028       old_value = NULL;
1029     }
1030
1031   if (has_atomic_revprops)
1032     SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
1033                                                    value, dont_care,
1034                                                    old_value));
1035   else
1036     SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
1037                                                   value));
1038
1039   SVN_ERR(handle_auth_request(sess_baton, pool));
1040   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1041   return SVN_NO_ERROR;
1042 }
1043
1044 static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
1045                                     apr_pool_t *pool)
1046 {
1047   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1048   svn_ra_svn_conn_t *conn = sess_baton->conn;
1049
1050   *uuid = conn->uuid;
1051   return SVN_NO_ERROR;
1052 }
1053
1054 static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
1055                                           apr_pool_t *pool)
1056 {
1057   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1058   svn_ra_svn_conn_t *conn = sess_baton->conn;
1059
1060   if (!conn->repos_root)
1061     return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
1062                             _("Server did not send repository root"));
1063   *url = conn->repos_root;
1064   return SVN_NO_ERROR;
1065 }
1066
1067 static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
1068                                         apr_hash_t **props, apr_pool_t *pool)
1069 {
1070   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1071   svn_ra_svn_conn_t *conn = sess_baton->conn;
1072   apr_array_header_t *proplist;
1073
1074   SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
1075   SVN_ERR(handle_auth_request(sess_baton, pool));
1076   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
1077   SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1078   return SVN_NO_ERROR;
1079 }
1080
1081 static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
1082                                     const char *name,
1083                                     svn_string_t **value, apr_pool_t *pool)
1084 {
1085   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1086   svn_ra_svn_conn_t *conn = sess_baton->conn;
1087
1088   SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
1089   SVN_ERR(handle_auth_request(sess_baton, pool));
1090   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
1091   return SVN_NO_ERROR;
1092 }
1093
1094 static svn_error_t *ra_svn_end_commit(void *baton)
1095 {
1096   ra_svn_commit_callback_baton_t *ccb = baton;
1097   svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
1098
1099   SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
1100   SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
1101                                  "r(?c)(?c)?(?c)",
1102                                  &(commit_info->revision),
1103                                  &(commit_info->date),
1104                                  &(commit_info->author),
1105                                  &(commit_info->post_commit_err)));
1106
1107   commit_info->repos_root = apr_pstrdup(ccb->pool,
1108                                         ccb->sess_baton->conn->repos_root);
1109
1110   if (ccb->callback)
1111     SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
1112
1113   return SVN_NO_ERROR;
1114 }
1115
1116 static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
1117                                   const svn_delta_editor_t **editor,
1118                                   void **edit_baton,
1119                                   apr_hash_t *revprop_table,
1120                                   svn_commit_callback2_t callback,
1121                                   void *callback_baton,
1122                                   apr_hash_t *lock_tokens,
1123                                   svn_boolean_t keep_locks,
1124                                   apr_pool_t *pool)
1125 {
1126   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1127   svn_ra_svn_conn_t *conn = sess_baton->conn;
1128   ra_svn_commit_callback_baton_t *ccb;
1129   apr_hash_index_t *hi;
1130   apr_pool_t *iterpool;
1131   const svn_string_t *log_msg = svn_hash_gets(revprop_table,
1132                                               SVN_PROP_REVISION_LOG);
1133
1134   if (log_msg == NULL &&
1135       ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1136     {
1137       return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL,
1138                                _("ra_svn does not support not specifying "
1139                                  "a log message with pre-1.5 servers; "
1140                                  "consider passing an empty one, or upgrading "
1141                                  "the server"));
1142     }
1143   else if (log_msg == NULL)
1144     /* 1.5+ server.  Set LOG_MSG to something, since the 'logmsg' argument
1145        to the 'commit' protocol command is non-optional; on the server side,
1146        only REVPROP_TABLE will be used, and LOG_MSG will be ignored.  The
1147        "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit
1148        will have a NULL log message (not just "", really NULL).
1149
1150        svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was
1151        present; this was elevated to a protocol promise in r1498550 (and
1152        later documented in this comment) in order to fix the segmentation
1153        fault bug described in the log message of r1498550.*/
1154     log_msg = svn_string_create("", pool);
1155
1156   /* If we're sending revprops other than svn:log, make sure the server won't
1157      silently ignore them. */
1158   if (apr_hash_count(revprop_table) > 1 &&
1159       ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1160     return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1161                             _("Server doesn't support setting arbitrary "
1162                               "revision properties during commit"));
1163
1164   /* If the server supports ephemeral txnprops, add the one that
1165      reports the client's version level string. */
1166   if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
1167       svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
1168     {
1169       svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
1170                     svn_string_create(SVN_VER_NUMBER, pool));
1171       svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
1172                     svn_string_create(sess_baton->useragent, pool));
1173     }
1174
1175   /* Tell the server we're starting the commit.
1176      Send log message here for backwards compatibility with servers
1177      before 1.5. */
1178   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
1179                                   log_msg->data));
1180   if (lock_tokens)
1181     {
1182       iterpool = svn_pool_create(pool);
1183       for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
1184         {
1185           const void *key;
1186           void *val;
1187           const char *path, *token;
1188
1189           svn_pool_clear(iterpool);
1190           apr_hash_this(hi, &key, NULL, &val);
1191           path = key;
1192           token = val;
1193           SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
1194         }
1195       svn_pool_destroy(iterpool);
1196     }
1197   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
1198   SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
1199   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1200   SVN_ERR(handle_auth_request(sess_baton, pool));
1201   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1202
1203   /* Remember a few arguments for when the commit is over. */
1204   ccb = apr_palloc(pool, sizeof(*ccb));
1205   ccb->sess_baton = sess_baton;
1206   ccb->pool = pool;
1207   ccb->new_rev = NULL;
1208   ccb->callback = callback;
1209   ccb->callback_baton = callback_baton;
1210
1211   /* Fetch an editor for the caller to drive.  The editor will call
1212    * ra_svn_end_commit() upon close_edit(), at which point we'll fill
1213    * in the new_rev, committed_date, and committed_author values. */
1214   svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
1215                         ra_svn_end_commit, ccb);
1216   return SVN_NO_ERROR;
1217 }
1218
1219 /* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
1220    const char * repos relative paths and properties for those paths, storing
1221    the result as an array of svn_prop_inherited_item_t *items. */
1222 static svn_error_t *
1223 parse_iproplist(apr_array_header_t **inherited_props,
1224                 const apr_array_header_t *iproplist,
1225                 svn_ra_session_t *session,
1226                 apr_pool_t *result_pool,
1227                 apr_pool_t *scratch_pool)
1228
1229 {
1230   int i;
1231   apr_pool_t *iterpool;
1232
1233   if (iproplist == NULL)
1234     {
1235       /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1236          capability we shouldn't be asking for inherited props, but if we
1237          did and the server sent back nothing then we'll want to handle
1238          that. */
1239       *inherited_props = NULL;
1240       return SVN_NO_ERROR;
1241     }
1242
1243   *inherited_props = apr_array_make(
1244     result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1245
1246   iterpool = svn_pool_create(scratch_pool);
1247
1248   for (i = 0; i < iproplist->nelts; i++)
1249     {
1250       apr_array_header_t *iprop_list;
1251       char *parent_rel_path;
1252       apr_hash_t *iprops;
1253       apr_hash_index_t *hi;
1254       svn_prop_inherited_item_t *new_iprop =
1255         apr_palloc(result_pool, sizeof(*new_iprop));
1256       svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
1257                                               svn_ra_svn_item_t);
1258       if (elt->kind != SVN_RA_SVN_LIST)
1259         return svn_error_create(
1260           SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1261           _("Inherited proplist element not a list"));
1262
1263       svn_pool_clear(iterpool);
1264
1265       SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
1266                                       &parent_rel_path, &iprop_list));
1267       SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1268       new_iprop->path_or_url = apr_pstrdup(result_pool, parent_rel_path);
1269       new_iprop->prop_hash = svn_hash__make(result_pool);
1270       for (hi = apr_hash_first(iterpool, iprops);
1271            hi;
1272            hi = apr_hash_next(hi))
1273         {
1274           const char *name = apr_hash_this_key(hi);
1275           svn_string_t *value = apr_hash_this_val(hi);
1276           svn_hash_sets(new_iprop->prop_hash,
1277                         apr_pstrdup(result_pool, name),
1278                         svn_string_dup(value, result_pool));
1279         }
1280       APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1281         new_iprop;
1282     }
1283   svn_pool_destroy(iterpool);
1284   return SVN_NO_ERROR;
1285 }
1286
1287 static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1288                                     svn_revnum_t rev, svn_stream_t *stream,
1289                                     svn_revnum_t *fetched_rev,
1290                                     apr_hash_t **props,
1291                                     apr_pool_t *pool)
1292 {
1293   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1294   svn_ra_svn_conn_t *conn = sess_baton->conn;
1295   apr_array_header_t *proplist;
1296   const char *expected_digest;
1297   svn_checksum_t *expected_checksum = NULL;
1298   svn_checksum_ctx_t *checksum_ctx;
1299   apr_pool_t *iterpool;
1300
1301   SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1302                                          (props != NULL), (stream != NULL)));
1303   SVN_ERR(handle_auth_request(sess_baton, pool));
1304   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1305                                         &expected_digest,
1306                                         &rev, &proplist));
1307
1308   if (fetched_rev)
1309     *fetched_rev = rev;
1310   if (props)
1311     SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1312
1313   /* We're done if the contents weren't wanted. */
1314   if (!stream)
1315     return SVN_NO_ERROR;
1316
1317   if (expected_digest)
1318     {
1319       SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1320                                      expected_digest, pool));
1321       checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1322     }
1323
1324   /* Read the file's contents. */
1325   iterpool = svn_pool_create(pool);
1326   while (1)
1327     {
1328       svn_ra_svn_item_t *item;
1329
1330       svn_pool_clear(iterpool);
1331       SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1332       if (item->kind != SVN_RA_SVN_STRING)
1333         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1334                                 _("Non-string as part of file contents"));
1335       if (item->u.string->len == 0)
1336         break;
1337
1338       if (expected_checksum)
1339         SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
1340                                     item->u.string->len));
1341
1342       SVN_ERR(svn_stream_write(stream, item->u.string->data,
1343                                &item->u.string->len));
1344     }
1345   svn_pool_destroy(iterpool);
1346
1347   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1348
1349   if (expected_checksum)
1350     {
1351       svn_checksum_t *checksum;
1352
1353       SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1354       if (!svn_checksum_match(checksum, expected_checksum))
1355         return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1356                                          _("Checksum mismatch for '%s'"),
1357                                          path);
1358     }
1359
1360   return SVN_NO_ERROR;
1361 }
1362
1363 static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1364                                    apr_hash_t **dirents,
1365                                    svn_revnum_t *fetched_rev,
1366                                    apr_hash_t **props,
1367                                    const char *path,
1368                                    svn_revnum_t rev,
1369                                    apr_uint32_t dirent_fields,
1370                                    apr_pool_t *pool)
1371 {
1372   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1373   svn_ra_svn_conn_t *conn = sess_baton->conn;
1374   apr_array_header_t *proplist, *dirlist;
1375   int i;
1376
1377   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1378                                   rev, (props != NULL), (dirents != NULL)));
1379   if (dirent_fields & SVN_DIRENT_KIND)
1380     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
1381   if (dirent_fields & SVN_DIRENT_SIZE)
1382     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
1383   if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1384     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1385   if (dirent_fields & SVN_DIRENT_CREATED_REV)
1386     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
1387   if (dirent_fields & SVN_DIRENT_TIME)
1388     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1389   if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1390     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1391
1392   /* Always send the, nominally optional, want-iprops as "false" to
1393      workaround a bug in svnserve 1.8.0-1.8.8 that causes the server
1394      to see "true" if it is omitted. */
1395   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE));
1396
1397   SVN_ERR(handle_auth_request(sess_baton, pool));
1398   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1399                                         &dirlist));
1400
1401   if (fetched_rev)
1402     *fetched_rev = rev;
1403   if (props)
1404     SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1405
1406   /* We're done if dirents aren't wanted. */
1407   if (!dirents)
1408     return SVN_NO_ERROR;
1409
1410   /* Interpret the directory list. */
1411   *dirents = svn_hash__make(pool);
1412   for (i = 0; i < dirlist->nelts; i++)
1413     {
1414       const char *name, *kind, *cdate, *cauthor;
1415       svn_boolean_t has_props;
1416       svn_dirent_t *dirent;
1417       apr_uint64_t size;
1418       svn_revnum_t crev;
1419       svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1420
1421       if (elt->kind != SVN_RA_SVN_LIST)
1422         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1423                                 _("Dirlist element not a list"));
1424       SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1425                                       &name, &kind, &size, &has_props,
1426                                       &crev, &cdate, &cauthor));
1427
1428       /* Nothing to sanitize here.  Any multi-segment path is simply
1429          illegal in the hash returned by svn_ra_get_dir2. */
1430       if (strchr(name, '/'))
1431         return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1432                                  _("Invalid directory entry name '%s'"),
1433                                  name);
1434
1435       dirent = svn_dirent_create(pool);
1436       dirent->kind = svn_node_kind_from_word(kind);
1437       dirent->size = size;/* FIXME: svn_filesize_t */
1438       dirent->has_props = has_props;
1439       dirent->created_rev = crev;
1440       /* NOTE: the tuple's format string says CDATE may be NULL. But this
1441          function does not allow that. The server has always sent us some
1442          random date, however, so this just happens to work. But let's
1443          be wary of servers that are (improperly) fixed to send NULL.
1444
1445          Note: they should NOT be "fixed" to send NULL, as that would break
1446          any older clients which received that NULL. But we may as well
1447          be defensive against a malicous server.  */
1448       if (cdate == NULL)
1449         dirent->time = 0;
1450       else
1451         SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1452       dirent->last_author = cauthor;
1453       svn_hash_sets(*dirents, name, dirent);
1454     }
1455
1456   return SVN_NO_ERROR;
1457 }
1458
1459 /* Converts a apr_uint64_t with values TRUE, FALSE or
1460    SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1461    to a svn_tristate_t */
1462 static svn_tristate_t
1463 optbool_to_tristate(apr_uint64_t v)
1464 {
1465   if (v == TRUE)  /* not just non-zero but exactly equal to 'TRUE' */
1466     return svn_tristate_true;
1467   if (v == FALSE)
1468     return svn_tristate_false;
1469
1470   return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1471 }
1472
1473 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1474    server, which defaults to youngest. */
1475 static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1476                                          svn_mergeinfo_catalog_t *catalog,
1477                                          const apr_array_header_t *paths,
1478                                          svn_revnum_t revision,
1479                                          svn_mergeinfo_inheritance_t inherit,
1480                                          svn_boolean_t include_descendants,
1481                                          apr_pool_t *pool)
1482 {
1483   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1484   svn_ra_svn_conn_t *conn = sess_baton->conn;
1485   int i;
1486   apr_array_header_t *mergeinfo_tuple;
1487   svn_ra_svn_item_t *elt;
1488   const char *path;
1489
1490   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1491   for (i = 0; i < paths->nelts; i++)
1492     {
1493       path = APR_ARRAY_IDX(paths, i, const char *);
1494       SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1495     }
1496   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1497                                   svn_inheritance_to_word(inherit),
1498                                   include_descendants));
1499
1500   SVN_ERR(handle_auth_request(sess_baton, pool));
1501   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1502
1503   *catalog = NULL;
1504   if (mergeinfo_tuple->nelts > 0)
1505     {
1506       *catalog = svn_hash__make(pool);
1507       for (i = 0; i < mergeinfo_tuple->nelts; i++)
1508         {
1509           svn_mergeinfo_t for_path;
1510           const char *to_parse;
1511
1512           elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1513           if (elt->kind != SVN_RA_SVN_LIST)
1514             return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1515                                     _("Mergeinfo element is not a list"));
1516           SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
1517                                           &path, &to_parse));
1518           SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1519           /* Correct for naughty servers that send "relative" paths
1520              with leading slashes! */
1521           svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
1522         }
1523     }
1524
1525   return SVN_NO_ERROR;
1526 }
1527
1528 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1529                                   const svn_ra_reporter3_t **reporter,
1530                                   void **report_baton, svn_revnum_t rev,
1531                                   const char *target, svn_depth_t depth,
1532                                   svn_boolean_t send_copyfrom_args,
1533                                   svn_boolean_t ignore_ancestry,
1534                                   const svn_delta_editor_t *update_editor,
1535                                   void *update_baton,
1536                                   apr_pool_t *pool,
1537                                   apr_pool_t *scratch_pool)
1538 {
1539   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1540   svn_ra_svn_conn_t *conn = sess_baton->conn;
1541   svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1542
1543   /* Tell the server we want to start an update. */
1544   SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1545                                        depth, send_copyfrom_args,
1546                                        ignore_ancestry));
1547   SVN_ERR(handle_auth_request(sess_baton, pool));
1548
1549   /* Fetch a reporter for the caller to drive.  The reporter will drive
1550    * update_editor upon finish_report(). */
1551   SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1552                               target, depth, reporter, report_baton));
1553   return SVN_NO_ERROR;
1554 }
1555
1556 static svn_error_t *
1557 ra_svn_switch(svn_ra_session_t *session,
1558               const svn_ra_reporter3_t **reporter,
1559               void **report_baton, svn_revnum_t rev,
1560               const char *target, svn_depth_t depth,
1561               const char *switch_url,
1562               svn_boolean_t send_copyfrom_args,
1563               svn_boolean_t ignore_ancestry,
1564               const svn_delta_editor_t *update_editor,
1565               void *update_baton,
1566               apr_pool_t *result_pool,
1567               apr_pool_t *scratch_pool)
1568 {
1569   apr_pool_t *pool = result_pool;
1570   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1571   svn_ra_svn_conn_t *conn = sess_baton->conn;
1572   svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1573
1574   /* Tell the server we want to start a switch. */
1575   SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1576                                        switch_url, depth,
1577                                        send_copyfrom_args, ignore_ancestry));
1578   SVN_ERR(handle_auth_request(sess_baton, pool));
1579
1580   /* Fetch a reporter for the caller to drive.  The reporter will drive
1581    * update_editor upon finish_report(). */
1582   SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1583                               target, depth, reporter, report_baton));
1584   return SVN_NO_ERROR;
1585 }
1586
1587 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1588                                   const svn_ra_reporter3_t **reporter,
1589                                   void **report_baton,
1590                                   const char *target, svn_revnum_t rev,
1591                                   svn_depth_t depth,
1592                                   const svn_delta_editor_t *status_editor,
1593                                   void *status_baton, apr_pool_t *pool)
1594 {
1595   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1596   svn_ra_svn_conn_t *conn = sess_baton->conn;
1597   svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1598
1599   /* Tell the server we want to start a status operation. */
1600   SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1601                                        depth));
1602   SVN_ERR(handle_auth_request(sess_baton, pool));
1603
1604   /* Fetch a reporter for the caller to drive.  The reporter will drive
1605    * status_editor upon finish_report(). */
1606   SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1607                               target, depth, reporter, report_baton));
1608   return SVN_NO_ERROR;
1609 }
1610
1611 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1612                                 const svn_ra_reporter3_t **reporter,
1613                                 void **report_baton,
1614                                 svn_revnum_t rev, const char *target,
1615                                 svn_depth_t depth,
1616                                 svn_boolean_t ignore_ancestry,
1617                                 svn_boolean_t text_deltas,
1618                                 const char *versus_url,
1619                                 const svn_delta_editor_t *diff_editor,
1620                                 void *diff_baton, apr_pool_t *pool)
1621 {
1622   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1623   svn_ra_svn_conn_t *conn = sess_baton->conn;
1624   svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1625
1626   /* Tell the server we want to start a diff. */
1627   SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1628                                      ignore_ancestry, versus_url,
1629                                      text_deltas, depth));
1630   SVN_ERR(handle_auth_request(sess_baton, pool));
1631
1632   /* Fetch a reporter for the caller to drive.  The reporter will drive
1633    * diff_editor upon finish_report(). */
1634   SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1635                               target, depth, reporter, report_baton));
1636   return SVN_NO_ERROR;
1637 }
1638
1639
1640 static svn_error_t *
1641 perform_ra_svn_log(svn_error_t **outer_error,
1642                    svn_ra_session_t *session,
1643                    const apr_array_header_t *paths,
1644                    svn_revnum_t start, svn_revnum_t end,
1645                    int limit,
1646                    svn_boolean_t discover_changed_paths,
1647                    svn_boolean_t strict_node_history,
1648                    svn_boolean_t include_merged_revisions,
1649                    const apr_array_header_t *revprops,
1650                    svn_log_entry_receiver_t receiver,
1651                    void *receiver_baton,
1652                    apr_pool_t *pool)
1653 {
1654   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1655   svn_ra_svn_conn_t *conn = sess_baton->conn;
1656   apr_pool_t *iterpool;
1657   int i;
1658   int nest_level = 0;
1659   const char *path;
1660   char *name;
1661   svn_boolean_t want_custom_revprops;
1662   svn_boolean_t want_author = FALSE;
1663   svn_boolean_t want_message = FALSE;
1664   svn_boolean_t want_date = FALSE;
1665   int nreceived = 0;
1666
1667   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1668   if (paths)
1669     {
1670       for (i = 0; i < paths->nelts; i++)
1671         {
1672           path = APR_ARRAY_IDX(paths, i, const char *);
1673           SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1674         }
1675     }
1676   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1677                                   discover_changed_paths, strict_node_history,
1678                                   (apr_uint64_t) limit,
1679                                   include_merged_revisions));
1680   if (revprops)
1681     {
1682       want_custom_revprops = FALSE;
1683       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1684       for (i = 0; i < revprops->nelts; i++)
1685         {
1686           name = APR_ARRAY_IDX(revprops, i, char *);
1687           SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1688
1689           if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1690             want_author = TRUE;
1691           else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1692             want_date = TRUE;
1693           else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1694             want_message = TRUE;
1695           else
1696             want_custom_revprops = TRUE;
1697         }
1698       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1699     }
1700   else
1701     {
1702       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1703
1704       want_author = TRUE;
1705       want_date = TRUE;
1706       want_message = TRUE;
1707       want_custom_revprops = TRUE;
1708     }
1709
1710   SVN_ERR(handle_auth_request(sess_baton, pool));
1711
1712   /* Read the log messages. */
1713   iterpool = svn_pool_create(pool);
1714   while (1)
1715     {
1716       apr_uint64_t has_children_param, invalid_revnum_param;
1717       apr_uint64_t has_subtractive_merge_param;
1718       svn_string_t *author, *date, *message;
1719       apr_array_header_t *cplist, *rplist;
1720       svn_log_entry_t *log_entry;
1721       svn_boolean_t has_children;
1722       svn_boolean_t subtractive_merge = FALSE;
1723       apr_uint64_t revprop_count;
1724       svn_ra_svn_item_t *item;
1725       apr_hash_t *cphash;
1726       svn_revnum_t rev;
1727
1728       svn_pool_clear(iterpool);
1729       SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1730       if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1731         break;
1732       if (item->kind != SVN_RA_SVN_LIST)
1733         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1734                                 _("Log entry not a list"));
1735       SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
1736                                       "lr(?s)(?s)(?s)?BBnl?B",
1737                                       &cplist, &rev, &author, &date,
1738                                       &message, &has_children_param,
1739                                       &invalid_revnum_param,
1740                                       &revprop_count, &rplist,
1741                                       &has_subtractive_merge_param));
1742       if (want_custom_revprops && rplist == NULL)
1743         {
1744           /* Caller asked for custom revprops, but server is too old. */
1745           return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1746                                   _("Server does not support custom revprops"
1747                                     " via log"));
1748         }
1749
1750       if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1751         has_children = FALSE;
1752       else
1753         has_children = (svn_boolean_t) has_children_param;
1754
1755       if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1756         subtractive_merge = FALSE;
1757       else
1758         subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1759
1760       /* Because the svn protocol won't let us send an invalid revnum, we have
1761          to recover that fact using the extra parameter. */
1762       if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1763             && invalid_revnum_param)
1764         rev = SVN_INVALID_REVNUM;
1765
1766       if (cplist->nelts > 0)
1767         {
1768           /* Interpret the changed-paths list. */
1769           cphash = svn_hash__make(iterpool);
1770           for (i = 0; i < cplist->nelts; i++)
1771             {
1772               svn_log_changed_path2_t *change;
1773               svn_string_t *cpath;
1774               const char *copy_path, *action, *kind_str;
1775               apr_uint64_t text_mods, prop_mods;
1776               svn_revnum_t copy_rev;
1777               svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
1778                                                       svn_ra_svn_item_t);
1779
1780               if (elt->kind != SVN_RA_SVN_LIST)
1781                 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1782                                         _("Changed-path entry not a list"));
1783               SVN_ERR(svn_ra_svn__read_data_log_changed_entry(elt->u.list,
1784                                               &cpath, &action, &copy_path,
1785                                               &copy_rev, &kind_str,
1786                                               &text_mods, &prop_mods));
1787
1788               if (!svn_fspath__is_canonical(cpath->data))
1789                 {
1790                   cpath->data = svn_fspath__canonicalize(cpath->data, iterpool);
1791                   cpath->len = strlen(cpath->data);
1792                 }
1793               if (copy_path && !svn_fspath__is_canonical(copy_path))
1794                 copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1795
1796               change = svn_log_changed_path2_create(iterpool);
1797               change->action = *action;
1798               change->copyfrom_path = copy_path;
1799               change->copyfrom_rev = copy_rev;
1800               change->node_kind = svn_node_kind_from_word(kind_str);
1801               change->text_modified = optbool_to_tristate(text_mods);
1802               change->props_modified = optbool_to_tristate(prop_mods);
1803               apr_hash_set(cphash, cpath->data, cpath->len, change);
1804             }
1805         }
1806       else
1807         cphash = NULL;
1808
1809       /* Invoke RECEIVER
1810           - Except if the server sends more than a >= 1 limit top level items
1811           - Or when the callback reported a SVN_ERR_CEASE_INVOCATION
1812             in an earlier invocation. */
1813       if (! (limit && (nest_level == 0) && (++nreceived > limit))
1814           && ! *outer_error)
1815         {
1816           svn_error_t *err;
1817           log_entry = svn_log_entry_create(iterpool);
1818
1819           log_entry->changed_paths = cphash;
1820           log_entry->changed_paths2 = cphash;
1821           log_entry->revision = rev;
1822           log_entry->has_children = has_children;
1823           log_entry->subtractive_merge = subtractive_merge;
1824           if (rplist)
1825             SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
1826                                                &log_entry->revprops));
1827           if (log_entry->revprops == NULL)
1828             log_entry->revprops = svn_hash__make(iterpool);
1829
1830           if (author && want_author)
1831             svn_hash_sets(log_entry->revprops,
1832                           SVN_PROP_REVISION_AUTHOR, author);
1833           if (date && want_date)
1834             svn_hash_sets(log_entry->revprops,
1835                           SVN_PROP_REVISION_DATE, date);
1836           if (message && want_message)
1837             svn_hash_sets(log_entry->revprops,
1838                           SVN_PROP_REVISION_LOG, message);
1839
1840           err = receiver(receiver_baton, log_entry, iterpool);
1841           if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1842             {
1843               *outer_error = svn_error_trace(
1844                                 svn_error_compose_create(*outer_error, err));
1845             }
1846           else
1847             SVN_ERR(err);
1848
1849           if (log_entry->has_children)
1850             {
1851               nest_level++;
1852             }
1853           if (! SVN_IS_VALID_REVNUM(log_entry->revision))
1854             {
1855               SVN_ERR_ASSERT(nest_level);
1856               nest_level--;
1857             }
1858         }
1859     }
1860   svn_pool_destroy(iterpool);
1861
1862   /* Read the response. */
1863   return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1864 }
1865
1866 static svn_error_t *
1867 ra_svn_log(svn_ra_session_t *session,
1868            const apr_array_header_t *paths,
1869            svn_revnum_t start, svn_revnum_t end,
1870            int limit,
1871            svn_boolean_t discover_changed_paths,
1872            svn_boolean_t strict_node_history,
1873            svn_boolean_t include_merged_revisions,
1874            const apr_array_header_t *revprops,
1875            svn_log_entry_receiver_t receiver,
1876            void *receiver_baton, apr_pool_t *pool)
1877 {
1878   svn_error_t *outer_error = NULL;
1879   svn_error_t *err;
1880
1881   err = svn_error_trace(perform_ra_svn_log(&outer_error,
1882                                            session, paths,
1883                                            start, end,
1884                                            limit,
1885                                            discover_changed_paths,
1886                                            strict_node_history,
1887                                            include_merged_revisions,
1888                                            revprops,
1889                                            receiver, receiver_baton,
1890                                            pool));
1891   return svn_error_trace(
1892             svn_error_compose_create(outer_error,
1893                                      err));
1894 }
1895
1896
1897
1898 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1899                                       const char *path, svn_revnum_t rev,
1900                                       svn_node_kind_t *kind, apr_pool_t *pool)
1901 {
1902   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1903   svn_ra_svn_conn_t *conn = sess_baton->conn;
1904   const char *kind_word;
1905
1906   SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
1907   SVN_ERR(handle_auth_request(sess_baton, pool));
1908   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
1909   *kind = svn_node_kind_from_word(kind_word);
1910   return SVN_NO_ERROR;
1911 }
1912
1913
1914 /* If ERR is a command not supported error, wrap it in a
1915    SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG.  Else, return err. */
1916 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1917                                            const char *msg)
1918 {
1919   if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1920     return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1921                             _(msg));
1922   return err;
1923 }
1924
1925
1926 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1927                                 const char *path, svn_revnum_t rev,
1928                                 svn_dirent_t **dirent, apr_pool_t *pool)
1929 {
1930   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1931   svn_ra_svn_conn_t *conn = sess_baton->conn;
1932   apr_array_header_t *list = NULL;
1933   svn_dirent_t *the_dirent;
1934
1935   SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
1936   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1937                                  N_("'stat' not implemented")));
1938   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
1939
1940   if (! list)
1941     {
1942       *dirent = NULL;
1943     }
1944   else
1945     {
1946       const char *kind, *cdate, *cauthor;
1947       svn_boolean_t has_props;
1948       svn_revnum_t crev;
1949       apr_uint64_t size;
1950
1951       SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
1952                                       &kind, &size, &has_props,
1953                                       &crev, &cdate, &cauthor));
1954
1955       the_dirent = svn_dirent_create(pool);
1956       the_dirent->kind = svn_node_kind_from_word(kind);
1957       the_dirent->size = size;/* FIXME: svn_filesize_t */
1958       the_dirent->has_props = has_props;
1959       the_dirent->created_rev = crev;
1960       SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1961       the_dirent->last_author = cauthor;
1962
1963       *dirent = the_dirent;
1964     }
1965
1966   return SVN_NO_ERROR;
1967 }
1968
1969
1970 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1971                                          apr_hash_t **locations,
1972                                          const char *path,
1973                                          svn_revnum_t peg_revision,
1974                                          const apr_array_header_t *location_revisions,
1975                                          apr_pool_t *pool)
1976 {
1977   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1978   svn_ra_svn_conn_t *conn = sess_baton->conn;
1979   svn_revnum_t revision;
1980   svn_boolean_t is_done;
1981   int i;
1982
1983   /* Transmit the parameters. */
1984   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
1985                                   "get-locations", path, peg_revision));
1986   for (i = 0; i < location_revisions->nelts; i++)
1987     {
1988       revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1989       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
1990     }
1991
1992   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1993
1994   /* Servers before 1.1 don't support this command. Check for this here. */
1995   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1996                                  N_("'get-locations' not implemented")));
1997
1998   /* Read the hash items. */
1999   is_done = FALSE;
2000   *locations = apr_hash_make(pool);
2001   while (!is_done)
2002     {
2003       svn_ra_svn_item_t *item;
2004       const char *ret_path;
2005
2006       SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
2007       if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2008         is_done = 1;
2009       else if (item->kind != SVN_RA_SVN_LIST)
2010         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2011                                 _("Location entry not a list"));
2012       else
2013         {
2014           SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
2015                                           &revision, &ret_path));
2016           ret_path = svn_fspath__canonicalize(ret_path, pool);
2017           apr_hash_set(*locations, apr_pmemdup(pool, &revision,
2018                                                sizeof(revision)),
2019                        sizeof(revision), ret_path);
2020         }
2021     }
2022
2023   /* Read the response. This is so the server would have a chance to
2024    * report an error. */
2025   return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
2026 }
2027
2028 static svn_error_t *
2029 ra_svn_get_location_segments(svn_ra_session_t *session,
2030                              const char *path,
2031                              svn_revnum_t peg_revision,
2032                              svn_revnum_t start_rev,
2033                              svn_revnum_t end_rev,
2034                              svn_location_segment_receiver_t receiver,
2035                              void *receiver_baton,
2036                              apr_pool_t *pool)
2037 {
2038   svn_ra_svn__session_baton_t *sess_baton = session->priv;
2039   svn_ra_svn_conn_t *conn = sess_baton->conn;
2040   svn_boolean_t is_done;
2041   apr_pool_t *iterpool = svn_pool_create(pool);
2042
2043   /* Transmit the parameters. */
2044   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
2045                                   "get-location-segments",
2046                                   path, peg_revision, start_rev, end_rev));
2047
2048   /* Servers before 1.5 don't support this command. Check for this here. */
2049   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2050                                  N_("'get-location-segments'"
2051                                     " not implemented")));
2052
2053   /* Parse the response. */
2054   is_done = FALSE;
2055   while (!is_done)
2056     {
2057       svn_revnum_t range_start, range_end;
2058       svn_ra_svn_item_t *item;
2059       const char *ret_path;
2060
2061       svn_pool_clear(iterpool);
2062       SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
2063       if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2064         is_done = 1;
2065       else if (item->kind != SVN_RA_SVN_LIST)
2066         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2067                                 _("Location segment entry not a list"));
2068       else
2069         {
2070           svn_location_segment_t *segment = apr_pcalloc(iterpool,
2071                                                         sizeof(*segment));
2072           SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
2073                                           &range_start, &range_end, &ret_path));
2074           if (! (SVN_IS_VALID_REVNUM(range_start)
2075                  && SVN_IS_VALID_REVNUM(range_end)))
2076             return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2077                                     _("Expected valid revision range"));
2078           if (ret_path)
2079             ret_path = svn_relpath_canonicalize(ret_path, iterpool);
2080           segment->path = ret_path;
2081           segment->range_start = range_start;
2082           segment->range_end = range_end;
2083           SVN_ERR(receiver(segment, receiver_baton, iterpool));
2084         }
2085     }
2086   svn_pool_destroy(iterpool);
2087
2088   /* Read the response. This is so the server would have a chance to
2089    * report an error. */
2090   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2091
2092   return SVN_NO_ERROR;
2093 }
2094
2095 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
2096                                          const char *path,
2097                                          svn_revnum_t start, svn_revnum_t end,
2098                                          svn_boolean_t include_merged_revisions,
2099                                          svn_file_rev_handler_t handler,
2100                                          void *handler_baton, apr_pool_t *pool)
2101 {
2102   svn_ra_svn__session_baton_t *sess_baton = session->priv;
2103   apr_pool_t *rev_pool, *chunk_pool;
2104   svn_boolean_t has_txdelta;
2105   svn_boolean_t had_revision = FALSE;
2106
2107   /* One sub-pool for each revision and one for each txdelta chunk.
2108      Note that the rev_pool must live during the following txdelta. */
2109   rev_pool = svn_pool_create(pool);
2110   chunk_pool = svn_pool_create(pool);
2111
2112   SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
2113                                               path, start, end,
2114                                               include_merged_revisions));
2115
2116   /* Servers before 1.1 don't support this command.  Check for this here. */
2117   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2118                                  N_("'get-file-revs' not implemented")));
2119
2120   while (1)
2121     {
2122       apr_array_header_t *rev_proplist, *proplist;
2123       apr_uint64_t merged_rev_param;
2124       apr_array_header_t *props;
2125       svn_ra_svn_item_t *item;
2126       apr_hash_t *rev_props;
2127       svn_revnum_t rev;
2128       const char *p;
2129       svn_boolean_t merged_rev;
2130       svn_txdelta_window_handler_t d_handler;
2131       void *d_baton;
2132
2133       svn_pool_clear(rev_pool);
2134       svn_pool_clear(chunk_pool);
2135       SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
2136       if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2137         break;
2138       /* Either we've got a correct revision or we will error out below. */
2139       had_revision = TRUE;
2140       if (item->kind != SVN_RA_SVN_LIST)
2141         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2142                                 _("Revision entry not a list"));
2143
2144       SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
2145                                       "crll?B", &p, &rev, &rev_proplist,
2146                                       &proplist, &merged_rev_param));
2147       p = svn_fspath__canonicalize(p, rev_pool);
2148       SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
2149       SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
2150       if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2151         merged_rev = FALSE;
2152       else
2153         merged_rev = (svn_boolean_t) merged_rev_param;
2154
2155       /* Get the first delta chunk so we know if there is a delta. */
2156       SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
2157       if (item->kind != SVN_RA_SVN_STRING)
2158         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2159                                 _("Text delta chunk not a string"));
2160       has_txdelta = item->u.string->len > 0;
2161
2162       SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2163                       has_txdelta ? &d_handler : NULL, &d_baton,
2164                       props, rev_pool));
2165
2166       /* Process the text delta if any. */
2167       if (has_txdelta)
2168         {
2169           svn_stream_t *stream;
2170
2171           if (d_handler && d_handler != svn_delta_noop_window_handler)
2172             stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2173                                                rev_pool);
2174           else
2175             stream = NULL;
2176           while (item->u.string->len > 0)
2177             {
2178               apr_size_t size;
2179
2180               size = item->u.string->len;
2181               if (stream)
2182                 SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
2183               svn_pool_clear(chunk_pool);
2184
2185               SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2186                                             &item));
2187               if (item->kind != SVN_RA_SVN_STRING)
2188                 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2189                                         _("Text delta chunk not a string"));
2190             }
2191           if (stream)
2192             SVN_ERR(svn_stream_close(stream));
2193         }
2194     }
2195
2196   SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2197
2198   /* Return error if we didn't get any revisions. */
2199   if (!had_revision)
2200     return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2201                             _("The get-file-revs command didn't return "
2202                               "any revisions"));
2203
2204   svn_pool_destroy(chunk_pool);
2205   svn_pool_destroy(rev_pool);
2206
2207   return SVN_NO_ERROR;
2208 }
2209
2210 /* For each path in PATH_REVS, send a 'lock' command to the server.
2211    Used with 1.2.x series servers which support locking, but of only
2212    one path at a time.  ra_svn_lock(), which supports 'lock-many'
2213    is now the default.  See svn_ra_lock() docstring for interface details. */
2214 static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
2215                                        apr_hash_t *path_revs,
2216                                        const char *comment,
2217                                        svn_boolean_t steal_lock,
2218                                        svn_ra_lock_callback_t lock_func,
2219                                        void *lock_baton,
2220                                        apr_pool_t *pool)
2221 {
2222   svn_ra_svn__session_baton_t *sess = session->priv;
2223   svn_ra_svn_conn_t* conn = sess->conn;
2224   apr_array_header_t *list;
2225   apr_hash_index_t *hi;
2226   apr_pool_t *iterpool = svn_pool_create(pool);
2227
2228   for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2229     {
2230       svn_lock_t *lock;
2231       const void *key;
2232       const char *path;
2233       void *val;
2234       svn_revnum_t *revnum;
2235       svn_error_t *err, *callback_err = NULL;
2236
2237       svn_pool_clear(iterpool);
2238
2239       apr_hash_this(hi, &key, NULL, &val);
2240       path = key;
2241       revnum = val;
2242
2243       SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2244                                          steal_lock, *revnum));
2245
2246       /* Servers before 1.2 doesn't support locking.  Check this here. */
2247       SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2248                                      N_("Server doesn't support "
2249                                         "the lock command")));
2250
2251       err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2252
2253       if (!err)
2254         SVN_ERR(parse_lock(list, iterpool, &lock));
2255
2256       if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2257         return err;
2258
2259       if (lock_func)
2260         callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2261                                  err, iterpool);
2262
2263       svn_error_clear(err);
2264
2265       if (callback_err)
2266         return callback_err;
2267     }
2268
2269   svn_pool_destroy(iterpool);
2270
2271   return SVN_NO_ERROR;
2272 }
2273
2274 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2275    Used with 1.2.x series servers which support unlocking, but of only
2276    one path at a time.  ra_svn_unlock(), which supports 'unlock-many' is
2277    now the default.  See svn_ra_unlock() docstring for interface details. */
2278 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2279                                          apr_hash_t *path_tokens,
2280                                          svn_boolean_t break_lock,
2281                                          svn_ra_lock_callback_t lock_func,
2282                                          void *lock_baton,
2283                                          apr_pool_t *pool)
2284 {
2285   svn_ra_svn__session_baton_t *sess = session->priv;
2286   svn_ra_svn_conn_t* conn = sess->conn;
2287   apr_hash_index_t *hi;
2288   apr_pool_t *iterpool = svn_pool_create(pool);
2289
2290   for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2291     {
2292       const void *key;
2293       const char *path;
2294       void *val;
2295       const char *token;
2296       svn_error_t *err, *callback_err = NULL;
2297
2298       svn_pool_clear(iterpool);
2299
2300       apr_hash_this(hi, &key, NULL, &val);
2301       path = key;
2302       if (strcmp(val, "") != 0)
2303         token = val;
2304       else
2305         token = NULL;
2306
2307       SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2308                                            break_lock));
2309
2310       /* Servers before 1.2 don't support locking.  Check this here. */
2311       SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2312                                      N_("Server doesn't support the unlock "
2313                                         "command")));
2314
2315       err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2316
2317       if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2318         return err;
2319
2320       if (lock_func)
2321         callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2322
2323       svn_error_clear(err);
2324
2325       if (callback_err)
2326         return callback_err;
2327     }
2328
2329   svn_pool_destroy(iterpool);
2330
2331   return SVN_NO_ERROR;
2332 }
2333
2334 /* Tell the server to lock all paths in PATH_REVS.
2335    See svn_ra_lock() for interface details. */
2336 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
2337                                 apr_hash_t *path_revs,
2338                                 const char *comment,
2339                                 svn_boolean_t steal_lock,
2340                                 svn_ra_lock_callback_t lock_func,
2341                                 void *lock_baton,
2342                                 apr_pool_t *pool)
2343 {
2344   svn_ra_svn__session_baton_t *sess = session->priv;
2345   svn_ra_svn_conn_t *conn = sess->conn;
2346   apr_hash_index_t *hi;
2347   svn_error_t *err;
2348   apr_pool_t *iterpool = svn_pool_create(pool);
2349
2350   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2351                                   comment, steal_lock));
2352
2353   for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2354     {
2355       const void *key;
2356       const char *path;
2357       void *val;
2358       svn_revnum_t *revnum;
2359
2360       svn_pool_clear(iterpool);
2361       apr_hash_this(hi, &key, NULL, &val);
2362       path = key;
2363       revnum = val;
2364
2365       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
2366     }
2367
2368   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2369
2370   err = handle_auth_request(sess, pool);
2371
2372   /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
2373    * to 'lock'. */
2374   if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2375     {
2376       svn_error_clear(err);
2377       return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2378                                 lock_func, lock_baton, pool);
2379     }
2380
2381   if (err)
2382     return err;
2383
2384   /* Loop over responses to get lock information. */
2385   for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2386     {
2387       svn_ra_svn_item_t *elt;
2388       const void *key;
2389       const char *path;
2390       svn_error_t *callback_err;
2391       const char *status;
2392       svn_lock_t *lock;
2393       apr_array_header_t *list;
2394
2395       apr_hash_this(hi, &key, NULL, NULL);
2396       path = key;
2397
2398       svn_pool_clear(iterpool);
2399       SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2400
2401       /* The server might have encountered some sort of fatal error in
2402          the middle of the request list.  If this happens, it will
2403          transmit "done" to end the lock-info early, and then the
2404          overall command response will talk about the fatal error. */
2405       if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
2406         break;
2407
2408       if (elt->kind != SVN_RA_SVN_LIST)
2409         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2410                                 _("Lock response not a list"));
2411
2412       SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2413                                       &list));
2414
2415       if (strcmp(status, "failure") == 0)
2416         err = svn_ra_svn__handle_failure_status(list, iterpool);
2417       else if (strcmp(status, "success") == 0)
2418         {
2419           SVN_ERR(parse_lock(list, iterpool, &lock));
2420           err = NULL;
2421         }
2422       else
2423         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2424                                 _("Unknown status for lock command"));
2425
2426       if (lock_func)
2427         callback_err = lock_func(lock_baton, path, TRUE,
2428                                  err ? NULL : lock,
2429                                  err, iterpool);
2430       else
2431         callback_err = SVN_NO_ERROR;
2432
2433       svn_error_clear(err);
2434
2435       if (callback_err)
2436         return callback_err;
2437     }
2438
2439   /* If we didn't break early above, and the whole hash was traversed,
2440      read the final "done" from the server. */
2441   if (!hi)
2442     {
2443       svn_ra_svn_item_t *elt;
2444
2445       SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2446       if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2447         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2448                                 _("Didn't receive end marker for lock "
2449                                   "responses"));
2450     }
2451
2452   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2453
2454   svn_pool_destroy(iterpool);
2455
2456   return SVN_NO_ERROR;
2457 }
2458
2459 /* Tell the server to unlock all paths in PATH_TOKENS.
2460    See svn_ra_unlock() for interface details. */
2461 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2462                                   apr_hash_t *path_tokens,
2463                                   svn_boolean_t break_lock,
2464                                   svn_ra_lock_callback_t lock_func,
2465                                   void *lock_baton,
2466                                   apr_pool_t *pool)
2467 {
2468   svn_ra_svn__session_baton_t *sess = session->priv;
2469   svn_ra_svn_conn_t *conn = sess->conn;
2470   apr_hash_index_t *hi;
2471   apr_pool_t *iterpool = svn_pool_create(pool);
2472   svn_error_t *err;
2473   const char *path;
2474
2475   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2476                                   break_lock));
2477
2478   for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2479     {
2480       void *val;
2481       const void *key;
2482       const char *token;
2483
2484       svn_pool_clear(iterpool);
2485       apr_hash_this(hi, &key, NULL, &val);
2486       path = key;
2487
2488       if (strcmp(val, "") != 0)
2489         token = val;
2490       else
2491         token = NULL;
2492
2493       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2494     }
2495
2496   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2497
2498   err = handle_auth_request(sess, pool);
2499
2500   /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2501    * to 'unlock'.
2502    */
2503   if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2504     {
2505       svn_error_clear(err);
2506       return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2507                                   lock_baton, pool);
2508     }
2509
2510   if (err)
2511     return err;
2512
2513   /* Loop over responses to unlock files. */
2514   for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2515     {
2516       svn_ra_svn_item_t *elt;
2517       const void *key;
2518       svn_error_t *callback_err;
2519       const char *status;
2520       apr_array_header_t *list;
2521
2522       svn_pool_clear(iterpool);
2523
2524       SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2525
2526       /* The server might have encountered some sort of fatal error in
2527          the middle of the request list.  If this happens, it will
2528          transmit "done" to end the lock-info early, and then the
2529          overall command response will talk about the fatal error. */
2530       if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2531         break;
2532
2533       apr_hash_this(hi, &key, NULL, NULL);
2534       path = key;
2535
2536       if (elt->kind != SVN_RA_SVN_LIST)
2537         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2538                                 _("Unlock response not a list"));
2539
2540       SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2541                                       &list));
2542
2543       if (strcmp(status, "failure") == 0)
2544         err = svn_ra_svn__handle_failure_status(list, iterpool);
2545       else if (strcmp(status, "success") == 0)
2546         {
2547           SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
2548           err = SVN_NO_ERROR;
2549         }
2550       else
2551         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2552                                 _("Unknown status for unlock command"));
2553
2554       if (lock_func)
2555         callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2556                                  iterpool);
2557       else
2558         callback_err = SVN_NO_ERROR;
2559
2560       svn_error_clear(err);
2561
2562       if (callback_err)
2563         return callback_err;
2564     }
2565
2566   /* If we didn't break early above, and the whole hash was traversed,
2567      read the final "done" from the server. */
2568   if (!hi)
2569     {
2570       svn_ra_svn_item_t *elt;
2571
2572       SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2573       if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2574         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2575                                 _("Didn't receive end marker for unlock "
2576                                   "responses"));
2577     }
2578
2579   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2580
2581   svn_pool_destroy(iterpool);
2582
2583   return SVN_NO_ERROR;
2584 }
2585
2586 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2587                                     svn_lock_t **lock,
2588                                     const char *path,
2589                                     apr_pool_t *pool)
2590 {
2591   svn_ra_svn__session_baton_t *sess = session->priv;
2592   svn_ra_svn_conn_t* conn = sess->conn;
2593   apr_array_header_t *list;
2594
2595   SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2596
2597   /* Servers before 1.2 doesn't support locking.  Check this here. */
2598   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2599                                  N_("Server doesn't support the get-lock "
2600                                     "command")));
2601
2602   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2603   if (list)
2604     SVN_ERR(parse_lock(list, pool, lock));
2605   else
2606     *lock = NULL;
2607
2608   return SVN_NO_ERROR;
2609 }
2610
2611 /* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2612    to prevent a dependency cycle. */
2613 static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2614                                           const char **rel_path,
2615                                           const char *url,
2616                                           apr_pool_t *pool)
2617 {
2618   const char *root_url;
2619
2620   SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2621   *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2622   if (! *rel_path)
2623     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2624                              _("'%s' isn't a child of repository root "
2625                                "URL '%s'"),
2626                              url, root_url);
2627   return SVN_NO_ERROR;
2628 }
2629
2630 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2631                                      apr_hash_t **locks,
2632                                      const char *path,
2633                                      svn_depth_t depth,
2634                                      apr_pool_t *pool)
2635 {
2636   svn_ra_svn__session_baton_t *sess = session->priv;
2637   svn_ra_svn_conn_t* conn = sess->conn;
2638   apr_array_header_t *list;
2639   const char *full_url, *abs_path;
2640   int i;
2641
2642   /* Figure out the repository abspath from PATH. */
2643   full_url = svn_path_url_add_component2(sess->url, path, pool);
2644   SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2645   abs_path = svn_fspath__canonicalize(abs_path, pool);
2646
2647   SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2648
2649   /* Servers before 1.2 doesn't support locking.  Check this here. */
2650   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2651                                  N_("Server doesn't support the get-lock "
2652                                     "command")));
2653
2654   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2655
2656   *locks = apr_hash_make(pool);
2657
2658   for (i = 0; i < list->nelts; ++i)
2659     {
2660       svn_lock_t *lock;
2661       svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2662
2663       if (elt->kind != SVN_RA_SVN_LIST)
2664         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2665                                 _("Lock element not a list"));
2666       SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2667
2668       /* Filter out unwanted paths.  Since Subversion only allows
2669          locks on files, we can treat depth=immediates the same as
2670          depth=files for filtering purposes.  Meaning, we'll keep
2671          this lock if:
2672
2673          a) its path is the very path we queried, or
2674          b) we've asked for a fully recursive answer, or
2675          c) we've asked for depth=files or depth=immediates, and this
2676             lock is on an immediate child of our query path.
2677       */
2678       if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2679         {
2680           svn_hash_sets(*locks, lock->path, lock);
2681         }
2682       else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2683         {
2684           const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2685           if (relpath && (svn_path_component_count(relpath) == 1))
2686             svn_hash_sets(*locks, lock->path, lock);
2687         }
2688     }
2689
2690   return SVN_NO_ERROR;
2691 }
2692
2693
2694 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2695                                   svn_revnum_t revision,
2696                                   svn_revnum_t low_water_mark,
2697                                   svn_boolean_t send_deltas,
2698                                   const svn_delta_editor_t *editor,
2699                                   void *edit_baton,
2700                                   apr_pool_t *pool)
2701 {
2702   svn_ra_svn__session_baton_t *sess = session->priv;
2703
2704   SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2705                                        low_water_mark, send_deltas));
2706
2707   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2708                                  N_("Server doesn't support the replay "
2709                                     "command")));
2710
2711   SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2712                                    NULL, TRUE));
2713
2714   return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2715 }
2716
2717
2718 static svn_error_t *
2719 ra_svn_replay_range(svn_ra_session_t *session,
2720                     svn_revnum_t start_revision,
2721                     svn_revnum_t end_revision,
2722                     svn_revnum_t low_water_mark,
2723                     svn_boolean_t send_deltas,
2724                     svn_ra_replay_revstart_callback_t revstart_func,
2725                     svn_ra_replay_revfinish_callback_t revfinish_func,
2726                     void *replay_baton,
2727                     apr_pool_t *pool)
2728 {
2729   svn_ra_svn__session_baton_t *sess = session->priv;
2730   apr_pool_t *iterpool;
2731   svn_revnum_t rev;
2732   svn_boolean_t drive_aborted = FALSE;
2733
2734   SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2735                                              start_revision, end_revision,
2736                                              low_water_mark, send_deltas));
2737
2738   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2739                                  N_("Server doesn't support the "
2740                                     "replay-range command")));
2741
2742   iterpool = svn_pool_create(pool);
2743   for (rev = start_revision; rev <= end_revision; rev++)
2744     {
2745       const svn_delta_editor_t *editor;
2746       void *edit_baton;
2747       apr_hash_t *rev_props;
2748       const char *word;
2749       apr_array_header_t *list;
2750
2751       svn_pool_clear(iterpool);
2752
2753       SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
2754                                      "wl", &word, &list));
2755       if (strcmp(word, "revprops") != 0)
2756         return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2757                                  _("Expected 'revprops', found '%s'"),
2758                                  word);
2759
2760       SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
2761
2762       SVN_ERR(revstart_func(rev, replay_baton,
2763                             &editor, &edit_baton,
2764                             rev_props,
2765                             iterpool));
2766       SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2767                                        editor, edit_baton,
2768                                        &drive_aborted, TRUE));
2769       /* If drive_editor2() aborted the commit, do NOT try to call
2770          revfinish_func and commit the transaction! */
2771       if (drive_aborted) {
2772         svn_pool_destroy(iterpool);
2773         return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
2774                                 _("Error while replaying commit"));
2775       }
2776       SVN_ERR(revfinish_func(rev, replay_baton,
2777                              editor, edit_baton,
2778                              rev_props,
2779                              iterpool));
2780     }
2781   svn_pool_destroy(iterpool);
2782
2783   return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2784 }
2785
2786
2787 static svn_error_t *
2788 ra_svn_has_capability(svn_ra_session_t *session,
2789                       svn_boolean_t *has,
2790                       const char *capability,
2791                       apr_pool_t *pool)
2792 {
2793   svn_ra_svn__session_baton_t *sess = session->priv;
2794   static const char* capabilities[][2] =
2795   {
2796       /* { ra capability string, svn:// wire capability string} */
2797       {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
2798       {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
2799       {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
2800       {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
2801       {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
2802       {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
2803       {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
2804       {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2805                                           SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
2806       {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
2807                                        SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
2808
2809       {NULL, NULL} /* End of list marker */
2810   };
2811   int i;
2812
2813   *has = FALSE;
2814
2815   for (i = 0; capabilities[i][0]; i++)
2816     {
2817       if (strcmp(capability, capabilities[i][0]) == 0)
2818         {
2819           *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
2820           return SVN_NO_ERROR;
2821         }
2822     }
2823
2824   return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2825                            _("Don't know anything about capability '%s'"),
2826                            capability);
2827 }
2828
2829 static svn_error_t *
2830 ra_svn_get_deleted_rev(svn_ra_session_t *session,
2831                        const char *path,
2832                        svn_revnum_t peg_revision,
2833                        svn_revnum_t end_revision,
2834                        svn_revnum_t *revision_deleted,
2835                        apr_pool_t *pool)
2836
2837 {
2838   svn_ra_svn__session_baton_t *sess_baton = session->priv;
2839   svn_ra_svn_conn_t *conn = sess_baton->conn;
2840
2841   /* Transmit the parameters. */
2842   SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
2843                                                peg_revision, end_revision));
2844
2845   /* Servers before 1.6 don't support this command.  Check for this here. */
2846   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2847                                  N_("'get-deleted-rev' not implemented")));
2848
2849   return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r",
2850                                                        revision_deleted));
2851 }
2852
2853 static svn_error_t *
2854 ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
2855                                       svn_delta_shim_callbacks_t *callbacks)
2856 {
2857   svn_ra_svn__session_baton_t *sess_baton = session->priv;
2858   svn_ra_svn_conn_t *conn = sess_baton->conn;
2859
2860   conn->shim_callbacks = callbacks;
2861
2862   return SVN_NO_ERROR;
2863 }
2864
2865 static svn_error_t *
2866 ra_svn_get_inherited_props(svn_ra_session_t *session,
2867                            apr_array_header_t **iprops,
2868                            const char *path,
2869                            svn_revnum_t revision,
2870                            apr_pool_t *result_pool,
2871                            apr_pool_t *scratch_pool)
2872 {
2873   svn_ra_svn__session_baton_t *sess_baton = session->priv;
2874   svn_ra_svn_conn_t *conn = sess_baton->conn;
2875   apr_array_header_t *iproplist;
2876   svn_boolean_t iprop_capable;
2877
2878   SVN_ERR(ra_svn_has_capability(session, &iprop_capable,
2879                                 SVN_RA_CAPABILITY_INHERITED_PROPS,
2880                                 scratch_pool));
2881
2882   /* If we don't support native iprop handling, use the implementation
2883      in libsvn_ra */
2884   if (!iprop_capable)
2885     return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
2886
2887   SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
2888                                            path, revision));
2889   SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
2890   SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
2891   SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
2892                           scratch_pool));
2893
2894   return SVN_NO_ERROR;
2895 }
2896
2897 static const svn_ra__vtable_t ra_svn_vtable = {
2898   svn_ra_svn_version,
2899   ra_svn_get_description,
2900   ra_svn_get_schemes,
2901   ra_svn_open,
2902   ra_svn_dup_session,
2903   ra_svn_reparent,
2904   ra_svn_get_session_url,
2905   ra_svn_get_latest_rev,
2906   ra_svn_get_dated_rev,
2907   ra_svn_change_rev_prop,
2908   ra_svn_rev_proplist,
2909   ra_svn_rev_prop,
2910   ra_svn_commit,
2911   ra_svn_get_file,
2912   ra_svn_get_dir,
2913   ra_svn_get_mergeinfo,
2914   ra_svn_update,
2915   ra_svn_switch,
2916   ra_svn_status,
2917   ra_svn_diff,
2918   ra_svn_log,
2919   ra_svn_check_path,
2920   ra_svn_stat,
2921   ra_svn_get_uuid,
2922   ra_svn_get_repos_root,
2923   ra_svn_get_locations,
2924   ra_svn_get_location_segments,
2925   ra_svn_get_file_revs,
2926   ra_svn_lock,
2927   ra_svn_unlock,
2928   ra_svn_get_lock,
2929   ra_svn_get_locks,
2930   ra_svn_replay,
2931   ra_svn_has_capability,
2932   ra_svn_replay_range,
2933   ra_svn_get_deleted_rev,
2934   ra_svn_register_editor_shim_callbacks,
2935   ra_svn_get_inherited_props
2936 };
2937
2938 svn_error_t *
2939 svn_ra_svn__init(const svn_version_t *loader_version,
2940                  const svn_ra__vtable_t **vtable,
2941                  apr_pool_t *pool)
2942 {
2943   static const svn_version_checklist_t checklist[] =
2944     {
2945       { "svn_subr",  svn_subr_version },
2946       { "svn_delta", svn_delta_version },
2947       { NULL, NULL }
2948     };
2949
2950   SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal));
2951
2952   /* Simplified version check to make sure we can safely use the
2953      VTABLE parameter. The RA loader does a more exhaustive check. */
2954   if (loader_version->major != SVN_VER_MAJOR)
2955     {
2956       return svn_error_createf
2957         (SVN_ERR_VERSION_MISMATCH, NULL,
2958          _("Unsupported RA loader version (%d) for ra_svn"),
2959          loader_version->major);
2960     }
2961
2962   *vtable = &ra_svn_vtable;
2963
2964 #ifdef SVN_HAVE_SASL
2965   SVN_ERR(svn_ra_svn__sasl_init());
2966 #endif
2967
2968   return SVN_NO_ERROR;
2969 }
2970
2971 /* Compatibility wrapper for the 1.1 and before API. */
2972 #define NAME "ra_svn"
2973 #define DESCRIPTION RA_SVN_DESCRIPTION
2974 #define VTBL ra_svn_vtable
2975 #define INITFUNC svn_ra_svn__init
2976 #define COMPAT_INITFUNC svn_ra_svn_init
2977 #include "../libsvn_ra/wrapper_template.h"