]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/svnserve/serve.c
dts: Update our copy for arm, arm64 and riscv dts to Linux 5.5
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / svnserve / serve.c
1 /*
2  * serve.c :  Functions for serving 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 \f
27 #include <limits.h> /* for UINT_MAX */
28 #include <stdarg.h>
29
30 #define APR_WANT_STRFUNC
31 #include <apr_want.h>
32 #include <apr_general.h>
33 #include <apr_lib.h>
34 #include <apr_strings.h>
35
36 #include "svn_compat.h"
37 #include "svn_private_config.h"  /* For SVN_PATH_LOCAL_SEPARATOR */
38 #include "svn_hash.h"
39 #include "svn_types.h"
40 #include "svn_string.h"
41 #include "svn_pools.h"
42 #include "svn_error.h"
43 #include "svn_ra.h"              /* for SVN_RA_CAPABILITY_* */
44 #include "svn_ra_svn.h"
45 #include "svn_repos.h"
46 #include "svn_dirent_uri.h"
47 #include "svn_path.h"
48 #include "svn_time.h"
49 #include "svn_config.h"
50 #include "svn_props.h"
51 #include "svn_mergeinfo.h"
52 #include "svn_user.h"
53
54 #include "private/svn_log.h"
55 #include "private/svn_mergeinfo_private.h"
56 #include "private/svn_ra_svn_private.h"
57 #include "private/svn_fspath.h"
58
59 #ifdef HAVE_UNISTD_H
60 #include <unistd.h>   /* For getpid() */
61 #endif
62
63 #include "server.h"
64 #include "logger.h"
65
66 typedef struct commit_callback_baton_t {
67   apr_pool_t *pool;
68   svn_revnum_t *new_rev;
69   const char **date;
70   const char **author;
71   const char **post_commit_err;
72 } commit_callback_baton_t;
73
74 typedef struct report_driver_baton_t {
75   server_baton_t *sb;
76   const char *repos_url;  /* Decoded repository URL. */
77   void *report_baton;
78   svn_error_t *err;
79   /* so update() can distinguish checkout from update in logging */
80   int entry_counter;
81   svn_boolean_t only_empty_entries;
82   /* for diff() logging */
83   svn_revnum_t *from_rev;
84 } report_driver_baton_t;
85
86 typedef struct log_baton_t {
87   const char *fs_path;
88   svn_ra_svn_conn_t *conn;
89   int stack_depth;
90
91   /* Set to TRUE when at least one changed path has been sent. */
92   svn_boolean_t started;
93 } log_baton_t;
94
95 typedef struct file_revs_baton_t {
96   svn_ra_svn_conn_t *conn;
97   apr_pool_t *pool;  /* Pool provided in the handler call. */
98 } file_revs_baton_t;
99
100 typedef struct fs_warning_baton_t {
101   server_baton_t *server;
102   svn_ra_svn_conn_t *conn;
103 } fs_warning_baton_t;
104
105 typedef struct authz_baton_t {
106   server_baton_t *server;
107   svn_ra_svn_conn_t *conn;
108 } authz_baton_t;
109
110 /* svn_error_create() a new error, log_server_error() it, and
111    return it. */
112 static void
113 log_error(svn_error_t *err, server_baton_t *server)
114 {
115   logger__log_error(server->logger, err, server->repository,
116                     server->client_info);
117 }
118
119 /* svn_error_create() a new error, log_server_error() it, and
120    return it. */
121 static svn_error_t *
122 error_create_and_log(apr_status_t apr_err, svn_error_t *child,
123                      const char *message, server_baton_t *server)
124 {
125   svn_error_t *err = svn_error_create(apr_err, child, message);
126   log_error(err, server);
127   return err;
128 }
129
130 /* Log a failure ERR, transmit ERR back to the client (as part of a
131    "failure" notification), consume ERR, and flush the connection. */
132 static svn_error_t *
133 log_fail_and_flush(svn_error_t *err, server_baton_t *server,
134                    svn_ra_svn_conn_t *conn, apr_pool_t *pool)
135 {
136   svn_error_t *io_err;
137
138   log_error(err, server);
139   io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
140   svn_error_clear(err);
141   SVN_ERR(io_err);
142   return svn_ra_svn__flush(conn, pool);
143 }
144
145 /* Log a client command. */
146 static svn_error_t *log_command(server_baton_t *b,
147                                 svn_ra_svn_conn_t *conn,
148                                 apr_pool_t *pool,
149                                 const char *fmt, ...)
150 {
151   const char *remote_host, *timestr, *log, *line;
152   va_list ap;
153   apr_size_t nbytes;
154
155   if (b->logger == NULL)
156     return SVN_NO_ERROR;
157
158   remote_host = svn_ra_svn_conn_remote_host(conn);
159   timestr = svn_time_to_cstring(apr_time_now(), pool);
160
161   va_start(ap, fmt);
162   log = apr_pvsprintf(pool, fmt, ap);
163   va_end(ap);
164
165   line = apr_psprintf(pool, "%" APR_PID_T_FMT
166                       " %s %s %s %s %s" APR_EOL_STR,
167                       getpid(), timestr,
168                       (remote_host ? remote_host : "-"),
169                       (b->client_info->user ? b->client_info->user : "-"),
170                       b->repository->repos_name, log);
171   nbytes = strlen(line);
172
173   return logger__write(b->logger, line, nbytes);
174 }
175
176 /* Log an authz failure */
177 static svn_error_t *
178 log_authz_denied(const char *path,
179                  svn_repos_authz_access_t required,
180                  server_baton_t *b,
181                  apr_pool_t *pool)
182 {
183   const char *timestr, *remote_host, *line;
184
185   if (!b->logger)
186     return SVN_NO_ERROR;
187
188   if (!b->client_info || !b->client_info->user)
189     return SVN_NO_ERROR;
190
191   timestr = svn_time_to_cstring(apr_time_now(), pool);
192   remote_host = b->client_info->remote_host;
193
194   line = apr_psprintf(pool, "%" APR_PID_T_FMT
195                       " %s %s %s %s Authorization Failed %s%s %s" APR_EOL_STR,
196                       getpid(), timestr,
197                       (remote_host ? remote_host : "-"),
198                       b->client_info->user,
199                       b->repository->repos_name,
200                       (required & svn_authz_recursive ? "recursive " : ""),
201                       (required & svn_authz_write ? "write" : "read"),
202                       (path && path[0] ? path : "/"));
203
204   return logger__write(b->logger, line, strlen(line));
205 }
206
207 /* If CFG specifies a path to the password DB, read that DB through
208  * CONFIG_POOL and store it in REPOSITORY->PWDB.
209  */
210 static svn_error_t *
211 load_pwdb_config(repository_t *repository,
212                  svn_config_t *cfg,
213                  svn_repos__config_pool_t *config_pool,
214                  apr_pool_t *pool)
215 {
216   const char *pwdb_path;
217   svn_error_t *err;
218
219   svn_config_get(cfg, &pwdb_path,
220                  SVN_CONFIG_SECTION_GENERAL,
221                  SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
222
223   repository->pwdb = NULL;
224   if (pwdb_path)
225     {
226       pwdb_path = svn_dirent_internal_style(pwdb_path, pool);
227       pwdb_path = svn_dirent_join(repository->base, pwdb_path, pool);
228
229       err = svn_repos__config_pool_get(&repository->pwdb, config_pool,
230                                        pwdb_path, TRUE,
231                                        repository->repos, pool);
232       if (err)
233         {
234           /* Because it may be possible to read the pwdb file with some
235              access methods and not others, ignore errors reading the pwdb
236              file and just don't present password authentication as an
237              option.  Also, some authentications (e.g. --tunnel) can
238              proceed without it anyway.
239
240              ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked
241              ### for here.  That seems to have been introduced in r856914,
242              ### and only in r870942 was the APR_EACCES check introduced. */
243           if (err->apr_err != SVN_ERR_BAD_FILENAME
244               && ! APR_STATUS_IS_EACCES(err->apr_err))
245             {
246               return svn_error_create(SVN_ERR_AUTHN_FAILED, err, NULL);
247             }
248           else
249             /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */
250             svn_error_clear(err);
251         }
252     }
253
254   return SVN_NO_ERROR;
255 }
256
257 /* Canonicalize *ACCESS_FILE based on the type of argument.  Results are
258  * placed in *ACCESS_FILE.  REPOSITORY is used to convert relative paths to
259  * absolute paths rooted at the server root.  REPOS_ROOT is used to calculate
260  * an absolute URL for repos-relative URLs. */
261 static svn_error_t *
262 canonicalize_access_file(const char **access_file, repository_t *repository,
263                          const char *repos_root, apr_pool_t *pool)
264 {
265   if (svn_path_is_url(*access_file))
266     {
267       *access_file = svn_uri_canonicalize(*access_file, pool);
268     }
269   else if (svn_path_is_repos_relative_url(*access_file))
270     {
271       const char *repos_root_url;
272
273       SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_root_url, repos_root,
274                                                pool));
275       SVN_ERR(svn_path_resolve_repos_relative_url(access_file, *access_file,
276                                                   repos_root_url, pool));
277       *access_file = svn_uri_canonicalize(*access_file, pool);
278     }
279   else
280     {
281       *access_file = svn_dirent_internal_style(*access_file, pool);
282       *access_file = svn_dirent_join(repository->base, *access_file, pool);
283     }
284
285   return SVN_NO_ERROR;
286 }
287
288 /* Load the authz database for the listening server based on the entries
289    in the SERVER struct.
290
291    SERVER and CONN must not be NULL. The real errors will be logged with
292    SERVER and CONN but return generic errors to the client. */
293 static svn_error_t *
294 load_authz_config(repository_t *repository,
295                   const char *repos_root,
296                   svn_config_t *cfg,
297                   apr_pool_t *pool)
298 {
299   const char *authzdb_path;
300   const char *groupsdb_path;
301   svn_error_t *err;
302
303   /* Read authz configuration. */
304   svn_config_get(cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
305                  SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
306
307   svn_config_get(cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL,
308                  SVN_CONFIG_OPTION_GROUPS_DB, NULL);
309
310   if (authzdb_path)
311     {
312       const char *case_force_val;
313
314       /* Canonicalize and add the base onto the authzdb_path (if needed). */
315       err = canonicalize_access_file(&authzdb_path, repository,
316                                      repos_root, pool);
317
318       /* Same for the groupsdb_path if it is present. */
319       if (groupsdb_path && !err)
320         err = canonicalize_access_file(&groupsdb_path, repository,
321                                        repos_root, pool);
322
323       if (!err)
324         err = svn_repos_authz_read3(&repository->authzdb, authzdb_path,
325                                     groupsdb_path, TRUE, repository->repos,
326                                     pool, pool);
327
328       if (err)
329         return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, err, NULL);
330
331       /* Are we going to be case-normalizing usernames when we consult
332        * this authz file? */
333       svn_config_get(cfg, &case_force_val,
334                      SVN_CONFIG_SECTION_GENERAL,
335                      SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL);
336       if (case_force_val)
337         {
338           if (strcmp(case_force_val, "upper") == 0)
339             repository->username_case = CASE_FORCE_UPPER;
340           else if (strcmp(case_force_val, "lower") == 0)
341             repository->username_case = CASE_FORCE_LOWER;
342           else
343             repository->username_case = CASE_ASIS;
344         }
345     }
346   else
347     {
348       repository->authzdb = NULL;
349       repository->username_case = CASE_ASIS;
350     }
351
352   return SVN_NO_ERROR;
353 }
354
355 /* If ERROR is a AUTH* error as returned by load_pwdb_config or
356  * load_authz_config, write it to SERVER's log file.
357  * Return a sanitized version of ERROR.
358  */
359 static svn_error_t *
360 handle_config_error(svn_error_t *error,
361                     server_baton_t *server)
362 {
363   if (   error
364       && (   error->apr_err == SVN_ERR_AUTHZ_INVALID_CONFIG
365           || error->apr_err == SVN_ERR_AUTHN_FAILED))
366     {
367       apr_status_t apr_err = error->apr_err;
368       log_error(error, server);
369
370       /* Now that we've logged the error, clear it and return a
371        * nice, generic error to the user:
372        * http://subversion.tigris.org/issues/show_bug.cgi?id=2271 */
373       svn_error_clear(error);
374       return svn_error_create(apr_err, NULL, NULL);
375     }
376
377   return error;
378 }
379
380 /* Set *FS_PATH to the portion of URL that is the path within the
381    repository, if URL is inside REPOS_URL (if URL is not inside
382    REPOS_URL, then error, with the effect on *FS_PATH undefined).
383
384    If the resultant fs path would be the empty string (i.e., URL and
385    REPOS_URL are the same), then set *FS_PATH to "/".
386
387    Assume that REPOS_URL and URL are already URI-decoded. */
388 static svn_error_t *get_fs_path(const char *repos_url, const char *url,
389                                 const char **fs_path)
390 {
391   apr_size_t len;
392
393   len = strlen(repos_url);
394   if (strncmp(url, repos_url, len) != 0)
395     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
396                              "'%s' is not the same repository as '%s'",
397                              url, repos_url);
398   *fs_path = url + len;
399   if (! **fs_path)
400     *fs_path = "/";
401
402   return SVN_NO_ERROR;
403 }
404
405 /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
406
407 /* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else
408    converts it to lower case. */
409 static void convert_case(char *text, svn_boolean_t to_uppercase)
410 {
411   char *c = text;
412   while (*c)
413     {
414       *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
415       ++c;
416     }
417 }
418
419 /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
420    the user described in BATON according to the authz rules in BATON.
421    Use POOL for temporary allocations only.  If no authz rules are
422    present in BATON, grant access by default. */
423 static svn_error_t *authz_check_access(svn_boolean_t *allowed,
424                                        const char *path,
425                                        svn_repos_authz_access_t required,
426                                        server_baton_t *b,
427                                        apr_pool_t *pool)
428 {
429   repository_t *repository = b->repository;
430   client_info_t *client_info = b->client_info;
431
432   /* If authz cannot be performed, grant access.  This is NOT the same
433      as the default policy when authz is performed on a path with no
434      rules.  In the latter case, the default is to deny access, and is
435      set by svn_repos_authz_check_access. */
436   if (!repository->authzdb)
437     {
438       *allowed = TRUE;
439       return SVN_NO_ERROR;
440     }
441
442   /* If the authz request is for the empty path (ie. ""), replace it
443      with the root path.  This happens because of stripping done at
444      various levels in svnserve that remove the leading / on an
445      absolute path. Passing such a malformed path to the authz
446      routines throws them into an infinite loop and makes them miss
447      ACLs. */
448   if (path && *path != '/')
449     path = svn_fspath__canonicalize(path, pool);
450
451   /* If we have a username, and we've not yet used it + any username
452      case normalization that might be requested to determine "the
453      username we used for authz purposes", do so now. */
454   if (client_info->user && (! client_info->authz_user))
455     {
456       char *authz_user = apr_pstrdup(b->pool, client_info->user);
457       if (repository->username_case == CASE_FORCE_UPPER)
458         convert_case(authz_user, TRUE);
459       else if (repository->username_case == CASE_FORCE_LOWER)
460         convert_case(authz_user, FALSE);
461
462       client_info->authz_user = authz_user;
463     }
464
465   SVN_ERR(svn_repos_authz_check_access(repository->authzdb,
466                                        repository->authz_repos_name,
467                                        path, client_info->authz_user,
468                                        required, allowed, pool));
469   if (!*allowed)
470     SVN_ERR(log_authz_denied(path, required, b, pool));
471
472   return SVN_NO_ERROR;
473 }
474
475 /* Set *ALLOWED to TRUE if PATH is readable by the user described in
476  * BATON.  Use POOL for temporary allocations only.  ROOT is not used.
477  * Implements the svn_repos_authz_func_t interface.
478  */
479 static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed,
480                                           svn_fs_root_t *root,
481                                           const char *path,
482                                           void *baton,
483                                           apr_pool_t *pool)
484 {
485   authz_baton_t *sb = baton;
486
487   return authz_check_access(allowed, path, svn_authz_read,
488                             sb->server, pool);
489 }
490
491 /* If authz is enabled in the specified BATON, return a read authorization
492    function. Otherwise, return NULL. */
493 static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton)
494 {
495   if (baton->repository->authzdb)
496      return authz_check_access_cb;
497   return NULL;
498 }
499
500 /* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
501  * according to the state in BATON.  Use POOL for temporary
502  * allocations only.  ROOT is not used.  Implements the
503  * svn_repos_authz_callback_t interface.
504  */
505 static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required,
506                                     svn_boolean_t *allowed,
507                                     svn_fs_root_t *root,
508                                     const char *path,
509                                     void *baton,
510                                     apr_pool_t *pool)
511 {
512   authz_baton_t *sb = baton;
513
514   return authz_check_access(allowed, path, required, sb->server, pool);
515 }
516
517 /* Return the access level specified for OPTION in CFG.  If no such
518  * setting exists, use DEF.  If READ_ONLY is set, unconditionally disable
519  * write access.
520  */
521 static enum access_type
522 get_access(svn_config_t *cfg,
523            const char *option,
524            const char *def,
525            svn_boolean_t read_only)
526 {
527   enum access_type result;
528   const char *val;
529
530   svn_config_get(cfg, &val, SVN_CONFIG_SECTION_GENERAL, option, def);
531   result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
532             strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
533
534   return result == WRITE_ACCESS && read_only ? READ_ACCESS : result;
535 }
536
537 /* Set the *_ACCESS members in REPOSITORY according to the settings in
538  * CFG.  If READ_ONLY is set, unconditionally disable write access.
539  */
540 static void
541 set_access(repository_t *repository,
542            svn_config_t *cfg,
543            svn_boolean_t read_only)
544 {
545   repository->auth_access = get_access(cfg, SVN_CONFIG_OPTION_AUTH_ACCESS,
546                                        "write", read_only);
547   repository->anon_access = get_access(cfg, SVN_CONFIG_OPTION_ANON_ACCESS,
548                                        "read", read_only);
549 }
550
551 /* Return the access level for the user in B.
552  */
553 static enum access_type
554 current_access(server_baton_t *b)
555 {
556   return b->client_info->user ? b->repository->auth_access
557                               : b->repository->anon_access;
558 }
559
560 /* Send authentication mechs for ACCESS_TYPE to the client.  If NEEDS_USERNAME
561    is true, don't send anonymous mech even if that would give the desired
562    access. */
563 static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
564                                server_baton_t *b, enum access_type required,
565                                svn_boolean_t needs_username)
566 {
567   if (!needs_username && b->repository->anon_access >= required)
568     SVN_ERR(svn_ra_svn__write_word(conn, pool, "ANONYMOUS"));
569   if (b->client_info->tunnel_user && b->repository->auth_access >= required)
570     SVN_ERR(svn_ra_svn__write_word(conn, pool, "EXTERNAL"));
571   if (b->repository->pwdb && b->repository->auth_access >= required)
572     SVN_ERR(svn_ra_svn__write_word(conn, pool, "CRAM-MD5"));
573   return SVN_NO_ERROR;
574 }
575
576 /* Context for cleanup handler. */
577 struct cleanup_fs_access_baton
578 {
579   svn_fs_t *fs;
580   apr_pool_t *pool;
581 };
582
583 /* Pool cleanup handler.  Make sure fs's access_t points to NULL when
584    the command pool is destroyed. */
585 static apr_status_t cleanup_fs_access(void *data)
586 {
587   svn_error_t *serr;
588   struct cleanup_fs_access_baton *baton = data;
589
590   serr = svn_fs_set_access(baton->fs, NULL);
591   if (serr)
592     {
593       apr_status_t apr_err = serr->apr_err;
594       svn_error_clear(serr);
595       return apr_err;
596     }
597
598   return APR_SUCCESS;
599 }
600
601
602 /* Create an svn_fs_access_t in POOL for USER and associate it with
603    B's filesystem.  Also, register a cleanup handler with POOL which
604    de-associates the svn_fs_access_t from B's filesystem. */
605 static svn_error_t *
606 create_fs_access(server_baton_t *b, apr_pool_t *pool)
607 {
608   svn_fs_access_t *fs_access;
609   struct cleanup_fs_access_baton *cleanup_baton;
610
611   if (!b->client_info->user)
612     return SVN_NO_ERROR;
613
614   SVN_ERR(svn_fs_create_access(&fs_access, b->client_info->user, pool));
615   SVN_ERR(svn_fs_set_access(b->repository->fs, fs_access));
616
617   cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
618   cleanup_baton->pool = pool;
619   cleanup_baton->fs = b->repository->fs;
620   apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
621                             apr_pool_cleanup_null);
622
623   return SVN_NO_ERROR;
624 }
625
626 /* Authenticate, once the client has chosen a mechanism and possibly
627  * sent an initial mechanism token.  On success, set *success to true
628  * and b->user to the authenticated username (or NULL for anonymous).
629  * On authentication failure, report failure to the client and set
630  * *success to FALSE.  On communications failure, return an error.
631  * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
632 static svn_error_t *auth(svn_boolean_t *success,
633                          svn_ra_svn_conn_t *conn,
634                          const char *mech, const char *mecharg,
635                          server_baton_t *b, enum access_type required,
636                          svn_boolean_t needs_username,
637                          apr_pool_t *scratch_pool)
638 {
639   const char *user;
640   *success = FALSE;
641
642   if (b->repository->auth_access >= required
643       && b->client_info->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
644     {
645       if (*mecharg && strcmp(mecharg, b->client_info->tunnel_user) != 0)
646         return svn_ra_svn__write_tuple(conn, scratch_pool, "w(c)", "failure",
647                                        "Requested username does not match");
648       b->client_info->user = b->client_info->tunnel_user;
649       SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w()", "success"));
650       *success = TRUE;
651       return SVN_NO_ERROR;
652     }
653
654   if (b->repository->anon_access >= required
655       && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
656     {
657       SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w()", "success"));
658       *success = TRUE;
659       return SVN_NO_ERROR;
660     }
661
662   if (b->repository->auth_access >= required
663       && b->repository->pwdb && strcmp(mech, "CRAM-MD5") == 0)
664     {
665       SVN_ERR(svn_ra_svn_cram_server(conn, scratch_pool, b->repository->pwdb,
666                                      &user, success));
667       b->client_info->user = apr_pstrdup(b->pool, user);
668       return SVN_NO_ERROR;
669     }
670
671   return svn_ra_svn__write_tuple(conn, scratch_pool, "w(c)", "failure",
672                                 "Must authenticate with listed mechanism");
673 }
674
675 /* Perform an authentication request using the built-in SASL implementation. */
676 static svn_error_t *
677 internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
678                       server_baton_t *b, enum access_type required,
679                       svn_boolean_t needs_username)
680 {
681   svn_boolean_t success;
682   const char *mech, *mecharg;
683   apr_pool_t *iterpool;
684
685   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
686   SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
687   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)c)", b->repository->realm));
688
689   iterpool = svn_pool_create(pool);
690   do
691     {
692       svn_pool_clear(iterpool);
693
694       SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
695       if (!*mech)
696         break;
697       SVN_ERR(auth(&success, conn, mech, mecharg, b, required,
698                    needs_username, iterpool));
699     }
700   while (!success);
701   svn_pool_destroy(iterpool);
702
703   return SVN_NO_ERROR;
704 }
705
706 /* Perform an authentication request in order to get an access level of
707  * REQUIRED or higher.  Since the client may escape the authentication
708  * exchange, the caller should check current_access(b) to see if
709  * authentication succeeded. */
710 static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
711                                  server_baton_t *b, enum access_type required,
712                                  svn_boolean_t needs_username)
713 {
714 #ifdef SVN_HAVE_SASL
715   if (b->repository->use_sasl)
716     return cyrus_auth_request(conn, pool, b, required, needs_username);
717 #endif
718
719   return internal_auth_request(conn, pool, b, required, needs_username);
720 }
721
722 /* Send a trivial auth notification on CONN which lists no mechanisms,
723  * indicating that authentication is unnecessary.  Usually called in
724  * response to invocation of a svnserve command.
725  */
726 static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
727                                          apr_pool_t *pool, server_baton_t *b)
728 {
729   return svn_ra_svn__write_cmd_response(conn, pool, "()c", "");
730 }
731
732 /* Ensure that the client has the REQUIRED access by checking the
733  * access directives (both blanket and per-directory) in BATON.  If
734  * PATH is NULL, then only the blanket access configuration will
735  * impact the result.
736  *
737  * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the
738  * user described in BATON is authenticated and, well, has a username
739  * assigned to him.
740  *
741  * Use POOL for temporary allocations only.
742  */
743 static svn_boolean_t lookup_access(apr_pool_t *pool,
744                                    server_baton_t *baton,
745                                    svn_repos_authz_access_t required,
746                                    const char *path,
747                                    svn_boolean_t needs_username)
748 {
749   enum access_type req = (required & svn_authz_write) ?
750     WRITE_ACCESS : READ_ACCESS;
751   svn_boolean_t authorized;
752   svn_error_t *err;
753
754   /* Get authz's opinion on the access. */
755   err = authz_check_access(&authorized, path, required, baton, pool);
756
757   /* If an error made lookup fail, deny access. */
758   if (err)
759     {
760       log_error(err, baton);
761       svn_error_clear(err);
762       return FALSE;
763     }
764
765   /* If the required access is blanket-granted AND granted by authz
766      AND we already have a username if one is required, then the
767      lookup has succeeded. */
768   if (current_access(baton) >= req
769       && authorized
770       && (! needs_username || baton->client_info->user))
771     return TRUE;
772
773   return FALSE;
774 }
775
776 /* Check that the client has the REQUIRED access by consulting the
777  * authentication and authorization states stored in BATON.  If the
778  * client does not have the required access credentials, attempt to
779  * authenticate the client to get that access, using CONN for
780  * communication.
781  *
782  * This function is supposed to be called to handle the authentication
783  * half of a standard svn protocol reply.  If an error is returned, it
784  * probably means that the server can terminate the client connection
785  * with an apologetic error, as it implies an authentication failure.
786  *
787  * PATH and NEEDS_USERNAME are passed along to lookup_access, their
788  * behaviour is documented there.
789  */
790 static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn,
791                                      apr_pool_t *pool,
792                                      server_baton_t *b,
793                                      svn_repos_authz_access_t required,
794                                      const char *path,
795                                      svn_boolean_t needs_username)
796 {
797   enum access_type req = (required & svn_authz_write) ?
798     WRITE_ACCESS : READ_ACCESS;
799
800   /* See whether the user already has the required access.  If so,
801      nothing needs to be done.  Create the FS access and send a
802      trivial auth request. */
803   if (lookup_access(pool, b, required, path, needs_username))
804     {
805       SVN_ERR(create_fs_access(b, pool));
806       return trivial_auth_request(conn, pool, b);
807     }
808
809   /* If the required blanket access can be obtained by authenticating,
810      try that.  Unfortunately, we can't tell until after
811      authentication whether authz will work or not.  We force
812      requiring a username because we need one to be able to check
813      authz configuration again with a different user credentials than
814      the first time round. */
815   if (b->client_info->user == NULL
816       && b->repository->auth_access >= req
817       && (b->client_info->tunnel_user || b->repository->pwdb
818           || b->repository->use_sasl))
819     SVN_ERR(auth_request(conn, pool, b, req, TRUE));
820
821   /* Now that an authentication has been done get the new take of
822      authz on the request. */
823   if (! lookup_access(pool, b, required, path, needs_username))
824     return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
825                             error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
826                                                  NULL, NULL, b),
827                             NULL);
828
829   /* Else, access is granted, and there is much rejoicing. */
830   SVN_ERR(create_fs_access(b, pool));
831
832   return SVN_NO_ERROR;
833 }
834
835 /* --- REPORTER COMMAND SET --- */
836
837 /* To allow for pipelining, reporter commands have no reponses.  If we
838  * get an error, we ignore all subsequent reporter commands and return
839  * the error finish_report, to be handled by the calling command.
840  */
841
842 static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
843                              svn_ra_svn__list_t *params, void *baton)
844 {
845   report_driver_baton_t *b = baton;
846   const char *path, *lock_token, *depth_word;
847   svn_revnum_t rev;
848   /* Default to infinity, for old clients that don't send depth. */
849   svn_depth_t depth = svn_depth_infinity;
850   svn_boolean_t start_empty;
851
852   SVN_ERR(svn_ra_svn__parse_tuple(params, "crb?(?c)?w",
853                                   &path, &rev, &start_empty, &lock_token,
854                                   &depth_word));
855   if (depth_word)
856     depth = svn_depth_from_word(depth_word);
857   path = svn_relpath_canonicalize(path, pool);
858   if (b->from_rev && strcmp(path, "") == 0)
859     *b->from_rev = rev;
860   if (!b->err)
861     b->err = svn_repos_set_path3(b->report_baton, path, rev, depth,
862                                  start_empty, lock_token, pool);
863   b->entry_counter++;
864   if (!start_empty)
865     b->only_empty_entries = FALSE;
866   return SVN_NO_ERROR;
867 }
868
869 static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
870                                 svn_ra_svn__list_t *params, void *baton)
871 {
872   report_driver_baton_t *b = baton;
873   const char *path;
874
875   SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &path));
876   path = svn_relpath_canonicalize(path, pool);
877   if (!b->err)
878     b->err = svn_repos_delete_path(b->report_baton, path, pool);
879   return SVN_NO_ERROR;
880 }
881
882 static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
883                               svn_ra_svn__list_t *params, void *baton)
884 {
885   report_driver_baton_t *b = baton;
886   const char *path, *url, *lock_token, *fs_path, *depth_word;
887   svn_revnum_t rev;
888   svn_boolean_t start_empty;
889   /* Default to infinity, for old clients that don't send depth. */
890   svn_depth_t depth = svn_depth_infinity;
891
892   SVN_ERR(svn_ra_svn__parse_tuple(params, "ccrb?(?c)?w",
893                                  &path, &url, &rev, &start_empty,
894                                  &lock_token, &depth_word));
895
896   /* ### WHAT?!  The link path is an absolute URL?!  Didn't see that
897      coming...   -- cmpilato  */
898   path = svn_relpath_canonicalize(path, pool);
899   url = svn_uri_canonicalize(url, pool);
900   if (depth_word)
901     depth = svn_depth_from_word(depth_word);
902   if (!b->err)
903     b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool),
904                          svn_path_uri_decode(url, pool),
905                          &fs_path);
906   if (!b->err)
907     b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev,
908                                   depth, start_empty, lock_token, pool);
909   b->entry_counter++;
910   return SVN_NO_ERROR;
911 }
912
913 static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
914                                   svn_ra_svn__list_t *params, void *baton)
915 {
916   report_driver_baton_t *b = baton;
917
918   /* No arguments to parse. */
919   SVN_ERR(trivial_auth_request(conn, pool, b->sb));
920   if (!b->err)
921     b->err = svn_repos_finish_report(b->report_baton, pool);
922   return SVN_NO_ERROR;
923 }
924
925 static svn_error_t *
926 abort_report(svn_ra_svn_conn_t *conn,
927              apr_pool_t *pool,
928              svn_ra_svn__list_t *params,
929              void *baton)
930 {
931   report_driver_baton_t *b = baton;
932
933   /* No arguments to parse. */
934   svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
935   return SVN_NO_ERROR;
936 }
937
938 static const svn_ra_svn__cmd_entry_t report_commands[] = {
939   { "set-path",      set_path },
940   { "delete-path",   delete_path },
941   { "link-path",     link_path },
942   { "finish-report", finish_report, NULL, TRUE },
943   { "abort-report",  abort_report,  NULL, TRUE },
944   { NULL }
945 };
946
947 /* Accept a report from the client, drive the network editor with the
948  * result, and then write an empty command response.  If there is a
949  * non-protocol failure, accept_report will abort the edit and return
950  * a command error to be reported by handle_commands().
951  *
952  * If only_empty_entry is not NULL and the report contains only one
953  * item, and that item is empty, set *only_empty_entry to TRUE, else
954  * set it to FALSE.
955  *
956  * If from_rev is not NULL, set *from_rev to the revision number from
957  * the set-path on ""; if somehow set-path "" never happens, set
958  * *from_rev to SVN_INVALID_REVNUM.
959  */
960 static svn_error_t *accept_report(svn_boolean_t *only_empty_entry,
961                                   svn_revnum_t *from_rev,
962                                   svn_ra_svn_conn_t *conn, apr_pool_t *pool,
963                                   server_baton_t *b, svn_revnum_t rev,
964                                   const char *target, const char *tgt_path,
965                                   svn_boolean_t text_deltas,
966                                   svn_depth_t depth,
967                                   svn_boolean_t send_copyfrom_args,
968                                   svn_boolean_t ignore_ancestry)
969 {
970   const svn_delta_editor_t *editor;
971   void *edit_baton, *report_baton;
972   report_driver_baton_t rb;
973   svn_error_t *err;
974   authz_baton_t ab;
975
976   ab.server = b;
977   ab.conn = conn;
978
979   /* Make an svn_repos report baton.  Tell it to drive the network editor
980    * when the report is complete. */
981   svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
982   SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev,
983                                       b->repository->repos,
984                                       b->repository->fs_path->data, target,
985                                       tgt_path, text_deltas, depth,
986                                       ignore_ancestry, send_copyfrom_args,
987                                       editor, edit_baton,
988                                       authz_check_access_cb_func(b),
989                                       &ab, svn_ra_svn_zero_copy_limit(conn),
990                                       pool));
991
992   rb.sb = b;
993   rb.repos_url = svn_path_uri_decode(b->repository->repos_url, pool);
994   rb.report_baton = report_baton;
995   rb.err = NULL;
996   rb.entry_counter = 0;
997   rb.only_empty_entries = TRUE;
998   rb.from_rev = from_rev;
999   if (from_rev)
1000     *from_rev = SVN_INVALID_REVNUM;
1001   err = svn_ra_svn__handle_commands2(conn, pool, report_commands, &rb, TRUE);
1002   if (err)
1003     {
1004       /* Network or protocol error while handling commands. */
1005       svn_error_clear(rb.err);
1006       return err;
1007     }
1008   else if (rb.err)
1009     {
1010       /* Some failure during the reporting or editing operations. */
1011       SVN_CMD_ERR(rb.err);
1012     }
1013   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1014
1015   if (only_empty_entry)
1016     *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries;
1017
1018   return SVN_NO_ERROR;
1019 }
1020
1021 /* --- MAIN COMMAND SET --- */
1022
1023 /* Write out a list of property diffs.  PROPDIFFS is an array of svn_prop_t
1024  * values. */
1025 static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
1026                                      apr_pool_t *pool,
1027                                      const apr_array_header_t *propdiffs)
1028 {
1029   int i;
1030
1031   for (i = 0; i < propdiffs->nelts; ++i)
1032     {
1033       const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
1034
1035       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "c(?s)",
1036                                       prop->name, prop->value));
1037     }
1038
1039   return SVN_NO_ERROR;
1040 }
1041
1042 /* Write out a lock to the client. */
1043 static svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
1044                                apr_pool_t *pool,
1045                                const svn_lock_t *lock)
1046 {
1047   const char *cdate, *edate;
1048
1049   cdate = svn_time_to_cstring(lock->creation_date, pool);
1050   edate = lock->expiration_date
1051     ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
1052   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
1053                                   lock->token, lock->owner, lock->comment,
1054                                   cdate, edate));
1055
1056   return SVN_NO_ERROR;
1057 }
1058
1059 /* ### This really belongs in libsvn_repos. */
1060 /* Get the explicit properties and/or inherited properties for a PATH in
1061    ROOT, with hardcoded committed-info values. */
1062 static svn_error_t *
1063 get_props(apr_hash_t **props,
1064           apr_array_header_t **iprops,
1065           authz_baton_t *b,
1066           svn_fs_root_t *root,
1067           const char *path,
1068           apr_pool_t *pool)
1069 {
1070   /* Get the explicit properties. */
1071   if (props)
1072     {
1073       svn_string_t *str;
1074       svn_revnum_t crev;
1075       const char *cdate, *cauthor, *uuid;
1076
1077       SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
1078
1079       /* Hardcode the values for the committed revision, date, and author. */
1080       SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
1081                                            path, pool));
1082       str = svn_string_createf(pool, "%ld", crev);
1083       svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, str);
1084       str = (cdate) ? svn_string_create(cdate, pool) : NULL;
1085       svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, str);
1086       str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
1087       svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, str);
1088
1089       /* Hardcode the values for the UUID. */
1090       SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
1091       str = (uuid) ? svn_string_create(uuid, pool) : NULL;
1092       svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, str);
1093     }
1094
1095   /* Get any inherited properties the user is authorized to. */
1096   if (iprops)
1097     {
1098       SVN_ERR(svn_repos_fs_get_inherited_props(
1099                 iprops, root, path, NULL,
1100                 authz_check_access_cb_func(b->server),
1101                 b, pool, pool));
1102     }
1103
1104   return SVN_NO_ERROR;
1105 }
1106
1107 /* Set BATON->FS_PATH for the repository URL found in PARAMS. */
1108 static svn_error_t *
1109 reparent(svn_ra_svn_conn_t *conn,
1110          apr_pool_t *pool,
1111          svn_ra_svn__list_t *params,
1112          void *baton)
1113 {
1114   server_baton_t *b = baton;
1115   const char *url;
1116   const char *fs_path;
1117
1118   SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &url));
1119   url = svn_uri_canonicalize(url, pool);
1120   SVN_ERR(trivial_auth_request(conn, pool, b));
1121   SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url, pool),
1122                           svn_path_uri_decode(url, pool),
1123                           &fs_path));
1124   SVN_ERR(log_command(b, conn, pool, "%s", svn_log__reparent(fs_path, pool)));
1125   svn_stringbuf_set(b->repository->fs_path, fs_path);
1126   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1127   return SVN_NO_ERROR;
1128 }
1129
1130 static svn_error_t *
1131 get_latest_rev(svn_ra_svn_conn_t *conn,
1132                apr_pool_t *pool,
1133                svn_ra_svn__list_t *params,
1134                void *baton)
1135 {
1136   server_baton_t *b = baton;
1137   svn_revnum_t rev;
1138
1139   SVN_ERR(log_command(b, conn, pool, "get-latest-rev"));
1140
1141   SVN_ERR(trivial_auth_request(conn, pool, b));
1142   SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1143   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
1144   return SVN_NO_ERROR;
1145 }
1146
1147 static svn_error_t *
1148 get_dated_rev(svn_ra_svn_conn_t *conn,
1149               apr_pool_t *pool,
1150               svn_ra_svn__list_t *params,
1151               void *baton)
1152 {
1153   server_baton_t *b = baton;
1154   svn_revnum_t rev;
1155   apr_time_t tm;
1156   const char *timestr;
1157
1158   SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &timestr));
1159   SVN_ERR(log_command(b, conn, pool, "get-dated-rev %s", timestr));
1160
1161   SVN_ERR(trivial_auth_request(conn, pool, b));
1162   SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
1163   SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repository->repos, tm, pool));
1164   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
1165   return SVN_NO_ERROR;
1166 }
1167
1168 /* Common logic for change_rev_prop() and change_rev_prop2(). */
1169 static svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn,
1170                                        server_baton_t *b,
1171                                        svn_revnum_t rev,
1172                                        const char *name,
1173                                        const svn_string_t *const *old_value_p,
1174                                        const svn_string_t *value,
1175                                        apr_pool_t *pool)
1176 {
1177   authz_baton_t ab;
1178
1179   ab.server = b;
1180   ab.conn = conn;
1181
1182   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
1183   SVN_ERR(log_command(b, conn, pool, "%s",
1184                       svn_log__change_rev_prop(rev, name, pool)));
1185   SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repository->repos, rev,
1186                                             b->client_info->user,
1187                                             name, old_value_p, value,
1188                                             TRUE, TRUE,
1189                                             authz_check_access_cb_func(b), &ab,
1190                                             pool));
1191   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1192
1193   return SVN_NO_ERROR;
1194 }
1195
1196 static svn_error_t *
1197 change_rev_prop2(svn_ra_svn_conn_t *conn,
1198                  apr_pool_t *pool,
1199                  svn_ra_svn__list_t *params,
1200                  void *baton)
1201 {
1202   server_baton_t *b = baton;
1203   svn_revnum_t rev;
1204   const char *name;
1205   svn_string_t *value;
1206   const svn_string_t *const *old_value_p;
1207   svn_string_t *old_value;
1208   svn_boolean_t dont_care;
1209
1210   SVN_ERR(svn_ra_svn__parse_tuple(params, "rc(?s)(b?s)",
1211                                   &rev, &name, &value,
1212                                   &dont_care, &old_value));
1213
1214   /* Argument parsing. */
1215   if (dont_care)
1216     old_value_p = NULL;
1217   else
1218     old_value_p = (const svn_string_t *const *)&old_value;
1219
1220   /* Input validation. */
1221   if (dont_care && old_value)
1222     {
1223       svn_error_t *err;
1224       err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1225                              "'previous-value' and 'dont-care' cannot both be "
1226                              "set in 'change-rev-prop2' request");
1227       return log_fail_and_flush(err, b, conn, pool);
1228     }
1229
1230   /* Do it. */
1231   SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool));
1232
1233   return SVN_NO_ERROR;
1234 }
1235
1236 static svn_error_t *
1237 change_rev_prop(svn_ra_svn_conn_t *conn,
1238                 apr_pool_t *pool,
1239                 svn_ra_svn__list_t *params,
1240                 void *baton)
1241 {
1242   server_baton_t *b = baton;
1243   svn_revnum_t rev;
1244   const char *name;
1245   svn_string_t *value;
1246
1247   /* Because the revprop value was at one time mandatory, the usual
1248      optional element pattern "(?s)" isn't used. */
1249   SVN_ERR(svn_ra_svn__parse_tuple(params, "rc?s", &rev, &name, &value));
1250
1251   SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool));
1252
1253   return SVN_NO_ERROR;
1254 }
1255
1256 static svn_error_t *
1257 rev_proplist(svn_ra_svn_conn_t *conn,
1258              apr_pool_t *pool,
1259              svn_ra_svn__list_t *params,
1260              void *baton)
1261 {
1262   server_baton_t *b = baton;
1263   svn_revnum_t rev;
1264   apr_hash_t *props;
1265   authz_baton_t ab;
1266
1267   ab.server = b;
1268   ab.conn = conn;
1269
1270   SVN_ERR(svn_ra_svn__parse_tuple(params, "r", &rev));
1271   SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool)));
1272
1273   SVN_ERR(trivial_auth_request(conn, pool, b));
1274   SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repository->repos,
1275                                              rev,
1276                                              authz_check_access_cb_func(b),
1277                                              &ab, pool));
1278   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
1279   SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1280   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1281   return SVN_NO_ERROR;
1282 }
1283
1284 static svn_error_t *
1285 rev_prop(svn_ra_svn_conn_t *conn,
1286          apr_pool_t *pool,
1287          svn_ra_svn__list_t *params,
1288          void *baton)
1289 {
1290   server_baton_t *b = baton;
1291   svn_revnum_t rev;
1292   const char *name;
1293   svn_string_t *value;
1294   authz_baton_t ab;
1295
1296   ab.server = b;
1297   ab.conn = conn;
1298
1299   SVN_ERR(svn_ra_svn__parse_tuple(params, "rc", &rev, &name));
1300   SVN_ERR(log_command(b, conn, pool, "%s",
1301                       svn_log__rev_prop(rev, name, pool)));
1302
1303   SVN_ERR(trivial_auth_request(conn, pool, b));
1304   SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repository->repos, rev,
1305                                          name, authz_check_access_cb_func(b),
1306                                          &ab, pool));
1307   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(?s)", value));
1308   return SVN_NO_ERROR;
1309 }
1310
1311 static svn_error_t *commit_done(const svn_commit_info_t *commit_info,
1312                                 void *baton, apr_pool_t *pool)
1313 {
1314   commit_callback_baton_t *ccb = baton;
1315
1316   *ccb->new_rev = commit_info->revision;
1317   *ccb->date = commit_info->date
1318     ? apr_pstrdup(ccb->pool, commit_info->date): NULL;
1319   *ccb->author = commit_info->author
1320     ? apr_pstrdup(ccb->pool, commit_info->author) : NULL;
1321   *ccb->post_commit_err = commit_info->post_commit_err
1322     ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL;
1323   return SVN_NO_ERROR;
1324 }
1325
1326 /* Add the LOCK_TOKENS (if any) to the filesystem access context,
1327  * checking path authorizations using the state in SB as we go.
1328  * LOCK_TOKENS is an array of svn_ra_svn__item_t structs.  Return a
1329  * client error if LOCK_TOKENS is not a list of lists.  If a lock
1330  * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
1331  * to the client.  Use POOL for temporary allocations only.
1332  */
1333 static svn_error_t *
1334 add_lock_tokens(const svn_ra_svn__list_t *lock_tokens,
1335                 server_baton_t *sb,
1336                 apr_pool_t *pool)
1337 {
1338   int i;
1339   svn_fs_access_t *fs_access;
1340
1341   SVN_ERR(svn_fs_get_access(&fs_access, sb->repository->fs));
1342
1343   /* If there is no access context, nowhere to add the tokens. */
1344   if (! fs_access)
1345     return SVN_NO_ERROR;
1346
1347   for (i = 0; i < lock_tokens->nelts; ++i)
1348     {
1349       const char *path, *token, *full_path;
1350       svn_ra_svn__item_t *path_item, *token_item;
1351       svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(lock_tokens, i);
1352       if (item->kind != SVN_RA_SVN_LIST)
1353         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1354                                 "Lock tokens aren't a list of lists");
1355
1356       path_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 0);
1357       if (path_item->kind != SVN_RA_SVN_STRING)
1358         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1359                                 "Lock path isn't a string");
1360
1361       token_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 1);
1362       if (token_item->kind != SVN_RA_SVN_STRING)
1363         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1364                                 "Lock token isn't a string");
1365
1366       path = path_item->u.string.data;
1367       full_path = svn_fspath__join(sb->repository->fs_path->data,
1368                                    svn_relpath_canonicalize(path, pool),
1369                                    pool);
1370
1371       if (! lookup_access(pool, sb, svn_authz_write, full_path, TRUE))
1372         return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
1373                                     sb);
1374
1375       token = token_item->u.string.data;
1376       SVN_ERR(svn_fs_access_add_lock_token2(fs_access, path, token));
1377     }
1378
1379   return SVN_NO_ERROR;
1380 }
1381
1382 /* Implements svn_fs_lock_callback_t. */
1383 static svn_error_t *
1384 lock_cb(void *baton,
1385         const char *path,
1386         const svn_lock_t *lock,
1387         svn_error_t *fs_err,
1388         apr_pool_t *pool)
1389 {
1390   server_baton_t *sb = baton;
1391
1392   log_error(fs_err, sb);
1393
1394   return SVN_NO_ERROR;
1395 }
1396
1397 /* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
1398    LOCK_TOKENS contains svn_ra_svn__item_t elements, assumed to be lists. */
1399 static svn_error_t *
1400 unlock_paths(const svn_ra_svn__list_t *lock_tokens,
1401              server_baton_t *sb,
1402              apr_pool_t *pool)
1403 {
1404   int i;
1405   apr_pool_t *subpool = svn_pool_create(pool);
1406   apr_hash_t *targets = apr_hash_make(subpool);
1407   svn_error_t *err;
1408
1409   for (i = 0; i < lock_tokens->nelts; ++i)
1410     {
1411       svn_ra_svn__item_t *item, *path_item, *token_item;
1412       const char *path, *token, *full_path;
1413
1414       item = &SVN_RA_SVN__LIST_ITEM(lock_tokens, i);
1415       path_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 0);
1416       token_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 1);
1417
1418       path = path_item->u.string.data;
1419       full_path = svn_fspath__join(sb->repository->fs_path->data,
1420                                    svn_relpath_canonicalize(path, subpool),
1421                                    subpool);
1422       token = token_item->u.string.data;
1423       svn_hash_sets(targets, full_path, token);
1424     }
1425
1426
1427   /* The lock may have become defunct after the commit, so ignore such
1428      errors. */
1429   err = svn_repos_fs_unlock_many(sb->repository->repos, targets, FALSE,
1430                                  lock_cb, sb, subpool, subpool);
1431   log_error(err, sb);
1432   svn_error_clear(err);
1433
1434   svn_pool_destroy(subpool);
1435
1436   return SVN_NO_ERROR;
1437 }
1438
1439 static svn_error_t *
1440 commit(svn_ra_svn_conn_t *conn,
1441        apr_pool_t *pool,
1442        svn_ra_svn__list_t *params,
1443        void *baton)
1444 {
1445   server_baton_t *b = baton;
1446   const char *log_msg,
1447              *date = NULL,
1448              *author = NULL,
1449              *post_commit_err = NULL;
1450   svn_ra_svn__list_t *lock_tokens;
1451   svn_boolean_t keep_locks;
1452   svn_ra_svn__list_t *revprop_list;
1453   apr_hash_t *revprop_table;
1454   const svn_delta_editor_t *editor;
1455   void *edit_baton;
1456   svn_boolean_t aborted;
1457   commit_callback_baton_t ccb;
1458   svn_revnum_t new_rev;
1459   authz_baton_t ab;
1460
1461   ab.server = b;
1462   ab.conn = conn;
1463
1464   if (params->nelts == 1)
1465     {
1466       /* Clients before 1.2 don't send lock-tokens, keep-locks,
1467          and rev-props fields. */
1468       SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &log_msg));
1469       lock_tokens = NULL;
1470       keep_locks = TRUE;
1471       revprop_list = NULL;
1472     }
1473   else
1474     {
1475       /* Clients before 1.5 don't send the rev-props field. */
1476       SVN_ERR(svn_ra_svn__parse_tuple(params, "clb?l", &log_msg,
1477                                       &lock_tokens, &keep_locks,
1478                                       &revprop_list));
1479     }
1480
1481   /* The handling for locks is a little problematic, because the
1482      protocol won't let us send several auth requests once one has
1483      succeeded.  So we request write access and a username before
1484      adding tokens (if we have any), and subsequently fail if a lock
1485      violates authz. */
1486   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
1487                            NULL,
1488                            (lock_tokens && lock_tokens->nelts)));
1489
1490   /* Authorize the lock tokens and give them to the FS if we got
1491      any. */
1492   if (lock_tokens && lock_tokens->nelts)
1493     SVN_CMD_ERR(add_lock_tokens(lock_tokens, b, pool));
1494
1495   /* Ignore LOG_MSG, per the protocol.  See ra_svn_commit(). */
1496   if (revprop_list)
1497     SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table));
1498   else
1499     {
1500       revprop_table = apr_hash_make(pool);
1501       svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
1502                     svn_string_create(log_msg, pool));
1503     }
1504
1505   /* Get author from the baton, making sure clients can't circumvent
1506      the authentication via the revision props. */
1507   svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
1508                 b->client_info->user
1509                    ? svn_string_create(b->client_info->user, pool)
1510                    : NULL);
1511
1512   ccb.pool = pool;
1513   ccb.new_rev = &new_rev;
1514   ccb.date = &date;
1515   ccb.author = &author;
1516   ccb.post_commit_err = &post_commit_err;
1517   /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
1518   SVN_CMD_ERR(svn_repos_get_commit_editor5
1519               (&editor, &edit_baton, b->repository->repos, NULL,
1520                svn_path_uri_decode(b->repository->repos_url, pool),
1521                b->repository->fs_path->data, revprop_table,
1522                commit_done, &ccb,
1523                authz_commit_cb, &ab, pool));
1524   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1525   SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton,
1526                                    &aborted, FALSE));
1527   if (!aborted)
1528     {
1529       SVN_ERR(log_command(b, conn, pool, "%s",
1530                           svn_log__commit(new_rev, pool)));
1531       SVN_ERR(trivial_auth_request(conn, pool, b));
1532
1533       /* In tunnel mode, deltify before answering the client, because
1534          answering may cause the client to terminate the connection
1535          and thus kill the server.  But otherwise, deltify after
1536          answering the client, to avoid user-visible delay. */
1537
1538       if (b->client_info->tunnel)
1539         SVN_ERR(svn_fs_deltify_revision(b->repository->fs, new_rev, pool));
1540
1541       /* Unlock the paths. */
1542       if (! keep_locks && lock_tokens && lock_tokens->nelts)
1543         SVN_ERR(unlock_paths(lock_tokens, b, pool));
1544
1545       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)",
1546                                       new_rev, date, author, post_commit_err));
1547
1548       if (! b->client_info->tunnel)
1549         SVN_ERR(svn_fs_deltify_revision(b->repository->fs, new_rev, pool));
1550     }
1551   return SVN_NO_ERROR;
1552 }
1553
1554 static svn_error_t *
1555 get_file(svn_ra_svn_conn_t *conn,
1556          apr_pool_t *pool,
1557          svn_ra_svn__list_t *params,
1558          void *baton)
1559 {
1560   server_baton_t *b = baton;
1561   const char *path, *full_path, *hex_digest;
1562   svn_revnum_t rev;
1563   svn_fs_root_t *root;
1564   svn_stream_t *contents;
1565   apr_hash_t *props = NULL;
1566   apr_array_header_t *inherited_props;
1567   svn_string_t write_str;
1568   char buf[4096];
1569   apr_size_t len;
1570   svn_boolean_t want_props, want_contents;
1571   apr_uint64_t wants_inherited_props;
1572   svn_checksum_t *checksum;
1573   svn_error_t *err, *write_err;
1574   int i;
1575   authz_baton_t ab;
1576
1577   ab.server = b;
1578   ab.conn = conn;
1579
1580   /* Parse arguments. */
1581   SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)bb?B", &path, &rev,
1582                                   &want_props, &want_contents,
1583                                   &wants_inherited_props));
1584
1585   if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1586     wants_inherited_props = FALSE;
1587
1588   full_path = svn_fspath__join(b->repository->fs_path->data,
1589                                svn_relpath_canonicalize(path, pool), pool);
1590
1591   /* Check authorizations */
1592   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1593                            full_path, FALSE));
1594
1595   if (!SVN_IS_VALID_REVNUM(rev))
1596     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1597
1598   SVN_ERR(log_command(b, conn, pool, "%s",
1599                       svn_log__get_file(full_path, rev,
1600                                         want_contents, want_props, pool)));
1601
1602   /* Fetch the properties and a stream for the contents. */
1603   SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
1604   SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
1605                                    full_path, TRUE, pool));
1606   hex_digest = svn_checksum_to_cstring_display(checksum, pool);
1607
1608   /* Fetch the file's explicit and/or inherited properties if
1609      requested.  Although the wants-iprops boolean was added to the
1610      protocol in 1.8 a standard 1.8 client never requests iprops. */
1611   if (want_props || wants_inherited_props)
1612     SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1613                           wants_inherited_props ? &inherited_props : NULL,
1614                           &ab, root, full_path,
1615                           pool));
1616   if (want_contents)
1617     SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
1618
1619   /* Send successful command response with revision and props. */
1620   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success",
1621                                   hex_digest, rev));
1622   SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1623
1624   if (wants_inherited_props)
1625     {
1626       apr_pool_t *iterpool = svn_pool_create(pool);
1627
1628       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1629       for (i = 0; i < inherited_props->nelts; i++)
1630         {
1631           svn_prop_inherited_item_t *iprop =
1632             APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1633
1634           svn_pool_clear(iterpool);
1635           SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1636                                           iprop->path_or_url));
1637           SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1638           SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1639                                           iprop->path_or_url));
1640         }
1641       svn_pool_destroy(iterpool);
1642     }
1643
1644   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1645
1646   /* Now send the file's contents. */
1647   if (want_contents)
1648     {
1649       err = SVN_NO_ERROR;
1650       while (1)
1651         {
1652           len = sizeof(buf);
1653           err = svn_stream_read_full(contents, buf, &len);
1654           if (err)
1655             break;
1656           if (len > 0)
1657             {
1658               write_str.data = buf;
1659               write_str.len = len;
1660               SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str));
1661             }
1662           if (len < sizeof(buf))
1663             {
1664               err = svn_stream_close(contents);
1665               break;
1666             }
1667         }
1668       write_err = svn_ra_svn__write_cstring(conn, pool, "");
1669       if (write_err)
1670         {
1671           svn_error_clear(err);
1672           return write_err;
1673         }
1674       SVN_CMD_ERR(err);
1675       SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1676     }
1677
1678   return SVN_NO_ERROR;
1679 }
1680
1681 /* Translate all the words in DIRENT_FIELDS_LIST into the flags in
1682  * DIRENT_FIELDS_P.  If DIRENT_FIELDS_LIST is NULL, set all flags. */
1683 static svn_error_t *
1684 parse_dirent_fields(apr_uint32_t *dirent_fields_p,
1685                     svn_ra_svn__list_t *dirent_fields_list)
1686 {
1687   static const svn_string_t str_kind
1688     = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_KIND);
1689   static const svn_string_t str_size
1690     = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_SIZE);
1691   static const svn_string_t str_has_props
1692     = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_HAS_PROPS);
1693   static const svn_string_t str_created_rev
1694     = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_CREATED_REV);
1695   static const svn_string_t str_time
1696     = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_TIME);
1697   static const svn_string_t str_last_author
1698     = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_LAST_AUTHOR);
1699
1700   apr_uint32_t dirent_fields;
1701
1702   if (! dirent_fields_list)
1703     {
1704       dirent_fields = SVN_DIRENT_ALL;
1705     }
1706   else
1707     {
1708       int i;
1709       dirent_fields = 0;
1710
1711       for (i = 0; i < dirent_fields_list->nelts; ++i)
1712         {
1713           svn_ra_svn__item_t *elt
1714             = &SVN_RA_SVN__LIST_ITEM(dirent_fields_list, i);
1715
1716           if (elt->kind != SVN_RA_SVN_WORD)
1717             return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1718                                     "Dirent field not a word");
1719
1720           if (svn_string_compare(&str_kind, &elt->u.word))
1721             dirent_fields |= SVN_DIRENT_KIND;
1722           else if (svn_string_compare(&str_size, &elt->u.word))
1723             dirent_fields |= SVN_DIRENT_SIZE;
1724           else if (svn_string_compare(&str_has_props, &elt->u.word))
1725             dirent_fields |= SVN_DIRENT_HAS_PROPS;
1726           else if (svn_string_compare(&str_created_rev, &elt->u.word))
1727             dirent_fields |= SVN_DIRENT_CREATED_REV;
1728           else if (svn_string_compare(&str_time, &elt->u.word))
1729             dirent_fields |= SVN_DIRENT_TIME;
1730           else if (svn_string_compare(&str_last_author, &elt->u.word))
1731             dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
1732         }
1733     }
1734
1735   *dirent_fields_p = dirent_fields;
1736   return SVN_NO_ERROR;
1737 }
1738
1739 static svn_error_t *
1740 get_dir(svn_ra_svn_conn_t *conn,
1741         apr_pool_t *pool,
1742         svn_ra_svn__list_t *params,
1743         void *baton)
1744 {
1745   server_baton_t *b = baton;
1746   const char *path, *full_path;
1747   svn_revnum_t rev;
1748   apr_hash_t *entries, *props = NULL;
1749   apr_array_header_t *inherited_props;
1750   apr_hash_index_t *hi;
1751   svn_fs_root_t *root;
1752   apr_pool_t *subpool;
1753   svn_boolean_t want_props, want_contents;
1754   apr_uint64_t wants_inherited_props;
1755   apr_uint32_t dirent_fields;
1756   svn_ra_svn__list_t *dirent_fields_list = NULL;
1757   int i;
1758   authz_baton_t ab;
1759
1760   ab.server = b;
1761   ab.conn = conn;
1762
1763   SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)bb?l?B", &path, &rev,
1764                                   &want_props, &want_contents,
1765                                   &dirent_fields_list,
1766                                   &wants_inherited_props));
1767
1768   if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1769     wants_inherited_props = FALSE;
1770
1771   SVN_ERR(parse_dirent_fields(&dirent_fields, dirent_fields_list));
1772   full_path = svn_fspath__join(b->repository->fs_path->data,
1773                                svn_relpath_canonicalize(path, pool), pool);
1774
1775   /* Check authorizations */
1776   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1777                            full_path, FALSE));
1778
1779   if (!SVN_IS_VALID_REVNUM(rev))
1780     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1781
1782   SVN_ERR(log_command(b, conn, pool, "%s",
1783                       svn_log__get_dir(full_path, rev,
1784                                        want_contents, want_props,
1785                                        dirent_fields, pool)));
1786
1787   /* Fetch the root of the appropriate revision. */
1788   SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
1789
1790   /* Fetch the directory's explicit and/or inherited properties if
1791      requested.  Although the wants-iprops boolean was added to the
1792      protocol in 1.8 a standard 1.8 client never requests iprops. */
1793   if (want_props || wants_inherited_props)
1794     SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1795                           wants_inherited_props ? &inherited_props : NULL,
1796                           &ab, root, full_path,
1797                           pool));
1798
1799   /* Fetch the directories' entries before starting the response, to allow
1800      proper error handling in cases like when FULL_PATH doesn't exist */
1801   if (want_contents)
1802       SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
1803
1804   /* Begin response ... */
1805   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev));
1806   SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1807   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!"));
1808
1809   /* Fetch the directory entries if requested and send them immediately. */
1810   if (want_contents)
1811     {
1812       /* Use epoch for a placeholder for a missing date.  */
1813       const char *missing_date = svn_time_to_cstring(0, pool);
1814
1815       /* Transform the hash table's FS entries into dirents.  This probably
1816        * belongs in libsvn_repos. */
1817       subpool = svn_pool_create(pool);
1818       for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1819         {
1820           const char *name = apr_hash_this_key(hi);
1821           svn_fs_dirent_t *fsent = apr_hash_this_val(hi);
1822           const char *file_path;
1823
1824           /* The fields in the entry tuple.  */
1825           svn_node_kind_t entry_kind = svn_node_none;
1826           svn_filesize_t entry_size = 0;
1827           svn_boolean_t has_props = FALSE;
1828           /* If 'created rev' was not requested, send 0.  We can't use
1829            * SVN_INVALID_REVNUM as the tuple field is not optional.
1830            * See the email thread on dev@, 2012-03-28, subject
1831            * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra",
1832            * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */
1833           svn_revnum_t created_rev = 0;
1834           const char *cdate = NULL;
1835           const char *last_author = NULL;
1836
1837           svn_pool_clear(subpool);
1838
1839           file_path = svn_fspath__join(full_path, name, subpool);
1840           if (! lookup_access(subpool, b, svn_authz_read, file_path, FALSE))
1841             continue;
1842
1843           if (dirent_fields & SVN_DIRENT_KIND)
1844               entry_kind = fsent->kind;
1845
1846           if (dirent_fields & SVN_DIRENT_SIZE)
1847               if (fsent->kind != svn_node_dir)
1848                 SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path,
1849                                                subpool));
1850
1851           if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1852             {
1853               /* has_props */
1854               SVN_CMD_ERR(svn_fs_node_has_props(&has_props, root, file_path,
1855                                                subpool));
1856             }
1857
1858           if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1859               || (dirent_fields & SVN_DIRENT_TIME)
1860               || (dirent_fields & SVN_DIRENT_CREATED_REV))
1861             {
1862               /* created_rev, last_author, time */
1863               SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev,
1864                                                        &cdate,
1865                                                        &last_author,
1866                                                        root,
1867                                                        file_path,
1868                                                        subpool));
1869             }
1870
1871           /* The client does not properly handle a missing CDATE. For
1872              interoperability purposes, we must fill in some junk.
1873
1874              See libsvn_ra_svn/client.c:ra_svn_get_dir()  */
1875           if (cdate == NULL)
1876             cdate = missing_date;
1877
1878           /* Send the entry. */
1879           SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
1880                                           svn_node_kind_to_word(entry_kind),
1881                                           (apr_uint64_t) entry_size,
1882                                           has_props, created_rev,
1883                                           cdate, last_author));
1884         }
1885       svn_pool_destroy(subpool);
1886     }
1887
1888   if (wants_inherited_props)
1889     {
1890       apr_pool_t *iterpool = svn_pool_create(pool);
1891
1892       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1893       for (i = 0; i < inherited_props->nelts; i++)
1894         {
1895           svn_prop_inherited_item_t *iprop =
1896             APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1897
1898           svn_pool_clear(iterpool);
1899           SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1900                                           iprop->path_or_url));
1901           SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1902           SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1903                                           iprop->path_or_url));
1904         }
1905       svn_pool_destroy(iterpool);
1906     }
1907
1908   /* Finish response. */
1909   return svn_ra_svn__write_tuple(conn, pool, "!))");
1910 }
1911
1912 static svn_error_t *
1913 update(svn_ra_svn_conn_t *conn,
1914        apr_pool_t *pool,
1915        svn_ra_svn__list_t *params,
1916        void *baton)
1917 {
1918   server_baton_t *b = baton;
1919   svn_revnum_t rev;
1920   const char *target, *full_path, *depth_word;
1921   svn_boolean_t recurse;
1922   svn_tristate_t send_copyfrom_args; /* Optional; default FALSE */
1923   svn_tristate_t ignore_ancestry; /* Optional; default FALSE */
1924   /* Default to unknown.  Old clients won't send depth, but we'll
1925      handle that by converting recurse if necessary. */
1926   svn_depth_t depth = svn_depth_unknown;
1927   svn_boolean_t is_checkout;
1928
1929   /* Parse the arguments. */
1930   SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cb?w3?3", &rev, &target,
1931                                   &recurse, &depth_word,
1932                                   &send_copyfrom_args, &ignore_ancestry));
1933   target = svn_relpath_canonicalize(target, pool);
1934
1935   if (depth_word)
1936     depth = svn_depth_from_word(depth_word);
1937   else
1938     depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1939
1940   full_path = svn_fspath__join(b->repository->fs_path->data, target, pool);
1941   /* Check authorization and authenticate the user if necessary. */
1942   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
1943
1944   if (!SVN_IS_VALID_REVNUM(rev))
1945     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1946
1947   SVN_ERR(accept_report(&is_checkout, NULL,
1948                         conn, pool, b, rev, target, NULL, TRUE,
1949                         depth,
1950                         (send_copyfrom_args == svn_tristate_true),
1951                         (ignore_ancestry == svn_tristate_true)));
1952   if (is_checkout)
1953     {
1954       SVN_ERR(log_command(b, conn, pool, "%s",
1955                           svn_log__checkout(full_path, rev,
1956                                             depth, pool)));
1957     }
1958   else
1959     {
1960       SVN_ERR(log_command(b, conn, pool, "%s",
1961                           svn_log__update(full_path, rev, depth,
1962                                           (send_copyfrom_args
1963                                            == svn_tristate_true),
1964                                           pool)));
1965     }
1966
1967   return SVN_NO_ERROR;
1968 }
1969
1970 static svn_error_t *
1971 switch_cmd(svn_ra_svn_conn_t *conn,
1972            apr_pool_t *pool,
1973            svn_ra_svn__list_t *params,
1974            void *baton)
1975 {
1976   server_baton_t *b = baton;
1977   svn_revnum_t rev;
1978   const char *target, *depth_word;
1979   const char *switch_url, *switch_path;
1980   svn_boolean_t recurse;
1981   /* Default to unknown.  Old clients won't send depth, but we'll
1982      handle that by converting recurse if necessary. */
1983   svn_depth_t depth = svn_depth_unknown;
1984   svn_tristate_t send_copyfrom_args; /* Optional; default FALSE */
1985   svn_tristate_t ignore_ancestry; /* Optional; default TRUE */
1986
1987   /* Parse the arguments. */
1988   SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbc?w?33", &rev, &target,
1989                                   &recurse, &switch_url, &depth_word,
1990                                   &send_copyfrom_args, &ignore_ancestry));
1991   target = svn_relpath_canonicalize(target, pool);
1992   switch_url = svn_uri_canonicalize(switch_url, pool);
1993
1994   if (depth_word)
1995     depth = svn_depth_from_word(depth_word);
1996   else
1997     depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1998
1999   SVN_ERR(trivial_auth_request(conn, pool, b));
2000   if (!SVN_IS_VALID_REVNUM(rev))
2001     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2002
2003   SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url,
2004                                               pool),
2005                           svn_path_uri_decode(switch_url, pool),
2006                           &switch_path));
2007
2008   {
2009     const char *full_path = svn_fspath__join(b->repository->fs_path->data,
2010                                              target, pool);
2011     SVN_ERR(log_command(b, conn, pool, "%s",
2012                         svn_log__switch(full_path, switch_path, rev,
2013                                         depth, pool)));
2014   }
2015
2016   return accept_report(NULL, NULL,
2017                        conn, pool, b, rev, target, switch_path, TRUE,
2018                        depth,
2019                        (send_copyfrom_args == svn_tristate_true),
2020                        (ignore_ancestry != svn_tristate_false));
2021 }
2022
2023 static svn_error_t *
2024 status(svn_ra_svn_conn_t *conn,
2025        apr_pool_t *pool,
2026        svn_ra_svn__list_t *params,
2027        void *baton)
2028 {
2029   server_baton_t *b = baton;
2030   svn_revnum_t rev;
2031   const char *target, *depth_word;
2032   svn_boolean_t recurse;
2033   /* Default to unknown.  Old clients won't send depth, but we'll
2034      handle that by converting recurse if necessary. */
2035   svn_depth_t depth = svn_depth_unknown;
2036
2037   /* Parse the arguments. */
2038   SVN_ERR(svn_ra_svn__parse_tuple(params, "cb?(?r)?w",
2039                                   &target, &recurse, &rev, &depth_word));
2040   target = svn_relpath_canonicalize(target, pool);
2041
2042   if (depth_word)
2043     depth = svn_depth_from_word(depth_word);
2044   else
2045     depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
2046
2047   SVN_ERR(trivial_auth_request(conn, pool, b));
2048   if (!SVN_IS_VALID_REVNUM(rev))
2049     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2050
2051   {
2052     const char *full_path = svn_fspath__join(b->repository->fs_path->data,
2053                                              target, pool);
2054     SVN_ERR(log_command(b, conn, pool, "%s",
2055                         svn_log__status(full_path, rev, depth, pool)));
2056   }
2057
2058   return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
2059                        depth, FALSE, FALSE);
2060 }
2061
2062 static svn_error_t *
2063 diff(svn_ra_svn_conn_t *conn,
2064      apr_pool_t *pool,
2065      svn_ra_svn__list_t *params,
2066      void *baton)
2067 {
2068   server_baton_t *b = baton;
2069   svn_revnum_t rev;
2070   const char *target, *versus_url, *versus_path, *depth_word;
2071   svn_boolean_t recurse, ignore_ancestry;
2072   svn_boolean_t text_deltas;
2073   /* Default to unknown.  Old clients won't send depth, but we'll
2074      handle that by converting recurse if necessary. */
2075   svn_depth_t depth = svn_depth_unknown;
2076
2077   /* Parse the arguments. */
2078   if (params->nelts == 5)
2079     {
2080       /* Clients before 1.4 don't send the text_deltas boolean or depth. */
2081       SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbbc", &rev, &target,
2082                                       &recurse, &ignore_ancestry, &versus_url));
2083       text_deltas = TRUE;
2084       depth_word = NULL;
2085     }
2086   else
2087     {
2088       SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbbcb?w",
2089                                       &rev, &target, &recurse,
2090                                       &ignore_ancestry, &versus_url,
2091                                       &text_deltas, &depth_word));
2092     }
2093   target = svn_relpath_canonicalize(target, pool);
2094   versus_url = svn_uri_canonicalize(versus_url, pool);
2095
2096   if (depth_word)
2097     depth = svn_depth_from_word(depth_word);
2098   else
2099     depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
2100
2101   SVN_ERR(trivial_auth_request(conn, pool, b));
2102
2103   if (!SVN_IS_VALID_REVNUM(rev))
2104     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2105   SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url,
2106                                               pool),
2107                           svn_path_uri_decode(versus_url, pool),
2108                           &versus_path));
2109
2110   {
2111     const char *full_path = svn_fspath__join(b->repository->fs_path->data,
2112                                              target, pool);
2113     svn_revnum_t from_rev;
2114     SVN_ERR(accept_report(NULL, &from_rev,
2115                           conn, pool, b, rev, target, versus_path,
2116                           text_deltas, depth, FALSE, ignore_ancestry));
2117     SVN_ERR(log_command(b, conn, pool, "%s",
2118                         svn_log__diff(full_path, from_rev, versus_path,
2119                                       rev, depth, ignore_ancestry,
2120                                       pool)));
2121   }
2122   return SVN_NO_ERROR;
2123 }
2124
2125 /* Baton type to be used with mergeinfo_receiver. */
2126 typedef struct mergeinfo_receiver_baton_t
2127 {
2128   /* Send the response over this connection. */
2129   svn_ra_svn_conn_t *conn;
2130
2131   /* Start path of the query; report paths relative to this one. */
2132   const char *fs_path;
2133
2134   /* Did we already send the opening sequence? */
2135   svn_boolean_t starting_tuple_sent;
2136 } mergeinfo_receiver_baton_t;
2137
2138 /* Utility method sending the start of the "get m/i" response once
2139    over BATON->CONN. */
2140 static svn_error_t *
2141 send_mergeinfo_starting_tuple(mergeinfo_receiver_baton_t *baton,
2142                               apr_pool_t *scratch_pool)
2143 {
2144   if (baton->starting_tuple_sent)
2145     return SVN_NO_ERROR;
2146
2147   SVN_ERR(svn_ra_svn__write_tuple(baton->conn, scratch_pool,
2148                                   "w((!", "success"));
2149   baton->starting_tuple_sent = TRUE;
2150
2151   return SVN_NO_ERROR;
2152 }
2153
2154 /* Implements svn_repos_mergeinfo_receiver_t, sending the MERGEINFO
2155  * out over the connection in the mergeinfo_receiver_baton_t * BATON. */
2156 static svn_error_t *
2157 mergeinfo_receiver(const char *path,
2158                    svn_mergeinfo_t mergeinfo,
2159                    void *baton,
2160                    apr_pool_t *scratch_pool)
2161 {
2162   mergeinfo_receiver_baton_t *b = baton;
2163   svn_string_t *mergeinfo_string;
2164
2165   /* Delay starting the response until we checked that the initial
2166      request went through.  We are at that point now b/c we've got
2167      the first results in. */
2168   SVN_ERR(send_mergeinfo_starting_tuple(b, scratch_pool));
2169
2170   /* Adjust the path info and send the m/i. */
2171   path = svn_fspath__skip_ancestor(b->fs_path, path);
2172   SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo,
2173                                   scratch_pool));
2174   SVN_ERR(svn_ra_svn__write_tuple(b->conn, scratch_pool, "cs", path,
2175                                   mergeinfo_string));
2176
2177   return SVN_NO_ERROR;
2178 }
2179
2180 /* Regardless of whether a client's capabilities indicate an
2181    understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
2182    we provide a response.
2183
2184    ASSUMPTION: When performing a 'merge' with two URLs at different
2185    revisions, the client will call this command more than once. */
2186 static svn_error_t *
2187 get_mergeinfo(svn_ra_svn_conn_t *conn,
2188               apr_pool_t *pool,
2189               svn_ra_svn__list_t *params,
2190               void *baton)
2191 {
2192   server_baton_t *b = baton;
2193   svn_revnum_t rev;
2194   svn_ra_svn__list_t *paths;
2195   apr_array_header_t *canonical_paths;
2196   int i;
2197   const char *inherit_word;
2198   svn_mergeinfo_inheritance_t inherit;
2199   svn_boolean_t include_descendants;
2200   authz_baton_t ab;
2201   mergeinfo_receiver_baton_t mergeinfo_baton;
2202
2203   ab.server = b;
2204   ab.conn = conn;
2205
2206   mergeinfo_baton.conn = conn;
2207   mergeinfo_baton.fs_path = b->repository->fs_path->data;
2208   mergeinfo_baton.starting_tuple_sent = FALSE;
2209
2210   SVN_ERR(svn_ra_svn__parse_tuple(params, "l(?r)wb", &paths, &rev,
2211                                   &inherit_word, &include_descendants));
2212   inherit = svn_inheritance_from_word(inherit_word);
2213
2214   /* Canonicalize the paths which mergeinfo has been requested for. */
2215   canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2216   for (i = 0; i < paths->nelts; i++)
2217      {
2218         svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(paths, i);
2219         const char *full_path;
2220
2221         if (item->kind != SVN_RA_SVN_STRING)
2222           return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2223                                   _("Path is not a string"));
2224         full_path = svn_relpath_canonicalize(item->u.string.data, pool);
2225         full_path = svn_fspath__join(b->repository->fs_path->data, full_path, pool);
2226         APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
2227      }
2228
2229   SVN_ERR(log_command(b, conn, pool, "%s",
2230                       svn_log__get_mergeinfo(canonical_paths, inherit,
2231                                              include_descendants,
2232                                              pool)));
2233
2234   SVN_ERR(trivial_auth_request(conn, pool, b));
2235
2236   SVN_CMD_ERR(svn_repos_fs_get_mergeinfo2(b->repository->repos,
2237                                           canonical_paths, rev,
2238                                           inherit,
2239                                           include_descendants,
2240                                           authz_check_access_cb_func(b), &ab,
2241                                           mergeinfo_receiver,
2242                                           &mergeinfo_baton,
2243                                           pool));
2244
2245   /* We might not have sent anything
2246      => ensure to begin the response in any case. */
2247   SVN_ERR(send_mergeinfo_starting_tuple(&mergeinfo_baton, pool));
2248   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2249
2250   return SVN_NO_ERROR;
2251 }
2252
2253 /* Send a changed paths list entry to the client.
2254    This implements svn_repos_path_change_receiver_t. */
2255 static svn_error_t *
2256 path_change_receiver(void *baton,
2257                      svn_repos_path_change_t *change,
2258                      apr_pool_t *scratch_pool)
2259 {
2260   const char symbol[] = "MADR";
2261
2262   log_baton_t *b = baton;
2263   svn_ra_svn_conn_t *conn = b->conn;
2264
2265   /* Sanitize and convert change kind to ra-svn level action.
2266
2267      Pushing that conversion down into libsvn_ra_svn would add yet another
2268      API dependency there. */
2269   char action = (   change->change_kind < svn_fs_path_change_modify
2270                  || change->change_kind > svn_fs_path_change_replace)
2271               ? 0
2272               : symbol[change->change_kind];
2273
2274   /* Open lists once: LOG_ENTRY and LOG_ENTRY->CHANGED_PATHS. */
2275   if (!b->started)
2276     {
2277       SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool));
2278       SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool));
2279       b->started = TRUE;
2280     }
2281
2282   /* Serialize CHANGE. */
2283   SVN_ERR(svn_ra_svn__write_data_log_changed_path(
2284               conn, scratch_pool,
2285               &change->path,
2286               action,
2287               change->copyfrom_path,
2288               change->copyfrom_rev,
2289               change->node_kind,
2290               change->text_mod,
2291               change->prop_mod));
2292
2293   return SVN_NO_ERROR;
2294 }
2295
2296 /* Send a the meta data and the revpros for LOG_ENTRY to the client.
2297    This implements svn_log_entry_receiver_t. */
2298 static svn_error_t *
2299 revision_receiver(void *baton,
2300                   svn_repos_log_entry_t *log_entry,
2301                   apr_pool_t *scratch_pool)
2302 {
2303   log_baton_t *b = baton;
2304   svn_ra_svn_conn_t *conn = b->conn;
2305   svn_boolean_t invalid_revnum = FALSE;
2306   const svn_string_t *author, *date, *message;
2307   unsigned revprop_count;
2308
2309   if (log_entry->revision == SVN_INVALID_REVNUM)
2310     {
2311       /* If the stack depth is zero, we've seen the last revision, so don't
2312          send it, just return. */
2313       if (b->stack_depth == 0)
2314         return SVN_NO_ERROR;
2315
2316       /* Because the svn protocol won't let us send an invalid revnum, we have
2317          to fudge here and send an additional flag. */
2318       log_entry->revision = 0;
2319       invalid_revnum = TRUE;
2320       b->stack_depth--;
2321     }
2322
2323   svn_compat_log_revprops_out_string(&author, &date, &message,
2324                                      log_entry->revprops);
2325
2326   /* Revprops list filtering is somewhat expensive.
2327      Avoid doing that for the 90% case where only the standard revprops
2328      have been requested and delivered. */
2329   if (author && date && message && apr_hash_count(log_entry->revprops) == 3)
2330     {
2331       revprop_count = 0;
2332     }
2333   else
2334     {
2335       svn_compat_log_revprops_clear(log_entry->revprops);
2336       if (log_entry->revprops)
2337         revprop_count = apr_hash_count(log_entry->revprops);
2338       else
2339         revprop_count = 0;
2340     }
2341
2342   /* Open lists once: LOG_ENTRY and LOG_ENTRY->CHANGED_PATHS. */
2343   if (!b->started)
2344     {
2345       SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool));
2346       SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool));
2347     }
2348
2349   /* Close LOG_ENTRY->CHANGED_PATHS. */
2350   SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool));
2351   b->started = FALSE;
2352
2353   /* send LOG_ENTRY main members */
2354   SVN_ERR(svn_ra_svn__write_data_log_entry(conn, scratch_pool,
2355                                            log_entry->revision,
2356                                            author, date, message,
2357                                            log_entry->has_children,
2358                                            invalid_revnum, revprop_count));
2359
2360   /* send LOG_ENTRY->REVPROPS */
2361   SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool));
2362   if (revprop_count)
2363     SVN_ERR(svn_ra_svn__write_proplist(conn, scratch_pool,
2364                                        log_entry->revprops));
2365   SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool));
2366
2367   /* send LOG_ENTRY members that were added in later SVN releases */
2368   SVN_ERR(svn_ra_svn__write_boolean(conn, scratch_pool,
2369                                     log_entry->subtractive_merge));
2370   SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool));
2371
2372   if (log_entry->has_children)
2373     b->stack_depth++;
2374
2375   return SVN_NO_ERROR;
2376 }
2377
2378 static svn_error_t *
2379 log_cmd(svn_ra_svn_conn_t *conn,
2380         apr_pool_t *pool,
2381         svn_ra_svn__list_t *params,
2382         void *baton)
2383 {
2384   svn_error_t *err, *write_err;
2385   server_baton_t *b = baton;
2386   svn_revnum_t start_rev, end_rev;
2387   const char *full_path;
2388   svn_boolean_t send_changed_paths, strict_node, include_merged_revisions;
2389   apr_array_header_t *full_paths, *revprops;
2390   svn_ra_svn__list_t *paths, *revprop_items;
2391   char *revprop_word;
2392   svn_ra_svn__item_t *elt;
2393   int i;
2394   apr_uint64_t limit, include_merged_revs_param;
2395   log_baton_t lb;
2396   authz_baton_t ab;
2397
2398   ab.server = b;
2399   ab.conn = conn;
2400
2401   SVN_ERR(svn_ra_svn__parse_tuple(params, "l(?r)(?r)bb?n?Bwl", &paths,
2402                                   &start_rev, &end_rev, &send_changed_paths,
2403                                   &strict_node, &limit,
2404                                   &include_merged_revs_param,
2405                                   &revprop_word, &revprop_items));
2406
2407   if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2408     include_merged_revisions = FALSE;
2409   else
2410     include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2411
2412   if (revprop_word == NULL)
2413     /* pre-1.5 client */
2414     revprops = svn_compat_log_revprops_in(pool);
2415   else if (strcmp(revprop_word, "all-revprops") == 0)
2416     revprops = NULL;
2417   else if (strcmp(revprop_word, "revprops") == 0)
2418     {
2419       SVN_ERR_ASSERT(revprop_items);
2420
2421       revprops = apr_array_make(pool, revprop_items->nelts,
2422                                 sizeof(char *));
2423       for (i = 0; i < revprop_items->nelts; i++)
2424         {
2425           elt = &SVN_RA_SVN__LIST_ITEM(revprop_items, i);
2426           if (elt->kind != SVN_RA_SVN_STRING)
2427             return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2428                                     _("Log revprop entry not a string"));
2429           APR_ARRAY_PUSH(revprops, const char *) = elt->u.string.data;
2430         }
2431     }
2432   else
2433     return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2434                              _("Unknown revprop word '%s' in log command"),
2435                              revprop_word);
2436
2437   /* If we got an unspecified number then the user didn't send us anything,
2438      so we assume no limit.  If it's larger than INT_MAX then someone is
2439      messing with us, since we know the svn client libraries will never send
2440      us anything that big, so play it safe and default to no limit. */
2441   if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
2442     limit = 0;
2443
2444   full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2445   for (i = 0; i < paths->nelts; i++)
2446     {
2447       elt = &SVN_RA_SVN__LIST_ITEM(paths, i);
2448       if (elt->kind != SVN_RA_SVN_STRING)
2449         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2450                                 _("Log path entry not a string"));
2451       full_path = svn_relpath_canonicalize(elt->u.string.data, pool),
2452       full_path = svn_fspath__join(b->repository->fs_path->data, full_path,
2453                                    pool);
2454       APR_ARRAY_PUSH(full_paths, const char *) = full_path;
2455     }
2456   SVN_ERR(trivial_auth_request(conn, pool, b));
2457
2458   SVN_ERR(log_command(b, conn, pool, "%s",
2459                       svn_log__log(full_paths, start_rev, end_rev,
2460                                    (int) limit, send_changed_paths,
2461                                    strict_node, include_merged_revisions,
2462                                    revprops, pool)));
2463
2464   /* Get logs.  (Can't report errors back to the client at this point.) */
2465   lb.fs_path = b->repository->fs_path->data;
2466   lb.conn = conn;
2467   lb.stack_depth = 0;
2468   lb.started = FALSE;
2469   err = svn_repos_get_logs5(b->repository->repos, full_paths, start_rev,
2470                             end_rev, (int) limit,
2471                             strict_node, include_merged_revisions,
2472                             revprops, authz_check_access_cb_func(b), &ab,
2473                             send_changed_paths ? path_change_receiver : NULL,
2474                             send_changed_paths ? &lb : NULL,
2475                             revision_receiver, &lb, pool);
2476
2477   write_err = svn_ra_svn__write_word(conn, pool, "done");
2478   if (write_err)
2479     {
2480       svn_error_clear(err);
2481       return write_err;
2482     }
2483   SVN_CMD_ERR(err);
2484   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2485   return SVN_NO_ERROR;
2486 }
2487
2488 static svn_error_t *
2489 check_path(svn_ra_svn_conn_t *conn,
2490            apr_pool_t *pool,
2491            svn_ra_svn__list_t *params,
2492            void *baton)
2493 {
2494   server_baton_t *b = baton;
2495   svn_revnum_t rev;
2496   const char *path, *full_path;
2497   svn_fs_root_t *root;
2498   svn_node_kind_t kind;
2499
2500   SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev));
2501   full_path = svn_fspath__join(b->repository->fs_path->data,
2502                                svn_relpath_canonicalize(path, pool), pool);
2503
2504   /* Check authorizations */
2505   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2506                            full_path, FALSE));
2507
2508   if (!SVN_IS_VALID_REVNUM(rev))
2509     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2510
2511   SVN_ERR(log_command(b, conn, pool, "check-path %s@%d",
2512                       svn_path_uri_encode(full_path, pool), rev));
2513
2514   SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
2515   SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
2516   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w",
2517                                          svn_node_kind_to_word(kind)));
2518   return SVN_NO_ERROR;
2519 }
2520
2521 static svn_error_t *
2522 stat_cmd(svn_ra_svn_conn_t *conn,
2523          apr_pool_t *pool,
2524          svn_ra_svn__list_t *params,
2525          void *baton)
2526 {
2527   server_baton_t *b = baton;
2528   svn_revnum_t rev;
2529   const char *path, *full_path, *cdate;
2530   svn_fs_root_t *root;
2531   svn_dirent_t *dirent;
2532
2533   SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev));
2534   full_path = svn_fspath__join(b->repository->fs_path->data,
2535                                svn_relpath_canonicalize(path, pool), pool);
2536
2537   /* Check authorizations */
2538   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2539                            full_path, FALSE));
2540
2541   if (!SVN_IS_VALID_REVNUM(rev))
2542     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2543
2544   SVN_ERR(log_command(b, conn, pool, "stat %s@%d",
2545                       svn_path_uri_encode(full_path, pool), rev));
2546
2547   SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
2548   SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
2549
2550   /* Need to return the equivalent of "(?l)", since that's what the
2551      client is reading.  */
2552
2553   if (dirent == NULL)
2554     {
2555       SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()"));
2556       return SVN_NO_ERROR;
2557     }
2558
2559   cdate = (dirent->time == (time_t) -1) ? NULL
2560     : svn_time_to_cstring(dirent->time, pool);
2561
2562   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
2563                                          svn_node_kind_to_word(dirent->kind),
2564                                          (apr_uint64_t) dirent->size,
2565                                          dirent->has_props, dirent->created_rev,
2566                                          cdate, dirent->last_author));
2567
2568   return SVN_NO_ERROR;
2569 }
2570
2571 static svn_error_t *
2572 get_locations(svn_ra_svn_conn_t *conn,
2573               apr_pool_t *pool,
2574               svn_ra_svn__list_t *params,
2575               void *baton)
2576 {
2577   svn_error_t *err, *write_err;
2578   server_baton_t *b = baton;
2579   svn_revnum_t revision;
2580   apr_array_header_t *location_revisions;
2581   svn_ra_svn__list_t *loc_revs_proto;
2582   svn_ra_svn__item_t *elt;
2583   int i;
2584   const char *relative_path;
2585   svn_revnum_t peg_revision;
2586   apr_hash_t *fs_locations;
2587   const char *abs_path;
2588   authz_baton_t ab;
2589
2590   ab.server = b;
2591   ab.conn = conn;
2592
2593   /* Parse the arguments. */
2594   SVN_ERR(svn_ra_svn__parse_tuple(params, "crl", &relative_path,
2595                                   &peg_revision,
2596                                   &loc_revs_proto));
2597   relative_path = svn_relpath_canonicalize(relative_path, pool);
2598
2599   abs_path = svn_fspath__join(b->repository->fs_path->data, relative_path,
2600                               pool);
2601
2602   location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
2603                                       sizeof(svn_revnum_t));
2604   for (i = 0; i < loc_revs_proto->nelts; i++)
2605     {
2606       elt = &SVN_RA_SVN__LIST_ITEM(loc_revs_proto, i);
2607       if (elt->kind != SVN_RA_SVN_NUMBER)
2608         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2609                                 "Get-locations location revisions entry "
2610                                 "not a revision number");
2611       revision = (svn_revnum_t)(elt->u.number);
2612       APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
2613     }
2614   SVN_ERR(trivial_auth_request(conn, pool, b));
2615   SVN_ERR(log_command(b, conn, pool, "%s",
2616                       svn_log__get_locations(abs_path, peg_revision,
2617                                              location_revisions, pool)));
2618
2619   /* All the parameters are fine - let's perform the query against the
2620    * repository. */
2621
2622   /* We store both err and write_err here, so the client will get
2623    * the "done" even if there was an error in fetching the results. */
2624
2625   err = svn_repos_trace_node_locations(b->repository->fs, &fs_locations,
2626                                        abs_path, peg_revision,
2627                                        location_revisions,
2628                                        authz_check_access_cb_func(b), &ab,
2629                                        pool);
2630
2631   /* Now, write the results to the connection. */
2632   if (!err)
2633     {
2634       if (fs_locations)
2635         {
2636           apr_hash_index_t *iter;
2637
2638           for (iter = apr_hash_first(pool, fs_locations); iter;
2639               iter = apr_hash_next(iter))
2640             {
2641               const svn_revnum_t *iter_key = apr_hash_this_key(iter);
2642               const char *iter_value = apr_hash_this_val(iter);
2643
2644               SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc",
2645                                               *iter_key, iter_value));
2646             }
2647         }
2648     }
2649
2650   write_err = svn_ra_svn__write_word(conn, pool, "done");
2651   if (write_err)
2652     {
2653       svn_error_clear(err);
2654       return write_err;
2655     }
2656   SVN_CMD_ERR(err);
2657
2658   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2659
2660   return SVN_NO_ERROR;
2661 }
2662
2663 static svn_error_t *gls_receiver(svn_location_segment_t *segment,
2664                                  void *baton,
2665                                  apr_pool_t *pool)
2666 {
2667   svn_ra_svn_conn_t *conn = baton;
2668   return svn_ra_svn__write_tuple(conn, pool, "rr(?c)",
2669                                  segment->range_start,
2670                                  segment->range_end,
2671                                  segment->path);
2672 }
2673
2674 static svn_error_t *
2675 get_location_segments(svn_ra_svn_conn_t *conn,
2676                       apr_pool_t *pool,
2677                       svn_ra_svn__list_t *params,
2678                       void *baton)
2679 {
2680   svn_error_t *err, *write_err;
2681   server_baton_t *b = baton;
2682   svn_revnum_t peg_revision, start_rev, end_rev;
2683   const char *relative_path;
2684   const char *abs_path;
2685   authz_baton_t ab;
2686
2687   ab.server = b;
2688   ab.conn = conn;
2689
2690   /* Parse the arguments. */
2691   SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)(?r)(?r)",
2692                                   &relative_path, &peg_revision,
2693                                   &start_rev, &end_rev));
2694   relative_path = svn_relpath_canonicalize(relative_path, pool);
2695
2696   abs_path = svn_fspath__join(b->repository->fs_path->data, relative_path,
2697                               pool);
2698
2699   SVN_ERR(trivial_auth_request(conn, pool, b));
2700   SVN_ERR(log_command(baton, conn, pool, "%s",
2701                       svn_log__get_location_segments(abs_path, peg_revision,
2702                                                      start_rev, end_rev,
2703                                                      pool)));
2704
2705   /* No START_REV or PEG_REVISION?  We'll use HEAD. */
2706   if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision))
2707     {
2708       svn_revnum_t youngest;
2709
2710       err = svn_fs_youngest_rev(&youngest, b->repository->fs, pool);
2711
2712       if (err)
2713         {
2714           err = svn_error_compose_create(
2715                     svn_ra_svn__write_word(conn, pool, "done"),
2716                     err);
2717
2718           return log_fail_and_flush(err, b, conn, pool);
2719         }
2720
2721       if (!SVN_IS_VALID_REVNUM(start_rev))
2722         start_rev = youngest;
2723       if (!SVN_IS_VALID_REVNUM(peg_revision))
2724         peg_revision = youngest;
2725     }
2726
2727   /* No END_REV?  We'll use 0. */
2728   if (!SVN_IS_VALID_REVNUM(end_rev))
2729     end_rev = 0;
2730
2731   if (end_rev > start_rev)
2732     {
2733       err = svn_ra_svn__write_word(conn, pool, "done");
2734       err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, err,
2735                               "Get-location-segments end revision must not be "
2736                               "younger than start revision");
2737       return log_fail_and_flush(err, b, conn, pool);
2738     }
2739
2740   if (start_rev > peg_revision)
2741     {
2742       err = svn_ra_svn__write_word(conn, pool, "done");
2743       err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, err,
2744                               "Get-location-segments start revision must not "
2745                               "be younger than peg revision");
2746       return log_fail_and_flush(err, b, conn, pool);
2747     }
2748
2749   /* All the parameters are fine - let's perform the query against the
2750    * repository. */
2751
2752   /* We store both err and write_err here, so the client will get
2753    * the "done" even if there was an error in fetching the results. */
2754
2755   err = svn_repos_node_location_segments(b->repository->repos, abs_path,
2756                                          peg_revision, start_rev, end_rev,
2757                                          gls_receiver, (void *)conn,
2758                                          authz_check_access_cb_func(b), &ab,
2759                                          pool);
2760   write_err = svn_ra_svn__write_word(conn, pool, "done");
2761   if (write_err)
2762     {
2763       return svn_error_compose_create(write_err, err);
2764     }
2765   SVN_CMD_ERR(err);
2766
2767   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2768
2769   return SVN_NO_ERROR;
2770 }
2771
2772 /* This implements svn_write_fn_t.  Write LEN bytes starting at DATA to the
2773    client as a string. */
2774 static svn_error_t *svndiff_handler(void *baton, const char *data,
2775                                     apr_size_t *len)
2776 {
2777   file_revs_baton_t *b = baton;
2778   svn_string_t str;
2779
2780   str.data = data;
2781   str.len = *len;
2782   return svn_ra_svn__write_string(b->conn, b->pool, &str);
2783 }
2784
2785 /* This implements svn_close_fn_t.  Mark the end of the data by writing an
2786    empty string to the client. */
2787 static svn_error_t *svndiff_close_handler(void *baton)
2788 {
2789   file_revs_baton_t *b = baton;
2790
2791   SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, ""));
2792   return SVN_NO_ERROR;
2793 }
2794
2795 /* This implements the svn_repos_file_rev_handler_t interface. */
2796 static svn_error_t *file_rev_handler(void *baton, const char *path,
2797                                      svn_revnum_t rev, apr_hash_t *rev_props,
2798                                      svn_boolean_t merged_revision,
2799                                      svn_txdelta_window_handler_t *d_handler,
2800                                      void **d_baton,
2801                                      apr_array_header_t *prop_diffs,
2802                                      apr_pool_t *pool)
2803 {
2804   file_revs_baton_t *frb = baton;
2805   svn_stream_t *stream;
2806
2807   SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!",
2808                                   path, rev));
2809   SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props));
2810   SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!"));
2811   SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
2812   SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision));
2813
2814   /* Store the pool for the delta stream. */
2815   frb->pool = pool;
2816
2817   /* Prepare for the delta or just write an empty string. */
2818   if (d_handler)
2819     {
2820       stream = svn_stream_create(baton, pool);
2821       svn_stream_set_write(stream, svndiff_handler);
2822       svn_stream_set_close(stream, svndiff_close_handler);
2823
2824       svn_txdelta_to_svndiff3(d_handler, d_baton, stream,
2825                               svn_ra_svn__svndiff_version(frb->conn),
2826                               svn_ra_svn_compression_level(frb->conn), pool);
2827     }
2828   else
2829     SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, ""));
2830
2831   return SVN_NO_ERROR;
2832 }
2833
2834 static svn_error_t *
2835 get_file_revs(svn_ra_svn_conn_t *conn,
2836               apr_pool_t *pool,
2837               svn_ra_svn__list_t *params,
2838               void *baton)
2839 {
2840   server_baton_t *b = baton;
2841   svn_error_t *err, *write_err;
2842   file_revs_baton_t frb;
2843   svn_revnum_t start_rev, end_rev;
2844   const char *path;
2845   const char *full_path;
2846   apr_uint64_t include_merged_revs_param;
2847   svn_boolean_t include_merged_revisions;
2848   authz_baton_t ab;
2849
2850   ab.server = b;
2851   ab.conn = conn;
2852
2853   /* Parse arguments. */
2854   SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)(?r)?B",
2855                                   &path, &start_rev, &end_rev,
2856                                   &include_merged_revs_param));
2857   path = svn_relpath_canonicalize(path, pool);
2858   SVN_ERR(trivial_auth_request(conn, pool, b));
2859   full_path = svn_fspath__join(b->repository->fs_path->data, path, pool);
2860
2861   if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2862     include_merged_revisions = FALSE;
2863   else
2864     include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2865
2866   SVN_ERR(log_command(b, conn, pool, "%s",
2867                       svn_log__get_file_revs(full_path, start_rev, end_rev,
2868                                              include_merged_revisions,
2869                                              pool)));
2870
2871   frb.conn = conn;
2872   frb.pool = NULL;
2873
2874   err = svn_repos_get_file_revs2(b->repository->repos, full_path, start_rev,
2875                                  end_rev, include_merged_revisions,
2876                                  authz_check_access_cb_func(b), &ab,
2877                                  file_rev_handler, &frb, pool);
2878   write_err = svn_ra_svn__write_word(conn, pool, "done");
2879   if (write_err)
2880     {
2881       svn_error_clear(err);
2882       return write_err;
2883     }
2884   SVN_CMD_ERR(err);
2885   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2886
2887   return SVN_NO_ERROR;
2888 }
2889
2890 static svn_error_t *
2891 lock(svn_ra_svn_conn_t *conn,
2892      apr_pool_t *pool,
2893      svn_ra_svn__list_t *params,
2894      void *baton)
2895 {
2896   server_baton_t *b = baton;
2897   const char *path;
2898   const char *comment;
2899   const char *full_path;
2900   svn_boolean_t steal_lock;
2901   svn_revnum_t current_rev;
2902   svn_lock_t *l;
2903
2904   SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?c)b(?r)", &path, &comment,
2905                                   &steal_lock, &current_rev));
2906   full_path = svn_fspath__join(b->repository->fs_path->data,
2907                                svn_relpath_canonicalize(path, pool), pool);
2908
2909   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2910                            full_path, TRUE));
2911   SVN_ERR(log_command(b, conn, pool, "%s",
2912                       svn_log__lock_one_path(full_path, steal_lock, pool)));
2913
2914   SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repository->repos, full_path, NULL,
2915                                 comment, 0, 0, /* No expiration time. */
2916                                 current_rev, steal_lock, pool));
2917
2918   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success"));
2919   SVN_ERR(write_lock(conn, pool, l));
2920   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)"));
2921
2922   return SVN_NO_ERROR;
2923 }
2924
2925 struct lock_result_t {
2926   const svn_lock_t *lock;
2927   svn_error_t *err;
2928 };
2929
2930 struct lock_many_baton_t {
2931   apr_hash_t *results;
2932   apr_pool_t *pool;
2933 };
2934
2935 /* Implements svn_fs_lock_callback_t. */
2936 static svn_error_t *
2937 lock_many_cb(void *baton,
2938              const char *path,
2939              const svn_lock_t *fs_lock,
2940              svn_error_t *fs_err,
2941              apr_pool_t *pool)
2942 {
2943   struct lock_many_baton_t *b = baton;
2944   struct lock_result_t *result = apr_palloc(b->pool,
2945                                             sizeof(struct lock_result_t));
2946
2947   result->lock = fs_lock;
2948   result->err = svn_error_dup(fs_err);
2949   svn_hash_sets(b->results, apr_pstrdup(b->pool, path), result);
2950
2951   return SVN_NO_ERROR;
2952 }
2953
2954 static void
2955 clear_lock_result_hash(apr_hash_t *results,
2956                        apr_pool_t *scratch_pool)
2957 {
2958   apr_hash_index_t *hi;
2959
2960   for (hi = apr_hash_first(scratch_pool, results); hi; hi = apr_hash_next(hi))
2961     {
2962       struct lock_result_t *result = apr_hash_this_val(hi);
2963       svn_error_clear(result->err);
2964     }
2965 }
2966
2967 static svn_error_t *
2968 lock_many(svn_ra_svn_conn_t *conn,
2969           apr_pool_t *pool,
2970           svn_ra_svn__list_t *params,
2971           void *baton)
2972 {
2973   server_baton_t *b = baton;
2974   svn_ra_svn__list_t *path_revs;
2975   const char *comment;
2976   svn_boolean_t steal_lock;
2977   int i;
2978   apr_pool_t *subpool;
2979   svn_error_t *err, *write_err = SVN_NO_ERROR;
2980   apr_hash_t *targets = apr_hash_make(pool);
2981   apr_hash_t *authz_results = apr_hash_make(pool);
2982   apr_hash_index_t *hi;
2983   struct lock_many_baton_t lmb;
2984
2985   SVN_ERR(svn_ra_svn__parse_tuple(params, "(?c)bl", &comment, &steal_lock,
2986                                   &path_revs));
2987
2988   subpool = svn_pool_create(pool);
2989
2990   /* Because we can only send a single auth reply per request, we send
2991      a reply before parsing the lock commands.  This means an authz
2992      access denial will abort the processing of the locks and return
2993      an error. */
2994   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
2995
2996   /* Parse the lock requests from PATH_REVS into TARGETS. */
2997   for (i = 0; i < path_revs->nelts; ++i)
2998     {
2999       const char *path, *full_path;
3000       svn_revnum_t current_rev;
3001       svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(path_revs, i);
3002       svn_fs_lock_target_t *target;
3003
3004       svn_pool_clear(subpool);
3005
3006       if (item->kind != SVN_RA_SVN_LIST)
3007         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
3008                                 "Lock requests should be list of lists");
3009
3010       SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "c(?r)", &path,
3011                                       &current_rev));
3012
3013       full_path = svn_fspath__join(b->repository->fs_path->data,
3014                                    svn_relpath_canonicalize(path, subpool),
3015                                    pool);
3016       target = svn_fs_lock_target_create(NULL, current_rev, pool);
3017
3018       /* Any duplicate paths, once canonicalized, get collapsed into a
3019          single path that is processed once.  The result is then
3020          returned multiple times. */
3021       svn_hash_sets(targets, full_path, target);
3022     }
3023
3024   SVN_ERR(log_command(b, conn, subpool, "%s",
3025                       svn_log__lock(targets, steal_lock, subpool)));
3026
3027   /* Check authz.
3028
3029      Note: From here on we need to make sure any errors in authz_results, or
3030      results, are cleared before returning from this function. */
3031   for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
3032     {
3033       const char *full_path = apr_hash_this_key(hi);
3034
3035       svn_pool_clear(subpool);
3036
3037       if (! lookup_access(subpool, b, svn_authz_write, full_path, TRUE))
3038         {
3039           struct lock_result_t *result
3040             = apr_palloc(pool, sizeof(struct lock_result_t));
3041
3042           result->lock = NULL;
3043           result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
3044                                              NULL, NULL, b);
3045           svn_hash_sets(authz_results, full_path, result);
3046           svn_hash_sets(targets, full_path, NULL);
3047         }
3048     }
3049
3050   lmb.results = apr_hash_make(pool);
3051   lmb.pool = pool;
3052
3053   err = svn_repos_fs_lock_many(b->repository->repos, targets,
3054                                comment, FALSE,
3055                                0, /* No expiration time. */
3056                                steal_lock, lock_many_cb, &lmb,
3057                                pool, subpool);
3058
3059   /* Return results in the same order as the paths were supplied. */
3060   for (i = 0; i < path_revs->nelts; ++i)
3061     {
3062       const char *path, *full_path;
3063       svn_revnum_t current_rev;
3064       svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(path_revs, i);
3065       struct lock_result_t *result;
3066
3067       svn_pool_clear(subpool);
3068
3069       write_err = svn_ra_svn__parse_tuple(&item->u.list, "c(?r)",
3070                                           &path, &current_rev);
3071       if (write_err)
3072         break;
3073
3074       full_path = svn_fspath__join(b->repository->fs_path->data,
3075                                    svn_relpath_canonicalize(path, subpool),
3076                                    subpool);
3077
3078       result = svn_hash_gets(lmb.results, full_path);
3079       if (!result)
3080         result = svn_hash_gets(authz_results, full_path);
3081       if (!result)
3082         {
3083           /* No result?  Something really odd happened, create a
3084              placeholder error so that any other results can be
3085              reported in the correct order. */
3086           result = apr_palloc(pool, sizeof(struct lock_result_t));
3087           result->err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 0,
3088                                           _("No result for '%s'."), path);
3089           svn_hash_sets(lmb.results, full_path, result);
3090         }
3091
3092       if (result->err)
3093         write_err = svn_ra_svn__write_cmd_failure(conn, subpool,
3094                                                   result->err);
3095       else
3096         {
3097           write_err = svn_ra_svn__write_tuple(conn, subpool,
3098                                               "w!", "success");
3099           if (!write_err)
3100             write_err = write_lock(conn, subpool, result->lock);
3101           if (!write_err)
3102             write_err = svn_ra_svn__write_tuple(conn, subpool, "!");
3103         }
3104       if (write_err)
3105         break;
3106     }
3107
3108   clear_lock_result_hash(authz_results, subpool);
3109   clear_lock_result_hash(lmb.results, subpool);
3110
3111   svn_pool_destroy(subpool);
3112
3113   if (!write_err)
3114     write_err = svn_ra_svn__write_word(conn, pool, "done");
3115   if (!write_err)
3116     SVN_CMD_ERR(err);
3117   svn_error_clear(err);
3118   SVN_ERR(write_err);
3119   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3120
3121   return SVN_NO_ERROR;
3122 }
3123
3124 static svn_error_t *
3125 unlock(svn_ra_svn_conn_t *conn,
3126        apr_pool_t *pool,
3127        svn_ra_svn__list_t *params,
3128        void *baton)
3129 {
3130   server_baton_t *b = baton;
3131   const char *path, *token, *full_path;
3132   svn_boolean_t break_lock;
3133
3134   SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?c)b", &path, &token,
3135                                  &break_lock));
3136
3137   full_path = svn_fspath__join(b->repository->fs_path->data,
3138                                svn_relpath_canonicalize(path, pool), pool);
3139
3140   /* Username required unless break_lock was specified. */
3141   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
3142                            full_path, ! break_lock));
3143   SVN_ERR(log_command(b, conn, pool, "%s",
3144                       svn_log__unlock_one_path(full_path, break_lock, pool)));
3145
3146   SVN_CMD_ERR(svn_repos_fs_unlock(b->repository->repos, full_path, token,
3147                                   break_lock, pool));
3148
3149   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3150
3151   return SVN_NO_ERROR;
3152 }
3153
3154 static svn_error_t *
3155 unlock_many(svn_ra_svn_conn_t *conn,
3156             apr_pool_t *pool,
3157             svn_ra_svn__list_t *params,
3158             void *baton)
3159 {
3160   server_baton_t *b = baton;
3161   svn_boolean_t break_lock;
3162   svn_ra_svn__list_t *unlock_tokens;
3163   int i;
3164   apr_pool_t *subpool;
3165   svn_error_t *err = SVN_NO_ERROR, *write_err = SVN_NO_ERROR;
3166   apr_hash_t *targets = apr_hash_make(pool);
3167   apr_hash_t *authz_results = apr_hash_make(pool);
3168   apr_hash_index_t *hi;
3169   struct lock_many_baton_t lmb;
3170
3171   SVN_ERR(svn_ra_svn__parse_tuple(params, "bl", &break_lock, &unlock_tokens));
3172
3173   /* Username required unless break_lock was specified. */
3174   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
3175
3176   subpool = svn_pool_create(pool);
3177
3178   /* Parse the unlock requests from PATH_REVS into TARGETS. */
3179   for (i = 0; i < unlock_tokens->nelts; i++)
3180     {
3181       svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(unlock_tokens, i);
3182       const char *path, *full_path, *token;
3183
3184       svn_pool_clear(subpool);
3185
3186       if (item->kind != SVN_RA_SVN_LIST)
3187         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
3188                                 "Unlock request should be a list of lists");
3189
3190       SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "c(?c)", &path,
3191                                       &token));
3192       if (!token)
3193         token = "";
3194
3195       full_path = svn_fspath__join(b->repository->fs_path->data,
3196                                    svn_relpath_canonicalize(path, subpool),
3197                                    pool);
3198
3199       /* Any duplicate paths, once canonicalized, get collapsed into a
3200          single path that is processed once.  The result is then
3201          returned multiple times. */
3202       svn_hash_sets(targets, full_path, token);
3203     }
3204
3205   SVN_ERR(log_command(b, conn, subpool, "%s",
3206                       svn_log__unlock(targets, break_lock, subpool)));
3207
3208   /* Check authz.
3209
3210      Note: From here on we need to make sure any errors in authz_results, or
3211      results, are cleared before returning from this function. */
3212   for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
3213     {
3214       const char *full_path = apr_hash_this_key(hi);
3215
3216       svn_pool_clear(subpool);
3217
3218       if (! lookup_access(subpool, b, svn_authz_write, full_path,
3219                           ! break_lock))
3220         {
3221           struct lock_result_t *result
3222             = apr_palloc(pool, sizeof(struct lock_result_t));
3223
3224           result->lock = NULL;
3225           result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
3226                                              NULL, NULL, b);
3227           svn_hash_sets(authz_results, full_path, result);
3228           svn_hash_sets(targets, full_path, NULL);
3229         }
3230     }
3231
3232   lmb.results = apr_hash_make(pool);
3233   lmb.pool = pool;
3234
3235   err = svn_repos_fs_unlock_many(b->repository->repos, targets,
3236                                  break_lock, lock_many_cb, &lmb,
3237                                  pool, subpool);
3238
3239   /* Return results in the same order as the paths were supplied. */
3240   for (i = 0; i < unlock_tokens->nelts; ++i)
3241     {
3242       const char *path, *token, *full_path;
3243       svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(unlock_tokens, i);
3244       struct lock_result_t *result;
3245
3246       svn_pool_clear(subpool);
3247
3248       write_err = svn_ra_svn__parse_tuple(&item->u.list, "c(?c)",
3249                                           &path, &token);
3250       if (write_err)
3251         break;
3252
3253       full_path = svn_fspath__join(b->repository->fs_path->data,
3254                                    svn_relpath_canonicalize(path, subpool),
3255                                    pool);
3256
3257       result = svn_hash_gets(lmb.results, full_path);
3258       if (!result)
3259         result = svn_hash_gets(authz_results, full_path);
3260       if (!result)
3261         {
3262           /* No result?  Something really odd happened, create a
3263              placeholder error so that any other results can be
3264              reported in the correct order. */
3265           result = apr_palloc(pool, sizeof(struct lock_result_t));
3266           result->err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 0,
3267                                           _("No result for '%s'."), path);
3268           svn_hash_sets(lmb.results, full_path, result);
3269         }
3270
3271       if (result->err)
3272         write_err = svn_ra_svn__write_cmd_failure(conn, pool, result->err);
3273       else
3274         write_err = svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
3275                                             path);
3276       if (write_err)
3277         break;
3278     }
3279
3280   clear_lock_result_hash(authz_results, subpool);
3281   clear_lock_result_hash(lmb.results, subpool);
3282
3283   svn_pool_destroy(subpool);
3284
3285   if (!write_err)
3286     write_err = svn_ra_svn__write_word(conn, pool, "done");
3287   if (! write_err)
3288     SVN_CMD_ERR(err);
3289   svn_error_clear(err);
3290   SVN_ERR(write_err);
3291   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3292
3293   return SVN_NO_ERROR;
3294 }
3295
3296 static svn_error_t *
3297 get_lock(svn_ra_svn_conn_t *conn,
3298          apr_pool_t *pool,
3299          svn_ra_svn__list_t *params,
3300          void *baton)
3301 {
3302   server_baton_t *b = baton;
3303   const char *path;
3304   const char *full_path;
3305   svn_lock_t *l;
3306
3307   SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &path));
3308
3309   full_path = svn_fspath__join(b->repository->fs_path->data,
3310                                svn_relpath_canonicalize(path, pool), pool);
3311
3312   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
3313                            full_path, FALSE));
3314   SVN_ERR(log_command(b, conn, pool, "get-lock %s",
3315                       svn_path_uri_encode(full_path, pool)));
3316
3317   SVN_CMD_ERR(svn_fs_get_lock(&l, b->repository->fs, full_path, pool));
3318
3319   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
3320   if (l)
3321     SVN_ERR(write_lock(conn, pool, l));
3322   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3323
3324   return SVN_NO_ERROR;
3325 }
3326
3327 static svn_error_t *
3328 get_locks(svn_ra_svn_conn_t *conn,
3329           apr_pool_t *pool,
3330           svn_ra_svn__list_t *params,
3331           void *baton)
3332 {
3333   server_baton_t *b = baton;
3334   const char *path;
3335   const char *full_path;
3336   const char *depth_word;
3337   svn_depth_t depth;
3338   apr_hash_t *locks;
3339   apr_hash_index_t *hi;
3340   svn_error_t *err;
3341   authz_baton_t ab;
3342
3343   ab.server = b;
3344   ab.conn = conn;
3345
3346   SVN_ERR(svn_ra_svn__parse_tuple(params, "c?(?w)", &path, &depth_word));
3347
3348   depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity;
3349   if ((depth != svn_depth_empty) &&
3350       (depth != svn_depth_files) &&
3351       (depth != svn_depth_immediates) &&
3352       (depth != svn_depth_infinity))
3353     {
3354       err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3355                              "Invalid 'depth' specified in get-locks request");
3356       return log_fail_and_flush(err, b, conn, pool);
3357     }
3358
3359   full_path = svn_fspath__join(b->repository->fs_path->data,
3360                                svn_relpath_canonicalize(path, pool), pool);
3361
3362   SVN_ERR(trivial_auth_request(conn, pool, b));
3363
3364   SVN_ERR(log_command(b, conn, pool, "get-locks %s",
3365                       svn_path_uri_encode(full_path, pool)));
3366   SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repository->repos,
3367                                       full_path, depth,
3368                                       authz_check_access_cb_func(b), &ab,
3369                                       pool));
3370
3371   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
3372   for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
3373     {
3374       svn_lock_t *l = apr_hash_this_val(hi);
3375
3376       SVN_ERR(write_lock(conn, pool, l));
3377     }
3378   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3379
3380   return SVN_NO_ERROR;
3381 }
3382
3383 static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
3384                                         server_baton_t *b,
3385                                         svn_revnum_t rev,
3386                                         svn_revnum_t low_water_mark,
3387                                         svn_boolean_t send_deltas,
3388                                         apr_pool_t *pool)
3389 {
3390   const svn_delta_editor_t *editor;
3391   void *edit_baton;
3392   svn_fs_root_t *root;
3393   svn_error_t *err;
3394   authz_baton_t ab;
3395
3396   ab.server = b;
3397   ab.conn = conn;
3398
3399   SVN_ERR(log_command(b, conn, pool,
3400                       svn_log__replay(b->repository->fs_path->data, rev,
3401                                       pool)));
3402
3403   svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
3404
3405   err = svn_fs_revision_root(&root, b->repository->fs, rev, pool);
3406
3407   if (! err)
3408     err = svn_repos_replay2(root, b->repository->fs_path->data,
3409                             low_water_mark, send_deltas, editor, edit_baton,
3410                             authz_check_access_cb_func(b), &ab, pool);
3411
3412   if (err)
3413     svn_error_clear(editor->abort_edit(edit_baton, pool));
3414   SVN_CMD_ERR(err);
3415
3416   return svn_ra_svn__write_cmd_finish_replay(conn, pool);
3417 }
3418
3419 static svn_error_t *
3420 replay(svn_ra_svn_conn_t *conn,
3421        apr_pool_t *pool,
3422        svn_ra_svn__list_t *params,
3423        void *baton)
3424 {
3425   svn_revnum_t rev, low_water_mark;
3426   svn_boolean_t send_deltas;
3427   server_baton_t *b = baton;
3428
3429   SVN_ERR(svn_ra_svn__parse_tuple(params, "rrb", &rev, &low_water_mark,
3430                                  &send_deltas));
3431
3432   SVN_ERR(trivial_auth_request(conn, pool, b));
3433
3434   SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3435                               send_deltas, pool));
3436
3437   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3438
3439   return SVN_NO_ERROR;
3440 }
3441
3442 static svn_error_t *
3443 replay_range(svn_ra_svn_conn_t *conn,
3444              apr_pool_t *pool,
3445              svn_ra_svn__list_t *params,
3446              void *baton)
3447 {
3448   svn_revnum_t start_rev, end_rev, rev, low_water_mark;
3449   svn_boolean_t send_deltas;
3450   server_baton_t *b = baton;
3451   apr_pool_t *iterpool;
3452   authz_baton_t ab;
3453
3454   ab.server = b;
3455   ab.conn = conn;
3456
3457   SVN_ERR(svn_ra_svn__parse_tuple(params, "rrrb", &start_rev,
3458                                  &end_rev, &low_water_mark,
3459                                  &send_deltas));
3460
3461   SVN_ERR(trivial_auth_request(conn, pool, b));
3462
3463   iterpool = svn_pool_create(pool);
3464   for (rev = start_rev; rev <= end_rev; rev++)
3465     {
3466       apr_hash_t *props;
3467
3468       svn_pool_clear(iterpool);
3469
3470       SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props,
3471                                                  b->repository->repos, rev,
3472                                                  authz_check_access_cb_func(b),
3473                                                  &ab,
3474                                                  iterpool));
3475       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops"));
3476       SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props));
3477       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)"));
3478
3479       SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3480                                   send_deltas, iterpool));
3481
3482     }
3483   svn_pool_destroy(iterpool);
3484
3485   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3486
3487   return SVN_NO_ERROR;
3488 }
3489
3490 static svn_error_t *
3491 get_deleted_rev(svn_ra_svn_conn_t *conn,
3492                 apr_pool_t *pool,
3493                 svn_ra_svn__list_t *params,
3494                 void *baton)
3495 {
3496   server_baton_t *b = baton;
3497   const char *path, *full_path;
3498   svn_revnum_t peg_revision;
3499   svn_revnum_t end_revision;
3500   svn_revnum_t revision_deleted;
3501
3502   SVN_ERR(svn_ra_svn__parse_tuple(params, "crr",
3503                                  &path, &peg_revision, &end_revision));
3504   full_path = svn_fspath__join(b->repository->fs_path->data,
3505                                svn_relpath_canonicalize(path, pool), pool);
3506   SVN_ERR(log_command(b, conn, pool, "get-deleted-rev"));
3507   SVN_ERR(trivial_auth_request(conn, pool, b));
3508   SVN_ERR(svn_repos_deleted_rev(b->repository->fs, full_path, peg_revision,
3509                                 end_revision, &revision_deleted, pool));
3510   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted));
3511   return SVN_NO_ERROR;
3512 }
3513
3514 static svn_error_t *
3515 get_inherited_props(svn_ra_svn_conn_t *conn,
3516                     apr_pool_t *pool,
3517                     svn_ra_svn__list_t *params,
3518                     void *baton)
3519 {
3520   server_baton_t *b = baton;
3521   const char *path, *full_path;
3522   svn_revnum_t rev;
3523   svn_fs_root_t *root;
3524   apr_array_header_t *inherited_props;
3525   int i;
3526   apr_pool_t *iterpool = svn_pool_create(pool);
3527   authz_baton_t ab;
3528   svn_node_kind_t node_kind;
3529
3530   ab.server = b;
3531   ab.conn = conn;
3532
3533   /* Parse arguments. */
3534   SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev));
3535
3536   full_path = svn_fspath__join(b->repository->fs_path->data,
3537                                svn_relpath_canonicalize(path, iterpool),
3538                                pool);
3539
3540   /* Check authorizations */
3541   SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read,
3542                            full_path, FALSE));
3543
3544   SVN_ERR(log_command(b, conn, pool, "%s",
3545                       svn_log__get_inherited_props(full_path, rev,
3546                                                    iterpool)));
3547
3548   /* Fetch the properties and a stream for the contents. */
3549   SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, iterpool));
3550   SVN_CMD_ERR(svn_fs_check_path(&node_kind, root, full_path, pool));
3551   if (node_kind == svn_node_none)
3552     {
3553       SVN_CMD_ERR(svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
3554                                     _("'%s' path not found"), full_path));
3555     }
3556   SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool));
3557
3558   /* Send successful command response with revision and props. */
3559   SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success"));
3560
3561   SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!"));
3562
3563   for (i = 0; i < inherited_props->nelts; i++)
3564     {
3565       svn_prop_inherited_item_t *iprop =
3566         APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
3567
3568       svn_pool_clear(iterpool);
3569       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
3570                                       iprop->path_or_url));
3571       SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
3572       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
3573                                       iprop->path_or_url));
3574     }
3575
3576   SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))"));
3577   svn_pool_destroy(iterpool);
3578   return SVN_NO_ERROR;
3579 }
3580
3581 /* Baton type to be used with list_receiver. */
3582 typedef struct list_receiver_baton_t
3583 {
3584   /* Send the data through this connection. */
3585   svn_ra_svn_conn_t *conn;
3586
3587   /* Send the field selected by these flags. */
3588   apr_uint32_t dirent_fields;
3589 } list_receiver_baton_t;
3590
3591 /* Implements svn_repos_dirent_receiver_t, sending DIRENT and PATH to the
3592  * client.  BATON must be a list_receiver_baton_t. */
3593 static svn_error_t *
3594 list_receiver(const char *path,
3595               svn_dirent_t *dirent,
3596               void *baton,
3597               apr_pool_t *pool)
3598 {
3599   list_receiver_baton_t *b = baton;
3600   return svn_error_trace(svn_ra_svn__write_dirent(b->conn, pool, path, dirent,
3601                                                   b->dirent_fields));
3602 }
3603
3604 static svn_error_t *
3605 list(svn_ra_svn_conn_t *conn,
3606      apr_pool_t *pool,
3607      svn_ra_svn__list_t *params,
3608      void *baton)
3609 {
3610   server_baton_t *b = baton;
3611   const char *path, *full_path;
3612   svn_revnum_t rev;
3613   svn_depth_t depth;
3614   apr_array_header_t *patterns = NULL;
3615   svn_fs_root_t *root;
3616   const char *depth_word;
3617   svn_boolean_t path_info_only;
3618   svn_ra_svn__list_t *dirent_fields_list = NULL;
3619   svn_ra_svn__list_t *patterns_list = NULL;
3620   int i;
3621   list_receiver_baton_t rb;
3622   svn_error_t *err, *write_err;
3623
3624   authz_baton_t ab;
3625   ab.server = b;
3626   ab.conn = conn;
3627
3628   /* Read the command parameters. */
3629   SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)w?l?l", &path, &rev,
3630                                   &depth_word, &dirent_fields_list,
3631                                   &patterns_list));
3632
3633   rb.conn = conn;
3634   SVN_ERR(parse_dirent_fields(&rb.dirent_fields, dirent_fields_list));
3635
3636   depth = svn_depth_from_word(depth_word);
3637   full_path = svn_fspath__join(b->repository->fs_path->data,
3638                                svn_relpath_canonicalize(path, pool), pool);
3639
3640   /* Read the patterns list.  */
3641   if (patterns_list)
3642     {
3643       patterns = apr_array_make(pool, 0, sizeof(const char *));
3644       for (i = 0; i < patterns_list->nelts; ++i)
3645         {
3646           svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(patterns_list, i);
3647
3648           if (elt->kind != SVN_RA_SVN_STRING)
3649             return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
3650                                     "Pattern field not a string");
3651
3652           APR_ARRAY_PUSH(patterns, const char *) = elt->u.string.data;
3653         }
3654     }
3655
3656   /* Check authorizations */
3657   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
3658                            full_path, FALSE));
3659
3660   if (!SVN_IS_VALID_REVNUM(rev))
3661     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
3662
3663   SVN_ERR(log_command(b, conn, pool, "%s",
3664                       svn_log__list(full_path, rev, patterns, depth,
3665                                     rb.dirent_fields, pool)));
3666
3667   /* Fetch the root of the appropriate revision. */
3668   SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
3669
3670   /* Fetch the directory entries if requested and send them immediately. */
3671   path_info_only = (rb.dirent_fields & ~SVN_DIRENT_KIND) == 0;
3672   err = svn_repos_list(root, full_path, patterns, depth, path_info_only,
3673                        authz_check_access_cb_func(b), &ab, list_receiver,
3674                        &rb, NULL, NULL, pool);
3675
3676
3677   /* Finish response. */
3678   write_err = svn_ra_svn__write_word(conn, pool, "done");
3679   if (write_err)
3680     {
3681       svn_error_clear(err);
3682       return write_err;
3683     }
3684   SVN_CMD_ERR(err);
3685
3686   return svn_error_trace(svn_ra_svn__write_cmd_response(conn, pool, ""));
3687 }
3688
3689 static const svn_ra_svn__cmd_entry_t main_commands[] = {
3690   { "reparent",        reparent },
3691   { "get-latest-rev",  get_latest_rev },
3692   { "get-dated-rev",   get_dated_rev },
3693   { "change-rev-prop", change_rev_prop },
3694   { "change-rev-prop2",change_rev_prop2 },
3695   { "rev-proplist",    rev_proplist },
3696   { "rev-prop",        rev_prop },
3697   { "commit",          commit },
3698   { "get-file",        get_file },
3699   { "get-dir",         get_dir },
3700   { "update",          update },
3701   { "switch",          switch_cmd },
3702   { "status",          status },
3703   { "diff",            diff },
3704   { "get-mergeinfo",   get_mergeinfo },
3705   { "log",             log_cmd },
3706   { "check-path",      check_path },
3707   { "stat",            stat_cmd },
3708   { "get-locations",   get_locations },
3709   { "get-location-segments",   get_location_segments },
3710   { "get-file-revs",   get_file_revs },
3711   { "lock",            lock },
3712   { "lock-many",       lock_many },
3713   { "unlock",          unlock },
3714   { "unlock-many",     unlock_many },
3715   { "get-lock",        get_lock },
3716   { "get-locks",       get_locks },
3717   { "replay",          replay },
3718   { "replay-range",    replay_range },
3719   { "get-deleted-rev", get_deleted_rev },
3720   { "get-iprops",      get_inherited_props },
3721   { "list",            list },
3722   { NULL }
3723 };
3724
3725 /* Skip past the scheme part of a URL, including the tunnel specification
3726  * if present.  Return NULL if the scheme part is invalid for ra_svn. */
3727 static const char *skip_scheme_part(const char *url)
3728 {
3729   if (strncmp(url, "svn", 3) != 0)
3730     return NULL;
3731   url += 3;
3732   if (*url == '+')
3733     url += strcspn(url, ":");
3734   if (strncmp(url, "://", 3) != 0)
3735     return NULL;
3736   return url + 3;
3737 }
3738
3739 /* Check that PATH is a valid repository path, meaning it doesn't contain any
3740    '..' path segments.
3741    NOTE: This is similar to svn_path_is_backpath_present, but that function
3742    assumes the path separator is '/'.  This function also checks for
3743    segments delimited by the local path separator. */
3744 static svn_boolean_t
3745 repos_path_valid(const char *path)
3746 {
3747   const char *s = path;
3748
3749   while (*s)
3750     {
3751       /* Scan for the end of the segment. */
3752       while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
3753         ++path;
3754
3755       /* Check for '..'. */
3756 #ifdef WIN32
3757       /* On Windows, don't allow sequences of more than one character
3758          consisting of just dots and spaces.  Win32 functions treat
3759          paths such as ".. " and "......." inconsistently.  Make sure
3760          no one can escape out of the root. */
3761       if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s))
3762         return FALSE;
3763 #else  /* ! WIN32 */
3764       if (path - s == 2 && s[0] == '.' && s[1] == '.')
3765         return FALSE;
3766 #endif
3767
3768       /* Skip all separators. */
3769       while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
3770         ++path;
3771       s = path;
3772     }
3773
3774   return TRUE;
3775 }
3776
3777 /* Look for the repository given by URL, using ROOT as the virtual
3778  * repository root.  If we find one, fill in the repos, fs, repos_url,
3779  * and fs_path fields of REPOSITORY.  VHOST and READ_ONLY flags are the
3780  * same as in the server baton.
3781  *
3782  * CONFIG_POOL shall be used to load config objects.
3783  *
3784  * Use SCRATCH_POOL for temporary allocations.
3785  *
3786  */
3787 static svn_error_t *
3788 find_repos(const char *url,
3789            const char *root,
3790            svn_boolean_t vhost,
3791            svn_boolean_t read_only,
3792            svn_config_t *cfg,
3793            repository_t *repository,
3794            svn_repos__config_pool_t *config_pool,
3795            apr_hash_t *fs_config,
3796            apr_pool_t *result_pool,
3797            apr_pool_t *scratch_pool)
3798 {
3799   const char *path, *full_path, *fs_path, *hooks_env;
3800   svn_stringbuf_t *url_buf;
3801   svn_boolean_t sasl_requested;
3802
3803   /* Skip past the scheme and authority part. */
3804   path = skip_scheme_part(url);
3805   if (path == NULL)
3806     return svn_error_createf(SVN_ERR_BAD_URL, NULL,
3807                              "Non-svn URL passed to svn server: '%s'", url);
3808
3809   if (! vhost)
3810     {
3811       path = strchr(path, '/');
3812       if (path == NULL)
3813         path = "";
3814     }
3815   path = svn_relpath_canonicalize(path, scratch_pool);
3816   path = svn_path_uri_decode(path, scratch_pool);
3817
3818   /* Ensure that it isn't possible to escape the root by disallowing
3819      '..' segments. */
3820   if (!repos_path_valid(path))
3821     return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
3822                             "Couldn't determine repository path");
3823
3824   /* Join the server-configured root with the client path. */
3825   full_path = svn_dirent_join(svn_dirent_canonicalize(root, scratch_pool),
3826                               path, scratch_pool);
3827
3828   /* Search for a repository in the full path. */
3829   repository->repos_root = svn_repos_find_root_path(full_path, result_pool);
3830   if (!repository->repos_root)
3831     return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
3832                              "No repository found in '%s'", url);
3833
3834   /* Open the repository and fill in b with the resulting information. */
3835   SVN_ERR(svn_repos_open3(&repository->repos, repository->repos_root,
3836                           fs_config, result_pool, scratch_pool));
3837   SVN_ERR(svn_repos_remember_client_capabilities(repository->repos,
3838                                                  repository->capabilities));
3839   repository->fs = svn_repos_fs(repository->repos);
3840   fs_path = full_path + strlen(repository->repos_root);
3841   repository->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/",
3842                                              result_pool);
3843   url_buf = svn_stringbuf_create(url, result_pool);
3844   svn_path_remove_components(url_buf,
3845                         svn_path_component_count(repository->fs_path->data));
3846   repository->repos_url = url_buf->data;
3847   repository->authz_repos_name = svn_dirent_is_child(root,
3848                                                      repository->repos_root,
3849                                                      result_pool);
3850   if (repository->authz_repos_name == NULL)
3851     repository->repos_name = svn_dirent_basename(repository->repos_root,
3852                                                  result_pool);
3853   else
3854     repository->repos_name = repository->authz_repos_name;
3855   repository->repos_name = svn_path_uri_encode(repository->repos_name,
3856                                                result_pool);
3857
3858   /* If the svnserve configuration has not been loaded then load it from the
3859    * repository. */
3860   if (NULL == cfg)
3861     {
3862       repository->base = svn_repos_conf_dir(repository->repos, result_pool);
3863
3864       SVN_ERR(svn_repos__config_pool_get(&cfg, config_pool,
3865                                          svn_repos_svnserve_conf
3866                                             (repository->repos, result_pool),
3867                                          FALSE, repository->repos,
3868                                          result_pool));
3869     }
3870
3871   SVN_ERR(load_pwdb_config(repository, cfg, config_pool, result_pool));
3872   SVN_ERR(load_authz_config(repository, repository->repos_root, cfg,
3873                             result_pool));
3874
3875   /* Should we use Cyrus SASL? */
3876   SVN_ERR(svn_config_get_bool(cfg, &sasl_requested,
3877                               SVN_CONFIG_SECTION_SASL,
3878                               SVN_CONFIG_OPTION_USE_SASL, FALSE));
3879   if (sasl_requested)
3880     {
3881 #ifdef SVN_HAVE_SASL
3882       const char *val;
3883
3884       repository->use_sasl = sasl_requested;
3885
3886       svn_config_get(cfg, &val, SVN_CONFIG_SECTION_SASL,
3887                     SVN_CONFIG_OPTION_MIN_SSF, "0");
3888       SVN_ERR(svn_cstring_atoui(&repository->min_ssf, val));
3889
3890       svn_config_get(cfg, &val, SVN_CONFIG_SECTION_SASL,
3891                     SVN_CONFIG_OPTION_MAX_SSF, "256");
3892       SVN_ERR(svn_cstring_atoui(&repository->max_ssf, val));
3893 #else /* !SVN_HAVE_SASL */
3894       return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
3895                                _("SASL requested but not compiled in; "
3896                                  "set '%s' to 'false' or recompile "
3897                                  "svnserve with SASL support"),
3898                                SVN_CONFIG_OPTION_USE_SASL);
3899 #endif /* SVN_HAVE_SASL */
3900     }
3901   else
3902     {
3903       repository->use_sasl = FALSE;
3904     }
3905
3906   /* Use the repository UUID as the default realm. */
3907   SVN_ERR(svn_fs_get_uuid(repository->fs, &repository->realm, scratch_pool));
3908   svn_config_get(cfg, &repository->realm, SVN_CONFIG_SECTION_GENERAL,
3909                  SVN_CONFIG_OPTION_REALM, repository->realm);
3910   repository->realm = apr_pstrdup(result_pool, repository->realm);
3911
3912   /* Make sure it's possible for the client to authenticate.  Note
3913      that this doesn't take into account any authz configuration read
3914      above, because we can't know about access it grants until paths
3915      are given by the client. */
3916   set_access(repository, cfg, read_only);
3917
3918   /* Configure hook script environment variables. */
3919   svn_config_get(cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL,
3920                  SVN_CONFIG_OPTION_HOOKS_ENV, NULL);
3921   if (hooks_env)
3922     hooks_env = svn_dirent_internal_style(hooks_env, scratch_pool);
3923
3924   SVN_ERR(svn_repos_hooks_setenv(repository->repos, hooks_env, scratch_pool));
3925   repository->hooks_env = apr_pstrdup(result_pool, hooks_env);
3926
3927   return SVN_NO_ERROR;
3928 }
3929
3930 /* Compute the authentication name EXTERNAL should be able to get, if any. */
3931 static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
3932 {
3933   /* Only offer EXTERNAL for connections tunneled over a login agent. */
3934   if (!params->tunnel)
3935     return NULL;
3936
3937   /* If a tunnel user was provided on the command line, use that. */
3938   if (params->tunnel_user)
3939     return params->tunnel_user;
3940
3941   return svn_user_get_name(pool);
3942 }
3943
3944 static void
3945 fs_warning_func(void *baton, svn_error_t *err)
3946 {
3947   fs_warning_baton_t *b = baton;
3948   log_error(err, b->server);
3949 }
3950
3951 /* Return the normalized repository-relative path for the given PATH
3952  * (may be a URL, full path or relative path) and fs contained in the
3953  * server baton BATON. Allocate the result in POOL.
3954  */
3955 static const char *
3956 get_normalized_repo_rel_path(void *baton,
3957                              const char *path,
3958                              apr_pool_t *pool)
3959 {
3960   server_baton_t *sb = baton;
3961
3962   if (svn_path_is_url(path))
3963     {
3964       /* This is a copyfrom URL. */
3965       path = svn_uri_skip_ancestor(sb->repository->repos_url, path, pool);
3966       path = svn_fspath__canonicalize(path, pool);
3967     }
3968   else
3969     {
3970       /* This is a base-relative path. */
3971       if ((path)[0] != '/')
3972         /* Get an absolute path for use in the FS. */
3973         path = svn_fspath__join(sb->repository->fs_path->data, path, pool);
3974     }
3975
3976   return path;
3977 }
3978
3979 /* Get the revision root for REVISION in fs given by server baton BATON
3980  * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM.
3981  * Use POOL for allocations.
3982  */
3983 static svn_error_t *
3984 get_revision_root(svn_fs_root_t **fs_root,
3985                   void *baton,
3986                   svn_revnum_t revision,
3987                   apr_pool_t *pool)
3988 {
3989   server_baton_t *sb = baton;
3990
3991   if (!SVN_IS_VALID_REVNUM(revision))
3992     SVN_ERR(svn_fs_youngest_rev(&revision, sb->repository->fs, pool));
3993
3994   SVN_ERR(svn_fs_revision_root(fs_root, sb->repository->fs, revision, pool));
3995
3996   return SVN_NO_ERROR;
3997 }
3998
3999 static svn_error_t *
4000 fetch_props_func(apr_hash_t **props,
4001                  void *baton,
4002                  const char *path,
4003                  svn_revnum_t base_revision,
4004                  apr_pool_t *result_pool,
4005                  apr_pool_t *scratch_pool)
4006 {
4007   svn_fs_root_t *fs_root;
4008   svn_error_t *err;
4009
4010   path = get_normalized_repo_rel_path(baton, path, scratch_pool);
4011   SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
4012
4013   err = svn_fs_node_proplist(props, fs_root, path, result_pool);
4014   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
4015     {
4016       svn_error_clear(err);
4017       *props = apr_hash_make(result_pool);
4018       return SVN_NO_ERROR;
4019     }
4020   else if (err)
4021     return svn_error_trace(err);
4022
4023   return SVN_NO_ERROR;
4024 }
4025
4026 static svn_error_t *
4027 fetch_kind_func(svn_node_kind_t *kind,
4028                 void *baton,
4029                 const char *path,
4030                 svn_revnum_t base_revision,
4031                 apr_pool_t *scratch_pool)
4032 {
4033   svn_fs_root_t *fs_root;
4034
4035   path = get_normalized_repo_rel_path(baton, path, scratch_pool);
4036   SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
4037
4038   SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
4039
4040   return SVN_NO_ERROR;
4041 }
4042
4043 static svn_error_t *
4044 fetch_base_func(const char **filename,
4045                 void *baton,
4046                 const char *path,
4047                 svn_revnum_t base_revision,
4048                 apr_pool_t *result_pool,
4049                 apr_pool_t *scratch_pool)
4050 {
4051   svn_stream_t *contents;
4052   svn_stream_t *file_stream;
4053   const char *tmp_filename;
4054   svn_fs_root_t *fs_root;
4055   svn_error_t *err;
4056
4057   path = get_normalized_repo_rel_path(baton, path, scratch_pool);
4058   SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
4059
4060   err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
4061   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
4062     {
4063       svn_error_clear(err);
4064       *filename = NULL;
4065       return SVN_NO_ERROR;
4066     }
4067   else if (err)
4068     return svn_error_trace(err);
4069   SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
4070                                  svn_io_file_del_on_pool_cleanup,
4071                                  scratch_pool, scratch_pool));
4072   SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
4073
4074   *filename = apr_pstrdup(result_pool, tmp_filename);
4075
4076   return SVN_NO_ERROR;
4077 }
4078
4079 client_info_t *
4080 get_client_info(svn_ra_svn_conn_t *conn,
4081                 serve_params_t *params,
4082                 apr_pool_t *pool)
4083 {
4084   client_info_t *client_info = apr_pcalloc(pool, sizeof(*client_info));
4085
4086   client_info->tunnel = params->tunnel;
4087   client_info->tunnel_user = get_tunnel_user(params, pool);
4088   client_info->user = NULL;
4089   client_info->authz_user = NULL;
4090   client_info->remote_host = svn_ra_svn_conn_remote_host(conn);
4091
4092   return client_info;
4093 }
4094
4095 /* Construct the server baton for CONN using PARAMS and return it in *BATON.
4096  * It's lifetime is the same as that of CONN.  SCRATCH_POOL
4097  */
4098 static svn_error_t *
4099 construct_server_baton(server_baton_t **baton,
4100                        svn_ra_svn_conn_t *conn,
4101                        serve_params_t *params,
4102                        apr_pool_t *scratch_pool)
4103 {
4104   svn_error_t *err, *io_err;
4105   apr_uint64_t ver;
4106   const char *client_url, *ra_client_string, *client_string;
4107   svn_ra_svn__list_t *caplist;
4108   apr_pool_t *conn_pool = svn_ra_svn__get_pool(conn);
4109   server_baton_t *b = apr_pcalloc(conn_pool, sizeof(*b));
4110   fs_warning_baton_t *warn_baton;
4111   svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(scratch_pool);
4112
4113   b->repository = apr_pcalloc(conn_pool, sizeof(*b->repository));
4114   b->repository->username_case = params->username_case;
4115   b->repository->base = params->base;
4116   b->repository->pwdb = NULL;
4117   b->repository->authzdb = NULL;
4118   b->repository->realm = NULL;
4119   b->repository->use_sasl = FALSE;
4120
4121   b->read_only = params->read_only;
4122   b->pool = conn_pool;
4123   b->vhost = params->vhost;
4124
4125   b->logger = params->logger;
4126   b->client_info = get_client_info(conn, params, conn_pool);
4127
4128   /* Send greeting.  We don't support version 1 any more, so we can
4129    * send an empty mechlist. */
4130   if (params->compression_level > 0)
4131     SVN_ERR(svn_ra_svn__write_cmd_response(conn, scratch_pool,
4132                                            "nn()(wwwwwwwwwwwww)",
4133                                            (apr_uint64_t) 2, (apr_uint64_t) 2,
4134                                            SVN_RA_SVN_CAP_EDIT_PIPELINE,
4135                                            SVN_RA_SVN_CAP_SVNDIFF1,
4136                                            SVN_RA_SVN_CAP_SVNDIFF2_ACCEPTED,
4137                                            SVN_RA_SVN_CAP_ABSENT_ENTRIES,
4138                                            SVN_RA_SVN_CAP_COMMIT_REVPROPS,
4139                                            SVN_RA_SVN_CAP_DEPTH,
4140                                            SVN_RA_SVN_CAP_LOG_REVPROPS,
4141                                            SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
4142                                            SVN_RA_SVN_CAP_PARTIAL_REPLAY,
4143                                            SVN_RA_SVN_CAP_INHERITED_PROPS,
4144                                            SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
4145                                            SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE,
4146                                            SVN_RA_SVN_CAP_LIST
4147                                            ));
4148   else
4149     SVN_ERR(svn_ra_svn__write_cmd_response(conn, scratch_pool,
4150                                            "nn()(wwwwwwwwwww)",
4151                                            (apr_uint64_t) 2, (apr_uint64_t) 2,
4152                                            SVN_RA_SVN_CAP_EDIT_PIPELINE,
4153                                            SVN_RA_SVN_CAP_ABSENT_ENTRIES,
4154                                            SVN_RA_SVN_CAP_COMMIT_REVPROPS,
4155                                            SVN_RA_SVN_CAP_DEPTH,
4156                                            SVN_RA_SVN_CAP_LOG_REVPROPS,
4157                                            SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
4158                                            SVN_RA_SVN_CAP_PARTIAL_REPLAY,
4159                                            SVN_RA_SVN_CAP_INHERITED_PROPS,
4160                                            SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
4161                                            SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE,
4162                                            SVN_RA_SVN_CAP_LIST
4163                                            ));
4164
4165   /* Read client response, which we assume to be in version 2 format:
4166    * version, capability list, and client URL; then we do an auth
4167    * request. */
4168   SVN_ERR(svn_ra_svn__read_tuple(conn, scratch_pool, "nlc?c(?c)",
4169                                  &ver, &caplist, &client_url,
4170                                  &ra_client_string,
4171                                  &client_string));
4172   if (ver != 2)
4173     return SVN_NO_ERROR;
4174
4175   client_url = svn_uri_canonicalize(client_url, conn_pool);
4176   SVN_ERR(svn_ra_svn__set_capabilities(conn, caplist));
4177
4178   /* All released versions of Subversion support edit-pipeline,
4179    * so we do not accept connections from clients that do not. */
4180   if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
4181     return SVN_NO_ERROR;
4182
4183   /* find_repos needs the capabilities as a list of words (eventually
4184      they get handed to the start-commit hook).  While we could add a
4185      new interface to re-retrieve them from conn and convert the
4186      result to a list, it's simpler to just convert caplist by hand
4187      here, since we already have it and turning 'svn_ra_svn__item_t's
4188      into 'const char *'s is pretty easy.
4189
4190      We only record capabilities we care about.  The client may report
4191      more (because it doesn't know what the server cares about). */
4192   {
4193     int i;
4194     svn_ra_svn__item_t *item;
4195
4196     b->repository->capabilities = apr_array_make(conn_pool, 1,
4197                                                  sizeof(const char *));
4198     for (i = 0; i < caplist->nelts; i++)
4199       {
4200         static const svn_string_t str_cap_mergeinfo
4201           = SVN__STATIC_STRING(SVN_RA_SVN_CAP_MERGEINFO);
4202
4203         item = &SVN_RA_SVN__LIST_ITEM(caplist, i);
4204         /* ra_svn_set_capabilities() already type-checked for us */
4205         if (svn_string_compare(&item->u.word, &str_cap_mergeinfo))
4206           {
4207             APR_ARRAY_PUSH(b->repository->capabilities, const char *)
4208               = SVN_RA_CAPABILITY_MERGEINFO;
4209           }
4210         /* Save for operational log. */
4211         if (cap_log->len > 0)
4212           svn_stringbuf_appendcstr(cap_log, " ");
4213         svn_stringbuf_appendcstr(cap_log, item->u.word.data);
4214       }
4215   }
4216
4217   err = handle_config_error(find_repos(client_url, params->root, b->vhost,
4218                                        b->read_only, params->cfg,
4219                                        b->repository, params->config_pool,
4220                                        params->fs_config,
4221                                        conn_pool, scratch_pool),
4222                             b);
4223   if (!err)
4224     {
4225       if (b->repository->anon_access == NO_ACCESS
4226           && (b->repository->auth_access == NO_ACCESS
4227               || (!b->client_info->tunnel_user && !b->repository->pwdb
4228                   && !b->repository->use_sasl)))
4229         err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
4230                                    "No access allowed to this repository",
4231                                    b);
4232     }
4233   if (!err)
4234     {
4235       SVN_ERR(auth_request(conn, scratch_pool, b, READ_ACCESS, FALSE));
4236       if (current_access(b) == NO_ACCESS)
4237         err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
4238                                    "Not authorized for access", b);
4239     }
4240   if (err)
4241     {
4242       log_error(err, b);
4243       io_err = svn_ra_svn__write_cmd_failure(conn, scratch_pool, err);
4244       svn_error_clear(err);
4245       SVN_ERR(io_err);
4246       return svn_ra_svn__flush(conn, scratch_pool);
4247     }
4248
4249   SVN_ERR(svn_fs_get_uuid(b->repository->fs, &b->repository->uuid,
4250                           conn_pool));
4251
4252   /* We can't claim mergeinfo capability until we know whether the
4253      repository supports mergeinfo (i.e., is not a 1.4 repository),
4254      but we don't get the repository url from the client until after
4255      we've already sent the initial list of server capabilities.  So
4256      we list repository capabilities here, in our first response after
4257      the client has sent the url. */
4258   {
4259     svn_boolean_t supports_mergeinfo;
4260     SVN_ERR(svn_repos_has_capability(b->repository->repos,
4261                                      &supports_mergeinfo,
4262                                      SVN_REPOS_CAPABILITY_MERGEINFO,
4263                                      scratch_pool));
4264
4265     SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w(cc(!",
4266                                     "success", b->repository->uuid,
4267                                     b->repository->repos_url));
4268     if (supports_mergeinfo)
4269       SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool,
4270                                      SVN_RA_SVN_CAP_MERGEINFO));
4271     SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!))"));
4272     SVN_ERR(svn_ra_svn__flush(conn, scratch_pool));
4273   }
4274
4275   /* Log the open. */
4276   if (ra_client_string == NULL || ra_client_string[0] == '\0')
4277     ra_client_string = "-";
4278   else
4279     ra_client_string = svn_path_uri_encode(ra_client_string, scratch_pool);
4280   if (client_string == NULL || client_string[0] == '\0')
4281     client_string = "-";
4282   else
4283     client_string = svn_path_uri_encode(client_string, scratch_pool);
4284   SVN_ERR(log_command(b, conn, scratch_pool,
4285                       "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s",
4286                       ver, cap_log->data,
4287                       svn_path_uri_encode(b->repository->fs_path->data,
4288                                           scratch_pool),
4289                       ra_client_string, client_string));
4290
4291   warn_baton = apr_pcalloc(conn_pool, sizeof(*warn_baton));
4292   warn_baton->server = b;
4293   warn_baton->conn = conn;
4294   svn_fs_set_warning_func(b->repository->fs, fs_warning_func, warn_baton);
4295
4296   /* Set up editor shims. */
4297   {
4298     svn_delta_shim_callbacks_t *callbacks =
4299                                 svn_delta_shim_callbacks_default(conn_pool);
4300
4301     callbacks->fetch_base_func = fetch_base_func;
4302     callbacks->fetch_props_func = fetch_props_func;
4303     callbacks->fetch_kind_func = fetch_kind_func;
4304     callbacks->fetch_baton = b;
4305
4306     SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks));
4307   }
4308
4309   *baton = b;
4310
4311   return SVN_NO_ERROR;
4312 }
4313
4314 svn_error_t *
4315 serve_interruptable(svn_boolean_t *terminate_p,
4316                     connection_t *connection,
4317                     svn_boolean_t (* is_busy)(connection_t *),
4318                     apr_pool_t *pool)
4319 {
4320   svn_boolean_t terminate = FALSE;
4321   svn_error_t *err = NULL;
4322   const svn_ra_svn__cmd_entry_t *command;
4323   apr_pool_t *iterpool = svn_pool_create(pool);
4324
4325   /* Prepare command parser. */
4326   apr_hash_t *cmd_hash = apr_hash_make(pool);
4327   for (command = main_commands; command->cmdname; command++)
4328     svn_hash_sets(cmd_hash, command->cmdname, command);
4329
4330   /* Auto-initialize connection */
4331   if (! connection->conn)
4332     {
4333       apr_status_t ar;
4334
4335       /* Enable TCP keep-alives on the socket so we time out when
4336        * the connection breaks due to network-layer problems.
4337        * If the peer has dropped the connection due to a network partition
4338        * or a crash, or if the peer no longer considers the connection
4339        * valid because we are behind a NAT and our public IP has changed,
4340        * it will respond to the keep-alive probe with a RST instead of an
4341        * acknowledgment segment, which will cause svn to abort the session
4342        * even while it is currently blocked waiting for data from the peer. */
4343       ar = apr_socket_opt_set(connection->usock, APR_SO_KEEPALIVE, 1);
4344       if (ar)
4345         {
4346           /* It's not a fatal error if we cannot enable keep-alives. */
4347         }
4348
4349       /* create the connection, configure ports etc. */
4350       connection->conn
4351         = svn_ra_svn_create_conn5(connection->usock, NULL, NULL,
4352                                   connection->params->compression_level,
4353                                   connection->params->zero_copy_limit,
4354                                   connection->params->error_check_interval,
4355                                   connection->params->max_request_size,
4356                                   connection->params->max_response_size,
4357                                   connection->pool);
4358
4359       /* Construct server baton and open the repository for the first time. */
4360       err = construct_server_baton(&connection->baton, connection->conn,
4361                                    connection->params, pool);
4362     }
4363
4364   /* If we can't access the repo for some reason, end this connection. */
4365   if (err)
4366     terminate = TRUE;
4367
4368   /* Process incoming commands. */
4369   while (!terminate && !err)
4370     {
4371       svn_pool_clear(iterpool);
4372       if (is_busy && is_busy(connection))
4373         {
4374           svn_boolean_t has_command;
4375
4376           /* If the server is busy, execute just one command and only if
4377            * there is one currently waiting in our receive buffers.
4378            */
4379           err = svn_ra_svn__has_command(&has_command, &terminate,
4380                                         connection->conn, iterpool);
4381           if (!err && has_command)
4382             err = svn_ra_svn__handle_command(&terminate, cmd_hash,
4383                                              connection->baton,
4384                                              connection->conn,
4385                                              FALSE, iterpool);
4386
4387           break;
4388         }
4389       else
4390         {
4391           /* The server is not busy, thus let's serve whichever command
4392            * comes in next and whenever it comes in.  This requires the
4393            * busy() callback test to return TRUE while there are still some
4394            * resources left.
4395            */
4396           err = svn_ra_svn__handle_command(&terminate, cmd_hash,
4397                                            connection->baton,
4398                                            connection->conn,
4399                                            FALSE, iterpool);
4400         }
4401     }
4402
4403   /* error or normal end of session. Close the connection */
4404   svn_pool_destroy(iterpool);
4405   if (terminate_p)
4406     *terminate_p = terminate;
4407
4408   return svn_error_trace(err);
4409 }
4410
4411 svn_error_t *serve(svn_ra_svn_conn_t *conn,
4412                    serve_params_t *params,
4413                    apr_pool_t *pool)
4414 {
4415   server_baton_t *baton = NULL;
4416
4417   SVN_ERR(construct_server_baton(&baton, conn, params, pool));
4418   return svn_ra_svn__handle_commands2(conn, pool, main_commands, baton, FALSE);
4419 }