~svn/ubuntu/raring/subversion/ppa

« back to all changes in this revision

Viewing changes to subversion/svnserve/serve.c

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-12-05 01:26:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20051205012614-qom4xfypgtsqc2xq
Tags: 1.2.3dfsg1-3ubuntu1
Merge with the final Debian release of 1.2.3dfsg1-3, bringing in
fixes to the clean target, better documentation of the libdb4.3
upgrade and build fixes to work with swig1.3_1.3.27.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * serve.c :  Functions for serving the Subversion protocol
 
3
 *
 
4
 * ====================================================================
 
5
 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
 
6
 *
 
7
 * This software is licensed as described in the file COPYING, which
 
8
 * you should have received as part of this distribution.  The terms
 
9
 * are also available at http://subversion.tigris.org/license-1.html.
 
10
 * If newer versions of this license are posted there, you may use a
 
11
 * newer version instead, at your option.
 
12
 *
 
13
 * This software consists of voluntary contributions made by many
 
14
 * individuals.  For exact contribution history, see the revision
 
15
 * history and logs, available at http://subversion.tigris.org/.
 
16
 * ====================================================================
 
17
 */
 
18
 
 
19
 
 
20
 
 
21
#include <limits.h> /* for UINT_MAX */
 
22
 
 
23
#define APR_WANT_STRFUNC
 
24
#include <apr_want.h>
 
25
#include <apr_general.h>
 
26
#include <apr_strings.h>
 
27
#include <apr_user.h>
 
28
#include <apr_md5.h>
 
29
 
 
30
#include "svn_private_config.h"  /* For SVN_PATH_LOCAL_SEPARATOR */
 
31
#include <svn_types.h>
 
32
#include <svn_string.h>
 
33
#include <svn_pools.h>
 
34
#include <svn_error.h>
 
35
#include <svn_ra_svn.h>
 
36
#include <svn_repos.h>
 
37
#include <svn_path.h>
 
38
#include <svn_time.h>
 
39
#include <svn_md5.h>
 
40
#include <svn_config.h>
 
41
#include <svn_props.h>
 
42
 
 
43
#include "server.h"
 
44
 
 
45
typedef struct {
 
46
  svn_repos_t *repos;
 
47
  svn_fs_t *fs;            /* For convenience; same as svn_repos_fs(repos) */
 
48
  svn_config_t *cfg;       /* Parsed repository svnserve.conf */
 
49
  svn_config_t *pwdb;      /* Parsed password database */
 
50
  const char *realm;       /* Authentication realm */
 
51
  const char *repos_url;   /* URL to base of repository */
 
52
  const char *fs_path;     /* Decoded base path inside repository */
 
53
  const char *user;
 
54
  svn_boolean_t tunnel;    /* Tunneled through login agent */
 
55
  const char *tunnel_user; /* Allow EXTERNAL to authenticate as this */
 
56
  svn_boolean_t read_only; /* Disallow write access (global flag) */
 
57
  int protocol_version;
 
58
  apr_pool_t *pool;
 
59
} server_baton_t;
 
60
 
 
61
typedef struct {
 
62
  svn_revnum_t *new_rev;
 
63
  const char **date;
 
64
  const char **author;
 
65
} commit_callback_baton_t;
 
66
 
 
67
typedef struct {
 
68
  server_baton_t *sb;
 
69
  const char *repos_url;  /* Decoded repository URL. */
 
70
  void *report_baton;
 
71
  svn_error_t *err;
 
72
} report_driver_baton_t;
 
73
 
 
74
typedef struct {
 
75
  const char *fs_path;
 
76
  svn_ra_svn_conn_t *conn;
 
77
} log_baton_t;
 
78
 
 
79
typedef struct {
 
80
  svn_ra_svn_conn_t *conn;
 
81
  apr_pool_t *pool;  /* Pool provided in the handler call. */
 
82
} file_revs_baton_t;
 
83
 
 
84
enum authn_type { UNAUTHENTICATED, AUTHENTICATED };
 
85
enum access_type { NO_ACCESS, READ_ACCESS, WRITE_ACCESS };
 
86
 
 
87
/* Verify that URL is inside REPOS_URL and get its fs path. Assume that 
 
88
   REPOS_URL and URL are already URI-decoded. */
 
89
static svn_error_t *get_fs_path(const char *repos_url, const char *url,
 
90
                                const char **fs_path, apr_pool_t *pool)
 
91
{
 
92
  apr_size_t len;
 
93
 
 
94
  len = strlen(repos_url);
 
95
  if (strncmp(url, repos_url, len) != 0)
 
96
    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
 
97
                             "'%s' is not the same repository as '%s'",
 
98
                             url, repos_url);
 
99
  *fs_path = url + len;
 
100
  return SVN_NO_ERROR;
 
101
}
 
102
 
 
103
/* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
 
104
 
 
105
static enum access_type get_access(server_baton_t *b, enum authn_type auth)
 
106
{
 
107
  const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS :
 
108
    SVN_CONFIG_OPTION_ANON_ACCESS;
 
109
  const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read";
 
110
  enum access_type result;
 
111
 
 
112
  svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def);
 
113
  result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
 
114
            strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
 
115
  return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result;
 
116
}
 
117
 
 
118
static enum access_type current_access(server_baton_t *b)
 
119
{
 
120
  return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED);
 
121
}
 
122
 
 
123
/* Send authentication mechs for ACCESS_TYPE to the client.  If NEEDS_USERNAME
 
124
   is true, don't send anonymous mech even if that would give the desired
 
125
   access. */
 
126
static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
127
                               server_baton_t *b, enum access_type required,
 
128
                               svn_boolean_t needs_username)
 
129
{
 
130
  if (get_access(b, UNAUTHENTICATED) >= required)
 
131
    SVN_ERR(svn_ra_svn_write_word(conn, pool, "ANONYMOUS"));
 
132
  if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required)
 
133
    SVN_ERR(svn_ra_svn_write_word(conn, pool, "EXTERNAL"));
 
134
  if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
 
135
    SVN_ERR(svn_ra_svn_write_word(conn, pool, "CRAM-MD5"));
 
136
  return SVN_NO_ERROR;
 
137
}
 
138
 
 
139
/* Context for cleanup handler. */
 
140
struct cleanup_fs_access_baton
 
141
{
 
142
  svn_fs_t *fs;
 
143
  apr_pool_t *pool;
 
144
};
 
145
 
 
146
/* Pool cleanup handler.  Make sure fs's access_t points to NULL when
 
147
   the command pool is destroyed. */
 
148
static apr_status_t cleanup_fs_access(void *data)
 
149
{
 
150
  svn_error_t *serr;
 
151
  struct cleanup_fs_access_baton *baton = data;
 
152
 
 
153
  serr = svn_fs_set_access (baton->fs, NULL);
 
154
  if (serr)
 
155
    {
 
156
      apr_status_t apr_err = serr->apr_err;
 
157
      svn_error_clear(serr);
 
158
      return apr_err;
 
159
    }
 
160
 
 
161
  return APR_SUCCESS;
 
162
}
 
163
 
 
164
 
 
165
/* Create an svn_fs_access_t in POOL for USER and associate it with
 
166
   B's filesystem.  Also, register a cleanup handler with POOL which
 
167
   de-associates the svn_fs_access_t from B's filesystem. */
 
168
static svn_error_t *
 
169
create_fs_access(server_baton_t *b, apr_pool_t *pool)
 
170
{
 
171
  svn_fs_access_t *fs_access;
 
172
  struct cleanup_fs_access_baton *cleanup_baton;
 
173
 
 
174
  if (!b->user)
 
175
    return SVN_NO_ERROR;
 
176
 
 
177
  SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool));
 
178
  SVN_ERR(svn_fs_set_access(b->fs, fs_access));
 
179
 
 
180
  cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
 
181
  cleanup_baton->pool = pool;
 
182
  cleanup_baton->fs = b->fs;
 
183
  apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
 
184
                            apr_pool_cleanup_null);
 
185
 
 
186
  return SVN_NO_ERROR;
 
187
}
 
