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  /* Ignore LOG_MSG, per the protocol.  See ra_svn_commit(). */
1445  if (revprop_list)
1446    SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table));
1447  else
1448    {
1449      revprop_table = apr_hash_make(pool);
1450      svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
1451                    svn_string_create(log_msg, pool));
1452    }
1453
1454  /* Get author from the baton, making sure clients can't circumvent
1455     the authentication via the revision props. */
1456  svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
1457                b->user ? svn_string_create(b->user, pool) : NULL);
1458
1459  ccb.pool = pool;
1460  ccb.new_rev = &new_rev;
1461  ccb.date = &date;
1462  ccb.author = &author;
1463  ccb.post_commit_err = &post_commit_err;
1464  /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
1465  SVN_CMD_ERR(svn_repos_get_commit_editor5
1466              (&editor, &edit_baton, b->repos, NULL,
1467               svn_path_uri_decode(b->repos_url, pool),
1468               b->fs_path->data, revprop_table,
1469               commit_done, &ccb,
1470               authz_commit_cb, &ab, pool));
1471  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1472  SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton,
1473                                   &aborted, FALSE));
1474  if (!aborted)
1475    {
1476      SVN_ERR(log_command(b, conn, pool, "%s",
1477                          svn_log__commit(new_rev, pool)));
1478      SVN_ERR(trivial_auth_request(conn, pool, b));
1479
1480      /* In tunnel mode, deltify before answering the client, because
1481         answering may cause the client to terminate the connection
1482         and thus kill the server.  But otherwise, deltify after
1483         answering the client, to avoid user-visible delay. */
1484
1485      if (b->tunnel)
1486        SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1487
1488      /* Unlock the paths. */
1489      if (! keep_locks && lock_tokens && lock_tokens->nelts)
1490        SVN_ERR(unlock_paths(lock_tokens, b, conn, pool));
1491
1492      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)",
1493                                      new_rev, date, author, post_commit_err));
1494
1495      if (! b->tunnel)
1496        SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1497    }
1498  return SVN_NO_ERROR;
1499}
1500
1501static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1502                             apr_array_header_t *params, void *baton)
1503{
1504  server_baton_t *b = baton;
1505  const char *path, *full_path, *hex_digest;
1506  svn_revnum_t rev;
1507  svn_fs_root_t *root;
1508  svn_stream_t *contents;
1509  apr_hash_t *props = NULL;
1510  apr_array_header_t *inherited_props;
1511  svn_string_t write_str;
1512  char buf[4096];
1513  apr_size_t len;
1514  svn_boolean_t want_props, want_contents;
1515  apr_uint64_t wants_inherited_props;
1516  svn_checksum_t *checksum;
1517  svn_error_t *err, *write_err;
1518  int i;
1519  authz_baton_t ab;
1520
1521  ab.server = b;
1522  ab.conn = conn;
1523
1524  /* Parse arguments. */
1525  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?B", &path, &rev,
1526                                  &want_props, &want_contents,
1527                                  &wants_inherited_props));
1528
1529  full_path = svn_fspath__join(b->fs_path->data,
1530                               svn_relpath_canonicalize(path, pool), pool);
1531
1532  /* Check authorizations */
1533  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1534                           full_path, FALSE));
1535
1536  if (!SVN_IS_VALID_REVNUM(rev))
1537    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1538
1539  SVN_ERR(log_command(b, conn, pool, "%s",
1540                      svn_log__get_file(full_path, rev,
1541                                        want_contents, want_props, pool)));
1542
1543  /* Fetch the properties and a stream for the contents. */
1544  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1545  SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
1546                                   full_path, TRUE, pool));
1547  hex_digest = svn_checksum_to_cstring_display(checksum, pool);
1548  if (want_props || wants_inherited_props)
1549    SVN_CMD_ERR(get_props(&props, &inherited_props, &ab, root, full_path,
1550                          pool));
1551  if (want_contents)
1552    SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
1553
1554  /* Send successful command response with revision and props. */
1555  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success",
1556                                  hex_digest, rev));
1557  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1558
1559  if (wants_inherited_props)
1560    {
1561      apr_pool_t *iterpool = svn_pool_create(pool);
1562
1563      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1564      for (i = 0; i < inherited_props->nelts; i++)
1565        {
1566          svn_prop_inherited_item_t *iprop =
1567            APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1568
1569          svn_pool_clear(iterpool);
1570          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1571                                          iprop->path_or_url));
1572          SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1573          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1574                                          iprop->path_or_url));
1575        }
1576      svn_pool_destroy(iterpool);
1577    }
1578
1579  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1580
1581  /* Now send the file's contents. */
1582  if (want_contents)
1583    {
1584      err = SVN_NO_ERROR;
1585      while (1)
1586        {
1587          len = sizeof(buf);
1588          err = svn_stream_read(contents, buf, &len);
1589          if (err)
1590            break;
1591          if (len > 0)
1592            {
1593              write_str.data = buf;
1594              write_str.len = len;
1595              SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str));
1596            }
1597          if (len < sizeof(buf))
1598            {
1599              err = svn_stream_close(contents);
1600              break;
1601            }
1602        }
1603      write_err = svn_ra_svn__write_cstring(conn, pool, "");
1604      if (write_err)
1605        {
1606          svn_error_clear(err);
1607          return write_err;
1608        }
1609      SVN_CMD_ERR(err);
1610      SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1611    }
1612
1613  return SVN_NO_ERROR;
1614}
1615
1616static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1617                            apr_array_header_t *params, void *baton)
1618{
1619  server_baton_t *b = baton;
1620  const char *path, *full_path;
1621  svn_revnum_t rev;
1622  apr_hash_t *entries, *props = NULL;
1623  apr_array_header_t *inherited_props;
1624  apr_hash_index_t *hi;
1625  svn_fs_root_t *root;
1626  apr_pool_t *subpool;
1627  svn_boolean_t want_props, want_contents;
1628  apr_uint64_t wants_inherited_props;
1629  apr_uint64_t dirent_fields;
1630  apr_array_header_t *dirent_fields_list = NULL;
1631  svn_ra_svn_item_t *elt;
1632  int i;
1633  authz_baton_t ab;
1634
1635  ab.server = b;
1636  ab.conn = conn;
1637
1638  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?l?B", &path, &rev,
1639                                  &want_props, &want_contents,
1640                                  &dirent_fields_list,
1641                                  &wants_inherited_props));
1642
1643  if (! dirent_fields_list)
1644    {
1645      dirent_fields = SVN_DIRENT_ALL;
1646    }
1647  else
1648    {
1649      dirent_fields = 0;
1650
1651      for (i = 0; i < dirent_fields_list->nelts; ++i)
1652        {
1653          elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t);
1654
1655          if (elt->kind != SVN_RA_SVN_WORD)
1656            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1657                                    "Dirent field not a string");
1658
1659          if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0)
1660            dirent_fields |= SVN_DIRENT_KIND;
1661          else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0)
1662            dirent_fields |= SVN_DIRENT_SIZE;
1663          else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0)
1664            dirent_fields |= SVN_DIRENT_HAS_PROPS;
1665          else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0)
1666            dirent_fields |= SVN_DIRENT_CREATED_REV;
1667          else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0)
1668            dirent_fields |= SVN_DIRENT_TIME;
1669          else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0)
1670            dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
1671        }
1672    }
1673
1674  full_path = svn_fspath__join(b->fs_path->data,
1675                               svn_relpath_canonicalize(path, pool), pool);
1676
1677  /* Check authorizations */
1678  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1679                           full_path, FALSE));
1680
1681  if (!SVN_IS_VALID_REVNUM(rev))
1682    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1683
1684  SVN_ERR(log_command(b, conn, pool, "%s",
1685                      svn_log__get_dir(full_path, rev,
1686                                       want_contents, want_props,
1687                                       dirent_fields, pool)));
1688
1689  /* Fetch the root of the appropriate revision. */
1690  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1691
1692  /* Fetch the directory's explicit and/or inherited properties
1693     if requested. */
1694  if (want_props || wants_inherited_props)
1695    SVN_CMD_ERR(get_props(&props, &inherited_props, &ab, root, full_path,
1696                          pool));
1697
1698  /* Begin response ... */
1699  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev));
1700  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1701  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!"));
1702
1703  /* Fetch the directory entries if requested and send them immediately. */
1704  if (want_contents)
1705    {
1706      /* Use epoch for a placeholder for a missing date.  */
1707      const char *missing_date = svn_time_to_cstring(0, pool);
1708
1709      SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
1710
1711      /* Transform the hash table's FS entries into dirents.  This probably
1712       * belongs in libsvn_repos. */
1713      subpool = svn_pool_create(pool);
1714      for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1715        {
1716          const char *name = svn__apr_hash_index_key(hi);
1717          svn_fs_dirent_t *fsent = svn__apr_hash_index_val(hi);
1718          const char *file_path;
1719
1720          /* The fields in the entry tuple.  */
1721          svn_node_kind_t entry_kind = svn_node_none;
1722          svn_filesize_t entry_size = 0;
1723          svn_boolean_t has_props = FALSE;
1724          /* If 'created rev' was not requested, send 0.  We can't use
1725           * SVN_INVALID_REVNUM as the tuple field is not optional.
1726           * See the email thread on dev@, 2012-03-28, subject
1727           * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra",
1728           * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */
1729          svn_revnum_t created_rev = 0;
1730          const char *cdate = NULL;
1731          const char *last_author = NULL;
1732
1733          svn_pool_clear(subpool);
1734
1735          file_path = svn_fspath__join(full_path, name, subpool);
1736          if (! lookup_access(subpool, b, conn, svn_authz_read,
1737                              file_path, FALSE))
1738            continue;
1739
1740          if (dirent_fields & SVN_DIRENT_KIND)
1741              entry_kind = fsent->kind;
1742
1743          if (dirent_fields & SVN_DIRENT_SIZE)
1744              if (entry_kind != svn_node_dir)
1745                SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path,
1746                                               subpool));
1747
1748          if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1749            {
1750              apr_hash_t *file_props;
1751
1752              /* has_props */
1753              SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
1754                                               subpool));
1755              has_props = (apr_hash_count(file_props) > 0);
1756            }
1757
1758          if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1759              || (dirent_fields & SVN_DIRENT_TIME)
1760              || (dirent_fields & SVN_DIRENT_CREATED_REV))
1761            {
1762              /* created_rev, last_author, time */
1763              SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev,
1764                                                       &cdate,
1765                                                       &last_author,
1766                                                       root,
1767                                                       file_path,
1768                                                       subpool));
1769            }
1770
1771          /* The client does not properly handle a missing CDATE. For
1772             interoperability purposes, we must fill in some junk.
1773
1774             See libsvn_ra_svn/client.c:ra_svn_get_dir()  */
1775          if (cdate == NULL)
1776            cdate = missing_date;
1777
1778          /* Send the entry. */
1779          SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
1780                                          svn_node_kind_to_word(entry_kind),
1781                                          (apr_uint64_t) entry_size,
1782                                          has_props, created_rev,
1783                                          cdate, last_author));
1784        }
1785      svn_pool_destroy(subpool);
1786    }
1787
1788  if (wants_inherited_props)
1789    {
1790      apr_pool_t *iterpool = svn_pool_create(pool);
1791
1792      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1793      for (i = 0; i < inherited_props->nelts; i++)
1794        {
1795          svn_prop_inherited_item_t *iprop =
1796            APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1797
1798          svn_pool_clear(iterpool);
1799          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1800                                          iprop->path_or_url));
1801          SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1802          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1803                                          iprop->path_or_url));
1804        }
1805      svn_pool_destroy(iterpool);
1806    }
1807
1808  /* Finish response. */
1809  return svn_ra_svn__write_tuple(conn, pool, "!))");
1810}
1811
1812static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1813                           apr_array_header_t *params, void *baton)
1814{
1815  server_baton_t *b = baton;
1816  svn_revnum_t rev;
1817  const char *target, *full_path, *depth_word;
1818  svn_boolean_t recurse;
1819  apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
1820  apr_uint64_t ignore_ancestry; /* Optional; default FALSE */
1821  /* Default to unknown.  Old clients won't send depth, but we'll
1822     handle that by converting recurse if necessary. */
1823  svn_depth_t depth = svn_depth_unknown;
1824  svn_boolean_t is_checkout;
1825
1826  /* Parse the arguments. */
1827  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cb?wB?B", &rev, &target,
1828                                  &recurse, &depth_word,
1829                                  &send_copyfrom_args, &ignore_ancestry));
1830  target = svn_relpath_canonicalize(target, pool);
1831
1832  if (depth_word)
1833    depth = svn_depth_from_word(depth_word);
1834  else
1835    depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1836
1837  full_path = svn_fspath__join(b->fs_path->data, target, pool);
1838  /* Check authorization and authenticate the user if necessary. */
1839  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
1840
1841  if (!SVN_IS_VALID_REVNUM(rev))
1842    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1843
1844  SVN_ERR(accept_report(&is_checkout, NULL,
1845                        conn, pool, b, rev, target, NULL, TRUE,
1846                        depth,
1847                        (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
1848                        (ignore_ancestry == TRUE) /* ignore_ancestry */));
1849  if (is_checkout)
1850    {
1851      SVN_ERR(log_command(b, conn, pool, "%s",
1852                          svn_log__checkout(full_path, rev,
1853                                            depth, pool)));
1854    }
1855  else
1856    {
1857      SVN_ERR(log_command(b, conn, pool, "%s",
1858                          svn_log__update(full_path, rev, depth,
1859                                          send_copyfrom_args, pool)));
1860    }
1861
1862  return SVN_NO_ERROR;
1863}
1864
1865static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1866                               apr_array_header_t *params, void *baton)
1867{
1868  server_baton_t *b = baton;
1869  svn_revnum_t rev;
1870  const char *target, *depth_word;
1871  const char *switch_url, *switch_path;
1872  svn_boolean_t recurse;
1873  /* Default to unknown.  Old clients won't send depth, but we'll
1874     handle that by converting recurse if necessary. */
1875  svn_depth_t depth = svn_depth_unknown;
1876  apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
1877  apr_uint64_t ignore_ancestry; /* Optional; default TRUE */
1878
1879  /* Parse the arguments. */
1880  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbc?w?BB", &rev, &target,
1881                                  &recurse, &switch_url, &depth_word,
1882                                  &send_copyfrom_args, &ignore_ancestry));
1883  target = svn_relpath_canonicalize(target, pool);
1884  switch_url = svn_uri_canonicalize(switch_url, pool);
1885
1886  if (depth_word)
1887    depth = svn_depth_from_word(depth_word);
1888  else
1889    depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1890
1891  SVN_ERR(trivial_auth_request(conn, pool, b));
1892  if (!SVN_IS_VALID_REVNUM(rev))
1893    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1894
1895  SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1896                          svn_path_uri_decode(switch_url, pool),
1897                          &switch_path));
1898
1899  {
1900    const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1901    SVN_ERR(log_command(b, conn, pool, "%s",
1902                        svn_log__switch(full_path, switch_path, rev,
1903                                        depth, pool)));
1904  }
1905
1906  return accept_report(NULL, NULL,
1907                       conn, pool, b, rev, target, switch_path, TRUE,
1908                       depth,
1909                       (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
1910                       (ignore_ancestry != FALSE) /* ignore_ancestry */);
1911}
1912
1913static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1914                           apr_array_header_t *params, void *baton)
1915{
1916  server_baton_t *b = baton;
1917  svn_revnum_t rev;
1918  const char *target, *depth_word;
1919  svn_boolean_t recurse;
1920  /* Default to unknown.  Old clients won't send depth, but we'll
1921     handle that by converting recurse if necessary. */
1922  svn_depth_t depth = svn_depth_unknown;
1923
1924  /* Parse the arguments. */
1925  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cb?(?r)?w",
1926                                  &target, &recurse, &rev, &depth_word));
1927  target = svn_relpath_canonicalize(target, pool);
1928
1929  if (depth_word)
1930    depth = svn_depth_from_word(depth_word);
1931  else
1932    depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
1933
1934  SVN_ERR(trivial_auth_request(conn, pool, b));
1935  if (!SVN_IS_VALID_REVNUM(rev))
1936    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1937
1938  {
1939    const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1940    SVN_ERR(log_command(b, conn, pool, "%s",
1941                        svn_log__status(full_path, rev, depth, pool)));
1942  }
1943
1944  return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
1945                       depth, FALSE, FALSE);
1946}
1947
1948static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1949                         apr_array_header_t *params, void *baton)
1950{
1951  server_baton_t *b = baton;
1952  svn_revnum_t rev;
1953  const char *target, *versus_url, *versus_path, *depth_word;
1954  svn_boolean_t recurse, ignore_ancestry;
1955  svn_boolean_t text_deltas;
1956  /* Default to unknown.  Old clients won't send depth, but we'll
1957     handle that by converting recurse if necessary. */
1958  svn_depth_t depth = svn_depth_unknown;
1959
1960  /* Parse the arguments. */
1961  if (params->nelts == 5)
1962    {
1963      /* Clients before 1.4 don't send the text_deltas boolean or depth. */
1964      SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
1965                                      &recurse, &ignore_ancestry, &versus_url));
1966      text_deltas = TRUE;
1967      depth_word = NULL;
1968    }
1969  else
1970    {
1971      SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbcb?w",
1972                                      &rev, &target, &recurse,
1973                                      &ignore_ancestry, &versus_url,
1974                                      &text_deltas, &depth_word));
1975    }
1976  target = svn_relpath_canonicalize(target, pool);
1977  versus_url = svn_uri_canonicalize(versus_url, pool);
1978
1979  if (depth_word)
1980    depth = svn_depth_from_word(depth_word);
1981  else
1982    depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1983
1984  SVN_ERR(trivial_auth_request(conn, pool, b));
1985
1986  if (!SVN_IS_VALID_REVNUM(rev))
1987    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1988  SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1989                          svn_path_uri_decode(versus_url, pool),
1990                          &versus_path));
1991
1992  {
1993    const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1994    svn_revnum_t from_rev;
1995    SVN_ERR(accept_report(NULL, &from_rev,
1996                          conn, pool, b, rev, target, versus_path,
1997                          text_deltas, depth, FALSE, ignore_ancestry));
1998    SVN_ERR(log_command(b, conn, pool, "%s",
1999                        svn_log__diff(full_path, from_rev, versus_path,
2000                                      rev, depth, ignore_ancestry,
2001                                      pool)));
2002  }
2003  return SVN_NO_ERROR;
2004}
2005
2006/* Regardless of whether a client's capabilities indicate an
2007   understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
2008   we provide a response.
2009
2010   ASSUMPTION: When performing a 'merge' with two URLs at different
2011   revisions, the client will call this command more than once. */
2012static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2013                                  apr_array_header_t *params, void *baton)
2014{
2015  server_baton_t *b = baton;
2016  svn_revnum_t rev;
2017  apr_array_header_t *paths, *canonical_paths;
2018  svn_mergeinfo_catalog_t mergeinfo;
2019  int i;
2020  apr_hash_index_t *hi;
2021  const char *inherit_word;
2022  svn_mergeinfo_inheritance_t inherit;
2023  svn_boolean_t include_descendants;
2024  apr_pool_t *iterpool;
2025  authz_baton_t ab;
2026
2027  ab.server = b;
2028  ab.conn = conn;
2029
2030  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)wb", &paths, &rev,
2031                                  &inherit_word, &include_descendants));
2032  inherit = svn_inheritance_from_word(inherit_word);
2033
2034  /* Canonicalize the paths which mergeinfo has been requested for. */
2035  canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2036  for (i = 0; i < paths->nelts; i++)
2037     {
2038        svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2039        const char *full_path;
2040
2041        if (item->kind != SVN_RA_SVN_STRING)
2042          return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2043                                  _("Path is not a string"));
2044        full_path = svn_relpath_canonicalize(item->u.string->data, pool);
2045        full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
2046        APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
2047     }
2048
2049  SVN_ERR(log_command(b, conn, pool, "%s",
2050                      svn_log__get_mergeinfo(canonical_paths, inherit,
2051                                             include_descendants,
2052                                             pool)));
2053
2054  SVN_ERR(trivial_auth_request(conn, pool, b));
2055  SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos,
2056                                         canonical_paths, rev,
2057                                         inherit,
2058                                         include_descendants,
2059                                         authz_check_access_cb_func(b), &ab,
2060                                         pool));
2061  SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo,
2062                                                    b->fs_path->data, pool));
2063  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2064  iterpool = svn_pool_create(pool);
2065  for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
2066    {
2067      const char *key = svn__apr_hash_index_key(hi);
2068      svn_mergeinfo_t value = svn__apr_hash_index_val(hi);
2069      svn_string_t *mergeinfo_string;
2070
2071      svn_pool_clear(iterpool);
2072
2073      SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, value, iterpool));
2074      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cs", key,
2075                                      mergeinfo_string));
2076    }
2077  svn_pool_destroy(iterpool);
2078  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2079
2080  return SVN_NO_ERROR;
2081}
2082
2083/* Send a log entry to the client. */
2084static svn_error_t *log_receiver(void *baton,
2085                                 svn_log_entry_t *log_entry,
2086                                 apr_pool_t *pool)
2087{
2088  log_baton_t *b = baton;
2089  svn_ra_svn_conn_t *conn = b->conn;
2090  apr_hash_index_t *h;
2091  svn_boolean_t invalid_revnum = FALSE;
2092  char action[2];
2093  const char *author, *date, *message;
2094  apr_uint64_t revprop_count;
2095
2096  if (log_entry->revision == SVN_INVALID_REVNUM)
2097    {
2098      /* If the stack depth is zero, we've seen the last revision, so don't
2099         send it, just return. */
2100      if (b->stack_depth == 0)
2101        return SVN_NO_ERROR;
2102
2103      /* Because the svn protocol won't let us send an invalid revnum, we have
2104         to fudge here and send an additional flag. */
2105      log_entry->revision = 0;
2106      invalid_revnum = TRUE;
2107      b->stack_depth--;
2108    }
2109
2110  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "(!"));
2111  if (log_entry->changed_paths2)
2112    {
2113      for (h = apr_hash_first(pool, log_entry->changed_paths2); h;
2114                                                        h = apr_hash_next(h))
2115        {
2116          const char *path = svn__apr_hash_index_key(h);
2117          svn_log_changed_path2_t *change = svn__apr_hash_index_val(h);
2118
2119          action[0] = change->action;
2120          action[1] = '\0';
2121          SVN_ERR(svn_ra_svn__write_tuple(
2122                      conn, pool, "cw(?cr)(cbb)",
2123                      path,
2124                      action,
2125                      change->copyfrom_path,
2126                      change->copyfrom_rev,
2127                      svn_node_kind_to_word(change->node_kind),
2128                      /* text_modified and props_modified are never unknown */
2129                      change->text_modified  == svn_tristate_true,
2130                      change->props_modified == svn_tristate_true));
2131        }
2132    }
2133  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
2134  svn_compat_log_revprops_clear(log_entry->revprops);
2135  if (log_entry->revprops)
2136    revprop_count = apr_hash_count(log_entry->revprops);
2137  else
2138    revprop_count = 0;
2139  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
2140                                  log_entry->revision,
2141                                  author, date, message,
2142                                  log_entry->has_children,
2143                                  invalid_revnum, revprop_count));
2144  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, log_entry->revprops));
2145  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b",
2146                                  log_entry->subtractive_merge));
2147
2148  if (log_entry->has_children)
2149    b->stack_depth++;
2150
2151  return SVN_NO_ERROR;
2152}
2153
2154static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2155                            apr_array_header_t *params, void *baton)
2156{
2157  svn_error_t *err, *write_err;
2158  server_baton_t *b = baton;
2159  svn_revnum_t start_rev, end_rev;
2160  const char *full_path;
2161  svn_boolean_t send_changed_paths, strict_node, include_merged_revisions;
2162  apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
2163  char *revprop_word;
2164  svn_ra_svn_item_t *elt;
2165  int i;
2166  apr_uint64_t limit, include_merged_revs_param;
2167  log_baton_t lb;
2168  authz_baton_t ab;
2169
2170  ab.server = b;
2171  ab.conn = conn;
2172
2173  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
2174                                  &start_rev, &end_rev, &send_changed_paths,
2175                                  &strict_node, &limit,
2176                                  &include_merged_revs_param,
2177                                  &revprop_word, &revprop_items));
2178
2179  if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2180    include_merged_revisions = FALSE;
2181  else
2182    include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2183
2184  if (revprop_word == NULL)
2185    /* pre-1.5 client */
2186    revprops = svn_compat_log_revprops_in(pool);
2187  else if (strcmp(revprop_word, "all-revprops") == 0)
2188    revprops = NULL;
2189  else if (strcmp(revprop_word, "revprops") == 0)
2190    {
2191      SVN_ERR_ASSERT(revprop_items);
2192
2193      revprops = apr_array_make(pool, revprop_items->nelts,
2194                                sizeof(char *));
2195      for (i = 0; i < revprop_items->nelts; i++)
2196        {
2197          elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
2198          if (elt->kind != SVN_RA_SVN_STRING)
2199            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2200                                    _("Log revprop entry not a string"));
2201          APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
2202        }
2203    }
2204  else
2205    return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2206                             _("Unknown revprop word '%s' in log command"),
2207                             revprop_word);
2208
2209  /* If we got an unspecified number then the user didn't send us anything,
2210     so we assume no limit.  If it's larger than INT_MAX then someone is
2211     messing with us, since we know the svn client libraries will never send
2212     us anything that big, so play it safe and default to no limit. */
2213  if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
2214    limit = 0;
2215
2216  full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2217  for (i = 0; i < paths->nelts; i++)
2218    {
2219      elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2220      if (elt->kind != SVN_RA_SVN_STRING)
2221        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2222                                _("Log path entry not a string"));
2223      full_path = svn_relpath_canonicalize(elt->u.string->data, pool),
2224      full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
2225      APR_ARRAY_PUSH(full_paths, const char *) = full_path;
2226    }
2227  SVN_ERR(trivial_auth_request(conn, pool, b));
2228
2229  SVN_ERR(log_command(b, conn, pool, "%s",
2230                      svn_log__log(full_paths, start_rev, end_rev,
2231                                   (int) limit, send_changed_paths,
2232                                   strict_node, include_merged_revisions,
2233                                   revprops, pool)));
2234
2235  /* Get logs.  (Can't report errors back to the client at this point.) */
2236  lb.fs_path = b->fs_path->data;
2237  lb.conn = conn;
2238  lb.stack_depth = 0;
2239  err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
2240                            (int) limit, send_changed_paths, strict_node,
2241                            include_merged_revisions, revprops,
2242                            authz_check_access_cb_func(b), &ab, log_receiver,
2243                            &lb, pool);
2244
2245  write_err = svn_ra_svn__write_word(conn, pool, "done");
2246  if (write_err)
2247    {
2248      svn_error_clear(err);
2249      return write_err;
2250    }
2251  SVN_CMD_ERR(err);
2252  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2253  return SVN_NO_ERROR;
2254}
2255
2256static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2257                               apr_array_header_t *params, void *baton)
2258{
2259  server_baton_t *b = baton;
2260  svn_revnum_t rev;
2261  const char *path, *full_path;
2262  svn_fs_root_t *root;
2263  svn_node_kind_t kind;
2264
2265  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2266  full_path = svn_fspath__join(b->fs_path->data,
2267                               svn_relpath_canonicalize(path, pool), pool);
2268
2269  /* Check authorizations */
2270  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2271                           full_path, FALSE));
2272
2273  if (!SVN_IS_VALID_REVNUM(rev))
2274    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2275
2276  SVN_ERR(log_command(b, conn, pool, "check-path %s@%d",
2277                      svn_path_uri_encode(full_path, pool), rev));
2278
2279  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
2280  SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
2281  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w",
2282                                         svn_node_kind_to_word(kind)));
2283  return SVN_NO_ERROR;
2284}
2285
2286static svn_error_t *stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2287                             apr_array_header_t *params, void *baton)
2288{
2289  server_baton_t *b = baton;
2290  svn_revnum_t rev;
2291  const char *path, *full_path, *cdate;
2292  svn_fs_root_t *root;
2293  svn_dirent_t *dirent;
2294
2295  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2296  full_path = svn_fspath__join(b->fs_path->data,
2297                               svn_relpath_canonicalize(path, pool), pool);
2298
2299  /* Check authorizations */
2300  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2301                           full_path, FALSE));
2302
2303  if (!SVN_IS_VALID_REVNUM(rev))
2304    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2305
2306  SVN_ERR(log_command(b, conn, pool, "stat %s@%d",
2307                      svn_path_uri_encode(full_path, pool), rev));
2308
2309  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
2310  SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
2311
2312  /* Need to return the equivalent of "(?l)", since that's what the
2313     client is reading.  */
2314
2315  if (dirent == NULL)
2316    {
2317      SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()"));
2318      return SVN_NO_ERROR;
2319    }
2320
2321  cdate = (dirent->time == (time_t) -1) ? NULL
2322    : svn_time_to_cstring(dirent->time, pool);
2323
2324  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
2325                                         svn_node_kind_to_word(dirent->kind),
2326                                         (apr_uint64_t) dirent->size,
2327                                         dirent->has_props, dirent->created_rev,
2328                                         cdate, dirent->last_author));
2329
2330  return SVN_NO_ERROR;
2331}
2332
2333static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2334                                  apr_array_header_t *params, void *baton)
2335{
2336  svn_error_t *err, *write_err;
2337  server_baton_t *b = baton;
2338  svn_revnum_t revision;
2339  apr_array_header_t *location_revisions, *loc_revs_proto;
2340  svn_ra_svn_item_t *elt;
2341  int i;
2342  const char *relative_path;
2343  svn_revnum_t peg_revision;
2344  apr_hash_t *fs_locations;
2345  const char *abs_path;
2346  authz_baton_t ab;
2347
2348  ab.server = b;
2349  ab.conn = conn;
2350
2351  /* Parse the arguments. */
2352  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crl", &relative_path,
2353                                  &peg_revision,
2354                                  &loc_revs_proto));
2355  relative_path = svn_relpath_canonicalize(relative_path, pool);
2356
2357  abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
2358
2359  location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
2360                                      sizeof(svn_revnum_t));
2361  for (i = 0; i < loc_revs_proto->nelts; i++)
2362    {
2363      elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
2364      if (elt->kind != SVN_RA_SVN_NUMBER)
2365        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2366                                "Get-locations location revisions entry "
2367                                "not a revision number");
2368      revision = (svn_revnum_t)(elt->u.number);
2369      APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
2370    }
2371  SVN_ERR(trivial_auth_request(conn, pool, b));
2372  SVN_ERR(log_command(b, conn, pool, "%s",
2373                      svn_log__get_locations(abs_path, peg_revision,
2374                                             location_revisions, pool)));
2375
2376  /* All the parameters are fine - let's perform the query against the
2377   * repository. */
2378
2379  /* We store both err and write_err here, so the client will get
2380   * the "done" even if there was an error in fetching the results. */
2381
2382  err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
2383                                       peg_revision, location_revisions,
2384                                       authz_check_access_cb_func(b), &ab,
2385                                       pool);
2386
2387  /* Now, write the results to the connection. */
2388  if (!err)
2389    {
2390      if (fs_locations)
2391        {
2392          apr_hash_index_t *iter;
2393
2394          for (iter = apr_hash_first(pool, fs_locations); iter;
2395              iter = apr_hash_next(iter))
2396            {
2397              const svn_revnum_t *iter_key = svn__apr_hash_index_key(iter);
2398              const char *iter_value = svn__apr_hash_index_val(iter);
2399
2400              SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc",
2401                                              *iter_key, iter_value));
2402            }
2403        }
2404    }
2405
2406  write_err = svn_ra_svn__write_word(conn, pool, "done");
2407  if (write_err)
2408    {
2409      svn_error_clear(err);
2410      return write_err;
2411    }
2412  SVN_CMD_ERR(err);
2413
2414  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2415
2416  return SVN_NO_ERROR;
2417}
2418
2419static svn_error_t *gls_receiver(svn_location_segment_t *segment,
2420                                 void *baton,
2421                                 apr_pool_t *pool)
2422{
2423  svn_ra_svn_conn_t *conn = baton;
2424  return svn_ra_svn__write_tuple(conn, pool, "rr(?c)",
2425                                 segment->range_start,
2426                                 segment->range_end,
2427                                 segment->path);
2428}
2429
2430static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn,
2431                                          apr_pool_t *pool,
2432                                          apr_array_header_t *params,
2433                                          void *baton)
2434{
2435  svn_error_t *err, *write_err;
2436  server_baton_t *b = baton;
2437  svn_revnum_t peg_revision, start_rev, end_rev;
2438  const char *relative_path;
2439  const char *abs_path;
2440  authz_baton_t ab;
2441
2442  ab.server = b;
2443  ab.conn = conn;
2444
2445  /* Parse the arguments. */
2446  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)(?r)",
2447                                  &relative_path, &peg_revision,
2448                                  &start_rev, &end_rev));
2449  relative_path = svn_relpath_canonicalize(relative_path, pool);
2450
2451  abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
2452
2453  if (SVN_IS_VALID_REVNUM(start_rev)
2454      && SVN_IS_VALID_REVNUM(end_rev)
2455      && (end_rev > start_rev))
2456    {
2457      err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2458                              "Get-location-segments end revision must not be "
2459                              "younger than start revision");
2460      return log_fail_and_flush(err, b, conn, pool);
2461    }
2462
2463  if (SVN_IS_VALID_REVNUM(peg_revision)
2464      && SVN_IS_VALID_REVNUM(start_rev)
2465      && (start_rev > peg_revision))
2466    {
2467      err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2468                              "Get-location-segments start revision must not "
2469                              "be younger than peg revision");
2470      return log_fail_and_flush(err, b, conn, pool);
2471    }
2472
2473  SVN_ERR(trivial_auth_request(conn, pool, b));
2474  SVN_ERR(log_command(baton, conn, pool, "%s",
2475                      svn_log__get_location_segments(abs_path, peg_revision,
2476                                                     start_rev, end_rev,
2477                                                     pool)));
2478
2479  /* All the parameters are fine - let's perform the query against the
2480   * repository. */
2481
2482  /* We store both err and write_err here, so the client will get
2483   * the "done" even if there was an error in fetching the results. */
2484
2485  err = svn_repos_node_location_segments(b->repos, abs_path,
2486                                         peg_revision, start_rev, end_rev,
2487                                         gls_receiver, (void *)conn,
2488                                         authz_check_access_cb_func(b), &ab,
2489                                         pool);
2490  write_err = svn_ra_svn__write_word(conn, pool, "done");
2491  if (write_err)
2492    {
2493      svn_error_clear(err);
2494      return write_err;
2495    }
2496  SVN_CMD_ERR(err);
2497
2498  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2499
2500  return SVN_NO_ERROR;
2501}
2502
2503/* This implements svn_write_fn_t.  Write LEN bytes starting at DATA to the
2504   client as a string. */
2505static svn_error_t *svndiff_handler(void *baton, const char *data,
2506                                    apr_size_t *len)
2507{
2508  file_revs_baton_t *b = baton;
2509  svn_string_t str;
2510
2511  str.data = data;
2512  str.len = *len;
2513  return svn_ra_svn__write_string(b->conn, b->pool, &str);
2514}
2515
2516/* This implements svn_close_fn_t.  Mark the end of the data by writing an
2517   empty string to the client. */
2518static svn_error_t *svndiff_close_handler(void *baton)
2519{
2520  file_revs_baton_t *b = baton;
2521
2522  SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, ""));
2523  return SVN_NO_ERROR;
2524}
2525
2526/* This implements the svn_repos_file_rev_handler_t interface. */
2527static svn_error_t *file_rev_handler(void *baton, const char *path,
2528                                     svn_revnum_t rev, apr_hash_t *rev_props,
2529                                     svn_boolean_t merged_revision,
2530                                     svn_txdelta_window_handler_t *d_handler,
2531                                     void **d_baton,
2532                                     apr_array_header_t *prop_diffs,
2533                                     apr_pool_t *pool)
2534{
2535  file_revs_baton_t *frb = baton;
2536  svn_stream_t *stream;
2537
2538  SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!",
2539                                  path, rev));
2540  SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props));
2541  SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!"));
2542  SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
2543  SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision));
2544
2545  /* Store the pool for the delta stream. */
2546  frb->pool = pool;
2547
2548  /* Prepare for the delta or just write an empty string. */
2549  if (d_handler)
2550    {
2551      stream = svn_stream_create(baton, pool);
2552      svn_stream_set_write(stream, svndiff_handler);
2553      svn_stream_set_close(stream, svndiff_close_handler);
2554
2555      /* If the connection does not support SVNDIFF1 or if we don't want to use
2556       * compression, use the non-compressing "version 0" implementation */
2557      if (   svn_ra_svn_compression_level(frb->conn) > 0
2558          && svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1))
2559        svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 1,
2560                                svn_ra_svn_compression_level(frb->conn), pool);
2561      else
2562        svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 0,
2563                                svn_ra_svn_compression_level(frb->conn), pool);
2564    }
2565  else
2566    SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, ""));
2567
2568  return SVN_NO_ERROR;
2569}
2570
2571static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2572                                  apr_array_header_t *params, void *baton)
2573{
2574  server_baton_t *b = baton;
2575  svn_error_t *err, *write_err;
2576  file_revs_baton_t frb;
2577  svn_revnum_t start_rev, end_rev;
2578  const char *path;
2579  const char *full_path;
2580  apr_uint64_t include_merged_revs_param;
2581  svn_boolean_t include_merged_revisions;
2582  authz_baton_t ab;
2583
2584  ab.server = b;
2585  ab.conn = conn;
2586
2587  /* Parse arguments. */
2588  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)?B",
2589                                  &path, &start_rev, &end_rev,
2590                                  &include_merged_revs_param));
2591  path = svn_relpath_canonicalize(path, pool);
2592  SVN_ERR(trivial_auth_request(conn, pool, b));
2593  full_path = svn_fspath__join(b->fs_path->data, path, pool);
2594
2595  if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2596    include_merged_revisions = FALSE;
2597  else
2598    include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2599
2600  SVN_ERR(log_command(b, conn, pool, "%s",
2601                      svn_log__get_file_revs(full_path, start_rev, end_rev,
2602                                             include_merged_revisions,
2603                                             pool)));
2604
2605  frb.conn = conn;
2606  frb.pool = NULL;
2607
2608  err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev,
2609                                 include_merged_revisions,
2610                                 authz_check_access_cb_func(b), &ab,
2611                                 file_rev_handler, &frb, pool);
2612  write_err = svn_ra_svn__write_word(conn, pool, "done");
2613  if (write_err)
2614    {
2615      svn_error_clear(err);
2616      return write_err;
2617    }
2618  SVN_CMD_ERR(err);
2619  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2620
2621  return SVN_NO_ERROR;
2622}
2623
2624static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2625                         apr_array_header_t *params, void *baton)
2626{
2627  server_baton_t *b = baton;
2628  const char *path;
2629  const char *comment;
2630  const char *full_path;
2631  svn_boolean_t steal_lock;
2632  svn_revnum_t current_rev;
2633  svn_lock_t *l;
2634
2635  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
2636                                  &steal_lock, &current_rev));
2637  full_path = svn_fspath__join(b->fs_path->data,
2638                               svn_relpath_canonicalize(path, pool), pool);
2639
2640  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2641                           full_path, TRUE));
2642  SVN_ERR(log_command(b, conn, pool, "%s",
2643                      svn_log__lock_one_path(full_path, steal_lock, pool)));
2644
2645  SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
2646                                0, /* No expiration time. */
2647                                current_rev, steal_lock, pool));
2648
2649  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success"));
2650  SVN_ERR(write_lock(conn, pool, l));
2651  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)"));
2652
2653  return SVN_NO_ERROR;
2654}
2655
2656static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2657                              apr_array_header_t *params, void *baton)
2658{
2659  server_baton_t *b = baton;
2660  apr_array_header_t *path_revs;
2661  const char *comment;
2662  svn_boolean_t steal_lock;
2663  int i;
2664  apr_pool_t *subpool;
2665  const char *path;
2666  const char *full_path;
2667  svn_revnum_t current_rev;
2668  apr_array_header_t *log_paths;
2669  svn_lock_t *l;
2670  svn_error_t *err = SVN_NO_ERROR, *write_err;
2671
2672  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
2673                                  &path_revs));
2674
2675  subpool = svn_pool_create(pool);
2676
2677  /* Because we can only send a single auth reply per request, we send
2678     a reply before parsing the lock commands.  This means an authz
2679     access denial will abort the processing of the locks and return
2680     an error. */
2681  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
2682
2683  /* Loop through the lock requests. */
2684  log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path));
2685  for (i = 0; i < path_revs->nelts; ++i)
2686    {
2687      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
2688                                               svn_ra_svn_item_t);
2689
2690      svn_pool_clear(subpool);
2691
2692      if (item->kind != SVN_RA_SVN_LIST)
2693        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2694                                "Lock requests should be list of lists");
2695
2696      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "c(?r)", &path,
2697                                      &current_rev));
2698
2699      /* Allocate the full_path out of pool so it will survive for use
2700       * by operational logging, after this loop. */
2701      full_path = svn_fspath__join(b->fs_path->data,
2702                                   svn_relpath_canonicalize(path, subpool),
2703                                   pool);
2704      APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2705
2706      if (! lookup_access(pool, b, conn, svn_authz_write, full_path, TRUE))
2707        {
2708          err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
2709                                     b, conn, pool);
2710          break;
2711        }
2712
2713      err = svn_repos_fs_lock(&l, b->repos, full_path,
2714                              NULL, comment, FALSE,
2715                              0, /* No expiration time. */
2716                              current_rev,
2717                              steal_lock, subpool);
2718
2719      if (err)
2720        {
2721          if (SVN_ERR_IS_LOCK_ERROR(err))
2722            {
2723              write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
2724              svn_error_clear(err);
2725              err = NULL;
2726              SVN_ERR(write_err);
2727            }
2728          else
2729            break;
2730        }
2731      else
2732        {
2733          SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w!", "success"));
2734          SVN_ERR(write_lock(conn, subpool, l));
2735          SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "!"));
2736        }
2737    }
2738
2739  svn_pool_destroy(subpool);
2740
2741  SVN_ERR(log_command(b, conn, pool, "%s",
2742                      svn_log__lock(log_paths, steal_lock, pool)));
2743
2744  /* NOTE: err might contain a fatal locking error from the loop above. */
2745  write_err = svn_ra_svn__write_word(conn, pool, "done");
2746  if (!write_err)
2747    SVN_CMD_ERR(err);
2748  svn_error_clear(err);
2749  SVN_ERR(write_err);
2750  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2751
2752  return SVN_NO_ERROR;
2753}
2754
2755static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2756                           apr_array_header_t *params, void *baton)
2757{
2758  server_baton_t *b = baton;
2759  const char *path, *token, *full_path;
2760  svn_boolean_t break_lock;
2761
2762  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b", &path, &token,
2763                                 &break_lock));
2764
2765  full_path = svn_fspath__join(b->fs_path->data,
2766                               svn_relpath_canonicalize(path, pool), pool);
2767
2768  /* Username required unless break_lock was specified. */
2769  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2770                           full_path, ! break_lock));
2771  SVN_ERR(log_command(b, conn, pool, "%s",
2772                      svn_log__unlock_one_path(full_path, break_lock, pool)));
2773
2774  SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2775                                  pool));
2776
2777  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2778
2779  return SVN_NO_ERROR;
2780}
2781
2782static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2783                                apr_array_header_t *params, void *baton)
2784{
2785  server_baton_t *b = baton;
2786  svn_boolean_t break_lock;
2787  apr_array_header_t *unlock_tokens;
2788  int i;
2789  apr_pool_t *subpool;
2790  const char *path;
2791  const char *full_path;
2792  apr_array_header_t *log_paths;
2793  const char *token;
2794  svn_error_t *err = SVN_NO_ERROR, *write_err;
2795
2796  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock,
2797                                  &unlock_tokens));
2798
2799  /* Username required unless break_lock was specified. */
2800  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
2801
2802  subpool = svn_pool_create(pool);
2803
2804  /* Loop through the unlock requests. */
2805  log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path));
2806  for (i = 0; i < unlock_tokens->nelts; i++)
2807    {
2808      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
2809                                               svn_ra_svn_item_t);
2810
2811      svn_pool_clear(subpool);
2812
2813      if (item->kind != SVN_RA_SVN_LIST)
2814        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2815                                "Unlock request should be a list of lists");
2816
2817      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
2818                                      &token));
2819
2820      /* Allocate the full_path out of pool so it will survive for use
2821       * by operational logging, after this loop. */
2822      full_path = svn_fspath__join(b->fs_path->data,
2823                                   svn_relpath_canonicalize(path, subpool),
2824                                   pool);
2825      APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2826
2827      if (! lookup_access(subpool, b, conn, svn_authz_write, full_path,
2828                          ! break_lock))
2829        return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
2830                                error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
2831                                                     NULL, NULL,
2832                                                     b, conn, pool),
2833                                NULL);
2834
2835      err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2836                                subpool);
2837      if (err)
2838        {
2839          if (SVN_ERR_IS_UNLOCK_ERROR(err))
2840            {
2841              write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
2842              svn_error_clear(err);
2843              err = NULL;
2844              SVN_ERR(write_err);
2845            }
2846          else
2847            break;
2848        }
2849      else
2850        SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
2851                                        path));
2852    }
2853
2854  svn_pool_destroy(subpool);
2855
2856  SVN_ERR(log_command(b, conn, pool, "%s",
2857                      svn_log__unlock(log_paths, break_lock, pool)));
2858
2859  /* NOTE: err might contain a fatal unlocking error from the loop above. */
2860  write_err = svn_ra_svn__write_word(conn, pool, "done");
2861  if (! write_err)
2862    SVN_CMD_ERR(err);
2863  svn_error_clear(err);
2864  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2865
2866  return SVN_NO_ERROR;
2867}
2868
2869static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2870                             apr_array_header_t *params, void *baton)
2871{
2872  server_baton_t *b = baton;
2873  const char *path;
2874  const char *full_path;
2875  svn_lock_t *l;
2876
2877  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
2878
2879  full_path = svn_fspath__join(b->fs_path->data,
2880                               svn_relpath_canonicalize(path, pool), pool);
2881
2882  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2883                           full_path, FALSE));
2884  SVN_ERR(log_command(b, conn, pool, "get-lock %s",
2885                      svn_path_uri_encode(full_path, pool)));
2886
2887  SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
2888
2889  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2890  if (l)
2891    SVN_ERR(write_lock(conn, pool, l));
2892  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2893
2894  return SVN_NO_ERROR;
2895}
2896
2897static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2898                              apr_array_header_t *params, void *baton)
2899{
2900  server_baton_t *b = baton;
2901  const char *path;
2902  const char *full_path;
2903  const char *depth_word;
2904  svn_depth_t depth;
2905  apr_hash_t *locks;
2906  apr_hash_index_t *hi;
2907  svn_error_t *err;
2908  authz_baton_t ab;
2909
2910  ab.server = b;
2911  ab.conn = conn;
2912
2913  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c?(?w)", &path, &depth_word));
2914
2915  depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity;
2916  if ((depth != svn_depth_empty) &&
2917      (depth != svn_depth_files) &&
2918      (depth != svn_depth_immediates) &&
2919      (depth != svn_depth_infinity))
2920    {
2921      err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2922                             "Invalid 'depth' specified in get-locks request");
2923      return log_fail_and_flush(err, b, conn, pool);
2924    }
2925
2926  full_path = svn_fspath__join(b->fs_path->data,
2927                               svn_relpath_canonicalize(path, pool), pool);
2928
2929  SVN_ERR(trivial_auth_request(conn, pool, b));
2930
2931  SVN_ERR(log_command(b, conn, pool, "get-locks %s",
2932                      svn_path_uri_encode(full_path, pool)));
2933  SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repos, full_path, depth,
2934                                      authz_check_access_cb_func(b), &ab,
2935                                      pool));
2936
2937  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2938  for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2939    {
2940      svn_lock_t *l = svn__apr_hash_index_val(hi);
2941
2942      SVN_ERR(write_lock(conn, pool, l));
2943    }
2944  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2945
2946  return SVN_NO_ERROR;
2947}
2948
2949static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
2950                                        server_baton_t *b,
2951                                        svn_revnum_t rev,
2952                                        svn_revnum_t low_water_mark,
2953                                        svn_boolean_t send_deltas,
2954                                        apr_pool_t *pool)
2955{
2956  const svn_delta_editor_t *editor;
2957  void *edit_baton;
2958  svn_fs_root_t *root;
2959  svn_error_t *err;
2960  authz_baton_t ab;
2961
2962  ab.server = b;
2963  ab.conn = conn;
2964
2965  SVN_ERR(log_command(b, conn, pool,
2966                      svn_log__replay(b->fs_path->data, rev, pool)));
2967
2968  svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
2969
2970  err = svn_fs_revision_root(&root, b->fs, rev, pool);
2971
2972  if (! err)
2973    err = svn_repos_replay2(root, b->fs_path->data, low_water_mark,
2974                            send_deltas, editor, edit_baton,
2975                            authz_check_access_cb_func(b), &ab, pool);
2976
2977  if (err)
2978    svn_error_clear(editor->abort_edit(edit_baton, pool));
2979  SVN_CMD_ERR(err);
2980
2981  return svn_ra_svn__write_cmd_finish_replay(conn, pool);
2982}
2983
2984static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2985                           apr_array_header_t *params, void *baton)
2986{
2987  svn_revnum_t rev, low_water_mark;
2988  svn_boolean_t send_deltas;
2989  server_baton_t *b = baton;
2990
2991  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrb", &rev, &low_water_mark,
2992                                 &send_deltas));
2993
2994  SVN_ERR(trivial_auth_request(conn, pool, b));
2995
2996  SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
2997                              send_deltas, pool));
2998
2999  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3000
3001  return SVN_NO_ERROR;
3002}
3003
3004static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3005                                 apr_array_header_t *params, void *baton)
3006{
3007  svn_revnum_t start_rev, end_rev, rev, low_water_mark;
3008  svn_boolean_t send_deltas;
3009  server_baton_t *b = baton;
3010  apr_pool_t *iterpool;
3011  authz_baton_t ab;
3012
3013  ab.server = b;
3014  ab.conn = conn;
3015
3016  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrrb", &start_rev,
3017                                 &end_rev, &low_water_mark,
3018                                 &send_deltas));
3019
3020  SVN_ERR(trivial_auth_request(conn, pool, b));
3021
3022  iterpool = svn_pool_create(pool);
3023  for (rev = start_rev; rev <= end_rev; rev++)
3024    {
3025      apr_hash_t *props;
3026
3027      svn_pool_clear(iterpool);
3028
3029      SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
3030                                                 authz_check_access_cb_func(b),
3031                                                 &ab,
3032                                                 iterpool));
3033      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops"));
3034      SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props));
3035      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)"));
3036
3037      SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3038                                  send_deltas, iterpool));
3039
3040    }
3041  svn_pool_destroy(iterpool);
3042
3043  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3044
3045  return SVN_NO_ERROR;
3046}
3047
3048static svn_error_t *
3049get_deleted_rev(svn_ra_svn_conn_t *conn,
3050                apr_pool_t *pool,
3051                apr_array_header_t *params,
3052                void *baton)
3053{
3054  server_baton_t *b = baton;
3055  const char *path, *full_path;
3056  svn_revnum_t peg_revision;
3057  svn_revnum_t end_revision;
3058  svn_revnum_t revision_deleted;
3059
3060  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crr",
3061                                 &path, &peg_revision, &end_revision));
3062  full_path = svn_fspath__join(b->fs_path->data,
3063                               svn_relpath_canonicalize(path, pool), pool);
3064  SVN_ERR(log_command(b, conn, pool, "get-deleted-rev"));
3065  SVN_ERR(trivial_auth_request(conn, pool, b));
3066  SVN_ERR(svn_repos_deleted_rev(b->fs, full_path, peg_revision, end_revision,
3067                                &revision_deleted, pool));
3068  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted));
3069  return SVN_NO_ERROR;
3070}
3071
3072static svn_error_t *
3073get_inherited_props(svn_ra_svn_conn_t *conn,
3074                    apr_pool_t *pool,
3075                    apr_array_header_t *params,
3076                    void *baton)
3077{
3078  server_baton_t *b = baton;
3079  const char *path, *full_path;
3080  svn_revnum_t rev;
3081  svn_fs_root_t *root;
3082  apr_array_header_t *inherited_props;
3083  int i;
3084  apr_pool_t *iterpool = svn_pool_create(pool);
3085  authz_baton_t ab;
3086
3087  ab.server = b;
3088  ab.conn = conn;
3089
3090  /* Parse arguments. */
3091  SVN_ERR(svn_ra_svn__parse_tuple(params, iterpool, "c(?r)", &path, &rev));
3092
3093  full_path = svn_fspath__join(b->fs_path->data,
3094                               svn_relpath_canonicalize(path, iterpool),
3095                               pool);
3096
3097  /* Check authorizations */
3098  SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read,
3099                           full_path, FALSE));
3100
3101  if (!SVN_IS_VALID_REVNUM(rev))
3102    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
3103
3104  SVN_ERR(log_command(b, conn, pool, "%s",
3105                      svn_log__get_inherited_props(full_path, rev,
3106                                                   iterpool)));
3107
3108  /* Fetch the properties and a stream for the contents. */
3109  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, iterpool));
3110  SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool));
3111
3112  /* Send successful command response with revision and props. */
3113  SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success"));
3114
3115  SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!"));
3116
3117  for (i = 0; i < inherited_props->nelts; i++)
3118    {
3119      svn_prop_inherited_item_t *iprop =
3120        APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
3121
3122      svn_pool_clear(iterpool);
3123      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
3124                                      iprop->path_or_url));
3125      SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
3126      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
3127                                      iprop->path_or_url));
3128    }
3129
3130  SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))"));
3131  svn_pool_destroy(iterpool);
3132  return SVN_NO_ERROR;
3133}
3134
3135static const svn_ra_svn_cmd_entry_t main_commands[] = {
3136  { "reparent",        reparent },
3137  { "get-latest-rev",  get_latest_rev },
3138  { "get-dated-rev",   get_dated_rev },
3139  { "change-rev-prop", change_rev_prop },
3140  { "change-rev-prop2",change_rev_prop2 },
3141  { "rev-proplist",    rev_proplist },
3142  { "rev-prop",        rev_prop },
3143  { "commit",          commit },
3144  { "get-file",        get_file },
3145  { "get-dir",         get_dir },
3146  { "update",          update },
3147  { "switch",          switch_cmd },
3148  { "status",          status },
3149  { "diff",            diff },
3150  { "get-mergeinfo",   get_mergeinfo },
3151  { "log",             log_cmd },
3152  { "check-path",      check_path },
3153  { "stat",            stat_cmd },
3154  { "get-locations",   get_locations },
3155  { "get-location-segments",   get_location_segments },
3156  { "get-file-revs",   get_file_revs },
3157  { "lock",            lock },
3158  { "lock-many",       lock_many },
3159  { "unlock",          unlock },
3160  { "unlock-many",     unlock_many },
3161  { "get-lock",        get_lock },
3162  { "get-locks",       get_locks },
3163  { "replay",          replay },
3164  { "replay-range",    replay_range },
3165  { "get-deleted-rev", get_deleted_rev },
3166  { "get-iprops",      get_inherited_props },
3167  { NULL }
3168};
3169
3170/* Skip past the scheme part of a URL, including the tunnel specification
3171 * if present.  Return NULL if the scheme part is invalid for ra_svn. */
3172static const char *skip_scheme_part(const char *url)
3173{
3174  if (strncmp(url, "svn", 3) != 0)
3175    return NULL;
3176  url += 3;
3177  if (*url == '+')
3178    url += strcspn(url, ":");
3179  if (strncmp(url, "://", 3) != 0)
3180    return NULL;
3181  return url + 3;
3182}
3183
3184/* Check that PATH is a valid repository path, meaning it doesn't contain any
3185   '..' path segments.
3186   NOTE: This is similar to svn_path_is_backpath_present, but that function
3187   assumes the path separator is '/'.  This function also checks for
3188   segments delimited by the local path separator. */
3189static svn_boolean_t
3190repos_path_valid(const char *path)
3191{
3192  const char *s = path;
3193
3194  while (*s)
3195    {
3196      /* Scan for the end of the segment. */
3197      while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
3198        ++path;
3199
3200      /* Check for '..'. */
3201#ifdef WIN32
3202      /* On Windows, don't allow sequences of more than one character
3203         consisting of just dots and spaces.  Win32 functions treat
3204         paths such as ".. " and "......." inconsistently.  Make sure
3205         no one can escape out of the root. */
3206      if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s))
3207        return FALSE;
3208#else  /* ! WIN32 */
3209      if (path - s == 2 && s[0] == '.' && s[1] == '.')
3210        return FALSE;
3211#endif
3212
3213      /* Skip all separators. */
3214      while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
3215        ++path;
3216      s = path;
3217    }
3218
3219  return TRUE;
3220}
3221
3222/* Look for the repository given by URL, using ROOT as the virtual
3223 * repository root.  If we find one, fill in the repos, fs, cfg,
3224 * repos_url, and fs_path fields of B.  Set B->repos's client
3225 * capabilities to CAPABILITIES, which must be at least as long-lived
3226 * as POOL, and whose elements are SVN_RA_CAPABILITY_*.
3227 */
3228static svn_error_t *find_repos(const char *url, const char *root,
3229                               server_baton_t *b,
3230                               svn_ra_svn_conn_t *conn,
3231                               const apr_array_header_t *capabilities,
3232                               apr_pool_t *pool)
3233{
3234  const char *path, *full_path, *repos_root, *fs_path, *hooks_env;
3235  svn_stringbuf_t *url_buf;
3236
3237  /* Skip past the scheme and authority part. */
3238  path = skip_scheme_part(url);
3239  if (path == NULL)
3240    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
3241                             "Non-svn URL passed to svn server: '%s'", url);
3242
3243  if (! b->vhost)
3244    {
3245      path = strchr(path, '/');
3246      if (path == NULL)
3247        path = "";
3248    }
3249  path = svn_relpath_canonicalize(path, pool);
3250  path = svn_path_uri_decode(path, pool);
3251
3252  /* Ensure that it isn't possible to escape the root by disallowing
3253     '..' segments. */
3254  if (!repos_path_valid(path))
3255    return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
3256                            "Couldn't determine repository path");
3257
3258  /* Join the server-configured root with the client path. */
3259  full_path = svn_dirent_join(svn_dirent_canonicalize(root, pool),
3260                              path, pool);
3261
3262  /* Search for a repository in the full path. */
3263  repos_root = svn_repos_find_root_path(full_path, pool);
3264  if (!repos_root)
3265    return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
3266                             "No repository found in '%s'", url);
3267
3268  /* Open the repository and fill in b with the resulting information. */
3269  SVN_ERR(svn_repos_open2(&b->repos, repos_root, b->fs_config, pool));
3270  SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities));
3271  b->fs = svn_repos_fs(b->repos);
3272  fs_path = full_path + strlen(repos_root);
3273  b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool);
3274  url_buf = svn_stringbuf_create(url, pool);
3275  svn_path_remove_components(url_buf,
3276                             svn_path_component_count(b->fs_path->data));
3277  b->repos_url = url_buf->data;
3278  b->authz_repos_name = svn_dirent_is_child(root, repos_root, pool);
3279  if (b->authz_repos_name == NULL)
3280    b->repos_name = svn_dirent_basename(repos_root, pool);
3281  else
3282    b->repos_name = b->authz_repos_name;
3283  b->repos_name = svn_path_uri_encode(b->repos_name, pool);
3284
3285  /* If the svnserve configuration has not been loaded then load it from the
3286   * repository. */
3287  if (NULL == b->cfg)
3288    {
3289      b->base = svn_repos_conf_dir(b->repos, pool);
3290
3291      SVN_ERR(svn_config_read3(&b->cfg, svn_repos_svnserve_conf(b->repos, pool),
3292                               FALSE, /* must_exist */
3293                               FALSE, /* section_names_case_sensitive */
3294                               FALSE, /* option_names_case_sensitive */
3295                               pool));
3296      SVN_ERR(load_pwdb_config(b, conn, pool));
3297      SVN_ERR(load_authz_config(b, conn, repos_root, pool));
3298    }
3299  /* svnserve.conf has been loaded via the --config-file option so need
3300   * to load pwdb and authz. */
3301  else
3302    {
3303      SVN_ERR(load_pwdb_config(b, conn, pool));
3304      SVN_ERR(load_authz_config(b, conn, repos_root, pool));
3305    }
3306
3307#ifdef SVN_HAVE_SASL
3308  /* Should we use Cyrus SASL? */
3309  SVN_ERR(svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
3310                              SVN_CONFIG_OPTION_USE_SASL, FALSE));
3311#endif
3312
3313  /* Use the repository UUID as the default realm. */
3314  SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
3315  svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
3316                 SVN_CONFIG_OPTION_REALM, b->realm);
3317
3318  /* Make sure it's possible for the client to authenticate.  Note
3319     that this doesn't take into account any authz configuration read
3320     above, because we can't know about access it grants until paths
3321     are given by the client. */
3322  if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
3323      && (get_access(b, AUTHENTICATED) == NO_ACCESS
3324          || (!b->tunnel_user && !b->pwdb && !b->use_sasl)))
3325    return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3326                                 "No access allowed to this repository",
3327                                 b, conn, pool);
3328
3329  /* Configure hook script environment variables. */
3330  svn_config_get(b->cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL,
3331                 SVN_CONFIG_OPTION_HOOKS_ENV, NULL);
3332  if (hooks_env)
3333    hooks_env = svn_dirent_internal_style(hooks_env, pool);
3334  SVN_ERR(svn_repos_hooks_setenv(b->repos, hooks_env, pool));
3335
3336  return SVN_NO_ERROR;
3337}
3338
3339/* Compute the authentication name EXTERNAL should be able to get, if any. */
3340static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
3341{
3342  /* Only offer EXTERNAL for connections tunneled over a login agent. */
3343  if (!params->tunnel)
3344    return NULL;
3345
3346  /* If a tunnel user was provided on the command line, use that. */
3347  if (params->tunnel_user)
3348    return params->tunnel_user;
3349
3350  return svn_user_get_name(pool);
3351}
3352
3353static void
3354fs_warning_func(void *baton, svn_error_t *err)
3355{
3356  fs_warning_baton_t *b = baton;
3357  log_server_error(err, b->server, b->conn, b->pool);
3358  /* TODO: Keep log_pool in the server baton, cleared after every log? */
3359  svn_pool_clear(b->pool);
3360}
3361
3362/* Return the normalized repository-relative path for the given PATH
3363 * (may be a URL, full path or relative path) and fs contained in the
3364 * server baton BATON. Allocate the result in POOL.
3365 */
3366static const char *
3367get_normalized_repo_rel_path(void *baton,
3368                             const char *path,
3369                             apr_pool_t *pool)
3370{
3371  server_baton_t *sb = baton;
3372
3373  if (svn_path_is_url(path))
3374    {
3375      /* This is a copyfrom URL. */
3376      path = svn_uri_skip_ancestor(sb->repos_url, path, pool);
3377      path = svn_fspath__canonicalize(path, pool);
3378    }
3379  else
3380    {
3381      /* This is a base-relative path. */
3382      if ((path)[0] != '/')
3383        /* Get an absolute path for use in the FS. */
3384        path = svn_fspath__join(sb->fs_path->data, path, pool);
3385    }
3386
3387  return path;
3388}
3389
3390/* Get the revision root for REVISION in fs given by server baton BATON
3391 * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM.
3392 * Use POOL for allocations.
3393 */
3394static svn_error_t *
3395get_revision_root(svn_fs_root_t **fs_root,
3396                  void *baton,
3397                  svn_revnum_t revision,
3398                  apr_pool_t *pool)
3399{
3400  server_baton_t *sb = baton;
3401
3402  if (!SVN_IS_VALID_REVNUM(revision))
3403    SVN_ERR(svn_fs_youngest_rev(&revision, sb->fs, pool));
3404
3405  SVN_ERR(svn_fs_revision_root(fs_root, sb->fs, revision, pool));
3406
3407  return SVN_NO_ERROR;
3408}
3409
3410static svn_error_t *
3411fetch_props_func(apr_hash_t **props,
3412                 void *baton,
3413                 const char *path,
3414                 svn_revnum_t base_revision,
3415                 apr_pool_t *result_pool,
3416                 apr_pool_t *scratch_pool)
3417{
3418  svn_fs_root_t *fs_root;
3419  svn_error_t *err;
3420
3421  path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3422  SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3423
3424  err = svn_fs_node_proplist(props, fs_root, path, result_pool);
3425  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3426    {
3427      svn_error_clear(err);
3428      *props = apr_hash_make(result_pool);
3429      return SVN_NO_ERROR;
3430    }
3431  else if (err)
3432    return svn_error_trace(err);
3433
3434  return SVN_NO_ERROR;
3435}
3436
3437static svn_error_t *
3438fetch_kind_func(svn_node_kind_t *kind,
3439                void *baton,
3440                const char *path,
3441                svn_revnum_t base_revision,
3442                apr_pool_t *scratch_pool)
3443{
3444  svn_fs_root_t *fs_root;
3445
3446  path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3447  SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3448
3449  SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
3450
3451  return SVN_NO_ERROR;
3452}
3453
3454static svn_error_t *
3455fetch_base_func(const char **filename,
3456                void *baton,
3457                const char *path,
3458                svn_revnum_t base_revision,
3459                apr_pool_t *result_pool,
3460                apr_pool_t *scratch_pool)
3461{
3462  svn_stream_t *contents;
3463  svn_stream_t *file_stream;
3464  const char *tmp_filename;
3465  svn_fs_root_t *fs_root;
3466  svn_error_t *err;
3467
3468  path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3469  SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3470
3471  err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
3472  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3473    {
3474      svn_error_clear(err);
3475      *filename = NULL;
3476      return SVN_NO_ERROR;
3477    }
3478  else if (err)
3479    return svn_error_trace(err);
3480  SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
3481                                 svn_io_file_del_on_pool_cleanup,
3482                                 scratch_pool, scratch_pool));
3483  SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
3484
3485  *filename = apr_pstrdup(result_pool, tmp_filename);
3486
3487  return SVN_NO_ERROR;
3488}
3489
3490svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
3491                   apr_pool_t *pool)
3492{
3493  svn_error_t *err, *io_err;
3494  apr_uint64_t ver;
3495  const char *uuid, *client_url, *ra_client_string, *client_string;
3496  apr_array_header_t *caplist, *cap_words;
3497  server_baton_t b;
3498  fs_warning_baton_t warn_baton;
3499  svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(pool);
3500
3501  b.tunnel = params->tunnel;
3502  b.tunnel_user = get_tunnel_user(params, pool);
3503  b.read_only = params->read_only;
3504  b.user = NULL;
3505  b.username_case = params->username_case;
3506  b.authz_user = NULL;
3507  b.base = params->base;
3508  b.cfg = params->cfg;
3509  b.pwdb = NULL;
3510  b.authzdb = NULL;
3511  b.realm = NULL;
3512  b.log_file = params->log_file;
3513  b.pool = pool;
3514  b.use_sasl = FALSE;
3515  b.vhost = params->vhost;
3516
3517  /* construct FS configuration parameters */
3518  b.fs_config = apr_hash_make(pool);
3519  svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
3520                params->cache_txdeltas ? "1" :"0");
3521  svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
3522                params->cache_fulltexts ? "1" :"0");
3523  svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
3524                params->cache_revprops ? "1" :"0");
3525
3526  /* Send greeting.  We don't support version 1 any more, so we can
3527   * send an empty mechlist. */
3528  if (params->compression_level > 0)
3529    SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwwww)",
3530                                           (apr_uint64_t) 2, (apr_uint64_t) 2,
3531                                           SVN_RA_SVN_CAP_EDIT_PIPELINE,
3532                                           SVN_RA_SVN_CAP_SVNDIFF1,
3533                                           SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3534                                           SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3535                                           SVN_RA_SVN_CAP_DEPTH,
3536                                           SVN_RA_SVN_CAP_LOG_REVPROPS,
3537                                           SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3538                                           SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3539                                           SVN_RA_SVN_CAP_INHERITED_PROPS,
3540                                           SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3541                                           SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3542                                           ));
3543  else
3544    SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwww)",
3545                                           (apr_uint64_t) 2, (apr_uint64_t) 2,
3546                                           SVN_RA_SVN_CAP_EDIT_PIPELINE,
3547                                           SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3548                                           SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3549                                           SVN_RA_SVN_CAP_DEPTH,
3550                                           SVN_RA_SVN_CAP_LOG_REVPROPS,
3551                                           SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3552                                           SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3553                                           SVN_RA_SVN_CAP_INHERITED_PROPS,
3554                                           SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3555                                           SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3556                                           ));
3557
3558  /* Read client response, which we assume to be in version 2 format:
3559   * version, capability list, and client URL; then we do an auth
3560   * request. */
3561  SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "nlc?c(?c)",
3562                                 &ver, &caplist, &client_url,
3563                                 &ra_client_string,
3564                                 &client_string));
3565  if (ver != 2)
3566    return SVN_NO_ERROR;
3567
3568  client_url = svn_uri_canonicalize(client_url, pool);
3569  SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
3570
3571  /* All released versions of Subversion support edit-pipeline,
3572   * so we do not accept connections from clients that do not. */
3573  if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
3574    return SVN_NO_ERROR;
3575
3576  /* find_repos needs the capabilities as a list of words (eventually
3577     they get handed to the start-commit hook).  While we could add a
3578     new interface to re-retrieve them from conn and convert the
3579     result to a list, it's simpler to just convert caplist by hand
3580     here, since we already have it and turning 'svn_ra_svn_item_t's
3581     into 'const char *'s is pretty easy.
3582
3583     We only record capabilities we care about.  The client may report
3584     more (because it doesn't know what the server cares about). */
3585  {
3586    int i;
3587    svn_ra_svn_item_t *item;
3588
3589    cap_words = apr_array_make(pool, 1, sizeof(const char *));
3590    for (i = 0; i < caplist->nelts; i++)
3591      {
3592        item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
3593        /* ra_svn_set_capabilities() already type-checked for us */
3594        if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0)
3595          {
3596            APR_ARRAY_PUSH(cap_words, const char *)
3597              = SVN_RA_CAPABILITY_MERGEINFO;
3598          }
3599        /* Save for operational log. */
3600        if (cap_log->len > 0)
3601          svn_stringbuf_appendcstr(cap_log, " ");
3602        svn_stringbuf_appendcstr(cap_log, item->u.word);
3603      }
3604  }
3605
3606  err = find_repos(client_url, params->root, &b, conn, cap_words, pool);
3607  if (!err)
3608    {
3609      SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
3610      if (current_access(&b) == NO_ACCESS)
3611        err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3612                                   "Not authorized for access",
3613                                   &b, conn, pool);
3614    }
3615  if (err)
3616    {
3617      log_error(err, b.log_file, svn_ra_svn_conn_remote_host(conn),
3618                b.user, NULL, pool);
3619      io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
3620      svn_error_clear(err);
3621      SVN_ERR(io_err);
3622      return svn_ra_svn__flush(conn, pool);
3623    }
3624
3625  /* Log the open. */
3626  if (ra_client_string == NULL || ra_client_string[0] == '\0')
3627    ra_client_string = "-";
3628  else
3629    ra_client_string = svn_path_uri_encode(ra_client_string, pool);
3630  if (client_string == NULL || client_string[0] == '\0')
3631    client_string = "-";
3632  else
3633    client_string = svn_path_uri_encode(client_string, pool);
3634  SVN_ERR(log_command(&b, conn, pool,
3635                      "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s",
3636                      ver, cap_log->data,
3637                      svn_path_uri_encode(b.fs_path->data, pool),
3638                      ra_client_string, client_string));
3639
3640  warn_baton.server = &b;
3641  warn_baton.conn = conn;
3642  warn_baton.pool = svn_pool_create(pool);
3643  svn_fs_set_warning_func(b.fs, fs_warning_func, &warn_baton);
3644
3645  SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
3646
3647  /* We can't claim mergeinfo capability until we know whether the
3648     repository supports mergeinfo (i.e., is not a 1.4 repository),
3649     but we don't get the repository url from the client until after
3650     we've already sent the initial list of server capabilities.  So
3651     we list repository capabilities here, in our first response after
3652     the client has sent the url. */
3653  {
3654    svn_boolean_t supports_mergeinfo;
3655    SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo,
3656                                     SVN_REPOS_CAPABILITY_MERGEINFO, pool));
3657
3658    SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cc(!",
3659                                    "success", uuid, b.repos_url));
3660    if (supports_mergeinfo)
3661      SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO));
3662    SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3663  }
3664
3665  /* Set up editor shims. */
3666  {
3667    svn_delta_shim_callbacks_t *callbacks =
3668                                svn_delta_shim_callbacks_default(pool);
3669
3670    callbacks->fetch_base_func = fetch_base_func;
3671    callbacks->fetch_props_func = fetch_props_func;
3672    callbacks->fetch_kind_func = fetch_kind_func;
3673    callbacks->fetch_baton = &b;
3674
3675    SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks));
3676  }
3677
3678  return svn_ra_svn__handle_commands2(conn, pool, main_commands, &b, FALSE);
3679}
3680