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