188
 
 
189
/* Authenticate, once the client has chosen a mechanism and possibly
 
190
 * sent an initial mechanism token.  On success, set *success to true
 
191
 * and b->user to the authenticated username (or NULL for anonymous).
 
192
 * On authentication failure, report failure to the client and set
 
193
 * *success to FALSE.  On communications failure, return an error.
 
194
 * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
 
195
static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
196
                         const char *mech, const char *mecharg,
 
197
                         server_baton_t *b, enum access_type required,
 
198
                         svn_boolean_t needs_username,
 
199
                         svn_boolean_t *success)
 
200
{
 
201
  const char *user;
 
202
  *success = FALSE;
 
203
 
 
204
  if (get_access(b, AUTHENTICATED) >= required
 
205
      && b->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
 
206
    {
 
207
      if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0)
 
208
        return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
 
209
                                      "Requested username does not match");
 
210
      b->user = b->tunnel_user;
 
211
      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
 
212
      *success = TRUE;
 
213
      return SVN_NO_ERROR;
 
214
    }
 
215
 
 
216
  if (get_access(b, UNAUTHENTICATED) >= required
 
217
      && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
 
218
    {
 
219
      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
 
220
      *success = TRUE;
 
221
      return SVN_NO_ERROR;
 
222
    }
 
223
 
 
224
  if (get_access(b, AUTHENTICATED) >= required
 
225
      && b->pwdb && strcmp(mech, "CRAM-MD5") == 0)
 
226
    {
 
227
      SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success));
 
228
      b->user = apr_pstrdup (b->pool, user);
 
229
      return SVN_NO_ERROR;
 
230
    }
 
231
 
 
232
  return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
 
233
                                "Must authenticate with listed mechanism");
 
234
}
 
235
 
 
236
/* Perform an authentication request in order to get an access level of
 
237
 * REQUIRED or higher.  Since the client may escape the authentication
 
238
 * exchange, the caller should check current_access(b) to see if
 
239
 * authentication succeeded. */
 
240
static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
241
                                 server_baton_t *b, enum access_type required,
 
242
                                 svn_boolean_t needs_username)
 
243
{
 
244
  svn_boolean_t success;
 
245
  const char *mech, *mecharg;
 
246
 
 
247
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
 
248
  SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
 
249
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)c)", b->realm));
 
250
  do
 
251
    {
 
252
      SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
 
253
      if (!*mech)
 
254
        break;
 
255
      SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username,
 
256
                   &success));
 
257
    }
 
258
  while (!success);
 
259
  return SVN_NO_ERROR;
 
260
}
 
261
 
 
262
/* Send a trivial auth request, listing no mechanisms. */
 
263
static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
 
264
                                         apr_pool_t *pool, server_baton_t *b)
 
265
{
 
266
  if (b->protocol_version < 2)
 
267
    return SVN_NO_ERROR;
 
268
  return svn_ra_svn_write_cmd_response(conn, pool, "()c", "");
 
269
}
 
270
 
 
271
/* Ensure that the client has write access.  If the client already has
 
272
   write access, just send a trivial auth request.  Else, try to authenticate
 
273
   the client.  If NEEDS_USERNAME is TRUE, only use auth mechs that will yield
 
274
   a username.  Return an error if write access couldn't be achieved. */
 
275
static svn_error_t *must_have_write_access(svn_ra_svn_conn_t *conn,
 
276
                                           apr_pool_t *pool, server_baton_t *b,
 
277
                                           svn_boolean_t needs_username)
 
278
{
 
279
  if (current_access(b) == WRITE_ACCESS
 
280
      && (! needs_username || b->user))
 
281
    {
 
282
      SVN_ERR(create_fs_access(b, pool));
 
283
      return trivial_auth_request(conn, pool, b);
 
284
    }
 
285
 
 
286
  /* If we can get write access by authenticating, try that. */
 
287
  if (b->user == NULL && get_access(b, AUTHENTICATED) == WRITE_ACCESS
 
288
      && (b->tunnel_user || b->pwdb) && b->protocol_version >= 2)
 
289
    SVN_ERR(auth_request(conn, pool, b, WRITE_ACCESS, needs_username));
 
290
 
 
291
  if (current_access(b) != WRITE_ACCESS)
 
292
    return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
 
293
                            svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
 
294
                                             "Connection is read-only"), NULL);
 
295
 
 
296
  SVN_ERR(create_fs_access(b, pool));
 
297
 
 
298
  return SVN_NO_ERROR;
 
299
}
 
300
 
 
301
/* --- REPORTER COMMAND SET --- */
 
302
 
 
303
/* To allow for pipelining, reporter commands have no reponses.  If we
 
304
 * get an error, we ignore all subsequent reporter commands and return
 
305
 * the error finish_report, to be handled by the calling command.
 
306
 */
 
307
 
 
308
static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
309
                             apr_array_header_t *params, void *baton)
 
310
{
 
311
  report_driver_baton_t *b = baton;
 
312
  const char *path, *lock_token;
 
313
  svn_revnum_t rev;
 
314
  svn_boolean_t start_empty;
 
315
 
 
316
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crb?(?c)",
 
317
                                 &path, &rev, &start_empty, &lock_token));
 
318
  path = svn_path_canonicalize(path, pool);
 
319
  if (!b->err)
 
320
    b->err = svn_repos_set_path2(b->report_baton, path, rev, start_empty,
 
321
                                 lock_token, pool);
 
322
  return SVN_NO_ERROR;
 
323
}
 
324
 
 
325
static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
326
                                apr_array_header_t *params, void *baton)
 
327
{
 
328
  report_driver_baton_t *b = baton;
 
329
  const char *path;
 
330
 
 
331
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
 
332
  path = svn_path_canonicalize(path, pool);
 
333
  if (!b->err)
 
334
    b->err = svn_repos_delete_path(b->report_baton, path, pool);
 
335
  return SVN_NO_ERROR;
 
336
}
 
337
 
 
338
static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
339
                              apr_array_header_t *params, void *baton)
 
340
{
 
341
  report_driver_baton_t *b = baton;
 
342
  const char *path, *url, *lock_token, *fs_path;
 
343
  svn_revnum_t rev;
 
344
  svn_boolean_t start_empty;
 
345
 
 
346
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccrb?(?c)",
 
347
                                 &path, &url, &rev, &start_empty,
 
348
                                 &lock_token));
 
349
  path = svn_path_canonicalize(path, pool);
 
350
  url = svn_path_uri_decode(svn_path_canonicalize(url, pool), pool);
 
351
  if (!b->err)
 
352
    b->err = get_fs_path(b->repos_url, url, &fs_path, pool);
 
353
  if (!b->err)
 
354
    b->err = svn_repos_link_path2(b->report_baton, path, fs_path, rev,
 
355
                                  start_empty, lock_token, pool);
 
356
  return SVN_NO_ERROR;
 
357
}
 
358
 
 
359
static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
360
                                  apr_array_header_t *params, void *baton)
 
361
{
 
362
  report_driver_baton_t *b = baton;
 
363
 
 
364
  /* No arguments to parse. */
 
365
  SVN_ERR(trivial_auth_request(conn, pool, b->sb));
 
366
  if (!b->err)
 
367
    b->err = svn_repos_finish_report(b->report_baton, pool);
 
368
  return SVN_NO_ERROR;
 
369
}
 
370
 
 
371
static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
372
                                   apr_array_header_t *params, void *baton)
 
373
{
 
374
  report_driver_baton_t *b = baton;
 
375
 
 
376
  /* No arguments to parse. */
 
377
  svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
 
378
  return SVN_NO_ERROR;
 
379
}
 
380
 
 
381
static const svn_ra_svn_cmd_entry_t report_commands[] = {
 
382
  { "set-path",      set_path },
 
383
  { "delete-path",   delete_path },
 
384
  { "link-path",     link_path },
 
385
  { "finish-report", finish_report, TRUE },
 
386
  { "abort-report",  abort_report,  TRUE },
 
387
  { NULL }
 
388
};
 
389
 
 
390
/* Accept a report from the client, drive the network editor with the
 
391
 * result, and then write an empty command response.  If there is a
 
392
 * non-protocol failure, accept_report will abort the edit and return
 
393
 * a command error to be reported by handle_commands(). */
 
394
static svn_error_t *accept_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
395
                                  server_baton_t *b, svn_revnum_t rev,
 
396
                                  const char *target, const char *tgt_path,
 
397
                                  svn_boolean_t text_deltas,
 
398
                                  svn_boolean_t recurse,
 
399
                                  svn_boolean_t ignore_ancestry)
 
400
{
 
401
  const svn_delta_editor_t *editor;
 
402
  void *edit_baton, *report_baton;
 
403
  report_driver_baton_t rb;
 
404
  svn_error_t *err;
 
405
 
 
406
  /* Make an svn_repos report baton.  Tell it to drive the network editor
 
407
   * when the report is complete. */
 
408
  svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
 
409
  SVN_CMD_ERR(svn_repos_begin_report(&report_baton, rev, b->user, b->repos,
 
410
                                     b->fs_path, target, tgt_path, text_deltas,
 
411
                                     recurse, ignore_ancestry, editor,
 
412
                                     edit_baton, NULL, NULL, pool));
 
413
 
 
414
  rb.sb = b;
 
415
  rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
 
416
  rb.report_baton = report_baton;
 
417
  rb.err = NULL;
 
418
  err = svn_ra_svn_handle_commands(conn, pool, report_commands, &rb);
 
419
  if (err)
 
420
    {
 
421
      /* Network or protocol error while handling commands. */
 
422
      svn_error_clear(rb.err);
 
423
      return err;
 
424
    }
 
425
  else if (rb.err)
 
426
    {
 
427
      /* Some failure during the reporting or editing operations. */
 
428
      svn_error_clear(editor->abort_edit(edit_baton, pool));
 
429
      SVN_CMD_ERR(rb.err);
 
430
    }
 
431
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
 
432
  return SVN_NO_ERROR;
 
433
}
 
