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