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