434
 
 
435
/* --- MAIN COMMAND SET --- */
 
436
 
 
437
/* Write out a property list.  PROPS is allowed to be NULL, in which case
 
438
 * an empty list will be written out; this happens if the client could
 
439
 * have asked for props but didn't. */
 
440
static svn_error_t *write_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
441
                                   apr_hash_t *props)
 
442
{
 
443
  apr_hash_index_t *hi;
 
444
  const void *namevar;
 
445
  void *valuevar;
 
446
  const char *name;
 
447
  svn_string_t *value;
 
448
 
 
449
  if (props)
 
450
    {
 
451
      for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
 
452
        {
 
453
          apr_hash_this(hi, &namevar, NULL, &valuevar);
 
454
          name = namevar;
 
455
          value = valuevar;
 
456
          SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cs", name, value));
 
457
        }
 
458
    }
 
459
  return SVN_NO_ERROR;
 
460
}
 
461
 
 
462
/* Write out a list of property diffs.  PROPDIFFS is an array of svn_prop_t
 
463
 * values. */
 
464
static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
 
465
                                     apr_pool_t *pool,
 
466
                                     apr_array_header_t *propdiffs)
 
467
{
 
468
  int i;
 
469
 
 
470
  for (i = 0; i < propdiffs->nelts; ++i)
 
471
    {
 
472
      const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
 
473
 
 
474
      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "c(?s)",
 
475
                                     prop->name, prop->value));
 
476
    }
 
477
 
 
478
  return SVN_NO_ERROR;
 
479
}
 
480
 
 
481
/* Write out a lock to the client. */
 
482
static svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
 
483
                               apr_pool_t *pool,
 
484
                               svn_lock_t *lock)
 
485
{
 
486
  const char *cdate, *edate;
 
487
 
 
488
  cdate = svn_time_to_cstring(lock->creation_date, pool);
 
489
  edate = lock->expiration_date
 
490
    ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
 
491
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
 
492
                                 lock->token, lock->owner, lock->comment,
 
493
                                 cdate, edate));
 
494
 
 
495
  return SVN_NO_ERROR;
 
496
}
 
497
 
 
498
static const char *kind_word(svn_node_kind_t kind)
 
499
{
 
500
  switch (kind)
 
501
    {
 
502
    case svn_node_none:
 
503
      return "none";
 
504
    case svn_node_file:
 
505
      return "file";
 
506
    case svn_node_dir:
 
507
      return "dir";
 
508
    case svn_node_unknown:
 
509
      return "unknown";
 
510
    default:
 
511
      abort();
 
512
    }
 
513
}
 
514
 
 
515
/* ### This really belongs in libsvn_repos. */
 
516
/* Get the properties for a path, with hardcoded committed-info values. */
 
517
static svn_error_t *get_props(apr_hash_t **props, svn_fs_root_t *root,
 
518
                              const char *path, apr_pool_t *pool)
 
519
{
 
520
  svn_string_t *str;
 
521
  svn_revnum_t crev;
 
522
  const char *cdate, *cauthor, *uuid;
 
523
 
 
524
  /* Get the properties. */
 
525
  SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
 
526
 
 
527
  /* Hardcode the values for the committed revision, date, and author. */
 
528
  SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
 
529
                                       path, pool));
 
530
  str = svn_string_create(apr_psprintf(pool, "%ld", crev),
 
531
                          pool);
 
532
  apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING, str);
 
533
  str = (cdate) ? svn_string_create(cdate, pool) : NULL;
 
534
  apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING,
 
535
               str);
 
536
  str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
 
537
  apr_hash_set(*props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING, str);
 
538
 
 
539
  /* Hardcode the values for the UUID. */
 
540
  SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
 
541
  str = (uuid) ? svn_string_create(uuid, pool) : NULL;
 
542
  apr_hash_set(*props, SVN_PROP_ENTRY_UUID, APR_HASH_KEY_STRING, str);
 
543
 
 
544
  return SVN_NO_ERROR;
 
545
}
 
546
 
 
547
static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
548
                                   apr_array_header_t *params, void *baton)
 
549
{
 
550
  server_baton_t *b = baton;
 
551
  svn_revnum_t rev;
 
552
 
 
553
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
554
  SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
 
555
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev));
 
556
  return SVN_NO_ERROR;
 
557
}
 
558
 
 
559
static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
560
                                  apr_array_header_t *params, void *baton)
 
561
{
 
562
  server_baton_t *b = baton;
 
563
  svn_revnum_t rev;
 
564
  apr_time_t tm;
 
565
  const char *timestr;
 
566
 
 
567
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &timestr));
 
568
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
569
  SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
 
570
  SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool));
 
571
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev));
 
572
  return SVN_NO_ERROR;
 
573
}
 
574
 
 
575
static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
576
                                    apr_array_header_t *params, void *baton)
 
577
{
 
578
  server_baton_t *b = baton;
 
579
  svn_revnum_t rev;
 
580
  const char *name;
 
581
  svn_string_t *value;
 
582
 
 
583
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc?s", &rev, &name, &value));
 
584
  SVN_ERR(must_have_write_access(conn, pool, b, FALSE));
 
585
  SVN_CMD_ERR(svn_repos_fs_change_rev_prop2(b->repos, rev, b->user,
 
586
                                            name, value, NULL, NULL, pool));
 
587
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
 
588
  return SVN_NO_ERROR;
 
589
}
 
590
 
 
591
static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
592
                                 apr_array_header_t *params, void *baton)
 
593
{
 
594
  server_baton_t *b = baton;
 
595
  svn_revnum_t rev;
 
596
  apr_hash_t *props;
 
597
 
 
598
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev));
 
599
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
600
  SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
 
601
                                              NULL, NULL, pool));
 
602
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
 
603
  SVN_ERR(write_proplist(conn, pool, props));
 
604
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
 
605
  return SVN_NO_ERROR;
 
606
}
 
607
 
 
608
static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
609
                             apr_array_header_t *params, void *baton)
 
610
{
 
611
  server_baton_t *b = baton;
 
612
  svn_revnum_t rev;
 
613
  const char *name;
 
614
  svn_string_t *value;
 
615
 
 
616
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc", &rev, &name));
 
617
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
618
  SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name,
 
619
                                         NULL, NULL, pool));
 
620
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "(?s)", value));
 
621
  return SVN_NO_ERROR;
 
622
}
 
623
 
 
624
static svn_error_t *commit_done(svn_revnum_t new_rev, const char *date,
 
625
                                const char *author, void *baton)
 
626
{
 
627
  commit_callback_baton_t *ccb = baton;
 
628
 
 
629
  *ccb->new_rev = new_rev;
 
630
  *ccb->date = date;
 
631
  *ccb->author = author;
 
632
  return SVN_NO_ERROR;
 
633
}
 
634
 
 
635
/* Add the LOCK_TOKENS to the filesystem access context if any. LOCK_TOKENS is
 
636
   an array of svn_ra_svn_item_t structs.  Return an error if they are
 
637
   not a list of lists. */
 
638
static svn_error_t *add_lock_tokens(apr_array_header_t *lock_tokens,
 
639
                                    server_baton_t *sb,
 
640
                                    apr_pool_t *pool)
 
641
{
 
642
  int i;
 
643
  svn_fs_access_t *fs_access;
 
644
 
 
645
  SVN_ERR(svn_fs_get_access(&fs_access, sb->fs));
 
646
 
 
647
  /* If there is no access context, nowhere to add the tokens. */
 
648
  if (! fs_access)
 
649
    return SVN_NO_ERROR;
 
650
 
 
651
  for (i = 0; i < lock_tokens->nelts; ++i)
 
652
    {
 
653
      const char *token;
 
654
      svn_ra_svn_item_t *path_item, *token_item;
 
655
      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i,
 
656
                                               svn_ra_svn_item_t);
 
657
      if (item->kind != SVN_RA_SVN_LIST)
 
658
        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
 
659
                                "Lock tokens aren't a list of lists");
 
660
 
 
661
      path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
 
662
      if (path_item->kind != SVN_RA_SVN_STRING)
 
663
        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
 
664
                                "Lock path isn't a string.");
 
665
 
 
666
      token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
 
667
      if (token_item->kind != SVN_RA_SVN_STRING)
 
668
        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
 
669
                                "Lock token isn't a string");
 
670
 
 
671
      token = token_item->u.string->data;
 
672
      SVN_ERR(svn_fs_access_add_lock_token(fs_access, token));
 
673
    }
 
674
 
 
675
  return SVN_NO_ERROR;
 
676
}
 
677
 
 
678
/* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
 
679
   LOCK_TOKENS contais svn_ra_svn_item_t elements, assumed to be lists. */
 
