2 * serve.c : Functions for serving the Subversion protocol
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
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.
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 * ====================================================================
22 #include <limits.h> /* for UINT_MAX */
24 #define APR_WANT_STRFUNC
26 #include <apr_general.h>
27 #include <apr_strings.h>
30 #include "svn_compat.h"
31 #include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */
32 #include "svn_types.h"
33 #include "svn_string.h"
34 #include "svn_pools.h"
35 #include "svn_error.h"
36 #include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
37 #include "svn_ra_svn.h"
38 #include "svn_repos.h"
42 #include "svn_config.h"
43 #include "svn_props.h"
44 #include "svn_mergeinfo.h"
47 #include "private/svn_mergeinfo_private.h"
53 svn_revnum_t
*new_rev
;
56 const char **post_commit_err
;
57 } commit_callback_baton_t
;
61 const char *repos_url
; /* Decoded repository URL. */
64 } report_driver_baton_t
;
68 svn_ra_svn_conn_t
*conn
;
73 svn_ra_svn_conn_t
*conn
;
74 apr_pool_t
*pool
; /* Pool provided in the handler call. */
77 svn_error_t
*load_configs(svn_config_t
**cfg
,
79 svn_authz_t
**authzdb
,
81 svn_boolean_t must_exist
,
85 const char *pwdb_path
, *authzdb_path
;
88 SVN_ERR(svn_config_read(cfg
, filename
, must_exist
, pool
));
90 svn_config_get(*cfg
, &pwdb_path
, SVN_CONFIG_SECTION_GENERAL
,
91 SVN_CONFIG_OPTION_PASSWORD_DB
, NULL
);
96 pwdb_path
= svn_path_join(base
, pwdb_path
, pool
);
98 /* Because it may be possible to read the pwdb file with some
99 * access methods and not others, ignore errors reading the pwdb
100 * file and just don't present password authentication as an
101 * option. TODO: Log a warning in this case, when we have a way
102 * of doing logging. */
103 err
= svn_config_read(pwdb
, pwdb_path
, TRUE
, pool
);
104 if (err
&& err
->apr_err
== SVN_ERR_BAD_FILENAME
)
105 svn_error_clear(err
);
110 /* Read authz configuration. */
111 svn_config_get(*cfg
, &authzdb_path
, SVN_CONFIG_SECTION_GENERAL
,
112 SVN_CONFIG_OPTION_AUTHZ_DB
, NULL
);
115 authzdb_path
= svn_path_join(base
, authzdb_path
, pool
);
116 SVN_ERR(svn_repos_authz_read(authzdb
, authzdb_path
, TRUE
, pool
));
126 /* Set *FS_PATH to the portion of URL that is the path within the
127 repository, if URL is inside REPOS_URL (if URL is not inside
128 REPOS_URL, then error, with the effect on *FS_PATH undefined).
130 If the resultant fs path would be the empty string (i.e., URL and
131 REPOS_URL are the same), then set *FS_PATH to "/".
133 Assume that REPOS_URL and URL are already URI-decoded. */
134 static svn_error_t
*get_fs_path(const char *repos_url
, const char *url
,
135 const char **fs_path
)
139 len
= strlen(repos_url
);
140 if (strncmp(url
, repos_url
, len
) != 0)
141 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
142 "'%s' is not the same repository as '%s'",
144 *fs_path
= url
+ len
;
151 /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
153 /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
154 the user described in BATON according to the authz rules in BATON.
155 Use POOL for temporary allocations only. If no authz rules are
156 present in BATON, grant access by default. */
157 static svn_error_t
*authz_check_access(svn_boolean_t
*allowed
,
159 svn_repos_authz_access_t required
,
163 /* If authz cannot be performed, grant access. This is NOT the same
164 as the default policy when authz is performed on a path with no
165 rules. In the latter case, the default is to deny access, and is
166 set by svn_repos_authz_check_access. */
173 /* If the authz request is for the empty path (ie. ""), replace it
174 with the root path. This happens because of stripping done at
175 various levels in svnserve that remove the leading / on an
176 absolute path. Passing such a malformed path to the authz
177 routines throws them into an infinite loop and makes them miss
179 if (path
&& *path
!= '/')
180 path
= svn_path_join("/", path
, pool
);
182 return svn_repos_authz_check_access(b
->authzdb
, b
->authz_repos_name
,
183 path
, b
->user
, required
,
187 /* Set *ALLOWED to TRUE if PATH is readable by the user described in
188 * BATON. Use POOL for temporary allocations only. ROOT is not used.
189 * Implements the svn_repos_authz_func_t interface.
191 static svn_error_t
*authz_check_access_cb(svn_boolean_t
*allowed
,
197 server_baton_t
*sb
= baton
;
199 return authz_check_access(allowed
, path
, svn_authz_read
, sb
, pool
);
202 /* If authz is enabled in the specified BATON, return a read authorization
203 function. Otherwise, return NULL. */
204 static svn_repos_authz_func_t
authz_check_access_cb_func(server_baton_t
*baton
)
207 return authz_check_access_cb
;
211 /* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
212 * according to the state in BATON. Use POOL for temporary
213 * allocations only. ROOT is not used. Implements the
214 * svn_repos_authz_callback_t interface.
216 static svn_error_t
*authz_commit_cb(svn_repos_authz_access_t required
,
217 svn_boolean_t
*allowed
,
223 server_baton_t
*sb
= baton
;
225 return authz_check_access(allowed
, path
, required
, sb
, pool
);
229 enum access_type
get_access(server_baton_t
*b
, enum authn_type auth
)
231 const char *var
= (auth
== AUTHENTICATED
) ? SVN_CONFIG_OPTION_AUTH_ACCESS
:
232 SVN_CONFIG_OPTION_ANON_ACCESS
;
233 const char *val
, *def
= (auth
== AUTHENTICATED
) ? "write" : "read";
234 enum access_type result
;
236 svn_config_get(b
->cfg
, &val
, SVN_CONFIG_SECTION_GENERAL
, var
, def
);
237 result
= (strcmp(val
, "write") == 0 ? WRITE_ACCESS
:
238 strcmp(val
, "read") == 0 ? READ_ACCESS
: NO_ACCESS
);
239 return (result
== WRITE_ACCESS
&& b
->read_only
) ? READ_ACCESS
: result
;
242 static enum access_type
current_access(server_baton_t
*b
)
244 return get_access(b
, (b
->user
) ? AUTHENTICATED
: UNAUTHENTICATED
);
247 /* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME
248 is true, don't send anonymous mech even if that would give the desired
250 static svn_error_t
*send_mechs(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
251 server_baton_t
*b
, enum access_type required
,
252 svn_boolean_t needs_username
)
254 if (!needs_username
&& get_access(b
, UNAUTHENTICATED
) >= required
)
255 SVN_ERR(svn_ra_svn_write_word(conn
, pool
, "ANONYMOUS"));
256 if (b
->tunnel_user
&& get_access(b
, AUTHENTICATED
) >= required
)
257 SVN_ERR(svn_ra_svn_write_word(conn
, pool
, "EXTERNAL"));
258 if (b
->pwdb
&& get_access(b
, AUTHENTICATED
) >= required
)
259 SVN_ERR(svn_ra_svn_write_word(conn
, pool
, "CRAM-MD5"));
263 /* Context for cleanup handler. */
264 struct cleanup_fs_access_baton
270 /* Pool cleanup handler. Make sure fs's access_t points to NULL when
271 the command pool is destroyed. */
272 static apr_status_t
cleanup_fs_access(void *data
)
275 struct cleanup_fs_access_baton
*baton
= data
;
277 serr
= svn_fs_set_access(baton
->fs
, NULL
);
280 apr_status_t apr_err
= serr
->apr_err
;
281 svn_error_clear(serr
);
289 /* Create an svn_fs_access_t in POOL for USER and associate it with
290 B's filesystem. Also, register a cleanup handler with POOL which
291 de-associates the svn_fs_access_t from B's filesystem. */
293 create_fs_access(server_baton_t
*b
, apr_pool_t
*pool
)
295 svn_fs_access_t
*fs_access
;
296 struct cleanup_fs_access_baton
*cleanup_baton
;
301 SVN_ERR(svn_fs_create_access(&fs_access
, b
->user
, pool
));
302 SVN_ERR(svn_fs_set_access(b
->fs
, fs_access
));
304 cleanup_baton
= apr_pcalloc(pool
, sizeof(*cleanup_baton
));
305 cleanup_baton
->pool
= pool
;
306 cleanup_baton
->fs
= b
->fs
;
307 apr_pool_cleanup_register(pool
, cleanup_baton
, cleanup_fs_access
,
308 apr_pool_cleanup_null
);
313 /* Authenticate, once the client has chosen a mechanism and possibly
314 * sent an initial mechanism token. On success, set *success to true
315 * and b->user to the authenticated username (or NULL for anonymous).
316 * On authentication failure, report failure to the client and set
317 * *success to FALSE. On communications failure, return an error.
318 * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
319 static svn_error_t
*auth(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
320 const char *mech
, const char *mecharg
,
321 server_baton_t
*b
, enum access_type required
,
322 svn_boolean_t needs_username
,
323 svn_boolean_t
*success
)
328 if (get_access(b
, AUTHENTICATED
) >= required
329 && b
->tunnel_user
&& strcmp(mech
, "EXTERNAL") == 0)
331 if (*mecharg
&& strcmp(mecharg
, b
->tunnel_user
) != 0)
332 return svn_ra_svn_write_tuple(conn
, pool
, "w(c)", "failure",
333 "Requested username does not match");
334 b
->user
= b
->tunnel_user
;
335 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w()", "success"));
340 if (get_access(b
, UNAUTHENTICATED
) >= required
341 && strcmp(mech
, "ANONYMOUS") == 0 && ! needs_username
)
343 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w()", "success"));
348 if (get_access(b
, AUTHENTICATED
) >= required
349 && b
->pwdb
&& strcmp(mech
, "CRAM-MD5") == 0)
351 SVN_ERR(svn_ra_svn_cram_server(conn
, pool
, b
->pwdb
, &user
, success
));
352 b
->user
= apr_pstrdup(b
->pool
, user
);
356 return svn_ra_svn_write_tuple(conn
, pool
, "w(c)", "failure",
357 "Must authenticate with listed mechanism");
360 /* Perform an authentication request using the built-in SASL implementation. */
362 internal_auth_request(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
363 server_baton_t
*b
, enum access_type required
,
364 svn_boolean_t needs_username
)
366 svn_boolean_t success
;
367 const char *mech
, *mecharg
;
369 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((!", "success"));
370 SVN_ERR(send_mechs(conn
, pool
, b
, required
, needs_username
));
371 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!)c)", b
->realm
));
374 SVN_ERR(svn_ra_svn_read_tuple(conn
, pool
, "w(?c)", &mech
, &mecharg
));
377 SVN_ERR(auth(conn
, pool
, mech
, mecharg
, b
, required
, needs_username
,
384 /* Perform an authentication request in order to get an access level of
385 * REQUIRED or higher. Since the client may escape the authentication
386 * exchange, the caller should check current_access(b) to see if
387 * authentication succeeded. */
388 static svn_error_t
*auth_request(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
389 server_baton_t
*b
, enum access_type required
,
390 svn_boolean_t needs_username
)
394 return cyrus_auth_request(conn
, pool
, b
, required
, needs_username
);
397 return internal_auth_request(conn
, pool
, b
, required
, needs_username
);
400 /* Send a trivial auth notification on CONN which lists no mechanisms,
401 * indicating that authentication is unnecessary. Usually called in
402 * response to invocation of a svnserve command.
404 static svn_error_t
*trivial_auth_request(svn_ra_svn_conn_t
*conn
,
405 apr_pool_t
*pool
, server_baton_t
*b
)
407 return svn_ra_svn_write_cmd_response(conn
, pool
, "()c", "");
410 /* Ensure that the client has the REQUIRED access by checking the
411 * access directives (both blanket and per-directory) in BATON. If
412 * PATH is NULL, then only the blanket access configuration will
415 * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the
416 * user described in BATON is authenticated and, well, has a username
419 * Use POOL for temporary allocations only.
421 static svn_boolean_t
lookup_access(apr_pool_t
*pool
,
422 server_baton_t
*baton
,
423 svn_repos_authz_access_t required
,
425 svn_boolean_t needs_username
)
427 enum access_type req
= (required
& svn_authz_write
) ?
428 WRITE_ACCESS
: READ_ACCESS
;
429 svn_boolean_t authorized
;
432 /* Get authz's opinion on the access. */
433 err
= authz_check_access(&authorized
, path
, required
, baton
, pool
);
435 /* If an error made lookup fail, deny access. ### TODO: Once
436 logging is implemented, this is a perfect place to log the
440 svn_error_clear(err
);
444 /* If the required access is blanket-granted AND granted by authz
445 AND we already have a username if one is required, then the
446 lookup has succeeded. */
447 if (current_access(baton
) >= req
449 && (! needs_username
|| baton
->user
))
455 /* Check that the client has the REQUIRED access by consulting the
456 * authentication and authorization states stored in BATON. If the
457 * client does not have the required access credentials, attempt to
458 * authenticate the client to get that access, using CONN for
461 * This function is supposed to be called to handle the authentication
462 * half of a standard svn protocol reply. If an error is returned, it
463 * probably means that the server can terminate the client connection
464 * with an apologetic error, as it implies an authentication failure.
466 * PATH and NEEDS_USERNAME are passed along to lookup_access, their
467 * behaviour is documented there.
469 static svn_error_t
*must_have_access(svn_ra_svn_conn_t
*conn
,
472 svn_repos_authz_access_t required
,
474 svn_boolean_t needs_username
)
476 enum access_type req
= (required
& svn_authz_write
) ?
477 WRITE_ACCESS
: READ_ACCESS
;
479 /* See whether the user already has the required access. If so,
480 nothing needs to be done. Create the FS access and send a
481 trivial auth request. */
482 if (lookup_access(pool
, b
, required
, path
, needs_username
))
484 SVN_ERR(create_fs_access(b
, pool
));
485 return trivial_auth_request(conn
, pool
, b
);
488 /* If the required blanket access can be obtained by authenticating,
489 try that. Unfortunately, we can't tell until after
490 authentication whether authz will work or not. We force
491 requiring a username because we need one to be able to check
492 authz configuration again with a different user credentials than
493 the first time round. */
495 && get_access(b
, AUTHENTICATED
) >= req
496 && (b
->tunnel_user
|| b
->pwdb
|| b
->use_sasl
))
497 SVN_ERR(auth_request(conn
, pool
, b
, req
, TRUE
));
499 /* Now that an authentication has been done get the new take of
500 authz on the request. */
501 if (! lookup_access(pool
, b
, required
, path
, needs_username
))
502 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR
,
503 svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
,
506 /* Else, access is granted, and there is much rejoicing. */
507 SVN_ERR(create_fs_access(b
, pool
));
512 /* --- REPORTER COMMAND SET --- */
514 /* To allow for pipelining, reporter commands have no reponses. If we
515 * get an error, we ignore all subsequent reporter commands and return
516 * the error finish_report, to be handled by the calling command.
519 static svn_error_t
*set_path(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
520 apr_array_header_t
*params
, void *baton
)
522 report_driver_baton_t
*b
= baton
;
523 const char *path
, *lock_token
, *depth_word
;
525 /* Default to infinity, for old clients that don't send depth. */
526 svn_depth_t depth
= svn_depth_infinity
;
527 svn_boolean_t start_empty
;
529 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "crb?(?c)?w",
530 &path
, &rev
, &start_empty
, &lock_token
,
533 depth
= svn_depth_from_word(depth_word
);
534 path
= svn_path_canonicalize(path
, pool
);
536 b
->err
= svn_repos_set_path3(b
->report_baton
, path
, rev
, depth
,
537 start_empty
, lock_token
, pool
);
541 static svn_error_t
*delete_path(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
542 apr_array_header_t
*params
, void *baton
)
544 report_driver_baton_t
*b
= baton
;
547 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c", &path
));
548 path
= svn_path_canonicalize(path
, pool
);
550 b
->err
= svn_repos_delete_path(b
->report_baton
, path
, pool
);
554 static svn_error_t
*link_path(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
555 apr_array_header_t
*params
, void *baton
)
557 report_driver_baton_t
*b
= baton
;
558 const char *path
, *url
, *lock_token
, *fs_path
, *depth_word
;
560 svn_boolean_t start_empty
;
561 /* Default to infinity, for old clients that don't send depth. */
562 svn_depth_t depth
= svn_depth_infinity
;
564 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "ccrb?(?c)?w",
565 &path
, &url
, &rev
, &start_empty
,
566 &lock_token
, &depth_word
));
567 path
= svn_path_canonicalize(path
, pool
);
568 url
= svn_path_uri_decode(svn_path_canonicalize(url
, pool
), pool
);
570 depth
= svn_depth_from_word(depth_word
);
572 b
->err
= get_fs_path(svn_path_uri_decode(b
->repos_url
, pool
),
575 b
->err
= svn_repos_link_path3(b
->report_baton
, path
, fs_path
, rev
,
576 depth
, start_empty
, lock_token
, pool
);
580 static svn_error_t
*finish_report(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
581 apr_array_header_t
*params
, void *baton
)
583 report_driver_baton_t
*b
= baton
;
585 /* No arguments to parse. */
586 SVN_ERR(trivial_auth_request(conn
, pool
, b
->sb
));
588 b
->err
= svn_repos_finish_report(b
->report_baton
, pool
);
592 static svn_error_t
*abort_report(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
593 apr_array_header_t
*params
, void *baton
)
595 report_driver_baton_t
*b
= baton
;
597 /* No arguments to parse. */
598 svn_error_clear(svn_repos_abort_report(b
->report_baton
, pool
));
602 static const svn_ra_svn_cmd_entry_t report_commands
[] = {
603 { "set-path", set_path
},
604 { "delete-path", delete_path
},
605 { "link-path", link_path
},
606 { "finish-report", finish_report
, TRUE
},
607 { "abort-report", abort_report
, TRUE
},
611 /* Accept a report from the client, drive the network editor with the
612 * result, and then write an empty command response. If there is a
613 * non-protocol failure, accept_report will abort the edit and return
614 * a command error to be reported by handle_commands(). */
615 static svn_error_t
*accept_report(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
616 server_baton_t
*b
, svn_revnum_t rev
,
617 const char *target
, const char *tgt_path
,
618 svn_boolean_t text_deltas
,
620 svn_boolean_t send_copyfrom_args
,
621 svn_boolean_t ignore_ancestry
)
623 const svn_delta_editor_t
*editor
;
624 void *edit_baton
, *report_baton
;
625 report_driver_baton_t rb
;
628 /* Make an svn_repos report baton. Tell it to drive the network editor
629 * when the report is complete. */
630 svn_ra_svn_get_editor(&editor
, &edit_baton
, conn
, pool
, NULL
, NULL
);
631 SVN_CMD_ERR(svn_repos_begin_report2(&report_baton
, rev
, b
->repos
,
632 b
->fs_path
->data
, target
, tgt_path
,
633 text_deltas
, depth
, ignore_ancestry
,
636 authz_check_access_cb_func(b
),
640 rb
.repos_url
= svn_path_uri_decode(b
->repos_url
, pool
);
641 rb
.report_baton
= report_baton
;
643 err
= svn_ra_svn_handle_commands(conn
, pool
, report_commands
, &rb
);
646 /* Network or protocol error while handling commands. */
647 svn_error_clear(rb
.err
);
652 /* Some failure during the reporting or editing operations. */
653 svn_error_clear(editor
->abort_edit(edit_baton
, pool
));
656 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
660 /* --- MAIN COMMAND SET --- */
662 /* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t
664 static svn_error_t
*write_prop_diffs(svn_ra_svn_conn_t
*conn
,
666 apr_array_header_t
*propdiffs
)
670 for (i
= 0; i
< propdiffs
->nelts
; ++i
)
672 const svn_prop_t
*prop
= &APR_ARRAY_IDX(propdiffs
, i
, svn_prop_t
);
674 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "c(?s)",
675 prop
->name
, prop
->value
));
681 /* Write out a lock to the client. */
682 static svn_error_t
*write_lock(svn_ra_svn_conn_t
*conn
,
686 const char *cdate
, *edate
;
688 cdate
= svn_time_to_cstring(lock
->creation_date
, pool
);
689 edate
= lock
->expiration_date
690 ? svn_time_to_cstring(lock
->expiration_date
, pool
) : NULL
;
691 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "ccc(?c)c(?c)", lock
->path
,
692 lock
->token
, lock
->owner
, lock
->comment
,
698 static const char *kind_word(svn_node_kind_t kind
)
708 case svn_node_unknown
:
714 /* Make the compiler happy */
718 /* ### This really belongs in libsvn_repos. */
719 /* Get the properties for a path, with hardcoded committed-info values. */
720 static svn_error_t
*get_props(apr_hash_t
**props
, svn_fs_root_t
*root
,
721 const char *path
, apr_pool_t
*pool
)
725 const char *cdate
, *cauthor
, *uuid
;
727 /* Get the properties. */
728 SVN_ERR(svn_fs_node_proplist(props
, root
, path
, pool
));
730 /* Hardcode the values for the committed revision, date, and author. */
731 SVN_ERR(svn_repos_get_committed_info(&crev
, &cdate
, &cauthor
, root
,
733 str
= svn_string_create(apr_psprintf(pool
, "%ld", crev
),
735 apr_hash_set(*props
, SVN_PROP_ENTRY_COMMITTED_REV
, APR_HASH_KEY_STRING
, str
);
736 str
= (cdate
) ? svn_string_create(cdate
, pool
) : NULL
;
737 apr_hash_set(*props
, SVN_PROP_ENTRY_COMMITTED_DATE
, APR_HASH_KEY_STRING
,
739 str
= (cauthor
) ? svn_string_create(cauthor
, pool
) : NULL
;
740 apr_hash_set(*props
, SVN_PROP_ENTRY_LAST_AUTHOR
, APR_HASH_KEY_STRING
, str
);
742 /* Hardcode the values for the UUID. */
743 SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root
), &uuid
, pool
));
744 str
= (uuid
) ? svn_string_create(uuid
, pool
) : NULL
;
745 apr_hash_set(*props
, SVN_PROP_ENTRY_UUID
, APR_HASH_KEY_STRING
, str
);
750 /* Set BATON->FS_PATH for the repository URL found in PARAMS. */
751 static svn_error_t
*reparent(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
752 apr_array_header_t
*params
, void *baton
)
754 server_baton_t
*b
= baton
;
758 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c", &url
));
759 url
= svn_path_uri_decode(svn_path_canonicalize(url
, pool
), pool
);
760 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
761 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b
->repos_url
, pool
),
763 svn_stringbuf_set(b
->fs_path
, fs_path
);
764 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
768 static svn_error_t
*get_latest_rev(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
769 apr_array_header_t
*params
, void *baton
)
771 server_baton_t
*b
= baton
;
774 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
775 SVN_CMD_ERR(svn_fs_youngest_rev(&rev
, b
->fs
, pool
));
776 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, "r", rev
));
780 static svn_error_t
*get_dated_rev(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
781 apr_array_header_t
*params
, void *baton
)
783 server_baton_t
*b
= baton
;
788 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c", ×tr
));
789 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
790 SVN_CMD_ERR(svn_time_from_cstring(&tm
, timestr
, pool
));
791 SVN_CMD_ERR(svn_repos_dated_revision(&rev
, b
->repos
, tm
, pool
));
792 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, "r", rev
));
796 static svn_error_t
*change_rev_prop(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
797 apr_array_header_t
*params
, void *baton
)
799 server_baton_t
*b
= baton
;
804 /* Because the revprop value was at one time mandatory, the usual
805 optional element pattern "(?s)" isn't used. */
806 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "rc?s", &rev
, &name
, &value
));
807 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_write
, NULL
, FALSE
));
808 SVN_CMD_ERR(svn_repos_fs_change_rev_prop3(b
->repos
, rev
, b
->user
,
809 name
, value
, TRUE
, TRUE
,
810 authz_check_access_cb_func(b
), b
,
812 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
816 static svn_error_t
*rev_proplist(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
817 apr_array_header_t
*params
, void *baton
)
819 server_baton_t
*b
= baton
;
823 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "r", &rev
));
824 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
825 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props
, b
->repos
, rev
,
826 authz_check_access_cb_func(b
), b
,
828 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((!", "success"));
829 SVN_ERR(svn_ra_svn_write_proplist(conn
, pool
, props
));
830 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
834 static svn_error_t
*rev_prop(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
835 apr_array_header_t
*params
, void *baton
)
837 server_baton_t
*b
= baton
;
842 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "rc", &rev
, &name
));
843 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
844 SVN_CMD_ERR(svn_repos_fs_revision_prop(&value
, b
->repos
, rev
, name
,
845 authz_check_access_cb_func(b
), b
,
847 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, "(?s)", value
));
851 static svn_error_t
*commit_done(const svn_commit_info_t
*commit_info
,
852 void *baton
, apr_pool_t
*pool
)
854 commit_callback_baton_t
*ccb
= baton
;
856 *ccb
->new_rev
= commit_info
->revision
;
857 *ccb
->date
= commit_info
->date
858 ? apr_pstrdup(ccb
->pool
, commit_info
->date
): NULL
;
859 *ccb
->author
= commit_info
->author
860 ? apr_pstrdup(ccb
->pool
, commit_info
->author
) : NULL
;
861 *ccb
->post_commit_err
= commit_info
->post_commit_err
862 ? apr_pstrdup(ccb
->pool
, commit_info
->post_commit_err
) : NULL
;
866 /* Add the LOCK_TOKENS (if any) to the filesystem access context,
867 * checking path authorizations using the state in SB as we go.
868 * LOCK_TOKENS is an array of svn_ra_svn_item_t structs. Return a
869 * client error if LOCK_TOKENS is not a list of lists. If a lock
870 * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
871 * to the client. Use POOL for temporary allocations only.
873 static svn_error_t
*add_lock_tokens(svn_ra_svn_conn_t
*conn
,
874 apr_array_header_t
*lock_tokens
,
879 svn_fs_access_t
*fs_access
;
881 SVN_ERR(svn_fs_get_access(&fs_access
, sb
->fs
));
883 /* If there is no access context, nowhere to add the tokens. */
887 for (i
= 0; i
< lock_tokens
->nelts
; ++i
)
889 const char *path
, *token
, *full_path
;
890 svn_ra_svn_item_t
*path_item
, *token_item
;
891 svn_ra_svn_item_t
*item
= &APR_ARRAY_IDX(lock_tokens
, i
,
893 if (item
->kind
!= SVN_RA_SVN_LIST
)
894 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
895 "Lock tokens aren't a list of lists");
897 path_item
= &APR_ARRAY_IDX(item
->u
.list
, 0, svn_ra_svn_item_t
);
898 if (path_item
->kind
!= SVN_RA_SVN_STRING
)
899 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
900 "Lock path isn't a string");
902 token_item
= &APR_ARRAY_IDX(item
->u
.list
, 1, svn_ra_svn_item_t
);
903 if (token_item
->kind
!= SVN_RA_SVN_STRING
)
904 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
905 "Lock token isn't a string");
907 path
= path_item
->u
.string
->data
;
908 full_path
= svn_path_join(sb
->fs_path
->data
,
909 svn_path_canonicalize(path
, pool
),
912 if (! lookup_access(pool
, sb
, svn_authz_write
,
914 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
,
917 token
= token_item
->u
.string
->data
;
918 SVN_ERR(svn_fs_access_add_lock_token(fs_access
, token
));
924 /* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
925 LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */
926 static svn_error_t
*unlock_paths(apr_array_header_t
*lock_tokens
,
931 apr_pool_t
*iterpool
;
933 iterpool
= svn_pool_create(pool
);
935 for (i
= 0; i
< lock_tokens
->nelts
; ++i
)
937 svn_ra_svn_item_t
*item
, *path_item
, *token_item
;
938 const char *path
, *token
, *full_path
;
939 svn_pool_clear(iterpool
);
941 item
= &APR_ARRAY_IDX(lock_tokens
, i
, svn_ra_svn_item_t
);
942 path_item
= &APR_ARRAY_IDX(item
->u
.list
, 0, svn_ra_svn_item_t
);
943 token_item
= &APR_ARRAY_IDX(item
->u
.list
, 1, svn_ra_svn_item_t
);
945 path
= path_item
->u
.string
->data
;
946 token
= token_item
->u
.string
->data
;
948 full_path
= svn_path_join(sb
->fs_path
->data
,
949 svn_path_canonicalize(path
, iterpool
),
952 /* The lock may have become defunct after the commit, so ignore such
955 ### If we ever write a logging facility for svnserve, this
956 would be a good place to log an error before clearing
958 svn_error_clear(svn_repos_fs_unlock(sb
->repos
, full_path
, token
,
962 svn_pool_destroy(iterpool
);
967 static svn_error_t
*commit(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
968 apr_array_header_t
*params
, void *baton
)
970 server_baton_t
*b
= baton
;
971 const char *log_msg
= NULL
,
974 *post_commit_err
= NULL
;
975 apr_array_header_t
*lock_tokens
;
976 svn_boolean_t keep_locks
;
977 apr_array_header_t
*revprop_list
= NULL
;
978 apr_hash_t
*revprop_table
;
979 const svn_delta_editor_t
*editor
;
981 svn_boolean_t aborted
;
982 commit_callback_baton_t ccb
;
983 svn_revnum_t new_rev
;
985 if (params
->nelts
== 1)
987 /* Clients before 1.2 don't send lock-tokens, keep-locks,
988 and rev-props fields. */
989 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c", &log_msg
));
996 /* Clients before 1.5 don't send the rev-props field. */
997 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "clb?l", &log_msg
,
998 &lock_tokens
, &keep_locks
,
1002 /* The handling for locks is a little problematic, because the
1003 protocol won't let us send several auth requests once one has
1004 succeeded. So we request write access and a username before
1005 adding tokens (if we have any), and subsequently fail if a lock
1007 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_write
,
1009 (lock_tokens
&& lock_tokens
->nelts
) ? TRUE
: FALSE
));
1011 /* Authorize the lock tokens and give them to the FS if we got
1013 if (lock_tokens
&& lock_tokens
->nelts
)
1014 SVN_CMD_ERR(add_lock_tokens(conn
, lock_tokens
, b
, pool
));
1017 SVN_ERR(svn_ra_svn_parse_proplist(revprop_list
, pool
, &revprop_table
));
1020 revprop_table
= apr_hash_make(pool
);
1021 apr_hash_set(revprop_table
, SVN_PROP_REVISION_LOG
, APR_HASH_KEY_STRING
,
1022 svn_string_create(log_msg
, pool
));
1025 /* Get author from the baton, making sure clients can't circumvent
1026 the authentication via the revision props. */
1027 apr_hash_set(revprop_table
, SVN_PROP_REVISION_AUTHOR
, APR_HASH_KEY_STRING
,
1028 b
->user
? svn_string_create(b
->user
, pool
) : NULL
);
1031 ccb
.new_rev
= &new_rev
;
1033 ccb
.author
= &author
;
1034 ccb
.post_commit_err
= &post_commit_err
;
1035 /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
1036 SVN_CMD_ERR(svn_repos_get_commit_editor5
1037 (&editor
, &edit_baton
, b
->repos
, NULL
,
1038 svn_path_uri_decode(b
->repos_url
, pool
),
1039 b
->fs_path
->data
, revprop_table
,
1041 authz_commit_cb
, baton
, pool
));
1042 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
1043 SVN_ERR(svn_ra_svn_drive_editor(conn
, pool
, editor
, edit_baton
, &aborted
));
1046 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
1048 /* In tunnel mode, deltify before answering the client, because
1049 answering may cause the client to terminate the connection
1050 and thus kill the server. But otherwise, deltify after
1051 answering the client, to avoid user-visible delay. */
1054 SVN_ERR(svn_fs_deltify_revision(b
->fs
, new_rev
, pool
));
1056 /* Unlock the paths. */
1057 if (! keep_locks
&& lock_tokens
&& lock_tokens
->nelts
)
1058 SVN_ERR(unlock_paths(lock_tokens
, b
, pool
));
1060 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "r(?c)(?c)(?c)",
1061 new_rev
, date
, author
, post_commit_err
));
1064 SVN_ERR(svn_fs_deltify_revision(b
->fs
, new_rev
, pool
));
1066 return SVN_NO_ERROR
;
1069 static svn_error_t
*get_file(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1070 apr_array_header_t
*params
, void *baton
)
1072 server_baton_t
*b
= baton
;
1073 const char *path
, *full_path
, *hex_digest
;
1075 svn_fs_root_t
*root
;
1076 svn_stream_t
*contents
;
1077 apr_hash_t
*props
= NULL
;
1078 svn_string_t write_str
;
1081 svn_boolean_t want_props
, want_contents
;
1082 unsigned char digest
[APR_MD5_DIGESTSIZE
];
1083 svn_error_t
*err
, *write_err
;
1085 /* Parse arguments. */
1086 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c(?r)bb", &path
, &rev
,
1087 &want_props
, &want_contents
));
1089 full_path
= svn_path_join(b
->fs_path
->data
,
1090 svn_path_canonicalize(path
, pool
), pool
);
1092 /* Check authorizations */
1093 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_read
,
1096 if (!SVN_IS_VALID_REVNUM(rev
))
1097 SVN_CMD_ERR(svn_fs_youngest_rev(&rev
, b
->fs
, pool
));
1099 /* Fetch the properties and a stream for the contents. */
1100 SVN_CMD_ERR(svn_fs_revision_root(&root
, b
->fs
, rev
, pool
));
1101 SVN_CMD_ERR(svn_fs_file_md5_checksum(digest
, root
, full_path
, pool
));
1102 hex_digest
= svn_md5_digest_to_cstring_display(digest
, pool
);
1104 SVN_CMD_ERR(get_props(&props
, root
, full_path
, pool
));
1106 SVN_CMD_ERR(svn_fs_file_contents(&contents
, root
, full_path
, pool
));
1108 /* Send successful command response with revision and props. */
1109 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((?c)r(!", "success",
1111 SVN_ERR(svn_ra_svn_write_proplist(conn
, pool
, props
));
1112 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1114 /* Now send the file's contents. */
1121 err
= svn_stream_read(contents
, buf
, &len
);
1126 write_str
.data
= buf
;
1127 write_str
.len
= len
;
1128 SVN_ERR(svn_ra_svn_write_string(conn
, pool
, &write_str
));
1130 if (len
< sizeof(buf
))
1132 err
= svn_stream_close(contents
);
1136 write_err
= svn_ra_svn_write_cstring(conn
, pool
, "");
1139 svn_error_clear(err
);
1143 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
1146 return SVN_NO_ERROR
;
1149 static svn_error_t
*get_dir(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1150 apr_array_header_t
*params
, void *baton
)
1152 server_baton_t
*b
= baton
;
1153 const char *path
, *full_path
, *file_path
, *name
, *cauthor
, *cdate
;
1155 apr_hash_t
*entries
, *props
= NULL
, *file_props
;
1156 apr_hash_index_t
*hi
;
1157 svn_fs_dirent_t
*fsent
;
1158 svn_dirent_t
*entry
;
1161 svn_fs_root_t
*root
;
1162 apr_pool_t
*subpool
;
1163 svn_boolean_t want_props
, want_contents
;
1164 apr_uint64_t dirent_fields
;
1165 apr_array_header_t
*dirent_fields_list
= NULL
;
1166 svn_ra_svn_item_t
*elt
;
1169 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c(?r)bb?l", &path
, &rev
,
1170 &want_props
, &want_contents
,
1171 &dirent_fields_list
));
1173 if (! dirent_fields_list
)
1175 dirent_fields
= SVN_DIRENT_ALL
;
1181 for (i
= 0; i
< dirent_fields_list
->nelts
; ++i
)
1183 elt
= &APR_ARRAY_IDX(dirent_fields_list
, i
, svn_ra_svn_item_t
);
1185 if (elt
->kind
!= SVN_RA_SVN_WORD
)
1186 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1187 "Dirent field not a string");
1189 if (strcmp(SVN_RA_SVN_DIRENT_KIND
, elt
->u
.word
) == 0)
1190 dirent_fields
|= SVN_DIRENT_KIND
;
1191 else if (strcmp(SVN_RA_SVN_DIRENT_SIZE
, elt
->u
.word
) == 0)
1192 dirent_fields
|= SVN_DIRENT_SIZE
;
1193 else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS
, elt
->u
.word
) == 0)
1194 dirent_fields
|= SVN_DIRENT_HAS_PROPS
;
1195 else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV
, elt
->u
.word
) == 0)
1196 dirent_fields
|= SVN_DIRENT_CREATED_REV
;
1197 else if (strcmp(SVN_RA_SVN_DIRENT_TIME
, elt
->u
.word
) == 0)
1198 dirent_fields
|= SVN_DIRENT_TIME
;
1199 else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR
, elt
->u
.word
) == 0)
1200 dirent_fields
|= SVN_DIRENT_LAST_AUTHOR
;
1204 full_path
= svn_path_join(b
->fs_path
->data
,
1205 svn_path_canonicalize(path
, pool
), pool
);
1207 /* Check authorizations */
1208 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_read
,
1211 if (!SVN_IS_VALID_REVNUM(rev
))
1212 SVN_CMD_ERR(svn_fs_youngest_rev(&rev
, b
->fs
, pool
));
1214 /* Fetch the root of the appropriate revision. */
1215 SVN_CMD_ERR(svn_fs_revision_root(&root
, b
->fs
, rev
, pool
));
1217 /* Fetch the directory properties if requested. */
1219 SVN_CMD_ERR(get_props(&props
, root
, full_path
, pool
));
1221 /* Fetch the directory entries if requested. */
1224 SVN_CMD_ERR(svn_fs_dir_entries(&entries
, root
, full_path
, pool
));
1226 /* Transform the hash table's FS entries into dirents. This probably
1227 * belongs in libsvn_repos. */
1228 subpool
= svn_pool_create(pool
);
1229 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
1231 apr_hash_this(hi
, &key
, NULL
, &val
);
1235 svn_pool_clear(subpool
);
1237 file_path
= svn_path_join(full_path
, name
, subpool
);
1238 entry
= apr_pcalloc(pool
, sizeof(*entry
));
1240 if (dirent_fields
& SVN_DIRENT_KIND
)
1243 entry
->kind
= fsent
->kind
;
1246 if (dirent_fields
& SVN_DIRENT_SIZE
)
1249 if (entry
->kind
== svn_node_dir
)
1252 SVN_CMD_ERR(svn_fs_file_length(&entry
->size
, root
, file_path
,
1256 if (dirent_fields
& SVN_DIRENT_HAS_PROPS
)
1259 SVN_CMD_ERR(svn_fs_node_proplist(&file_props
, root
, file_path
,
1261 entry
->has_props
= (apr_hash_count(file_props
) > 0) ? TRUE
1265 if ((dirent_fields
& SVN_DIRENT_LAST_AUTHOR
)
1266 || (dirent_fields
& SVN_DIRENT_TIME
)
1267 || (dirent_fields
& SVN_DIRENT_CREATED_REV
))
1269 /* created_rev, last_author, time */
1270 SVN_CMD_ERR(svn_repos_get_committed_info(&entry
->created_rev
,
1275 entry
->last_author
= apr_pstrdup(pool
, cauthor
);
1277 SVN_CMD_ERR(svn_time_from_cstring(&entry
->time
, cdate
,
1280 entry
->time
= (time_t) -1;
1283 /* Store the entry. */
1284 apr_hash_set(entries
, name
, APR_HASH_KEY_STRING
, entry
);
1286 svn_pool_destroy(subpool
);
1289 /* Write out response. */
1290 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(r(!", "success", rev
));
1291 SVN_ERR(svn_ra_svn_write_proplist(conn
, pool
, props
));
1292 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!)(!"));
1295 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
1297 apr_hash_this(hi
, &key
, NULL
, &val
);
1300 cdate
= (entry
->time
== (time_t) -1) ? NULL
1301 : svn_time_to_cstring(entry
->time
, pool
);
1302 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "cwnbr(?c)(?c)", name
,
1303 kind_word(entry
->kind
),
1304 (apr_uint64_t
) entry
->size
,
1305 entry
->has_props
, entry
->created_rev
,
1306 cdate
, entry
->last_author
));
1309 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1310 return SVN_NO_ERROR
;
1314 static svn_error_t
*update(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1315 apr_array_header_t
*params
, void *baton
)
1317 server_baton_t
*b
= baton
;
1319 const char *target
, *full_path
, *depth_word
;
1320 svn_boolean_t recurse
;
1321 svn_boolean_t send_copyfrom_args
;
1322 apr_uint64_t send_copyfrom_param
;
1323 /* Default to unknown. Old clients won't send depth, but we'll
1324 handle that by converting recurse if necessary. */
1325 svn_depth_t depth
= svn_depth_unknown
;
1327 /* Parse the arguments. */
1328 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "(?r)cb?wB", &rev
, &target
,
1329 &recurse
, &depth_word
, &send_copyfrom_param
));
1330 target
= svn_path_canonicalize(target
, pool
);
1333 depth
= svn_depth_from_word(depth_word
);
1335 depth
= SVN_DEPTH_INFINITY_OR_FILES(recurse
);
1337 send_copyfrom_args
= (send_copyfrom_param
== SVN_RA_SVN_UNSPECIFIED_NUMBER
) ?
1338 FALSE
: (svn_boolean_t
) send_copyfrom_param
;
1340 full_path
= svn_path_join(b
->fs_path
->data
, target
, pool
);
1341 /* Check authorization and authenticate the user if necessary. */
1342 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_read
, full_path
, FALSE
));
1344 if (!SVN_IS_VALID_REVNUM(rev
))
1345 SVN_CMD_ERR(svn_fs_youngest_rev(&rev
, b
->fs
, pool
));
1347 return accept_report(conn
, pool
, b
, rev
, target
, NULL
, TRUE
,
1348 depth
, send_copyfrom_args
, FALSE
);
1351 static svn_error_t
*switch_cmd(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1352 apr_array_header_t
*params
, void *baton
)
1354 server_baton_t
*b
= baton
;
1356 const char *target
, *depth_word
;
1357 const char *switch_url
, *switch_path
;
1358 svn_boolean_t recurse
;
1359 /* Default to unknown. Old clients won't send depth, but we'll
1360 handle that by converting recurse if necessary. */
1361 svn_depth_t depth
= svn_depth_unknown
;
1363 /* Parse the arguments. */
1364 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "(?r)cbc?w", &rev
, &target
,
1365 &recurse
, &switch_url
, &depth_word
));
1366 target
= svn_path_canonicalize(target
, pool
);
1367 switch_url
= svn_path_canonicalize(switch_url
, pool
);
1370 depth
= svn_depth_from_word(depth_word
);
1372 depth
= SVN_DEPTH_INFINITY_OR_FILES(recurse
);
1374 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
1375 if (!SVN_IS_VALID_REVNUM(rev
))
1376 SVN_CMD_ERR(svn_fs_youngest_rev(&rev
, b
->fs
, pool
));
1377 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b
->repos_url
, pool
),
1378 svn_path_uri_decode(switch_url
, pool
),
1381 return accept_report(conn
, pool
, b
, rev
, target
, switch_path
, TRUE
,
1383 FALSE
/* TODO(sussman): no copyfrom args for now */,
1387 static svn_error_t
*status(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1388 apr_array_header_t
*params
, void *baton
)
1390 server_baton_t
*b
= baton
;
1392 const char *target
, *depth_word
;
1393 svn_boolean_t recurse
;
1394 /* Default to unknown. Old clients won't send depth, but we'll
1395 handle that by converting recurse if necessary. */
1396 svn_depth_t depth
= svn_depth_unknown
;
1398 /* Parse the arguments. */
1399 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "cb?(?r)?w",
1400 &target
, &recurse
, &rev
, &depth_word
));
1401 target
= svn_path_canonicalize(target
, pool
);
1404 depth
= svn_depth_from_word(depth_word
);
1406 depth
= recurse
? svn_depth_infinity
: svn_depth_empty
;
1408 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
1409 if (!SVN_IS_VALID_REVNUM(rev
))
1410 SVN_CMD_ERR(svn_fs_youngest_rev(&rev
, b
->fs
, pool
));
1412 return accept_report(conn
, pool
, b
, rev
, target
, NULL
, FALSE
,
1413 depth
, FALSE
, FALSE
);
1416 static svn_error_t
*diff(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1417 apr_array_header_t
*params
, void *baton
)
1419 server_baton_t
*b
= baton
;
1421 const char *target
, *versus_url
, *versus_path
, *depth_word
;
1422 svn_boolean_t recurse
, ignore_ancestry
;
1423 svn_boolean_t text_deltas
;
1424 /* Default to unknown. Old clients won't send depth, but we'll
1425 handle that by converting recurse if necessary. */
1426 svn_depth_t depth
= svn_depth_unknown
;
1428 /* Parse the arguments. */
1429 if (params
->nelts
== 5)
1431 /* Clients before 1.4 don't send the text_deltas boolean or depth. */
1432 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "(?r)cbbc", &rev
, &target
,
1433 &recurse
, &ignore_ancestry
, &versus_url
));
1439 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "(?r)cbbcb?w",
1440 &rev
, &target
, &recurse
,
1441 &ignore_ancestry
, &versus_url
,
1442 &text_deltas
, &depth_word
));
1444 target
= svn_path_canonicalize(target
, pool
);
1445 versus_url
= svn_path_canonicalize(versus_url
, pool
);
1448 depth
= svn_depth_from_word(depth_word
);
1450 depth
= SVN_DEPTH_INFINITY_OR_FILES(recurse
);
1452 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
1454 if (!SVN_IS_VALID_REVNUM(rev
))
1455 SVN_CMD_ERR(svn_fs_youngest_rev(&rev
, b
->fs
, pool
));
1456 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b
->repos_url
, pool
),
1457 svn_path_uri_decode(versus_url
, pool
),
1460 return accept_report(conn
, pool
, b
, rev
, target
, versus_path
,
1461 text_deltas
, depth
, FALSE
, ignore_ancestry
);
1464 /* Regardless of whether a client's capabilities indicate an
1465 understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
1466 we provide a response.
1468 ASSUMPTION: When performing a 'merge' with two URLs at different
1469 revisions, the client will call this command more than once. */
1470 static svn_error_t
*get_mergeinfo(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1471 apr_array_header_t
*params
, void *baton
)
1473 server_baton_t
*b
= baton
;
1475 apr_array_header_t
*paths
, *canonical_paths
;
1476 svn_mergeinfo_catalog_t mergeinfo
;
1478 apr_hash_index_t
*hi
;
1479 const char *inherit_word
;
1480 svn_mergeinfo_inheritance_t inherit
;
1481 svn_boolean_t include_descendants
;
1482 apr_pool_t
*iterpool
;
1484 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "l(?r)wb", &paths
, &rev
,
1485 &inherit_word
, &include_descendants
));
1486 inherit
= svn_inheritance_from_word(inherit_word
);
1488 /* Canonicalize the paths which mergeinfo has been requested for. */
1489 canonical_paths
= apr_array_make(pool
, paths
->nelts
, sizeof(const char *));
1490 for (i
= 0; i
< paths
->nelts
; i
++)
1492 svn_ra_svn_item_t
*item
= &APR_ARRAY_IDX(paths
, i
, svn_ra_svn_item_t
);
1493 const char *full_path
;
1495 if (item
->kind
!= SVN_RA_SVN_STRING
)
1496 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1497 _("Path is not a string"));
1498 full_path
= svn_path_join(b
->fs_path
->data
,
1499 svn_path_canonicalize(item
->u
.string
->data
,
1502 APR_ARRAY_PUSH(canonical_paths
, const char *) = full_path
;
1505 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
1506 SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo
, b
->repos
,
1507 canonical_paths
, rev
,
1509 include_descendants
,
1510 authz_check_access_cb_func(b
), b
,
1512 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo
, mergeinfo
,
1513 b
->fs_path
->data
, pool
));
1514 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((!", "success"));
1515 iterpool
= svn_pool_create(pool
);
1516 for (hi
= apr_hash_first(pool
, mergeinfo
); hi
; hi
= apr_hash_next(hi
))
1520 svn_string_t
*mergeinfo_string
;
1522 svn_pool_clear(iterpool
);
1524 apr_hash_this(hi
, &key
, NULL
, &value
);
1525 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string
,
1526 (svn_mergeinfo_t
) value
,
1528 SVN_ERR(svn_ra_svn_write_tuple(conn
, iterpool
, "cs", (const char *) key
,
1531 svn_pool_destroy(iterpool
);
1532 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1534 return SVN_NO_ERROR
;
1537 /* Send a log entry to the client. */
1538 static svn_error_t
*log_receiver(void *baton
,
1539 svn_log_entry_t
*log_entry
,
1542 log_baton_t
*b
= baton
;
1543 svn_ra_svn_conn_t
*conn
= b
->conn
;
1544 apr_hash_index_t
*h
;
1548 svn_log_changed_path_t
*change
;
1549 svn_boolean_t invalid_revnum
= FALSE
;
1551 const char *author
, *date
, *message
;
1552 apr_uint64_t revprop_count
;
1554 if (log_entry
->revision
== SVN_INVALID_REVNUM
)
1556 /* If the stack depth is zero, we've seen the last revision, so don't
1557 send it, just return. */
1558 if (b
->stack_depth
== 0)
1559 return SVN_NO_ERROR
;
1561 /* Because the svn protocol won't let us send an invalid revnum, we have
1562 to fudge here and send an additional flag. */
1563 log_entry
->revision
= 0;
1564 invalid_revnum
= TRUE
;
1568 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "(!"));
1569 if (log_entry
->changed_paths
)
1571 for (h
= apr_hash_first(pool
, log_entry
->changed_paths
); h
;
1572 h
= apr_hash_next(h
))
1574 apr_hash_this(h
, &key
, NULL
, &val
);
1577 action
[0] = change
->action
;
1579 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "cw(?cr)", path
, action
,
1580 change
->copyfrom_path
,
1581 change
->copyfrom_rev
));
1584 svn_compat_log_revprops_out(&author
, &date
, &message
, log_entry
->revprops
);
1585 svn_compat_log_revprops_clear(log_entry
->revprops
);
1586 if (log_entry
->revprops
)
1587 revprop_count
= apr_hash_count(log_entry
->revprops
);
1590 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!)r(?c)(?c)(?c)bbn(!",
1591 log_entry
->revision
,
1592 author
, date
, message
,
1593 log_entry
->has_children
,
1594 invalid_revnum
, revprop_count
));
1595 SVN_ERR(svn_ra_svn_write_proplist(conn
, pool
, log_entry
->revprops
));
1596 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!)"));
1598 if (log_entry
->has_children
)
1601 return SVN_NO_ERROR
;
1604 static svn_error_t
*log_cmd(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1605 apr_array_header_t
*params
, void *baton
)
1607 svn_error_t
*err
, *write_err
;
1608 server_baton_t
*b
= baton
;
1609 svn_revnum_t start_rev
, end_rev
;
1610 const char *full_path
;
1611 svn_boolean_t changed_paths
, strict_node
, include_merged_revisions
;
1612 apr_array_header_t
*paths
, *full_paths
, *revprop_items
, *revprops
;
1614 svn_ra_svn_item_t
*elt
;
1616 apr_uint64_t limit
, include_merged_revs_param
;
1619 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "l(?r)(?r)bb?n?Bwl", &paths
,
1620 &start_rev
, &end_rev
, &changed_paths
,
1621 &strict_node
, &limit
,
1622 &include_merged_revs_param
,
1623 &revprop_word
, &revprop_items
));
1625 if (include_merged_revs_param
== SVN_RA_SVN_UNSPECIFIED_NUMBER
)
1626 include_merged_revisions
= FALSE
;
1628 include_merged_revisions
= (svn_boolean_t
) include_merged_revs_param
;
1630 if (revprop_word
== NULL
)
1631 /* pre-1.5 client */
1632 revprops
= svn_compat_log_revprops_in(pool
);
1633 else if (strcmp(revprop_word
, "all-revprops") == 0)
1635 else if (strcmp(revprop_word
, "revprops") == 0)
1637 revprops
= apr_array_make(pool
, revprop_items
->nelts
,
1641 for (i
= 0; i
< revprop_items
->nelts
; i
++)
1643 elt
= &APR_ARRAY_IDX(revprop_items
, i
, svn_ra_svn_item_t
);
1644 if (elt
->kind
!= SVN_RA_SVN_STRING
)
1645 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1646 _("Log revprop entry not a string"));
1647 APR_ARRAY_PUSH(revprops
, const char *) = elt
->u
.string
->data
;
1652 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1653 _("Unknown revprop word '%s' in log command"),
1656 /* If we got an unspecified number then the user didn't send us anything,
1657 so we assume no limit. If it's larger than INT_MAX then someone is
1658 messing with us, since we know the svn client libraries will never send
1659 us anything that big, so play it safe and default to no limit. */
1660 if (limit
== SVN_RA_SVN_UNSPECIFIED_NUMBER
|| limit
> INT_MAX
)
1663 full_paths
= apr_array_make(pool
, paths
->nelts
, sizeof(const char *));
1664 for (i
= 0; i
< paths
->nelts
; i
++)
1666 elt
= &APR_ARRAY_IDX(paths
, i
, svn_ra_svn_item_t
);
1667 if (elt
->kind
!= SVN_RA_SVN_STRING
)
1668 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1669 _("Log path entry not a string"));
1670 full_path
= svn_path_join(b
->fs_path
->data
,
1671 svn_path_canonicalize(elt
->u
.string
->data
,
1674 APR_ARRAY_PUSH(full_paths
, const char *) = full_path
;
1676 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
1678 /* Get logs. (Can't report errors back to the client at this point.) */
1679 lb
.fs_path
= b
->fs_path
->data
;
1682 err
= svn_repos_get_logs4(b
->repos
, full_paths
, start_rev
, end_rev
,
1683 (int) limit
, changed_paths
, strict_node
,
1684 include_merged_revisions
, revprops
,
1685 authz_check_access_cb_func(b
), b
, log_receiver
,
1688 write_err
= svn_ra_svn_write_word(conn
, pool
, "done");
1691 svn_error_clear(err
);
1695 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
1696 return SVN_NO_ERROR
;
1699 static svn_error_t
*check_path(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1700 apr_array_header_t
*params
, void *baton
)
1702 server_baton_t
*b
= baton
;
1704 const char *path
, *full_path
;
1705 svn_fs_root_t
*root
;
1706 svn_node_kind_t kind
;
1708 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c(?r)", &path
, &rev
));
1709 full_path
= svn_path_join(b
->fs_path
->data
,
1710 svn_path_canonicalize(path
, pool
), pool
);
1712 /* Check authorizations */
1713 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_read
,
1716 if (!SVN_IS_VALID_REVNUM(rev
))
1717 SVN_CMD_ERR(svn_fs_youngest_rev(&rev
, b
->fs
, pool
));
1719 SVN_CMD_ERR(svn_fs_revision_root(&root
, b
->fs
, rev
, pool
));
1720 SVN_CMD_ERR(svn_fs_check_path(&kind
, root
, full_path
, pool
));
1721 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, "w", kind_word(kind
)));
1722 return SVN_NO_ERROR
;
1725 static svn_error_t
*stat(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1726 apr_array_header_t
*params
, void *baton
)
1728 server_baton_t
*b
= baton
;
1730 const char *path
, *full_path
, *cdate
;
1731 svn_fs_root_t
*root
;
1732 svn_dirent_t
*dirent
;
1734 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c(?r)", &path
, &rev
));
1735 full_path
= svn_path_join(b
->fs_path
->data
,
1736 svn_path_canonicalize(path
, pool
), pool
);
1738 /* Check authorizations */
1739 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_read
,
1742 if (!SVN_IS_VALID_REVNUM(rev
))
1743 SVN_CMD_ERR(svn_fs_youngest_rev(&rev
, b
->fs
, pool
));
1744 SVN_CMD_ERR(svn_fs_revision_root(&root
, b
->fs
, rev
, pool
));
1745 SVN_CMD_ERR(svn_repos_stat(&dirent
, root
, full_path
, pool
));
1747 /* Need to return the equivalent of "(?l)", since that's what the
1748 client is reading. */
1752 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, "()"));
1753 return SVN_NO_ERROR
;
1756 cdate
= (dirent
->time
== (time_t) -1) ? NULL
1757 : svn_time_to_cstring(dirent
->time
, pool
);
1759 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, "((wnbr(?c)(?c)))",
1760 kind_word(dirent
->kind
),
1761 (apr_uint64_t
) dirent
->size
,
1762 dirent
->has_props
, dirent
->created_rev
,
1763 cdate
, dirent
->last_author
));
1765 return SVN_NO_ERROR
;
1768 static svn_error_t
*get_locations(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1769 apr_array_header_t
*params
, void *baton
)
1771 svn_error_t
*err
, *write_err
;
1772 server_baton_t
*b
= baton
;
1773 svn_revnum_t revision
;
1774 apr_array_header_t
*location_revisions
, *loc_revs_proto
;
1775 svn_ra_svn_item_t
*elt
;
1777 const char *relative_path
;
1778 svn_revnum_t peg_revision
;
1779 apr_hash_t
*fs_locations
;
1780 apr_hash_index_t
*iter
;
1781 const char *abs_path
;
1782 const void *iter_key
;
1785 /* Parse the arguments. */
1786 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "crl", &relative_path
,
1789 relative_path
= svn_path_canonicalize(relative_path
, pool
);
1791 abs_path
= svn_path_join(b
->fs_path
->data
, relative_path
, pool
);
1793 location_revisions
= apr_array_make(pool
, loc_revs_proto
->nelts
,
1794 sizeof(svn_revnum_t
));
1795 for (i
= 0; i
< loc_revs_proto
->nelts
; i
++)
1797 elt
= &APR_ARRAY_IDX(loc_revs_proto
, i
, svn_ra_svn_item_t
);
1798 if (elt
->kind
!= SVN_RA_SVN_NUMBER
)
1799 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1800 "Get-locations location revisions entry "
1801 "not a revision number");
1802 revision
= (svn_revnum_t
)(elt
->u
.number
);
1803 APR_ARRAY_PUSH(location_revisions
, svn_revnum_t
) = revision
;
1805 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
1807 /* All the parameters are fine - let's perform the query against the
1810 /* We store both err and write_err here, so the client will get
1811 * the "done" even if there was an error in fetching the results. */
1813 err
= svn_repos_trace_node_locations(b
->fs
, &fs_locations
, abs_path
,
1814 peg_revision
, location_revisions
,
1815 authz_check_access_cb_func(b
), b
, pool
);
1817 /* Now, write the results to the connection. */
1822 for (iter
= apr_hash_first(pool
, fs_locations
); iter
;
1823 iter
= apr_hash_next(iter
))
1825 apr_hash_this(iter
, &iter_key
, NULL
, &iter_value
);
1826 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "rc",
1827 *(const svn_revnum_t
*)iter_key
,
1828 (const char *)iter_value
));
1833 write_err
= svn_ra_svn_write_word(conn
, pool
, "done");
1836 svn_error_clear(err
);
1841 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
1843 return SVN_NO_ERROR
;
1846 static svn_error_t
*gls_receiver(svn_location_segment_t
*segment
,
1850 svn_ra_svn_conn_t
*conn
= baton
;
1851 return svn_ra_svn_write_tuple(conn
, pool
, "rr(?c)",
1852 segment
->range_start
,
1857 static svn_error_t
*get_location_segments(svn_ra_svn_conn_t
*conn
,
1859 apr_array_header_t
*params
,
1862 svn_error_t
*err
, *write_err
;
1863 server_baton_t
*b
= baton
;
1864 svn_revnum_t peg_revision
, start_rev
, end_rev
;
1865 const char *relative_path
;
1866 const char *abs_path
;
1868 /* Parse the arguments. */
1869 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c(?r)(?r)(?r)",
1870 &relative_path
, &peg_revision
,
1871 &start_rev
, &end_rev
));
1872 relative_path
= svn_path_canonicalize(relative_path
, pool
);
1874 abs_path
= svn_path_join(b
->fs_path
->data
, relative_path
, pool
);
1876 if (SVN_IS_VALID_REVNUM(start_rev
)
1877 && SVN_IS_VALID_REVNUM(end_rev
)
1878 && (end_rev
> start_rev
))
1879 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS
, NULL
,
1880 "Get-location-segments end revision must not be "
1881 "younger than start revision");
1883 if (SVN_IS_VALID_REVNUM(peg_revision
)
1884 && SVN_IS_VALID_REVNUM(start_rev
)
1885 && (start_rev
> peg_revision
))
1886 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS
, NULL
,
1887 "Get-location-segments start revision must not "
1888 "be younger than peg revision");
1890 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
1892 /* All the parameters are fine - let's perform the query against the
1895 /* We store both err and write_err here, so the client will get
1896 * the "done" even if there was an error in fetching the results. */
1898 err
= svn_repos_node_location_segments(b
->repos
, abs_path
,
1899 peg_revision
, start_rev
, end_rev
,
1900 gls_receiver
, (void *)conn
,
1901 authz_check_access_cb_func(b
), b
,
1903 write_err
= svn_ra_svn_write_word(conn
, pool
, "done");
1906 svn_error_clear(err
);
1911 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
1913 return SVN_NO_ERROR
;
1916 /* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the
1917 client as a string. */
1918 static svn_error_t
*svndiff_handler(void *baton
, const char *data
,
1921 file_revs_baton_t
*b
= baton
;
1926 return svn_ra_svn_write_string(b
->conn
, b
->pool
, &str
);
1929 /* This implements svn_close_fn_t. Mark the end of the data by writing an
1930 empty string to the client. */
1931 static svn_error_t
*svndiff_close_handler(void *baton
)
1933 file_revs_baton_t
*b
= baton
;
1935 SVN_ERR(svn_ra_svn_write_cstring(b
->conn
, b
->pool
, ""));
1936 return SVN_NO_ERROR
;
1939 /* This implements the svn_repos_file_rev_handler_t interface. */
1940 static svn_error_t
*file_rev_handler(void *baton
, const char *path
,
1941 svn_revnum_t rev
, apr_hash_t
*rev_props
,
1942 svn_boolean_t merged_revision
,
1943 svn_txdelta_window_handler_t
*d_handler
,
1945 apr_array_header_t
*prop_diffs
,
1948 file_revs_baton_t
*frb
= baton
;
1949 svn_stream_t
*stream
;
1951 SVN_ERR(svn_ra_svn_write_tuple(frb
->conn
, pool
, "cr(!",
1953 SVN_ERR(svn_ra_svn_write_proplist(frb
->conn
, pool
, rev_props
));
1954 SVN_ERR(svn_ra_svn_write_tuple(frb
->conn
, pool
, "!)(!"));
1955 SVN_ERR(write_prop_diffs(frb
->conn
, pool
, prop_diffs
));
1956 SVN_ERR(svn_ra_svn_write_tuple(frb
->conn
, pool
, "!)b", merged_revision
));
1958 /* Store the pool for the delta stream. */
1961 /* Prepare for the delta or just write an empty string. */
1964 stream
= svn_stream_create(baton
, pool
);
1965 svn_stream_set_write(stream
, svndiff_handler
);
1966 svn_stream_set_close(stream
, svndiff_close_handler
);
1968 if (svn_ra_svn_has_capability(frb
->conn
, SVN_RA_SVN_CAP_SVNDIFF1
))
1969 svn_txdelta_to_svndiff2(d_handler
, d_baton
, stream
, 1, pool
);
1971 svn_txdelta_to_svndiff2(d_handler
, d_baton
, stream
, 0, pool
);
1974 SVN_ERR(svn_ra_svn_write_cstring(frb
->conn
, pool
, ""));
1976 return SVN_NO_ERROR
;
1979 static svn_error_t
*get_file_revs(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
1980 apr_array_header_t
*params
, void *baton
)
1982 server_baton_t
*b
= baton
;
1983 svn_error_t
*err
, *write_err
;
1984 file_revs_baton_t frb
;
1985 svn_revnum_t start_rev
, end_rev
;
1987 const char *full_path
;
1988 apr_uint64_t include_merged_revs_param
;
1989 svn_boolean_t include_merged_revisions
;
1991 /* Parse arguments. */
1992 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c(?r)(?r)?B",
1993 &path
, &start_rev
, &end_rev
,
1994 &include_merged_revs_param
));
1995 path
= svn_path_canonicalize(path
, pool
);
1996 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
1997 full_path
= svn_path_join(b
->fs_path
->data
, path
, pool
);
1999 if (include_merged_revs_param
== SVN_RA_SVN_UNSPECIFIED_NUMBER
)
2000 include_merged_revisions
= FALSE
;
2002 include_merged_revisions
= (svn_boolean_t
) include_merged_revs_param
;
2007 err
= svn_repos_get_file_revs2(b
->repos
, full_path
, start_rev
, end_rev
,
2008 include_merged_revisions
,
2009 authz_check_access_cb_func(b
), b
,
2010 file_rev_handler
, &frb
, pool
);
2011 write_err
= svn_ra_svn_write_word(conn
, pool
, "done");
2014 svn_error_clear(err
);
2018 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
2020 return SVN_NO_ERROR
;
2023 static svn_error_t
*lock(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
2024 apr_array_header_t
*params
, void *baton
)
2026 server_baton_t
*b
= baton
;
2028 const char *comment
;
2029 const char *full_path
;
2030 svn_boolean_t steal_lock
;
2031 svn_revnum_t current_rev
;
2034 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c(?c)b(?r)", &path
, &comment
,
2035 &steal_lock
, ¤t_rev
));
2036 full_path
= svn_path_join(b
->fs_path
->data
,
2037 svn_path_canonicalize(path
, pool
), pool
);
2039 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_write
,
2042 SVN_CMD_ERR(svn_repos_fs_lock(&l
, b
->repos
, full_path
, NULL
, comment
, 0,
2043 0, /* No expiration time. */
2044 current_rev
, steal_lock
, pool
));
2046 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(!", "success"));
2047 SVN_ERR(write_lock(conn
, pool
, l
));
2048 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!)"));
2050 return SVN_NO_ERROR
;
2053 static svn_error_t
*lock_many(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
2054 apr_array_header_t
*params
, void *baton
)
2056 server_baton_t
*b
= baton
;
2057 apr_array_header_t
*path_revs
;
2058 const char *comment
;
2059 svn_boolean_t steal_lock
;
2061 apr_pool_t
*subpool
;
2063 const char *full_path
;
2064 svn_revnum_t current_rev
;
2066 svn_error_t
*err
= SVN_NO_ERROR
, *write_err
;
2068 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "(?c)bl", &comment
, &steal_lock
,
2071 subpool
= svn_pool_create(pool
);
2073 /* Because we can only send a single auth reply per request, we send
2074 a reply before parsing the lock commands. This means an authz
2075 access denial will abort the processing of the locks and return
2077 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_write
, NULL
, TRUE
));
2079 /* Loop through the lock requests. */
2080 for (i
= 0; i
< path_revs
->nelts
; ++i
)
2082 svn_ra_svn_item_t
*item
= &APR_ARRAY_IDX(path_revs
, i
,
2085 svn_pool_clear(subpool
);
2087 if (item
->kind
!= SVN_RA_SVN_LIST
)
2088 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2089 "Lock requests should be list of lists");
2091 SVN_ERR(svn_ra_svn_parse_tuple(item
->u
.list
, pool
, "c(?r)", &path
,
2094 full_path
= svn_path_join(b
->fs_path
->data
,
2095 svn_path_canonicalize(path
, subpool
),
2098 if (! lookup_access(pool
, b
, svn_authz_write
, full_path
, TRUE
))
2100 err
= svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
,
2105 err
= svn_repos_fs_lock(&l
, b
->repos
, full_path
,
2106 NULL
, comment
, FALSE
,
2107 0, /* No expiration time. */
2109 steal_lock
, subpool
);
2113 if (SVN_ERR_IS_LOCK_ERROR(err
))
2115 write_err
= svn_ra_svn_write_cmd_failure(conn
, pool
, err
);
2116 svn_error_clear(err
);
2125 SVN_ERR(svn_ra_svn_write_tuple(conn
, subpool
, "w!", "success"));
2126 SVN_ERR(write_lock(conn
, subpool
, l
));
2127 SVN_ERR(svn_ra_svn_write_tuple(conn
, subpool
, "!"));
2131 svn_pool_destroy(subpool
);
2133 /* NOTE: err might contain a fatal locking error from the loop above. */
2134 write_err
= svn_ra_svn_write_word(conn
, pool
, "done");
2137 svn_error_clear(err
);
2139 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
2141 return SVN_NO_ERROR
;
2144 static svn_error_t
*unlock(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
2145 apr_array_header_t
*params
, void *baton
)
2147 server_baton_t
*b
= baton
;
2148 const char *path
, *token
, *full_path
;
2149 svn_boolean_t break_lock
;
2151 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c(?c)b", &path
, &token
,
2154 full_path
= svn_path_join(b
->fs_path
->data
, svn_path_canonicalize(path
, pool
),
2157 /* Username required unless break_lock was specified. */
2158 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_write
,
2159 full_path
, ! break_lock
));
2161 SVN_CMD_ERR(svn_repos_fs_unlock(b
->repos
, full_path
, token
, break_lock
,
2164 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
2166 return SVN_NO_ERROR
;
2169 static svn_error_t
*unlock_many(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
2170 apr_array_header_t
*params
, void *baton
)
2172 server_baton_t
*b
= baton
;
2173 svn_boolean_t break_lock
;
2174 apr_array_header_t
*unlock_tokens
;
2176 apr_pool_t
*subpool
;
2178 const char *full_path
;
2180 svn_error_t
*err
= SVN_NO_ERROR
, *write_err
;
2182 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "bl", &break_lock
,
2185 /* Username required unless break_lock was specified. */
2186 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_write
, NULL
, ! break_lock
));
2188 subpool
= svn_pool_create(pool
);
2190 /* Loop through the unlock requests. */
2191 for (i
= 0; i
< unlock_tokens
->nelts
; i
++)
2193 svn_ra_svn_item_t
*item
= &APR_ARRAY_IDX(unlock_tokens
, i
,
2196 svn_pool_clear(subpool
);
2198 if (item
->kind
!= SVN_RA_SVN_LIST
)
2199 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2200 "Unlock request should be a list of lists");
2202 SVN_ERR(svn_ra_svn_parse_tuple(item
->u
.list
, subpool
, "c(?c)", &path
,
2205 full_path
= svn_path_join(b
->fs_path
->data
,
2206 svn_path_canonicalize(path
, subpool
),
2209 if (! lookup_access(subpool
, b
, svn_authz_write
, full_path
,
2211 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR
,
2212 svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
,
2215 err
= svn_repos_fs_unlock(b
->repos
, full_path
, token
, break_lock
,
2219 if (SVN_ERR_IS_UNLOCK_ERROR(err
))
2221 write_err
= svn_ra_svn_write_cmd_failure(conn
, pool
, err
);
2222 svn_error_clear(err
);
2230 SVN_ERR(svn_ra_svn_write_tuple(conn
, subpool
, "w(c)", "success",
2234 svn_pool_destroy(subpool
);
2236 /* NOTE: err might contain a fatal unlocking error from the loop above. */
2237 write_err
= svn_ra_svn_write_word(conn
, pool
, "done");
2240 svn_error_clear(err
);
2241 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
2243 return SVN_NO_ERROR
;
2246 static svn_error_t
*get_lock(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
2247 apr_array_header_t
*params
, void *baton
)
2249 server_baton_t
*b
= baton
;
2251 const char *full_path
;
2254 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c", &path
));
2256 full_path
= svn_path_join(b
->fs_path
->data
, svn_path_canonicalize(path
,
2260 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_read
,
2263 SVN_CMD_ERR(svn_fs_get_lock(&l
, b
->fs
, full_path
, pool
));
2265 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((!", "success"));
2267 SVN_ERR(write_lock(conn
, pool
, l
));
2268 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
2270 return SVN_NO_ERROR
;
2273 static svn_error_t
*get_locks(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
2274 apr_array_header_t
*params
, void *baton
)
2276 server_baton_t
*b
= baton
;
2278 const char *full_path
;
2280 apr_hash_index_t
*hi
;
2284 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c", &path
));
2286 full_path
= svn_path_join(b
->fs_path
->data
, svn_path_canonicalize(path
,
2290 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
2292 SVN_CMD_ERR(svn_repos_fs_get_locks(&locks
, b
->repos
, full_path
,
2293 authz_check_access_cb_func(b
), b
, pool
));
2295 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((!", "success"));
2296 for (hi
= apr_hash_first(pool
, locks
); hi
; hi
= apr_hash_next(hi
))
2298 apr_hash_this(hi
, NULL
, NULL
, &val
);
2300 SVN_ERR(write_lock(conn
, pool
, l
));
2302 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
2304 return SVN_NO_ERROR
;
2307 static svn_error_t
*replay_one_revision(svn_ra_svn_conn_t
*conn
,
2310 svn_revnum_t low_water_mark
,
2311 svn_boolean_t send_deltas
,
2314 const svn_delta_editor_t
*editor
;
2316 svn_fs_root_t
*root
;
2319 svn_ra_svn_get_editor(&editor
, &edit_baton
, conn
, pool
, NULL
, NULL
);
2321 err
= svn_fs_revision_root(&root
, b
->fs
, rev
, pool
);
2324 err
= svn_repos_replay2(root
, b
->fs_path
->data
, low_water_mark
,
2325 send_deltas
, editor
, edit_baton
,
2326 authz_check_access_cb_func(b
), b
, pool
);
2329 svn_error_clear(editor
->abort_edit(edit_baton
, pool
));
2332 return svn_ra_svn_write_cmd(conn
, pool
, "finish-replay", "");
2335 static svn_error_t
*replay(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
2336 apr_array_header_t
*params
, void *baton
)
2338 svn_revnum_t rev
, low_water_mark
;
2339 svn_boolean_t send_deltas
;
2340 server_baton_t
*b
= baton
;
2342 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "rrb", &rev
, &low_water_mark
,
2345 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
2347 SVN_ERR(replay_one_revision(conn
, b
, rev
, low_water_mark
,
2348 send_deltas
, pool
));
2350 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
2352 return SVN_NO_ERROR
;
2355 static svn_error_t
*replay_range(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
2356 apr_array_header_t
*params
, void *baton
)
2358 svn_revnum_t start_rev
, end_rev
, rev
, low_water_mark
;
2359 svn_boolean_t send_deltas
;
2360 server_baton_t
*b
= baton
;
2361 apr_pool_t
*iterpool
;
2363 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "rrrb", &start_rev
,
2364 &end_rev
, &low_water_mark
,
2367 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
2369 iterpool
= svn_pool_create(pool
);
2370 for (rev
= start_rev
; rev
<= end_rev
; rev
++)
2374 svn_pool_clear(iterpool
);
2376 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props
, b
->repos
, rev
,
2377 authz_check_access_cb_func(b
),
2380 SVN_ERR(svn_ra_svn_write_tuple(conn
, iterpool
, "w(!", "revprops"));
2381 SVN_ERR(svn_ra_svn_write_proplist(conn
, iterpool
, props
));
2382 SVN_ERR(svn_ra_svn_write_tuple(conn
, iterpool
, "!)"));
2384 SVN_ERR(replay_one_revision(conn
, b
, rev
, low_water_mark
,
2385 send_deltas
, iterpool
));
2388 svn_pool_destroy(iterpool
);
2390 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
2392 return SVN_NO_ERROR
;
2396 static const svn_ra_svn_cmd_entry_t main_commands
[] = {
2397 { "reparent", reparent
},
2398 { "get-latest-rev", get_latest_rev
},
2399 { "get-dated-rev", get_dated_rev
},
2400 { "change-rev-prop", change_rev_prop
},
2401 { "rev-proplist", rev_proplist
},
2402 { "rev-prop", rev_prop
},
2403 { "commit", commit
},
2404 { "get-file", get_file
},
2405 { "get-dir", get_dir
},
2406 { "update", update
},
2407 { "switch", switch_cmd
},
2408 { "status", status
},
2410 { "get-mergeinfo", get_mergeinfo
},
2412 { "check-path", check_path
},
2414 { "get-locations", get_locations
},
2415 { "get-location-segments", get_location_segments
},
2416 { "get-file-revs", get_file_revs
},
2418 { "lock-many", lock_many
},
2419 { "unlock", unlock
},
2420 { "unlock-many", unlock_many
},
2421 { "get-lock", get_lock
},
2422 { "get-locks", get_locks
},
2423 { "replay", replay
},
2424 { "replay-range", replay_range
},
2428 /* Skip past the scheme part of a URL, including the tunnel specification
2429 * if present. Return NULL if the scheme part is invalid for ra_svn. */
2430 static const char *skip_scheme_part(const char *url
)
2432 if (strncmp(url
, "svn", 3) != 0)
2436 url
+= strcspn(url
, ":");
2437 if (strncmp(url
, "://", 3) != 0)
2442 /* Check that PATH is a valid repository path, meaning it doesn't contain any
2444 NOTE: This is similar to svn_path_is_backpath_present, but that function
2445 assumes the path separator is '/'. This function also checks for
2446 segments delimited by the local path separator. */
2447 static svn_boolean_t
2448 repos_path_valid(const char *path
)
2450 const char *s
= path
;
2454 /* Scan for the end of the segment. */
2455 while (*path
&& *path
!= '/' && *path
!= SVN_PATH_LOCAL_SEPARATOR
)
2458 /* Check for '..'. */
2460 /* On Windows, don't allow sequences of more than one character
2461 consisting of just dots and spaces. Win32 functions treat
2462 paths such as ".. " and "......." inconsistently. Make sure
2463 no one can escape out of the root. */
2464 if (path
- s
>= 2 && strspn(s
, ". ") == path
- s
)
2467 if (path
- s
== 2 && s
[0] == '.' && s
[1] == '.')
2471 /* Skip all separators. */
2472 while (*path
&& (*path
== '/' || *path
== SVN_PATH_LOCAL_SEPARATOR
))
2480 /* Look for the repository given by URL, using ROOT as the virtual
2481 * repository root. If we find one, fill in the repos, fs, cfg,
2482 * repos_url, and fs_path fields of B. Set B->repos's client
2483 * capabilities to CAPABILITIES, which must be at least as long-lived
2484 * as POOL, and whose elements are SVN_RA_CAPABILITY_*.
2486 static svn_error_t
*find_repos(const char *url
, const char *root
,
2488 apr_array_header_t
*capabilities
,
2491 const char *path
, *full_path
, *repos_root
, *fs_path
;
2492 svn_stringbuf_t
*url_buf
;
2494 /* Skip past the scheme and authority part. */
2495 path
= skip_scheme_part(url
);
2497 return svn_error_createf(SVN_ERR_BAD_URL
, NULL
,
2498 "Non-svn URL passed to svn server: '%s'", url
);
2499 path
= strchr(path
, '/');
2500 path
= (path
== NULL
) ? "" : path
+ 1;
2502 /* Decode URI escapes from the path. */
2503 path
= svn_path_uri_decode(path
, pool
);
2505 /* Ensure that it isn't possible to escape the root by skipping leading
2506 slashes and not allowing '..' segments. */
2507 while (*path
== '/')
2509 if (!repos_path_valid(path
))
2510 return svn_error_create(SVN_ERR_BAD_FILENAME
, NULL
,
2511 "Couldn't determine repository path");
2513 /* Join the server-configured root with the client path. */
2514 full_path
= svn_path_join(svn_path_canonicalize(root
, pool
),
2515 svn_path_canonicalize(path
, pool
), pool
);
2517 /* Search for a repository in the full path. */
2518 repos_root
= svn_repos_find_root_path(full_path
, pool
);
2520 return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND
, NULL
,
2521 "No repository found in '%s'", url
);
2523 /* Open the repository and fill in b with the resulting information. */
2524 SVN_ERR(svn_repos_open(&b
->repos
, repos_root
, pool
));
2525 SVN_ERR(svn_repos_remember_client_capabilities(b
->repos
, capabilities
));
2526 b
->fs
= svn_repos_fs(b
->repos
);
2527 fs_path
= full_path
+ strlen(repos_root
);
2528 b
->fs_path
= svn_stringbuf_create(*fs_path
? fs_path
: "/", pool
);
2529 url_buf
= svn_stringbuf_create(url
, pool
);
2530 svn_path_remove_components(url_buf
,
2531 svn_path_component_count(b
->fs_path
->data
));
2532 b
->repos_url
= url_buf
->data
;
2533 b
->authz_repos_name
= svn_path_is_child(root
, repos_root
, pool
);
2535 /* If the svnserve configuration files have not been loaded then
2536 load them from the repository. */
2538 SVN_ERR(load_configs(&b
->cfg
, &b
->pwdb
, &b
->authzdb
,
2539 svn_repos_svnserve_conf(b
->repos
, pool
), FALSE
,
2540 svn_repos_conf_dir(b
->repos
, pool
),
2543 #ifdef SVN_HAVE_SASL
2544 /* Should we use Cyrus SASL? */
2545 svn_config_get_bool(b
->cfg
, &b
->use_sasl
, SVN_CONFIG_SECTION_SASL
,
2546 SVN_CONFIG_OPTION_USE_SASL
, FALSE
);
2549 /* Use the repository UUID as the default realm. */
2550 SVN_ERR(svn_fs_get_uuid(b
->fs
, &b
->realm
, pool
));
2551 svn_config_get(b
->cfg
, &b
->realm
, SVN_CONFIG_SECTION_GENERAL
,
2552 SVN_CONFIG_OPTION_REALM
, b
->realm
);
2554 /* Make sure it's possible for the client to authenticate. Note
2555 that this doesn't take into account any authz configuration read
2556 above, because we can't know about access it grants until paths
2557 are given by the client. */
2558 if (get_access(b
, UNAUTHENTICATED
) == NO_ACCESS
2559 && (get_access(b
, AUTHENTICATED
) == NO_ACCESS
2560 || (!b
->tunnel_user
&& !b
->pwdb
&& !b
->use_sasl
)))
2561 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
, NULL
,
2562 "No access allowed to this repository");
2563 return SVN_NO_ERROR
;
2566 /* Compute the authentication name EXTERNAL should be able to get, if any. */
2567 static const char *get_tunnel_user(serve_params_t
*params
, apr_pool_t
*pool
)
2569 /* Only offer EXTERNAL for connections tunneled over a login agent. */
2570 if (!params
->tunnel
)
2573 /* If a tunnel user was provided on the command line, use that. */
2574 if (params
->tunnel_user
)
2575 return params
->tunnel_user
;
2577 return svn_user_get_name(pool
);
2580 svn_error_t
*serve(svn_ra_svn_conn_t
*conn
, serve_params_t
*params
,
2583 svn_error_t
*err
, *io_err
;
2585 const char *uuid
, *client_url
;
2586 apr_array_header_t
*caplist
, *cap_words
;
2589 b
.tunnel
= params
->tunnel
;
2590 b
.tunnel_user
= get_tunnel_user(params
, pool
);
2591 b
.read_only
= params
->read_only
;
2593 b
.cfg
= params
->cfg
; /* Ugly; can drop when we remove v1 support. */
2594 b
.pwdb
= params
->pwdb
; /* Likewise. */
2595 b
.authzdb
= params
->authzdb
;
2600 /* Send greeting. We don't support version 1 any more, so we can
2601 * send an empty mechlist. */
2602 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, "nn()(wwwwwww)",
2603 (apr_uint64_t
) 2, (apr_uint64_t
) 2,
2604 SVN_RA_SVN_CAP_EDIT_PIPELINE
,
2605 SVN_RA_SVN_CAP_SVNDIFF1
,
2606 SVN_RA_SVN_CAP_ABSENT_ENTRIES
,
2607 SVN_RA_SVN_CAP_COMMIT_REVPROPS
,
2608 SVN_RA_SVN_CAP_DEPTH
,
2609 SVN_RA_SVN_CAP_LOG_REVPROPS
,
2610 SVN_RA_SVN_CAP_PARTIAL_REPLAY
));
2612 /* Read client response, which we assume to be in version 2 format:
2613 * version, capability list, and client URL; then we do an auth
2615 SVN_ERR(svn_ra_svn_read_tuple(conn
, pool
, "nlc",
2616 &ver
, &caplist
, &client_url
));
2618 return SVN_NO_ERROR
;
2620 client_url
= svn_path_canonicalize(client_url
, pool
);
2621 SVN_ERR(svn_ra_svn_set_capabilities(conn
, caplist
));
2623 /* All released versions of Subversion support edit-pipeline,
2624 * so we do not accept connections from clients that do not. */
2625 if (! svn_ra_svn_has_capability(conn
, SVN_RA_SVN_CAP_EDIT_PIPELINE
))
2626 return SVN_NO_ERROR
;
2628 /* find_repos needs the capabilities as a list of words (eventually
2629 they get handed to the start-commit hook). While we could add a
2630 new interface to re-retrieve them from conn and convert the
2631 result to a list, it's simpler to just convert caplist by hand
2632 here, since we already have it and turning 'svn_ra_svn_item_t's
2633 into 'const char *'s is pretty easy.
2635 We only record capabilities we care about. The client may report
2636 more (because it doesn't know what the server cares about). */
2639 svn_ra_svn_item_t
*item
;
2641 cap_words
= apr_array_make(pool
, 1, sizeof(const char *));
2642 for (i
= 0; i
< caplist
->nelts
; i
++)
2644 item
= &APR_ARRAY_IDX(caplist
, i
, svn_ra_svn_item_t
);
2645 /* ra_svn_set_capabilities() already type-checked for us */
2646 if (strcmp(item
->u
.word
, SVN_RA_SVN_CAP_MERGEINFO
) == 0)
2648 APR_ARRAY_PUSH(cap_words
, const char *)
2649 = SVN_RA_CAPABILITY_MERGEINFO
;
2654 err
= find_repos(client_url
, params
->root
, &b
, cap_words
, pool
);
2657 SVN_ERR(auth_request(conn
, pool
, &b
, READ_ACCESS
, FALSE
));
2658 if (current_access(&b
) == NO_ACCESS
)
2659 err
= svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
, NULL
,
2660 "Not authorized for access");
2664 io_err
= svn_ra_svn_write_cmd_failure(conn
, pool
, err
);
2665 svn_error_clear(err
);
2667 return svn_ra_svn_flush(conn
, pool
);
2670 SVN_ERR(svn_fs_get_uuid(b
.fs
, &uuid
, pool
));
2672 /* We can't claim mergeinfo capability until we know whether the
2673 repository supports mergeinfo (i.e., is not a 1.4 repository),
2674 but we don't get the repository url from the client until after
2675 we've already sent the initial list of server capabilities. So
2676 we list repository capabilities here, in our first response after
2677 the client has sent the url. */
2679 svn_boolean_t supports_mergeinfo
;
2680 SVN_ERR(svn_repos_has_capability(b
.repos
, &supports_mergeinfo
,
2681 SVN_REPOS_CAPABILITY_MERGEINFO
, pool
));
2683 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(cc(!",
2684 "success", uuid
, b
.repos_url
));
2685 if (supports_mergeinfo
)
2686 SVN_ERR(svn_ra_svn_write_word(conn
, pool
, SVN_RA_SVN_CAP_MERGEINFO
));
2687 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
2690 return svn_ra_svn_handle_commands(conn
, pool
, main_commands
, &b
);