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"
64299742Sdim#include "logger.h"
65251881Speter
66251881Spetertypedef struct commit_callback_baton_t {
67251881Speter  apr_pool_t *pool;
68251881Speter  svn_revnum_t *new_rev;
69251881Speter  const char **date;
70251881Speter  const char **author;
71251881Speter  const char **post_commit_err;
72251881Speter} commit_callback_baton_t;
73251881Speter
74251881Spetertypedef struct report_driver_baton_t {
75251881Speter  server_baton_t *sb;
76251881Speter  const char *repos_url;  /* Decoded repository URL. */
77251881Speter  void *report_baton;
78251881Speter  svn_error_t *err;
79251881Speter  /* so update() can distinguish checkout from update in logging */
80251881Speter  int entry_counter;
81251881Speter  svn_boolean_t only_empty_entries;
82251881Speter  /* for diff() logging */
83251881Speter  svn_revnum_t *from_rev;
84251881Speter} report_driver_baton_t;
85251881Speter
86251881Spetertypedef struct log_baton_t {
87251881Speter  const char *fs_path;
88251881Speter  svn_ra_svn_conn_t *conn;
89251881Speter  int stack_depth;
90251881Speter} log_baton_t;
91251881Speter
92251881Spetertypedef struct file_revs_baton_t {
93251881Speter  svn_ra_svn_conn_t *conn;
94251881Speter  apr_pool_t *pool;  /* Pool provided in the handler call. */
95251881Speter} file_revs_baton_t;
96251881Speter
97251881Spetertypedef struct fs_warning_baton_t {
98251881Speter  server_baton_t *server;
99251881Speter  svn_ra_svn_conn_t *conn;
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
107299742Sdim/* svn_error_create() a new error, log_server_error() it, and
108299742Sdim   return it. */
109251881Speterstatic void
110299742Sdimlog_error(svn_error_t *err, server_baton_t *server)
111251881Speter{
112299742Sdim  logger__log_error(server->logger, err, server->repository,
113299742Sdim                    server->client_info);
114251881Speter}
115251881Speter
116251881Speter/* svn_error_create() a new error, log_server_error() it, and
117251881Speter   return it. */
118251881Speterstatic svn_error_t *
119251881Spetererror_create_and_log(apr_status_t apr_err, svn_error_t *child,
120299742Sdim                     const char *message, server_baton_t *server)
121251881Speter{
122251881Speter  svn_error_t *err = svn_error_create(apr_err, child, message);
123299742Sdim  log_error(err, server);
124251881Speter  return err;
125251881Speter}
126251881Speter
127251881Speter/* Log a failure ERR, transmit ERR back to the client (as part of a
128251881Speter   "failure" notification), consume ERR, and flush the connection. */
129251881Speterstatic svn_error_t *
130251881Speterlog_fail_and_flush(svn_error_t *err, server_baton_t *server,
131251881Speter                   svn_ra_svn_conn_t *conn, apr_pool_t *pool)
132251881Speter{
133251881Speter  svn_error_t *io_err;
134251881Speter
135299742Sdim  log_error(err, server);
136251881Speter  io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
137251881Speter  svn_error_clear(err);
138251881Speter  SVN_ERR(io_err);
139251881Speter  return svn_ra_svn__flush(conn, pool);
140251881Speter}
141251881Speter
142251881Speter/* Log a client command. */
143251881Speterstatic svn_error_t *log_command(server_baton_t *b,
144251881Speter                                svn_ra_svn_conn_t *conn,
145251881Speter                                apr_pool_t *pool,
146251881Speter                                const char *fmt, ...)
147251881Speter{
148251881Speter  const char *remote_host, *timestr, *log, *line;
149251881Speter  va_list ap;
150251881Speter  apr_size_t nbytes;
151251881Speter
152299742Sdim  if (b->logger == NULL)
153251881Speter    return SVN_NO_ERROR;
154251881Speter
155251881Speter  remote_host = svn_ra_svn_conn_remote_host(conn);
156251881Speter  timestr = svn_time_to_cstring(apr_time_now(), pool);
157251881Speter
158251881Speter  va_start(ap, fmt);
159251881Speter  log = apr_pvsprintf(pool, fmt, ap);
160251881Speter  va_end(ap);
161251881Speter
162251881Speter  line = apr_psprintf(pool, "%" APR_PID_T_FMT
163251881Speter                      " %s %s %s %s %s" APR_EOL_STR,
164251881Speter                      getpid(), timestr,
165251881Speter                      (remote_host ? remote_host : "-"),
166299742Sdim                      (b->client_info->user ? b->client_info->user : "-"),
167299742Sdim                      b->repository->repos_name, log);
168251881Speter  nbytes = strlen(line);
169251881Speter
170299742Sdim  return logger__write(b->logger, line, nbytes);
171251881Speter}
172251881Speter
173251881Speter/* Log an authz failure */
174251881Speterstatic svn_error_t *
175251881Speterlog_authz_denied(const char *path,
176251881Speter                 svn_repos_authz_access_t required,
177251881Speter                 server_baton_t *b,
178251881Speter                 apr_pool_t *pool)
179251881Speter{
180251881Speter  const char *timestr, *remote_host, *line;
181251881Speter
182299742Sdim  if (!b->logger)
183251881Speter    return SVN_NO_ERROR;
184251881Speter
185299742Sdim  if (!b->client_info || !b->client_info->user)
186251881Speter    return SVN_NO_ERROR;
187251881Speter
188251881Speter  timestr = svn_time_to_cstring(apr_time_now(), pool);
189299742Sdim  remote_host = b->client_info->remote_host;
190251881Speter
191251881Speter  line = apr_psprintf(pool, "%" APR_PID_T_FMT
192251881Speter                      " %s %s %s %s Authorization Failed %s%s %s" APR_EOL_STR,
193251881Speter                      getpid(), timestr,
194251881Speter                      (remote_host ? remote_host : "-"),
195299742Sdim                      b->client_info->user,
196299742Sdim                      b->repository->repos_name,
197251881Speter                      (required & svn_authz_recursive ? "recursive " : ""),
198251881Speter                      (required & svn_authz_write ? "write" : "read"),
199251881Speter                      (path && path[0] ? path : "/"));
200251881Speter
201299742Sdim  return logger__write(b->logger, line, strlen(line));
202251881Speter}
203251881Speter
204299742Sdim/* If CFG specifies a path to the password DB, read that DB through
205299742Sdim * CONFIG_POOL and store it in REPOSITORY->PWDB.
206299742Sdim */
207299742Sdimstatic svn_error_t *
208299742Sdimload_pwdb_config(repository_t *repository,
209299742Sdim                 svn_config_t *cfg,
210299742Sdim                 svn_repos__config_pool_t *config_pool,
211299742Sdim                 apr_pool_t *pool)
212251881Speter{
213251881Speter  const char *pwdb_path;
214251881Speter  svn_error_t *err;
215251881Speter
216299742Sdim  svn_config_get(cfg, &pwdb_path,
217299742Sdim                 SVN_CONFIG_SECTION_GENERAL,
218251881Speter                 SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
219251881Speter
220299742Sdim  repository->pwdb = NULL;
221251881Speter  if (pwdb_path)
222251881Speter    {
223251881Speter      pwdb_path = svn_dirent_internal_style(pwdb_path, pool);
224299742Sdim      pwdb_path = svn_dirent_join(repository->base, pwdb_path, pool);
225251881Speter
226299742Sdim      err = svn_repos__config_pool_get(&repository->pwdb, NULL, config_pool,
227299742Sdim                                       pwdb_path, TRUE, FALSE,
228299742Sdim                                       repository->repos, pool);
229251881Speter      if (err)
230251881Speter        {
231251881Speter          /* Because it may be possible to read the pwdb file with some
232251881Speter             access methods and not others, ignore errors reading the pwdb
233251881Speter             file and just don't present password authentication as an
234251881Speter             option.  Also, some authentications (e.g. --tunnel) can
235251881Speter             proceed without it anyway.
236251881Speter
237251881Speter             ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked
238251881Speter             ### for here.  That seems to have been introduced in r856914,
239251881Speter             ### and only in r870942 was the APR_EACCES check introduced. */
240251881Speter          if (err->apr_err != SVN_ERR_BAD_FILENAME
241251881Speter              && ! APR_STATUS_IS_EACCES(err->apr_err))
242251881Speter            {
243299742Sdim              return svn_error_create(SVN_ERR_AUTHN_FAILED, err, NULL);
244251881Speter            }
245251881Speter          else
246251881Speter            /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */
247251881Speter            svn_error_clear(err);
248251881Speter        }
249251881Speter    }
250251881Speter
251251881Speter  return SVN_NO_ERROR;
252251881Speter}
253251881Speter
254251881Speter/* Canonicalize *ACCESS_FILE based on the type of argument.  Results are
255299742Sdim * placed in *ACCESS_FILE.  REPOSITORY is used to convert relative paths to
256251881Speter * absolute paths rooted at the server root.  REPOS_ROOT is used to calculate
257251881Speter * an absolute URL for repos-relative URLs. */
258251881Speterstatic svn_error_t *
259299742Sdimcanonicalize_access_file(const char **access_file, repository_t *repository,
260251881Speter                         const char *repos_root, apr_pool_t *pool)
261251881Speter{
262251881Speter  if (svn_path_is_url(*access_file))
263251881Speter    {
264251881Speter      *access_file = svn_uri_canonicalize(*access_file, pool);
265251881Speter    }
266251881Speter  else if (svn_path_is_repos_relative_url(*access_file))
267251881Speter    {
268251881Speter      const char *repos_root_url;
269251881Speter
270251881Speter      SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_root_url, repos_root,
271251881Speter                                               pool));
272251881Speter      SVN_ERR(svn_path_resolve_repos_relative_url(access_file, *access_file,
273251881Speter                                                  repos_root_url, pool));
274251881Speter      *access_file = svn_uri_canonicalize(*access_file, pool);
275251881Speter    }
276251881Speter  else
277251881Speter    {
278251881Speter      *access_file = svn_dirent_internal_style(*access_file, pool);
279299742Sdim      *access_file = svn_dirent_join(repository->base, *access_file, pool);
280251881Speter    }
281251881Speter
282251881Speter  return SVN_NO_ERROR;
283251881Speter}
284251881Speter
285299742Sdim/* Load the authz database for the listening server through AUTHZ_POOL
286299742Sdim   based on the entries in the SERVER struct.
287299742Sdim
288299742Sdim   SERVER and CONN must not be NULL. The real errors will be logged with
289299742Sdim   SERVER and CONN but return generic errors to the client. */
290299742Sdimstatic svn_error_t *
291299742Sdimload_authz_config(repository_t *repository,
292299742Sdim                  const char *repos_root,
293299742Sdim                  svn_config_t *cfg,
294299742Sdim                  svn_repos__authz_pool_t *authz_pool,
295299742Sdim                  apr_pool_t *pool)
296251881Speter{
297251881Speter  const char *authzdb_path;
298251881Speter  const char *groupsdb_path;
299251881Speter  svn_error_t *err;
300251881Speter
301251881Speter  /* Read authz configuration. */
302299742Sdim  svn_config_get(cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
303251881Speter                 SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
304251881Speter
305299742Sdim  svn_config_get(cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL,
306251881Speter                 SVN_CONFIG_OPTION_GROUPS_DB, NULL);
307251881Speter
308251881Speter  if (authzdb_path)
309251881Speter    {
310251881Speter      const char *case_force_val;
311251881Speter
312251881Speter      /* Canonicalize and add the base onto the authzdb_path (if needed). */
313299742Sdim      err = canonicalize_access_file(&authzdb_path, repository,
314251881Speter                                     repos_root, pool);
315251881Speter
316251881Speter      /* Same for the groupsdb_path if it is present. */
317251881Speter      if (groupsdb_path && !err)
318299742Sdim        err = canonicalize_access_file(&groupsdb_path, repository,
319251881Speter                                       repos_root, pool);
320251881Speter
321251881Speter      if (!err)
322299742Sdim        err = svn_repos__authz_pool_get(&repository->authzdb, authz_pool,
323299742Sdim                                        authzdb_path, groupsdb_path, TRUE,
324299742Sdim                                        repository->repos, pool);
325251881Speter
326251881Speter      if (err)
327299742Sdim        return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, err, NULL);
328251881Speter
329251881Speter      /* Are we going to be case-normalizing usernames when we consult
330251881Speter       * this authz file? */
331299742Sdim      svn_config_get(cfg, &case_force_val,
332299742Sdim                     SVN_CONFIG_SECTION_GENERAL,
333251881Speter                     SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL);
334251881Speter      if (case_force_val)
335251881Speter        {
336251881Speter          if (strcmp(case_force_val, "upper") == 0)
337299742Sdim            repository->username_case = CASE_FORCE_UPPER;
338251881Speter          else if (strcmp(case_force_val, "lower") == 0)
339299742Sdim            repository->username_case = CASE_FORCE_LOWER;
340251881Speter          else
341299742Sdim            repository->username_case = CASE_ASIS;
342251881Speter        }
343251881Speter    }
344251881Speter  else
345251881Speter    {
346299742Sdim      repository->authzdb = NULL;
347299742Sdim      repository->username_case = CASE_ASIS;
348251881Speter    }
349251881Speter
350251881Speter  return SVN_NO_ERROR;
351251881Speter}
352251881Speter
353299742Sdim/* If ERROR is a AUTH* error as returned by load_pwdb_config or
354299742Sdim * load_authz_config, write it to SERVER's log file.
355299742Sdim * Return a sanitized version of ERROR.
356299742Sdim */
357299742Sdimstatic svn_error_t *
358299742Sdimhandle_config_error(svn_error_t *error,
359299742Sdim                    server_baton_t *server)
360299742Sdim{
361299742Sdim  if (   error
362299742Sdim      && (   error->apr_err == SVN_ERR_AUTHZ_INVALID_CONFIG
363299742Sdim          || error->apr_err == SVN_ERR_AUTHN_FAILED))
364299742Sdim    {
365299742Sdim      apr_status_t apr_err = error->apr_err;
366299742Sdim      log_error(error, server);
367299742Sdim
368299742Sdim      /* Now that we've logged the error, clear it and return a
369299742Sdim       * nice, generic error to the user:
370299742Sdim       * http://subversion.tigris.org/issues/show_bug.cgi?id=2271 */
371299742Sdim      svn_error_clear(error);
372299742Sdim      return svn_error_create(apr_err, NULL, NULL);
373299742Sdim    }
374299742Sdim
375299742Sdim  return error;
376299742Sdim}
377299742Sdim
378251881Speter/* Set *FS_PATH to the portion of URL that is the path within the
379251881Speter   repository, if URL is inside REPOS_URL (if URL is not inside
380251881Speter   REPOS_URL, then error, with the effect on *FS_PATH undefined).
381251881Speter
382251881Speter   If the resultant fs path would be the empty string (i.e., URL and
383251881Speter   REPOS_URL are the same), then set *FS_PATH to "/".
384251881Speter
385251881Speter   Assume that REPOS_URL and URL are already URI-decoded. */
386251881Speterstatic svn_error_t *get_fs_path(const char *repos_url, const char *url,
387251881Speter                                const char **fs_path)
388251881Speter{
389251881Speter  apr_size_t len;
390251881Speter
391251881Speter  len = strlen(repos_url);
392251881Speter  if (strncmp(url, repos_url, len) != 0)
393251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
394251881Speter                             "'%s' is not the same repository as '%s'",
395251881Speter                             url, repos_url);
396251881Speter  *fs_path = url + len;
397251881Speter  if (! **fs_path)
398251881Speter    *fs_path = "/";
399251881Speter
400251881Speter  return SVN_NO_ERROR;
401251881Speter}
402251881Speter
403251881Speter/* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
404251881Speter
405251881Speter/* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else
406251881Speter   converts it to lower case. */
407251881Speterstatic void convert_case(char *text, svn_boolean_t to_uppercase)
408251881Speter{
409251881Speter  char *c = text;
410251881Speter  while (*c)
411251881Speter    {
412251881Speter      *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
413251881Speter      ++c;
414251881Speter    }
415251881Speter}
416251881Speter
417251881Speter/* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
418251881Speter   the user described in BATON according to the authz rules in BATON.
419251881Speter   Use POOL for temporary allocations only.  If no authz rules are
420251881Speter   present in BATON, grant access by default. */
421251881Speterstatic svn_error_t *authz_check_access(svn_boolean_t *allowed,
422251881Speter                                       const char *path,
423251881Speter                                       svn_repos_authz_access_t required,
424251881Speter                                       server_baton_t *b,
425251881Speter                                       apr_pool_t *pool)
426251881Speter{
427299742Sdim  repository_t *repository = b->repository;
428299742Sdim  client_info_t *client_info = b->client_info;
429299742Sdim
430251881Speter  /* If authz cannot be performed, grant access.  This is NOT the same
431251881Speter     as the default policy when authz is performed on a path with no
432251881Speter     rules.  In the latter case, the default is to deny access, and is
433251881Speter     set by svn_repos_authz_check_access. */
434299742Sdim  if (!repository->authzdb)
435251881Speter    {
436251881Speter      *allowed = TRUE;
437251881Speter      return SVN_NO_ERROR;
438251881Speter    }
439251881Speter
440251881Speter  /* If the authz request is for the empty path (ie. ""), replace it
441251881Speter     with the root path.  This happens because of stripping done at
442251881Speter     various levels in svnserve that remove the leading / on an
443251881Speter     absolute path. Passing such a malformed path to the authz
444251881Speter     routines throws them into an infinite loop and makes them miss
445251881Speter     ACLs. */
446251881Speter  if (path)
447251881Speter    path = svn_fspath__canonicalize(path, pool);
448251881Speter
449251881Speter  /* If we have a username, and we've not yet used it + any username
450251881Speter     case normalization that might be requested to determine "the
451251881Speter     username we used for authz purposes", do so now. */
452299742Sdim  if (client_info->user && (! client_info->authz_user))
453251881Speter    {
454299742Sdim      char *authz_user = apr_pstrdup(b->pool, client_info->user);
455299742Sdim      if (repository->username_case == CASE_FORCE_UPPER)
456251881Speter        convert_case(authz_user, TRUE);
457299742Sdim      else if (repository->username_case == CASE_FORCE_LOWER)
458251881Speter        convert_case(authz_user, FALSE);
459299742Sdim
460299742Sdim      client_info->authz_user = authz_user;
461251881Speter    }
462251881Speter
463299742Sdim  SVN_ERR(svn_repos_authz_check_access(repository->authzdb,
464299742Sdim                                       repository->authz_repos_name,
465299742Sdim                                       path, client_info->authz_user,
466299742Sdim                                       required, allowed, pool));
467251881Speter  if (!*allowed)
468299742Sdim    SVN_ERR(log_authz_denied(path, required, b, pool));
469251881Speter
470251881Speter  return SVN_NO_ERROR;
471251881Speter}
472251881Speter
473251881Speter/* Set *ALLOWED to TRUE if PATH is readable by the user described in
474251881Speter * BATON.  Use POOL for temporary allocations only.  ROOT is not used.
475251881Speter * Implements the svn_repos_authz_func_t interface.
476251881Speter */
477251881Speterstatic svn_error_t *authz_check_access_cb(svn_boolean_t *allowed,
478251881Speter                                          svn_fs_root_t *root,
479251881Speter                                          const char *path,
480251881Speter                                          void *baton,
481251881Speter                                          apr_pool_t *pool)
482251881Speter{
483251881Speter  authz_baton_t *sb = baton;
484251881Speter
485251881Speter  return authz_check_access(allowed, path, svn_authz_read,
486299742Sdim                            sb->server, pool);
487251881Speter}
488251881Speter
489251881Speter/* If authz is enabled in the specified BATON, return a read authorization
490251881Speter   function. Otherwise, return NULL. */
491251881Speterstatic svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton)
492251881Speter{
493299742Sdim  if (baton->repository->authzdb)
494251881Speter     return authz_check_access_cb;
495251881Speter  return NULL;
496251881Speter}
497251881Speter
498251881Speter/* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
499251881Speter * according to the state in BATON.  Use POOL for temporary
500251881Speter * allocations only.  ROOT is not used.  Implements the
501251881Speter * svn_repos_authz_callback_t interface.
502251881Speter */
503251881Speterstatic svn_error_t *authz_commit_cb(svn_repos_authz_access_t required,
504251881Speter                                    svn_boolean_t *allowed,
505251881Speter                                    svn_fs_root_t *root,
506251881Speter                                    const char *path,
507251881Speter                                    void *baton,
508251881Speter                                    apr_pool_t *pool)
509251881Speter{
510251881Speter  authz_baton_t *sb = baton;
511251881Speter
512299742Sdim  return authz_check_access(allowed, path, required, sb->server, pool);
513251881Speter}
514251881Speter
515299742Sdim/* Return the access level specified for OPTION in CFG.  If no such
516299742Sdim * setting exists, use DEF.  If READ_ONLY is set, unconditionally disable
517299742Sdim * write access.
518299742Sdim */
519299742Sdimstatic enum access_type
520299742Sdimget_access(svn_config_t *cfg,
521299742Sdim           const char *option,
522299742Sdim           const char *def,
523299742Sdim           svn_boolean_t read_only)
524251881Speter{
525251881Speter  enum access_type result;
526299742Sdim  const char *val;
527251881Speter
528299742Sdim  svn_config_get(cfg, &val, SVN_CONFIG_SECTION_GENERAL, option, def);
529251881Speter  result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
530251881Speter            strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
531299742Sdim
532299742Sdim  return result == WRITE_ACCESS && read_only ? READ_ACCESS : result;
533251881Speter}
534251881Speter
535299742Sdim/* Set the *_ACCESS members in REPOSITORY according to the settings in
536299742Sdim * CFG.  If READ_ONLY is set, unconditionally disable write access.
537299742Sdim */
538299742Sdimstatic void
539299742Sdimset_access(repository_t *repository,
540299742Sdim           svn_config_t *cfg,
541299742Sdim           svn_boolean_t read_only)
542251881Speter{
543299742Sdim  repository->auth_access = get_access(cfg, SVN_CONFIG_OPTION_AUTH_ACCESS,
544299742Sdim                                       "write", read_only);
545299742Sdim  repository->anon_access = get_access(cfg, SVN_CONFIG_OPTION_ANON_ACCESS,
546299742Sdim                                       "read", read_only);
547251881Speter}
548251881Speter
549299742Sdim/* Return the access level for the user in B.
550299742Sdim */
551299742Sdimstatic enum access_type
552299742Sdimcurrent_access(server_baton_t *b)
553299742Sdim{
554299742Sdim  return b->client_info->user ? b->repository->auth_access
555299742Sdim                              : b->repository->anon_access;
556299742Sdim}
557299742Sdim
558251881Speter/* Send authentication mechs for ACCESS_TYPE to the client.  If NEEDS_USERNAME
559251881Speter   is true, don't send anonymous mech even if that would give the desired
560251881Speter   access. */
561251881Speterstatic svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
562251881Speter                               server_baton_t *b, enum access_type required,
563251881Speter                               svn_boolean_t needs_username)
564251881Speter{
565299742Sdim  if (!needs_username && b->repository->anon_access >= required)
566251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, "ANONYMOUS"));
567299742Sdim  if (b->client_info->tunnel_user && b->repository->auth_access >= required)
568251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, "EXTERNAL"));
569299742Sdim  if (b->repository->pwdb && b->repository->auth_access >= required)
570251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, "CRAM-MD5"));
571251881Speter  return SVN_NO_ERROR;
572251881Speter}
573251881Speter
574251881Speter/* Context for cleanup handler. */
575251881Speterstruct cleanup_fs_access_baton
576251881Speter{
577251881Speter  svn_fs_t *fs;
578251881Speter  apr_pool_t *pool;
579251881Speter};
580251881Speter
581251881Speter/* Pool cleanup handler.  Make sure fs's access_t points to NULL when
582251881Speter   the command pool is destroyed. */
583251881Speterstatic apr_status_t cleanup_fs_access(void *data)
584251881Speter{
585251881Speter  svn_error_t *serr;
586251881Speter  struct cleanup_fs_access_baton *baton = data;
587251881Speter
588251881Speter  serr = svn_fs_set_access(baton->fs, NULL);
589251881Speter  if (serr)
590251881Speter    {
591251881Speter      apr_status_t apr_err = serr->apr_err;
592251881Speter      svn_error_clear(serr);
593251881Speter      return apr_err;
594251881Speter    }
595251881Speter
596251881Speter  return APR_SUCCESS;
597251881Speter}
598251881Speter
599251881Speter
600251881Speter/* Create an svn_fs_access_t in POOL for USER and associate it with
601251881Speter   B's filesystem.  Also, register a cleanup handler with POOL which
602251881Speter   de-associates the svn_fs_access_t from B's filesystem. */
603251881Speterstatic svn_error_t *
604251881Spetercreate_fs_access(server_baton_t *b, apr_pool_t *pool)
605251881Speter{
606251881Speter  svn_fs_access_t *fs_access;
607251881Speter  struct cleanup_fs_access_baton *cleanup_baton;
608251881Speter
609299742Sdim  if (!b->client_info->user)
610251881Speter    return SVN_NO_ERROR;
611251881Speter
612299742Sdim  SVN_ERR(svn_fs_create_access(&fs_access, b->client_info->user, pool));
613299742Sdim  SVN_ERR(svn_fs_set_access(b->repository->fs, fs_access));
614251881Speter
615251881Speter  cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
616251881Speter  cleanup_baton->pool = pool;
617299742Sdim  cleanup_baton->fs = b->repository->fs;
618251881Speter  apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
619251881Speter                            apr_pool_cleanup_null);
620251881Speter
621251881Speter  return SVN_NO_ERROR;
622251881Speter}
623251881Speter
624251881Speter/* Authenticate, once the client has chosen a mechanism and possibly
625251881Speter * sent an initial mechanism token.  On success, set *success to true
626251881Speter * and b->user to the authenticated username (or NULL for anonymous).
627251881Speter * On authentication failure, report failure to the client and set
628251881Speter * *success to FALSE.  On communications failure, return an error.
629251881Speter * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
630251881Speterstatic svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
631251881Speter                         const char *mech, const char *mecharg,
632251881Speter                         server_baton_t *b, enum access_type required,
633251881Speter                         svn_boolean_t needs_username,
634251881Speter                         svn_boolean_t *success)
635251881Speter{
636251881Speter  const char *user;
637251881Speter  *success = FALSE;
638251881Speter
639299742Sdim  if (b->repository->auth_access >= required
640299742Sdim      && b->client_info->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
641251881Speter    {
642299742Sdim      if (*mecharg && strcmp(mecharg, b->client_info->tunnel_user) != 0)
643251881Speter        return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure",
644251881Speter                                       "Requested username does not match");
645299742Sdim      b->client_info->user = b->client_info->tunnel_user;
646251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success"));
647251881Speter      *success = TRUE;
648251881Speter      return SVN_NO_ERROR;
649251881Speter    }
650251881Speter
651299742Sdim  if (b->repository->anon_access >= required
652251881Speter      && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
653251881Speter    {
654251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success"));
655251881Speter      *success = TRUE;
656251881Speter      return SVN_NO_ERROR;
657251881Speter    }
658251881Speter
659299742Sdim  if (b->repository->auth_access >= required
660299742Sdim      && b->repository->pwdb && strcmp(mech, "CRAM-MD5") == 0)
661251881Speter    {
662299742Sdim      SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->repository->pwdb,
663299742Sdim                                     &user, success));
664299742Sdim      b->client_info->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));
683299742Sdim  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)c)", b->repository->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
705299742Sdim  if (b->repository->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_repos_authz_access_t required,
736251881Speter                                   const char *path,
737251881Speter                                   svn_boolean_t needs_username)
738251881Speter{
739251881Speter  enum access_type req = (required & svn_authz_write) ?
740251881Speter    WRITE_ACCESS : READ_ACCESS;
741251881Speter  svn_boolean_t authorized;
742251881Speter  svn_error_t *err;
743251881Speter
744251881Speter  /* Get authz's opinion on the access. */
745299742Sdim  err = authz_check_access(&authorized, path, required, baton, pool);
746251881Speter
747251881Speter  /* If an error made lookup fail, deny access. */
748251881Speter  if (err)
749251881Speter    {
750299742Sdim      log_error(err, baton);
751251881Speter      svn_error_clear(err);
752251881Speter      return FALSE;
753251881Speter    }
754251881Speter
755251881Speter  /* If the required access is blanket-granted AND granted by authz
756251881Speter     AND we already have a username if one is required, then the
757251881Speter     lookup has succeeded. */
758251881Speter  if (current_access(baton) >= req
759251881Speter      && authorized
760299742Sdim      && (! needs_username || baton->client_info->user))
761251881Speter    return TRUE;
762251881Speter
763251881Speter  return FALSE;
764251881Speter}
765251881Speter
766251881Speter/* Check that the client has the REQUIRED access by consulting the
767251881Speter * authentication and authorization states stored in BATON.  If the
768251881Speter * client does not have the required access credentials, attempt to
769251881Speter * authenticate the client to get that access, using CONN for
770251881Speter * communication.
771251881Speter *
772251881Speter * This function is supposed to be called to handle the authentication
773251881Speter * half of a standard svn protocol reply.  If an error is returned, it
774251881Speter * probably means that the server can terminate the client connection
775251881Speter * with an apologetic error, as it implies an authentication failure.
776251881Speter *
777251881Speter * PATH and NEEDS_USERNAME are passed along to lookup_access, their
778251881Speter * behaviour is documented there.
779251881Speter */
780251881Speterstatic svn_error_t *must_have_access(svn_ra_svn_conn_t *conn,
781251881Speter                                     apr_pool_t *pool,
782251881Speter                                     server_baton_t *b,
783251881Speter                                     svn_repos_authz_access_t required,
784251881Speter                                     const char *path,
785251881Speter                                     svn_boolean_t needs_username)
786251881Speter{
787251881Speter  enum access_type req = (required & svn_authz_write) ?
788251881Speter    WRITE_ACCESS : READ_ACCESS;
789251881Speter
790251881Speter  /* See whether the user already has the required access.  If so,
791251881Speter     nothing needs to be done.  Create the FS access and send a
792251881Speter     trivial auth request. */
793299742Sdim  if (lookup_access(pool, b, required, path, needs_username))
794251881Speter    {
795251881Speter      SVN_ERR(create_fs_access(b, pool));
796251881Speter      return trivial_auth_request(conn, pool, b);
797251881Speter    }
798251881Speter
799251881Speter  /* If the required blanket access can be obtained by authenticating,
800251881Speter     try that.  Unfortunately, we can't tell until after
801251881Speter     authentication whether authz will work or not.  We force
802251881Speter     requiring a username because we need one to be able to check
803251881Speter     authz configuration again with a different user credentials than
804251881Speter     the first time round. */
805299742Sdim  if (b->client_info->user == NULL
806299742Sdim      && b->repository->auth_access >= req
807299742Sdim      && (b->client_info->tunnel_user || b->repository->pwdb
808299742Sdim          || b->repository->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. */
813299742Sdim  if (! lookup_access(pool, b, 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,
816299742Sdim                                                 NULL, NULL, b),
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);
969299742Sdim  SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev,
970299742Sdim                                      b->repository->repos,
971299742Sdim                                      b->repository->fs_path->data, target,
972299742Sdim                                      tgt_path, text_deltas, depth,
973299742Sdim                                      ignore_ancestry, send_copyfrom_args,
974251881Speter                                      editor, edit_baton,
975251881Speter                                      authz_check_access_cb_func(b),
976251881Speter                                      &ab, svn_ra_svn_zero_copy_limit(conn),
977251881Speter                                      pool));
978251881Speter
979251881Speter  rb.sb = b;
980299742Sdim  rb.repos_url = svn_path_uri_decode(b->repository->repos_url, pool);
981251881Speter  rb.report_baton = report_baton;
982251881Speter  rb.err = NULL;
983251881Speter  rb.entry_counter = 0;
984251881Speter  rb.only_empty_entries = TRUE;
985251881Speter  rb.from_rev = from_rev;
986251881Speter  if (from_rev)
987251881Speter    *from_rev = SVN_INVALID_REVNUM;
988251881Speter  err = svn_ra_svn__handle_commands2(conn, pool, report_commands, &rb, TRUE);
989251881Speter  if (err)
990251881Speter    {
991251881Speter      /* Network or protocol error while handling commands. */
992251881Speter      svn_error_clear(rb.err);
993251881Speter      return err;
994251881Speter    }
995251881Speter  else if (rb.err)
996251881Speter    {
997251881Speter      /* Some failure during the reporting or editing operations. */
998251881Speter      SVN_CMD_ERR(rb.err);
999251881Speter    }
1000251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1001251881Speter
1002251881Speter  if (only_empty_entry)
1003251881Speter    *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries;
1004251881Speter
1005251881Speter  return SVN_NO_ERROR;
1006251881Speter}
1007251881Speter
1008251881Speter/* --- MAIN COMMAND SET --- */
1009251881Speter
1010251881Speter/* Write out a list of property diffs.  PROPDIFFS is an array of svn_prop_t
1011251881Speter * values. */
1012251881Speterstatic svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
1013251881Speter                                     apr_pool_t *pool,
1014251881Speter                                     const apr_array_header_t *propdiffs)
1015251881Speter{
1016251881Speter  int i;
1017251881Speter
1018251881Speter  for (i = 0; i < propdiffs->nelts; ++i)
1019251881Speter    {
1020251881Speter      const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
1021251881Speter
1022251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "c(?s)",
1023251881Speter                                      prop->name, prop->value));
1024251881Speter    }
1025251881Speter
1026251881Speter  return SVN_NO_ERROR;
1027251881Speter}
1028251881Speter
1029251881Speter/* Write out a lock to the client. */
1030251881Speterstatic svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
1031251881Speter                               apr_pool_t *pool,
1032299742Sdim                               const svn_lock_t *lock)
1033251881Speter{
1034251881Speter  const char *cdate, *edate;
1035251881Speter
1036251881Speter  cdate = svn_time_to_cstring(lock->creation_date, pool);
1037251881Speter  edate = lock->expiration_date
1038251881Speter    ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
1039251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
1040251881Speter                                  lock->token, lock->owner, lock->comment,
1041251881Speter                                  cdate, edate));
1042251881Speter
1043251881Speter  return SVN_NO_ERROR;
1044251881Speter}
1045251881Speter
1046251881Speter/* ### This really belongs in libsvn_repos. */
1047251881Speter/* Get the explicit properties and/or inherited properties for a PATH in
1048251881Speter   ROOT, with hardcoded committed-info values. */
1049251881Speterstatic svn_error_t *
1050251881Speterget_props(apr_hash_t **props,
1051251881Speter          apr_array_header_t **iprops,
1052251881Speter          authz_baton_t *b,
1053251881Speter          svn_fs_root_t *root,
1054251881Speter          const char *path,
1055251881Speter          apr_pool_t *pool)
1056251881Speter{
1057251881Speter  /* Get the explicit properties. */
1058251881Speter  if (props)
1059251881Speter    {
1060251881Speter      svn_string_t *str;
1061251881Speter      svn_revnum_t crev;
1062251881Speter      const char *cdate, *cauthor, *uuid;
1063251881Speter
1064251881Speter      SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
1065251881Speter
1066251881Speter      /* Hardcode the values for the committed revision, date, and author. */
1067251881Speter      SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
1068251881Speter                                           path, pool));
1069299742Sdim      str = svn_string_createf(pool, "%ld", crev);
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));
1105299742Sdim  SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->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)));
1109299742Sdim  svn_stringbuf_set(b->repository->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));
1123299742Sdim  SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->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));
1141299742Sdim  SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repository->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)));
1163299742Sdim  SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repository->repos, rev,
1164299742Sdim                                            b->client_info->user,
1165251881Speter                                            name, old_value_p, value,
1166251881Speter                                            TRUE, TRUE,
1167251881Speter                                            authz_check_access_cb_func(b), &ab,
1168251881Speter                                            pool));
1169251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1170251881Speter
1171251881Speter  return SVN_NO_ERROR;
1172251881Speter}
1173251881Speter
1174251881Speterstatic svn_error_t *change_rev_prop2(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1175251881Speter                                     apr_array_header_t *params, void *baton)
1176251881Speter{
1177251881Speter  server_baton_t *b = baton;
1178251881Speter  svn_revnum_t rev;
1179251881Speter  const char *name;
1180251881Speter  svn_string_t *value;
1181251881Speter  const svn_string_t *const *old_value_p;
1182251881Speter  svn_string_t *old_value;
1183251881Speter  svn_boolean_t dont_care;
1184251881Speter
1185251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc(?s)(b?s)",
1186251881Speter                                  &rev, &name, &value,
1187251881Speter                                  &dont_care, &old_value));
1188251881Speter
1189251881Speter  /* Argument parsing. */
1190251881Speter  if (dont_care)
1191251881Speter    old_value_p = NULL;
1192251881Speter  else
1193251881Speter    old_value_p = (const svn_string_t *const *)&old_value;
1194251881Speter
1195251881Speter  /* Input validation. */
1196251881Speter  if (dont_care && old_value)
1197251881Speter    {
1198251881Speter      svn_error_t *err;
1199251881Speter      err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1200251881Speter                             "'previous-value' and 'dont-care' cannot both be "
1201251881Speter                             "set in 'change-rev-prop2' request");
1202251881Speter      return log_fail_and_flush(err, b, conn, pool);
1203251881Speter    }
1204251881Speter
1205251881Speter  /* Do it. */
1206251881Speter  SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool));
1207251881Speter
1208251881Speter  return SVN_NO_ERROR;
1209251881Speter}
1210251881Speter
1211251881Speterstatic svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1212251881Speter                                    apr_array_header_t *params, void *baton)
1213251881Speter{
1214251881Speter  server_baton_t *b = baton;
1215251881Speter  svn_revnum_t rev;
1216251881Speter  const char *name;
1217251881Speter  svn_string_t *value;
1218251881Speter
1219251881Speter  /* Because the revprop value was at one time mandatory, the usual
1220251881Speter     optional element pattern "(?s)" isn't used. */
1221251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc?s", &rev, &name, &value));
1222251881Speter
1223251881Speter  SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool));
1224251881Speter
1225251881Speter  return SVN_NO_ERROR;
1226251881Speter}
1227251881Speter
1228251881Speterstatic svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1229251881Speter                                 apr_array_header_t *params, void *baton)
1230251881Speter{
1231251881Speter  server_baton_t *b = baton;
1232251881Speter  svn_revnum_t rev;
1233251881Speter  apr_hash_t *props;
1234251881Speter  authz_baton_t ab;
1235251881Speter
1236251881Speter  ab.server = b;
1237251881Speter  ab.conn = conn;
1238251881Speter
1239251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
1240251881Speter  SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool)));
1241251881Speter
1242251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1243299742Sdim  SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repository->repos,
1244299742Sdim                                             rev,
1245299742Sdim                                             authz_check_access_cb_func(b),
1246299742Sdim                                             &ab, pool));
1247251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
1248251881Speter  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1249251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1250251881Speter  return SVN_NO_ERROR;
1251251881Speter}
1252251881Speter
1253251881Speterstatic svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1254251881Speter                             apr_array_header_t *params, void *baton)
1255251881Speter{
1256251881Speter  server_baton_t *b = baton;
1257251881Speter  svn_revnum_t rev;
1258251881Speter  const char *name;
1259251881Speter  svn_string_t *value;
1260251881Speter  authz_baton_t ab;
1261251881Speter
1262251881Speter  ab.server = b;
1263251881Speter  ab.conn = conn;
1264251881Speter
1265251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc", &rev, &name));
1266251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
1267251881Speter                      svn_log__rev_prop(rev, name, pool)));
1268251881Speter
1269251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1270299742Sdim  SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repository->repos, rev,
1271299742Sdim                                         name, authz_check_access_cb_func(b),
1272299742Sdim                                         &ab, pool));
1273251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(?s)", value));
1274251881Speter  return SVN_NO_ERROR;
1275251881Speter}
1276251881Speter
1277251881Speterstatic svn_error_t *commit_done(const svn_commit_info_t *commit_info,
1278251881Speter                                void *baton, apr_pool_t *pool)
1279251881Speter{
1280251881Speter  commit_callback_baton_t *ccb = baton;
1281251881Speter
1282251881Speter  *ccb->new_rev = commit_info->revision;
1283251881Speter  *ccb->date = commit_info->date
1284251881Speter    ? apr_pstrdup(ccb->pool, commit_info->date): NULL;
1285251881Speter  *ccb->author = commit_info->author
1286251881Speter    ? apr_pstrdup(ccb->pool, commit_info->author) : NULL;
1287251881Speter  *ccb->post_commit_err = commit_info->post_commit_err
1288251881Speter    ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL;
1289251881Speter  return SVN_NO_ERROR;
1290251881Speter}
1291251881Speter
1292251881Speter/* Add the LOCK_TOKENS (if any) to the filesystem access context,
1293251881Speter * checking path authorizations using the state in SB as we go.
1294251881Speter * LOCK_TOKENS is an array of svn_ra_svn_item_t structs.  Return a
1295251881Speter * client error if LOCK_TOKENS is not a list of lists.  If a lock
1296251881Speter * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
1297251881Speter * to the client.  Use POOL for temporary allocations only.
1298251881Speter */
1299299742Sdimstatic svn_error_t *add_lock_tokens(const apr_array_header_t *lock_tokens,
1300251881Speter                                    server_baton_t *sb,
1301251881Speter                                    apr_pool_t *pool)
1302251881Speter{
1303251881Speter  int i;
1304251881Speter  svn_fs_access_t *fs_access;
1305251881Speter
1306299742Sdim  SVN_ERR(svn_fs_get_access(&fs_access, sb->repository->fs));
1307251881Speter
1308251881Speter  /* If there is no access context, nowhere to add the tokens. */
1309251881Speter  if (! fs_access)
1310251881Speter    return SVN_NO_ERROR;
1311251881Speter
1312251881Speter  for (i = 0; i < lock_tokens->nelts; ++i)
1313251881Speter    {
1314251881Speter      const char *path, *token, *full_path;
1315251881Speter      svn_ra_svn_item_t *path_item, *token_item;
1316251881Speter      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i,
1317251881Speter                                               svn_ra_svn_item_t);
1318251881Speter      if (item->kind != SVN_RA_SVN_LIST)
1319251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1320251881Speter                                "Lock tokens aren't a list of lists");
1321251881Speter
1322251881Speter      path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1323251881Speter      if (path_item->kind != SVN_RA_SVN_STRING)
1324251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1325251881Speter                                "Lock path isn't a string");
1326251881Speter
1327251881Speter      token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
1328251881Speter      if (token_item->kind != SVN_RA_SVN_STRING)
1329251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1330251881Speter                                "Lock token isn't a string");
1331251881Speter
1332251881Speter      path = path_item->u.string->data;
1333299742Sdim      full_path = svn_fspath__join(sb->repository->fs_path->data,
1334251881Speter                                   svn_relpath_canonicalize(path, pool),
1335251881Speter                                   pool);
1336251881Speter
1337299742Sdim      if (! lookup_access(pool, sb, svn_authz_write, full_path, TRUE))
1338251881Speter        return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
1339299742Sdim                                    sb);
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
1348299742Sdim/* Implements svn_fs_lock_callback_t. */
1349299742Sdimstatic svn_error_t *
1350299742Sdimlock_cb(void *baton,
1351299742Sdim        const char *path,
1352299742Sdim        const svn_lock_t *lock,
1353299742Sdim        svn_error_t *fs_err,
1354299742Sdim        apr_pool_t *pool)
1355299742Sdim{
1356299742Sdim  server_baton_t *sb = baton;
1357299742Sdim
1358299742Sdim  log_error(fs_err, sb);
1359299742Sdim
1360299742Sdim  return SVN_NO_ERROR;
1361299742Sdim}
1362299742Sdim
1363251881Speter/* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
1364251881Speter   LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */
1365251881Speterstatic svn_error_t *unlock_paths(const apr_array_header_t *lock_tokens,
1366251881Speter                                 server_baton_t *sb,
1367251881Speter                                 apr_pool_t *pool)
1368251881Speter{
1369251881Speter  int i;
1370299742Sdim  apr_pool_t *subpool = svn_pool_create(pool);
1371299742Sdim  apr_hash_t *targets = apr_hash_make(subpool);
1372299742Sdim  svn_error_t *err;
1373251881Speter
1374251881Speter  for (i = 0; i < lock_tokens->nelts; ++i)
1375251881Speter    {
1376251881Speter      svn_ra_svn_item_t *item, *path_item, *token_item;
1377251881Speter      const char *path, *token, *full_path;
1378251881Speter
1379251881Speter      item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
1380251881Speter      path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1381251881Speter      token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
1382251881Speter
1383251881Speter      path = path_item->u.string->data;
1384299742Sdim      full_path = svn_fspath__join(sb->repository->fs_path->data,
1385299742Sdim                                   svn_relpath_canonicalize(path, subpool),
1386299742Sdim                                   subpool);
1387251881Speter      token = token_item->u.string->data;
1388299742Sdim      svn_hash_sets(targets, full_path, token);
1389299742Sdim    }
1390251881Speter
1391251881Speter
1392299742Sdim  /* The lock may have become defunct after the commit, so ignore such
1393299742Sdim     errors. */
1394299742Sdim  err = svn_repos_fs_unlock_many(sb->repository->repos, targets, FALSE,
1395299742Sdim                                 lock_cb, sb, subpool, subpool);
1396299742Sdim  log_error(err, sb);
1397299742Sdim  svn_error_clear(err);
1398251881Speter
1399299742Sdim  svn_pool_destroy(subpool);
1400251881Speter
1401251881Speter  return SVN_NO_ERROR;
1402251881Speter}
1403251881Speter
1404251881Speterstatic svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1405251881Speter                           apr_array_header_t *params, void *baton)
1406251881Speter{
1407251881Speter  server_baton_t *b = baton;
1408299742Sdim  const char *log_msg,
1409251881Speter             *date = NULL,
1410251881Speter             *author = NULL,
1411251881Speter             *post_commit_err = NULL;
1412251881Speter  apr_array_header_t *lock_tokens;
1413251881Speter  svn_boolean_t keep_locks;
1414299742Sdim  apr_array_header_t *revprop_list;
1415251881Speter  apr_hash_t *revprop_table;
1416251881Speter  const svn_delta_editor_t *editor;
1417251881Speter  void *edit_baton;
1418251881Speter  svn_boolean_t aborted;
1419251881Speter  commit_callback_baton_t ccb;
1420251881Speter  svn_revnum_t new_rev;
1421251881Speter  authz_baton_t ab;
1422251881Speter
1423251881Speter  ab.server = b;
1424251881Speter  ab.conn = conn;
1425251881Speter
1426251881Speter  if (params->nelts == 1)
1427251881Speter    {
1428251881Speter      /* Clients before 1.2 don't send lock-tokens, keep-locks,
1429251881Speter         and rev-props fields. */
1430251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &log_msg));
1431251881Speter      lock_tokens = NULL;
1432251881Speter      keep_locks = TRUE;
1433251881Speter      revprop_list = NULL;
1434251881Speter    }
1435251881Speter  else
1436251881Speter    {
1437251881Speter      /* Clients before 1.5 don't send the rev-props field. */
1438251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "clb?l", &log_msg,
1439251881Speter                                      &lock_tokens, &keep_locks,
1440251881Speter                                      &revprop_list));
1441251881Speter    }
1442251881Speter
1443251881Speter  /* The handling for locks is a little problematic, because the
1444251881Speter     protocol won't let us send several auth requests once one has
1445251881Speter     succeeded.  So we request write access and a username before
1446251881Speter     adding tokens (if we have any), and subsequently fail if a lock
1447251881Speter     violates authz. */
1448251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
1449251881Speter                           NULL,
1450251881Speter                           (lock_tokens && lock_tokens->nelts)));
1451251881Speter
1452251881Speter  /* Authorize the lock tokens and give them to the FS if we got
1453251881Speter     any. */
1454251881Speter  if (lock_tokens && lock_tokens->nelts)
1455299742Sdim    SVN_CMD_ERR(add_lock_tokens(lock_tokens, b, pool));
1456251881Speter
1457253734Speter  /* Ignore LOG_MSG, per the protocol.  See ra_svn_commit(). */
1458251881Speter  if (revprop_list)
1459251881Speter    SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table));
1460251881Speter  else
1461251881Speter    {
1462251881Speter      revprop_table = apr_hash_make(pool);
1463251881Speter      svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
1464251881Speter                    svn_string_create(log_msg, pool));
1465251881Speter    }
1466251881Speter
1467251881Speter  /* Get author from the baton, making sure clients can't circumvent
1468251881Speter     the authentication via the revision props. */
1469251881Speter  svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
1470299742Sdim                b->client_info->user
1471299742Sdim                   ? svn_string_create(b->client_info->user, pool)
1472299742Sdim                   : NULL);
1473251881Speter
1474251881Speter  ccb.pool = pool;
1475251881Speter  ccb.new_rev = &new_rev;
1476251881Speter  ccb.date = &date;
1477251881Speter  ccb.author = &author;
1478251881Speter  ccb.post_commit_err = &post_commit_err;
1479251881Speter  /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
1480251881Speter  SVN_CMD_ERR(svn_repos_get_commit_editor5
1481299742Sdim              (&editor, &edit_baton, b->repository->repos, NULL,
1482299742Sdim               svn_path_uri_decode(b->repository->repos_url, pool),
1483299742Sdim               b->repository->fs_path->data, revprop_table,
1484251881Speter               commit_done, &ccb,
1485251881Speter               authz_commit_cb, &ab, pool));
1486251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1487251881Speter  SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton,
1488251881Speter                                   &aborted, FALSE));
1489251881Speter  if (!aborted)
1490251881Speter    {
1491251881Speter      SVN_ERR(log_command(b, conn, pool, "%s",
1492251881Speter                          svn_log__commit(new_rev, pool)));
1493251881Speter      SVN_ERR(trivial_auth_request(conn, pool, b));
1494251881Speter
1495251881Speter      /* In tunnel mode, deltify before answering the client, because
1496251881Speter         answering may cause the client to terminate the connection
1497251881Speter         and thus kill the server.  But otherwise, deltify after
1498251881Speter         answering the client, to avoid user-visible delay. */
1499251881Speter
1500299742Sdim      if (b->client_info->tunnel)
1501299742Sdim        SVN_ERR(svn_fs_deltify_revision(b->repository->fs, new_rev, pool));
1502251881Speter
1503251881Speter      /* Unlock the paths. */
1504251881Speter      if (! keep_locks && lock_tokens && lock_tokens->nelts)
1505299742Sdim        SVN_ERR(unlock_paths(lock_tokens, b, pool));
1506251881Speter
1507251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)",
1508251881Speter                                      new_rev, date, author, post_commit_err));
1509251881Speter
1510299742Sdim      if (! b->client_info->tunnel)
1511299742Sdim        SVN_ERR(svn_fs_deltify_revision(b->repository->fs, new_rev, pool));
1512251881Speter    }
1513251881Speter  return SVN_NO_ERROR;
1514251881Speter}
1515251881Speter
1516251881Speterstatic svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1517251881Speter                             apr_array_header_t *params, void *baton)
1518251881Speter{
1519251881Speter  server_baton_t *b = baton;
1520251881Speter  const char *path, *full_path, *hex_digest;
1521251881Speter  svn_revnum_t rev;
1522251881Speter  svn_fs_root_t *root;
1523251881Speter  svn_stream_t *contents;
1524251881Speter  apr_hash_t *props = NULL;
1525251881Speter  apr_array_header_t *inherited_props;
1526251881Speter  svn_string_t write_str;
1527251881Speter  char buf[4096];
1528251881Speter  apr_size_t len;
1529251881Speter  svn_boolean_t want_props, want_contents;
1530251881Speter  apr_uint64_t wants_inherited_props;
1531251881Speter  svn_checksum_t *checksum;
1532251881Speter  svn_error_t *err, *write_err;
1533251881Speter  int i;
1534251881Speter  authz_baton_t ab;
1535251881Speter
1536251881Speter  ab.server = b;
1537251881Speter  ab.conn = conn;
1538251881Speter
1539251881Speter  /* Parse arguments. */
1540251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?B", &path, &rev,
1541251881Speter                                  &want_props, &want_contents,
1542251881Speter                                  &wants_inherited_props));
1543251881Speter
1544269847Speter  if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1545269847Speter    wants_inherited_props = FALSE;
1546269847Speter
1547299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data,
1548251881Speter                               svn_relpath_canonicalize(path, pool), pool);
1549251881Speter
1550251881Speter  /* Check authorizations */
1551251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1552251881Speter                           full_path, FALSE));
1553251881Speter
1554251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1555299742Sdim    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1556251881Speter
1557251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
1558251881Speter                      svn_log__get_file(full_path, rev,
1559251881Speter                                        want_contents, want_props, pool)));
1560251881Speter
1561251881Speter  /* Fetch the properties and a stream for the contents. */
1562299742Sdim  SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
1563251881Speter  SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
1564251881Speter                                   full_path, TRUE, pool));
1565251881Speter  hex_digest = svn_checksum_to_cstring_display(checksum, pool);
1566269847Speter
1567269847Speter  /* Fetch the file's explicit and/or inherited properties if
1568269847Speter     requested.  Although the wants-iprops boolean was added to the
1569269847Speter     protocol in 1.8 a standard 1.8 client never requests iprops. */
1570251881Speter  if (want_props || wants_inherited_props)
1571269847Speter    SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1572269847Speter                          wants_inherited_props ? &inherited_props : NULL,
1573269847Speter                          &ab, root, full_path,
1574251881Speter                          pool));
1575251881Speter  if (want_contents)
1576251881Speter    SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
1577251881Speter
1578251881Speter  /* Send successful command response with revision and props. */
1579251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success",
1580251881Speter                                  hex_digest, rev));
1581251881Speter  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1582251881Speter
1583251881Speter  if (wants_inherited_props)
1584251881Speter    {
1585251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
1586251881Speter
1587251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1588251881Speter      for (i = 0; i < inherited_props->nelts; i++)
1589251881Speter        {
1590251881Speter          svn_prop_inherited_item_t *iprop =
1591251881Speter            APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1592251881Speter
1593251881Speter          svn_pool_clear(iterpool);
1594251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1595251881Speter                                          iprop->path_or_url));
1596251881Speter          SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1597251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1598251881Speter                                          iprop->path_or_url));
1599251881Speter        }
1600251881Speter      svn_pool_destroy(iterpool);
1601251881Speter    }
1602251881Speter
1603251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1604251881Speter
1605251881Speter  /* Now send the file's contents. */
1606251881Speter  if (want_contents)
1607251881Speter    {
1608251881Speter      err = SVN_NO_ERROR;
1609251881Speter      while (1)
1610251881Speter        {
1611251881Speter          len = sizeof(buf);
1612299742Sdim          err = svn_stream_read_full(contents, buf, &len);
1613251881Speter          if (err)
1614251881Speter            break;
1615251881Speter          if (len > 0)
1616251881Speter            {
1617251881Speter              write_str.data = buf;
1618251881Speter              write_str.len = len;
1619251881Speter              SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str));
1620251881Speter            }
1621251881Speter          if (len < sizeof(buf))
1622251881Speter            {
1623251881Speter              err = svn_stream_close(contents);
1624251881Speter              break;
1625251881Speter            }
1626251881Speter        }
1627251881Speter      write_err = svn_ra_svn__write_cstring(conn, pool, "");
1628251881Speter      if (write_err)
1629251881Speter        {
1630251881Speter          svn_error_clear(err);
1631251881Speter          return write_err;
1632251881Speter        }
1633251881Speter      SVN_CMD_ERR(err);
1634251881Speter      SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1635251881Speter    }
1636251881Speter
1637251881Speter  return SVN_NO_ERROR;
1638251881Speter}
1639251881Speter
1640251881Speterstatic svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1641251881Speter                            apr_array_header_t *params, void *baton)
1642251881Speter{
1643251881Speter  server_baton_t *b = baton;
1644251881Speter  const char *path, *full_path;
1645251881Speter  svn_revnum_t rev;
1646251881Speter  apr_hash_t *entries, *props = NULL;
1647251881Speter  apr_array_header_t *inherited_props;
1648251881Speter  apr_hash_index_t *hi;
1649251881Speter  svn_fs_root_t *root;
1650251881Speter  apr_pool_t *subpool;
1651251881Speter  svn_boolean_t want_props, want_contents;
1652251881Speter  apr_uint64_t wants_inherited_props;
1653251881Speter  apr_uint64_t dirent_fields;
1654251881Speter  apr_array_header_t *dirent_fields_list = NULL;
1655251881Speter  svn_ra_svn_item_t *elt;
1656251881Speter  int i;
1657251881Speter  authz_baton_t ab;
1658251881Speter
1659251881Speter  ab.server = b;
1660251881Speter  ab.conn = conn;
1661251881Speter
1662251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?l?B", &path, &rev,
1663251881Speter                                  &want_props, &want_contents,
1664251881Speter                                  &dirent_fields_list,
1665251881Speter                                  &wants_inherited_props));
1666251881Speter
1667269847Speter  if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1668269847Speter    wants_inherited_props = FALSE;
1669269847Speter
1670251881Speter  if (! dirent_fields_list)
1671251881Speter    {
1672251881Speter      dirent_fields = SVN_DIRENT_ALL;
1673251881Speter    }
1674251881Speter  else
1675251881Speter    {
1676251881Speter      dirent_fields = 0;
1677251881Speter
1678251881Speter      for (i = 0; i < dirent_fields_list->nelts; ++i)
1679251881Speter        {
1680251881Speter          elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t);
1681251881Speter
1682251881Speter          if (elt->kind != SVN_RA_SVN_WORD)
1683251881Speter            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1684251881Speter                                    "Dirent field not a string");
1685251881Speter
1686251881Speter          if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0)
1687251881Speter            dirent_fields |= SVN_DIRENT_KIND;
1688251881Speter          else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0)
1689251881Speter            dirent_fields |= SVN_DIRENT_SIZE;
1690251881Speter          else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0)
1691251881Speter            dirent_fields |= SVN_DIRENT_HAS_PROPS;
1692251881Speter          else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0)
1693251881Speter            dirent_fields |= SVN_DIRENT_CREATED_REV;
1694251881Speter          else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0)
1695251881Speter            dirent_fields |= SVN_DIRENT_TIME;
1696251881Speter          else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0)
1697251881Speter            dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
1698251881Speter        }
1699251881Speter    }
1700251881Speter
1701299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data,
1702251881Speter                               svn_relpath_canonicalize(path, pool), pool);
1703251881Speter
1704251881Speter  /* Check authorizations */
1705251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1706251881Speter                           full_path, FALSE));
1707251881Speter
1708251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1709299742Sdim    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1710251881Speter
1711251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
1712251881Speter                      svn_log__get_dir(full_path, rev,
1713251881Speter                                       want_contents, want_props,
1714251881Speter                                       dirent_fields, pool)));
1715251881Speter
1716251881Speter  /* Fetch the root of the appropriate revision. */
1717299742Sdim  SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
1718251881Speter
1719269847Speter  /* Fetch the directory's explicit and/or inherited properties if
1720269847Speter     requested.  Although the wants-iprops boolean was added to the
1721269847Speter     protocol in 1.8 a standard 1.8 client never requests iprops. */
1722251881Speter  if (want_props || wants_inherited_props)
1723269847Speter    SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1724269847Speter                          wants_inherited_props ? &inherited_props : NULL,
1725269847Speter                          &ab, root, full_path,
1726251881Speter                          pool));
1727251881Speter
1728289166Speter  /* Fetch the directories' entries before starting the response, to allow
1729289166Speter     proper error handling in cases like when FULL_PATH doesn't exist */
1730289166Speter  if (want_contents)
1731289166Speter      SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
1732289166Speter
1733251881Speter  /* Begin response ... */
1734251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev));
1735251881Speter  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1736251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!"));
1737251881Speter
1738251881Speter  /* Fetch the directory entries if requested and send them immediately. */
1739251881Speter  if (want_contents)
1740251881Speter    {
1741251881Speter      /* Use epoch for a placeholder for a missing date.  */
1742251881Speter      const char *missing_date = svn_time_to_cstring(0, pool);
1743251881Speter
1744251881Speter      /* Transform the hash table's FS entries into dirents.  This probably
1745251881Speter       * belongs in libsvn_repos. */
1746251881Speter      subpool = svn_pool_create(pool);
1747251881Speter      for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1748251881Speter        {
1749299742Sdim          const char *name = apr_hash_this_key(hi);
1750299742Sdim          svn_fs_dirent_t *fsent = apr_hash_this_val(hi);
1751251881Speter          const char *file_path;
1752251881Speter
1753251881Speter          /* The fields in the entry tuple.  */
1754251881Speter          svn_node_kind_t entry_kind = svn_node_none;
1755251881Speter          svn_filesize_t entry_size = 0;
1756251881Speter          svn_boolean_t has_props = FALSE;
1757251881Speter          /* If 'created rev' was not requested, send 0.  We can't use
1758251881Speter           * SVN_INVALID_REVNUM as the tuple field is not optional.
1759251881Speter           * See the email thread on dev@, 2012-03-28, subject
1760251881Speter           * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra",
1761251881Speter           * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */
1762251881Speter          svn_revnum_t created_rev = 0;
1763251881Speter          const char *cdate = NULL;
1764251881Speter          const char *last_author = NULL;
1765251881Speter
1766251881Speter          svn_pool_clear(subpool);
1767251881Speter
1768251881Speter          file_path = svn_fspath__join(full_path, name, subpool);
1769299742Sdim          if (! lookup_access(subpool, b, svn_authz_read, file_path, FALSE))
1770251881Speter            continue;
1771251881Speter
1772251881Speter          if (dirent_fields & SVN_DIRENT_KIND)
1773251881Speter              entry_kind = fsent->kind;
1774251881Speter
1775251881Speter          if (dirent_fields & SVN_DIRENT_SIZE)
1776251881Speter              if (entry_kind != svn_node_dir)
1777251881Speter                SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path,
1778251881Speter                                               subpool));
1779251881Speter
1780251881Speter          if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1781251881Speter            {
1782251881Speter              /* has_props */
1783299742Sdim              SVN_CMD_ERR(svn_fs_node_has_props(&has_props, root, file_path,
1784251881Speter                                               subpool));
1785251881Speter            }
1786251881Speter
1787251881Speter          if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1788251881Speter              || (dirent_fields & SVN_DIRENT_TIME)
1789251881Speter              || (dirent_fields & SVN_DIRENT_CREATED_REV))
1790251881Speter            {
1791251881Speter              /* created_rev, last_author, time */
1792251881Speter              SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev,
1793251881Speter                                                       &cdate,
1794251881Speter                                                       &last_author,
1795251881Speter                                                       root,
1796251881Speter                                                       file_path,
1797251881Speter                                                       subpool));
1798251881Speter            }
1799251881Speter
1800251881Speter          /* The client does not properly handle a missing CDATE. For
1801251881Speter             interoperability purposes, we must fill in some junk.
1802251881Speter
1803251881Speter             See libsvn_ra_svn/client.c:ra_svn_get_dir()  */
1804251881Speter          if (cdate == NULL)
1805251881Speter            cdate = missing_date;
1806251881Speter
1807251881Speter          /* Send the entry. */
1808251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
1809251881Speter                                          svn_node_kind_to_word(entry_kind),
1810251881Speter                                          (apr_uint64_t) entry_size,
1811251881Speter                                          has_props, created_rev,
1812251881Speter                                          cdate, last_author));
1813251881Speter        }
1814251881Speter      svn_pool_destroy(subpool);
1815251881Speter    }
1816251881Speter
1817251881Speter  if (wants_inherited_props)
1818251881Speter    {
1819251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
1820251881Speter
1821251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1822251881Speter      for (i = 0; i < inherited_props->nelts; i++)
1823251881Speter        {
1824251881Speter          svn_prop_inherited_item_t *iprop =
1825251881Speter            APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1826251881Speter
1827251881Speter          svn_pool_clear(iterpool);
1828251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1829251881Speter                                          iprop->path_or_url));
1830251881Speter          SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1831251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1832251881Speter                                          iprop->path_or_url));
1833251881Speter        }
1834251881Speter      svn_pool_destroy(iterpool);
1835251881Speter    }
1836251881Speter
1837251881Speter  /* Finish response. */
1838251881Speter  return svn_ra_svn__write_tuple(conn, pool, "!))");
1839251881Speter}
1840251881Speter
1841251881Speterstatic svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1842251881Speter                           apr_array_header_t *params, void *baton)
1843251881Speter{
1844251881Speter  server_baton_t *b = baton;
1845251881Speter  svn_revnum_t rev;
1846251881Speter  const char *target, *full_path, *depth_word;
1847251881Speter  svn_boolean_t recurse;
1848299742Sdim  svn_tristate_t send_copyfrom_args; /* Optional; default FALSE */
1849299742Sdim  svn_tristate_t ignore_ancestry; /* Optional; default FALSE */
1850251881Speter  /* Default to unknown.  Old clients won't send depth, but we'll
1851251881Speter     handle that by converting recurse if necessary. */
1852251881Speter  svn_depth_t depth = svn_depth_unknown;
1853251881Speter  svn_boolean_t is_checkout;
1854251881Speter
1855251881Speter  /* Parse the arguments. */
1856299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cb?w3?3", &rev, &target,
1857251881Speter                                  &recurse, &depth_word,
1858251881Speter                                  &send_copyfrom_args, &ignore_ancestry));
1859251881Speter  target = svn_relpath_canonicalize(target, pool);
1860251881Speter
1861251881Speter  if (depth_word)
1862251881Speter    depth = svn_depth_from_word(depth_word);
1863251881Speter  else
1864251881Speter    depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1865251881Speter
1866299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data, target, pool);
1867251881Speter  /* Check authorization and authenticate the user if necessary. */
1868251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
1869251881Speter
1870251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1871299742Sdim    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1872251881Speter
1873251881Speter  SVN_ERR(accept_report(&is_checkout, NULL,
1874251881Speter                        conn, pool, b, rev, target, NULL, TRUE,
1875251881Speter                        depth,
1876299742Sdim                        (send_copyfrom_args == svn_tristate_true),
1877299742Sdim                        (ignore_ancestry == svn_tristate_true)));
1878251881Speter  if (is_checkout)
1879251881Speter    {
1880251881Speter      SVN_ERR(log_command(b, conn, pool, "%s",
1881251881Speter                          svn_log__checkout(full_path, rev,
1882251881Speter                                            depth, pool)));
1883251881Speter    }
1884251881Speter  else
1885251881Speter    {
1886251881Speter      SVN_ERR(log_command(b, conn, pool, "%s",
1887251881Speter                          svn_log__update(full_path, rev, depth,
1888299742Sdim                                          (send_copyfrom_args
1889299742Sdim                                           == svn_tristate_true),
1890299742Sdim                                          pool)));
1891251881Speter    }
1892251881Speter
1893251881Speter  return SVN_NO_ERROR;
1894251881Speter}
1895251881Speter
1896251881Speterstatic svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1897251881Speter                               apr_array_header_t *params, void *baton)
1898251881Speter{
1899251881Speter  server_baton_t *b = baton;
1900251881Speter  svn_revnum_t rev;
1901251881Speter  const char *target, *depth_word;
1902251881Speter  const char *switch_url, *switch_path;
1903251881Speter  svn_boolean_t recurse;
1904251881Speter  /* Default to unknown.  Old clients won't send depth, but we'll
1905251881Speter     handle that by converting recurse if necessary. */
1906251881Speter  svn_depth_t depth = svn_depth_unknown;
1907299742Sdim  svn_tristate_t send_copyfrom_args; /* Optional; default FALSE */
1908299742Sdim  svn_tristate_t ignore_ancestry; /* Optional; default TRUE */
1909251881Speter
1910251881Speter  /* Parse the arguments. */
1911299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbc?w?33", &rev, &target,
1912251881Speter                                  &recurse, &switch_url, &depth_word,
1913251881Speter                                  &send_copyfrom_args, &ignore_ancestry));
1914251881Speter  target = svn_relpath_canonicalize(target, pool);
1915251881Speter  switch_url = svn_uri_canonicalize(switch_url, pool);
1916251881Speter
1917251881Speter  if (depth_word)
1918251881Speter    depth = svn_depth_from_word(depth_word);
1919251881Speter  else
1920251881Speter    depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1921251881Speter
1922251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1923251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1924299742Sdim    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1925251881Speter
1926299742Sdim  SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url,
1927299742Sdim                                              pool),
1928251881Speter                          svn_path_uri_decode(switch_url, pool),
1929251881Speter                          &switch_path));
1930251881Speter
1931251881Speter  {
1932299742Sdim    const char *full_path = svn_fspath__join(b->repository->fs_path->data,
1933299742Sdim                                             target, pool);
1934251881Speter    SVN_ERR(log_command(b, conn, pool, "%s",
1935251881Speter                        svn_log__switch(full_path, switch_path, rev,
1936251881Speter                                        depth, pool)));
1937251881Speter  }
1938251881Speter
1939251881Speter  return accept_report(NULL, NULL,
1940251881Speter                       conn, pool, b, rev, target, switch_path, TRUE,
1941251881Speter                       depth,
1942299742Sdim                       (send_copyfrom_args == svn_tristate_true),
1943299742Sdim                       (ignore_ancestry != svn_tristate_false));
1944251881Speter}
1945251881Speter
1946251881Speterstatic svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1947251881Speter                           apr_array_header_t *params, void *baton)
1948251881Speter{
1949251881Speter  server_baton_t *b = baton;
1950251881Speter  svn_revnum_t rev;
1951251881Speter  const char *target, *depth_word;
1952251881Speter  svn_boolean_t recurse;
1953251881Speter  /* Default to unknown.  Old clients won't send depth, but we'll
1954251881Speter     handle that by converting recurse if necessary. */
1955251881Speter  svn_depth_t depth = svn_depth_unknown;
1956251881Speter
1957251881Speter  /* Parse the arguments. */
1958251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cb?(?r)?w",
1959251881Speter                                  &target, &recurse, &rev, &depth_word));
1960251881Speter  target = svn_relpath_canonicalize(target, pool);
1961251881Speter
1962251881Speter  if (depth_word)
1963251881Speter    depth = svn_depth_from_word(depth_word);
1964251881Speter  else
1965251881Speter    depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
1966251881Speter
1967251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
1968251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
1969299742Sdim    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1970251881Speter
1971251881Speter  {
1972299742Sdim    const char *full_path = svn_fspath__join(b->repository->fs_path->data,
1973299742Sdim                                             target, pool);
1974251881Speter    SVN_ERR(log_command(b, conn, pool, "%s",
1975251881Speter                        svn_log__status(full_path, rev, depth, pool)));
1976251881Speter  }
1977251881Speter
1978251881Speter  return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
1979251881Speter                       depth, FALSE, FALSE);
1980251881Speter}
1981251881Speter
1982251881Speterstatic svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1983251881Speter                         apr_array_header_t *params, void *baton)
1984251881Speter{
1985251881Speter  server_baton_t *b = baton;
1986251881Speter  svn_revnum_t rev;
1987251881Speter  const char *target, *versus_url, *versus_path, *depth_word;
1988251881Speter  svn_boolean_t recurse, ignore_ancestry;
1989251881Speter  svn_boolean_t text_deltas;
1990251881Speter  /* Default to unknown.  Old clients won't send depth, but we'll
1991251881Speter     handle that by converting recurse if necessary. */
1992251881Speter  svn_depth_t depth = svn_depth_unknown;
1993251881Speter
1994251881Speter  /* Parse the arguments. */
1995251881Speter  if (params->nelts == 5)
1996251881Speter    {
1997251881Speter      /* Clients before 1.4 don't send the text_deltas boolean or depth. */
1998251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
1999251881Speter                                      &recurse, &ignore_ancestry, &versus_url));
2000251881Speter      text_deltas = TRUE;
2001251881Speter      depth_word = NULL;
2002251881Speter    }
2003251881Speter  else
2004251881Speter    {
2005251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbcb?w",
2006251881Speter                                      &rev, &target, &recurse,
2007251881Speter                                      &ignore_ancestry, &versus_url,
2008251881Speter                                      &text_deltas, &depth_word));
2009251881Speter    }
2010251881Speter  target = svn_relpath_canonicalize(target, pool);
2011251881Speter  versus_url = svn_uri_canonicalize(versus_url, pool);
2012251881Speter
2013251881Speter  if (depth_word)
2014251881Speter    depth = svn_depth_from_word(depth_word);
2015251881Speter  else
2016251881Speter    depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
2017251881Speter
2018251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2019251881Speter
2020251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
2021299742Sdim    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2022299742Sdim  SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url,
2023299742Sdim                                              pool),
2024251881Speter                          svn_path_uri_decode(versus_url, pool),
2025251881Speter                          &versus_path));
2026251881Speter
2027251881Speter  {
2028299742Sdim    const char *full_path = svn_fspath__join(b->repository->fs_path->data,
2029299742Sdim                                             target, pool);
2030251881Speter    svn_revnum_t from_rev;
2031251881Speter    SVN_ERR(accept_report(NULL, &from_rev,
2032251881Speter                          conn, pool, b, rev, target, versus_path,
2033251881Speter                          text_deltas, depth, FALSE, ignore_ancestry));
2034251881Speter    SVN_ERR(log_command(b, conn, pool, "%s",
2035251881Speter                        svn_log__diff(full_path, from_rev, versus_path,
2036251881Speter                                      rev, depth, ignore_ancestry,
2037251881Speter                                      pool)));
2038251881Speter  }
2039251881Speter  return SVN_NO_ERROR;
2040251881Speter}
2041251881Speter
2042251881Speter/* Regardless of whether a client's capabilities indicate an
2043251881Speter   understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
2044251881Speter   we provide a response.
2045251881Speter
2046251881Speter   ASSUMPTION: When performing a 'merge' with two URLs at different
2047251881Speter   revisions, the client will call this command more than once. */
2048251881Speterstatic svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2049251881Speter                                  apr_array_header_t *params, void *baton)
2050251881Speter{
2051251881Speter  server_baton_t *b = baton;
2052251881Speter  svn_revnum_t rev;
2053251881Speter  apr_array_header_t *paths, *canonical_paths;
2054251881Speter  svn_mergeinfo_catalog_t mergeinfo;
2055251881Speter  int i;
2056251881Speter  apr_hash_index_t *hi;
2057251881Speter  const char *inherit_word;
2058251881Speter  svn_mergeinfo_inheritance_t inherit;
2059251881Speter  svn_boolean_t include_descendants;
2060251881Speter  apr_pool_t *iterpool;
2061251881Speter  authz_baton_t ab;
2062251881Speter
2063251881Speter  ab.server = b;
2064251881Speter  ab.conn = conn;
2065251881Speter
2066251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)wb", &paths, &rev,
2067251881Speter                                  &inherit_word, &include_descendants));
2068251881Speter  inherit = svn_inheritance_from_word(inherit_word);
2069251881Speter
2070251881Speter  /* Canonicalize the paths which mergeinfo has been requested for. */
2071251881Speter  canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2072251881Speter  for (i = 0; i < paths->nelts; i++)
2073251881Speter     {
2074251881Speter        svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2075251881Speter        const char *full_path;
2076251881Speter
2077251881Speter        if (item->kind != SVN_RA_SVN_STRING)
2078251881Speter          return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2079251881Speter                                  _("Path is not a string"));
2080251881Speter        full_path = svn_relpath_canonicalize(item->u.string->data, pool);
2081299742Sdim        full_path = svn_fspath__join(b->repository->fs_path->data, full_path, pool);
2082251881Speter        APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
2083251881Speter     }
2084251881Speter
2085251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2086251881Speter                      svn_log__get_mergeinfo(canonical_paths, inherit,
2087251881Speter                                             include_descendants,
2088251881Speter                                             pool)));
2089251881Speter
2090251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2091299742Sdim  SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repository->repos,
2092251881Speter                                         canonical_paths, rev,
2093251881Speter                                         inherit,
2094251881Speter                                         include_descendants,
2095251881Speter                                         authz_check_access_cb_func(b), &ab,
2096251881Speter                                         pool));
2097251881Speter  SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo,
2098299742Sdim                                      b->repository->fs_path->data, pool));
2099251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2100251881Speter  iterpool = svn_pool_create(pool);
2101251881Speter  for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
2102251881Speter    {
2103299742Sdim      const char *key = apr_hash_this_key(hi);
2104299742Sdim      svn_mergeinfo_t value = apr_hash_this_val(hi);
2105251881Speter      svn_string_t *mergeinfo_string;
2106251881Speter
2107251881Speter      svn_pool_clear(iterpool);
2108251881Speter
2109251881Speter      SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, value, iterpool));
2110251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cs", key,
2111251881Speter                                      mergeinfo_string));
2112251881Speter    }
2113251881Speter  svn_pool_destroy(iterpool);
2114251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2115251881Speter
2116251881Speter  return SVN_NO_ERROR;
2117251881Speter}
2118251881Speter
2119251881Speter/* Send a log entry to the client. */
2120251881Speterstatic svn_error_t *log_receiver(void *baton,
2121251881Speter                                 svn_log_entry_t *log_entry,
2122251881Speter                                 apr_pool_t *pool)
2123251881Speter{
2124251881Speter  log_baton_t *b = baton;
2125251881Speter  svn_ra_svn_conn_t *conn = b->conn;
2126251881Speter  apr_hash_index_t *h;
2127251881Speter  svn_boolean_t invalid_revnum = FALSE;
2128299742Sdim  const svn_string_t *author, *date, *message;
2129299742Sdim  unsigned revprop_count;
2130251881Speter
2131251881Speter  if (log_entry->revision == SVN_INVALID_REVNUM)
2132251881Speter    {
2133251881Speter      /* If the stack depth is zero, we've seen the last revision, so don't
2134251881Speter         send it, just return. */
2135251881Speter      if (b->stack_depth == 0)
2136251881Speter        return SVN_NO_ERROR;
2137251881Speter
2138251881Speter      /* Because the svn protocol won't let us send an invalid revnum, we have
2139251881Speter         to fudge here and send an additional flag. */
2140251881Speter      log_entry->revision = 0;
2141251881Speter      invalid_revnum = TRUE;
2142251881Speter      b->stack_depth--;
2143251881Speter    }
2144251881Speter
2145299742Sdim  svn_compat_log_revprops_out_string(&author, &date, &message,
2146299742Sdim                                     log_entry->revprops);
2147299742Sdim  svn_compat_log_revprops_clear(log_entry->revprops);
2148299742Sdim  if (log_entry->revprops)
2149299742Sdim    revprop_count = apr_hash_count(log_entry->revprops);
2150299742Sdim  else
2151299742Sdim    revprop_count = 0;
2152299742Sdim
2153299742Sdim  /* send LOG_ENTRY */
2154299742Sdim  SVN_ERR(svn_ra_svn__start_list(conn, pool));
2155299742Sdim
2156299742Sdim  /* send LOG_ENTRY->CHANGED_PATHS2 */
2157299742Sdim  SVN_ERR(svn_ra_svn__start_list(conn, pool));
2158251881Speter  if (log_entry->changed_paths2)
2159251881Speter    {
2160251881Speter      for (h = apr_hash_first(pool, log_entry->changed_paths2); h;
2161251881Speter                                                        h = apr_hash_next(h))
2162251881Speter        {
2163299742Sdim          const char *path = apr_hash_this_key(h);
2164299742Sdim          svn_log_changed_path2_t *change = apr_hash_this_val(h);
2165251881Speter
2166299742Sdim          SVN_ERR(svn_ra_svn__write_data_log_changed_path(
2167299742Sdim                      conn, pool,
2168251881Speter                      path,
2169299742Sdim                      change->action,
2170251881Speter                      change->copyfrom_path,
2171251881Speter                      change->copyfrom_rev,
2172299742Sdim                      change->node_kind,
2173251881Speter                      /* text_modified and props_modified are never unknown */
2174251881Speter                      change->text_modified  == svn_tristate_true,
2175251881Speter                      change->props_modified == svn_tristate_true));
2176251881Speter        }
2177251881Speter    }
2178299742Sdim  SVN_ERR(svn_ra_svn__end_list(conn, pool));
2179251881Speter
2180299742Sdim  /* send LOG_ENTRY main members */
2181299742Sdim  SVN_ERR(svn_ra_svn__write_data_log_entry(conn, pool,
2182299742Sdim                                           log_entry->revision,
2183299742Sdim                                           author, date, message,
2184299742Sdim                                           log_entry->has_children,
2185299742Sdim                                           invalid_revnum, revprop_count));
2186299742Sdim
2187299742Sdim  /* send LOG_ENTRY->REVPROPS */
2188299742Sdim  SVN_ERR(svn_ra_svn__start_list(conn, pool));
2189299742Sdim  if (revprop_count)
2190299742Sdim    SVN_ERR(svn_ra_svn__write_proplist(conn, pool, log_entry->revprops));
2191299742Sdim  SVN_ERR(svn_ra_svn__end_list(conn, pool));
2192299742Sdim
2193299742Sdim  /* send LOG_ENTRY members that were added in later SVN releases */
2194299742Sdim  SVN_ERR(svn_ra_svn__write_boolean(conn, pool, log_entry->subtractive_merge));
2195299742Sdim  SVN_ERR(svn_ra_svn__end_list(conn, pool));
2196299742Sdim
2197251881Speter  if (log_entry->has_children)
2198251881Speter    b->stack_depth++;
2199251881Speter
2200251881Speter  return SVN_NO_ERROR;
2201251881Speter}
2202251881Speter
2203251881Speterstatic svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2204251881Speter                            apr_array_header_t *params, void *baton)
2205251881Speter{
2206251881Speter  svn_error_t *err, *write_err;
2207251881Speter  server_baton_t *b = baton;
2208251881Speter  svn_revnum_t start_rev, end_rev;
2209251881Speter  const char *full_path;
2210251881Speter  svn_boolean_t send_changed_paths, strict_node, include_merged_revisions;
2211251881Speter  apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
2212251881Speter  char *revprop_word;
2213251881Speter  svn_ra_svn_item_t *elt;
2214251881Speter  int i;
2215251881Speter  apr_uint64_t limit, include_merged_revs_param;
2216251881Speter  log_baton_t lb;
2217251881Speter  authz_baton_t ab;
2218251881Speter
2219251881Speter  ab.server = b;
2220251881Speter  ab.conn = conn;
2221251881Speter
2222251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
2223251881Speter                                  &start_rev, &end_rev, &send_changed_paths,
2224251881Speter                                  &strict_node, &limit,
2225251881Speter                                  &include_merged_revs_param,
2226251881Speter                                  &revprop_word, &revprop_items));
2227251881Speter
2228251881Speter  if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2229251881Speter    include_merged_revisions = FALSE;
2230251881Speter  else
2231251881Speter    include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2232251881Speter
2233251881Speter  if (revprop_word == NULL)
2234251881Speter    /* pre-1.5 client */
2235251881Speter    revprops = svn_compat_log_revprops_in(pool);
2236251881Speter  else if (strcmp(revprop_word, "all-revprops") == 0)
2237251881Speter    revprops = NULL;
2238251881Speter  else if (strcmp(revprop_word, "revprops") == 0)
2239251881Speter    {
2240251881Speter      SVN_ERR_ASSERT(revprop_items);
2241251881Speter
2242251881Speter      revprops = apr_array_make(pool, revprop_items->nelts,
2243251881Speter                                sizeof(char *));
2244251881Speter      for (i = 0; i < revprop_items->nelts; i++)
2245251881Speter        {
2246251881Speter          elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
2247251881Speter          if (elt->kind != SVN_RA_SVN_STRING)
2248251881Speter            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2249251881Speter                                    _("Log revprop entry not a string"));
2250251881Speter          APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
2251251881Speter        }
2252251881Speter    }
2253251881Speter  else
2254251881Speter    return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2255251881Speter                             _("Unknown revprop word '%s' in log command"),
2256251881Speter                             revprop_word);
2257251881Speter
2258251881Speter  /* If we got an unspecified number then the user didn't send us anything,
2259251881Speter     so we assume no limit.  If it's larger than INT_MAX then someone is
2260251881Speter     messing with us, since we know the svn client libraries will never send
2261251881Speter     us anything that big, so play it safe and default to no limit. */
2262251881Speter  if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
2263251881Speter    limit = 0;
2264251881Speter
2265251881Speter  full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2266251881Speter  for (i = 0; i < paths->nelts; i++)
2267251881Speter    {
2268251881Speter      elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2269251881Speter      if (elt->kind != SVN_RA_SVN_STRING)
2270251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2271251881Speter                                _("Log path entry not a string"));
2272251881Speter      full_path = svn_relpath_canonicalize(elt->u.string->data, pool),
2273299742Sdim      full_path = svn_fspath__join(b->repository->fs_path->data, full_path,
2274299742Sdim                                   pool);
2275251881Speter      APR_ARRAY_PUSH(full_paths, const char *) = full_path;
2276251881Speter    }
2277251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2278251881Speter
2279251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2280251881Speter                      svn_log__log(full_paths, start_rev, end_rev,
2281251881Speter                                   (int) limit, send_changed_paths,
2282251881Speter                                   strict_node, include_merged_revisions,
2283251881Speter                                   revprops, pool)));
2284251881Speter
2285251881Speter  /* Get logs.  (Can't report errors back to the client at this point.) */
2286299742Sdim  lb.fs_path = b->repository->fs_path->data;
2287251881Speter  lb.conn = conn;
2288251881Speter  lb.stack_depth = 0;
2289299742Sdim  err = svn_repos_get_logs4(b->repository->repos, full_paths, start_rev,
2290299742Sdim                            end_rev, (int) limit, send_changed_paths,
2291299742Sdim                            strict_node, include_merged_revisions,
2292299742Sdim                            revprops, authz_check_access_cb_func(b), &ab,
2293299742Sdim                            log_receiver, &lb, pool);
2294251881Speter
2295251881Speter  write_err = svn_ra_svn__write_word(conn, pool, "done");
2296251881Speter  if (write_err)
2297251881Speter    {
2298251881Speter      svn_error_clear(err);
2299251881Speter      return write_err;
2300251881Speter    }
2301251881Speter  SVN_CMD_ERR(err);
2302251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2303251881Speter  return SVN_NO_ERROR;
2304251881Speter}
2305251881Speter
2306251881Speterstatic svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2307251881Speter                               apr_array_header_t *params, void *baton)
2308251881Speter{
2309251881Speter  server_baton_t *b = baton;
2310251881Speter  svn_revnum_t rev;
2311251881Speter  const char *path, *full_path;
2312251881Speter  svn_fs_root_t *root;
2313251881Speter  svn_node_kind_t kind;
2314251881Speter
2315251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2316299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data,
2317251881Speter                               svn_relpath_canonicalize(path, pool), pool);
2318251881Speter
2319251881Speter  /* Check authorizations */
2320251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2321251881Speter                           full_path, FALSE));
2322251881Speter
2323251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
2324299742Sdim    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2325251881Speter
2326251881Speter  SVN_ERR(log_command(b, conn, pool, "check-path %s@%d",
2327251881Speter                      svn_path_uri_encode(full_path, pool), rev));
2328251881Speter
2329299742Sdim  SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
2330251881Speter  SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
2331251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w",
2332251881Speter                                         svn_node_kind_to_word(kind)));
2333251881Speter  return SVN_NO_ERROR;
2334251881Speter}
2335251881Speter
2336251881Speterstatic svn_error_t *stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2337251881Speter                             apr_array_header_t *params, void *baton)
2338251881Speter{
2339251881Speter  server_baton_t *b = baton;
2340251881Speter  svn_revnum_t rev;
2341251881Speter  const char *path, *full_path, *cdate;
2342251881Speter  svn_fs_root_t *root;
2343251881Speter  svn_dirent_t *dirent;
2344251881Speter
2345251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2346299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data,
2347251881Speter                               svn_relpath_canonicalize(path, pool), pool);
2348251881Speter
2349251881Speter  /* Check authorizations */
2350251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2351251881Speter                           full_path, FALSE));
2352251881Speter
2353251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
2354299742Sdim    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2355251881Speter
2356251881Speter  SVN_ERR(log_command(b, conn, pool, "stat %s@%d",
2357251881Speter                      svn_path_uri_encode(full_path, pool), rev));
2358251881Speter
2359299742Sdim  SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
2360251881Speter  SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
2361251881Speter
2362251881Speter  /* Need to return the equivalent of "(?l)", since that's what the
2363251881Speter     client is reading.  */
2364251881Speter
2365251881Speter  if (dirent == NULL)
2366251881Speter    {
2367251881Speter      SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()"));
2368251881Speter      return SVN_NO_ERROR;
2369251881Speter    }
2370251881Speter
2371251881Speter  cdate = (dirent->time == (time_t) -1) ? NULL
2372251881Speter    : svn_time_to_cstring(dirent->time, pool);
2373251881Speter
2374251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
2375251881Speter                                         svn_node_kind_to_word(dirent->kind),
2376251881Speter                                         (apr_uint64_t) dirent->size,
2377251881Speter                                         dirent->has_props, dirent->created_rev,
2378251881Speter                                         cdate, dirent->last_author));
2379251881Speter
2380251881Speter  return SVN_NO_ERROR;
2381251881Speter}
2382251881Speter
2383251881Speterstatic svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2384251881Speter                                  apr_array_header_t *params, void *baton)
2385251881Speter{
2386251881Speter  svn_error_t *err, *write_err;
2387251881Speter  server_baton_t *b = baton;
2388251881Speter  svn_revnum_t revision;
2389251881Speter  apr_array_header_t *location_revisions, *loc_revs_proto;
2390251881Speter  svn_ra_svn_item_t *elt;
2391251881Speter  int i;
2392251881Speter  const char *relative_path;
2393251881Speter  svn_revnum_t peg_revision;
2394251881Speter  apr_hash_t *fs_locations;
2395251881Speter  const char *abs_path;
2396251881Speter  authz_baton_t ab;
2397251881Speter
2398251881Speter  ab.server = b;
2399251881Speter  ab.conn = conn;
2400251881Speter
2401251881Speter  /* Parse the arguments. */
2402251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crl", &relative_path,
2403251881Speter                                  &peg_revision,
2404251881Speter                                  &loc_revs_proto));
2405251881Speter  relative_path = svn_relpath_canonicalize(relative_path, pool);
2406251881Speter
2407299742Sdim  abs_path = svn_fspath__join(b->repository->fs_path->data, relative_path,
2408299742Sdim                              pool);
2409251881Speter
2410251881Speter  location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
2411251881Speter                                      sizeof(svn_revnum_t));
2412251881Speter  for (i = 0; i < loc_revs_proto->nelts; i++)
2413251881Speter    {
2414251881Speter      elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
2415251881Speter      if (elt->kind != SVN_RA_SVN_NUMBER)
2416251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2417251881Speter                                "Get-locations location revisions entry "
2418251881Speter                                "not a revision number");
2419251881Speter      revision = (svn_revnum_t)(elt->u.number);
2420251881Speter      APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
2421251881Speter    }
2422251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2423251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2424251881Speter                      svn_log__get_locations(abs_path, peg_revision,
2425251881Speter                                             location_revisions, pool)));
2426251881Speter
2427251881Speter  /* All the parameters are fine - let's perform the query against the
2428251881Speter   * repository. */
2429251881Speter
2430251881Speter  /* We store both err and write_err here, so the client will get
2431251881Speter   * the "done" even if there was an error in fetching the results. */
2432251881Speter
2433299742Sdim  err = svn_repos_trace_node_locations(b->repository->fs, &fs_locations,
2434299742Sdim                                       abs_path, peg_revision,
2435299742Sdim                                       location_revisions,
2436251881Speter                                       authz_check_access_cb_func(b), &ab,
2437251881Speter                                       pool);
2438251881Speter
2439251881Speter  /* Now, write the results to the connection. */
2440251881Speter  if (!err)
2441251881Speter    {
2442251881Speter      if (fs_locations)
2443251881Speter        {
2444251881Speter          apr_hash_index_t *iter;
2445251881Speter
2446251881Speter          for (iter = apr_hash_first(pool, fs_locations); iter;
2447251881Speter              iter = apr_hash_next(iter))
2448251881Speter            {
2449299742Sdim              const svn_revnum_t *iter_key = apr_hash_this_key(iter);
2450299742Sdim              const char *iter_value = apr_hash_this_val(iter);
2451251881Speter
2452251881Speter              SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc",
2453251881Speter                                              *iter_key, iter_value));
2454251881Speter            }
2455251881Speter        }
2456251881Speter    }
2457251881Speter
2458251881Speter  write_err = svn_ra_svn__write_word(conn, pool, "done");
2459251881Speter  if (write_err)
2460251881Speter    {
2461251881Speter      svn_error_clear(err);
2462251881Speter      return write_err;
2463251881Speter    }
2464251881Speter  SVN_CMD_ERR(err);
2465251881Speter
2466251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2467251881Speter
2468251881Speter  return SVN_NO_ERROR;
2469251881Speter}
2470251881Speter
2471251881Speterstatic svn_error_t *gls_receiver(svn_location_segment_t *segment,
2472251881Speter                                 void *baton,
2473251881Speter                                 apr_pool_t *pool)
2474251881Speter{
2475251881Speter  svn_ra_svn_conn_t *conn = baton;
2476251881Speter  return svn_ra_svn__write_tuple(conn, pool, "rr(?c)",
2477251881Speter                                 segment->range_start,
2478251881Speter                                 segment->range_end,
2479251881Speter                                 segment->path);
2480251881Speter}
2481251881Speter
2482251881Speterstatic svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn,
2483251881Speter                                          apr_pool_t *pool,
2484251881Speter                                          apr_array_header_t *params,
2485251881Speter                                          void *baton)
2486251881Speter{
2487251881Speter  svn_error_t *err, *write_err;
2488251881Speter  server_baton_t *b = baton;
2489251881Speter  svn_revnum_t peg_revision, start_rev, end_rev;
2490251881Speter  const char *relative_path;
2491251881Speter  const char *abs_path;
2492251881Speter  authz_baton_t ab;
2493251881Speter
2494251881Speter  ab.server = b;
2495251881Speter  ab.conn = conn;
2496251881Speter
2497251881Speter  /* Parse the arguments. */
2498251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)(?r)",
2499251881Speter                                  &relative_path, &peg_revision,
2500251881Speter                                  &start_rev, &end_rev));
2501251881Speter  relative_path = svn_relpath_canonicalize(relative_path, pool);
2502251881Speter
2503299742Sdim  abs_path = svn_fspath__join(b->repository->fs_path->data, relative_path,
2504299742Sdim                              pool);
2505251881Speter
2506289166Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2507289166Speter  SVN_ERR(log_command(baton, conn, pool, "%s",
2508289166Speter                      svn_log__get_location_segments(abs_path, peg_revision,
2509289166Speter                                                     start_rev, end_rev,
2510289166Speter                                                     pool)));
2511289166Speter
2512289166Speter  /* No START_REV or PEG_REVISION?  We'll use HEAD. */
2513289166Speter  if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision))
2514251881Speter    {
2515289166Speter      svn_revnum_t youngest;
2516289166Speter
2517299742Sdim      err = svn_fs_youngest_rev(&youngest, b->repository->fs, pool);
2518289166Speter
2519299742Sdim      if (err)
2520299742Sdim        {
2521299742Sdim          err = svn_error_compose_create(
2522299742Sdim                    svn_ra_svn__write_word(conn, pool, "done"),
2523299742Sdim                    err);
2524299742Sdim
2525299742Sdim          return log_fail_and_flush(err, b, conn, pool);
2526299742Sdim        }
2527299742Sdim
2528289166Speter      if (!SVN_IS_VALID_REVNUM(start_rev))
2529289166Speter        start_rev = youngest;
2530289166Speter      if (!SVN_IS_VALID_REVNUM(peg_revision))
2531289166Speter        peg_revision = youngest;
2532289166Speter    }
2533289166Speter
2534289166Speter  /* No END_REV?  We'll use 0. */
2535289166Speter  if (!SVN_IS_VALID_REVNUM(end_rev))
2536289166Speter    end_rev = 0;
2537289166Speter
2538289166Speter  if (end_rev > start_rev)
2539289166Speter    {
2540299742Sdim      err = svn_ra_svn__write_word(conn, pool, "done");
2541299742Sdim      err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, err,
2542251881Speter                              "Get-location-segments end revision must not be "
2543251881Speter                              "younger than start revision");
2544251881Speter      return log_fail_and_flush(err, b, conn, pool);
2545251881Speter    }
2546251881Speter
2547289166Speter  if (start_rev > peg_revision)
2548251881Speter    {
2549299742Sdim      err = svn_ra_svn__write_word(conn, pool, "done");
2550299742Sdim      err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, err,
2551251881Speter                              "Get-location-segments start revision must not "
2552251881Speter                              "be younger than peg revision");
2553251881Speter      return log_fail_and_flush(err, b, conn, pool);
2554251881Speter    }
2555251881Speter
2556251881Speter  /* All the parameters are fine - let's perform the query against the
2557251881Speter   * repository. */
2558251881Speter
2559251881Speter  /* We store both err and write_err here, so the client will get
2560251881Speter   * the "done" even if there was an error in fetching the results. */
2561251881Speter
2562299742Sdim  err = svn_repos_node_location_segments(b->repository->repos, abs_path,
2563251881Speter                                         peg_revision, start_rev, end_rev,
2564251881Speter                                         gls_receiver, (void *)conn,
2565251881Speter                                         authz_check_access_cb_func(b), &ab,
2566251881Speter                                         pool);
2567251881Speter  write_err = svn_ra_svn__write_word(conn, pool, "done");
2568251881Speter  if (write_err)
2569251881Speter    {
2570299742Sdim      return svn_error_compose_create(write_err, err);
2571251881Speter    }
2572251881Speter  SVN_CMD_ERR(err);
2573251881Speter
2574251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2575251881Speter
2576251881Speter  return SVN_NO_ERROR;
2577251881Speter}
2578251881Speter
2579251881Speter/* This implements svn_write_fn_t.  Write LEN bytes starting at DATA to the
2580251881Speter   client as a string. */
2581251881Speterstatic svn_error_t *svndiff_handler(void *baton, const char *data,
2582251881Speter                                    apr_size_t *len)
2583251881Speter{
2584251881Speter  file_revs_baton_t *b = baton;
2585251881Speter  svn_string_t str;
2586251881Speter
2587251881Speter  str.data = data;
2588251881Speter  str.len = *len;
2589251881Speter  return svn_ra_svn__write_string(b->conn, b->pool, &str);
2590251881Speter}
2591251881Speter
2592251881Speter/* This implements svn_close_fn_t.  Mark the end of the data by writing an
2593251881Speter   empty string to the client. */
2594251881Speterstatic svn_error_t *svndiff_close_handler(void *baton)
2595251881Speter{
2596251881Speter  file_revs_baton_t *b = baton;
2597251881Speter
2598251881Speter  SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, ""));
2599251881Speter  return SVN_NO_ERROR;
2600251881Speter}
2601251881Speter
2602251881Speter/* This implements the svn_repos_file_rev_handler_t interface. */
2603251881Speterstatic svn_error_t *file_rev_handler(void *baton, const char *path,
2604251881Speter                                     svn_revnum_t rev, apr_hash_t *rev_props,
2605251881Speter                                     svn_boolean_t merged_revision,
2606251881Speter                                     svn_txdelta_window_handler_t *d_handler,
2607251881Speter                                     void **d_baton,
2608251881Speter                                     apr_array_header_t *prop_diffs,
2609251881Speter                                     apr_pool_t *pool)
2610251881Speter{
2611251881Speter  file_revs_baton_t *frb = baton;
2612251881Speter  svn_stream_t *stream;
2613251881Speter
2614251881Speter  SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!",
2615251881Speter                                  path, rev));
2616251881Speter  SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props));
2617251881Speter  SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!"));
2618251881Speter  SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
2619251881Speter  SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision));
2620251881Speter
2621251881Speter  /* Store the pool for the delta stream. */
2622251881Speter  frb->pool = pool;
2623251881Speter
2624251881Speter  /* Prepare for the delta or just write an empty string. */
2625251881Speter  if (d_handler)
2626251881Speter    {
2627251881Speter      stream = svn_stream_create(baton, pool);
2628251881Speter      svn_stream_set_write(stream, svndiff_handler);
2629251881Speter      svn_stream_set_close(stream, svndiff_close_handler);
2630251881Speter
2631251881Speter      /* If the connection does not support SVNDIFF1 or if we don't want to use
2632251881Speter       * compression, use the non-compressing "version 0" implementation */
2633251881Speter      if (   svn_ra_svn_compression_level(frb->conn) > 0
2634251881Speter          && svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1))
2635251881Speter        svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 1,
2636251881Speter                                svn_ra_svn_compression_level(frb->conn), pool);
2637251881Speter      else
2638251881Speter        svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 0,
2639251881Speter                                svn_ra_svn_compression_level(frb->conn), pool);
2640251881Speter    }
2641251881Speter  else
2642251881Speter    SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, ""));
2643251881Speter
2644251881Speter  return SVN_NO_ERROR;
2645251881Speter}
2646251881Speter
2647251881Speterstatic svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2648251881Speter                                  apr_array_header_t *params, void *baton)
2649251881Speter{
2650251881Speter  server_baton_t *b = baton;
2651251881Speter  svn_error_t *err, *write_err;
2652251881Speter  file_revs_baton_t frb;
2653251881Speter  svn_revnum_t start_rev, end_rev;
2654251881Speter  const char *path;
2655251881Speter  const char *full_path;
2656251881Speter  apr_uint64_t include_merged_revs_param;
2657251881Speter  svn_boolean_t include_merged_revisions;
2658251881Speter  authz_baton_t ab;
2659251881Speter
2660251881Speter  ab.server = b;
2661251881Speter  ab.conn = conn;
2662251881Speter
2663251881Speter  /* Parse arguments. */
2664251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)?B",
2665251881Speter                                  &path, &start_rev, &end_rev,
2666251881Speter                                  &include_merged_revs_param));
2667251881Speter  path = svn_relpath_canonicalize(path, pool);
2668251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
2669299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data, path, pool);
2670251881Speter
2671251881Speter  if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2672251881Speter    include_merged_revisions = FALSE;
2673251881Speter  else
2674251881Speter    include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2675251881Speter
2676251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2677251881Speter                      svn_log__get_file_revs(full_path, start_rev, end_rev,
2678251881Speter                                             include_merged_revisions,
2679251881Speter                                             pool)));
2680251881Speter
2681251881Speter  frb.conn = conn;
2682251881Speter  frb.pool = NULL;
2683251881Speter
2684299742Sdim  err = svn_repos_get_file_revs2(b->repository->repos, full_path, start_rev,
2685299742Sdim                                 end_rev, include_merged_revisions,
2686251881Speter                                 authz_check_access_cb_func(b), &ab,
2687251881Speter                                 file_rev_handler, &frb, pool);
2688251881Speter  write_err = svn_ra_svn__write_word(conn, pool, "done");
2689251881Speter  if (write_err)
2690251881Speter    {
2691251881Speter      svn_error_clear(err);
2692251881Speter      return write_err;
2693251881Speter    }
2694251881Speter  SVN_CMD_ERR(err);
2695251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2696251881Speter
2697251881Speter  return SVN_NO_ERROR;
2698251881Speter}
2699251881Speter
2700251881Speterstatic svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2701251881Speter                         apr_array_header_t *params, void *baton)
2702251881Speter{
2703251881Speter  server_baton_t *b = baton;
2704251881Speter  const char *path;
2705251881Speter  const char *comment;
2706251881Speter  const char *full_path;
2707251881Speter  svn_boolean_t steal_lock;
2708251881Speter  svn_revnum_t current_rev;
2709251881Speter  svn_lock_t *l;
2710251881Speter
2711251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
2712251881Speter                                  &steal_lock, &current_rev));
2713299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data,
2714251881Speter                               svn_relpath_canonicalize(path, pool), pool);
2715251881Speter
2716251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2717251881Speter                           full_path, TRUE));
2718251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2719251881Speter                      svn_log__lock_one_path(full_path, steal_lock, pool)));
2720251881Speter
2721299742Sdim  SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repository->repos, full_path, NULL,
2722299742Sdim                                comment, 0, 0, /* No expiration time. */
2723251881Speter                                current_rev, steal_lock, pool));
2724251881Speter
2725251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success"));
2726251881Speter  SVN_ERR(write_lock(conn, pool, l));
2727251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)"));
2728251881Speter
2729251881Speter  return SVN_NO_ERROR;
2730251881Speter}
2731251881Speter
2732299742Sdimstruct lock_result_t {
2733299742Sdim  const svn_lock_t *lock;
2734299742Sdim  svn_error_t *err;
2735299742Sdim};
2736299742Sdim
2737299742Sdimstruct lock_many_baton_t {
2738299742Sdim  apr_hash_t *results;
2739299742Sdim  apr_pool_t *pool;
2740299742Sdim};
2741299742Sdim
2742299742Sdim/* Implements svn_fs_lock_callback_t. */
2743299742Sdimstatic svn_error_t *
2744299742Sdimlock_many_cb(void *baton,
2745299742Sdim             const char *path,
2746299742Sdim             const svn_lock_t *fs_lock,
2747299742Sdim             svn_error_t *fs_err,
2748299742Sdim             apr_pool_t *pool)
2749299742Sdim{
2750299742Sdim  struct lock_many_baton_t *b = baton;
2751299742Sdim  struct lock_result_t *result = apr_palloc(b->pool,
2752299742Sdim                                            sizeof(struct lock_result_t));
2753299742Sdim
2754299742Sdim  result->lock = fs_lock;
2755299742Sdim  result->err = svn_error_dup(fs_err);
2756299742Sdim  svn_hash_sets(b->results, apr_pstrdup(b->pool, path), result);
2757299742Sdim
2758299742Sdim  return SVN_NO_ERROR;
2759299742Sdim}
2760299742Sdim
2761299742Sdimstatic void
2762299742Sdimclear_lock_result_hash(apr_hash_t *results,
2763299742Sdim                       apr_pool_t *scratch_pool)
2764299742Sdim{
2765299742Sdim  apr_hash_index_t *hi;
2766299742Sdim
2767299742Sdim  for (hi = apr_hash_first(scratch_pool, results); hi; hi = apr_hash_next(hi))
2768299742Sdim    {
2769299742Sdim      struct lock_result_t *result = apr_hash_this_val(hi);
2770299742Sdim      svn_error_clear(result->err);
2771299742Sdim    }
2772299742Sdim}
2773299742Sdim
2774251881Speterstatic svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2775251881Speter                              apr_array_header_t *params, void *baton)
2776251881Speter{
2777251881Speter  server_baton_t *b = baton;
2778251881Speter  apr_array_header_t *path_revs;
2779251881Speter  const char *comment;
2780251881Speter  svn_boolean_t steal_lock;
2781251881Speter  int i;
2782251881Speter  apr_pool_t *subpool;
2783299742Sdim  svn_error_t *err, *write_err = SVN_NO_ERROR;
2784299742Sdim  apr_hash_t *targets = apr_hash_make(pool);
2785299742Sdim  apr_hash_t *authz_results = apr_hash_make(pool);
2786299742Sdim  apr_hash_index_t *hi;
2787299742Sdim  struct lock_many_baton_t lmb;
2788251881Speter
2789251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
2790251881Speter                                  &path_revs));
2791251881Speter
2792251881Speter  subpool = svn_pool_create(pool);
2793251881Speter
2794251881Speter  /* Because we can only send a single auth reply per request, we send
2795251881Speter     a reply before parsing the lock commands.  This means an authz
2796251881Speter     access denial will abort the processing of the locks and return
2797251881Speter     an error. */
2798251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
2799251881Speter
2800299742Sdim  /* Parse the lock requests from PATH_REVS into TARGETS. */
2801251881Speter  for (i = 0; i < path_revs->nelts; ++i)
2802251881Speter    {
2803299742Sdim      const char *path, *full_path;
2804299742Sdim      svn_revnum_t current_rev;
2805251881Speter      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
2806251881Speter                                               svn_ra_svn_item_t);
2807299742Sdim      svn_fs_lock_target_t *target;
2808251881Speter
2809251881Speter      svn_pool_clear(subpool);
2810251881Speter
2811251881Speter      if (item->kind != SVN_RA_SVN_LIST)
2812251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2813251881Speter                                "Lock requests should be list of lists");
2814251881Speter
2815299742Sdim      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?r)", &path,
2816251881Speter                                      &current_rev));
2817251881Speter
2818299742Sdim      full_path = svn_fspath__join(b->repository->fs_path->data,
2819251881Speter                                   svn_relpath_canonicalize(path, subpool),
2820251881Speter                                   pool);
2821299742Sdim      target = svn_fs_lock_target_create(NULL, current_rev, pool);
2822251881Speter
2823299742Sdim      /* Any duplicate paths, once canonicalized, get collapsed into a
2824299742Sdim         single path that is processed once.  The result is then
2825299742Sdim         returned multiple times. */
2826299742Sdim      svn_hash_sets(targets, full_path, target);
2827299742Sdim    }
2828299742Sdim
2829299742Sdim  SVN_ERR(log_command(b, conn, subpool, "%s",
2830299742Sdim                      svn_log__lock(targets, steal_lock, subpool)));
2831299742Sdim
2832299742Sdim  /* Check authz.
2833299742Sdim
2834299742Sdim     Note: From here on we need to make sure any errors in authz_results, or
2835299742Sdim     results, are cleared before returning from this function. */
2836299742Sdim  for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
2837299742Sdim    {
2838299742Sdim      const char *full_path = apr_hash_this_key(hi);
2839299742Sdim
2840299742Sdim      svn_pool_clear(subpool);
2841299742Sdim
2842299742Sdim      if (! lookup_access(subpool, b, svn_authz_write, full_path, TRUE))
2843251881Speter        {
2844299742Sdim          struct lock_result_t *result
2845299742Sdim            = apr_palloc(pool, sizeof(struct lock_result_t));
2846299742Sdim
2847299742Sdim          result->lock = NULL;
2848299742Sdim          result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
2849299742Sdim                                             NULL, NULL, b);
2850299742Sdim          svn_hash_sets(authz_results, full_path, result);
2851299742Sdim          svn_hash_sets(targets, full_path, NULL);
2852251881Speter        }
2853299742Sdim    }
2854251881Speter
2855299742Sdim  lmb.results = apr_hash_make(pool);
2856299742Sdim  lmb.pool = pool;
2857251881Speter
2858299742Sdim  err = svn_repos_fs_lock_many(b->repository->repos, targets,
2859299742Sdim                               comment, FALSE,
2860299742Sdim                               0, /* No expiration time. */
2861299742Sdim                               steal_lock, lock_many_cb, &lmb,
2862299742Sdim                               pool, subpool);
2863299742Sdim
2864299742Sdim  /* Return results in the same order as the paths were supplied. */
2865299742Sdim  for (i = 0; i < path_revs->nelts; ++i)
2866299742Sdim    {
2867299742Sdim      const char *path, *full_path;
2868299742Sdim      svn_revnum_t current_rev;
2869299742Sdim      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
2870299742Sdim                                               svn_ra_svn_item_t);
2871299742Sdim      struct lock_result_t *result;
2872299742Sdim
2873299742Sdim      svn_pool_clear(subpool);
2874299742Sdim
2875299742Sdim      write_err = svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?r)", &path,
2876299742Sdim                                          &current_rev);
2877299742Sdim      if (write_err)
2878299742Sdim        break;
2879299742Sdim
2880299742Sdim      full_path = svn_fspath__join(b->repository->fs_path->data,
2881299742Sdim                                   svn_relpath_canonicalize(path, subpool),
2882299742Sdim                                   subpool);
2883299742Sdim
2884299742Sdim      result = svn_hash_gets(lmb.results, full_path);
2885299742Sdim      if (!result)
2886299742Sdim        result = svn_hash_gets(authz_results, full_path);
2887299742Sdim      if (!result)
2888251881Speter        {
2889299742Sdim          /* No result?  Something really odd happened, create a
2890299742Sdim             placeholder error so that any other results can be
2891299742Sdim             reported in the correct order. */
2892299742Sdim          result = apr_palloc(pool, sizeof(struct lock_result_t));
2893299742Sdim          result->err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 0,
2894299742Sdim                                          _("No result for '%s'."), path);
2895299742Sdim          svn_hash_sets(lmb.results, full_path, result);
2896251881Speter        }
2897299742Sdim
2898299742Sdim      if (result->err)
2899299742Sdim        write_err = svn_ra_svn__write_cmd_failure(conn, subpool,
2900299742Sdim                                                  result->err);
2901251881Speter      else
2902251881Speter        {
2903299742Sdim          write_err = svn_ra_svn__write_tuple(conn, subpool,
2904299742Sdim                                              "w!", "success");
2905299742Sdim          if (!write_err)
2906299742Sdim            write_err = write_lock(conn, subpool, result->lock);
2907299742Sdim          if (!write_err)
2908299742Sdim            write_err = svn_ra_svn__write_tuple(conn, subpool, "!");
2909251881Speter        }
2910299742Sdim      if (write_err)
2911299742Sdim        break;
2912251881Speter    }
2913251881Speter
2914299742Sdim  clear_lock_result_hash(authz_results, subpool);
2915299742Sdim  clear_lock_result_hash(lmb.results, subpool);
2916299742Sdim
2917251881Speter  svn_pool_destroy(subpool);
2918251881Speter
2919251881Speter  if (!write_err)
2920299742Sdim    write_err = svn_ra_svn__write_word(conn, pool, "done");
2921299742Sdim  if (!write_err)
2922251881Speter    SVN_CMD_ERR(err);
2923251881Speter  svn_error_clear(err);
2924251881Speter  SVN_ERR(write_err);
2925251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2926251881Speter
2927251881Speter  return SVN_NO_ERROR;
2928251881Speter}
2929251881Speter
2930251881Speterstatic svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2931251881Speter                           apr_array_header_t *params, void *baton)
2932251881Speter{
2933251881Speter  server_baton_t *b = baton;
2934251881Speter  const char *path, *token, *full_path;
2935251881Speter  svn_boolean_t break_lock;
2936251881Speter
2937251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b", &path, &token,
2938251881Speter                                 &break_lock));
2939251881Speter
2940299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data,
2941251881Speter                               svn_relpath_canonicalize(path, pool), pool);
2942251881Speter
2943251881Speter  /* Username required unless break_lock was specified. */
2944251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2945251881Speter                           full_path, ! break_lock));
2946251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
2947251881Speter                      svn_log__unlock_one_path(full_path, break_lock, pool)));
2948251881Speter
2949299742Sdim  SVN_CMD_ERR(svn_repos_fs_unlock(b->repository->repos, full_path, token,
2950299742Sdim                                  break_lock, pool));
2951251881Speter
2952251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2953251881Speter
2954251881Speter  return SVN_NO_ERROR;
2955251881Speter}
2956251881Speter
2957251881Speterstatic svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2958251881Speter                                apr_array_header_t *params, void *baton)
2959251881Speter{
2960251881Speter  server_baton_t *b = baton;
2961251881Speter  svn_boolean_t break_lock;
2962251881Speter  apr_array_header_t *unlock_tokens;
2963251881Speter  int i;
2964251881Speter  apr_pool_t *subpool;
2965299742Sdim  svn_error_t *err = SVN_NO_ERROR, *write_err = SVN_NO_ERROR;
2966299742Sdim  apr_hash_t *targets = apr_hash_make(pool);
2967299742Sdim  apr_hash_t *authz_results = apr_hash_make(pool);
2968299742Sdim  apr_hash_index_t *hi;
2969299742Sdim  struct lock_many_baton_t lmb;
2970251881Speter
2971251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock,
2972251881Speter                                  &unlock_tokens));
2973251881Speter
2974251881Speter  /* Username required unless break_lock was specified. */
2975251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
2976251881Speter
2977251881Speter  subpool = svn_pool_create(pool);
2978251881Speter
2979299742Sdim  /* Parse the unlock requests from PATH_REVS into TARGETS. */
2980251881Speter  for (i = 0; i < unlock_tokens->nelts; i++)
2981251881Speter    {
2982251881Speter      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
2983251881Speter                                               svn_ra_svn_item_t);
2984299742Sdim      const char *path, *full_path, *token;
2985251881Speter
2986251881Speter      svn_pool_clear(subpool);
2987251881Speter
2988251881Speter      if (item->kind != SVN_RA_SVN_LIST)
2989251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2990251881Speter                                "Unlock request should be a list of lists");
2991251881Speter
2992251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
2993251881Speter                                      &token));
2994299742Sdim      if (!token)
2995299742Sdim        token = "";
2996251881Speter
2997299742Sdim      full_path = svn_fspath__join(b->repository->fs_path->data,
2998251881Speter                                   svn_relpath_canonicalize(path, subpool),
2999251881Speter                                   pool);
3000251881Speter
3001299742Sdim      /* Any duplicate paths, once canonicalized, get collapsed into a
3002299742Sdim         single path that is processed once.  The result is then
3003299742Sdim         returned multiple times. */
3004299742Sdim      svn_hash_sets(targets, full_path, token);
3005299742Sdim    }
3006299742Sdim
3007299742Sdim  SVN_ERR(log_command(b, conn, subpool, "%s",
3008299742Sdim                      svn_log__unlock(targets, break_lock, subpool)));
3009299742Sdim
3010299742Sdim  /* Check authz.
3011299742Sdim
3012299742Sdim     Note: From here on we need to make sure any errors in authz_results, or
3013299742Sdim     results, are cleared before returning from this function. */
3014299742Sdim  for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
3015299742Sdim    {
3016299742Sdim      const char *full_path = apr_hash_this_key(hi);
3017299742Sdim
3018299742Sdim      svn_pool_clear(subpool);
3019299742Sdim
3020299742Sdim      if (! lookup_access(subpool, b, svn_authz_write, full_path,
3021251881Speter                          ! break_lock))
3022299742Sdim        {
3023299742Sdim          struct lock_result_t *result
3024299742Sdim            = apr_palloc(pool, sizeof(struct lock_result_t));
3025251881Speter
3026299742Sdim          result->lock = NULL;
3027299742Sdim          result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
3028299742Sdim                                             NULL, NULL, b);
3029299742Sdim          svn_hash_sets(authz_results, full_path, result);
3030299742Sdim          svn_hash_sets(targets, full_path, NULL);
3031299742Sdim        }
3032299742Sdim    }
3033299742Sdim
3034299742Sdim  lmb.results = apr_hash_make(pool);
3035299742Sdim  lmb.pool = pool;
3036299742Sdim
3037299742Sdim  err = svn_repos_fs_unlock_many(b->repository->repos, targets,
3038299742Sdim                                 break_lock, lock_many_cb, &lmb,
3039299742Sdim                                 pool, subpool);
3040299742Sdim
3041299742Sdim  /* Return results in the same order as the paths were supplied. */
3042299742Sdim  for (i = 0; i < unlock_tokens->nelts; ++i)
3043299742Sdim    {
3044299742Sdim      const char *path, *token, *full_path;
3045299742Sdim      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
3046299742Sdim                                               svn_ra_svn_item_t);
3047299742Sdim      struct lock_result_t *result;
3048299742Sdim
3049299742Sdim      svn_pool_clear(subpool);
3050299742Sdim
3051299742Sdim      write_err = svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
3052299742Sdim                                          &token);
3053299742Sdim      if (write_err)
3054299742Sdim        break;
3055299742Sdim
3056299742Sdim      full_path = svn_fspath__join(b->repository->fs_path->data,
3057299742Sdim                                   svn_relpath_canonicalize(path, subpool),
3058299742Sdim                                   pool);
3059299742Sdim
3060299742Sdim      result = svn_hash_gets(lmb.results, full_path);
3061299742Sdim      if (!result)
3062299742Sdim        result = svn_hash_gets(authz_results, full_path);
3063299742Sdim      if (!result)
3064251881Speter        {
3065299742Sdim          /* No result?  Something really odd happened, create a
3066299742Sdim             placeholder error so that any other results can be
3067299742Sdim             reported in the correct order. */
3068299742Sdim          result = apr_palloc(pool, sizeof(struct lock_result_t));
3069299742Sdim          result->err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 0,
3070299742Sdim                                          _("No result for '%s'."), path);
3071299742Sdim          svn_hash_sets(lmb.results, full_path, result);
3072251881Speter        }
3073299742Sdim
3074299742Sdim      if (result->err)
3075299742Sdim        write_err = svn_ra_svn__write_cmd_failure(conn, pool, result->err);
3076251881Speter      else
3077299742Sdim        write_err = svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
3078299742Sdim                                            path);
3079299742Sdim      if (write_err)
3080299742Sdim        break;
3081251881Speter    }
3082251881Speter
3083299742Sdim  clear_lock_result_hash(authz_results, subpool);
3084299742Sdim  clear_lock_result_hash(lmb.results, subpool);
3085299742Sdim
3086251881Speter  svn_pool_destroy(subpool);
3087251881Speter
3088299742Sdim  if (!write_err)
3089299742Sdim    write_err = svn_ra_svn__write_word(conn, pool, "done");
3090251881Speter  if (! write_err)
3091251881Speter    SVN_CMD_ERR(err);
3092251881Speter  svn_error_clear(err);
3093299742Sdim  SVN_ERR(write_err);
3094251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3095251881Speter
3096251881Speter  return SVN_NO_ERROR;
3097251881Speter}
3098251881Speter
3099251881Speterstatic svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3100251881Speter                             apr_array_header_t *params, void *baton)
3101251881Speter{
3102251881Speter  server_baton_t *b = baton;
3103251881Speter  const char *path;
3104251881Speter  const char *full_path;
3105251881Speter  svn_lock_t *l;
3106251881Speter
3107251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
3108251881Speter
3109299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data,
3110251881Speter                               svn_relpath_canonicalize(path, pool), pool);
3111251881Speter
3112251881Speter  SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
3113251881Speter                           full_path, FALSE));
3114251881Speter  SVN_ERR(log_command(b, conn, pool, "get-lock %s",
3115251881Speter                      svn_path_uri_encode(full_path, pool)));
3116251881Speter
3117299742Sdim  SVN_CMD_ERR(svn_fs_get_lock(&l, b->repository->fs, full_path, pool));
3118251881Speter
3119251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
3120251881Speter  if (l)
3121251881Speter    SVN_ERR(write_lock(conn, pool, l));
3122251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3123251881Speter
3124251881Speter  return SVN_NO_ERROR;
3125251881Speter}
3126251881Speter
3127251881Speterstatic svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3128251881Speter                              apr_array_header_t *params, void *baton)
3129251881Speter{
3130251881Speter  server_baton_t *b = baton;
3131251881Speter  const char *path;
3132251881Speter  const char *full_path;
3133251881Speter  const char *depth_word;
3134251881Speter  svn_depth_t depth;
3135251881Speter  apr_hash_t *locks;
3136251881Speter  apr_hash_index_t *hi;
3137251881Speter  svn_error_t *err;
3138251881Speter  authz_baton_t ab;
3139251881Speter
3140251881Speter  ab.server = b;
3141251881Speter  ab.conn = conn;
3142251881Speter
3143251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c?(?w)", &path, &depth_word));
3144251881Speter
3145251881Speter  depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity;
3146251881Speter  if ((depth != svn_depth_empty) &&
3147251881Speter      (depth != svn_depth_files) &&
3148251881Speter      (depth != svn_depth_immediates) &&
3149251881Speter      (depth != svn_depth_infinity))
3150251881Speter    {
3151251881Speter      err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3152251881Speter                             "Invalid 'depth' specified in get-locks request");
3153251881Speter      return log_fail_and_flush(err, b, conn, pool);
3154251881Speter    }
3155251881Speter
3156299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data,
3157251881Speter                               svn_relpath_canonicalize(path, pool), pool);
3158251881Speter
3159251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
3160251881Speter
3161251881Speter  SVN_ERR(log_command(b, conn, pool, "get-locks %s",
3162251881Speter                      svn_path_uri_encode(full_path, pool)));
3163299742Sdim  SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repository->repos,
3164299742Sdim                                      full_path, depth,
3165251881Speter                                      authz_check_access_cb_func(b), &ab,
3166251881Speter                                      pool));
3167251881Speter
3168251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
3169251881Speter  for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
3170251881Speter    {
3171299742Sdim      svn_lock_t *l = apr_hash_this_val(hi);
3172251881Speter
3173251881Speter      SVN_ERR(write_lock(conn, pool, l));
3174251881Speter    }
3175251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3176251881Speter
3177251881Speter  return SVN_NO_ERROR;
3178251881Speter}
3179251881Speter
3180251881Speterstatic svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
3181251881Speter                                        server_baton_t *b,
3182251881Speter                                        svn_revnum_t rev,
3183251881Speter                                        svn_revnum_t low_water_mark,
3184251881Speter                                        svn_boolean_t send_deltas,
3185251881Speter                                        apr_pool_t *pool)
3186251881Speter{
3187251881Speter  const svn_delta_editor_t *editor;
3188251881Speter  void *edit_baton;
3189251881Speter  svn_fs_root_t *root;
3190251881Speter  svn_error_t *err;
3191251881Speter  authz_baton_t ab;
3192251881Speter
3193251881Speter  ab.server = b;
3194251881Speter  ab.conn = conn;
3195251881Speter
3196251881Speter  SVN_ERR(log_command(b, conn, pool,
3197299742Sdim                      svn_log__replay(b->repository->fs_path->data, rev,
3198299742Sdim                                      pool)));
3199251881Speter
3200251881Speter  svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
3201251881Speter
3202299742Sdim  err = svn_fs_revision_root(&root, b->repository->fs, rev, pool);
3203251881Speter
3204251881Speter  if (! err)
3205299742Sdim    err = svn_repos_replay2(root, b->repository->fs_path->data,
3206299742Sdim                            low_water_mark, send_deltas, editor, edit_baton,
3207251881Speter                            authz_check_access_cb_func(b), &ab, pool);
3208251881Speter
3209251881Speter  if (err)
3210251881Speter    svn_error_clear(editor->abort_edit(edit_baton, pool));
3211251881Speter  SVN_CMD_ERR(err);
3212251881Speter
3213251881Speter  return svn_ra_svn__write_cmd_finish_replay(conn, pool);
3214251881Speter}
3215251881Speter
3216251881Speterstatic svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3217251881Speter                           apr_array_header_t *params, void *baton)
3218251881Speter{
3219251881Speter  svn_revnum_t rev, low_water_mark;
3220251881Speter  svn_boolean_t send_deltas;
3221251881Speter  server_baton_t *b = baton;
3222251881Speter
3223251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrb", &rev, &low_water_mark,
3224251881Speter                                 &send_deltas));
3225251881Speter
3226251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
3227251881Speter
3228251881Speter  SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3229251881Speter                              send_deltas, pool));
3230251881Speter
3231251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3232251881Speter
3233251881Speter  return SVN_NO_ERROR;
3234251881Speter}
3235251881Speter
3236251881Speterstatic svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3237251881Speter                                 apr_array_header_t *params, void *baton)
3238251881Speter{
3239251881Speter  svn_revnum_t start_rev, end_rev, rev, low_water_mark;
3240251881Speter  svn_boolean_t send_deltas;
3241251881Speter  server_baton_t *b = baton;
3242251881Speter  apr_pool_t *iterpool;
3243251881Speter  authz_baton_t ab;
3244251881Speter
3245251881Speter  ab.server = b;
3246251881Speter  ab.conn = conn;
3247251881Speter
3248251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrrb", &start_rev,
3249251881Speter                                 &end_rev, &low_water_mark,
3250251881Speter                                 &send_deltas));
3251251881Speter
3252251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
3253251881Speter
3254251881Speter  iterpool = svn_pool_create(pool);
3255251881Speter  for (rev = start_rev; rev <= end_rev; rev++)
3256251881Speter    {
3257251881Speter      apr_hash_t *props;
3258251881Speter
3259251881Speter      svn_pool_clear(iterpool);
3260251881Speter
3261299742Sdim      SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props,
3262299742Sdim                                                 b->repository->repos, rev,
3263251881Speter                                                 authz_check_access_cb_func(b),
3264251881Speter                                                 &ab,
3265251881Speter                                                 iterpool));
3266251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops"));
3267251881Speter      SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props));
3268251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)"));
3269251881Speter
3270251881Speter      SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3271251881Speter                                  send_deltas, iterpool));
3272251881Speter
3273251881Speter    }
3274251881Speter  svn_pool_destroy(iterpool);
3275251881Speter
3276251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3277251881Speter
3278251881Speter  return SVN_NO_ERROR;
3279251881Speter}
3280251881Speter
3281251881Speterstatic svn_error_t *
3282251881Speterget_deleted_rev(svn_ra_svn_conn_t *conn,
3283251881Speter                apr_pool_t *pool,
3284251881Speter                apr_array_header_t *params,
3285251881Speter                void *baton)
3286251881Speter{
3287251881Speter  server_baton_t *b = baton;
3288251881Speter  const char *path, *full_path;
3289251881Speter  svn_revnum_t peg_revision;
3290251881Speter  svn_revnum_t end_revision;
3291251881Speter  svn_revnum_t revision_deleted;
3292251881Speter
3293251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crr",
3294251881Speter                                 &path, &peg_revision, &end_revision));
3295299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data,
3296251881Speter                               svn_relpath_canonicalize(path, pool), pool);
3297251881Speter  SVN_ERR(log_command(b, conn, pool, "get-deleted-rev"));
3298251881Speter  SVN_ERR(trivial_auth_request(conn, pool, b));
3299299742Sdim  SVN_ERR(svn_repos_deleted_rev(b->repository->fs, full_path, peg_revision,
3300299742Sdim                                end_revision, &revision_deleted, pool));
3301251881Speter  SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted));
3302251881Speter  return SVN_NO_ERROR;
3303251881Speter}
3304251881Speter
3305251881Speterstatic svn_error_t *
3306251881Speterget_inherited_props(svn_ra_svn_conn_t *conn,
3307251881Speter                    apr_pool_t *pool,
3308251881Speter                    apr_array_header_t *params,
3309251881Speter                    void *baton)
3310251881Speter{
3311251881Speter  server_baton_t *b = baton;
3312251881Speter  const char *path, *full_path;
3313251881Speter  svn_revnum_t rev;
3314251881Speter  svn_fs_root_t *root;
3315251881Speter  apr_array_header_t *inherited_props;
3316251881Speter  int i;
3317251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
3318251881Speter  authz_baton_t ab;
3319251881Speter
3320251881Speter  ab.server = b;
3321251881Speter  ab.conn = conn;
3322251881Speter
3323251881Speter  /* Parse arguments. */
3324251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, iterpool, "c(?r)", &path, &rev));
3325251881Speter
3326299742Sdim  full_path = svn_fspath__join(b->repository->fs_path->data,
3327251881Speter                               svn_relpath_canonicalize(path, iterpool),
3328251881Speter                               pool);
3329251881Speter
3330251881Speter  /* Check authorizations */
3331251881Speter  SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read,
3332251881Speter                           full_path, FALSE));
3333251881Speter
3334251881Speter  if (!SVN_IS_VALID_REVNUM(rev))
3335299742Sdim    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
3336251881Speter
3337251881Speter  SVN_ERR(log_command(b, conn, pool, "%s",
3338251881Speter                      svn_log__get_inherited_props(full_path, rev,
3339251881Speter                                                   iterpool)));
3340251881Speter
3341251881Speter  /* Fetch the properties and a stream for the contents. */
3342299742Sdim  SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, iterpool));
3343251881Speter  SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool));
3344251881Speter
3345251881Speter  /* Send successful command response with revision and props. */
3346251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success"));
3347251881Speter
3348251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!"));
3349251881Speter
3350251881Speter  for (i = 0; i < inherited_props->nelts; i++)
3351251881Speter    {
3352251881Speter      svn_prop_inherited_item_t *iprop =
3353251881Speter        APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
3354251881Speter
3355251881Speter      svn_pool_clear(iterpool);
3356251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
3357251881Speter                                      iprop->path_or_url));
3358251881Speter      SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
3359251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
3360251881Speter                                      iprop->path_or_url));
3361251881Speter    }
3362251881Speter
3363251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))"));
3364251881Speter  svn_pool_destroy(iterpool);
3365251881Speter  return SVN_NO_ERROR;
3366251881Speter}
3367251881Speter
3368251881Speterstatic const svn_ra_svn_cmd_entry_t main_commands[] = {
3369251881Speter  { "reparent",        reparent },
3370251881Speter  { "get-latest-rev",  get_latest_rev },
3371251881Speter  { "get-dated-rev",   get_dated_rev },
3372251881Speter  { "change-rev-prop", change_rev_prop },
3373251881Speter  { "change-rev-prop2",change_rev_prop2 },
3374251881Speter  { "rev-proplist",    rev_proplist },
3375251881Speter  { "rev-prop",        rev_prop },
3376251881Speter  { "commit",          commit },
3377251881Speter  { "get-file",        get_file },
3378251881Speter  { "get-dir",         get_dir },
3379251881Speter  { "update",          update },
3380251881Speter  { "switch",          switch_cmd },
3381251881Speter  { "status",          status },
3382251881Speter  { "diff",            diff },
3383251881Speter  { "get-mergeinfo",   get_mergeinfo },
3384251881Speter  { "log",             log_cmd },
3385251881Speter  { "check-path",      check_path },
3386251881Speter  { "stat",            stat_cmd },
3387251881Speter  { "get-locations",   get_locations },
3388251881Speter  { "get-location-segments",   get_location_segments },
3389251881Speter  { "get-file-revs",   get_file_revs },
3390251881Speter  { "lock",            lock },
3391251881Speter  { "lock-many",       lock_many },
3392251881Speter  { "unlock",          unlock },
3393251881Speter  { "unlock-many",     unlock_many },
3394251881Speter  { "get-lock",        get_lock },
3395251881Speter  { "get-locks",       get_locks },
3396251881Speter  { "replay",          replay },
3397251881Speter  { "replay-range",    replay_range },
3398251881Speter  { "get-deleted-rev", get_deleted_rev },
3399251881Speter  { "get-iprops",      get_inherited_props },
3400251881Speter  { NULL }
3401251881Speter};
3402251881Speter
3403251881Speter/* Skip past the scheme part of a URL, including the tunnel specification
3404251881Speter * if present.  Return NULL if the scheme part is invalid for ra_svn. */
3405251881Speterstatic const char *skip_scheme_part(const char *url)
3406251881Speter{
3407251881Speter  if (strncmp(url, "svn", 3) != 0)
3408251881Speter    return NULL;
3409251881Speter  url += 3;
3410251881Speter  if (*url == '+')
3411251881Speter    url += strcspn(url, ":");
3412251881Speter  if (strncmp(url, "://", 3) != 0)
3413251881Speter    return NULL;
3414251881Speter  return url + 3;
3415251881Speter}
3416251881Speter
3417251881Speter/* Check that PATH is a valid repository path, meaning it doesn't contain any
3418251881Speter   '..' path segments.
3419251881Speter   NOTE: This is similar to svn_path_is_backpath_present, but that function
3420251881Speter   assumes the path separator is '/'.  This function also checks for
3421251881Speter   segments delimited by the local path separator. */
3422251881Speterstatic svn_boolean_t
3423251881Speterrepos_path_valid(const char *path)
3424251881Speter{
3425251881Speter  const char *s = path;
3426251881Speter
3427251881Speter  while (*s)
3428251881Speter    {
3429251881Speter      /* Scan for the end of the segment. */
3430251881Speter      while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
3431251881Speter        ++path;
3432251881Speter
3433251881Speter      /* Check for '..'. */
3434251881Speter#ifdef WIN32
3435251881Speter      /* On Windows, don't allow sequences of more than one character
3436251881Speter         consisting of just dots and spaces.  Win32 functions treat
3437251881Speter         paths such as ".. " and "......." inconsistently.  Make sure
3438251881Speter         no one can escape out of the root. */
3439251881Speter      if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s))
3440251881Speter        return FALSE;
3441251881Speter#else  /* ! WIN32 */
3442251881Speter      if (path - s == 2 && s[0] == '.' && s[1] == '.')
3443251881Speter        return FALSE;
3444251881Speter#endif
3445251881Speter
3446251881Speter      /* Skip all separators. */
3447251881Speter      while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
3448251881Speter        ++path;
3449251881Speter      s = path;
3450251881Speter    }
3451251881Speter
3452251881Speter  return TRUE;
3453251881Speter}
3454251881Speter
3455251881Speter/* Look for the repository given by URL, using ROOT as the virtual
3456299742Sdim * repository root.  If we find one, fill in the repos, fs, repos_url,
3457299742Sdim * and fs_path fields of REPOSITORY.  VHOST and READ_ONLY flags are the
3458299742Sdim * same as in the server baton.
3459299742Sdim *
3460299742Sdim * CONFIG_POOL and AUTHZ_POOL shall be used to load any object of the
3461299742Sdim * respective type.
3462299742Sdim *
3463299742Sdim * Use SCRATCH_POOL for temporary allocations.
3464299742Sdim *
3465251881Speter */
3466299742Sdimstatic svn_error_t *
3467299742Sdimfind_repos(const char *url,
3468299742Sdim           const char *root,
3469299742Sdim           svn_boolean_t vhost,
3470299742Sdim           svn_boolean_t read_only,
3471299742Sdim           svn_config_t *cfg,
3472299742Sdim           repository_t *repository,
3473299742Sdim           svn_repos__config_pool_t *config_pool,
3474299742Sdim           svn_repos__authz_pool_t *authz_pool,
3475299742Sdim           apr_hash_t *fs_config,
3476299742Sdim           apr_pool_t *result_pool,
3477299742Sdim           apr_pool_t *scratch_pool)
3478251881Speter{
3479299742Sdim  const char *path, *full_path, *fs_path, *hooks_env;
3480251881Speter  svn_stringbuf_t *url_buf;
3481251881Speter
3482251881Speter  /* Skip past the scheme and authority part. */
3483251881Speter  path = skip_scheme_part(url);
3484251881Speter  if (path == NULL)
3485251881Speter    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
3486251881Speter                             "Non-svn URL passed to svn server: '%s'", url);
3487251881Speter
3488299742Sdim  if (! vhost)
3489251881Speter    {
3490251881Speter      path = strchr(path, '/');
3491251881Speter      if (path == NULL)
3492251881Speter        path = "";
3493251881Speter    }
3494299742Sdim  path = svn_relpath_canonicalize(path, scratch_pool);
3495299742Sdim  path = svn_path_uri_decode(path, scratch_pool);
3496251881Speter
3497251881Speter  /* Ensure that it isn't possible to escape the root by disallowing
3498251881Speter     '..' segments. */
3499251881Speter  if (!repos_path_valid(path))
3500251881Speter    return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
3501251881Speter                            "Couldn't determine repository path");
3502251881Speter
3503251881Speter  /* Join the server-configured root with the client path. */
3504299742Sdim  full_path = svn_dirent_join(svn_dirent_canonicalize(root, scratch_pool),
3505299742Sdim                              path, scratch_pool);
3506251881Speter
3507251881Speter  /* Search for a repository in the full path. */
3508299742Sdim  repository->repos_root = svn_repos_find_root_path(full_path, result_pool);
3509299742Sdim  if (!repository->repos_root)
3510251881Speter    return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
3511251881Speter                             "No repository found in '%s'", url);
3512251881Speter
3513251881Speter  /* Open the repository and fill in b with the resulting information. */
3514299742Sdim  SVN_ERR(svn_repos_open3(&repository->repos, repository->repos_root,
3515299742Sdim                          fs_config, result_pool, scratch_pool));
3516299742Sdim  SVN_ERR(svn_repos_remember_client_capabilities(repository->repos,
3517299742Sdim                                                 repository->capabilities));
3518299742Sdim  repository->fs = svn_repos_fs(repository->repos);
3519299742Sdim  fs_path = full_path + strlen(repository->repos_root);
3520299742Sdim  repository->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/",
3521299742Sdim                                             result_pool);
3522299742Sdim  url_buf = svn_stringbuf_create(url, result_pool);
3523251881Speter  svn_path_remove_components(url_buf,
3524299742Sdim                        svn_path_component_count(repository->fs_path->data));
3525299742Sdim  repository->repos_url = url_buf->data;
3526299742Sdim  repository->authz_repos_name = svn_dirent_is_child(root,
3527299742Sdim                                                     repository->repos_root,
3528299742Sdim                                                     result_pool);
3529299742Sdim  if (repository->authz_repos_name == NULL)
3530299742Sdim    repository->repos_name = svn_dirent_basename(repository->repos_root,
3531299742Sdim                                                 result_pool);
3532251881Speter  else
3533299742Sdim    repository->repos_name = repository->authz_repos_name;
3534299742Sdim  repository->repos_name = svn_path_uri_encode(repository->repos_name,
3535299742Sdim                                               result_pool);
3536251881Speter
3537251881Speter  /* If the svnserve configuration has not been loaded then load it from the
3538251881Speter   * repository. */
3539299742Sdim  if (NULL == cfg)
3540251881Speter    {
3541299742Sdim      repository->base = svn_repos_conf_dir(repository->repos, result_pool);
3542251881Speter
3543299742Sdim      SVN_ERR(svn_repos__config_pool_get(&cfg, NULL, config_pool,
3544299742Sdim                                         svn_repos_svnserve_conf
3545299742Sdim                                            (repository->repos, result_pool),
3546299742Sdim                                         FALSE, FALSE, repository->repos,
3547299742Sdim                                         result_pool));
3548251881Speter    }
3549299742Sdim
3550299742Sdim  SVN_ERR(load_pwdb_config(repository, cfg, config_pool, result_pool));
3551299742Sdim  SVN_ERR(load_authz_config(repository, repository->repos_root, cfg,
3552299742Sdim                            authz_pool, result_pool));
3553299742Sdim
3554299742Sdim#ifdef SVN_HAVE_SASL
3555251881Speter    {
3556299742Sdim      const char *val;
3557299742Sdim
3558299742Sdim      /* Should we use Cyrus SASL? */
3559299742Sdim      SVN_ERR(svn_config_get_bool(cfg, &repository->use_sasl,
3560299742Sdim                                  SVN_CONFIG_SECTION_SASL,
3561299742Sdim                                  SVN_CONFIG_OPTION_USE_SASL, FALSE));
3562299742Sdim
3563299742Sdim      svn_config_get(cfg, &val, SVN_CONFIG_SECTION_SASL,
3564299742Sdim                    SVN_CONFIG_OPTION_MIN_SSF, "0");
3565299742Sdim      SVN_ERR(svn_cstring_atoui(&repository->min_ssf, val));
3566299742Sdim
3567299742Sdim      svn_config_get(cfg, &val, SVN_CONFIG_SECTION_SASL,
3568299742Sdim                    SVN_CONFIG_OPTION_MAX_SSF, "256");
3569299742Sdim      SVN_ERR(svn_cstring_atoui(&repository->max_ssf, val));
3570251881Speter    }
3571251881Speter#endif
3572251881Speter
3573251881Speter  /* Use the repository UUID as the default realm. */
3574299742Sdim  SVN_ERR(svn_fs_get_uuid(repository->fs, &repository->realm, scratch_pool));
3575299742Sdim  svn_config_get(cfg, &repository->realm, SVN_CONFIG_SECTION_GENERAL,
3576299742Sdim                 SVN_CONFIG_OPTION_REALM, repository->realm);
3577299742Sdim  repository->realm = apr_pstrdup(result_pool, repository->realm);
3578251881Speter
3579251881Speter  /* Make sure it's possible for the client to authenticate.  Note
3580251881Speter     that this doesn't take into account any authz configuration read
3581251881Speter     above, because we can't know about access it grants until paths
3582251881Speter     are given by the client. */
3583299742Sdim  set_access(repository, cfg, read_only);
3584251881Speter
3585251881Speter  /* Configure hook script environment variables. */
3586299742Sdim  svn_config_get(cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL,
3587251881Speter                 SVN_CONFIG_OPTION_HOOKS_ENV, NULL);
3588251881Speter  if (hooks_env)
3589299742Sdim    hooks_env = svn_dirent_internal_style(hooks_env, scratch_pool);
3590251881Speter
3591309512Speter  SVN_ERR(svn_repos_hooks_setenv(repository->repos, hooks_env, scratch_pool));
3592299742Sdim  repository->hooks_env = apr_pstrdup(result_pool, hooks_env);
3593299742Sdim
3594251881Speter  return SVN_NO_ERROR;
3595251881Speter}
3596251881Speter
3597251881Speter/* Compute the authentication name EXTERNAL should be able to get, if any. */
3598251881Speterstatic const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
3599251881Speter{
3600251881Speter  /* Only offer EXTERNAL for connections tunneled over a login agent. */
3601251881Speter  if (!params->tunnel)
3602251881Speter    return NULL;
3603251881Speter
3604251881Speter  /* If a tunnel user was provided on the command line, use that. */
3605251881Speter  if (params->tunnel_user)
3606251881Speter    return params->tunnel_user;
3607251881Speter
3608251881Speter  return svn_user_get_name(pool);
3609251881Speter}
3610251881Speter
3611251881Speterstatic void
3612251881Speterfs_warning_func(void *baton, svn_error_t *err)
3613251881Speter{
3614251881Speter  fs_warning_baton_t *b = baton;
3615299742Sdim  log_error(err, b->server);
3616251881Speter}
3617251881Speter
3618251881Speter/* Return the normalized repository-relative path for the given PATH
3619251881Speter * (may be a URL, full path or relative path) and fs contained in the
3620251881Speter * server baton BATON. Allocate the result in POOL.
3621251881Speter */
3622251881Speterstatic const char *
3623251881Speterget_normalized_repo_rel_path(void *baton,
3624251881Speter                             const char *path,
3625251881Speter                             apr_pool_t *pool)
3626251881Speter{
3627251881Speter  server_baton_t *sb = baton;
3628251881Speter
3629251881Speter  if (svn_path_is_url(path))
3630251881Speter    {
3631251881Speter      /* This is a copyfrom URL. */
3632299742Sdim      path = svn_uri_skip_ancestor(sb->repository->repos_url, path, pool);
3633251881Speter      path = svn_fspath__canonicalize(path, pool);
3634251881Speter    }
3635251881Speter  else
3636251881Speter    {
3637251881Speter      /* This is a base-relative path. */
3638251881Speter      if ((path)[0] != '/')
3639251881Speter        /* Get an absolute path for use in the FS. */
3640299742Sdim        path = svn_fspath__join(sb->repository->fs_path->data, path, pool);
3641251881Speter    }
3642251881Speter
3643251881Speter  return path;
3644251881Speter}
3645251881Speter
3646251881Speter/* Get the revision root for REVISION in fs given by server baton BATON
3647251881Speter * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM.
3648251881Speter * Use POOL for allocations.
3649251881Speter */
3650251881Speterstatic svn_error_t *
3651251881Speterget_revision_root(svn_fs_root_t **fs_root,
3652251881Speter                  void *baton,
3653251881Speter                  svn_revnum_t revision,
3654251881Speter                  apr_pool_t *pool)
3655251881Speter{
3656251881Speter  server_baton_t *sb = baton;
3657251881Speter
3658251881Speter  if (!SVN_IS_VALID_REVNUM(revision))
3659299742Sdim    SVN_ERR(svn_fs_youngest_rev(&revision, sb->repository->fs, pool));
3660251881Speter
3661299742Sdim  SVN_ERR(svn_fs_revision_root(fs_root, sb->repository->fs, revision, pool));
3662251881Speter
3663251881Speter  return SVN_NO_ERROR;
3664251881Speter}
3665251881Speter
3666251881Speterstatic svn_error_t *
3667251881Speterfetch_props_func(apr_hash_t **props,
3668251881Speter                 void *baton,
3669251881Speter                 const char *path,
3670251881Speter                 svn_revnum_t base_revision,
3671251881Speter                 apr_pool_t *result_pool,
3672251881Speter                 apr_pool_t *scratch_pool)
3673251881Speter{
3674251881Speter  svn_fs_root_t *fs_root;
3675251881Speter  svn_error_t *err;
3676251881Speter
3677251881Speter  path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3678251881Speter  SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3679251881Speter
3680251881Speter  err = svn_fs_node_proplist(props, fs_root, path, result_pool);
3681251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3682251881Speter    {
3683251881Speter      svn_error_clear(err);
3684251881Speter      *props = apr_hash_make(result_pool);
3685251881Speter      return SVN_NO_ERROR;
3686251881Speter    }
3687251881Speter  else if (err)
3688251881Speter    return svn_error_trace(err);
3689251881Speter
3690251881Speter  return SVN_NO_ERROR;
3691251881Speter}
3692251881Speter
3693251881Speterstatic svn_error_t *
3694251881Speterfetch_kind_func(svn_node_kind_t *kind,
3695251881Speter                void *baton,
3696251881Speter                const char *path,
3697251881Speter                svn_revnum_t base_revision,
3698251881Speter                apr_pool_t *scratch_pool)
3699251881Speter{
3700251881Speter  svn_fs_root_t *fs_root;
3701251881Speter
3702251881Speter  path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3703251881Speter  SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3704251881Speter
3705251881Speter  SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
3706251881Speter
3707251881Speter  return SVN_NO_ERROR;
3708251881Speter}
3709251881Speter
3710251881Speterstatic svn_error_t *
3711251881Speterfetch_base_func(const char **filename,
3712251881Speter                void *baton,
3713251881Speter                const char *path,
3714251881Speter                svn_revnum_t base_revision,
3715251881Speter                apr_pool_t *result_pool,
3716251881Speter                apr_pool_t *scratch_pool)
3717251881Speter{
3718251881Speter  svn_stream_t *contents;
3719251881Speter  svn_stream_t *file_stream;
3720251881Speter  const char *tmp_filename;
3721251881Speter  svn_fs_root_t *fs_root;
3722251881Speter  svn_error_t *err;
3723251881Speter
3724251881Speter  path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3725251881Speter  SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3726251881Speter
3727251881Speter  err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
3728251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3729251881Speter    {
3730251881Speter      svn_error_clear(err);
3731251881Speter      *filename = NULL;
3732251881Speter      return SVN_NO_ERROR;
3733251881Speter    }
3734251881Speter  else if (err)
3735251881Speter    return svn_error_trace(err);
3736251881Speter  SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
3737251881Speter                                 svn_io_file_del_on_pool_cleanup,
3738251881Speter                                 scratch_pool, scratch_pool));
3739251881Speter  SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
3740251881Speter
3741251881Speter  *filename = apr_pstrdup(result_pool, tmp_filename);
3742251881Speter
3743251881Speter  return SVN_NO_ERROR;
3744251881Speter}
3745251881Speter
3746299742Sdimclient_info_t *
3747299742Sdimget_client_info(svn_ra_svn_conn_t *conn,
3748299742Sdim                serve_params_t *params,
3749299742Sdim                apr_pool_t *pool)
3750251881Speter{
3751299742Sdim  client_info_t *client_info = apr_pcalloc(pool, sizeof(*client_info));
3752299742Sdim
3753299742Sdim  client_info->tunnel = params->tunnel;
3754299742Sdim  client_info->tunnel_user = get_tunnel_user(params, pool);
3755299742Sdim  client_info->user = NULL;
3756299742Sdim  client_info->authz_user = NULL;
3757299742Sdim  client_info->remote_host = svn_ra_svn_conn_remote_host(conn);
3758299742Sdim
3759299742Sdim  return client_info;
3760299742Sdim}
3761299742Sdim
3762299742Sdim/* Construct the server baton for CONN using PARAMS and return it in *BATON.
3763299742Sdim * It's lifetime is the same as that of CONN.  SCRATCH_POOL
3764299742Sdim */
3765299742Sdimstatic svn_error_t *
3766299742Sdimconstruct_server_baton(server_baton_t **baton,
3767299742Sdim                       svn_ra_svn_conn_t *conn,
3768299742Sdim                       serve_params_t *params,
3769299742Sdim                       apr_pool_t *scratch_pool)
3770299742Sdim{
3771251881Speter  svn_error_t *err, *io_err;
3772251881Speter  apr_uint64_t ver;
3773299742Sdim  const char *client_url, *ra_client_string, *client_string;
3774299742Sdim  apr_array_header_t *caplist;
3775299742Sdim  apr_pool_t *conn_pool = svn_ra_svn__get_pool(conn);
3776299742Sdim  server_baton_t *b = apr_pcalloc(conn_pool, sizeof(*b));
3777299742Sdim  fs_warning_baton_t *warn_baton;
3778299742Sdim  svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(scratch_pool);
3779251881Speter
3780299742Sdim  b->repository = apr_pcalloc(conn_pool, sizeof(*b->repository));
3781299742Sdim  b->repository->username_case = params->username_case;
3782299742Sdim  b->repository->base = params->base;
3783299742Sdim  b->repository->pwdb = NULL;
3784299742Sdim  b->repository->authzdb = NULL;
3785299742Sdim  b->repository->realm = NULL;
3786299742Sdim  b->repository->use_sasl = FALSE;
3787251881Speter
3788299742Sdim  b->read_only = params->read_only;
3789299742Sdim  b->pool = conn_pool;
3790299742Sdim  b->vhost = params->vhost;
3791251881Speter
3792299742Sdim  b->logger = params->logger;
3793299742Sdim  b->client_info = get_client_info(conn, params, conn_pool);
3794299742Sdim
3795251881Speter  /* Send greeting.  We don't support version 1 any more, so we can
3796251881Speter   * send an empty mechlist. */
3797251881Speter  if (params->compression_level > 0)
3798299742Sdim    SVN_ERR(svn_ra_svn__write_cmd_response(conn, scratch_pool,
3799299742Sdim                                           "nn()(wwwwwwwwwww)",
3800251881Speter                                           (apr_uint64_t) 2, (apr_uint64_t) 2,
3801251881Speter                                           SVN_RA_SVN_CAP_EDIT_PIPELINE,
3802251881Speter                                           SVN_RA_SVN_CAP_SVNDIFF1,
3803251881Speter                                           SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3804251881Speter                                           SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3805251881Speter                                           SVN_RA_SVN_CAP_DEPTH,
3806251881Speter                                           SVN_RA_SVN_CAP_LOG_REVPROPS,
3807251881Speter                                           SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3808251881Speter                                           SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3809251881Speter                                           SVN_RA_SVN_CAP_INHERITED_PROPS,
3810251881Speter                                           SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3811251881Speter                                           SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3812251881Speter                                           ));
3813251881Speter  else
3814299742Sdim    SVN_ERR(svn_ra_svn__write_cmd_response(conn, scratch_pool,
3815299742Sdim                                           "nn()(wwwwwwwwww)",
3816251881Speter                                           (apr_uint64_t) 2, (apr_uint64_t) 2,
3817251881Speter                                           SVN_RA_SVN_CAP_EDIT_PIPELINE,
3818251881Speter                                           SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3819251881Speter                                           SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3820251881Speter                                           SVN_RA_SVN_CAP_DEPTH,
3821251881Speter                                           SVN_RA_SVN_CAP_LOG_REVPROPS,
3822251881Speter                                           SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3823251881Speter                                           SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3824251881Speter                                           SVN_RA_SVN_CAP_INHERITED_PROPS,
3825251881Speter                                           SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3826251881Speter                                           SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3827251881Speter                                           ));
3828251881Speter
3829251881Speter  /* Read client response, which we assume to be in version 2 format:
3830251881Speter   * version, capability list, and client URL; then we do an auth
3831251881Speter   * request. */
3832299742Sdim  SVN_ERR(svn_ra_svn__read_tuple(conn, scratch_pool, "nlc?c(?c)",
3833251881Speter                                 &ver, &caplist, &client_url,
3834251881Speter                                 &ra_client_string,
3835251881Speter                                 &client_string));
3836251881Speter  if (ver != 2)
3837251881Speter    return SVN_NO_ERROR;
3838251881Speter
3839299742Sdim  client_url = svn_uri_canonicalize(client_url, conn_pool);
3840251881Speter  SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
3841251881Speter
3842251881Speter  /* All released versions of Subversion support edit-pipeline,
3843251881Speter   * so we do not accept connections from clients that do not. */
3844251881Speter  if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
3845251881Speter    return SVN_NO_ERROR;
3846251881Speter
3847251881Speter  /* find_repos needs the capabilities as a list of words (eventually
3848251881Speter     they get handed to the start-commit hook).  While we could add a
3849251881Speter     new interface to re-retrieve them from conn and convert the
3850251881Speter     result to a list, it's simpler to just convert caplist by hand
3851251881Speter     here, since we already have it and turning 'svn_ra_svn_item_t's
3852251881Speter     into 'const char *'s is pretty easy.
3853251881Speter
3854251881Speter     We only record capabilities we care about.  The client may report
3855251881Speter     more (because it doesn't know what the server cares about). */
3856251881Speter  {
3857251881Speter    int i;
3858251881Speter    svn_ra_svn_item_t *item;
3859251881Speter
3860299742Sdim    b->repository->capabilities = apr_array_make(conn_pool, 1,
3861299742Sdim                                                 sizeof(const char *));
3862251881Speter    for (i = 0; i < caplist->nelts; i++)
3863251881Speter      {
3864251881Speter        item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
3865251881Speter        /* ra_svn_set_capabilities() already type-checked for us */
3866251881Speter        if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0)
3867251881Speter          {
3868299742Sdim            APR_ARRAY_PUSH(b->repository->capabilities, const char *)
3869251881Speter              = SVN_RA_CAPABILITY_MERGEINFO;
3870251881Speter          }
3871251881Speter        /* Save for operational log. */
3872251881Speter        if (cap_log->len > 0)
3873251881Speter          svn_stringbuf_appendcstr(cap_log, " ");
3874251881Speter        svn_stringbuf_appendcstr(cap_log, item->u.word);
3875251881Speter      }
3876251881Speter  }
3877251881Speter
3878299742Sdim  err = handle_config_error(find_repos(client_url, params->root, b->vhost,
3879299742Sdim                                       b->read_only, params->cfg,
3880299742Sdim                                       b->repository, params->config_pool,
3881299742Sdim                                       params->authz_pool, params->fs_config,
3882299742Sdim                                       conn_pool, scratch_pool),
3883299742Sdim                            b);
3884251881Speter  if (!err)
3885251881Speter    {
3886299742Sdim      if (b->repository->anon_access == NO_ACCESS
3887299742Sdim          && (b->repository->auth_access == NO_ACCESS
3888299742Sdim              || (!b->client_info->tunnel_user && !b->repository->pwdb
3889299742Sdim                  && !b->repository->use_sasl)))
3890251881Speter        err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3891299742Sdim                                   "No access allowed to this repository",
3892299742Sdim                                   b);
3893251881Speter    }
3894299742Sdim  if (!err)
3895299742Sdim    {
3896299742Sdim      SVN_ERR(auth_request(conn, scratch_pool, b, READ_ACCESS, FALSE));
3897299742Sdim      if (current_access(b) == NO_ACCESS)
3898299742Sdim        err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3899299742Sdim                                   "Not authorized for access", b);
3900299742Sdim    }
3901251881Speter  if (err)
3902251881Speter    {
3903299742Sdim      log_error(err, b);
3904299742Sdim      io_err = svn_ra_svn__write_cmd_failure(conn, scratch_pool, err);
3905251881Speter      svn_error_clear(err);
3906251881Speter      SVN_ERR(io_err);
3907299742Sdim      return svn_ra_svn__flush(conn, scratch_pool);
3908251881Speter    }
3909251881Speter
3910299742Sdim  SVN_ERR(svn_fs_get_uuid(b->repository->fs, &b->repository->uuid,
3911299742Sdim                          conn_pool));
3912251881Speter
3913251881Speter  /* We can't claim mergeinfo capability until we know whether the
3914251881Speter     repository supports mergeinfo (i.e., is not a 1.4 repository),
3915251881Speter     but we don't get the repository url from the client until after
3916251881Speter     we've already sent the initial list of server capabilities.  So
3917251881Speter     we list repository capabilities here, in our first response after
3918251881Speter     the client has sent the url. */
3919251881Speter  {
3920251881Speter    svn_boolean_t supports_mergeinfo;
3921299742Sdim    SVN_ERR(svn_repos_has_capability(b->repository->repos,
3922299742Sdim                                     &supports_mergeinfo,
3923299742Sdim                                     SVN_REPOS_CAPABILITY_MERGEINFO,
3924299742Sdim                                     scratch_pool));
3925251881Speter
3926299742Sdim    SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w(cc(!",
3927299742Sdim                                    "success", b->repository->uuid,
3928299742Sdim                                    b->repository->repos_url));
3929251881Speter    if (supports_mergeinfo)
3930299742Sdim      SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool,
3931299742Sdim                                     SVN_RA_SVN_CAP_MERGEINFO));
3932299742Sdim    SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!))"));
3933299742Sdim    SVN_ERR(svn_ra_svn__flush(conn, scratch_pool));
3934251881Speter  }
3935251881Speter
3936299742Sdim  /* Log the open. */
3937299742Sdim  if (ra_client_string == NULL || ra_client_string[0] == '\0')
3938299742Sdim    ra_client_string = "-";
3939299742Sdim  else
3940299742Sdim    ra_client_string = svn_path_uri_encode(ra_client_string, scratch_pool);
3941299742Sdim  if (client_string == NULL || client_string[0] == '\0')
3942299742Sdim    client_string = "-";
3943299742Sdim  else
3944299742Sdim    client_string = svn_path_uri_encode(client_string, scratch_pool);
3945299742Sdim  SVN_ERR(log_command(b, conn, scratch_pool,
3946299742Sdim                      "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s",
3947299742Sdim                      ver, cap_log->data,
3948299742Sdim                      svn_path_uri_encode(b->repository->fs_path->data,
3949299742Sdim                                          scratch_pool),
3950299742Sdim                      ra_client_string, client_string));
3951299742Sdim
3952299742Sdim  warn_baton = apr_pcalloc(conn_pool, sizeof(*warn_baton));
3953299742Sdim  warn_baton->server = b;
3954299742Sdim  warn_baton->conn = conn;
3955299742Sdim  svn_fs_set_warning_func(b->repository->fs, fs_warning_func, warn_baton);
3956299742Sdim
3957251881Speter  /* Set up editor shims. */
3958251881Speter  {
3959251881Speter    svn_delta_shim_callbacks_t *callbacks =
3960299742Sdim                                svn_delta_shim_callbacks_default(conn_pool);
3961251881Speter
3962251881Speter    callbacks->fetch_base_func = fetch_base_func;
3963251881Speter    callbacks->fetch_props_func = fetch_props_func;
3964251881Speter    callbacks->fetch_kind_func = fetch_kind_func;
3965299742Sdim    callbacks->fetch_baton = b;
3966251881Speter
3967251881Speter    SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks));
3968251881Speter  }
3969251881Speter
3970299742Sdim  *baton = b;
3971299742Sdim
3972299742Sdim  return SVN_NO_ERROR;
3973251881Speter}
3974299742Sdim
3975299742Sdimsvn_error_t *
3976299742Sdimserve_interruptable(svn_boolean_t *terminate_p,
3977299742Sdim                    connection_t *connection,
3978299742Sdim                    svn_boolean_t (* is_busy)(connection_t *),
3979299742Sdim                    apr_pool_t *pool)
3980299742Sdim{
3981299742Sdim  svn_boolean_t terminate = FALSE;
3982299742Sdim  svn_error_t *err = NULL;
3983299742Sdim  const svn_ra_svn_cmd_entry_t *command;
3984299742Sdim  apr_pool_t *iterpool = svn_pool_create(pool);
3985299742Sdim
3986299742Sdim  /* Prepare command parser. */
3987299742Sdim  apr_hash_t *cmd_hash = apr_hash_make(pool);
3988299742Sdim  for (command = main_commands; command->cmdname; command++)
3989299742Sdim    svn_hash_sets(cmd_hash, command->cmdname, command);
3990299742Sdim
3991299742Sdim  /* Auto-initialize connection */
3992299742Sdim  if (! connection->conn)
3993299742Sdim    {
3994299742Sdim      apr_status_t ar;
3995299742Sdim
3996299742Sdim      /* Enable TCP keep-alives on the socket so we time out when
3997299742Sdim       * the connection breaks due to network-layer problems.
3998299742Sdim       * If the peer has dropped the connection due to a network partition
3999299742Sdim       * or a crash, or if the peer no longer considers the connection
4000299742Sdim       * valid because we are behind a NAT and our public IP has changed,
4001299742Sdim       * it will respond to the keep-alive probe with a RST instead of an
4002299742Sdim       * acknowledgment segment, which will cause svn to abort the session
4003299742Sdim       * even while it is currently blocked waiting for data from the peer. */
4004299742Sdim      ar = apr_socket_opt_set(connection->usock, APR_SO_KEEPALIVE, 1);
4005299742Sdim      if (ar)
4006299742Sdim        {
4007299742Sdim          /* It's not a fatal error if we cannot enable keep-alives. */
4008299742Sdim        }
4009299742Sdim
4010299742Sdim      /* create the connection, configure ports etc. */
4011299742Sdim      connection->conn
4012299742Sdim        = svn_ra_svn_create_conn4(connection->usock, NULL, NULL,
4013299742Sdim                                  connection->params->compression_level,
4014299742Sdim                                  connection->params->zero_copy_limit,
4015299742Sdim                                  connection->params->error_check_interval,
4016299742Sdim                                  connection->pool);
4017299742Sdim
4018299742Sdim      /* Construct server baton and open the repository for the first time. */
4019299742Sdim      err = construct_server_baton(&connection->baton, connection->conn,
4020299742Sdim                                   connection->params, pool);
4021299742Sdim    }
4022299742Sdim
4023299742Sdim  /* If we can't access the repo for some reason, end this connection. */
4024299742Sdim  if (err)
4025299742Sdim    terminate = TRUE;
4026299742Sdim
4027299742Sdim  /* Process incoming commands. */
4028299742Sdim  while (!terminate && !err)
4029299742Sdim    {
4030299742Sdim      svn_pool_clear(iterpool);
4031299742Sdim      if (is_busy && is_busy(connection))
4032299742Sdim        {
4033299742Sdim          svn_boolean_t has_command;
4034299742Sdim
4035299742Sdim          /* If the server is busy, execute just one command and only if
4036299742Sdim           * there is one currently waiting in our receive buffers.
4037299742Sdim           */
4038299742Sdim          err = svn_ra_svn__has_command(&has_command, &terminate,
4039299742Sdim                                        connection->conn, iterpool);
4040299742Sdim          if (!err && has_command)
4041299742Sdim            err = svn_ra_svn__handle_command(&terminate, cmd_hash,
4042299742Sdim                                             connection->baton,
4043299742Sdim                                             connection->conn,
4044299742Sdim                                             FALSE, iterpool);
4045299742Sdim
4046299742Sdim          break;
4047299742Sdim        }
4048299742Sdim      else
4049299742Sdim        {
4050299742Sdim          /* The server is not busy, thus let's serve whichever command
4051299742Sdim           * comes in next and whenever it comes in.  This requires the
4052299742Sdim           * busy() callback test to return TRUE while there are still some
4053299742Sdim           * resources left.
4054299742Sdim           */
4055299742Sdim          err = svn_ra_svn__handle_command(&terminate, cmd_hash,
4056299742Sdim                                           connection->baton,
4057299742Sdim                                           connection->conn,
4058299742Sdim                                           FALSE, iterpool);
4059299742Sdim        }
4060299742Sdim    }
4061299742Sdim
4062299742Sdim  /* error or normal end of session. Close the connection */
4063299742Sdim  svn_pool_destroy(iterpool);
4064299742Sdim  if (terminate_p)
4065299742Sdim    *terminate_p = terminate;
4066299742Sdim
4067299742Sdim  return svn_error_trace(err);
4068299742Sdim}
4069299742Sdim
4070299742Sdimsvn_error_t *serve(svn_ra_svn_conn_t *conn,
4071299742Sdim                   serve_params_t *params,
4072299742Sdim                   apr_pool_t *pool)
4073299742Sdim{
4074299742Sdim  server_baton_t *baton = NULL;
4075299742Sdim
4076299742Sdim  SVN_ERR(construct_server_baton(&baton, conn, params, pool));
4077299742Sdim  return svn_ra_svn__handle_commands2(conn, pool, main_commands, baton, FALSE);
4078299742Sdim}
4079