680
static svn_error_t *unlock_paths(apr_array_header_t *lock_tokens,
 
681
                                 server_baton_t *sb,
 
682
                                 apr_pool_t *pool)
 
683
{
 
684
  int i;
 
685
  apr_pool_t *iterpool;
 
686
  
 
687
  iterpool = svn_pool_create(pool);
 
688
 
 
689
  for (i = 0; i < lock_tokens->nelts; ++i)
 
690
    {
 
691
      svn_ra_svn_item_t *item, *path_item, *token_item;
 
692
      const char *path, *token, *full_path;
 
693
      svn_pool_clear(iterpool);
 
694
 
 
695
      item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
 
696
      path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
 
697
      token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
 
698
 
 
699
      path = path_item->u.string->data;
 
700
      token = token_item->u.string->data;
 
701
 
 
702
      full_path = svn_path_join(sb->fs_path,
 
703
                                svn_path_canonicalize(path, iterpool),
 
704
                                iterpool);
 
705
 
 
706
      /* The lock may have become defunct after the commit, so ignore such
 
707
         errors.
 
708
 
 
709
         ### If we ever write a logging facility for svnserve, this
 
710
             would be a good place to log an error before clearing
 
711
             it. */
 
712
      svn_error_clear(svn_repos_fs_unlock(sb->repos, full_path, token,
 
713
                                          FALSE, pool));
 
714
    }
 
715
                                       
 
716
  svn_pool_destroy(iterpool);
 
717
 
 
718
  return SVN_NO_ERROR;
 
719
}
 
720
 
 
721
static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
722
                           apr_array_header_t *params, void *baton)
 
723
{
 
724
  server_baton_t *b = baton;
 
725
  const char *log_msg, *date, *author;
 
726
  apr_array_header_t *lock_tokens;
 
727
  svn_boolean_t keep_locks;
 
728
  const svn_delta_editor_t *editor;
 
729
  void *edit_baton;
 
730
  svn_boolean_t aborted;
 
731
  commit_callback_baton_t ccb;
 
732
  svn_revnum_t new_rev;
 
733
 
 
734
  if (params->nelts == 1)
 
735
    {
 
736
      /* Clients before 1.2 don't send lock-tokens and keep-locks fields. */
 
737
      SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &log_msg));
 
738
      lock_tokens = NULL;
 
739
      keep_locks = TRUE;
 
740
    }
 
741
  else
 
742
    SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "clb", &log_msg,
 
743
                                   &lock_tokens, &keep_locks));
 
744
  /* Require a username if the client gave us any lock tokens. */
 
745
  SVN_ERR(must_have_write_access(conn, pool, b,
 
746
                                 lock_tokens && lock_tokens->nelts > 0));
 
747
 
 
748
  /* Give the lock tokens to the FS if we got any. */
 
749
  if (lock_tokens)
 
750
    SVN_CMD_ERR(add_lock_tokens(lock_tokens, b, pool));
 
751
  ccb.new_rev = &new_rev;
 
752
  ccb.date = &date;
 
753
  ccb.author = &author;
 
754
  /* ### Note that svn_repos_get_commit_editor actually wants a decoded URL. */
 
755
  SVN_CMD_ERR(svn_repos_get_commit_editor2
 
756
              (&editor, &edit_baton, b->repos, NULL,
 
757
               svn_path_uri_decode(b->repos_url, pool),
 
758
               b->fs_path, b->user,
 
759
               log_msg, commit_done, &ccb, pool));
 
760
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
 
761
  SVN_ERR(svn_ra_svn_drive_editor(conn, pool, editor, edit_baton, &aborted));
 
762
  if (!aborted)
 
763
    {
 
764
      SVN_ERR(trivial_auth_request(conn, pool, b));
 
765
 
 
766
      /* In tunnel mode, deltify before answering the client, because
 
767
         answering may cause the client to terminate the connection
 
768
         and thus kill the server.  But otherwise, deltify after
 
769
         answering the client, to avoid user-visible delay. */
 
770
 
 
771
      if (b->tunnel)
 
772
        SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
 
773
 
 
774
      /* Unlock the paths. */
 
775
      if (! keep_locks && lock_tokens)
 
776
        SVN_ERR(unlock_paths(lock_tokens, b, pool));
 
777
 
 
778
      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(?c)(?c)",
 
779
                                     new_rev, date, author));
 
780
 
 
781
      if (! b->tunnel)
 
782
        SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
 
783
    }
 
784
  return SVN_NO_ERROR;
 
785
}
 
786
 
 
787
static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
788
                             apr_array_header_t *params, void *baton)
 
789
{
 
790
  server_baton_t *b = baton;
 
791
  const char *path, *full_path, *hex_digest;
 
792
  svn_revnum_t rev;
 
793
  svn_fs_root_t *root;
 
794
  svn_stream_t *contents;
 
795
  apr_hash_t *props = NULL;
 
796
  svn_string_t write_str;
 
797
  char buf[4096];
 
798
  apr_size_t len;
 
799
  svn_boolean_t want_props, want_contents;
 
800
  unsigned char digest[APR_MD5_DIGESTSIZE];
 
801
  svn_error_t *err, *write_err;
 
802
 
 
803
  /* Parse arguments. */
 
804
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev,
 
805
                                 &want_props, &want_contents));
 
806
  path = svn_path_canonicalize(path, pool);
 
807
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
808
  if (!SVN_IS_VALID_REVNUM(rev))
 
809
    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
 
810
  full_path = svn_path_join(b->fs_path, path, pool);
 
811
 
 
812
  /* Fetch the properties and a stream for the contents. */
 
813
  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
 
814
  SVN_CMD_ERR(svn_fs_file_md5_checksum(digest, root, full_path, pool));
 
815
  hex_digest = svn_md5_digest_to_cstring_display(digest, pool);
 
816
  if (want_props)
 
817
    SVN_CMD_ERR(get_props(&props, root, full_path, pool));
 
818
  if (want_contents)
 
819
    SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
 
820
 
 
821
  /* Send successful command response with revision and props. */
 
822
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)r(!", "success",
 
823
                                 hex_digest, rev));
 
824
  SVN_ERR(write_proplist(conn, pool, props));
 
825
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
 
826
 
 
827
  /* Now send the file's contents. */
 
828
  if (want_contents)
 
829
    {
 
830
      err = SVN_NO_ERROR;
 
831
      while (1)
 
832
        {
 
833
          len = sizeof(buf);
 
834
          err = svn_stream_read(contents, buf, &len);
 
835
          if (err)
 
836
            break;
 
837
          if (len > 0)
 
838
            {
 
839
              write_str.data = buf;
 
840
              write_str.len = len;
 
841
              SVN_ERR(svn_ra_svn_write_string(conn, pool, &write_str));
 
842
            }
 
843
          if (len < sizeof(buf))
 
844
            {
 
845
              err = svn_stream_close(contents);
 
846
              break;
 
847
            }
 
848
        }
 
849
      write_err = svn_ra_svn_write_cstring(conn, pool, "");
 
850
      if (write_err)
 
851
        {
 
852
          svn_error_clear(err);
 
853
          return write_err;
 
854
        }
 
855
      SVN_CMD_ERR(err);
 
856
      SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
 
857
    }
 
858
 
 
859
  return SVN_NO_ERROR;
 
860
}
 
861
 
 
862
static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
863
                            apr_array_header_t *params, void *baton)
 
864
{
 
865
  server_baton_t *b = baton;
 
866
  const char *path, *full_path, *file_path, *name, *cauthor, *cdate;
 
867
  svn_revnum_t rev;
 
868
  apr_hash_t *entries, *props = NULL, *file_props;
 
869
  apr_hash_index_t *hi;
 
870
  svn_fs_dirent_t *fsent;
 
871
  svn_dirent_t *entry;
 
872
  const void *key;
 
873
  void *val;
 
874
  svn_fs_root_t *root;
 
875
  apr_pool_t *subpool;
 
876
  svn_boolean_t want_props, want_contents;
 
877
 
 
878
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev,
 
879
                                 &want_props, &want_contents));
 
880
  path = svn_path_canonicalize(path, pool);
 
881
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
882
  if (!SVN_IS_VALID_REVNUM(rev))
 
883
    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
 
884
  full_path = svn_path_join(b->fs_path, path, pool);
 
885
 
 
886
  /* Fetch the root of the appropriate revision. */
 
887
  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
 
888
 
 
889
  /* Fetch the directory properties if requested. */
 
890
  if (want_props)
 
891
    SVN_CMD_ERR(get_props(&props, root, full_path, pool));
 
892
 
 
893
  /* Fetch the directory entries if requested. */
 
894
  if (want_contents)
 
