1251881Speter/*
2251881Speter * serve.c :  Functions for serving the Subversion protocol
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter
27251881Speter#include <limits.h> /* for UINT_MAX */
28251881Speter#include <stdarg.h>
29251881Speter
30251881Speter#define APR_WANT_STRFUNC
31251881Speter#include <apr_want.h>
32251881Speter#include <apr_general.h>
33251881Speter#include <apr_lib.h>
34251881Speter#include <apr_strings.h>
35251881Speter
36251881Speter#include "svn_compat.h"
37251881Speter#include "svn_private_config.h"  /* For SVN_PATH_LOCAL_SEPARATOR */
38251881Speter#include "svn_hash.h"
39251881Speter#include "svn_types.h"
40251881Speter#include "svn_string.h"
41251881Speter#include "svn_pools.h"
42251881Speter#include "svn_error.h"
43251881Speter#include "svn_ra.h"              /* for SVN_RA_CAPABILITY_* */
44251881Speter#include "svn_ra_svn.h"
45251881Speter#include "svn_repos.h"
46251881Speter#include "svn_dirent_uri.h"
47251881Speter#include "svn_path.h"
48251881Speter#include "svn_time.h"
49251881Speter#include "svn_config.h"
50251881Speter#include "svn_props.h"
51251881Speter#include "svn_mergeinfo.h"
52251881Speter#include "svn_user.h"
53251881Speter
54251881Speter#include "private/svn_log.h"
55251881Speter#include "private/svn_mergeinfo_private.h"
56251881Speter#include "private/svn_ra_svn_private.h"
57251881Speter#include "private/svn_fspath.h"
58251881Speter
59251881Speter#ifdef HAVE_UNISTD_H
60251881Speter#include <unistd.h>   /* For getpid() */
61251881Speter#endif
62251881Speter
63251881Speter#include "server.h"
64251881Speter
65251881Spetertypedef struct commit_callback_baton_t {
66251881Speter  apr_pool_t *pool;
67251881Speter  svn_revnum_t *new_rev;
68251881Speter  const char **date;
69251881Speter  const char **author;
70251881Speter  const char **post_commit_err;
71251881Speter} commit_callback_baton_t;
72251881Speter
73251881Spetertypedef struct report_driver_baton_t {
74251881Speter  server_baton_t *sb;
75251881Speter  const char *repos_url;  /* Decoded repository URL. */
76251881Speter  void *report_baton;
77251881Speter  svn_error_t *err;
78251881Speter  /* so update() can distinguish checkout from update in logging */
79251881Speter  int entry_counter;
80251881Speter  svn_boolean_t only_empty_entries;
81251881Speter  /* for diff() logging */
82251881Speter  svn_revnum_t *from_rev;
83251881Speter} report_driver_baton_t;
84251881Speter
85251881Spetertypedef struct log_baton_t {
86251881Speter  const char *fs_path;
87251881Speter  svn_ra_svn_conn_t *conn;
88251881Speter  int stack_depth;
89251881Speter} log_baton_t;
90251881Speter
91251881Spetertypedef struct file_revs_baton_t {
92251881Speter  svn_ra_svn_conn_t *conn;
93251881Speter  apr_pool_t *pool;  /* Pool provided in the handler call. */
94251881Speter} file_revs_baton_t;
95251881Speter
96251881Spetertypedef struct fs_warning_baton_t {
97251881Speter  server_baton_t *server;
98251881Speter  svn_ra_svn_conn_t *conn;
99251881Speter  apr_pool_t *pool;
100251881Speter} fs_warning_baton_t;
101251881Speter
102251881Spetertypedef struct authz_baton_t {
103251881Speter  server_baton_t *server;
104251881Speter  svn_ra_svn_conn_t *conn;
105251881Speter} authz_baton_t;
106251881Speter
107251881Speter/* Write LEN bytes of ERRSTR to LOG_FILE with svn_io_file_write(). */
108251881Speterstatic svn_error_t *
109251881Speterlog_write(apr_file_t *log_file, const char *errstr, apr_size_t len,
110251881Speter          apr_pool_t *pool)
111251881Speter{
112251881Speter  return svn_io_file_write(log_file, errstr, &len, pool);
113251881Speter}
114251881Speter
115251881Spetervoid
116251881Speterlog_error(svn_error_t *err, apr_file_t *log_file, const char *remote_host,
117251881Speter          const char *user, const char *repos, apr_pool_t *pool)
118251881Speter{
119251881Speter  const char *timestr, *continuation;
120251881Speter  char errbuf[256];
121251881Speter  /* 8192 from MAX_STRING_LEN in from httpd-2.2.4/include/httpd.h */
122251881Speter  char errstr[8192];
123251881Speter
124251881Speter  if (err == SVN_NO_ERROR)
125251881Speter    return;
126251881Speter
127251881Speter  if (log_file == NULL)
128251881Speter    return;
129251881Speter
130251881Speter  timestr = svn_time_to_cstring(apr_time_now(), pool);
131251881Speter  remote_host = (remote_host ? remote_host : "-");
132251881Speter  user = (user ? user : "-");
133251881Speter  repos = (repos ? repos : "-");
134251881Speter
135251881Speter  continuation = "";
136251881Speter  while (err != NULL)
137251881Speter    {
138251881Speter      const char *message = svn_err_best_message(err, errbuf, sizeof(errbuf));
139251881Speter      /* based on httpd-2.2.4/server/log.c:log_error_core */
140251881Speter      apr_size_t len = apr_snprintf(errstr, sizeof(errstr),
141251881Speter                                    "%" APR_PID_T_FMT
142251881Speter                                    " %s %s %s %s ERR%s %s %ld %d ",
143251881Speter                                    getpid(), timestr, remote_host, user,
144251881Speter                                    repos, continuation,
145251881Speter                                    err->file ? err->file : "-", err->line,
146251881Speter                                    err->apr_err);
147251881Speter
148251881Speter      len += escape_errorlog_item(errstr + len, message,
149251881Speter                                  sizeof(errstr) - len);
150251881Speter      /* Truncate for the terminator (as apr_snprintf does) */
151251881Speter      if (len > sizeof(errstr) - sizeof(APR_EOL_STR)) {
152251881Speter        len = sizeof(errstr) - sizeof(APR_EOL_STR);
153251881Speter      }
154251881Speter      strcpy(errstr + len, APR_EOL_STR);
155251881Speter      len += strlen(APR_EOL_STR);
156251881Speter      svn_error_clear(log_write(log_file, errstr, len, pool));
157251881Speter
158251881Speter      continuation = "-";
159251881Speter      err = err->child;
160251881Speter    }
161251881Speter}
162251881Speter
163251881Speter/* Call log_error with log_file, remote_host, user, and repos
164251881Speter   arguments from SERVER and CONN. */
165251881Speterstatic void
166251881Speterlog_server_error(svn_error_t *err, server_baton_t *server,
167251881Speter                 svn_ra_svn_conn_t *conn, apr_pool_t *pool)
168251881Speter{
169251881Speter  log_error(err, server->log_file, svn_ra_svn_conn_remote_host(conn),
170251881Speter            server->user, server->repos_name, pool);
171251881Speter}
172251881Speter
173251881Speter/* svn_error_create() a new error, log_server_error() it, and
174251881Speter   return it. */
175251881Speterstatic svn_error_t *
176251881Spetererror_create_and_log(apr_status_t apr_err, svn_error_t *child,
177251881Speter                     const char *message, server_baton_t *server,
178251881Speter                     svn_ra_svn_conn_t *conn, apr_pool_t *pool)
179251881Speter{
180251881Speter  svn_error_t *err = svn_error_create(apr_err, child, message);
181251881Speter  log_server_error(err, server, conn, pool);
182251881Speter  return err;
183251881Speter}
184251881Speter
185251881Speter/* Log a failure ERR, transmit ERR back to the client (as part of a
186251881Speter   "failure" notification), consume ERR, and flush the connection. */
187251881Speterstatic svn_error_t *
188251881Speterlog_fail_and_flush(svn_error_t *err, server_baton_t *server,
189251881Speter                   svn_ra_svn_conn_t *conn, apr_pool_t *pool)
190251881Speter{
191251881Speter  svn_error_t *io_err;
192251881Speter
193251881Speter  log_server_error(err, server, conn, pool);
194251881Speter  io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
195251881Speter  svn_error_clear(err);
196251881Speter  SVN_ERR(io_err);
197251881Speter  return svn_ra_svn__flush(conn, pool);
198251881Speter}
199251881Speter
200251881Speter/* Log a client command. */
201251881Speterstatic svn_error_t *log_command(server_baton_t *b,
202251881Speter                                svn_ra_svn_conn_t *conn,
203251881Speter                                apr_pool_t *pool,
204251881Speter                                const char *fmt, ...)
205251881Speter{
206251881Speter  const char *remote_host, *timestr, *log, *line;
207251881Speter  va_list ap;
208251881Speter  apr_size_t nbytes;
209251881Speter
210251881Speter  if (b->log_file == NULL)
211251881Speter    return SVN_NO_ERROR;
212251881Speter
213251881Speter  remote_host = svn_ra_svn_conn_remote_host(conn);
214251881Speter  timestr = svn_time_to_cstring(apr_time_now(), pool);
215251881Speter
216251881Speter  va_start(ap, fmt);
217251881Speter  log = apr_pvsprintf(pool, fmt, ap);
218251881Speter  va_end(ap);
219251881Speter
220251881Speter  line = apr_psprintf(pool, "%" APR_PID_T_FMT
221251881Speter                      " %s %s %s %s %s" APR_EOL_STR,
222251881Speter                      getpid(), timestr,
223251881Speter                      (remote_host ? remote_host : "-"),
224251881Speter                      (b->user ? b->user : "-"), b->repos_name, log);
225251881Speter  nbytes = strlen(line);
226251881Speter
227251881Speter  return log_write(b->log_file, line, nbytes, pool);
228251881Speter}
229251881Speter
230251881Speter/* Log an authz failure */
231251881Speterstatic svn_error_t *
232251881Speterlog_authz_denied(const char *path,
233251881Speter                 svn_repos_authz_access_t required,
234251881Speter                 server_baton_t *b,
235251881Speter                 svn_ra_svn_conn_t *conn,
236251881Speter                 apr_pool_t *pool)
237251881Speter{
238251881Speter  const char *timestr, *remote_host, *line;
239251881Speter
240251881Speter  if (b->log_file == NULL)
241251881Speter    return SVN_NO_ERROR;
242251881Speter
243251881Speter  if (!b->user)
244251881Speter    return SVN_NO_ERROR;
245251881Speter
246251881Speter  timestr = svn_time_to_cstring(apr_time_now(), pool);
247251881Speter  remote_host = svn_ra_svn_conn_remote_host(conn);
248251881Speter
249251881Speter  line = apr_psprintf(pool, "%" APR_PID_T_FMT
250251881Speter                      " %s %s %s %s Authorization Failed %s%s %s" APR_EOL_STR,
251251881Speter                      getpid(), timestr,
252251881Speter                      (remote_host ? remote_host : "-"),
253251881Speter                      (b->user ? b->user : "-"),
254251881Speter                      b->repos_name,
255251881Speter                      (required & svn_authz_recursive ? "recursive " : ""),
256251881Speter                      (required & svn_authz_write ? "write" : "read"),
257251881Speter                      (path && path[0] ? path : "/"));
258251881Speter
259251881Speter  return log_write(b->log_file, line, strlen(line), pool);
260251881Speter}
261251881Speter
262251881Speter
263251881Spetersvn_error_t *load_pwdb_config(server_baton_t *server,
264251881Speter                              svn_ra_svn_conn_t *conn,
265251881Speter                              apr_pool_t *pool)
266251881Speter{
267251881Speter  const char *pwdb_path;
268251881Speter  svn_error_t *err;
269251881Speter
270251881Speter  svn_config_get(server->cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL,
271251881Speter                 SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
272251881Speter
273251881Speter  server->pwdb = NULL;
274251881Speter  if (pwdb_path)
275251881Speter    {
276251881Speter      pwdb_path = svn_dirent_internal_style(pwdb_path, pool);
277251881Speter      pwdb_path = svn_dirent_join(server->base, pwdb_path, pool);
278251881Speter
279251881Speter      err = svn_config_read3(&server->pwdb, pwdb_path, TRUE,
280251881Speter                             FALSE, FALSE, pool);
281251881Speter      if (err)
282251881Speter        {
283251881Speter          log_server_error(err, server, conn, pool);
284251881Speter
285251881Speter          /* Because it may be possible to read the pwdb file with some
286251881Speter             access methods and not others, ignore errors reading the pwdb
287251881Speter             file and just don't present password authentication as an
288251881Speter             option.  Also, some authentications (e.g. --tunnel) can
289251881Speter             proceed without it anyway.
290251881Speter
291251881Speter             ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked
292251881Speter             ### for here.  That seems to have been introduced in r856914,
293251881Speter             ### and only in r870942 was the APR_EACCES check introduced. */
294251881Speter          if (err->apr_err != SVN_ERR_BAD_FILENAME
295251881Speter              && ! APR_STATUS_IS_EACCES(err->apr_err))
296251881Speter            {
297251881Speter                /* Now that we've logged the error, clear it and return a
298251881Speter                 * nice, generic error to the user:
299251881Speter                 * http://subversion.tigris.org/issues/show_bug.cgi?id=2271 */
300251881Speter                svn_error_clear(err);
301251881Speter                return svn_error_create(SVN_ERR_AUTHN_FAILED, NULL, NULL);
302251881Speter            }
303251881Speter          else
304251881Speter            /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */
305251881Speter            svn_error_clear(err);
306251881Speter        }
307251881Speter    }
308251881Speter
309251881Speter  return SVN_NO_ERROR;
310251881Speter}
311251881Speter
312251881Speter/* Canonicalize *ACCESS_FILE based on the type of argument.  Results are
313251881Speter * placed in *ACCESS_FILE.  SERVER baton is used to convert relative paths to
314251881Speter * absolute paths rooted at the server root.  REPOS_ROOT is used to calculate
315251881Speter * an absolute URL for repos-relative URLs. */
316251881Speterstatic svn_error_t *
317251881Spetercanonicalize_access_file(const char **access_file, server_baton_t *server,
318251881Speter                         const char *repos_root, apr_pool_t *pool)
319251881Speter{
320251881Speter  if (svn_path_is_url(*access_file))
321251881Speter    {
322251881Speter      *access_file = svn_uri_canonicalize(*access_file, pool);
323251881Speter    }
324251881Speter  else if (svn_path_is_repos_relative_url(*access_file))
325251881Speter    {
326251881Speter      const char *repos_root_url;
327251881Speter
328251881Speter      SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_root_url, repos_root,
329251881Speter                                               pool));
330251881Speter      SVN_ERR(svn_path_resolve_repos_relative_url(access_file, *access_file,
331251881Speter                                                  repos_root_url, pool));
332251881Speter      *access_file = svn_uri_canonicalize(*access_file, pool);
333251881Speter    }
334251881Speter  else
335251881Speter    {
336251881Speter      *access_file = svn_dirent_internal_style(*access_file, pool);
337251881Speter      *access_file = svn_dirent_join(server->base, *access_file, pool);
338251881Speter    }
339251881Speter
340251881Speter  return SVN_NO_ERROR;
341251881Speter}
342251881Speter
343251881Spetersvn_error_t *load_authz_config(server_baton_t *server,
344251881Speter                               svn_ra_svn_conn_t *conn,
345251881Speter                               const char *repos_root,
346251881Speter                               apr_pool_t *pool)
347251881Speter{
348251881Speter  const char *authzdb_path;
349251881Speter  const char *groupsdb_path;
350251881Speter  svn_error_t *err;
351251881Speter
352251881Speter  /* Read authz configuration. */
353251881Speter  svn_config_get(server->cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
354251881Speter                 SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
355251881Speter
356251881Speter  svn_config_get(server->cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL,
357251881Speter                 SVN_CONFIG_OPTION_GROUPS_DB, NULL);
358251881Speter
359251881Speter  if (authzdb_path)
360251881Speter    {
361251881Speter      const char *case_force_val;
362251881Speter
363251881Speter      /* Canonicalize and add the base onto the authzdb_path (if needed). */
364251881Speter      err = canonicalize_access_file(&authzdb_path, server,
365251881Speter                                     repos_root, pool);
366251881Speter
367251881Speter      /* Same for the groupsdb_path if it is present. */
368251881Speter      if (groupsdb_path && !err)
369251881Speter        err = canonicalize_access_file(&groupsdb_path, server,
370251881Speter                                       repos_root, pool);
371251881Speter
372251881Speter      if (!err)
373251881Speter        err = svn_repos_authz_read2(&server->authzdb, authzdb_path,
374251881Speter                                    groupsdb_path, TRUE, pool);
375251881Speter
376251881Speter      if (err)
377251881Speter        {
378251881Speter          log_server_error(err, server, conn, pool);
379251881Speter          svn_error_clear(err);
380251881Speter          return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, NULL);
381251881Speter        }
382251881Speter
383251881Speter      /* Are we going to be case-normalizing usernames when we consult
384251881Speter       * this authz file? */
385251881Speter      svn_config_get(server->cfg, &case_force_val, SVN_CONFIG_SECTION_GENERAL,
386251881Speter                     SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL);
387251881Speter      if (case_force_val)
388251881Speter        {
389251881Speter          if (strcmp(case_force_val, "upper") == 0)
390251881Speter            server->username_case = CASE_FORCE_UPPER;
391251881Speter          else if (strcmp(case_force_val, "lower") == 0)
392251881Speter            server->username_case = CASE_FORCE_LOWER;
393251881Speter          else
394251881Speter            server->username_case = CASE_ASIS;
395251881Speter        }
396251881Speter    }
397251881Speter  else
398251881Speter    {
399251881Speter      server->authzdb = NULL;
400251881Speter      server->username_case = CASE_ASIS;
401251881Speter    }
402251881Speter
403251881Speter  return SVN_NO_ERROR;
404251881Speter}
405251881Speter
406251881Speter/* Set *FS_PATH to the portion of URL that is the path within the
407251881Speter   repository, if URL is inside REPOS_URL (if URL is not inside
408251881Speter   REPOS_URL, then error, with the effect on *FS_PATH undefined).
409251881Speter
410251881Speter   If the resultant fs path would be the empty string (i.e., URL and
411251881Speter   REPOS_URL are the same), then set *FS_PATH to "/".
412251881Speter
413251881Speter   Assume that REPOS_URL and URL are already URI-decoded. */
414251881Speterstatic svn_error_t *get_fs_path(const char *repos_url, const char *url,
415251881Speter                                const char **fs_path)
416251881Speter{
417251881Speter  apr_size_t len;
418251881Speter
419251881Speter  len = strlen(repos_url);
420251881Speter  if (strncmp(url, repos_url, len) != 0)
421251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
422251881Speter                             "'%s' is not the same repository as '%s'",
423251881Speter                             url, repos_url);
424251881Speter  *fs_path = url + len;
425251881Speter  if (! **fs_path)
426251881Speter    *fs_path = "/";
427251881Speter
428251881Speter  return SVN_NO_ERROR;
429251881Speter}
430251881Speter
431251881Speter/* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
432251881Speter
433251881Speter/* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else
434251881Speter   converts it to lower case. */
435251881Speterstatic void convert_case(char *text, svn_boolean_t to_uppercase)
436251881Speter{
437251881Speter  char *c = text;
438251881Speter  while (*c)
439251881Speter    {
440251881Speter      *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
441251881Speter      ++c;
442251881Speter    }
443251881Speter}
444251881Speter
445251881Speter/* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
446251881Speter   the user described in BATON according to the authz rules in BATON.
447251881Speter   Use POOL for temporary allocations only.  If no authz rules are
448251881Speter   present in BATON, grant access by default. */
449251881Speterstatic svn_error_t *authz_check_access(svn_boolean_t *allowed,
450251881Speter                                       const char *path,
451251881Speter                                       svn_repos_authz_access_t required,
452251881Speter                                       server_baton_t *b,
453251881Speter                                       svn_ra_svn_conn_t *conn,
454251881Speter                                       apr_pool_t *pool)
455251881Speter{
456251881Speter  /* If authz cannot be performed, grant access.  This is NOT the same
457251881Speter     as the default policy when authz is performed on a path with no
458251881Speter     rules.  In the latter case, the default is to deny access, and is
459251881Speter     set by svn_repos_authz_check_access. */
460251881Speter  if (!b->authzdb)
461251881Speter    {
462251881Speter      *allowed = TRUE;
463251881Speter      return SVN_NO_ERROR;
464251881Speter    }
465251881Speter
466251881Speter  /* If the authz request is for the empty path (ie. ""), replace it
467251881Speter     with the root path.  This happens because of stripping done at
468251881Speter     various levels in svnserve that remove the leading / on an
469251881Speter     absolute path. Passing such a malformed path to the authz
470251881Speter     routines throws them into an infinite loop and makes them miss
471251881Speter     ACLs. */
472251881Speter  if (path)
473251881Speter    path = svn_fspath__canonicalize(path, pool);
474251881Speter
475251881Speter  /* If we have a username, and we've not yet used it + any username
476251881Speter     case normalization that might be requested to determine "the
477251881Speter     username we used for authz purposes", do so now. */
478251881Speter  if (b->user && (! b->authz_user))
479251881Speter    {
480251881Speter      char *authz_user = apr_pstrdup(b->pool, b->user);
481251881Speter      if (b->username_case == CASE_FORCE_UPPER)
482251881Speter        convert_case(authz_user, TRUE);
483251881Speter      else if (b->username_case == CASE_FORCE_LOWER)
484251881Speter        convert_case(authz_user, FALSE);
485251881Speter      b->authz_user = authz_user;
486251881Speter    }
487251881Speter
488251881Speter  SVN_ERR(svn_repos_authz_check_access(b->authzdb, b->authz_repos_name,
489251881Speter                                       path, b->authz_user, required,
490251881Speter                                       allowed, pool));
491251881Speter  if (!*allowed)
492251881Speter    SVN_ERR(log_authz_denied(path, required, b, conn, pool));
493251881Speter
494251881Speter  return SVN_NO_ERROR;
495251881Speter}
496251881Speter
497251881Speter/* Set *ALLOWED to TRUE if PATH is readable by the user described in
498251881Speter * BATON.  Use POOL for temporary allocations only.  ROOT is not used.
499251881Speter * Implements the svn_repos_authz_func_t interface.
500251881Speter */
501251881Speterstatic svn_error_t *authz_check_access_cb(svn_boolean_t *allowed,
502251881Speter                                          svn_fs_root_t *root,
503251881Speter                                          const char *path,
504251881Speter                                          void *baton,
505251881Speter                                          apr_pool_t *pool)
506251881Speter{
507251881Speter  authz_baton_t *sb = baton;
508251881Speter
509251881Speter  return authz_check_access(allowed, path, svn_authz_read,
510251881Speter                            sb->server, sb->conn, pool);
511251881Speter}
512251881Speter
513251881Speter/* If authz is enabled in the specified BATON, return a read authorization
514251881Speter   function. Otherwise, return NULL. */
515251881Speterstatic svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton)
516251881Speter{
517251881Speter  if (baton->authzdb)
518251881Speter     return authz_check_access_cb;
519251881Speter  return NULL;
520251881Speter}
521251881Speter
522251881Speter/* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
523251881Speter * according to the state in BATON.  Use POOL for temporary
524251881Speter * allocations only.  ROOT is not used.  Implements the
525251881Speter * svn_repos_authz_callback_t interface.
526251881Speter */
527251881Speterstatic svn_error_t *authz_commit_cb(svn_repos_authz_access_t required,
528251881Speter                                    svn_boolean_t *allowed,
529251881Speter                                    svn_fs_root_t *root,
530251881Speter                                    const char *path,
531251881Speter                                    void *baton,
532251881Speter                                    apr_pool_t *pool)
533251881Speter{
534251881Speter  authz_baton_t *sb = baton;
535251881Speter
536251881Speter  return authz_check_access(allowed, path, required,
537251881Speter                            sb->server, sb->conn, pool);
538251881Speter}
539251881Speter
540251881Speter
541251881Speterenum access_type get_access(server_baton_t *b, enum authn_type auth)
542251881Speter{
543251881Speter  const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS :
544251881Speter    SVN_CONFIG_OPTION_ANON_ACCESS;
545251881Speter  const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read";
546251881Speter  enum access_type result;
547251881Speter
548251881Speter  svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def);
549251881Speter  result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
550251881Speter            strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
551251881Speter  return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result;
552251881Speter}
553251881Speter
554251881Speterstatic enum access_type current_access(server_baton_t *b)
555251881Speter{
556251881Speter  return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED);
557251881Speter}
558251881Speter
559251881Speter/* Send authentication mechs for ACCESS_TYPE to the client.  If NEEDS_USERNAME
560251881Speter   is true, don't send anonymous mech even if that would give the desired
561251881Speter   access. */
562251881Speterstatic svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
563251881Speter                               server_baton_t *b, enum access_type required,
564251881Speter                               svn_boolean_t needs_username)
565251881Speter{
566251881Speter  if (!needs_username && get_access(b, UNAUTHENTICATED) >= required)
567251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, "ANONYMOUS"));
568251881Speter  if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required)
569251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, "EXTERNAL"));
570251881Speter  if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
571251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, "CRAM-MD5"));
572251881Speter  return SVN_NO_ERROR;
573251881Speter}
574251881Speter
575251881Speter/* Context for cleanup handler. */
576251881Speterstruct cleanup_fs_access_baton
577251881Speter{
578251881Speter  svn_fs_t *fs;
579251881Speter  apr_pool_t *pool;
580251881Speter};
581251881Speter
582251881Speter/* Pool cleanup handler.  Make sure fs's access_t points to NULL when
583251881Speter   the command pool is destroyed. */
584251881Speterstatic apr_status_t cleanup_fs_access(void *data)
585251881Speter{
586251881Speter  svn_error_t *serr;
587251881Speter  struct cleanup_fs_access_baton *baton = data;
588251881Speter
589251881Speter  serr = svn_fs_set_access(baton->fs, NULL);
590251881Speter  if (serr)
591251881Speter    {
592251881Speter      apr_status_t apr_err = serr->apr_err;
593251881Speter      svn_error_clear(serr);
594251881Speter      return apr_err;
595251881Speter    }
596251881Speter
597251881Speter  return APR_SUCCESS;
598251881Speter}
599251881Speter
600251881Speter
601251881Speter/* Create an svn_fs_access_t in POOL for USER and associate it with
602251881Speter   B's filesystem.  Also, register a cleanup handler with POOL which
603251881Speter   de-associates the svn_fs_access_t from B's filesystem. */
604251881Speterstatic svn_error_t *
605251881Spetercreate_fs_access(server_baton_t *b, apr_pool_t *pool)
606251881Speter{
607251881Speter  svn_fs_access_t *fs_access;
608251881Speter  struct cleanup_fs_access_baton *cleanup_baton;
609251881Speter
610251881Speter  if (!b->user)
611251881Speter    return SVN_NO_ERROR;
612251881Speter
613251881Speter  SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool));
614251881Speter  SVN_ERR(svn_fs_set_access(b->fs, fs_access));
615251881Speter
616251881Speter  cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
617251881Speter  cleanup_baton->pool = pool;
618251881Speter  cleanup_baton->fs = b->fs;
619251881Speter  apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
620251881Speter                            apr_pool_cleanup_null);
621251881Speter
622251881Speter  return SVN_NO_ERROR;
623251881Speter}
624251881Speter
625251881Speter/* Authenticate, once the client has chosen a mechanism and possibly
626251881Speter * sent an initial mechanism token.  On success, set *success to true
627251881Speter * and b->user to the authenticated username (or NULL for anonymous).
628251881Speter * On authentication failure, report failure to the client and set
629251881Speter * *success to FALSE.  On communications failure, return an error.
630251881Speter * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
631251881Speterstatic svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
632251881Speter                         const char *mech, const char *mecharg,
633251881Speter                         server_baton_t *b, enum access_type required,
634251881Speter                         svn_boolean_t needs_username,
635251881Speter                         svn_boolean_t *success)
636251881Speter{
637251881Speter  const char *user;
638251881Speter  *success = FALSE;
639251881Speter
640251881Speter  if (get_access(b, AUTHENTICATED) >= required
641251881Speter      && b->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
642251881Speter    {
643251881Speter      if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0)
644251881Speter        return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure",
645251881Speter                                       "Requested username does not match");
646251881Speter      b->user = b->tunnel_user;
647251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success"));
648251881Speter      *success = TRUE;
649251881Speter      return SVN_NO_ERROR;
650251881Speter    }
651251881Speter
652251881Speter  if (get_access(b, UNAUTHENTICATED) >= required
653251881Speter      && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
654251881Speter    {
655251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success"));
656251881Speter      *success = TRUE;
657251881Speter      return SVN_NO_ERROR;
658251881Speter    }
659251881Speter
660251881Speter  if (get_access(b, AUTHENTICATED) >= required
661251881Speter      && b->pwdb && strcmp(mech, "CRAM-MD5") == 0)
662251881Speter    {
663251881Speter      SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success));
664251881Speter      b->user = apr_pstrdup(b->pool, user);
665251881Speter      return SVN_NO_ERROR;
666251881Speter    }
667251881Speter
668251881Speter  return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure",
669251881Speter                                "Must authenticate with listed mechanism");
670251881Speter}
671251881Speter
672251881Speter/* Perform an authentication request using the built-in SASL implementation. */
673251881Speterstatic svn_error_t *
674251881Speterinternal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
675251881Speter                      server_baton_t *b, enum access_type required,
676251881Speter                      svn_boolean_t needs_username)
677251881Speter{
678251881Speter  svn_boolean_t success;
679251881Speter  const char *mech, *mecharg;
680251881Speter
681251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
682251881Speter  SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
683251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)c)", b->realm));
684251881Speter  do
685251881Speter    {
686251881Speter      SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
687251881Speter      if (!*mech)
688251881Speter        break;
689251881Speter      SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username,
690251881Speter                   &success));
691251881Speter    }
692251881Speter  while (!success);
693251881Speter  return SVN_NO_ERROR;
694251881Speter}
695251881Speter
696251881Speter/* Perform an authentication request in order to get an access level of
697251881Speter * REQUIRED or higher.  Since the client may escape the authentication
698251881Speter * exchange, the caller should check current_access(b) to see if
699251881Speter * authentication succeeded. */
700251881Speterstatic svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
701251881Speter                                 server_baton_t *b, enum access_type required,
702251881Speter                                 svn_boolean_t needs_username)
703251881Speter{
704251881Speter#ifdef SVN_HAVE_SASL
705251881Speter  if (b->use_sasl)
706251881Speter    return cyrus_auth_request(conn, pool, b, required, needs_username);
707251881Speter#endif
708251881Speter
709251881Speter  return internal_auth_request(conn, pool, b, required, needs_username);
710251881Speter}
711251881Speter
712251881Speter/* Send a trivial auth notification on CONN which lists no mechanisms,
713251881Speter * indicating that authentication is unnecessary.  Usually called in
714251881Speter * response to invocation of a svnserve command.
715251881Speter */
716251881Speterstatic svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
717251881Speter                                         apr_pool_t *pool, server_baton_t *b)
718251881Speter{
719251881Speter  return svn_ra_svn__write_cmd_response(conn, pool, "()c", "");
720251881Speter}
721251881Speter
722251881Speter/* Ensure that the client has the REQUIRED access by checking the
723251881Speter * access directives (both blanket and per-directory) in BATON.  If
724251881Speter * PATH is NULL, then only the blanket access configuration will
725251881Speter * impact the result.
726251881Speter *
727251881Speter * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the
728251881Speter * user described in BATON is authenticated and, well, has a username
729251881Speter * assigned to him.
730251881Speter *
731251881Speter * Use POOL for temporary allocations only.
732251881Speter */
733251881Speterstatic svn_boolean_t lookup_access(apr_pool_t *pool,
734251881Speter                                   server_baton_t *baton,
735251881Speter                                   svn_ra_svn_conn_t *conn,
736251881Speter                                   svn_repos_authz_access_t required,
737251881Speter                                   const char *path,
738251881Speter                                   svn_boolean_t needs_username)
739251881Speter{
740251881Speter  enum access_type req = (required & svn_authz_write) ?
741251881Speter    WRITE_ACCESS : READ_ACCESS;
742251881Speter  svn_boolean_t authorized;
743251881Speter  svn_error_t *err;
744251881Speter
745251881Speter  /* Get authz's opinion on the access. */
746251881Speter  err = authz_check_access(&authorized, path, required, baton, conn, pool);
747251881Speter
748251881Speter  /* If an error made lookup fail, deny access. */
749251881Speter  if (err)
750251881Speter    {
751251881Speter      log_server_error(err, baton, conn, pool);
752251881Speter      svn_error_clear(err);
753251881Speter      return FALSE;
754251881Speter    }
755251881Speter
756251881Speter  /* If the required access is blanket-granted AND granted by authz
757251881Speter     AND we already have a username if one is required, then the
758251881Speter     lookup has succeeded. */
759251881Speter  if (current_access(baton) >= req
760251881Speter      && authorized
761251881Speter      && (! needs_username || baton->user))
762251881Speter    return TRUE;
763251881Speter
764251881Speter  return FALSE;
765251881Speter}
766251881Speter
767251881Speter/* Check that the client has the REQUIRED access by consulting the
768251881Speter * authentication and authorization states stored in BATON.  If the
769251881Speter * client does not have the required access credentials, attempt to
770251881Speter * authenticate the client to get that access, using CONN for
771251881Speter * communication.
772251881Speter *
773251881Speter * This function is supposed to be called to handle the authentication
774251881Speter * half of a standard svn protocol reply.  If an error is returned, it
775251881Speter * probably means that the server can terminate the client connection
776251881Speter * with an apologetic error, as it implies an authentication failure.
777251881Speter *
778251881Speter * PATH and NEEDS_USERNAME are passed along to lookup_access, their
779251881Speter * behaviour is documented there.
780251881Speter */
781251881Speterstatic svn_error_t *must_have_access(svn_ra_svn_conn_t *conn,
782251881Speter                                     apr_pool_t *pool,
783251881Speter                                     server_baton_t *b,
784251881Speter                                     svn_repos_authz_access_t required,
785251881Speter                                     const char *path,
786251881Speter                                     svn_boolean_t needs_username)
787251881Speter{
788251881Speter  enum access_type req = (required & svn_authz_write) ?
789251881Speter    WRITE_ACCESS : READ_ACCESS;
790251881Speter
791251881Speter  /* See whether the user already has the required access.  If so,
792251881Speter     nothing needs to be done.  Create the FS access and send a
793251881Speter     trivial auth request. */
794251881Speter  if (lookup_access(pool, b, conn, required, path, needs_username))
795251881Speter    {
796251881Speter      SVN_ERR(create_fs_access(b, pool));
797251881Speter      return trivial_auth_request(conn, pool, b);
798251881Speter    }
799251881Speter
800251881Speter  /* If the required blanket access can be obtained by authenticating,
801251881Speter     try that.  Unfortunately, we can't tell until after
802251881Speter     authentication whether authz will work or not.  We force
803251881Speter     requiring a username because we need one to be able to check
804251881Speter     authz configuration again with a different user credentials than
805251881Speter     the first time round. */
806251881Speter  if (b->user == NULL
807251881Speter      && get_access(b, AUTHENTICATED) >= req
808251881Speter      && (b->tunnel_user || b->pwdb || b->use_sasl))
809251881Speter    SVN_ERR(auth_request(conn, pool, b, req, TRUE));
810251881Speter
811251881Speter  /* Now that an authentication has been done get the new take of
812251881Speter     authz on the request. */
813251881Speter  if (! lookup_access(pool, b, conn, required, path, needs_username))
814251881Speter    return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
815251881Speter                            error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
816251881Speter                                                 NULL, NULL, b, conn, pool),
817251881Speter                            NULL);
818251881Speter
819251881Speter  /* Else, access is granted, and there is much rejoicing. */
820251881Speter  SVN_ERR(create_fs_access(b, pool));
821251881Speter
822251881Speter  return SVN_NO_ERROR;
823251881Speter}
824251881Speter
825251881Speter/* --- REPORTER COMMAND SET --- */
826251881Speter
827251881Speter/* To allow for pipelining, reporter commands have no reponses.  If we
828251881Speter * get an error, we ignore all subsequent reporter commands and return
829251881Speter * the error finish_report, to be handled by the calling command.
830251881Speter */
831251881Speter
832251881Speterstatic svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
833251881Speter                             apr_array_header_t *params, void *baton)
834251881Speter{
835251881Speter  report_driver_baton_t *b = baton;
836251881Speter  const char *path, *lock_token, *depth_word;
837251881Speter  svn_revnum_t rev;
838251881Speter  /* Default to infinity, for old clients that don't send depth. */
839251881Speter  svn_depth_t depth = svn_depth_infinity;
840251881Speter  svn_boolean_t start_empty;
841251881Speter
842251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crb?(?c)?w",
843251881Speter                                 &path, &rev, &start_empty, &lock_token,
844251881Speter                                 &depth_word));
845251881Speter  if (depth_word)
846251881Speter    depth = svn_depth_from_word(depth_word);
847251881Speter  path = svn_relpath_canonicalize(path, pool);
848251881Speter  if (b->from_rev && strcmp(path, "") == 0)
849251881Speter    *b->from_rev = rev;
850251881Speter  if (!b->err)
851251881Speter    b->err = svn_repos_set_path3(b->report_baton, path, rev, depth,
852251881Speter                                 start_empty, lock_token, pool);
853251881Speter  b->entry_counter++;
854251881Speter  if (!start_empty)
855251881Speter    b->only_empty_entries = FALSE;
856251881Speter  return SVN_NO_ERROR;
857251881Speter}
858251881Speter
859251881Speterstatic svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
860251881Speter                                apr_array_header_t *params, void *baton)
861251881Speter{
862251881Speter  report_driver_baton_t *b = baton;
863251881Speter  const char *path;
864251881Speter
865251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
866251881Speter  path = svn_relpath_canonicalize(path, pool);
867251881Speter  if (!b->err)
868251881Speter    b->err = svn_repos_delete_path(b->report_baton, path, pool);
869251881Speter  return SVN_NO_ERROR;
870251881Speter}
871251881Speter
872251881Speterstatic svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
873251881Speter                              apr_array_header_t *params, void *baton)
874251881Speter{
875251881Speter  report_driver_baton_t *b = baton;
876251881Speter  const char *path, *url, *lock_token, *fs_path, *depth_word;
877251881Speter  svn_revnum_t rev;
878251881Speter  svn_boolean_t start_empty;
879251881Speter  /* Default to infinity, for old clients that don't send depth. */
880251881Speter  svn_depth_t depth = svn_depth_infinity;
881251881Speter
882251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccrb?(?c)?w",
883251881Speter                                 &path, &url, &rev, &start_empty,
884251881Speter                                 &lock_token, &depth_word));
885251881Speter
886251881Speter  /* ### WHAT?!  The link path is an absolute URL?!  Didn't see that
887251881Speter     coming...   -- cmpilato  */
888251881Speter  path = svn_relpath_canonicalize(path, pool);
889251881Speter  url = svn_uri_canonicalize(url, pool);
890251881Speter  if (depth_word)
891251881Speter    depth = svn_depth_from_word(depth_word);
892251881Speter  if (!b->err)
893251881Speter    b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool),
894251881Speter                         svn_path_uri_decode(url, pool),
895251881Speter                         &fs_path);
896251881Speter  if (!b->err)
897251881Speter    b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev,
898251881Speter                                  depth, start_empty, lock_token, pool);
899251881Speter  b->entry_counter++;
900251881Speter  return SVN_NO_ERROR;
901251881Speter}
902251881Speter
903251881Speterstatic svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
904251881Speter                                  apr_array_header_t *params, void *baton)
905251881Speter{
906251881Speter  report_driver_baton_t *b = baton;
907251881Speter
908251881Speter  /* No arguments to parse. */
909251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b->sb));
910251881Speter  if (!b->err)
911251881Speter    b->err = svn_repos_finish_report(b->report_baton, pool);
912251881Speter  return SVN_NO_ERROR;
913251881Speter}
914251881Speter
915251881Speterstatic svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
916251881Speter                                 apr_array_header_t *params, void *baton)
917251881Speter{
918251881Speter  report_driver_baton_t *b = baton;
919251881Speter
920251881Speter  /* No arguments to parse. */
921251881Speter  svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
922251881Speter  return SVN_NO_ERROR;
923251881Speter}
924251881Speter
925251881Speterstatic const svn_ra_svn_cmd_entry_t report_commands[] = {
926251881Speter  { "set-path",      set_path },
927251881Speter  { "delete-path",   delete_path },
928251881Speter  { "link-path",     link_path },
929251881Speter  { "finish-report", finish_report, TRUE },
930251881Speter  { "abort-report",  abort_report,  TRUE },
931251881Speter  { NULL }
932251881Speter};
933251881Speter
934251881Speter/* Accept a report from the client, drive the network editor with the
935251881Speter * result, and then write an empty command response.  If there is a
936251881Speter * non-protocol failure, accept_report will abort the edit and return
937251881Speter * a command error to be reported by handle_commands().
938251881Speter *
939251881Speter * If only_empty_entry is not NULL and the report contains only one
940251881Speter * item, and that item is empty, set *only_empty_entry to TRUE, else
941251881Speter * set it to FALSE.
942251881Speter *
943251881Speter * If from_rev is not NULL, set *from_rev to the revision number from
944251881Speter * the set-path on ""; if somehow set-path "" never happens, set
945251881Speter * *from_rev to SVN_INVALID_REVNUM.
946251881Speter */
947251881Speterstatic svn_error_t *accept_report(svn_boolean_t *only_empty_entry,
948251881Speter                                  svn_revnum_t *from_rev,
949251881Speter                                  svn_ra_svn_conn_t *conn, apr_pool_t *pool,
950251881Speter                                  server_baton_t *b, svn_revnum_t rev,
951251881Speter                                  const char *target, const char *tgt_path,
952251881Speter                                  svn_boolean_t text_deltas,
953251881Speter                                  svn_depth_t depth,
954251881Speter                                  svn_boolean_t send_copyfrom_args,
955251881Speter                                  svn_boolean_t ignore_ancestry)
956251881Speter{
957251881Speter  const svn_delta_editor_t *editor;
958251881Speter  void *edit_baton, *report_baton;
959251881Speter  report_driver_baton_t rb;
960251881Speter  svn_error_t *err;
961251881Speter  authz_baton_t ab;
962251881Speter
963251881Speter  ab.server = b;
964251881Speter  ab.conn = conn;
965251881Speter
966251881Speter  /* Make an svn_repos report baton.  Tell it to drive the network editor
967251881Speter   * when the report is complete. */
968251881Speter  svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
969251881Speter  SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev, b->repos,
970251881Speter                                      b->fs_path->data, target, tgt_path,
971251881Speter                                      text_deltas, depth, ignore_ancestry,
972251881Speter                                      send_copyfrom_args,
973251881Speter                                      editor, edit_baton,
974251881Speter                                      authz_check_access_cb_func(b),
975251881Speter                                      &ab, svn_ra_svn_zero_copy_limit(conn),
976251881Speter                                      pool));
977251881Speter
978251881Speter  rb.sb = b;
979251881Speter  rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
980251881Speter  rb.report_baton = report_baton;
981251881Speter  rb.err = NULL;
982251881Speter  rb.entry_counter = 0;
983251881Speter  rb.only_empty_entries = TRUE;
984251881Speter  rb.from_rev = from_rev;
985251881Speter  if (from_rev)
986251881Speter    *from_rev = SVN_INVALID_REVNUM;
987251881Speter  err = svn_ra_svn__handle_commands2(conn, pool, report_commands, &rb, TRUE);
988251881Speter  if (err)
989251881Speter    {
990251881Speter      /* Network or protocol error while handling commands. */
991251881Speter      svn_error_clear(rb.err);
992251881Speter      return err;
993251881Speter    }
994251881Speter  else if (rb.err)
995251881Speter    {
996251881Speter      /* Some failure during the reporting or editing operations. */
997251881Speter      SVN_CMD_ERR(rb.err);
998251881Speter    }
999251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1000251881Speter
1001251881Speter  if (only_empty_entry)
1002251881Speter    *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries;
1003251881Speter
1004251881Speter  return SVN_NO_ERROR;
1005251881Speter}
1006251881Speter
1007251881Speter/* --- MAIN COMMAND SET --- */
1008251881Speter
1009251881Speter/* Write out a list of property diffs.  PROPDIFFS is an array of svn_prop_t
1010251881Speter * values. */
1011251881Speterstatic svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
1012251881Speter                                     apr_pool_t *pool,
1013251881Speter                                     const apr_array_header_t *propdiffs)
1014251881Speter{
1015251881Speter  int i;
1016251881Speter
1017251881Speter  for (i = 0; i < propdiffs->nelts; ++i)
1018251881Speter    {
1019251881Speter      const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
1020251881Speter
1021251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "c(?s)",
1022251881Speter                                      prop->name, prop->value));
1023251881Speter    }
1024251881Speter
1025251881Speter  return SVN_NO_ERROR;
1026251881Speter}
1027251881Speter
1028251881Speter/* Write out a lock to the client. */
1029251881Speterstatic svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
1030251881Speter                               apr_pool_t *pool,
1031251881Speter                               svn_lock_t *lock)
1032251881Speter{
1033251881Speter  const char *cdate, *edate;
1034251881Speter
1035251881Speter  cdate = svn_time_to_cstring(lock->creation_date, pool);
1036251881Speter  edate = lock->expiration_date
1037251881Speter    ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
1038251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
1039251881Speter                                  lock->token, lock->owner, lock->comment,
1040251881Speter                                  cdate, edate));
1041251881Speter
1042251881Speter  return SVN_NO_ERROR;
1043251881Speter}
1044251881Speter
1045251881Speter/* ### This really belongs in libsvn_repos. */
1046251881Speter/* Get the explicit properties and/or inherited properties for a PATH in
1047251881Speter   ROOT, with hardcoded committed-info values. */
1048251881Speterstatic svn_error_t *
1049251881Speterget_props(apr_hash_t **props,
1050251881Speter          apr_array_header_t **iprops,
1051251881Speter          authz_baton_t *b,
1052251881Speter          svn_fs_root_t *root,
1053251881Speter          const char *path,
1054251881Speter          apr_pool_t *pool)
1055251881Speter{
1056251881Speter  /* Get the explicit properties. */
1057251881Speter  if (props)
1058251881Speter    {
1059251881Speter      svn_string_t *str;
1060251881Speter      svn_revnum_t crev;
1061251881Speter      const char *cdate, *cauthor, *uuid;
1062251881Speter
1063251881Speter      SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
1064251881Speter
1065251881Speter      /* Hardcode the values for the committed revision, date, and author. */
1066251881Speter      SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
1067251881Speter                                           path, pool));
1068251881Speter      str = svn_string_create(apr_psprintf(pool, "%ld", crev),
1069251881Speter                              pool);
1070251881Speter      svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, str);
1071251881Speter      str = (cdate) ? svn_string_create(cdate, pool) : NULL;
1072251881Speter      svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, str);
1073251881Speter      str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
1074251881Speter      svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, str);
1075251881Speter
1076251881Speter      /* Hardcode the values for the UUID. */
1077251881Speter      SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
1078251881Speter      str = (uuid) ? svn_string_create(uuid, pool) : NULL;
1079251881Speter      svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, str);
1080251881Speter    }
1081251881Speter
1082251881Speter  /* Get any inherited properties the user is authorized to. */
1083251881Speter  if (iprops)
1084251881Speter    {
1085251881Speter      SVN_ERR(svn_repos_fs_get_inherited_props(
1086251881Speter                iprops, root, path, NULL,
1087251881Speter                authz_check_access_cb_func(b->server),
1088251881Speter                b, pool, pool));
1089251881Speter    }
1090251881Speter
1091251881Speter  return SVN_NO_ERROR;
1092251881Speter}
1093251881Speter
1094251881Speter/* Set BATON->FS_PATH for the repository URL found in PARAMS. */
1095251881Speterstatic svn_error_t *reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1096251881Speter                             apr_array_header_t *params, void *baton)
1097251881Speter{
1098251881Speter  server_baton_t *b = baton;
1099251881Speter  const char *url;
1100251881Speter  const char *fs_path;
1101251881Speter
1102251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &url));
1103251881Speter  url = svn_uri_canonicalize(url, pool);
1104251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1105251881Speter  SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1106251881Speter                          svn_path_uri_decode(url, pool),
1107251881Speter                          &fs_path));
1108251881Speter  SVN_ERR(log_command(b, conn, pool, "%s", svn_log__reparent(fs_path, pool)));
1109251881Speter  svn_stringbuf_set(b->fs_path, fs_path);
1110251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1111251881Speter  return SVN_NO_ERROR;
1112251881Speter}
1113251881Speter
1114251881Speterstatic svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1115251881Speter                                   apr_array_header_t *params, void *baton)
1116251881Speter{
1117251881Speter  server_baton_t *b = baton;
1118251881Speter  svn_revnum_t rev;
1119251881Speter
1120251881Speter  SVN_ERR(log_command(b, conn, pool, "get-latest-rev"));
1121251881Speter
1122251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1123251881Speter  SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1124251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
1125251881Speter  return SVN_NO_ERROR;
1126251881Speter}
1127251881Speter
1128251881Speterstatic svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1129251881Speter                                  apr_array_header_t *params, void *baton)
1130251881Speter{
1131251881Speter  server_baton_t *b = baton;
1132251881Speter  svn_revnum_t rev;
1133251881Speter  apr_time_t tm;
1134251881Speter  const char *timestr;
1135251881Speter
1136251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &timestr));
1137251881Speter  SVN_ERR(log_command(b, conn, pool, "get-dated-rev %s", timestr));
1138251881Speter
1139251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1140251881Speter  SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
1141251881Speter  SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool));
1142251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
1143251881Speter  return SVN_NO_ERROR;
1144251881Speter}
1145251881Speter
1146251881Speter/* Common logic for change_rev_prop() and change_rev_prop2(). */
1147251881Speterstatic svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn,
1148251881Speter                                       server_baton_t *b,
1149251881Speter                                       svn_revnum_t rev,
1150251881Speter                                       const char *name,
1151251881Speter                                       const svn_string_t *const *old_value_p,
1152251881Speter                                       const svn_string_t *value,
1153251881Speter                                       apr_pool_t *pool)
1154251881Speter{
1155251881Speter  authz_baton_t ab;
1156251881Speter
1157251881Speter  ab.server = b;
1158251881Speter  ab.conn = conn;
1159251881Speter
1160251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
1161251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
1162251881Speter                      svn_log__change_rev_prop(rev, name, pool)));
1163251881Speter  SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repos, rev, b->user,
1164251881Speter                                            name, old_value_p, value,
1165251881Speter                                            TRUE, TRUE,
1166251881Speter                                            authz_check_access_cb_func(b), &ab,
1167251881Speter                                            pool));
1168251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1169251881Speter
1170251881Speter  return SVN_NO_ERROR;
1171251881Speter}
1172251881Speter
1173251881Speterstatic svn_error_t *change_rev_prop2(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1174251881Speter                                     apr_array_header_t *params, void *baton)
1175251881Speter{
1176251881Speter  server_baton_t *b = baton;
1177251881Speter  svn_revnum_t rev;
1178251881Speter  const char *name;
1179251881Speter  svn_string_t *value;
1180251881Speter  const svn_string_t *const *old_value_p;
1181251881Speter  svn_string_t *old_value;
1182251881Speter  svn_boolean_t dont_care;
1183251881Speter
1184251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc(?s)(b?s)",
1185251881Speter                                  &rev, &name, &value,
1186251881Speter                                  &dont_care, &old_value));
1187251881Speter
1188251881Speter  /* Argument parsing. */
1189251881Speter  if (dont_care)
1190251881Speter    old_value_p = NULL;
1191251881Speter  else
1192251881Speter    old_value_p = (const svn_string_t *const *)&old_value;
1193251881Speter
1194251881Speter  /* Input validation. */
1195251881Speter  if (dont_care && old_value)
1196251881Speter    {
1197251881Speter      svn_error_t *err;
1198251881Speter      err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1199251881Speter                             "'previous-value' and 'dont-care' cannot both be "
1200251881Speter                             "set in 'change-rev-prop2' request");
1201251881Speter      return log_fail_and_flush(err, b, conn, pool);
1202251881Speter    }
1203251881Speter
1204251881Speter  /* Do it. */
1205251881Speter  SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool));
1206251881Speter
1207251881Speter  return SVN_NO_ERROR;
1208251881Speter}
1209251881Speter
1210251881Speterstatic svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1211251881Speter                                    apr_array_header_t *params, void *baton)
1212251881Speter{
1213251881Speter  server_baton_t *b = baton;
1214251881Speter  svn_revnum_t rev;
1215251881Speter  const char *name;
1216251881Speter  svn_string_t *value;
1217251881Speter
1218251881Speter  /* Because the revprop value was at one time mandatory, the usual
1219251881Speter     optional element pattern "(?s)" isn't used. */
1220251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc?s", &rev, &name, &value));
1221251881Speter
1222251881Speter  SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool));
1223251881Speter
1224251881Speter  return SVN_NO_ERROR;
1225251881Speter}
1226251881Speter
1227251881Speterstatic svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1228251881Speter                                 apr_array_header_t *params, void *baton)
1229251881Speter{
1230251881Speter  server_baton_t *b = baton;
1231251881Speter  svn_revnum_t rev;
1232251881Speter  apr_hash_t *props;
1233251881Speter  authz_baton_t ab;
1234251881Speter
1235251881Speter  ab.server = b;
1236251881Speter  ab.conn = conn;
1237251881Speter
1238251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
1239251881Speter  SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool)));
1240251881Speter
1241251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1242251881Speter  SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
1243251881Speter                                             authz_check_access_cb_func(b), &ab,
1244251881Speter                                             pool));
1245251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
1246251881Speter  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1247251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1248251881Speter  return SVN_NO_ERROR;
1249251881Speter}
1250251881Speter
1251251881Speterstatic svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1252251881Speter                             apr_array_header_t *params, void *baton)
1253251881Speter{
1254251881Speter  server_baton_t *b = baton;
1255251881Speter  svn_revnum_t rev;
1256251881Speter  const char *name;
1257251881Speter  svn_string_t *value;
1258251881Speter  authz_baton_t ab;
1259251881Speter
1260251881Speter  ab.server = b;
1261251881Speter  ab.conn = conn;
1262251881Speter
1263251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc", &rev, &name));
1264251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
1265251881Speter                      svn_log__rev_prop(rev, name, pool)));
1266251881Speter
1267251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1268251881Speter  SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name,
1269251881Speter                                         authz_check_access_cb_func(b), &ab,
1270251881Speter                                         pool));
1271251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(?s)", value));
1272251881Speter  return SVN_NO_ERROR;
1273251881Speter}
1274251881Speter
1275251881Speterstatic svn_error_t *commit_done(const svn_commit_info_t *commit_info,
1276251881Speter                                void *baton, apr_pool_t *pool)
1277251881Speter{
1278251881Speter  commit_callback_baton_t *ccb = baton;
1279251881Speter
1280251881Speter  *ccb->new_rev = commit_info->revision;
1281251881Speter  *ccb->date = commit_info->date
1282251881Speter    ? apr_pstrdup(ccb->pool, commit_info->date): NULL;
1283251881Speter  *ccb->author = commit_info->author
1284251881Speter    ? apr_pstrdup(ccb->pool, commit_info->author) : NULL;
1285251881Speter  *ccb->post_commit_err = commit_info->post_commit_err
1286251881Speter    ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL;
1287251881Speter  return SVN_NO_ERROR;
1288251881Speter}
1289251881Speter
1290251881Speter/* Add the LOCK_TOKENS (if any) to the filesystem access context,
1291251881Speter * checking path authorizations using the state in SB as we go.
1292251881Speter * LOCK_TOKENS is an array of svn_ra_svn_item_t structs.  Return a
1293251881Speter * client error if LOCK_TOKENS is not a list of lists.  If a lock
1294251881Speter * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
1295251881Speter * to the client.  Use POOL for temporary allocations only.
1296251881Speter */
1297251881Speterstatic svn_error_t *add_lock_tokens(svn_ra_svn_conn_t *conn,
1298251881Speter                                    const apr_array_header_t *lock_tokens,
1299251881Speter                                    server_baton_t *sb,
1300251881Speter                                    apr_pool_t *pool)
1301251881Speter{
1302251881Speter  int i;
1303251881Speter  svn_fs_access_t *fs_access;
1304251881Speter
1305251881Speter  SVN_ERR(svn_fs_get_access(&fs_access, sb->fs));
1306251881Speter
1307251881Speter  /* If there is no access context, nowhere to add the tokens. */
1308251881Speter  if (! fs_access)
1309251881Speter    return SVN_NO_ERROR;
1310251881Speter
1311251881Speter  for (i = 0; i < lock_tokens->nelts; ++i)
1312251881Speter    {
1313251881Speter      const char *path, *token, *full_path;
1314251881Speter      svn_ra_svn_item_t *path_item, *token_item;
1315251881Speter      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i,
1316251881Speter                                               svn_ra_svn_item_t);
1317251881Speter      if (item->kind != SVN_RA_SVN_LIST)
1318251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1319251881Speter                                "Lock tokens aren't a list of lists");
1320251881Speter
1321251881Speter      path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1322251881Speter      if (path_item->kind != SVN_RA_SVN_STRING)
1323251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1324251881Speter                                "Lock path isn't a string");
1325251881Speter
1326251881Speter      token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
1327251881Speter      if (token_item->kind != SVN_RA_SVN_STRING)
1328251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1329251881Speter                                "Lock token isn't a string");
1330251881Speter
1331251881Speter      path = path_item->u.string->data;
1332251881Speter      full_path = svn_fspath__join(sb->fs_path->data,
1333251881Speter                                   svn_relpath_canonicalize(path, pool),
1334251881Speter                                   pool);
1335251881Speter
1336251881Speter      if (! lookup_access(pool, sb, conn, svn_authz_write,
1337251881Speter                          full_path, TRUE))
1338251881Speter        return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
1339251881Speter                                    sb, conn, pool);
1340251881Speter
1341251881Speter      token = token_item->u.string->data;
1342251881Speter      SVN_ERR(svn_fs_access_add_lock_token2(fs_access, path, token));
1343251881Speter    }
1344251881Speter
1345251881Speter  return SVN_NO_ERROR;
1346251881Speter}
1347251881Speter
1348251881Speter/* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
1349251881Speter   LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */
1350251881Speterstatic svn_error_t *unlock_paths(const apr_array_header_t *lock_tokens,
1351251881Speter                                 server_baton_t *sb,
1352251881Speter                                 svn_ra_svn_conn_t *conn,
1353251881Speter                                 apr_pool_t *pool)
1354251881Speter{
1355251881Speter  int i;
1356251881Speter  apr_pool_t *iterpool;
1357251881Speter
1358251881Speter  iterpool = svn_pool_create(pool);
1359251881Speter
1360251881Speter  for (i = 0; i < lock_tokens->nelts; ++i)
1361251881Speter    {
1362251881Speter      svn_ra_svn_item_t *item, *path_item, *token_item;
1363251881Speter      const char *path, *token, *full_path;
1364251881Speter      svn_error_t *err;
1365251881Speter      svn_pool_clear(iterpool);
1366251881Speter
1367251881Speter      item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
1368251881Speter      path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1369251881Speter      token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
1370251881Speter
1371251881Speter      path = path_item->u.string->data;
1372251881Speter      token = token_item->u.string->data;
1373251881Speter
1374251881Speter      full_path = svn_fspath__join(sb->fs_path->data,
1375251881Speter                                   svn_relpath_canonicalize(path, iterpool),
1376251881Speter                                   iterpool);
1377251881Speter
1378251881Speter      /* The lock may have become defunct after the commit, so ignore such
1379251881Speter         errors. */
1380251881Speter      err = svn_repos_fs_unlock(sb->repos, full_path, token,
1381251881Speter                                FALSE, iterpool);
1382251881Speter      log_server_error(err, sb, conn, iterpool);
1383251881Speter      svn_error_clear(err);
1384251881Speter    }
1385251881Speter
1386251881Speter  svn_pool_destroy(iterpool);
1387251881Speter
1388251881Speter  return SVN_NO_ERROR;
1389251881Speter}
1390251881Speter
1391251881Speterstatic svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1392251881Speter                           apr_array_header_t *params, void *baton)
1393251881Speter{
1394251881Speter  server_baton_t *b = baton;
1395251881Speter  const char *log_msg = NULL,
1396251881Speter             *date = NULL,
1397251881Speter             *author = NULL,
1398251881Speter             *post_commit_err = NULL;
1399251881Speter  apr_array_header_t *lock_tokens;
1400251881Speter  svn_boolean_t keep_locks;
1401251881Speter  apr_array_header_t *revprop_list = NULL;
1402251881Speter  apr_hash_t *revprop_table;
1403251881Speter  const svn_delta_editor_t *editor;
1404251881Speter  void *edit_baton;
1405251881Speter  svn_boolean_t aborted;
1406251881Speter  commit_callback_baton_t ccb;
1407251881Speter  svn_revnum_t new_rev;
1408251881Speter  authz_baton_t ab;
1409251881Speter
1410251881Speter  ab.server = b;
1411251881Speter  ab.conn = conn;
1412251881Speter
1413251881Speter  if (params->nelts == 1)
1414251881Speter    {
1415251881Speter      /* Clients before 1.2 don't send lock-tokens, keep-locks,
1416251881Speter         and rev-props fields. */
1417251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &log_msg));
1418251881Speter      lock_tokens = NULL;
1419251881Speter      keep_locks = TRUE;
1420251881Speter      revprop_list = NULL;
1421251881Speter    }
1422251881Speter  else
1423251881Speter    {
1424251881Speter      /* Clients before 1.5 don't send the rev-props field. */
1425251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "clb?l", &log_msg,
1426251881Speter                                      &lock_tokens, &keep_locks,
1427251881Speter                                      &revprop_list));
1428251881Speter    }
1429251881Speter
1430251881Speter  /* The handling for locks is a little problematic, because the
1431251881Speter     protocol won't let us send several auth requests once one has
1432251881Speter     succeeded.  So we request write access and a username before
1433251881Speter     adding tokens (if we have any), and subsequently fail if a lock
1434251881Speter     violates authz. */
1435251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
1436251881Speter                           NULL,
1437251881Speter                           (lock_tokens && lock_tokens->nelts)));
1438251881Speter
1439251881Speter  /* Authorize the lock tokens and give them to the FS if we got
1440251881Speter     any. */
1441251881Speter  if (lock_tokens && lock_tokens->nelts)
1442251881Speter    SVN_CMD_ERR(add_lock_tokens(conn, lock_tokens, b, pool));
1443251881Speter
1444253734Speter  /* Ignore LOG_MSG, per the protocol.  See ra_svn_commit(). */
1445251881Speter  if (revprop_list)
1446251881Speter    SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table));
1447251881Speter  else
1448251881Speter    {
1449251881Speter      revprop_table = apr_hash_make(pool);
1450251881Speter      svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
1451251881Speter                    svn_string_create(log_msg, pool));
1452251881Speter    }
1453251881Speter
1454251881Speter  /* Get author from the baton, making sure clients can't circumvent
1455251881Speter     the authentication via the revision props. */
1456251881Speter  svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
1457251881Speter                b->user ? svn_string_create(b->user, pool) : NULL);
1458251881Speter
1459251881Speter  ccb.pool = pool;
1460251881Speter  ccb.new_rev = &new_rev;
1461251881Speter  ccb.date = &date;
1462251881Speter  ccb.author = &author;
1463251881Speter  ccb.post_commit_err = &post_commit_err;
1464251881Speter  /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
1465251881Speter  SVN_CMD_ERR(svn_repos_get_commit_editor5
1466251881Speter              (&editor, &edit_baton, b->repos, NULL,
1467251881Speter               svn_path_uri_decode(b->repos_url, pool),
1468251881Speter               b->fs_path->data, revprop_table,
1469251881Speter               commit_done, &ccb,
1470251881Speter               authz_commit_cb, &ab, pool));
1471251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1472251881Speter  SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton,
1473251881Speter                                   &aborted, FALSE));
1474251881Speter  if (!aborted)
1475251881Speter    {
1476251881Speter      SVN_ERR(log_command(b, conn, pool, "%s",
1477251881Speter                          svn_log__commit(new_rev, pool)));
1478251881Speter      SVN_ERR(trivial_auth_request(conn, pool, b));
1479251881Speter
1480251881Speter      /* In tunnel mode, deltify before answering the client, because
1481251881Speter         answering may cause the client to terminate the connection
1482251881Speter         and thus kill the server.  But otherwise, deltify after
1483251881Speter         answering the client, to avoid user-visible delay. */
1484251881Speter
1485251881Speter      if (b->tunnel)
1486251881Speter        SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1487251881Speter
1488251881Speter      /* Unlock the paths. */
1489251881Speter      if (! keep_locks && lock_tokens && lock_tokens->nelts)
1490251881Speter        SVN_ERR(unlock_paths(lock_tokens, b, conn, pool));
1491251881Speter
1492251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)",
1493251881Speter                                      new_rev, date, author, post_commit_err));
1494251881Speter
1495251881Speter      if (! b->tunnel)
1496251881Speter        SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1497251881Speter    }
1498251881Speter  return SVN_NO_ERROR;
1499251881Speter}
1500251881Speter
1501251881Speterstatic svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1502251881Speter                             apr_array_header_t *params, void *baton)
1503251881Speter{
1504251881Speter  server_baton_t *b = baton;
1505251881Speter  const char *path, *full_path, *hex_digest;
1506251881Speter  svn_revnum_t rev;
1507251881Speter  svn_fs_root_t *root;
1508251881Speter  svn_stream_t *contents;
1509251881Speter  apr_hash_t *props = NULL;
1510251881Speter  apr_array_header_t *inherited_props;
1511251881Speter  svn_string_t write_str;
1512251881Speter  char buf[4096];
1513251881Speter  apr_size_t len;
1514251881Speter  svn_boolean_t want_props, want_contents;
1515251881Speter  apr_uint64_t wants_inherited_props;
1516251881Speter  svn_checksum_t *checksum;
1517251881Speter  svn_error_t *err, *write_err;
1518251881Speter  int i;
1519251881Speter  authz_baton_t ab;
1520251881Speter
1521251881Speter  ab.server = b;
1522251881Speter  ab.conn = conn;
1523251881Speter
1524251881Speter  /* Parse arguments. */
1525251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?B", &path, &rev,
1526251881Speter                                  &want_props, &want_contents,
1527251881Speter                                  &wants_inherited_props));
1528251881Speter
1529251881Speter  full_path = svn_fspath__join(b->fs_path->data,
1530251881Speter                               svn_relpath_canonicalize(path, pool), pool);
1531251881Speter
1532251881Speter  /* Check authorizations */
1533251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1534251881Speter                           full_path, FALSE));
1535251881Speter
1536251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1537251881Speter    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1538251881Speter
1539251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
1540251881Speter                      svn_log__get_file(full_path, rev,
1541251881Speter                                        want_contents, want_props, pool)));
1542251881Speter
1543251881Speter  /* Fetch the properties and a stream for the contents. */
1544251881Speter  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1545251881Speter  SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
1546251881Speter                                   full_path, TRUE, pool));
1547251881Speter  hex_digest = svn_checksum_to_cstring_display(checksum, pool);
1548251881Speter  if (want_props || wants_inherited_props)
1549251881Speter    SVN_CMD_ERR(get_props(&props, &inherited_props, &ab, root, full_path,
1550251881Speter                          pool));
1551251881Speter  if (want_contents)
1552251881Speter    SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
1553251881Speter
1554251881Speter  /* Send successful command response with revision and props. */
1555251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success",
1556251881Speter                                  hex_digest, rev));
1557251881Speter  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1558251881Speter
1559251881Speter  if (wants_inherited_props)
1560251881Speter    {
1561251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
1562251881Speter
1563251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1564251881Speter      for (i = 0; i < inherited_props->nelts; i++)
1565251881Speter        {
1566251881Speter          svn_prop_inherited_item_t *iprop =
1567251881Speter            APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1568251881Speter
1569251881Speter          svn_pool_clear(iterpool);
1570251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1571251881Speter                                          iprop->path_or_url));
1572251881Speter          SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1573251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1574251881Speter                                          iprop->path_or_url));
1575251881Speter        }
1576251881Speter      svn_pool_destroy(iterpool);
1577251881Speter    }
1578251881Speter
1579251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1580251881Speter
1581251881Speter  /* Now send the file's contents. */
1582251881Speter  if (want_contents)
1583251881Speter    {
1584251881Speter      err = SVN_NO_ERROR;
1585251881Speter      while (1)
1586251881Speter        {
1587251881Speter          len = sizeof(buf);
1588251881Speter          err = svn_stream_read(contents, buf, &len);
1589251881Speter          if (err)
1590251881Speter            break;
1591251881Speter          if (len > 0)
1592251881Speter            {
1593251881Speter              write_str.data = buf;
1594251881Speter              write_str.len = len;
1595251881Speter              SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str));
1596251881Speter            }
1597251881Speter          if (len < sizeof(buf))
1598251881Speter            {
1599251881Speter              err = svn_stream_close(contents);
1600251881Speter              break;
1601251881Speter            }
1602251881Speter        }
1603251881Speter      write_err = svn_ra_svn__write_cstring(conn, pool, "");
1604251881Speter      if (write_err)
1605251881Speter        {
1606251881Speter          svn_error_clear(err);
1607251881Speter          return write_err;
1608251881Speter        }
1609251881Speter      SVN_CMD_ERR(err);
1610251881Speter      SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1611251881Speter    }
1612251881Speter
1613251881Speter  return SVN_NO_ERROR;
1614251881Speter}
1615251881Speter
1616251881Speterstatic svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1617251881Speter                            apr_array_header_t *params, void *baton)
1618251881Speter{
1619251881Speter  server_baton_t *b = baton;
1620251881Speter  const char *path, *full_path;
1621251881Speter  svn_revnum_t rev;
1622251881Speter  apr_hash_t *entries, *props = NULL;
1623251881Speter  apr_array_header_t *inherited_props;
1624251881Speter  apr_hash_index_t *hi;
1625251881Speter  svn_fs_root_t *root;
1626251881Speter  apr_pool_t *subpool;
1627251881Speter  svn_boolean_t want_props, want_contents;
1628251881Speter  apr_uint64_t wants_inherited_props;
1629251881Speter  apr_uint64_t dirent_fields;
1630251881Speter  apr_array_header_t *dirent_fields_list = NULL;
1631251881Speter  svn_ra_svn_item_t *elt;
1632251881Speter  int i;
1633251881Speter  authz_baton_t ab;
1634251881Speter
1635251881Speter  ab.server = b;
1636251881Speter  ab.conn = conn;
1637251881Speter
1638251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?l?B", &path, &rev,
1639251881Speter                                  &want_props, &want_contents,
1640251881Speter                                  &dirent_fields_list,
1641251881Speter                                  &wants_inherited_props));
1642251881Speter
1643251881Speter  if (! dirent_fields_list)
1644251881Speter    {
1645251881Speter      dirent_fields = SVN_DIRENT_ALL;
1646251881Speter    }
1647251881Speter  else
1648251881Speter    {
1649251881Speter      dirent_fields = 0;
1650251881Speter
1651251881Speter      for (i = 0; i < dirent_fields_list->nelts; ++i)
1652251881Speter        {
1653251881Speter          elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t);
1654251881Speter
1655251881Speter          if (elt->kind != SVN_RA_SVN_WORD)
1656251881Speter            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1657251881Speter                                    "Dirent field not a string");
1658251881Speter
1659251881Speter          if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0)
1660251881Speter            dirent_fields |= SVN_DIRENT_KIND;
1661251881Speter          else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0)
1662251881Speter            dirent_fields |= SVN_DIRENT_SIZE;
1663251881Speter          else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0)
1664251881Speter            dirent_fields |= SVN_DIRENT_HAS_PROPS;
1665251881Speter          else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0)
1666251881Speter            dirent_fields |= SVN_DIRENT_CREATED_REV;
1667251881Speter          else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0)
1668251881Speter            dirent_fields |= SVN_DIRENT_TIME;
1669251881Speter          else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0)
1670251881Speter            dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
1671251881Speter        }
1672251881Speter    }
1673251881Speter
1674251881Speter  full_path = svn_fspath__join(b->fs_path->data,
1675251881Speter                               svn_relpath_canonicalize(path, pool), pool);
1676251881Speter
1677251881Speter  /* Check authorizations */
1678251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1679251881Speter                           full_path, FALSE));
1680251881Speter
1681251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1682251881Speter    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1683251881Speter
1684251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
1685251881Speter                      svn_log__get_dir(full_path, rev,
1686251881Speter                                       want_contents, want_props,
1687251881Speter                                       dirent_fields, pool)));
1688251881Speter
1689251881Speter  /* Fetch the root of the appropriate revision. */
1690251881Speter  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1691251881Speter
1692251881Speter  /* Fetch the directory's explicit and/or inherited properties
1693251881Speter     if requested. */
1694251881Speter  if (want_props || wants_inherited_props)
1695251881Speter    SVN_CMD_ERR(get_props(&props, &inherited_props, &ab, root, full_path,
1696251881Speter                          pool));
1697251881Speter
1698251881Speter  /* Begin response ... */
1699251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev));
1700251881Speter  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1701251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!"));
1702251881Speter
1703251881Speter  /* Fetch the directory entries if requested and send them immediately. */
1704251881Speter  if (want_contents)
1705251881Speter    {
1706251881Speter      /* Use epoch for a placeholder for a missing date.  */
1707251881Speter      const char *missing_date = svn_time_to_cstring(0, pool);
1708251881Speter
1709251881Speter      SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
1710251881Speter
1711251881Speter      /* Transform the hash table's FS entries into dirents.  This probably
1712251881Speter       * belongs in libsvn_repos. */
1713251881Speter      subpool = svn_pool_create(pool);
1714251881Speter      for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1715251881Speter        {
1716251881Speter          const char *name = svn__apr_hash_index_key(hi);
1717251881Speter          svn_fs_dirent_t *fsent = svn__apr_hash_index_val(hi);
1718251881Speter          const char *file_path;
1719251881Speter
1720251881Speter          /* The fields in the entry tuple.  */
1721251881Speter          svn_node_kind_t entry_kind = svn_node_none;
1722251881Speter          svn_filesize_t entry_size = 0;
1723251881Speter          svn_boolean_t has_props = FALSE;
1724251881Speter          /* If 'created rev' was not requested, send 0.  We can't use
1725251881Speter           * SVN_INVALID_REVNUM as the tuple field is not optional.
1726251881Speter           * See the email thread on dev@, 2012-03-28, subject
1727251881Speter           * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra",
1728251881Speter           * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */
1729251881Speter          svn_revnum_t created_rev = 0;
1730251881Speter          const char *cdate = NULL;
1731251881Speter          const char *last_author = NULL;
1732251881Speter
1733251881Speter          svn_pool_clear(subpool);
1734251881Speter
1735251881Speter          file_path = svn_fspath__join(full_path, name, subpool);
1736251881Speter          if (! lookup_access(subpool, b, conn, svn_authz_read,
1737251881Speter                              file_path, FALSE))
1738251881Speter            continue;
1739251881Speter
1740251881Speter          if (dirent_fields & SVN_DIRENT_KIND)
1741251881Speter              entry_kind = fsent->kind;
1742251881Speter
1743251881Speter          if (dirent_fields & SVN_DIRENT_SIZE)
1744251881Speter              if (entry_kind != svn_node_dir)
1745251881Speter                SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path,
1746251881Speter                                               subpool));
1747251881Speter
1748251881Speter          if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1749251881Speter            {
1750251881Speter              apr_hash_t *file_props;
1751251881Speter
1752251881Speter              /* has_props */
1753251881Speter              SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
1754251881Speter                                               subpool));
1755251881Speter              has_props = (apr_hash_count(file_props) > 0);
1756251881Speter            }
1757251881Speter
1758251881Speter          if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1759251881Speter              || (dirent_fields & SVN_DIRENT_TIME)
1760251881Speter              || (dirent_fields & SVN_DIRENT_CREATED_REV))
1761251881Speter            {
1762251881Speter              /* created_rev, last_author, time */
1763251881Speter              SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev,
1764251881Speter                                                       &cdate,
1765251881Speter                                                       &last_author,
1766251881Speter                                                       root,
1767251881Speter                                                       file_path,
1768251881Speter                                                       subpool));
1769251881Speter            }
1770251881Speter
1771251881Speter          /* The client does not properly handle a missing CDATE. For
1772251881Speter             interoperability purposes, we must fill in some junk.
1773251881Speter
1774251881Speter             See libsvn_ra_svn/client.c:ra_svn_get_dir()  */
1775251881Speter          if (cdate == NULL)
1776251881Speter            cdate = missing_date;
1777251881Speter
1778251881Speter          /* Send the entry. */
1779251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
1780251881Speter                                          svn_node_kind_to_word(entry_kind),
1781251881Speter                                          (apr_uint64_t) entry_size,
1782251881Speter                                          has_props, created_rev,
1783251881Speter                                          cdate, last_author));
1784251881Speter        }
1785251881Speter      svn_pool_destroy(subpool);
1786251881Speter    }
1787251881Speter
1788251881Speter  if (wants_inherited_props)
1789251881Speter    {
1790251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
1791251881Speter
1792251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1793251881Speter      for (i = 0; i < inherited_props->nelts; i++)
1794251881Speter        {
1795251881Speter          svn_prop_inherited_item_t *iprop =
1796251881Speter            APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1797251881Speter
1798251881Speter          svn_pool_clear(iterpool);
1799251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1800251881Speter                                          iprop->path_or_url));
1801251881Speter          SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1802251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1803251881Speter                                          iprop->path_or_url));
1804251881Speter        }
1805251881Speter      svn_pool_destroy(iterpool);
1806251881Speter    }
1807251881Speter
1808251881Speter  /* Finish response. */
1809251881Speter  return svn_ra_svn__write_tuple(conn, pool, "!))");
1810251881Speter}
1811251881Speter
1812251881Speterstatic svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1813251881Speter                           apr_array_header_t *params, void *baton)
1814251881Speter{
1815251881Speter  server_baton_t *b = baton;
1816251881Speter  svn_revnum_t rev;
1817251881Speter  const char *target, *full_path, *depth_word;
1818251881Speter  svn_boolean_t recurse;
1819251881Speter  apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
1820251881Speter  apr_uint64_t ignore_ancestry; /* Optional; default FALSE */
1821251881Speter  /* Default to unknown.  Old clients won't send depth, but we'll
1822251881Speter     handle that by converting recurse if necessary. */
1823251881Speter  svn_depth_t depth = svn_depth_unknown;
1824251881Speter  svn_boolean_t is_checkout;
1825251881Speter
1826251881Speter  /* Parse the arguments. */
1827251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cb?wB?B", &rev, &target,
1828251881Speter                                  &recurse, &depth_word,
1829251881Speter                                  &send_copyfrom_args, &ignore_ancestry));
1830251881Speter  target = svn_relpath_canonicalize(target, pool);
1831251881Speter
1832251881Speter  if (depth_word)
1833251881Speter    depth = svn_depth_from_word(depth_word);
1834251881Speter  else
1835251881Speter    depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1836251881Speter
1837251881Speter  full_path = svn_fspath__join(b->fs_path->data, target, pool);
1838251881Speter  /* Check authorization and authenticate the user if necessary. */
1839251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
1840251881Speter
1841251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1842251881Speter    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1843251881Speter
1844251881Speter  SVN_ERR(accept_report(&is_checkout, NULL,
1845251881Speter                        conn, pool, b, rev, target, NULL, TRUE,
1846251881Speter                        depth,
1847251881Speter                        (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
1848251881Speter                        (ignore_ancestry == TRUE) /* ignore_ancestry */));
1849251881Speter  if (is_checkout)
1850251881Speter    {
1851251881Speter      SVN_ERR(log_command(b, conn, pool, "%s",
1852251881Speter                          svn_log__checkout(full_path, rev,
1853251881Speter                                            depth, pool)));
1854251881Speter    }
1855251881Speter  else
1856251881Speter    {
1857251881Speter      SVN_ERR(log_command(b, conn, pool, "%s",
1858251881Speter                          svn_log__update(full_path, rev, depth,
1859251881Speter                                          send_copyfrom_args, pool)));
1860251881Speter    }
1861251881Speter
1862251881Speter  return SVN_NO_ERROR;
1863251881Speter}
1864251881Speter
1865251881Speterstatic svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1866251881Speter                               apr_array_header_t *params, void *baton)
1867251881Speter{
1868251881Speter  server_baton_t *b = baton;
1869251881Speter  svn_revnum_t rev;
1870251881Speter  const char *target, *depth_word;
1871251881Speter  const char *switch_url, *switch_path;
1872251881Speter  svn_boolean_t recurse;
1873251881Speter  /* Default to unknown.  Old clients won't send depth, but we'll
1874251881Speter     handle that by converting recurse if necessary. */
1875251881Speter  svn_depth_t depth = svn_depth_unknown;
1876251881Speter  apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
1877251881Speter  apr_uint64_t ignore_ancestry; /* Optional; default TRUE */
1878251881Speter
1879251881Speter  /* Parse the arguments. */
1880251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbc?w?BB", &rev, &target,
1881251881Speter                                  &recurse, &switch_url, &depth_word,
1882251881Speter                                  &send_copyfrom_args, &ignore_ancestry));
1883251881Speter  target = svn_relpath_canonicalize(target, pool);
1884251881Speter  switch_url = svn_uri_canonicalize(switch_url, pool);
1885251881Speter
1886251881Speter  if (depth_word)
1887251881Speter    depth = svn_depth_from_word(depth_word);
1888251881Speter  else
1889251881Speter    depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1890251881Speter
1891251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1892251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1893251881Speter    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1894251881Speter
1895251881Speter  SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1896251881Speter                          svn_path_uri_decode(switch_url, pool),
1897251881Speter                          &switch_path));
1898251881Speter
1899251881Speter  {
1900251881Speter    const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1901251881Speter    SVN_ERR(log_command(b, conn, pool, "%s",
1902251881Speter                        svn_log__switch(full_path, switch_path, rev,
1903251881Speter                                        depth, pool)));
1904251881Speter  }
1905251881Speter
1906251881Speter  return accept_report(NULL, NULL,
1907251881Speter                       conn, pool, b, rev, target, switch_path, TRUE,
1908251881Speter                       depth,
1909251881Speter                       (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
1910251881Speter                       (ignore_ancestry != FALSE) /* ignore_ancestry */);
1911251881Speter}
1912251881Speter
1913251881Speterstatic svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1914251881Speter                           apr_array_header_t *params, void *baton)
1915251881Speter{
1916251881Speter  server_baton_t *b = baton;
1917251881Speter  svn_revnum_t rev;
1918251881Speter  const char *target, *depth_word;
1919251881Speter  svn_boolean_t recurse;
1920251881Speter  /* Default to unknown.  Old clients won't send depth, but we'll
1921251881Speter     handle that by converting recurse if necessary. */
1922251881Speter  svn_depth_t depth = svn_depth_unknown;
1923251881Speter
1924251881Speter  /* Parse the arguments. */
1925251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cb?(?r)?w",
1926251881Speter                                  &target, &recurse, &rev, &depth_word));
1927251881Speter  target = svn_relpath_canonicalize(target, pool);
1928251881Speter
1929251881Speter  if (depth_word)
1930251881Speter    depth = svn_depth_from_word(depth_word);
1931251881Speter  else
1932251881Speter    depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
1933251881Speter
1934251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1935251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1936251881Speter    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1937251881Speter
1938251881Speter  {
1939251881Speter    const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1940251881Speter    SVN_ERR(log_command(b, conn, pool, "%s",
1941251881Speter                        svn_log__status(full_path, rev, depth, pool)));
1942251881Speter  }
1943251881Speter
1944251881Speter  return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
1945251881Speter                       depth, FALSE, FALSE);
1946251881Speter}
1947251881Speter
1948251881Speterstatic svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1949251881Speter                         apr_array_header_t *params, void *baton)
1950251881Speter{
1951251881Speter  server_baton_t *b = baton;
1952251881Speter  svn_revnum_t rev;
1953251881Speter  const char *target, *versus_url, *versus_path, *depth_word;
1954251881Speter  svn_boolean_t recurse, ignore_ancestry;
1955251881Speter  svn_boolean_t text_deltas;
1956251881Speter  /* Default to unknown.  Old clients won't send depth, but we'll
1957251881Speter     handle that by converting recurse if necessary. */
1958251881Speter  svn_depth_t depth = svn_depth_unknown;
1959251881Speter
1960251881Speter  /* Parse the arguments. */
1961251881Speter  if (params->nelts == 5)
1962251881Speter    {
1963251881Speter      /* Clients before 1.4 don't send the text_deltas boolean or depth. */
1964251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
1965251881Speter                                      &recurse, &ignore_ancestry, &versus_url));
1966251881Speter      text_deltas = TRUE;
1967251881Speter      depth_word = NULL;
1968251881Speter    }
1969251881Speter  else
1970251881Speter    {
1971251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbcb?w",
1972251881Speter                                      &rev, &target, &recurse,
1973251881Speter                                      &ignore_ancestry, &versus_url,
1974251881Speter                                      &text_deltas, &depth_word));
1975251881Speter    }
1976251881Speter  target = svn_relpath_canonicalize(target, pool);
1977251881Speter  versus_url = svn_uri_canonicalize(versus_url, pool);
1978251881Speter
1979251881Speter  if (depth_word)
1980251881Speter    depth = svn_depth_from_word(depth_word);
1981251881Speter  else
1982251881Speter    depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1983251881Speter
1984251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1985251881Speter
1986251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1987251881Speter    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1988251881Speter  SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1989251881Speter                          svn_path_uri_decode(versus_url, pool),
1990251881Speter                          &versus_path));
1991251881Speter
1992251881Speter  {
1993251881Speter    const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1994251881Speter    svn_revnum_t from_rev;
1995251881Speter    SVN_ERR(accept_report(NULL, &from_rev,
1996251881Speter                          conn, pool, b, rev, target, versus_path,
1997251881Speter                          text_deltas, depth, FALSE, ignore_ancestry));
1998251881Speter    SVN_ERR(log_command(b, conn, pool, "%s",
1999251881Speter                        svn_log__diff(full_path, from_rev, versus_path,
2000251881Speter                                      rev, depth, ignore_ancestry,
2001251881Speter                                      pool)));
2002251881Speter  }
2003251881Speter  return SVN_NO_ERROR;
2004251881Speter}
2005251881Speter
2006251881Speter/* Regardless of whether a client's capabilities indicate an
2007251881Speter   understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
2008251881Speter   we provide a response.
2009251881Speter
2010251881Speter   ASSUMPTION: When performing a 'merge' with two URLs at different
2011251881Speter   revisions, the client will call this command more than once. */
2012251881Speterstatic svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2013251881Speter                                  apr_array_header_t *params, void *baton)
2014251881Speter{
2015251881Speter  server_baton_t *b = baton;
2016251881Speter  svn_revnum_t rev;
2017251881Speter  apr_array_header_t *paths, *canonical_paths;
2018251881Speter  svn_mergeinfo_catalog_t mergeinfo;
2019251881Speter  int i;
2020251881Speter  apr_hash_index_t *hi;
2021251881Speter  const char *inherit_word;
2022251881Speter  svn_mergeinfo_inheritance_t inherit;
2023251881Speter  svn_boolean_t include_descendants;
2024251881Speter  apr_pool_t *iterpool;
2025251881Speter  authz_baton_t ab;
2026251881Speter
2027251881Speter  ab.server = b;
2028251881Speter  ab.conn = conn;
2029251881Speter
2030251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)wb", &paths, &rev,
2031251881Speter                                  &inherit_word, &include_descendants));
2032251881Speter  inherit = svn_inheritance_from_word(inherit_word);
2033251881Speter
2034251881Speter  /* Canonicalize the paths which mergeinfo has been requested for. */
2035251881Speter  canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2036251881Speter  for (i = 0; i < paths->nelts; i++)
2037251881Speter     {
2038251881Speter        svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2039251881Speter        const char *full_path;
2040251881Speter
2041251881Speter        if (item->kind != SVN_RA_SVN_STRING)
2042251881Speter          return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2043251881Speter                                  _("Path is not a string"));
2044251881Speter        full_path = svn_relpath_canonicalize(item->u.string->data, pool);
2045251881Speter        full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
2046251881Speter        APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
2047251881Speter     }
2048251881Speter
2049251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2050251881Speter                      svn_log__get_mergeinfo(canonical_paths, inherit,
2051251881Speter                                             include_descendants,
2052251881Speter                                             pool)));
2053251881Speter
2054251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2055251881Speter  SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos,
2056251881Speter                                         canonical_paths, rev,
2057251881Speter                                         inherit,
2058251881Speter                                         include_descendants,
2059251881Speter                                         authz_check_access_cb_func(b), &ab,
2060251881Speter                                         pool));
2061251881Speter  SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo,
2062251881Speter                                                    b->fs_path->data, pool));
2063251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2064251881Speter  iterpool = svn_pool_create(pool);
2065251881Speter  for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
2066251881Speter    {
2067251881Speter      const char *key = svn__apr_hash_index_key(hi);
2068251881Speter      svn_mergeinfo_t value = svn__apr_hash_index_val(hi);
2069251881Speter      svn_string_t *mergeinfo_string;
2070251881Speter
2071251881Speter      svn_pool_clear(iterpool);
2072251881Speter
2073251881Speter      SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, value, iterpool));
2074251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cs", key,
2075251881Speter                                      mergeinfo_string));
2076251881Speter    }
2077251881Speter  svn_pool_destroy(iterpool);
2078251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2079251881Speter
2080251881Speter  return SVN_NO_ERROR;
2081251881Speter}
2082251881Speter
2083251881Speter/* Send a log entry to the client. */
2084251881Speterstatic svn_error_t *log_receiver(void *baton,
2085251881Speter                                 svn_log_entry_t *log_entry,
2086251881Speter                                 apr_pool_t *pool)
2087251881Speter{
2088251881Speter  log_baton_t *b = baton;
2089251881Speter  svn_ra_svn_conn_t *conn = b->conn;
2090251881Speter  apr_hash_index_t *h;
2091251881Speter  svn_boolean_t invalid_revnum = FALSE;
2092251881Speter  char action[2];
2093251881Speter  const char *author, *date, *message;
2094251881Speter  apr_uint64_t revprop_count;
2095251881Speter
2096251881Speter  if (log_entry->revision == SVN_INVALID_REVNUM)
2097251881Speter    {
2098251881Speter      /* If the stack depth is zero, we've seen the last revision, so don't
2099251881Speter         send it, just return. */
2100251881Speter      if (b->stack_depth == 0)
2101251881Speter        return SVN_NO_ERROR;
2102251881Speter
2103251881Speter      /* Because the svn protocol won't let us send an invalid revnum, we have
2104251881Speter         to fudge here and send an additional flag. */
2105251881Speter      log_entry->revision = 0;
2106251881Speter      invalid_revnum = TRUE;
2107251881Speter      b->stack_depth--;
2108251881Speter    }
2109251881Speter
2110251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "(!"));
2111251881Speter  if (log_entry->changed_paths2)
2112251881Speter    {
2113251881Speter      for (h = apr_hash_first(pool, log_entry->changed_paths2); h;
2114251881Speter                                                        h = apr_hash_next(h))
2115251881Speter        {
2116251881Speter          const char *path = svn__apr_hash_index_key(h);
2117251881Speter          svn_log_changed_path2_t *change = svn__apr_hash_index_val(h);
2118251881Speter
2119251881Speter          action[0] = change->action;
2120251881Speter          action[1] = '\0';
2121251881Speter          SVN_ERR(svn_ra_svn__write_tuple(
2122251881Speter                      conn, pool, "cw(?cr)(cbb)",
2123251881Speter                      path,
2124251881Speter                      action,
2125251881Speter                      change->copyfrom_path,
2126251881Speter                      change->copyfrom_rev,
2127251881Speter                      svn_node_kind_to_word(change->node_kind),
2128251881Speter                      /* text_modified and props_modified are never unknown */
2129251881Speter                      change->text_modified  == svn_tristate_true,
2130251881Speter                      change->props_modified == svn_tristate_true));
2131251881Speter        }
2132251881Speter    }
2133251881Speter  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
2134251881Speter  svn_compat_log_revprops_clear(log_entry->revprops);
2135251881Speter  if (log_entry->revprops)
2136251881Speter    revprop_count = apr_hash_count(log_entry->revprops);
2137251881Speter  else
2138251881Speter    revprop_count = 0;
2139251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
2140251881Speter                                  log_entry->revision,
2141251881Speter                                  author, date, message,
2142251881Speter                                  log_entry->has_children,
2143251881Speter                                  invalid_revnum, revprop_count));
2144251881Speter  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, log_entry->revprops));
2145251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b",
2146251881Speter                                  log_entry->subtractive_merge));
2147251881Speter
2148251881Speter  if (log_entry->has_children)
2149251881Speter    b->stack_depth++;
2150251881Speter
2151251881Speter  return SVN_NO_ERROR;
2152251881Speter}
2153251881Speter
2154251881Speterstatic svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2155251881Speter                            apr_array_header_t *params, void *baton)
2156251881Speter{
2157251881Speter  svn_error_t *err, *write_err;
2158251881Speter  server_baton_t *b = baton;
2159251881Speter  svn_revnum_t start_rev, end_rev;
2160251881Speter  const char *full_path;
2161251881Speter  svn_boolean_t send_changed_paths, strict_node, include_merged_revisions;
2162251881Speter  apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
2163251881Speter  char *revprop_word;
2164251881Speter  svn_ra_svn_item_t *elt;
2165251881Speter  int i;
2166251881Speter  apr_uint64_t limit, include_merged_revs_param;
2167251881Speter  log_baton_t lb;
2168251881Speter  authz_baton_t ab;
2169251881Speter
2170251881Speter  ab.server = b;
2171251881Speter  ab.conn = conn;
2172251881Speter
2173251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
2174251881Speter                                  &start_rev, &end_rev, &send_changed_paths,
2175251881Speter                                  &strict_node, &limit,
2176251881Speter                                  &include_merged_revs_param,
2177251881Speter                                  &revprop_word, &revprop_items));
2178251881Speter
2179251881Speter  if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2180251881Speter    include_merged_revisions = FALSE;
2181251881Speter  else
2182251881Speter    include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2183251881Speter
2184251881Speter  if (revprop_word == NULL)
2185251881Speter    /* pre-1.5 client */
2186251881Speter    revprops = svn_compat_log_revprops_in(pool);
2187251881Speter  else if (strcmp(revprop_word, "all-revprops") == 0)
2188251881Speter    revprops = NULL;
2189251881Speter  else if (strcmp(revprop_word, "revprops") == 0)
2190251881Speter    {
2191251881Speter      SVN_ERR_ASSERT(revprop_items);
2192251881Speter
2193251881Speter      revprops = apr_array_make(pool, revprop_items->nelts,
2194251881Speter                                sizeof(char *));
2195251881Speter      for (i = 0; i < revprop_items->nelts; i++)
2196251881Speter        {
2197251881Speter          elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
2198251881Speter          if (elt->kind != SVN_RA_SVN_STRING)
2199251881Speter            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2200251881Speter                                    _("Log revprop entry not a string"));
2201251881Speter          APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
2202251881Speter        }
2203251881Speter    }
2204251881Speter  else
2205251881Speter    return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2206251881Speter                             _("Unknown revprop word '%s' in log command"),
2207251881Speter                             revprop_word);
2208251881Speter
2209251881Speter  /* If we got an unspecified number then the user didn't send us anything,
2210251881Speter     so we assume no limit.  If it's larger than INT_MAX then someone is
2211251881Speter     messing with us, since we know the svn client libraries will never send
2212251881Speter     us anything that big, so play it safe and default to no limit. */
2213251881Speter  if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
2214251881Speter    limit = 0;
2215251881Speter
2216251881Speter  full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2217251881Speter  for (i = 0; i < paths->nelts; i++)
2218251881Speter    {
2219251881Speter      elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2220251881Speter      if (elt->kind != SVN_RA_SVN_STRING)
2221251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2222251881Speter                                _("Log path entry not a string"));
2223251881Speter      full_path = svn_relpath_canonicalize(elt->u.string->data, pool),
2224251881Speter      full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
2225251881Speter      APR_ARRAY_PUSH(full_paths, const char *) = full_path;
2226251881Speter    }
2227251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2228251881Speter
2229251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2230251881Speter                      svn_log__log(full_paths, start_rev, end_rev,
2231251881Speter                                   (int) limit, send_changed_paths,
2232251881Speter                                   strict_node, include_merged_revisions,
2233251881Speter                                   revprops, pool)));
2234251881Speter
2235251881Speter  /* Get logs.  (Can't report errors back to the client at this point.) */
2236251881Speter  lb.fs_path = b->fs_path->data;
2237251881Speter  lb.conn = conn;
2238251881Speter  lb.stack_depth = 0;
2239251881Speter  err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
2240251881Speter                            (int) limit, send_changed_paths, strict_node,
2241251881Speter                            include_merged_revisions, revprops,
2242251881Speter                            authz_check_access_cb_func(b), &ab, log_receiver,
2243251881Speter                            &lb, pool);
2244251881Speter
2245251881Speter  write_err = svn_ra_svn__write_word(conn, pool, "done");
2246251881Speter  if (write_err)
2247251881Speter    {
2248251881Speter      svn_error_clear(err);
2249251881Speter      return write_err;
2250251881Speter    }
2251251881Speter  SVN_CMD_ERR(err);
2252251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2253251881Speter  return SVN_NO_ERROR;
2254251881Speter}
2255251881Speter
2256251881Speterstatic svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2257251881Speter                               apr_array_header_t *params, void *baton)
2258251881Speter{
2259251881Speter  server_baton_t *b = baton;
2260251881Speter  svn_revnum_t rev;
2261251881Speter  const char *path, *full_path;
2262251881Speter  svn_fs_root_t *root;
2263251881Speter  svn_node_kind_t kind;
2264251881Speter
2265251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2266251881Speter  full_path = svn_fspath__join(b->fs_path->data,
2267251881Speter                               svn_relpath_canonicalize(path, pool), pool);
2268251881Speter
2269251881Speter  /* Check authorizations */
2270251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2271251881Speter                           full_path, FALSE));
2272251881Speter
2273251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
2274251881Speter    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2275251881Speter
2276251881Speter  SVN_ERR(log_command(b, conn, pool, "check-path %s@%d",
2277251881Speter                      svn_path_uri_encode(full_path, pool), rev));
2278251881Speter
2279251881Speter  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
2280251881Speter  SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
2281251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w",
2282251881Speter                                         svn_node_kind_to_word(kind)));
2283251881Speter  return SVN_NO_ERROR;
2284251881Speter}
2285251881Speter
2286251881Speterstatic svn_error_t *stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2287251881Speter                             apr_array_header_t *params, void *baton)
2288251881Speter{
2289251881Speter  server_baton_t *b = baton;
2290251881Speter  svn_revnum_t rev;
2291251881Speter  const char *path, *full_path, *cdate;
2292251881Speter  svn_fs_root_t *root;
2293251881Speter  svn_dirent_t *dirent;
2294251881Speter
2295251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2296251881Speter  full_path = svn_fspath__join(b->fs_path->data,
2297251881Speter                               svn_relpath_canonicalize(path, pool), pool);
2298251881Speter
2299251881Speter  /* Check authorizations */
2300251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2301251881Speter                           full_path, FALSE));
2302251881Speter
2303251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
2304251881Speter    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2305251881Speter
2306251881Speter  SVN_ERR(log_command(b, conn, pool, "stat %s@%d",
2307251881Speter                      svn_path_uri_encode(full_path, pool), rev));
2308251881Speter
2309251881Speter  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
2310251881Speter  SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
2311251881Speter
2312251881Speter  /* Need to return the equivalent of "(?l)", since that's what the
2313251881Speter     client is reading.  */
2314251881Speter
2315251881Speter  if (dirent == NULL)
2316251881Speter    {
2317251881Speter      SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()"));
2318251881Speter      return SVN_NO_ERROR;
2319251881Speter    }
2320251881Speter
2321251881Speter  cdate = (dirent->time == (time_t) -1) ? NULL
2322251881Speter    : svn_time_to_cstring(dirent->time, pool);
2323251881Speter
2324251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
2325251881Speter                                         svn_node_kind_to_word(dirent->kind),
2326251881Speter                                         (apr_uint64_t) dirent->size,
2327251881Speter                                         dirent->has_props, dirent->created_rev,
2328251881Speter                                         cdate, dirent->last_author));
2329251881Speter
2330251881Speter  return SVN_NO_ERROR;
2331251881Speter}
2332251881Speter
2333251881Speterstatic svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2334251881Speter                                  apr_array_header_t *params, void *baton)
2335251881Speter{
2336251881Speter  svn_error_t *err, *write_err;
2337251881Speter  server_baton_t *b = baton;
2338251881Speter  svn_revnum_t revision;
2339251881Speter  apr_array_header_t *location_revisions, *loc_revs_proto;
2340251881Speter  svn_ra_svn_item_t *elt;
2341251881Speter  int i;
2342251881Speter  const char *relative_path;
2343251881Speter  svn_revnum_t peg_revision;
2344251881Speter  apr_hash_t *fs_locations;
2345251881Speter  const char *abs_path;
2346251881Speter  authz_baton_t ab;
2347251881Speter
2348251881Speter  ab.server = b;
2349251881Speter  ab.conn = conn;
2350251881Speter
2351251881Speter  /* Parse the arguments. */
2352251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crl", &relative_path,
2353251881Speter                                  &peg_revision,
2354251881Speter                                  &loc_revs_proto));
2355251881Speter  relative_path = svn_relpath_canonicalize(relative_path, pool);
2356251881Speter
2357251881Speter  abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
2358251881Speter
2359251881Speter  location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
2360251881Speter                                      sizeof(svn_revnum_t));
2361251881Speter  for (i = 0; i < loc_revs_proto->nelts; i++)
2362251881Speter    {
2363251881Speter      elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
2364251881Speter      if (elt->kind != SVN_RA_SVN_NUMBER)
2365251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2366251881Speter                                "Get-locations location revisions entry "
2367251881Speter                                "not a revision number");
2368251881Speter      revision = (svn_revnum_t)(elt->u.number);
2369251881Speter      APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
2370251881Speter    }
2371251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2372251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2373251881Speter                      svn_log__get_locations(abs_path, peg_revision,
2374251881Speter                                             location_revisions, pool)));
2375251881Speter
2376251881Speter  /* All the parameters are fine - let's perform the query against the
2377251881Speter   * repository. */
2378251881Speter
2379251881Speter  /* We store both err and write_err here, so the client will get
2380251881Speter   * the "done" even if there was an error in fetching the results. */
2381251881Speter
2382251881Speter  err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
2383251881Speter                                       peg_revision, location_revisions,
2384251881Speter                                       authz_check_access_cb_func(b), &ab,
2385251881Speter                                       pool);
2386251881Speter
2387251881Speter  /* Now, write the results to the connection. */
2388251881Speter  if (!err)
2389251881Speter    {
2390251881Speter      if (fs_locations)
2391251881Speter        {
2392251881Speter          apr_hash_index_t *iter;
2393251881Speter
2394251881Speter          for (iter = apr_hash_first(pool, fs_locations); iter;
2395251881Speter              iter = apr_hash_next(iter))
2396251881Speter            {
2397251881Speter              const svn_revnum_t *iter_key = svn__apr_hash_index_key(iter);
2398251881Speter              const char *iter_value = svn__apr_hash_index_val(iter);
2399251881Speter
2400251881Speter              SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc",
2401251881Speter                                              *iter_key, iter_value));
2402251881Speter            }
2403251881Speter        }
2404251881Speter    }
2405251881Speter
2406251881Speter  write_err = svn_ra_svn__write_word(conn, pool, "done");
2407251881Speter  if (write_err)
2408251881Speter    {
2409251881Speter      svn_error_clear(err);
2410251881Speter      return write_err;
2411251881Speter    }
2412251881Speter  SVN_CMD_ERR(err);
2413251881Speter
2414251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2415251881Speter
2416251881Speter  return SVN_NO_ERROR;
2417251881Speter}
2418251881Speter
2419251881Speterstatic svn_error_t *gls_receiver(svn_location_segment_t *segment,
2420251881Speter                                 void *baton,
2421251881Speter                                 apr_pool_t *pool)
2422251881Speter{
2423251881Speter  svn_ra_svn_conn_t *conn = baton;
2424251881Speter  return svn_ra_svn__write_tuple(conn, pool, "rr(?c)",
2425251881Speter                                 segment->range_start,
2426251881Speter                                 segment->range_end,
2427251881Speter                                 segment->path);
2428251881Speter}
2429251881Speter
2430251881Speterstatic svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn,
2431251881Speter                                          apr_pool_t *pool,
2432251881Speter                                          apr_array_header_t *params,
2433251881Speter                                          void *baton)
2434251881Speter{
2435251881Speter  svn_error_t *err, *write_err;
2436251881Speter  server_baton_t *b = baton;
2437251881Speter  svn_revnum_t peg_revision, start_rev, end_rev;
2438251881Speter  const char *relative_path;
2439251881Speter  const char *abs_path;
2440251881Speter  authz_baton_t ab;
2441251881Speter
2442251881Speter  ab.server = b;
2443251881Speter  ab.conn = conn;
2444251881Speter
2445251881Speter  /* Parse the arguments. */
2446251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)(?r)",
2447251881Speter                                  &relative_path, &peg_revision,
2448251881Speter                                  &start_rev, &end_rev));
2449251881Speter  relative_path = svn_relpath_canonicalize(relative_path, pool);
2450251881Speter
2451251881Speter  abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
2452251881Speter
2453251881Speter  if (SVN_IS_VALID_REVNUM(start_rev)
2454251881Speter      && SVN_IS_VALID_REVNUM(end_rev)
2455251881Speter      && (end_rev > start_rev))
2456251881Speter    {
2457251881Speter      err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2458251881Speter                              "Get-location-segments end revision must not be "
2459251881Speter                              "younger than start revision");
2460251881Speter      return log_fail_and_flush(err, b, conn, pool);
2461251881Speter    }
2462251881Speter
2463251881Speter  if (SVN_IS_VALID_REVNUM(peg_revision)
2464251881Speter      && SVN_IS_VALID_REVNUM(start_rev)
2465251881Speter      && (start_rev > peg_revision))
2466251881Speter    {
2467251881Speter      err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2468251881Speter                              "Get-location-segments start revision must not "
2469251881Speter                              "be younger than peg revision");
2470251881Speter      return log_fail_and_flush(err, b, conn, pool);
2471251881Speter    }
2472251881Speter
2473251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2474251881Speter  SVN_ERR(log_command(baton, conn, pool, "%s",
2475251881Speter                      svn_log__get_location_segments(abs_path, peg_revision,
2476251881Speter                                                     start_rev, end_rev,
2477251881Speter                                                     pool)));
2478251881Speter
2479251881Speter  /* All the parameters are fine - let's perform the query against the
2480251881Speter   * repository. */
2481251881Speter
2482251881Speter  /* We store both err and write_err here, so the client will get
2483251881Speter   * the "done" even if there was an error in fetching the results. */
2484251881Speter
2485251881Speter  err = svn_repos_node_location_segments(b->repos, abs_path,
2486251881Speter                                         peg_revision, start_rev, end_rev,
2487251881Speter                                         gls_receiver, (void *)conn,
2488251881Speter                                         authz_check_access_cb_func(b), &ab,
2489251881Speter                                         pool);
2490251881Speter  write_err = svn_ra_svn__write_word(conn, pool, "done");
2491251881Speter  if (write_err)
2492251881Speter    {
2493251881Speter      svn_error_clear(err);
2494251881Speter      return write_err;
2495251881Speter    }
2496251881Speter  SVN_CMD_ERR(err);
2497251881Speter
2498251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2499251881Speter
2500251881Speter  return SVN_NO_ERROR;
2501251881Speter}
2502251881Speter
2503251881Speter/* This implements svn_write_fn_t.  Write LEN bytes starting at DATA to the
2504251881Speter   client as a string. */
2505251881Speterstatic svn_error_t *svndiff_handler(void *baton, const char *data,
2506251881Speter                                    apr_size_t *len)
2507251881Speter{
2508251881Speter  file_revs_baton_t *b = baton;
2509251881Speter  svn_string_t str;
2510251881Speter
2511251881Speter  str.data = data;
2512251881Speter  str.len = *len;
2513251881Speter  return svn_ra_svn__write_string(b->conn, b->pool, &str);
2514251881Speter}
2515251881Speter
2516251881Speter/* This implements svn_close_fn_t.  Mark the end of the data by writing an
2517251881Speter   empty string to the client. */
2518251881Speterstatic svn_error_t *svndiff_close_handler(void *baton)
2519251881Speter{
2520251881Speter  file_revs_baton_t *b = baton;
2521251881Speter
2522251881Speter  SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, ""));
2523251881Speter  return SVN_NO_ERROR;
2524251881Speter}
2525251881Speter
2526251881Speter/* This implements the svn_repos_file_rev_handler_t interface. */
2527251881Speterstatic svn_error_t *file_rev_handler(void *baton, const char *path,
2528251881Speter                                     svn_revnum_t rev, apr_hash_t *rev_props,
2529251881Speter                                     svn_boolean_t merged_revision,
2530251881Speter                                     svn_txdelta_window_handler_t *d_handler,
2531251881Speter                                     void **d_baton,
2532251881Speter                                     apr_array_header_t *prop_diffs,
2533251881Speter                                     apr_pool_t *pool)
2534251881Speter{
2535251881Speter  file_revs_baton_t *frb = baton;
2536251881Speter  svn_stream_t *stream;
2537251881Speter
2538251881Speter  SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!",
2539251881Speter                                  path, rev));
2540251881Speter  SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props));
2541251881Speter  SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!"));
2542251881Speter  SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
2543251881Speter  SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision));
2544251881Speter
2545251881Speter  /* Store the pool for the delta stream. */
2546251881Speter  frb->pool = pool;
2547251881Speter
2548251881Speter  /* Prepare for the delta or just write an empty string. */
2549251881Speter  if (d_handler)
2550251881Speter    {
2551251881Speter      stream = svn_stream_create(baton, pool);
2552251881Speter      svn_stream_set_write(stream, svndiff_handler);
2553251881Speter      svn_stream_set_close(stream, svndiff_close_handler);
2554251881Speter
2555251881Speter      /* If the connection does not support SVNDIFF1 or if we don't want to use
2556251881Speter       * compression, use the non-compressing "version 0" implementation */
2557251881Speter      if (   svn_ra_svn_compression_level(frb->conn) > 0
2558251881Speter          && svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1))
2559251881Speter        svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 1,
2560251881Speter                                svn_ra_svn_compression_level(frb->conn), pool);
2561251881Speter      else
2562251881Speter        svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 0,
2563251881Speter                                svn_ra_svn_compression_level(frb->conn), pool);
2564251881Speter    }
2565251881Speter  else
2566251881Speter    SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, ""));
2567251881Speter
2568251881Speter  return SVN_NO_ERROR;
2569251881Speter}
2570251881Speter
2571251881Speterstatic svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2572251881Speter                                  apr_array_header_t *params, void *baton)
2573251881Speter{
2574251881Speter  server_baton_t *b = baton;
2575251881Speter  svn_error_t *err, *write_err;
2576251881Speter  file_revs_baton_t frb;
2577251881Speter  svn_revnum_t start_rev, end_rev;
2578251881Speter  const char *path;
2579251881Speter  const char *full_path;
2580251881Speter  apr_uint64_t include_merged_revs_param;
2581251881Speter  svn_boolean_t include_merged_revisions;
2582251881Speter  authz_baton_t ab;
2583251881Speter
2584251881Speter  ab.server = b;
2585251881Speter  ab.conn = conn;
2586251881Speter
2587251881Speter  /* Parse arguments. */
2588251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)?B",
2589251881Speter                                  &path, &start_rev, &end_rev,
2590251881Speter                                  &include_merged_revs_param));
2591251881Speter  path = svn_relpath_canonicalize(path, pool);
2592251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2593251881Speter  full_path = svn_fspath__join(b->fs_path->data, path, pool);
2594251881Speter
2595251881Speter  if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2596251881Speter    include_merged_revisions = FALSE;
2597251881Speter  else
2598251881Speter    include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2599251881Speter
2600251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2601251881Speter                      svn_log__get_file_revs(full_path, start_rev, end_rev,
2602251881Speter                                             include_merged_revisions,
2603251881Speter                                             pool)));
2604251881Speter
2605251881Speter  frb.conn = conn;
2606251881Speter  frb.pool = NULL;
2607251881Speter
2608251881Speter  err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev,
2609251881Speter                                 include_merged_revisions,
2610251881Speter                                 authz_check_access_cb_func(b), &ab,
2611251881Speter                                 file_rev_handler, &frb, pool);
2612251881Speter  write_err = svn_ra_svn__write_word(conn, pool, "done");
2613251881Speter  if (write_err)
2614251881Speter    {
2615251881Speter      svn_error_clear(err);
2616251881Speter      return write_err;
2617251881Speter    }
2618251881Speter  SVN_CMD_ERR(err);
2619251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2620251881Speter
2621251881Speter  return SVN_NO_ERROR;
2622251881Speter}
2623251881Speter
2624251881Speterstatic svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2625251881Speter                         apr_array_header_t *params, void *baton)
2626251881Speter{
2627251881Speter  server_baton_t *b = baton;
2628251881Speter  const char *path;
2629251881Speter  const char *comment;
2630251881Speter  const char *full_path;
2631251881Speter  svn_boolean_t steal_lock;
2632251881Speter  svn_revnum_t current_rev;
2633251881Speter  svn_lock_t *l;
2634251881Speter
2635251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
2636251881Speter                                  &steal_lock, &current_rev));
2637251881Speter  full_path = svn_fspath__join(b->fs_path->data,
2638251881Speter                               svn_relpath_canonicalize(path, pool), pool);
2639251881Speter
2640251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2641251881Speter                           full_path, TRUE));
2642251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2643251881Speter                      svn_log__lock_one_path(full_path, steal_lock, pool)));
2644251881Speter
2645251881Speter  SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
2646251881Speter                                0, /* No expiration time. */
2647251881Speter                                current_rev, steal_lock, pool));
2648251881Speter
2649251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success"));
2650251881Speter  SVN_ERR(write_lock(conn, pool, l));
2651251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)"));
2652251881Speter
2653251881Speter  return SVN_NO_ERROR;
2654251881Speter}
2655251881Speter
2656251881Speterstatic svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2657251881Speter                              apr_array_header_t *params, void *baton)
2658251881Speter{
2659251881Speter  server_baton_t *b = baton;
2660251881Speter  apr_array_header_t *path_revs;
2661251881Speter  const char *comment;
2662251881Speter  svn_boolean_t steal_lock;
2663251881Speter  int i;
2664251881Speter  apr_pool_t *subpool;
2665251881Speter  const char *path;
2666251881Speter  const char *full_path;
2667251881Speter  svn_revnum_t current_rev;
2668251881Speter  apr_array_header_t *log_paths;
2669251881Speter  svn_lock_t *l;
2670251881Speter  svn_error_t *err = SVN_NO_ERROR, *write_err;
2671251881Speter
2672251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
2673251881Speter                                  &path_revs));
2674251881Speter
2675251881Speter  subpool = svn_pool_create(pool);
2676251881Speter
2677251881Speter  /* Because we can only send a single auth reply per request, we send
2678251881Speter     a reply before parsing the lock commands.  This means an authz
2679251881Speter     access denial will abort the processing of the locks and return
2680251881Speter     an error. */
2681251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
2682251881Speter
2683251881Speter  /* Loop through the lock requests. */
2684251881Speter  log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path));
2685251881Speter  for (i = 0; i < path_revs->nelts; ++i)
2686251881Speter    {
2687251881Speter      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
2688251881Speter                                               svn_ra_svn_item_t);
2689251881Speter
2690251881Speter      svn_pool_clear(subpool);
2691251881Speter
2692251881Speter      if (item->kind != SVN_RA_SVN_LIST)
2693251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2694251881Speter                                "Lock requests should be list of lists");
2695251881Speter
2696251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "c(?r)", &path,
2697251881Speter                                      &current_rev));
2698251881Speter
2699251881Speter      /* Allocate the full_path out of pool so it will survive for use
2700251881Speter       * by operational logging, after this loop. */
2701251881Speter      full_path = svn_fspath__join(b->fs_path->data,
2702251881Speter                                   svn_relpath_canonicalize(path, subpool),
2703251881Speter                                   pool);
2704251881Speter      APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2705251881Speter
2706251881Speter      if (! lookup_access(pool, b, conn, svn_authz_write, full_path, TRUE))
2707251881Speter        {
2708251881Speter          err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
2709251881Speter                                     b, conn, pool);
2710251881Speter          break;
2711251881Speter        }
2712251881Speter
2713251881Speter      err = svn_repos_fs_lock(&l, b->repos, full_path,
2714251881Speter                              NULL, comment, FALSE,
2715251881Speter                              0, /* No expiration time. */
2716251881Speter                              current_rev,
2717251881Speter                              steal_lock, subpool);
2718251881Speter
2719251881Speter      if (err)
2720251881Speter        {
2721251881Speter          if (SVN_ERR_IS_LOCK_ERROR(err))
2722251881Speter            {
2723251881Speter              write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
2724251881Speter              svn_error_clear(err);
2725251881Speter              err = NULL;
2726251881Speter              SVN_ERR(write_err);
2727251881Speter            }
2728251881Speter          else
2729251881Speter            break;
2730251881Speter        }
2731251881Speter      else
2732251881Speter        {
2733251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w!", "success"));
2734251881Speter          SVN_ERR(write_lock(conn, subpool, l));
2735251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "!"));
2736251881Speter        }
2737251881Speter    }
2738251881Speter
2739251881Speter  svn_pool_destroy(subpool);
2740251881Speter
2741251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2742251881Speter                      svn_log__lock(log_paths, steal_lock, pool)));
2743251881Speter
2744251881Speter  /* NOTE: err might contain a fatal locking error from the loop above. */
2745251881Speter  write_err = svn_ra_svn__write_word(conn, pool, "done");
2746251881Speter  if (!write_err)
2747251881Speter    SVN_CMD_ERR(err);
2748251881Speter  svn_error_clear(err);
2749251881Speter  SVN_ERR(write_err);
2750251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2751251881Speter
2752251881Speter  return SVN_NO_ERROR;
2753251881Speter}
2754251881Speter
2755251881Speterstatic svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2756251881Speter                           apr_array_header_t *params, void *baton)
2757251881Speter{
2758251881Speter  server_baton_t *b = baton;
2759251881Speter  const char *path, *token, *full_path;
2760251881Speter  svn_boolean_t break_lock;
2761251881Speter
2762251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b", &path, &token,
2763251881Speter                                 &break_lock));
2764251881Speter
2765251881Speter  full_path = svn_fspath__join(b->fs_path->data,
2766251881Speter                               svn_relpath_canonicalize(path, pool), pool);
2767251881Speter
2768251881Speter  /* Username required unless break_lock was specified. */
2769251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2770251881Speter                           full_path, ! break_lock));
2771251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2772251881Speter                      svn_log__unlock_one_path(full_path, break_lock, pool)));
2773251881Speter
2774251881Speter  SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2775251881Speter                                  pool));
2776251881Speter
2777251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2778251881Speter
2779251881Speter  return SVN_NO_ERROR;
2780251881Speter}
2781251881Speter
2782251881Speterstatic svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2783251881Speter                                apr_array_header_t *params, void *baton)
2784251881Speter{
2785251881Speter  server_baton_t *b = baton;
2786251881Speter  svn_boolean_t break_lock;
2787251881Speter  apr_array_header_t *unlock_tokens;
2788251881Speter  int i;
2789251881Speter  apr_pool_t *subpool;
2790251881Speter  const char *path;
2791251881Speter  const char *full_path;
2792251881Speter  apr_array_header_t *log_paths;
2793251881Speter  const char *token;
2794251881Speter  svn_error_t *err = SVN_NO_ERROR, *write_err;
2795251881Speter
2796251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock,
2797251881Speter                                  &unlock_tokens));
2798251881Speter
2799251881Speter  /* Username required unless break_lock was specified. */
2800251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
2801251881Speter
2802251881Speter  subpool = svn_pool_create(pool);
2803251881Speter
2804251881Speter  /* Loop through the unlock requests. */
2805251881Speter  log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path));
2806251881Speter  for (i = 0; i < unlock_tokens->nelts; i++)
2807251881Speter    {
2808251881Speter      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
2809251881Speter                                               svn_ra_svn_item_t);
2810251881Speter
2811251881Speter      svn_pool_clear(subpool);
2812251881Speter
2813251881Speter      if (item->kind != SVN_RA_SVN_LIST)
2814251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2815251881Speter                                "Unlock request should be a list of lists");
2816251881Speter
2817251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
2818251881Speter                                      &token));
2819251881Speter
2820251881Speter      /* Allocate the full_path out of pool so it will survive for use
2821251881Speter       * by operational logging, after this loop. */
2822251881Speter      full_path = svn_fspath__join(b->fs_path->data,
2823251881Speter                                   svn_relpath_canonicalize(path, subpool),
2824251881Speter                                   pool);
2825251881Speter      APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2826251881Speter
2827251881Speter      if (! lookup_access(subpool, b, conn, svn_authz_write, full_path,
2828251881Speter                          ! break_lock))
2829251881Speter        return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
2830251881Speter                                error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
2831251881Speter                                                     NULL, NULL,
2832251881Speter                                                     b, conn, pool),
2833251881Speter                                NULL);
2834251881Speter
2835251881Speter      err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2836251881Speter                                subpool);
2837251881Speter      if (err)
2838251881Speter        {
2839251881Speter          if (SVN_ERR_IS_UNLOCK_ERROR(err))
2840251881Speter            {
2841251881Speter              write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
2842251881Speter              svn_error_clear(err);
2843251881Speter              err = NULL;
2844251881Speter              SVN_ERR(write_err);
2845251881Speter            }
2846251881Speter          else
2847251881Speter            break;
2848251881Speter        }
2849251881Speter      else
2850251881Speter        SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
2851251881Speter                                        path));
2852251881Speter    }
2853251881Speter
2854251881Speter  svn_pool_destroy(subpool);
2855251881Speter
2856251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2857251881Speter                      svn_log__unlock(log_paths, break_lock, pool)));
2858251881Speter
2859251881Speter  /* NOTE: err might contain a fatal unlocking error from the loop above. */
2860251881Speter  write_err = svn_ra_svn__write_word(conn, pool, "done");
2861251881Speter  if (! write_err)
2862251881Speter    SVN_CMD_ERR(err);
2863251881Speter  svn_error_clear(err);
2864251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2865251881Speter
2866251881Speter  return SVN_NO_ERROR;
2867251881Speter}
2868251881Speter
2869251881Speterstatic svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2870251881Speter                             apr_array_header_t *params, void *baton)
2871251881Speter{
2872251881Speter  server_baton_t *b = baton;
2873251881Speter  const char *path;
2874251881Speter  const char *full_path;
2875251881Speter  svn_lock_t *l;
2876251881Speter
2877251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
2878251881Speter
2879251881Speter  full_path = svn_fspath__join(b->fs_path->data,
2880251881Speter                               svn_relpath_canonicalize(path, pool), pool);
2881251881Speter
2882251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2883251881Speter                           full_path, FALSE));
2884251881Speter  SVN_ERR(log_command(b, conn, pool, "get-lock %s",
2885251881Speter                      svn_path_uri_encode(full_path, pool)));
2886251881Speter
2887251881Speter  SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
2888251881Speter
2889251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2890251881Speter  if (l)
2891251881Speter    SVN_ERR(write_lock(conn, pool, l));
2892251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2893251881Speter
2894251881Speter  return SVN_NO_ERROR;
2895251881Speter}
2896251881Speter
2897251881Speterstatic svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2898251881Speter                              apr_array_header_t *params, void *baton)
2899251881Speter{
2900251881Speter  server_baton_t *b = baton;
2901251881Speter  const char *path;
2902251881Speter  const char *full_path;
2903251881Speter  const char *depth_word;
2904251881Speter  svn_depth_t depth;
2905251881Speter  apr_hash_t *locks;
2906251881Speter  apr_hash_index_t *hi;
2907251881Speter  svn_error_t *err;
2908251881Speter  authz_baton_t ab;
2909251881Speter
2910251881Speter  ab.server = b;
2911251881Speter  ab.conn = conn;
2912251881Speter
2913251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c?(?w)", &path, &depth_word));
2914251881Speter
2915251881Speter  depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity;
2916251881Speter  if ((depth != svn_depth_empty) &&
2917251881Speter      (depth != svn_depth_files) &&
2918251881Speter      (depth != svn_depth_immediates) &&
2919251881Speter      (depth != svn_depth_infinity))
2920251881Speter    {
2921251881Speter      err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2922251881Speter                             "Invalid 'depth' specified in get-locks request");
2923251881Speter      return log_fail_and_flush(err, b, conn, pool);
2924251881Speter    }
2925251881Speter
2926251881Speter  full_path = svn_fspath__join(b->fs_path->data,
2927251881Speter                               svn_relpath_canonicalize(path, pool), pool);
2928251881Speter
2929251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2930251881Speter
2931251881Speter  SVN_ERR(log_command(b, conn, pool, "get-locks %s",
2932251881Speter                      svn_path_uri_encode(full_path, pool)));
2933251881Speter  SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repos, full_path, depth,
2934251881Speter                                      authz_check_access_cb_func(b), &ab,
2935251881Speter                                      pool));
2936251881Speter
2937251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2938251881Speter  for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2939251881Speter    {
2940251881Speter      svn_lock_t *l = svn__apr_hash_index_val(hi);
2941251881Speter
2942251881Speter      SVN_ERR(write_lock(conn, pool, l));
2943251881Speter    }
2944251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2945251881Speter
2946251881Speter  return SVN_NO_ERROR;
2947251881Speter}
2948251881Speter
2949251881Speterstatic svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
2950251881Speter                                        server_baton_t *b,
2951251881Speter                                        svn_revnum_t rev,
2952251881Speter                                        svn_revnum_t low_water_mark,
2953251881Speter                                        svn_boolean_t send_deltas,
2954251881Speter                                        apr_pool_t *pool)
2955251881Speter{
2956251881Speter  const svn_delta_editor_t *editor;
2957251881Speter  void *edit_baton;
2958251881Speter  svn_fs_root_t *root;
2959251881Speter  svn_error_t *err;
2960251881Speter  authz_baton_t ab;
2961251881Speter
2962251881Speter  ab.server = b;
2963251881Speter  ab.conn = conn;
2964251881Speter
2965251881Speter  SVN_ERR(log_command(b, conn, pool,
2966251881Speter                      svn_log__replay(b->fs_path->data, rev, pool)));
2967251881Speter
2968251881Speter  svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
2969251881Speter
2970251881Speter  err = svn_fs_revision_root(&root, b->fs, rev, pool);
2971251881Speter
2972251881Speter  if (! err)
2973251881Speter    err = svn_repos_replay2(root, b->fs_path->data, low_water_mark,
2974251881Speter                            send_deltas, editor, edit_baton,
2975251881Speter                            authz_check_access_cb_func(b), &ab, pool);
2976251881Speter
2977251881Speter  if (err)
2978251881Speter    svn_error_clear(editor->abort_edit(edit_baton, pool));
2979251881Speter  SVN_CMD_ERR(err);
2980251881Speter
2981251881Speter  return svn_ra_svn__write_cmd_finish_replay(conn, pool);
2982251881Speter}
2983251881Speter
2984251881Speterstatic svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2985251881Speter                           apr_array_header_t *params, void *baton)
2986251881Speter{
2987251881Speter  svn_revnum_t rev, low_water_mark;
2988251881Speter  svn_boolean_t send_deltas;
2989251881Speter  server_baton_t *b = baton;
2990251881Speter
2991251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrb", &rev, &low_water_mark,
2992251881Speter                                 &send_deltas));
2993251881Speter
2994251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2995251881Speter
2996251881Speter  SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
2997251881Speter                              send_deltas, pool));
2998251881Speter
2999251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3000251881Speter
3001251881Speter  return SVN_NO_ERROR;
3002251881Speter}
3003251881Speter
3004251881Speterstatic svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3005251881Speter                                 apr_array_header_t *params, void *baton)
3006251881Speter{
3007251881Speter  svn_revnum_t start_rev, end_rev, rev, low_water_mark;
3008251881Speter  svn_boolean_t send_deltas;
3009251881Speter  server_baton_t *b = baton;
3010251881Speter  apr_pool_t *iterpool;
3011251881Speter  authz_baton_t ab;
3012251881Speter
3013251881Speter  ab.server = b;
3014251881Speter  ab.conn = conn;
3015251881Speter
3016251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrrb", &start_rev,
3017251881Speter                                 &end_rev, &low_water_mark,
3018251881Speter                                 &send_deltas));
3019251881Speter
3020251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
3021251881Speter
3022251881Speter  iterpool = svn_pool_create(pool);
3023251881Speter  for (rev = start_rev; rev <= end_rev; rev++)
3024251881Speter    {
3025251881Speter      apr_hash_t *props;
3026251881Speter
3027251881Speter      svn_pool_clear(iterpool);
3028251881Speter
3029251881Speter      SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
3030251881Speter                                                 authz_check_access_cb_func(b),
3031251881Speter                                                 &ab,
3032251881Speter                                                 iterpool));
3033251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops"));
3034251881Speter      SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props));
3035251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)"));
3036251881Speter
3037251881Speter      SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3038251881Speter                                  send_deltas, iterpool));
3039251881Speter
3040251881Speter    }
3041251881Speter  svn_pool_destroy(iterpool);
3042251881Speter
3043251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3044251881Speter
3045251881Speter  return SVN_NO_ERROR;
3046251881Speter}
3047251881Speter
3048251881Speterstatic svn_error_t *
3049251881Speterget_deleted_rev(svn_ra_svn_conn_t *conn,
3050251881Speter                apr_pool_t *pool,
3051251881Speter                apr_array_header_t *params,
3052251881Speter                void *baton)
3053251881Speter{
3054251881Speter  server_baton_t *b = baton;
3055251881Speter  const char *path, *full_path;
3056251881Speter  svn_revnum_t peg_revision;
3057251881Speter  svn_revnum_t end_revision;
3058251881Speter  svn_revnum_t revision_deleted;
3059251881Speter
3060251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crr",
3061251881Speter                                 &path, &peg_revision, &end_revision));
3062251881Speter  full_path = svn_fspath__join(b->fs_path->data,
3063251881Speter                               svn_relpath_canonicalize(path, pool), pool);
3064251881Speter  SVN_ERR(log_command(b, conn, pool, "get-deleted-rev"));
3065251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
3066251881Speter  SVN_ERR(svn_repos_deleted_rev(b->fs, full_path, peg_revision, end_revision,
3067251881Speter                                &revision_deleted, pool));
3068251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted));
3069251881Speter  return SVN_NO_ERROR;
3070251881Speter}
3071251881Speter
3072251881Speterstatic svn_error_t *
3073251881Speterget_inherited_props(svn_ra_svn_conn_t *conn,
3074251881Speter                    apr_pool_t *pool,
3075251881Speter                    apr_array_header_t *params,
3076251881Speter                    void *baton)
3077251881Speter{
3078251881Speter  server_baton_t *b = baton;
3079251881Speter  const char *path, *full_path;
3080251881Speter  svn_revnum_t rev;
3081251881Speter  svn_fs_root_t *root;
3082251881Speter  apr_array_header_t *inherited_props;
3083251881Speter  int i;
3084251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
3085251881Speter  authz_baton_t ab;
3086251881Speter
3087251881Speter  ab.server = b;
3088251881Speter  ab.conn = conn;
3089251881Speter
3090251881Speter  /* Parse arguments. */
3091251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, iterpool, "c(?r)", &path, &rev));
3092251881Speter
3093251881Speter  full_path = svn_fspath__join(b->fs_path->data,
3094251881Speter                               svn_relpath_canonicalize(path, iterpool),
3095251881Speter                               pool);
3096251881Speter
3097251881Speter  /* Check authorizations */
3098251881Speter  SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read,
3099251881Speter                           full_path, FALSE));
3100251881Speter
3101251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
3102251881Speter    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
3103251881Speter
3104251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
3105251881Speter                      svn_log__get_inherited_props(full_path, rev,
3106251881Speter                                                   iterpool)));
3107251881Speter
3108251881Speter  /* Fetch the properties and a stream for the contents. */
3109251881Speter  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, iterpool));
3110251881Speter  SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool));
3111251881Speter
3112251881Speter  /* Send successful command response with revision and props. */
3113251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success"));
3114251881Speter
3115251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!"));
3116251881Speter
3117251881Speter  for (i = 0; i < inherited_props->nelts; i++)
3118251881Speter    {
3119251881Speter      svn_prop_inherited_item_t *iprop =
3120251881Speter        APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
3121251881Speter
3122251881Speter      svn_pool_clear(iterpool);
3123251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
3124251881Speter                                      iprop->path_or_url));
3125251881Speter      SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
3126251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
3127251881Speter                                      iprop->path_or_url));
3128251881Speter    }
3129251881Speter
3130251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))"));
3131251881Speter  svn_pool_destroy(iterpool);
3132251881Speter  return SVN_NO_ERROR;
3133251881Speter}
3134251881Speter
3135251881Speterstatic const svn_ra_svn_cmd_entry_t main_commands[] = {
3136251881Speter  { "reparent",        reparent },
3137251881Speter  { "get-latest-rev",  get_latest_rev },
3138251881Speter  { "get-dated-rev",   get_dated_rev },
3139251881Speter  { "change-rev-prop", change_rev_prop },
3140251881Speter  { "change-rev-prop2",change_rev_prop2 },
3141251881Speter  { "rev-proplist",    rev_proplist },
3142251881Speter  { "rev-prop",        rev_prop },
3143251881Speter  { "commit",          commit },
3144251881Speter  { "get-file",        get_file },
3145251881Speter  { "get-dir",         get_dir },
3146251881Speter  { "update",          update },
3147251881Speter  { "switch",          switch_cmd },
3148251881Speter  { "status",          status },
3149251881Speter  { "diff",            diff },
3150251881Speter  { "get-mergeinfo",   get_mergeinfo },
3151251881Speter  { "log",             log_cmd },
3152251881Speter  { "check-path",      check_path },
3153251881Speter  { "stat",            stat_cmd },
3154251881Speter  { "get-locations",   get_locations },
3155251881Speter  { "get-location-segments",   get_location_segments },
3156251881Speter  { "get-file-revs",   get_file_revs },
3157251881Speter  { "lock",            lock },
3158251881Speter  { "lock-many",       lock_many },
3159251881Speter  { "unlock",          unlock },
3160251881Speter  { "unlock-many",     unlock_many },
3161251881Speter  { "get-lock",        get_lock },
3162251881Speter  { "get-locks",       get_locks },
3163251881Speter  { "replay",          replay },
3164251881Speter  { "replay-range",    replay_range },
3165251881Speter  { "get-deleted-rev", get_deleted_rev },
3166251881Speter  { "get-iprops",      get_inherited_props },
3167251881Speter  { NULL }
3168251881Speter};
3169251881Speter
3170251881Speter/* Skip past the scheme part of a URL, including the tunnel specification
3171251881Speter * if present.  Return NULL if the scheme part is invalid for ra_svn. */
3172251881Speterstatic const char *skip_scheme_part(const char *url)
3173251881Speter{
3174251881Speter  if (strncmp(url, "svn", 3) != 0)
3175251881Speter    return NULL;
3176251881Speter  url += 3;
3177251881Speter  if (*url == '+')
3178251881Speter    url += strcspn(url, ":");
3179251881Speter  if (strncmp(url, "://", 3) != 0)
3180251881Speter    return NULL;
3181251881Speter  return url + 3;
3182251881Speter}
3183251881Speter
3184251881Speter/* Check that PATH is a valid repository path, meaning it doesn't contain any
3185251881Speter   '..' path segments.
3186251881Speter   NOTE: This is similar to svn_path_is_backpath_present, but that function
3187251881Speter   assumes the path separator is '/'.  This function also checks for
3188251881Speter   segments delimited by the local path separator. */
3189251881Speterstatic svn_boolean_t
3190251881Speterrepos_path_valid(const char *path)
3191251881Speter{
3192251881Speter  const char *s = path;
3193251881Speter
3194251881Speter  while (*s)
3195251881Speter    {
3196251881Speter      /* Scan for the end of the segment. */
3197251881Speter      while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
3198251881Speter        ++path;
3199251881Speter
3200251881Speter      /* Check for '..'. */
3201251881Speter#ifdef WIN32
3202251881Speter      /* On Windows, don't allow sequences of more than one character
3203251881Speter         consisting of just dots and spaces.  Win32 functions treat
3204251881Speter         paths such as ".. " and "......." inconsistently.  Make sure
3205251881Speter         no one can escape out of the root. */
3206251881Speter      if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s))
3207251881Speter        return FALSE;
3208251881Speter#else  /* ! WIN32 */
3209251881Speter      if (path - s == 2 && s[0] == '.' && s[1] == '.')
3210251881Speter        return FALSE;
3211251881Speter#endif
3212251881Speter
3213251881Speter      /* Skip all separators. */
3214251881Speter      while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
3215251881Speter        ++path;
3216251881Speter      s = path;
3217251881Speter    }
3218251881Speter
3219251881Speter  return TRUE;
3220251881Speter}
3221251881Speter
3222251881Speter/* Look for the repository given by URL, using ROOT as the virtual
3223251881Speter * repository root.  If we find one, fill in the repos, fs, cfg,
3224251881Speter * repos_url, and fs_path fields of B.  Set B->repos's client
3225251881Speter * capabilities to CAPABILITIES, which must be at least as long-lived
3226251881Speter * as POOL, and whose elements are SVN_RA_CAPABILITY_*.
3227251881Speter */
3228251881Speterstatic svn_error_t *find_repos(const char *url, const char *root,
3229251881Speter                               server_baton_t *b,
3230251881Speter                               svn_ra_svn_conn_t *conn,
3231251881Speter                               const apr_array_header_t *capabilities,
3232251881Speter                               apr_pool_t *pool)
3233251881Speter{
3234251881Speter  const char *path, *full_path, *repos_root, *fs_path, *hooks_env;
3235251881Speter  svn_stringbuf_t *url_buf;
3236251881Speter
3237251881Speter  /* Skip past the scheme and authority part. */
3238251881Speter  path = skip_scheme_part(url);
3239251881Speter  if (path == NULL)
3240251881Speter    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
3241251881Speter                             "Non-svn URL passed to svn server: '%s'", url);
3242251881Speter
3243251881Speter  if (! b->vhost)
3244251881Speter    {
3245251881Speter      path = strchr(path, '/');
3246251881Speter      if (path == NULL)
3247251881Speter        path = "";
3248251881Speter    }
3249251881Speter  path = svn_relpath_canonicalize(path, pool);
3250251881Speter  path = svn_path_uri_decode(path, pool);
3251251881Speter
3252251881Speter  /* Ensure that it isn't possible to escape the root by disallowing
3253251881Speter     '..' segments. */
3254251881Speter  if (!repos_path_valid(path))
3255251881Speter    return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
3256251881Speter                            "Couldn't determine repository path");
3257251881Speter
3258251881Speter  /* Join the server-configured root with the client path. */
3259251881Speter  full_path = svn_dirent_join(svn_dirent_canonicalize(root, pool),
3260251881Speter                              path, pool);
3261251881Speter
3262251881Speter  /* Search for a repository in the full path. */
3263251881Speter  repos_root = svn_repos_find_root_path(full_path, pool);
3264251881Speter  if (!repos_root)
3265251881Speter    return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
3266251881Speter                             "No repository found in '%s'", url);
3267251881Speter
3268251881Speter  /* Open the repository and fill in b with the resulting information. */
3269251881Speter  SVN_ERR(svn_repos_open2(&b->repos, repos_root, b->fs_config, pool));
3270251881Speter  SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities));
3271251881Speter  b->fs = svn_repos_fs(b->repos);
3272251881Speter  fs_path = full_path + strlen(repos_root);
3273251881Speter  b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool);
3274251881Speter  url_buf = svn_stringbuf_create(url, pool);
3275251881Speter  svn_path_remove_components(url_buf,
3276251881Speter                             svn_path_component_count(b->fs_path->data));
3277251881Speter  b->repos_url = url_buf->data;
3278251881Speter  b->authz_repos_name = svn_dirent_is_child(root, repos_root, pool);
3279251881Speter  if (b->authz_repos_name == NULL)
3280251881Speter    b->repos_name = svn_dirent_basename(repos_root, pool);
3281251881Speter  else
3282251881Speter    b->repos_name = b->authz_repos_name;
3283251881Speter  b->repos_name = svn_path_uri_encode(b->repos_name, pool);
3284251881Speter
3285251881Speter  /* If the svnserve configuration has not been loaded then load it from the
3286251881Speter   * repository. */
3287251881Speter  if (NULL == b->cfg)
3288251881Speter    {
3289251881Speter      b->base = svn_repos_conf_dir(b->repos, pool);
3290251881Speter
3291251881Speter      SVN_ERR(svn_config_read3(&b->cfg, svn_repos_svnserve_conf(b->repos, pool),
3292251881Speter                               FALSE, /* must_exist */
3293251881Speter                               FALSE, /* section_names_case_sensitive */
3294251881Speter                               FALSE, /* option_names_case_sensitive */
3295251881Speter                               pool));
3296251881Speter      SVN_ERR(load_pwdb_config(b, conn, pool));
3297251881Speter      SVN_ERR(load_authz_config(b, conn, repos_root, pool));
3298251881Speter    }
3299251881Speter  /* svnserve.conf has been loaded via the --config-file option so need
3300251881Speter   * to load pwdb and authz. */
3301251881Speter  else
3302251881Speter    {
3303251881Speter      SVN_ERR(load_pwdb_config(b, conn, pool));
3304251881Speter      SVN_ERR(load_authz_config(b, conn, repos_root, pool));
3305251881Speter    }
3306251881Speter
3307251881Speter#ifdef SVN_HAVE_SASL
3308251881Speter  /* Should we use Cyrus SASL? */
3309251881Speter  SVN_ERR(svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
3310251881Speter                              SVN_CONFIG_OPTION_USE_SASL, FALSE));
3311251881Speter#endif
3312251881Speter
3313251881Speter  /* Use the repository UUID as the default realm. */
3314251881Speter  SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
3315251881Speter  svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
3316251881Speter                 SVN_CONFIG_OPTION_REALM, b->realm);
3317251881Speter
3318251881Speter  /* Make sure it's possible for the client to authenticate.  Note
3319251881Speter     that this doesn't take into account any authz configuration read
3320251881Speter     above, because we can't know about access it grants until paths
3321251881Speter     are given by the client. */
3322251881Speter  if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
3323251881Speter      && (get_access(b, AUTHENTICATED) == NO_ACCESS
3324251881Speter          || (!b->tunnel_user && !b->pwdb && !b->use_sasl)))
3325251881Speter    return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3326251881Speter                                 "No access allowed to this repository",
3327251881Speter                                 b, conn, pool);
3328251881Speter
3329251881Speter  /* Configure hook script environment variables. */
3330251881Speter  svn_config_get(b->cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL,
3331251881Speter                 SVN_CONFIG_OPTION_HOOKS_ENV, NULL);
3332251881Speter  if (hooks_env)
3333251881Speter    hooks_env = svn_dirent_internal_style(hooks_env, pool);
3334251881Speter  SVN_ERR(svn_repos_hooks_setenv(b->repos, hooks_env, pool));
3335251881Speter
3336251881Speter  return SVN_NO_ERROR;
3337251881Speter}
3338251881Speter
3339251881Speter/* Compute the authentication name EXTERNAL should be able to get, if any. */
3340251881Speterstatic const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
3341251881Speter{
3342251881Speter  /* Only offer EXTERNAL for connections tunneled over a login agent. */
3343251881Speter  if (!params->tunnel)
3344251881Speter    return NULL;
3345251881Speter
3346251881Speter  /* If a tunnel user was provided on the command line, use that. */
3347251881Speter  if (params->tunnel_user)
3348251881Speter    return params->tunnel_user;
3349251881Speter
3350251881Speter  return svn_user_get_name(pool);
3351251881Speter}
3352251881Speter
3353251881Speterstatic void
3354251881Speterfs_warning_func(void *baton, svn_error_t *err)
3355251881Speter{
3356251881Speter  fs_warning_baton_t *b = baton;
3357251881Speter  log_server_error(err, b->server, b->conn, b->pool);
3358251881Speter  /* TODO: Keep log_pool in the server baton, cleared after every log? */
3359251881Speter  svn_pool_clear(b->pool);
3360251881Speter}
3361251881Speter
3362251881Speter/* Return the normalized repository-relative path for the given PATH
3363251881Speter * (may be a URL, full path or relative path) and fs contained in the
3364251881Speter * server baton BATON. Allocate the result in POOL.
3365251881Speter */
3366251881Speterstatic const char *
3367251881Speterget_normalized_repo_rel_path(void *baton,
3368251881Speter                             const char *path,
3369251881Speter                             apr_pool_t *pool)
3370251881Speter{
3371251881Speter  server_baton_t *sb = baton;
3372251881Speter
3373251881Speter  if (svn_path_is_url(path))
3374251881Speter    {
3375251881Speter      /* This is a copyfrom URL. */
3376251881Speter      path = svn_uri_skip_ancestor(sb->repos_url, path, pool);
3377251881Speter      path = svn_fspath__canonicalize(path, pool);
3378251881Speter    }
3379251881Speter  else
3380251881Speter    {
3381251881Speter      /* This is a base-relative path. */
3382251881Speter      if ((path)[0] != '/')
3383251881Speter        /* Get an absolute path for use in the FS. */
3384251881Speter        path = svn_fspath__join(sb->fs_path->data, path, pool);
3385251881Speter    }
3386251881Speter
3387251881Speter  return path;
3388251881Speter}
3389251881Speter
3390251881Speter/* Get the revision root for REVISION in fs given by server baton BATON
3391251881Speter * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM.
3392251881Speter * Use POOL for allocations.
3393251881Speter */
3394251881Speterstatic svn_error_t *
3395251881Speterget_revision_root(svn_fs_root_t **fs_root,
3396251881Speter                  void *baton,
3397251881Speter                  svn_revnum_t revision,
3398251881Speter                  apr_pool_t *pool)
3399251881Speter{
3400251881Speter  server_baton_t *sb = baton;
3401251881Speter
3402251881Speter  if (!SVN_IS_VALID_REVNUM(revision))
3403251881Speter    SVN_ERR(svn_fs_youngest_rev(&revision, sb->fs, pool));
3404251881Speter
3405251881Speter  SVN_ERR(svn_fs_revision_root(fs_root, sb->fs, revision, pool));
3406251881Speter
3407251881Speter  return SVN_NO_ERROR;
3408251881Speter}
3409251881Speter
3410251881Speterstatic svn_error_t *
3411251881Speterfetch_props_func(apr_hash_t **props,
3412251881Speter                 void *baton,
3413251881Speter                 const char *path,
3414251881Speter                 svn_revnum_t base_revision,
3415251881Speter                 apr_pool_t *result_pool,
3416251881Speter                 apr_pool_t *scratch_pool)
3417251881Speter{
3418251881Speter  svn_fs_root_t *fs_root;
3419251881Speter  svn_error_t *err;
3420251881Speter
3421251881Speter  path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3422251881Speter  SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3423251881Speter
3424251881Speter  err = svn_fs_node_proplist(props, fs_root, path, result_pool);
3425251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3426251881Speter    {
3427251881Speter      svn_error_clear(err);
3428251881Speter      *props = apr_hash_make(result_pool);
3429251881Speter      return SVN_NO_ERROR;
3430251881Speter    }
3431251881Speter  else if (err)
3432251881Speter    return svn_error_trace(err);
3433251881Speter
3434251881Speter  return SVN_NO_ERROR;
3435251881Speter}
3436251881Speter
3437251881Speterstatic svn_error_t *
3438251881Speterfetch_kind_func(svn_node_kind_t *kind,
3439251881Speter                void *baton,
3440251881Speter                const char *path,
3441251881Speter                svn_revnum_t base_revision,
3442251881Speter                apr_pool_t *scratch_pool)
3443251881Speter{
3444251881Speter  svn_fs_root_t *fs_root;
3445251881Speter
3446251881Speter  path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3447251881Speter  SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3448251881Speter
3449251881Speter  SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
3450251881Speter
3451251881Speter  return SVN_NO_ERROR;
3452251881Speter}
3453251881Speter
3454251881Speterstatic svn_error_t *
3455251881Speterfetch_base_func(const char **filename,
3456251881Speter                void *baton,
3457251881Speter                const char *path,
3458251881Speter                svn_revnum_t base_revision,
3459251881Speter                apr_pool_t *result_pool,
3460251881Speter                apr_pool_t *scratch_pool)
3461251881Speter{
3462251881Speter  svn_stream_t *contents;
3463251881Speter  svn_stream_t *file_stream;
3464251881Speter  const char *tmp_filename;
3465251881Speter  svn_fs_root_t *fs_root;
3466251881Speter  svn_error_t *err;
3467251881Speter
3468251881Speter  path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3469251881Speter  SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3470251881Speter
3471251881Speter  err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
3472251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3473251881Speter    {
3474251881Speter      svn_error_clear(err);
3475251881Speter      *filename = NULL;
3476251881Speter      return SVN_NO_ERROR;
3477251881Speter    }
3478251881Speter  else if (err)
3479251881Speter    return svn_error_trace(err);
3480251881Speter  SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
3481251881Speter                                 svn_io_file_del_on_pool_cleanup,
3482251881Speter                                 scratch_pool, scratch_pool));
3483251881Speter  SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
3484251881Speter
3485251881Speter  *filename = apr_pstrdup(result_pool, tmp_filename);
3486251881Speter
3487251881Speter  return SVN_NO_ERROR;
3488251881Speter}
3489251881Speter
3490251881Spetersvn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
3491251881Speter                   apr_pool_t *pool)
3492251881Speter{
3493251881Speter  svn_error_t *err, *io_err;
3494251881Speter  apr_uint64_t ver;
3495251881Speter  const char *uuid, *client_url, *ra_client_string, *client_string;
3496251881Speter  apr_array_header_t *caplist, *cap_words;
3497251881Speter  server_baton_t b;
3498251881Speter  fs_warning_baton_t warn_baton;
3499251881Speter  svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(pool);
3500251881Speter
3501251881Speter  b.tunnel = params->tunnel;
3502251881Speter  b.tunnel_user = get_tunnel_user(params, pool);
3503251881Speter  b.read_only = params->read_only;
3504251881Speter  b.user = NULL;
3505251881Speter  b.username_case = params->username_case;
3506251881Speter  b.authz_user = NULL;
3507251881Speter  b.base = params->base;
3508251881Speter  b.cfg = params->cfg;
3509251881Speter  b.pwdb = NULL;
3510251881Speter  b.authzdb = NULL;
3511251881Speter  b.realm = NULL;
3512251881Speter  b.log_file = params->log_file;
3513251881Speter  b.pool = pool;
3514251881Speter  b.use_sasl = FALSE;
3515251881Speter  b.vhost = params->vhost;
3516251881Speter
3517251881Speter  /* construct FS configuration parameters */
3518251881Speter  b.fs_config = apr_hash_make(pool);
3519251881Speter  svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
3520251881Speter                params->cache_txdeltas ? "1" :"0");
3521251881Speter  svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
3522251881Speter                params->cache_fulltexts ? "1" :"0");
3523251881Speter  svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
3524251881Speter                params->cache_revprops ? "1" :"0");
3525251881Speter
3526251881Speter  /* Send greeting.  We don't support version 1 any more, so we can
3527251881Speter   * send an empty mechlist. */
3528251881Speter  if (params->compression_level > 0)
3529251881Speter    SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwwww)",
3530251881Speter                                           (apr_uint64_t) 2, (apr_uint64_t) 2,
3531251881Speter                                           SVN_RA_SVN_CAP_EDIT_PIPELINE,
3532251881Speter                                           SVN_RA_SVN_CAP_SVNDIFF1,
3533251881Speter                                           SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3534251881Speter                                           SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3535251881Speter                                           SVN_RA_SVN_CAP_DEPTH,
3536251881Speter                                           SVN_RA_SVN_CAP_LOG_REVPROPS,
3537251881Speter                                           SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3538251881Speter                                           SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3539251881Speter                                           SVN_RA_SVN_CAP_INHERITED_PROPS,
3540251881Speter                                           SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3541251881Speter                                           SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3542251881Speter                                           ));
3543251881Speter  else
3544251881Speter    SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwww)",
3545251881Speter                                           (apr_uint64_t) 2, (apr_uint64_t) 2,
3546251881Speter                                           SVN_RA_SVN_CAP_EDIT_PIPELINE,
3547251881Speter                                           SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3548251881Speter                                           SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3549251881Speter                                           SVN_RA_SVN_CAP_DEPTH,
3550251881Speter                                           SVN_RA_SVN_CAP_LOG_REVPROPS,
3551251881Speter                                           SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3552251881Speter                                           SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3553251881Speter                                           SVN_RA_SVN_CAP_INHERITED_PROPS,
3554251881Speter                                           SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3555251881Speter                                           SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3556251881Speter                                           ));
3557251881Speter
3558251881Speter  /* Read client response, which we assume to be in version 2 format:
3559251881Speter   * version, capability list, and client URL; then we do an auth
3560251881Speter   * request. */
3561251881Speter  SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "nlc?c(?c)",
3562251881Speter                                 &ver, &caplist, &client_url,
3563251881Speter                                 &ra_client_string,
3564251881Speter                                 &client_string));
3565251881Speter  if (ver != 2)
3566251881Speter    return SVN_NO_ERROR;
3567251881Speter
3568251881Speter  client_url = svn_uri_canonicalize(client_url, pool);
3569251881Speter  SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
3570251881Speter
3571251881Speter  /* All released versions of Subversion support edit-pipeline,
3572251881Speter   * so we do not accept connections from clients that do not. */
3573251881Speter  if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
3574251881Speter    return SVN_NO_ERROR;
3575251881Speter
3576251881Speter  /* find_repos needs the capabilities as a list of words (eventually
3577251881Speter     they get handed to the start-commit hook).  While we could add a
3578251881Speter     new interface to re-retrieve them from conn and convert the
3579251881Speter     result to a list, it's simpler to just convert caplist by hand
3580251881Speter     here, since we already have it and turning 'svn_ra_svn_item_t's
3581251881Speter     into 'const char *'s is pretty easy.
3582251881Speter
3583251881Speter     We only record capabilities we care about.  The client may report
3584251881Speter     more (because it doesn't know what the server cares about). */
3585251881Speter  {
3586251881Speter    int i;
3587251881Speter    svn_ra_svn_item_t *item;
3588251881Speter
3589251881Speter    cap_words = apr_array_make(pool, 1, sizeof(const char *));
3590251881Speter    for (i = 0; i < caplist->nelts; i++)
3591251881Speter      {
3592251881Speter        item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
3593251881Speter        /* ra_svn_set_capabilities() already type-checked for us */
3594251881Speter        if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0)
3595251881Speter          {
3596251881Speter            APR_ARRAY_PUSH(cap_words, const char *)
3597251881Speter              = SVN_RA_CAPABILITY_MERGEINFO;
3598251881Speter          }
3599251881Speter        /* Save for operational log. */
3600251881Speter        if (cap_log->len > 0)
3601251881Speter          svn_stringbuf_appendcstr(cap_log, " ");
3602251881Speter        svn_stringbuf_appendcstr(cap_log, item->u.word);
3603251881Speter      }
3604251881Speter  }
3605251881Speter
3606251881Speter  err = find_repos(client_url, params->root, &b, conn, cap_words, pool);
3607251881Speter  if (!err)
3608251881Speter    {
3609251881Speter      SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
3610251881Speter      if (current_access(&b) == NO_ACCESS)
3611251881Speter        err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3612251881Speter                                   "Not authorized for access",
3613251881Speter                                   &b, conn, pool);
3614251881Speter    }
3615251881Speter  if (err)
3616251881Speter    {
3617251881Speter      log_error(err, b.log_file, svn_ra_svn_conn_remote_host(conn),
3618251881Speter                b.user, NULL, pool);
3619251881Speter      io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
3620251881Speter      svn_error_clear(err);
3621251881Speter      SVN_ERR(io_err);
3622251881Speter      return svn_ra_svn__flush(conn, pool);
3623251881Speter    }
3624251881Speter
3625251881Speter  /* Log the open. */
3626251881Speter  if (ra_client_string == NULL || ra_client_string[0] == '\0')
3627251881Speter    ra_client_string = "-";
3628251881Speter  else
3629251881Speter    ra_client_string = svn_path_uri_encode(ra_client_string, pool);
3630251881Speter  if (client_string == NULL || client_string[0] == '\0')
3631251881Speter    client_string = "-";
3632251881Speter  else
3633251881Speter    client_string = svn_path_uri_encode(client_string, pool);
3634251881Speter  SVN_ERR(log_command(&b, conn, pool,
3635251881Speter                      "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s",
3636251881Speter                      ver, cap_log->data,
3637251881Speter                      svn_path_uri_encode(b.fs_path->data, pool),
3638251881Speter                      ra_client_string, client_string));
3639251881Speter
3640251881Speter  warn_baton.server = &b;
3641251881Speter  warn_baton.conn = conn;
3642251881Speter  warn_baton.pool = svn_pool_create(pool);
3643251881Speter  svn_fs_set_warning_func(b.fs, fs_warning_func, &warn_baton);
3644251881Speter
3645251881Speter  SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
3646251881Speter
3647251881Speter  /* We can't claim mergeinfo capability until we know whether the
3648251881Speter     repository supports mergeinfo (i.e., is not a 1.4 repository),
3649251881Speter     but we don't get the repository url from the client until after
3650251881Speter     we've already sent the initial list of server capabilities.  So
3651251881Speter     we list repository capabilities here, in our first response after
3652251881Speter     the client has sent the url. */
3653251881Speter  {
3654251881Speter    svn_boolean_t supports_mergeinfo;
3655251881Speter    SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo,
3656251881Speter                                     SVN_REPOS_CAPABILITY_MERGEINFO, pool));
3657251881Speter
3658251881Speter    SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cc(!",
3659251881Speter                                    "success", uuid, b.repos_url));
3660251881Speter    if (supports_mergeinfo)
3661251881Speter      SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO));
3662251881Speter    SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3663251881Speter  }
3664251881Speter
3665251881Speter  /* Set up editor shims. */
3666251881Speter  {
3667251881Speter    svn_delta_shim_callbacks_t *callbacks =
3668251881Speter                                svn_delta_shim_callbacks_default(pool);
3669251881Speter
3670251881Speter    callbacks->fetch_base_func = fetch_base_func;
3671251881Speter    callbacks->fetch_props_func = fetch_props_func;
3672251881Speter    callbacks->fetch_kind_func = fetch_kind_func;
3673251881Speter    callbacks->fetch_baton = &b;
3674251881Speter
3675251881Speter    SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks));
3676251881Speter  }
3677251881Speter
3678251881Speter  return svn_ra_svn__handle_commands2(conn, pool, main_commands, &b, FALSE);
3679251881Speter}
3680