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