895
    {
 
896
      SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
 
897
 
 
898
      /* Transform the hash table's FS entries into dirents.  This probably
 
899
       * belongs in libsvn_repos. */
 
900
      subpool = svn_pool_create(pool);
 
901
      for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
 
902
        {
 
903
          apr_hash_this(hi, &key, NULL, &val);
 
904
          name = key;
 
905
          fsent = val;
 
906
 
 
907
          svn_pool_clear(subpool);
 
908
 
 
909
          file_path = svn_path_join(full_path, name, subpool);
 
910
          entry = apr_pcalloc(pool, sizeof(*entry));
 
911
 
 
912
          /* kind */
 
913
          entry->kind = fsent->kind;
 
914
 
 
915
          /* size */
 
916
          if (entry->kind == svn_node_dir)
 
917
            entry->size = 0;
 
918
          else
 
919
            SVN_CMD_ERR(svn_fs_file_length(&entry->size, root, file_path,
 
920
                                           subpool));
 
921
 
 
922
          /* has_props */
 
923
          SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
 
924
                                           subpool));
 
925
          entry->has_props = (apr_hash_count(file_props) > 0) ? TRUE : FALSE;
 
926
 
 
927
          /* created_rev, last_author, time */
 
928
          SVN_CMD_ERR(svn_repos_get_committed_info(&entry->created_rev, &cdate,
 
929
                                                   &cauthor, root, file_path,
 
930
                                                   subpool));
 
931
          entry->last_author = apr_pstrdup(pool, cauthor);
 
932
          if (cdate)
 
933
            SVN_CMD_ERR(svn_time_from_cstring(&entry->time, cdate, subpool));
 
934
          else
 
935
            entry->time = (time_t) -1;
 
936
 
 
937
          /* Store the entry. */
 
938
          apr_hash_set(entries, name, APR_HASH_KEY_STRING, entry);
 
939
        }
 
940
      svn_pool_destroy(subpool);
 
941
    }
 
942
 
 
943
  /* Write out response. */
 
944
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(r(!", "success", rev));
 
945
  SVN_ERR(write_proplist(conn, pool, props));
 
946
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(!"));
 
947
  if (want_contents)
 
948
    {
 
949
      for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
 
950
        {
 
951
          apr_hash_this(hi, &key, NULL, &val);
 
952
          name = key;
 
953
          entry = val;
 
954
          cdate = (entry->time == (time_t) -1) ? NULL
 
955
            : svn_time_to_cstring(entry->time, pool);
 
956
          SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
 
957
                                         kind_word(entry->kind),
 
958
                                         (apr_uint64_t) entry->size,
 
959
                                         entry->has_props, entry->created_rev,
 
960
                                         cdate, entry->last_author));
 
961
        }
 
962
    }
 
963
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
 
964
  return SVN_NO_ERROR;
 
965
}
 
966
 
 
967
 
 
968
static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
969
                           apr_array_header_t *params, void *baton)
 
970
{
 
971
  server_baton_t *b = baton;
 
972
  svn_revnum_t rev;
 
973
  const char *target;
 
974
  svn_boolean_t recurse;
 
975
 
 
976
  /* Parse the arguments. */
 
977
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cb", &rev, &target,
 
978
                                 &recurse));
 
979
  target = svn_path_canonicalize(target, pool);
 
980
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
981
  if (!SVN_IS_VALID_REVNUM(rev))
 
982
    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
 
983
 
 
984
  return accept_report(conn, pool, b, rev, target, NULL, TRUE, recurse, FALSE);
 
985
}
 
986
 
 
987
static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
988
                               apr_array_header_t *params, void *baton)
 
989
{
 
990
  server_baton_t *b = baton;
 
991
  svn_revnum_t rev;
 
992
  const char *target;
 
993
  const char *switch_url, *switch_path;
 
994
  svn_boolean_t recurse;
 
995
 
 
996
  /* Parse the arguments. */
 
997
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbc", &rev, &target,
 
998
                                 &recurse, &switch_url));
 
999
  target = svn_path_canonicalize(target, pool);
 
1000
  switch_url = svn_path_canonicalize(switch_url, pool);
 
1001
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
1002
  if (!SVN_IS_VALID_REVNUM(rev))
 
1003
    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
 
1004
  SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
 
1005
                          svn_path_uri_decode(switch_url, pool),
 
1006
                          &switch_path, pool));
 
1007
 
 
1008
  return accept_report(conn, pool, b, rev, target, switch_path, TRUE, recurse,
 
1009
                       TRUE);
 
1010
}
 
1011
 
 
1012
static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1013
                           apr_array_header_t *params, void *baton)
 
1014
{
 
1015
  server_baton_t *b = baton;
 
1016
  svn_revnum_t rev;
 
1017
  const char *target;
 
1018
  svn_boolean_t recurse;
 
1019
 
 
1020
  /* Parse the arguments. */
 
1021
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cb?(?r)",
 
1022
                                 &target, &recurse, &rev));
 
1023
  target = svn_path_canonicalize(target, pool);
 
1024
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
1025
  if (!SVN_IS_VALID_REVNUM(rev))
 
1026
    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
 
1027
  return accept_report(conn, pool, b, rev, target, NULL, FALSE, recurse,
 
1028
                       FALSE);
 
1029
}
 
1030
 
 
1031
static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1032
                         apr_array_header_t *params, void *baton)
 
1033
{
 
1034
  server_baton_t *b = baton;
 
1035
  svn_revnum_t rev;
 
1036
  const char *target, *versus_url, *versus_path;
 
1037
  svn_boolean_t recurse, ignore_ancestry;
 
1038
 
 
1039
  /* Parse the arguments. */
 
1040
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
 
1041
                                 &recurse, &ignore_ancestry, &versus_url));
 
1042
  target = svn_path_canonicalize(target, pool);
 
1043
  versus_url = svn_path_canonicalize(versus_url, pool);
 
1044
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
1045
  if (!SVN_IS_VALID_REVNUM(rev))
 
1046
    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
 
1047
  SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
 
1048
                          svn_path_uri_decode(versus_url, pool),
 
1049
                          &versus_path, pool));
 
1050
 
 
1051
  return accept_report(conn, pool, b, rev, target, versus_path, TRUE, recurse,
 
1052
                       ignore_ancestry);
 
1053
}
 
1054
 
 
1055
/* Send a log entry to the client. */
 
1056
static svn_error_t *log_receiver(void *baton, apr_hash_t *changed_paths,
 
1057
                                 svn_revnum_t rev, const char *author,
 
1058
                                 const char *date, const char *message,
 
1059
                                 apr_pool_t *pool)
 
1060
{
 
1061
  log_baton_t *b = baton;
 
1062
  svn_ra_svn_conn_t *conn = b->conn;
 
1063
  apr_hash_index_t *h;
 
1064
  const void *key;
 
1065
  void *val;
 
1066
  const char *path;
 
1067
  svn_log_changed_path_t *change;
 
1068
  char action[2];
 
1069
 
 
1070
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "(!"));
 
1071
  if (changed_paths)
 
1072
    {
 
1073
      for (h = apr_hash_first(pool, changed_paths); h; h = apr_hash_next(h))
 
1074
        {
 
1075
          apr_hash_this(h, &key, NULL, &val);
 
1076
          path = key;
 
1077
          change = val;
 
1078
          action[0] = change->action;
 
1079
          action[1] = '\0';
 
1080
          SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cw(?cr)", path, action,
 
1081
                                         change->copyfrom_path,
 
1082
                                         change->copyfrom_rev));
 
1083
        }
 
1084
    }
 
1085
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)", rev, author,
 
1086
                                 date, message));
 
1087
  return SVN_NO_ERROR;
 
1088
}
 
1089
 
 
1090
static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1091
                            apr_array_header_t *params, void *baton)
 
1092
{
 
1093
  svn_error_t *err, *write_err;
 
1094
  server_baton_t *b = baton;
 
1095
  svn_revnum_t start_rev, end_rev;
 
1096
  const char *full_path;
 
1097
  svn_boolean_t changed_paths, strict_node;
 
1098
  apr_array_header_t *paths, *full_paths;
 
1099
  svn_ra_svn_item_t *elt;
 
1100
  int i;
 
1101
  apr_uint64_t limit;
 
1102
  log_baton_t lb;
 
1103
 
 
1104
  /* Parse the arguments. */
 
1105
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n", &paths,
 
1106
                                 &start_rev, &end_rev, &changed_paths,
 
1107
                                 &strict_node, &limit));
 
1108
 
 
1109
  /* If we got an unspecified number then the user didn't send us anything,
 
1110
     so we assume no limit.  If it's larger than INT_MAX then someone is 
 
1111
     messing with us, since we know the svn client libraries will never send
 
1112
     us anything that big, so play it safe and default to no limit. */
 
1113
  if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
 
1114
    limit = 0;
 
1115
 
 
1116
  full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
 
1117
  for (i = 0; i < paths->nelts; i++)
 
1118
    {
 
1119
      elt = &((svn_ra_svn_item_t *) paths->elts)[i];
 
1120
      if (elt->kind != SVN_RA_SVN_STRING)
 
1121
        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
 
1122
                                "Log path entry not a string");
 
1123
      full_path = svn_path_join(b->fs_path,
 
1124
                                svn_path_canonicalize(elt->u.string->data,
 
1125
                                                      pool),
 
1126
                                pool);
 
1127
      *((const char **) apr_array_push(full_paths)) = full_path;
 
1128
    }
 
1129
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
1130
 
 
1131
  /* Get logs.  (Can't report errors back to the client at this point.) */
 
1132
  lb.fs_path = b->fs_path;
 
1133
  lb.conn = conn;
 
1134
  err = svn_repos_get_logs3(b->repos, full_paths, start_rev, end_rev,
 
1135
                            (int) limit, changed_paths, strict_node,
 
1136
                            NULL, NULL, log_receiver, &lb, pool);
 
1137
 
 
1138
  write_err = svn_ra_svn_write_word(conn, pool, "done");
 
1139
  if (write_err)
 
1140
    {
 
1141
      svn_error_clear(err);
 
1142
      return write_err;
 
1143
    }
 
1144
  SVN_CMD_ERR(err);
 
1145
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
 
1146
  return SVN_NO_ERROR;
 
1147
}
 
1148
 
 
1149
static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1150
                               apr_array_header_t *params, void *baton)
 
1151
{
 
1152
  server_baton_t *b = baton;
 
1153
  svn_revnum_t rev;
 
1154
  const char *path, *full_path;
 
1155
  svn_fs_root_t *root;
 
1156
  svn_node_kind_t kind;
 
1157
 
 
1158
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev));
 
1159
  path = svn_path_canonicalize(path, pool);
 
1160
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
1161
  if (!SVN_IS_VALID_REVNUM(rev))
 
1162
    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
 
1163
  full_path = svn_path_join(b->fs_path, path, pool);
 
1164
  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
 
1165
  SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
 
1166
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "w", kind_word(kind)));
 
1167
  return SVN_NO_ERROR;
 
1168
}
 
1169
 
 
1170
static svn_error_t *stat(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1171
                         apr_array_header_t *params, void *baton)
 
1172
{
 
1173
  server_baton_t *b = baton;
 
1174
  svn_revnum_t rev;
 
1175
  const char *path, *full_path, *cdate;
 
1176
  svn_fs_root_t *root;
 
1177
  svn_dirent_t *dirent;
 
1178
 
 
1179
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev));
 
1180
  path = svn_path_canonicalize(path, pool);
 
1181
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
1182
  if (!SVN_IS_VALID_REVNUM(rev))
 
1183
    SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
 
1184
  full_path = svn_path_join(b->fs_path, path, pool);
 
1185
  SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
 
1186
  SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
 
1187
 
 
1188
  /* Need to return the equivalent of "(?l)", since that's what the
 
1189
     client is reading.  */
 
1190
 
 
1191
  if (dirent == NULL)
 
1192
    {
 
1193
      SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "()"));
 
1194
      return SVN_NO_ERROR;
 
1195
    }
 
1196
 
 
1197
  cdate = (dirent->time == (time_t) -1) ? NULL
 
1198
    : svn_time_to_cstring(dirent->time, pool);
 
1199
 
 
1200
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
 
1201
                                        kind_word(dirent->kind),
 
1202
                                        (apr_uint64_t) dirent->size,
 
1203
                                        dirent->has_props, dirent->created_rev,
 
1204
                                        cdate, dirent->last_author));
 
1205
 
 
1206
  return SVN_NO_ERROR;
 
1207
}
 
1208
 
 
1209
static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1210
                                  apr_array_header_t *params, void *baton)
 
1211
{
 
1212
  svn_error_t *err, *write_err;
 
1213
  server_baton_t *b = baton;
 
1214
  svn_revnum_t revision;
 
1215
  apr_array_header_t *location_revisions, *loc_revs_proto;
 
1216
  svn_ra_svn_item_t *elt;
 
1217
  int i;
 
1218
  const char *relative_path;
 
1219
  svn_revnum_t peg_revision;
 
1220
  apr_hash_t *fs_locations;
 
1221
  apr_hash_index_t *iter;
 
1222
  const char *abs_path;
 
1223
  const void *iter_key;
 
1224
  void *iter_value;
 
1225
 
 
1226
  /* Parse the arguments. */
 
1227
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crl", &relative_path,
 
1228
                                 &peg_revision,
 
1229
                                 &loc_revs_proto));
 
1230
  relative_path = svn_path_canonicalize(relative_path, pool);
 
1231
 
 
1232
  abs_path = svn_path_join(b->fs_path, relative_path, pool);
 
1233
 
 
1234
  location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
 
1235
                                      sizeof(svn_revnum_t));
 
1236
  for (i = 0; i < loc_revs_proto->nelts; i++)
 
1237
    {
 
1238
      elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
 
1239
      if (elt->kind != SVN_RA_SVN_NUMBER)
 
1240
        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
 
1241
                                "Get-locations location revisions entry "
 
1242
                                "not a revision number");
 
1243
      revision = (svn_revnum_t)(elt->u.number);
 
1244
      APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
 
1245
    }
 
1246
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
1247
 
 
1248
  /* All the parameters are fine - let's perform the query against the
 
1249
   * repository. */
 
1250
 
 
1251
  /* We store both err and write_err here, so the client will get
 
1252
   * the "done" even if there was an error in fetching the results. */
 
1253
 
 
1254
  err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
 
1255
                                       peg_revision, location_revisions,
 
1256
                                       NULL, NULL, pool);
 
1257
 
 
1258
  /* Now, write the results to the connection. */
 
1259
  if (!err)
 
1260
    {
 
1261
      if (fs_locations)
 
1262
        {
 
1263
          for (iter = apr_hash_first(pool, fs_locations); iter;
 
1264
              iter = apr_hash_next(iter))
 
1265
            {
 
1266
              apr_hash_this(iter, &iter_key, NULL, &iter_value);
 
1267
              SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "rc",
 
1268
                                             *(const svn_revnum_t *)iter_key,
 
1269
                                             (const char *)iter_value));
 
1270
            }
 
1271
        }
 
1272
    }
 
1273
 
 
1274
  write_err = svn_ra_svn_write_word(conn, pool, "done");
 
1275
  if (write_err)
 
1276
    {
 
1277
      svn_error_clear(err);
 
1278
      return write_err;
 
1279
    }
 
1280
  SVN_CMD_ERR(err);
 
1281
 
 
1282
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
 
1283
 
 
1284
  return SVN_NO_ERROR;
 
1285
}
 
1286
 
 
1287
/* This implements svn_write_fn_t.  Write LEN bytes starting at DATA to the
 
1288
   client as a string. */
 
1289
static svn_error_t *svndiff_handler(void *baton, const char *data,
 
1290
                                    apr_size_t *len)
 
1291
{
 
1292
  file_revs_baton_t *b = baton;
 
1293
  svn_string_t str;
 
1294
 
 
1295
  str.data = data;
 
1296
  str.len = *len;
 
1297
  return svn_ra_svn_write_string(b->conn, b->pool, &str);
 
1298
}
 
1299
 
 
1300
/* This implements svn_close_fn_t.  Mark the end of the data by writing an
 
1301
   empty string to the client. */
 
1302
static svn_error_t *svndiff_close_handler(void *baton)
 
1303
{
 
1304
  file_revs_baton_t *b = baton;
 
1305
 
 
1306
  SVN_ERR(svn_ra_svn_write_cstring(b->conn, b->pool, ""));
 
1307
  return SVN_NO_ERROR;
 
1308
}
 
1309
 
 
1310
/* This implements the svn_repos_file_rev_handler_t interface. */
 
1311
static svn_error_t *file_rev_handler(void *baton, const char *path,
 
1312
                                     svn_revnum_t rev, apr_hash_t *rev_props,
 
1313
                                     svn_txdelta_window_handler_t *d_handler,
 
1314
                                     void **d_baton,
 
1315
                                     apr_array_header_t *prop_diffs,
 
1316
                                     apr_pool_t *pool)
 
1317
{
 
1318
  file_revs_baton_t *frb = baton;
 
1319
  svn_stream_t *stream;
 
1320
 
 
1321
  SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "cr(!",
 
1322
                                 path, rev));
 
1323
  SVN_ERR(write_proplist(frb->conn, pool, rev_props));
 
1324
  SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)(!"));
 
1325
  SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
 
1326
  SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)"));
 
1327
 
 
1328
  /* Store the pool for the delta stream. */
 
1329
  frb->pool = pool;
 
1330
 
 
1331
  /* Prepare for the delta or just write an empty string. */
 
1332
  if (d_handler)
 
1333
    {
 
1334
      stream = svn_stream_create(baton, pool);
 
1335
      svn_stream_set_write(stream, svndiff_handler);
 
1336
      svn_stream_set_close(stream, svndiff_close_handler);
 
1337
 
 
1338
      svn_txdelta_to_svndiff(stream, pool, d_handler, d_baton);
 
1339
    }
 
1340
  else
 
1341
    SVN_ERR(svn_ra_svn_write_cstring(frb->conn, pool, ""));
 
1342
      
 
1343
  return SVN_NO_ERROR;
 
1344
}
 
1345
 
 
1346
static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1347
                                  apr_array_header_t *params, void *baton)
 
1348
{
 
1349
  server_baton_t *b = baton;
 
1350
  svn_error_t *err, *write_err;
 
1351
  file_revs_baton_t frb;
 
1352
  svn_revnum_t start_rev, end_rev;
 
1353
  const char *path;
 
1354
  const char *full_path;
 
1355
  
 
1356
  /* Parse arguments. */
 
1357
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)",
 
1358
                                 &path, &start_rev, &end_rev));
 
1359
  path = svn_path_canonicalize(path, pool);
 
1360
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
1361
  full_path = svn_path_join(b->fs_path, path, pool);
 
1362
 
 
1363
  frb.conn = conn;
 
1364
  frb.pool = NULL;
 
1365
 
 
1366
  err = svn_repos_get_file_revs(b->repos, full_path, start_rev, end_rev, NULL,
 
1367
                                NULL, file_rev_handler, &frb, pool);
 
1368
  write_err = svn_ra_svn_write_word(conn, pool, "done");
 
1369
  if (write_err)
 
1370
    {
 
1371
      svn_error_clear(err);
 
1372
      return write_err;
 
1373
    }
 
1374
  SVN_CMD_ERR(err);
 
1375
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
 
1376
 
 
1377
  return SVN_NO_ERROR;
 
1378
}
 
1379
 
 
1380
static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1381
                         apr_array_header_t *params, void *baton)
 
1382
{
 
1383
  server_baton_t *b = baton;
 
1384
  const char *path;
 
1385
  const char *comment;
 
1386
  const char *full_path;
 
1387
  svn_boolean_t force;
 
1388
  svn_revnum_t current_rev;
 
1389
  svn_lock_t *l;
 
1390
 
 
1391
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
 
1392
                                 &force, &current_rev));
 
1393
 
 
1394
  full_path = svn_path_join(b->fs_path, svn_path_canonicalize(path, pool),
 
1395
                            pool);
 
1396
 
 
1397
  SVN_ERR(must_have_write_access(conn, pool, b, TRUE));
 
1398
 
 
1399
  SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
 
1400
                                0, /* No expiration time. */
 
1401
                                current_rev,  force, pool));
 
1402
 
 
1403
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(!", "success"));
 
1404
  SVN_ERR(write_lock(conn, pool, l));
 
1405
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
 
1406
 
 
1407
  return SVN_NO_ERROR;
 
1408
}
 
1409
 
 
1410
static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1411
                           apr_array_header_t *params, void *baton)
 
1412
{
 
1413
  server_baton_t *b = baton;
 
1414
  const char *path, *token, *full_path;
 
1415
  svn_boolean_t force;
 
1416
 
 
1417
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b", &path, &token,
 
1418
                                 &force));
 
1419
 
 
1420
  full_path = svn_path_join(b->fs_path, svn_path_canonicalize(path, pool),
 
1421
                            pool);
 
1422
 
 
1423
  /* Username required unless force was specified. */
 
1424
  SVN_ERR(must_have_write_access(conn, pool, b, ! force));
 
1425
 
 
1426
  SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, force, pool));
 
1427
 
 
1428
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
 
1429
 
 
1430
  return SVN_NO_ERROR;
 
1431
}
 
1432
 
 
1433
static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1434
                             apr_array_header_t *params, void *baton)
 
1435
{
 
1436
  server_baton_t *b = baton;
 
1437
  const char *path;
 
1438
  const char *full_path;
 
1439
  svn_lock_t *l;
 
1440
 
 
1441
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
 
1442
 
 
1443
  full_path = svn_path_join(b->fs_path, svn_path_canonicalize(path, pool),
 
1444
                            pool);
 
1445
 
 
1446
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
1447
 
 
1448
  SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
 
1449
 
 
1450
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
 
1451
  if (l)
 
1452
    SVN_ERR(write_lock(conn, pool, l));
 
1453
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
 
1454
 
 
1455
  return SVN_NO_ERROR;
 
1456
}
 
1457
 
 
1458
static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
 
1459
                              apr_array_header_t *params, void *baton)
 
1460
{
 
1461
  server_baton_t *b = baton;
 
1462
  const char *path;
 
1463
  const char *full_path;
 
1464
  apr_hash_t *locks;
 
1465
  apr_hash_index_t *hi;
 
1466
  const void *key;
 
1467
  void *val;
 
1468
  svn_lock_t *l;
 
1469
 
 
1470
  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
 
1471
 
 
1472
  full_path = svn_path_join(b->fs_path, svn_path_canonicalize(path, pool),
 
1473
                            pool);
 
1474
 
 
1475
  SVN_ERR(trivial_auth_request(conn, pool, b));
 
1476
  
 
1477
  SVN_CMD_ERR(svn_repos_fs_get_locks(&locks, b->repos, full_path, 
 
1478
                                     NULL, NULL, pool));
 
1479
 
 
1480
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
 
1481
  for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
 
1482
    {
 
1483
      apr_hash_this(hi, &key, NULL, &val);
 
1484
      l = val;
 
1485
      SVN_ERR(write_lock(conn, pool, l));
 
1486
    }
 
1487
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
 
1488
 
 
1489
  return SVN_NO_ERROR;
 
1490
}
 
1491
 
 
1492
 
 
1493
static const svn_ra_svn_cmd_entry_t main_commands[] = {
 
1494
  { "get-latest-rev",  get_latest_rev },
 
1495
  { "get-dated-rev",   get_dated_rev },
 
1496
  { "change-rev-prop", change_rev_prop },
 
1497
  { "rev-proplist",    rev_proplist },
 
1498
  { "rev-prop",        rev_prop },
 
1499
  { "commit",          commit },
 
1500
  { "get-file",        get_file },
 
1501
  { "get-dir",         get_dir },
 
1502
  { "update",          update },
 
1503
  { "switch",          switch_cmd },
 
1504
  { "status",          status },
 
1505
  { "diff",            diff },
 
1506
  { "log",             log_cmd },
 
1507
  { "check-path",      check_path },
 
1508
  { "stat",            stat },
 
1509
  { "get-locations",   get_locations },
 
1510
  { "get-file-revs",   get_file_revs },
 
1511
  { "lock",            lock },
 
1512
  { "unlock",          unlock },
 
1513
  { "get-lock",        get_lock },
 
1514
  { "get-locks",       get_locks },
 
1515
  { NULL }
 
1516
};
 
1517
 
 
1518
/* Skip past the scheme part of a URL, including the tunnel specification
 
1519
 * if present.  Return NULL if the scheme part is invalid for ra_svn. */
 
1520
static const char *skip_scheme_part(const char *url)
 
1521
{
 
1522
  if (strncmp(url, "svn", 3) != 0)
 
1523
    return NULL;
 
1524
  url += 3;
 
1525
  if (*url == '+')
 
1526
    url += strcspn(url, ":");
 
1527
  if (strncmp(url, "://", 3) != 0)
 
1528
    return NULL;
 
1529
  return url + 3;
 
1530
}
 
1531
 
 
1532
/* Check that PATH is a valid repository path, meaning it doesn't contain any
 
1533
   '..' path segments.
 
1534
   NOTE: This is similar to svn_path_is_backpath_present, but that function
 
1535
   assumes the path separator is '/'.  This function also checks for
 
1536
   segments delimited by the local path separator. */
 
1537
static svn_boolean_t
 
1538
repos_path_valid(const char *path)
 
1539
{
 
1540
  const char *s = path;
 
1541
 
 
1542
  while (*s)
 
1543
    {
 
1544
      /* Scan for the end of the segment. */
 
1545
      while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
 
1546
        ++path;
 
1547
 
 
1548
      /* Check for '..'. */
 
1549
#ifdef WIN32
 
1550
      /* On Windows, don't allow sequences of more than one character
 
1551
         consisting of just dots and spaces.  Win32 functions treat
 
1552
         paths such as ".. " and "......." inconsistently.  Make sure
 
1553
         no one can escape out of the root. */
 
1554
      if (path - s >= 2 && strspn(s, ". ") == path - s)
 
1555
        return FALSE;
 
1556
#else  /* ! WIN32 */
 
1557
      if (path - s == 2 && s[0] == '.' && s[1] == '.')
 
1558
        return FALSE;
 
1559
#endif
 
1560
 
 
1561
      /* Skip all separators. */
 
1562
      while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
 
1563
        ++path;
 
1564
      s = path;
 
1565
    }
 
1566
 
 
1567
  return TRUE;
 
1568
}
 
1569
      
 
1570
/* Look for the repository given by URL, using ROOT as the virtual
 
1571
 * repository root.  If we find one, fill in the repos, fs, cfg,
 
1572
 * repos_url, and fs_path fields of B. */
 
1573
static svn_error_t *find_repos(const char *url, const char *root,
 
1574
                               server_baton_t *b, apr_pool_t *pool)
 
1575
{
 
1576
  const char *path, *full_path, *repos_root, *pwdb_path;
 
1577
  svn_stringbuf_t *url_buf;
 
1578
 
 
1579
  /* Skip past the scheme and authority part. */
 
1580
  path = skip_scheme_part(url);
 
1581
  if (path == NULL)
 
1582
    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
 
1583
                             "Non-svn URL passed to svn server: '%s'", url);
 
1584
  path = strchr(path, '/');
 
1585
  path = (path == NULL) ? "" : path + 1;
 
1586
 
 
1587
  /* Decode URI escapes from the path. */
 
1588
  path = svn_path_uri_decode(path, pool);
 
1589
 
 
1590
  /* Ensure that it isn't possible to escape the root by skipping leading
 
1591
     slashes and not allowing '..' segments. */
 
1592
  while (*path == '/')
 
1593
    ++path;
 
1594
  if (!repos_path_valid(path))
 
1595
    return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
 
1596
                            "Couldn't determine repository path");
 
1597
 
 
1598
  /* Join the server-configured root with the client path. */
 
1599
  full_path = svn_path_join(svn_path_canonicalize(root, pool),
 
1600
                            svn_path_canonicalize(path, pool), pool);
 
1601
 
 
1602
  /* Search for a repository in the full path. */
 
1603
  repos_root = svn_repos_find_root_path(full_path, pool);
 
1604
  if (!repos_root)
 
1605
    return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
 
1606
                             "No repository found in '%s'", url);
 
1607
 
 
1608
  /* Open the repository and fill in b with the resulting information. */
 
1609
  SVN_ERR(svn_repos_open(&b->repos, repos_root, pool));
 
1610
  b->fs = svn_repos_fs(b->repos);
 
1611
  b->fs_path = apr_pstrdup(pool, full_path + strlen(repos_root));
 
1612
  url_buf = svn_stringbuf_create(url, pool);
 
1613
  svn_path_remove_components(url_buf, svn_path_component_count(b->fs_path));
 
1614
  b->repos_url = url_buf->data;
 
1615
 
 
1616
  /* Read repository configuration. */
 
1617
  SVN_ERR(svn_config_read(&b->cfg, svn_repos_svnserve_conf(b->repos, pool),
 
1618
                          FALSE, pool));
 
1619
  svn_config_get(b->cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL,
 
1620
                 SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
 
1621
  if (pwdb_path)
 
1622
    {
 
1623
      pwdb_path = svn_path_join(svn_repos_conf_dir(b->repos, pool),
 
1624
                                pwdb_path, pool);
 
1625
      SVN_ERR(svn_config_read(&b->pwdb, pwdb_path, TRUE, pool));
 
1626
 
 
1627
      /* Use the repository UUID as the default realm. */
 
1628
      SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
 
1629
      svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
 
1630
                     SVN_CONFIG_OPTION_REALM, b->realm);
 
1631
    }
 
1632
  else
 
1633
    {
 
1634
      b->pwdb = NULL;
 
1635
      b->realm = "";
 
1636
    }
 
1637
 
 
1638
  /* Make sure it's possible for the client to authenticate. */
 
1639
  if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
 
1640
      && (get_access(b, AUTHENTICATED) == NO_ACCESS
 
1641
          || (!b->tunnel_user && !b->pwdb)))
 
1642
    return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
 
1643
                            "No access allowed to this repository");
 
1644
  return SVN_NO_ERROR;
 
1645
}
 
1646
 
 
1647
/* Compute the authentication name EXTERNAL should be able to get, if any. */
 
1648
static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
 
1649
{
 
1650
  apr_uid_t uid;
 
1651
  apr_gid_t gid;
 
1652
  char *user;
 
1653
 
 
1654
  /* Only offer EXTERNAL for connections tunneled over a login agent. */
 
1655
  if (!params->tunnel)
 
1656
    return NULL;
 
1657
 
 
1658
  /* If a tunnel user was provided on the command line, use that. */
 
1659
  if (params->tunnel_user)
 
1660
    return params->tunnel_user;
 
1661
 
 
1662
#if APR_HAS_USER
 
1663
  /* Use the current uid's name, if we can. */
 
1664
  if (apr_uid_current(&uid, &gid, pool) == APR_SUCCESS
 
1665
      && apr_uid_name_get(&user, uid, pool) == APR_SUCCESS)
 
1666
    return user;
 
1667
#endif
 
1668
 
 
1669
  /* Give up and don't offer EXTERNAL. */
 
1670
  return NULL;
 
1671
}
 
1672
 
 
1673
svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
 
1674
                   apr_pool_t *pool)
 
1675
{
 
1676
  svn_error_t *err, *io_err;
 
1677
  apr_uint64_t ver;
 
1678
  const char *mech, *mecharg, *uuid, *client_url;
 
1679
  apr_array_header_t *caplist;
 
1680
  server_baton_t b;
 
1681
  svn_boolean_t success;
 
1682
  svn_ra_svn_item_t *item, *first;
 
1683
 
 
1684
  b.tunnel = params->tunnel;
 
1685
  b.tunnel_user = get_tunnel_user(params, pool);
 
1686
  b.read_only = params->read_only;
 
1687
  b.user = NULL;
 
1688
  b.cfg = NULL;  /* Ugly; can drop when we remove v1 support. */
 
1689
  b.pwdb = NULL; /* Likewise */
 
1690
  b.pool = pool;
 
1691
 
 
1692
  /* Send greeting.   When we drop support for version 1, we can
 
1693
   * start sending an empty mechlist. */
 
1694
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(nn(!", "success",
 
1695
                                 (apr_uint64_t) 1, (apr_uint64_t) 2));
 
1696
  SVN_ERR(send_mechs(conn, pool, &b, READ_ACCESS, FALSE));
 
1697
  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(w))",
 
1698
                                 SVN_RA_SVN_CAP_EDIT_PIPELINE));
 
1699
 
 
1700
  /* Read client response.  Because the client response form changed
 
1701
   * between version 1 and version 2, we have to do some of this by
 
1702
   * hand until we punt support for version 1. */
 
1703
  SVN_ERR(svn_ra_svn_read_item(conn, pool, &item));
 
1704
  if (item->kind != SVN_RA_SVN_LIST || item->u.list->nelts < 2)
 
1705
    return SVN_NO_ERROR;
 
1706
  first = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
 
1707
  if (first->kind != SVN_RA_SVN_NUMBER)
 
1708
    return SVN_NO_ERROR;
 
1709
  b.protocol_version = (int) first->u.number;
 
1710
  if (b.protocol_version == 1)
 
1711
    {
 
1712
      /* Version 1: auth exchange is mixed with client version and
 
1713
       * capability list, and happens before the client URL is received. */
 
1714
      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "nw(?c)l",
 
1715
                                     &ver, &mech, &mecharg, &caplist));
 
1716
      SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
 
1717
      SVN_ERR(auth(conn, pool, mech, mecharg, &b, READ_ACCESS, FALSE,
 
1718
                   &success));
 
1719
      if (!success)
 
1720
        return svn_ra_svn_flush(conn, pool);
 
1721
      SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "c", &client_url));
 
1722
      client_url = svn_path_canonicalize(client_url, pool);
 
1723
      err = find_repos(client_url, params->root, &b, pool);
 
1724
      if (!err && current_access(&b) == NO_ACCESS)
 
1725
        err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
 
1726
                               "Not authorized for access");
 
1727
      if (err)
 
1728
        {
 
1729
          io_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
 
1730
          svn_error_clear(err);
 
1731
          SVN_ERR(io_err);
 
1732
          return svn_ra_svn_flush(conn, pool);
 
1733
        }
 
1734
    }
 
1735
  else if (b.protocol_version == 2)
 
1736
    {
 
1737
      /* Version 2: client sends version, capability list, and client
 
1738
       * URL, and then we do an auth request. */
 
1739
      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "nlc", &ver,
 
1740
                                     &caplist, &client_url));
 
1741
      client_url = svn_path_canonicalize(client_url, pool);
 
1742
      SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
 
1743
      err = find_repos(client_url, params->root, &b, pool);
 
1744
      if (!err)
 
1745
        {
 
1746
          SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
 
1747
          if (current_access(&b) == NO_ACCESS)
 
1748
            err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
 
1749
                                   "Not authorized for access");
 
1750
        }
 
1751
      if (err)
 
1752
        {
 
1753
          io_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
 
1754
          svn_error_clear(err);
 
1755
          SVN_ERR(io_err);
 
1756
          return svn_ra_svn_flush(conn, pool);
 
1757
        }
 
1758
    }
 
1759
  else
 
1760
    return SVN_NO_ERROR;
 
1761
 
 
1762
  SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
 
1763
  SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "cc", uuid, b.repos_url));
 
1764
 
 
1765
  return svn_ra_svn_handle_commands(conn, pool, main_commands, &b);
 
1766
}