serve.c revision 251881
1/* 2 * serve.c : Functions for serving the Subversion protocol 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26 27#include <limits.h> /* for UINT_MAX */ 28#include <stdarg.h> 29 30#define APR_WANT_STRFUNC 31#include <apr_want.h> 32#include <apr_general.h> 33#include <apr_lib.h> 34#include <apr_strings.h> 35 36#include "svn_compat.h" 37#include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */ 38#include "svn_hash.h" 39#include "svn_types.h" 40#include "svn_string.h" 41#include "svn_pools.h" 42#include "svn_error.h" 43#include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */ 44#include "svn_ra_svn.h" 45#include "svn_repos.h" 46#include "svn_dirent_uri.h" 47#include "svn_path.h" 48#include "svn_time.h" 49#include "svn_config.h" 50#include "svn_props.h" 51#include "svn_mergeinfo.h" 52#include "svn_user.h" 53 54#include "private/svn_log.h" 55#include "private/svn_mergeinfo_private.h" 56#include "private/svn_ra_svn_private.h" 57#include "private/svn_fspath.h" 58 59#ifdef HAVE_UNISTD_H 60#include <unistd.h> /* For getpid() */ 61#endif 62 63#include "server.h" 64 65typedef struct commit_callback_baton_t { 66 apr_pool_t *pool; 67 svn_revnum_t *new_rev; 68 const char **date; 69 const char **author; 70 const char **post_commit_err; 71} commit_callback_baton_t; 72 73typedef struct report_driver_baton_t { 74 server_baton_t *sb; 75 const char *repos_url; /* Decoded repository URL. */ 76 void *report_baton; 77 svn_error_t *err; 78 /* so update() can distinguish checkout from update in logging */ 79 int entry_counter; 80 svn_boolean_t only_empty_entries; 81 /* for diff() logging */ 82 svn_revnum_t *from_rev; 83} report_driver_baton_t; 84 85typedef struct log_baton_t { 86 const char *fs_path; 87 svn_ra_svn_conn_t *conn; 88 int stack_depth; 89} log_baton_t; 90 91typedef struct file_revs_baton_t { 92 svn_ra_svn_conn_t *conn; 93 apr_pool_t *pool; /* Pool provided in the handler call. */ 94} file_revs_baton_t; 95 96typedef struct fs_warning_baton_t { 97 server_baton_t *server; 98 svn_ra_svn_conn_t *conn; 99 apr_pool_t *pool; 100} fs_warning_baton_t; 101 102typedef struct authz_baton_t { 103 server_baton_t *server; 104 svn_ra_svn_conn_t *conn; 105} authz_baton_t; 106 107/* Write LEN bytes of ERRSTR to LOG_FILE with svn_io_file_write(). */ 108static svn_error_t * 109log_write(apr_file_t *log_file, const char *errstr, apr_size_t len, 110 apr_pool_t *pool) 111{ 112 return svn_io_file_write(log_file, errstr, &len, pool); 113} 114 115void 116log_error(svn_error_t *err, apr_file_t *log_file, const char *remote_host, 117 const char *user, const char *repos, apr_pool_t *pool) 118{ 119 const char *timestr, *continuation; 120 char errbuf[256]; 121 /* 8192 from MAX_STRING_LEN in from httpd-2.2.4/include/httpd.h */ 122 char errstr[8192]; 123 124 if (err == SVN_NO_ERROR) 125 return; 126 127 if (log_file == NULL) 128 return; 129 130 timestr = svn_time_to_cstring(apr_time_now(), pool); 131 remote_host = (remote_host ? remote_host : "-"); 132 user = (user ? user : "-"); 133 repos = (repos ? repos : "-"); 134 135 continuation = ""; 136 while (err != NULL) 137 { 138 const char *message = svn_err_best_message(err, errbuf, sizeof(errbuf)); 139 /* based on httpd-2.2.4/server/log.c:log_error_core */ 140 apr_size_t len = apr_snprintf(errstr, sizeof(errstr), 141 "%" APR_PID_T_FMT 142 " %s %s %s %s ERR%s %s %ld %d ", 143 getpid(), timestr, remote_host, user, 144 repos, continuation, 145 err->file ? err->file : "-", err->line, 146 err->apr_err); 147 148 len += escape_errorlog_item(errstr + len, message, 149 sizeof(errstr) - len); 150 /* Truncate for the terminator (as apr_snprintf does) */ 151 if (len > sizeof(errstr) - sizeof(APR_EOL_STR)) { 152 len = sizeof(errstr) - sizeof(APR_EOL_STR); 153 } 154 strcpy(errstr + len, APR_EOL_STR); 155 len += strlen(APR_EOL_STR); 156 svn_error_clear(log_write(log_file, errstr, len, pool)); 157 158 continuation = "-"; 159 err = err->child; 160 } 161} 162 163/* Call log_error with log_file, remote_host, user, and repos 164 arguments from SERVER and CONN. */ 165static void 166log_server_error(svn_error_t *err, server_baton_t *server, 167 svn_ra_svn_conn_t *conn, apr_pool_t *pool) 168{ 169 log_error(err, server->log_file, svn_ra_svn_conn_remote_host(conn), 170 server->user, server->repos_name, pool); 171} 172 173/* svn_error_create() a new error, log_server_error() it, and 174 return it. */ 175static svn_error_t * 176error_create_and_log(apr_status_t apr_err, svn_error_t *child, 177 const char *message, server_baton_t *server, 178 svn_ra_svn_conn_t *conn, apr_pool_t *pool) 179{ 180 svn_error_t *err = svn_error_create(apr_err, child, message); 181 log_server_error(err, server, conn, pool); 182 return err; 183} 184 185/* Log a failure ERR, transmit ERR back to the client (as part of a 186 "failure" notification), consume ERR, and flush the connection. */ 187static svn_error_t * 188log_fail_and_flush(svn_error_t *err, server_baton_t *server, 189 svn_ra_svn_conn_t *conn, apr_pool_t *pool) 190{ 191 svn_error_t *io_err; 192 193 log_server_error(err, server, conn, pool); 194 io_err = svn_ra_svn__write_cmd_failure(conn, pool, err); 195 svn_error_clear(err); 196 SVN_ERR(io_err); 197 return svn_ra_svn__flush(conn, pool); 198} 199 200/* Log a client command. */ 201static svn_error_t *log_command(server_baton_t *b, 202 svn_ra_svn_conn_t *conn, 203 apr_pool_t *pool, 204 const char *fmt, ...) 205{ 206 const char *remote_host, *timestr, *log, *line; 207 va_list ap; 208 apr_size_t nbytes; 209 210 if (b->log_file == NULL) 211 return SVN_NO_ERROR; 212 213 remote_host = svn_ra_svn_conn_remote_host(conn); 214 timestr = svn_time_to_cstring(apr_time_now(), pool); 215 216 va_start(ap, fmt); 217 log = apr_pvsprintf(pool, fmt, ap); 218 va_end(ap); 219 220 line = apr_psprintf(pool, "%" APR_PID_T_FMT 221 " %s %s %s %s %s" APR_EOL_STR, 222 getpid(), timestr, 223 (remote_host ? remote_host : "-"), 224 (b->user ? b->user : "-"), b->repos_name, log); 225 nbytes = strlen(line); 226 227 return log_write(b->log_file, line, nbytes, pool); 228} 229 230/* Log an authz failure */ 231static svn_error_t * 232log_authz_denied(const char *path, 233 svn_repos_authz_access_t required, 234 server_baton_t *b, 235 svn_ra_svn_conn_t *conn, 236 apr_pool_t *pool) 237{ 238 const char *timestr, *remote_host, *line; 239 240 if (b->log_file == NULL) 241 return SVN_NO_ERROR; 242 243 if (!b->user) 244 return SVN_NO_ERROR; 245 246 timestr = svn_time_to_cstring(apr_time_now(), pool); 247 remote_host = svn_ra_svn_conn_remote_host(conn); 248 249 line = apr_psprintf(pool, "%" APR_PID_T_FMT 250 " %s %s %s %s Authorization Failed %s%s %s" APR_EOL_STR, 251 getpid(), timestr, 252 (remote_host ? remote_host : "-"), 253 (b->user ? b->user : "-"), 254 b->repos_name, 255 (required & svn_authz_recursive ? "recursive " : ""), 256 (required & svn_authz_write ? "write" : "read"), 257 (path && path[0] ? path : "/")); 258 259 return log_write(b->log_file, line, strlen(line), pool); 260} 261 262 263svn_error_t *load_pwdb_config(server_baton_t *server, 264 svn_ra_svn_conn_t *conn, 265 apr_pool_t *pool) 266{ 267 const char *pwdb_path; 268 svn_error_t *err; 269 270 svn_config_get(server->cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL, 271 SVN_CONFIG_OPTION_PASSWORD_DB, NULL); 272 273 server->pwdb = NULL; 274 if (pwdb_path) 275 { 276 pwdb_path = svn_dirent_internal_style(pwdb_path, pool); 277 pwdb_path = svn_dirent_join(server->base, pwdb_path, pool); 278 279 err = svn_config_read3(&server->pwdb, pwdb_path, TRUE, 280 FALSE, FALSE, pool); 281 if (err) 282 { 283 log_server_error(err, server, conn, pool); 284 285 /* Because it may be possible to read the pwdb file with some 286 access methods and not others, ignore errors reading the pwdb 287 file and just don't present password authentication as an 288 option. Also, some authentications (e.g. --tunnel) can 289 proceed without it anyway. 290 291 ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked 292 ### for here. That seems to have been introduced in r856914, 293 ### and only in r870942 was the APR_EACCES check introduced. */ 294 if (err->apr_err != SVN_ERR_BAD_FILENAME 295 && ! APR_STATUS_IS_EACCES(err->apr_err)) 296 { 297 /* Now that we've logged the error, clear it and return a 298 * nice, generic error to the user: 299 * http://subversion.tigris.org/issues/show_bug.cgi?id=2271 */ 300 svn_error_clear(err); 301 return svn_error_create(SVN_ERR_AUTHN_FAILED, NULL, NULL); 302 } 303 else 304 /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */ 305 svn_error_clear(err); 306 } 307 } 308 309 return SVN_NO_ERROR; 310} 311 312/* Canonicalize *ACCESS_FILE based on the type of argument. Results are 313 * placed in *ACCESS_FILE. SERVER baton is used to convert relative paths to 314 * absolute paths rooted at the server root. REPOS_ROOT is used to calculate 315 * an absolute URL for repos-relative URLs. */ 316static svn_error_t * 317canonicalize_access_file(const char **access_file, server_baton_t *server, 318 const char *repos_root, apr_pool_t *pool) 319{ 320 if (svn_path_is_url(*access_file)) 321 { 322 *access_file = svn_uri_canonicalize(*access_file, pool); 323 } 324 else if (svn_path_is_repos_relative_url(*access_file)) 325 { 326 const char *repos_root_url; 327 328 SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_root_url, repos_root, 329 pool)); 330 SVN_ERR(svn_path_resolve_repos_relative_url(access_file, *access_file, 331 repos_root_url, pool)); 332 *access_file = svn_uri_canonicalize(*access_file, pool); 333 } 334 else 335 { 336 *access_file = svn_dirent_internal_style(*access_file, pool); 337 *access_file = svn_dirent_join(server->base, *access_file, pool); 338 } 339 340 return SVN_NO_ERROR; 341} 342 343svn_error_t *load_authz_config(server_baton_t *server, 344 svn_ra_svn_conn_t *conn, 345 const char *repos_root, 346 apr_pool_t *pool) 347{ 348 const char *authzdb_path; 349 const char *groupsdb_path; 350 svn_error_t *err; 351 352 /* Read authz configuration. */ 353 svn_config_get(server->cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL, 354 SVN_CONFIG_OPTION_AUTHZ_DB, NULL); 355 356 svn_config_get(server->cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL, 357 SVN_CONFIG_OPTION_GROUPS_DB, NULL); 358 359 if (authzdb_path) 360 { 361 const char *case_force_val; 362 363 /* Canonicalize and add the base onto the authzdb_path (if needed). */ 364 err = canonicalize_access_file(&authzdb_path, server, 365 repos_root, pool); 366 367 /* Same for the groupsdb_path if it is present. */ 368 if (groupsdb_path && !err) 369 err = canonicalize_access_file(&groupsdb_path, server, 370 repos_root, pool); 371 372 if (!err) 373 err = svn_repos_authz_read2(&server->authzdb, authzdb_path, 374 groupsdb_path, TRUE, pool); 375 376 if (err) 377 { 378 log_server_error(err, server, conn, pool); 379 svn_error_clear(err); 380 return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, NULL); 381 } 382 383 /* Are we going to be case-normalizing usernames when we consult 384 * this authz file? */ 385 svn_config_get(server->cfg, &case_force_val, SVN_CONFIG_SECTION_GENERAL, 386 SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL); 387 if (case_force_val) 388 { 389 if (strcmp(case_force_val, "upper") == 0) 390 server->username_case = CASE_FORCE_UPPER; 391 else if (strcmp(case_force_val, "lower") == 0) 392 server->username_case = CASE_FORCE_LOWER; 393 else 394 server->username_case = CASE_ASIS; 395 } 396 } 397 else 398 { 399 server->authzdb = NULL; 400 server->username_case = CASE_ASIS; 401 } 402 403 return SVN_NO_ERROR; 404} 405 406/* Set *FS_PATH to the portion of URL that is the path within the 407 repository, if URL is inside REPOS_URL (if URL is not inside 408 REPOS_URL, then error, with the effect on *FS_PATH undefined). 409 410 If the resultant fs path would be the empty string (i.e., URL and 411 REPOS_URL are the same), then set *FS_PATH to "/". 412 413 Assume that REPOS_URL and URL are already URI-decoded. */ 414static svn_error_t *get_fs_path(const char *repos_url, const char *url, 415 const char **fs_path) 416{ 417 apr_size_t len; 418 419 len = strlen(repos_url); 420 if (strncmp(url, repos_url, len) != 0) 421 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 422 "'%s' is not the same repository as '%s'", 423 url, repos_url); 424 *fs_path = url + len; 425 if (! **fs_path) 426 *fs_path = "/"; 427 428 return SVN_NO_ERROR; 429} 430 431/* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */ 432 433/* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else 434 converts it to lower case. */ 435static void convert_case(char *text, svn_boolean_t to_uppercase) 436{ 437 char *c = text; 438 while (*c) 439 { 440 *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c)); 441 ++c; 442 } 443} 444 445/* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to 446 the user described in BATON according to the authz rules in BATON. 447 Use POOL for temporary allocations only. If no authz rules are 448 present in BATON, grant access by default. */ 449static svn_error_t *authz_check_access(svn_boolean_t *allowed, 450 const char *path, 451 svn_repos_authz_access_t required, 452 server_baton_t *b, 453 svn_ra_svn_conn_t *conn, 454 apr_pool_t *pool) 455{ 456 /* If authz cannot be performed, grant access. This is NOT the same 457 as the default policy when authz is performed on a path with no 458 rules. In the latter case, the default is to deny access, and is 459 set by svn_repos_authz_check_access. */ 460 if (!b->authzdb) 461 { 462 *allowed = TRUE; 463 return SVN_NO_ERROR; 464 } 465 466 /* If the authz request is for the empty path (ie. ""), replace it 467 with the root path. This happens because of stripping done at 468 various levels in svnserve that remove the leading / on an 469 absolute path. Passing such a malformed path to the authz 470 routines throws them into an infinite loop and makes them miss 471 ACLs. */ 472 if (path) 473 path = svn_fspath__canonicalize(path, pool); 474 475 /* If we have a username, and we've not yet used it + any username 476 case normalization that might be requested to determine "the 477 username we used for authz purposes", do so now. */ 478 if (b->user && (! b->authz_user)) 479 { 480 char *authz_user = apr_pstrdup(b->pool, b->user); 481 if (b->username_case == CASE_FORCE_UPPER) 482 convert_case(authz_user, TRUE); 483 else if (b->username_case == CASE_FORCE_LOWER) 484 convert_case(authz_user, FALSE); 485 b->authz_user = authz_user; 486 } 487 488 SVN_ERR(svn_repos_authz_check_access(b->authzdb, b->authz_repos_name, 489 path, b->authz_user, required, 490 allowed, pool)); 491 if (!*allowed) 492 SVN_ERR(log_authz_denied(path, required, b, conn, pool)); 493 494 return SVN_NO_ERROR; 495} 496 497/* Set *ALLOWED to TRUE if PATH is readable by the user described in 498 * BATON. Use POOL for temporary allocations only. ROOT is not used. 499 * Implements the svn_repos_authz_func_t interface. 500 */ 501static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed, 502 svn_fs_root_t *root, 503 const char *path, 504 void *baton, 505 apr_pool_t *pool) 506{ 507 authz_baton_t *sb = baton; 508 509 return authz_check_access(allowed, path, svn_authz_read, 510 sb->server, sb->conn, pool); 511} 512 513/* If authz is enabled in the specified BATON, return a read authorization 514 function. Otherwise, return NULL. */ 515static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton) 516{ 517 if (baton->authzdb) 518 return authz_check_access_cb; 519 return NULL; 520} 521 522/* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted, 523 * according to the state in BATON. Use POOL for temporary 524 * allocations only. ROOT is not used. Implements the 525 * svn_repos_authz_callback_t interface. 526 */ 527static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required, 528 svn_boolean_t *allowed, 529 svn_fs_root_t *root, 530 const char *path, 531 void *baton, 532 apr_pool_t *pool) 533{ 534 authz_baton_t *sb = baton; 535 536 return authz_check_access(allowed, path, required, 537 sb->server, sb->conn, pool); 538} 539 540 541enum access_type get_access(server_baton_t *b, enum authn_type auth) 542{ 543 const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS : 544 SVN_CONFIG_OPTION_ANON_ACCESS; 545 const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read"; 546 enum access_type result; 547 548 svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def); 549 result = (strcmp(val, "write") == 0 ? WRITE_ACCESS : 550 strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS); 551 return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result; 552} 553 554static enum access_type current_access(server_baton_t *b) 555{ 556 return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED); 557} 558 559/* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME 560 is true, don't send anonymous mech even if that would give the desired 561 access. */ 562static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 563 server_baton_t *b, enum access_type required, 564 svn_boolean_t needs_username) 565{ 566 if (!needs_username && get_access(b, UNAUTHENTICATED) >= required) 567 SVN_ERR(svn_ra_svn__write_word(conn, pool, "ANONYMOUS")); 568 if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required) 569 SVN_ERR(svn_ra_svn__write_word(conn, pool, "EXTERNAL")); 570 if (b->pwdb && get_access(b, AUTHENTICATED) >= required) 571 SVN_ERR(svn_ra_svn__write_word(conn, pool, "CRAM-MD5")); 572 return SVN_NO_ERROR; 573} 574 575/* Context for cleanup handler. */ 576struct cleanup_fs_access_baton 577{ 578 svn_fs_t *fs; 579 apr_pool_t *pool; 580}; 581 582/* Pool cleanup handler. Make sure fs's access_t points to NULL when 583 the command pool is destroyed. */ 584static apr_status_t cleanup_fs_access(void *data) 585{ 586 svn_error_t *serr; 587 struct cleanup_fs_access_baton *baton = data; 588 589 serr = svn_fs_set_access(baton->fs, NULL); 590 if (serr) 591 { 592 apr_status_t apr_err = serr->apr_err; 593 svn_error_clear(serr); 594 return apr_err; 595 } 596 597 return APR_SUCCESS; 598} 599 600 601/* Create an svn_fs_access_t in POOL for USER and associate it with 602 B's filesystem. Also, register a cleanup handler with POOL which 603 de-associates the svn_fs_access_t from B's filesystem. */ 604static svn_error_t * 605create_fs_access(server_baton_t *b, apr_pool_t *pool) 606{ 607 svn_fs_access_t *fs_access; 608 struct cleanup_fs_access_baton *cleanup_baton; 609 610 if (!b->user) 611 return SVN_NO_ERROR; 612 613 SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool)); 614 SVN_ERR(svn_fs_set_access(b->fs, fs_access)); 615 616 cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton)); 617 cleanup_baton->pool = pool; 618 cleanup_baton->fs = b->fs; 619 apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access, 620 apr_pool_cleanup_null); 621 622 return SVN_NO_ERROR; 623} 624 625/* Authenticate, once the client has chosen a mechanism and possibly 626 * sent an initial mechanism token. On success, set *success to true 627 * and b->user to the authenticated username (or NULL for anonymous). 628 * On authentication failure, report failure to the client and set 629 * *success to FALSE. On communications failure, return an error. 630 * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */ 631static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 632 const char *mech, const char *mecharg, 633 server_baton_t *b, enum access_type required, 634 svn_boolean_t needs_username, 635 svn_boolean_t *success) 636{ 637 const char *user; 638 *success = FALSE; 639 640 if (get_access(b, AUTHENTICATED) >= required 641 && b->tunnel_user && strcmp(mech, "EXTERNAL") == 0) 642 { 643 if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0) 644 return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", 645 "Requested username does not match"); 646 b->user = b->tunnel_user; 647 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success")); 648 *success = TRUE; 649 return SVN_NO_ERROR; 650 } 651 652 if (get_access(b, UNAUTHENTICATED) >= required 653 && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username) 654 { 655 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success")); 656 *success = TRUE; 657 return SVN_NO_ERROR; 658 } 659 660 if (get_access(b, AUTHENTICATED) >= required 661 && b->pwdb && strcmp(mech, "CRAM-MD5") == 0) 662 { 663 SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success)); 664 b->user = apr_pstrdup(b->pool, user); 665 return SVN_NO_ERROR; 666 } 667 668 return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", 669 "Must authenticate with listed mechanism"); 670} 671 672/* Perform an authentication request using the built-in SASL implementation. */ 673static svn_error_t * 674internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 675 server_baton_t *b, enum access_type required, 676 svn_boolean_t needs_username) 677{ 678 svn_boolean_t success; 679 const char *mech, *mecharg; 680 681 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 682 SVN_ERR(send_mechs(conn, pool, b, required, needs_username)); 683 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)c)", b->realm)); 684 do 685 { 686 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &mech, &mecharg)); 687 if (!*mech) 688 break; 689 SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username, 690 &success)); 691 } 692 while (!success); 693 return SVN_NO_ERROR; 694} 695 696/* Perform an authentication request in order to get an access level of 697 * REQUIRED or higher. Since the client may escape the authentication 698 * exchange, the caller should check current_access(b) to see if 699 * authentication succeeded. */ 700static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 701 server_baton_t *b, enum access_type required, 702 svn_boolean_t needs_username) 703{ 704#ifdef SVN_HAVE_SASL 705 if (b->use_sasl) 706 return cyrus_auth_request(conn, pool, b, required, needs_username); 707#endif 708 709 return internal_auth_request(conn, pool, b, required, needs_username); 710} 711 712/* Send a trivial auth notification on CONN which lists no mechanisms, 713 * indicating that authentication is unnecessary. Usually called in 714 * response to invocation of a svnserve command. 715 */ 716static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn, 717 apr_pool_t *pool, server_baton_t *b) 718{ 719 return svn_ra_svn__write_cmd_response(conn, pool, "()c", ""); 720} 721 722/* Ensure that the client has the REQUIRED access by checking the 723 * access directives (both blanket and per-directory) in BATON. If 724 * PATH is NULL, then only the blanket access configuration will 725 * impact the result. 726 * 727 * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the 728 * user described in BATON is authenticated and, well, has a username 729 * assigned to him. 730 * 731 * Use POOL for temporary allocations only. 732 */ 733static svn_boolean_t lookup_access(apr_pool_t *pool, 734 server_baton_t *baton, 735 svn_ra_svn_conn_t *conn, 736 svn_repos_authz_access_t required, 737 const char *path, 738 svn_boolean_t needs_username) 739{ 740 enum access_type req = (required & svn_authz_write) ? 741 WRITE_ACCESS : READ_ACCESS; 742 svn_boolean_t authorized; 743 svn_error_t *err; 744 745 /* Get authz's opinion on the access. */ 746 err = authz_check_access(&authorized, path, required, baton, conn, pool); 747 748 /* If an error made lookup fail, deny access. */ 749 if (err) 750 { 751 log_server_error(err, baton, conn, pool); 752 svn_error_clear(err); 753 return FALSE; 754 } 755 756 /* If the required access is blanket-granted AND granted by authz 757 AND we already have a username if one is required, then the 758 lookup has succeeded. */ 759 if (current_access(baton) >= req 760 && authorized 761 && (! needs_username || baton->user)) 762 return TRUE; 763 764 return FALSE; 765} 766 767/* Check that the client has the REQUIRED access by consulting the 768 * authentication and authorization states stored in BATON. If the 769 * client does not have the required access credentials, attempt to 770 * authenticate the client to get that access, using CONN for 771 * communication. 772 * 773 * This function is supposed to be called to handle the authentication 774 * half of a standard svn protocol reply. If an error is returned, it 775 * probably means that the server can terminate the client connection 776 * with an apologetic error, as it implies an authentication failure. 777 * 778 * PATH and NEEDS_USERNAME are passed along to lookup_access, their 779 * behaviour is documented there. 780 */ 781static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn, 782 apr_pool_t *pool, 783 server_baton_t *b, 784 svn_repos_authz_access_t required, 785 const char *path, 786 svn_boolean_t needs_username) 787{ 788 enum access_type req = (required & svn_authz_write) ? 789 WRITE_ACCESS : READ_ACCESS; 790 791 /* See whether the user already has the required access. If so, 792 nothing needs to be done. Create the FS access and send a 793 trivial auth request. */ 794 if (lookup_access(pool, b, conn, required, path, needs_username)) 795 { 796 SVN_ERR(create_fs_access(b, pool)); 797 return trivial_auth_request(conn, pool, b); 798 } 799 800 /* If the required blanket access can be obtained by authenticating, 801 try that. Unfortunately, we can't tell until after 802 authentication whether authz will work or not. We force 803 requiring a username because we need one to be able to check 804 authz configuration again with a different user credentials than 805 the first time round. */ 806 if (b->user == NULL 807 && get_access(b, AUTHENTICATED) >= req 808 && (b->tunnel_user || b->pwdb || b->use_sasl)) 809 SVN_ERR(auth_request(conn, pool, b, req, TRUE)); 810 811 /* Now that an authentication has been done get the new take of 812 authz on the request. */ 813 if (! lookup_access(pool, b, conn, required, path, needs_username)) 814 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, 815 error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, 816 NULL, NULL, b, conn, pool), 817 NULL); 818 819 /* Else, access is granted, and there is much rejoicing. */ 820 SVN_ERR(create_fs_access(b, pool)); 821 822 return SVN_NO_ERROR; 823} 824 825/* --- REPORTER COMMAND SET --- */ 826 827/* To allow for pipelining, reporter commands have no reponses. If we 828 * get an error, we ignore all subsequent reporter commands and return 829 * the error finish_report, to be handled by the calling command. 830 */ 831 832static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 833 apr_array_header_t *params, void *baton) 834{ 835 report_driver_baton_t *b = baton; 836 const char *path, *lock_token, *depth_word; 837 svn_revnum_t rev; 838 /* Default to infinity, for old clients that don't send depth. */ 839 svn_depth_t depth = svn_depth_infinity; 840 svn_boolean_t start_empty; 841 842 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crb?(?c)?w", 843 &path, &rev, &start_empty, &lock_token, 844 &depth_word)); 845 if (depth_word) 846 depth = svn_depth_from_word(depth_word); 847 path = svn_relpath_canonicalize(path, pool); 848 if (b->from_rev && strcmp(path, "") == 0) 849 *b->from_rev = rev; 850 if (!b->err) 851 b->err = svn_repos_set_path3(b->report_baton, path, rev, depth, 852 start_empty, lock_token, pool); 853 b->entry_counter++; 854 if (!start_empty) 855 b->only_empty_entries = FALSE; 856 return SVN_NO_ERROR; 857} 858 859static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 860 apr_array_header_t *params, void *baton) 861{ 862 report_driver_baton_t *b = baton; 863 const char *path; 864 865 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path)); 866 path = svn_relpath_canonicalize(path, pool); 867 if (!b->err) 868 b->err = svn_repos_delete_path(b->report_baton, path, pool); 869 return SVN_NO_ERROR; 870} 871 872static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 873 apr_array_header_t *params, void *baton) 874{ 875 report_driver_baton_t *b = baton; 876 const char *path, *url, *lock_token, *fs_path, *depth_word; 877 svn_revnum_t rev; 878 svn_boolean_t start_empty; 879 /* Default to infinity, for old clients that don't send depth. */ 880 svn_depth_t depth = svn_depth_infinity; 881 882 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccrb?(?c)?w", 883 &path, &url, &rev, &start_empty, 884 &lock_token, &depth_word)); 885 886 /* ### WHAT?! The link path is an absolute URL?! Didn't see that 887 coming... -- cmpilato */ 888 path = svn_relpath_canonicalize(path, pool); 889 url = svn_uri_canonicalize(url, pool); 890 if (depth_word) 891 depth = svn_depth_from_word(depth_word); 892 if (!b->err) 893 b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool), 894 svn_path_uri_decode(url, pool), 895 &fs_path); 896 if (!b->err) 897 b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev, 898 depth, start_empty, lock_token, pool); 899 b->entry_counter++; 900 return SVN_NO_ERROR; 901} 902 903static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 904 apr_array_header_t *params, void *baton) 905{ 906 report_driver_baton_t *b = baton; 907 908 /* No arguments to parse. */ 909 SVN_ERR(trivial_auth_request(conn, pool, b->sb)); 910 if (!b->err) 911 b->err = svn_repos_finish_report(b->report_baton, pool); 912 return SVN_NO_ERROR; 913} 914 915static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 916 apr_array_header_t *params, void *baton) 917{ 918 report_driver_baton_t *b = baton; 919 920 /* No arguments to parse. */ 921 svn_error_clear(svn_repos_abort_report(b->report_baton, pool)); 922 return SVN_NO_ERROR; 923} 924 925static const svn_ra_svn_cmd_entry_t report_commands[] = { 926 { "set-path", set_path }, 927 { "delete-path", delete_path }, 928 { "link-path", link_path }, 929 { "finish-report", finish_report, TRUE }, 930 { "abort-report", abort_report, TRUE }, 931 { NULL } 932}; 933 934/* Accept a report from the client, drive the network editor with the 935 * result, and then write an empty command response. If there is a 936 * non-protocol failure, accept_report will abort the edit and return 937 * a command error to be reported by handle_commands(). 938 * 939 * If only_empty_entry is not NULL and the report contains only one 940 * item, and that item is empty, set *only_empty_entry to TRUE, else 941 * set it to FALSE. 942 * 943 * If from_rev is not NULL, set *from_rev to the revision number from 944 * the set-path on ""; if somehow set-path "" never happens, set 945 * *from_rev to SVN_INVALID_REVNUM. 946 */ 947static svn_error_t *accept_report(svn_boolean_t *only_empty_entry, 948 svn_revnum_t *from_rev, 949 svn_ra_svn_conn_t *conn, apr_pool_t *pool, 950 server_baton_t *b, svn_revnum_t rev, 951 const char *target, const char *tgt_path, 952 svn_boolean_t text_deltas, 953 svn_depth_t depth, 954 svn_boolean_t send_copyfrom_args, 955 svn_boolean_t ignore_ancestry) 956{ 957 const svn_delta_editor_t *editor; 958 void *edit_baton, *report_baton; 959 report_driver_baton_t rb; 960 svn_error_t *err; 961 authz_baton_t ab; 962 963 ab.server = b; 964 ab.conn = conn; 965 966 /* Make an svn_repos report baton. Tell it to drive the network editor 967 * when the report is complete. */ 968 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); 969 SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev, b->repos, 970 b->fs_path->data, target, tgt_path, 971 text_deltas, depth, ignore_ancestry, 972 send_copyfrom_args, 973 editor, edit_baton, 974 authz_check_access_cb_func(b), 975 &ab, svn_ra_svn_zero_copy_limit(conn), 976 pool)); 977 978 rb.sb = b; 979 rb.repos_url = svn_path_uri_decode(b->repos_url, pool); 980 rb.report_baton = report_baton; 981 rb.err = NULL; 982 rb.entry_counter = 0; 983 rb.only_empty_entries = TRUE; 984 rb.from_rev = from_rev; 985 if (from_rev) 986 *from_rev = SVN_INVALID_REVNUM; 987 err = svn_ra_svn__handle_commands2(conn, pool, report_commands, &rb, TRUE); 988 if (err) 989 { 990 /* Network or protocol error while handling commands. */ 991 svn_error_clear(rb.err); 992 return err; 993 } 994 else if (rb.err) 995 { 996 /* Some failure during the reporting or editing operations. */ 997 SVN_CMD_ERR(rb.err); 998 } 999 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 1000 1001 if (only_empty_entry) 1002 *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries; 1003 1004 return SVN_NO_ERROR; 1005} 1006 1007/* --- MAIN COMMAND SET --- */ 1008 1009/* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t 1010 * values. */ 1011static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn, 1012 apr_pool_t *pool, 1013 const apr_array_header_t *propdiffs) 1014{ 1015 int i; 1016 1017 for (i = 0; i < propdiffs->nelts; ++i) 1018 { 1019 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t); 1020 1021 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "c(?s)", 1022 prop->name, prop->value)); 1023 } 1024 1025 return SVN_NO_ERROR; 1026} 1027 1028/* Write out a lock to the client. */ 1029static svn_error_t *write_lock(svn_ra_svn_conn_t *conn, 1030 apr_pool_t *pool, 1031 svn_lock_t *lock) 1032{ 1033 const char *cdate, *edate; 1034 1035 cdate = svn_time_to_cstring(lock->creation_date, pool); 1036 edate = lock->expiration_date 1037 ? svn_time_to_cstring(lock->expiration_date, pool) : NULL; 1038 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path, 1039 lock->token, lock->owner, lock->comment, 1040 cdate, edate)); 1041 1042 return SVN_NO_ERROR; 1043} 1044 1045/* ### This really belongs in libsvn_repos. */ 1046/* Get the explicit properties and/or inherited properties for a PATH in 1047 ROOT, with hardcoded committed-info values. */ 1048static svn_error_t * 1049get_props(apr_hash_t **props, 1050 apr_array_header_t **iprops, 1051 authz_baton_t *b, 1052 svn_fs_root_t *root, 1053 const char *path, 1054 apr_pool_t *pool) 1055{ 1056 /* Get the explicit properties. */ 1057 if (props) 1058 { 1059 svn_string_t *str; 1060 svn_revnum_t crev; 1061 const char *cdate, *cauthor, *uuid; 1062 1063 SVN_ERR(svn_fs_node_proplist(props, root, path, pool)); 1064 1065 /* Hardcode the values for the committed revision, date, and author. */ 1066 SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root, 1067 path, pool)); 1068 str = svn_string_create(apr_psprintf(pool, "%ld", crev), 1069 pool); 1070 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, str); 1071 str = (cdate) ? svn_string_create(cdate, pool) : NULL; 1072 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, str); 1073 str = (cauthor) ? svn_string_create(cauthor, pool) : NULL; 1074 svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, str); 1075 1076 /* Hardcode the values for the UUID. */ 1077 SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool)); 1078 str = (uuid) ? svn_string_create(uuid, pool) : NULL; 1079 svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, str); 1080 } 1081 1082 /* Get any inherited properties the user is authorized to. */ 1083 if (iprops) 1084 { 1085 SVN_ERR(svn_repos_fs_get_inherited_props( 1086 iprops, root, path, NULL, 1087 authz_check_access_cb_func(b->server), 1088 b, pool, pool)); 1089 } 1090 1091 return SVN_NO_ERROR; 1092} 1093 1094/* Set BATON->FS_PATH for the repository URL found in PARAMS. */ 1095static svn_error_t *reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1096 apr_array_header_t *params, void *baton) 1097{ 1098 server_baton_t *b = baton; 1099 const char *url; 1100 const char *fs_path; 1101 1102 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &url)); 1103 url = svn_uri_canonicalize(url, pool); 1104 SVN_ERR(trivial_auth_request(conn, pool, b)); 1105 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool), 1106 svn_path_uri_decode(url, pool), 1107 &fs_path)); 1108 SVN_ERR(log_command(b, conn, pool, "%s", svn_log__reparent(fs_path, pool))); 1109 svn_stringbuf_set(b->fs_path, fs_path); 1110 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 1111 return SVN_NO_ERROR; 1112} 1113 1114static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1115 apr_array_header_t *params, void *baton) 1116{ 1117 server_baton_t *b = baton; 1118 svn_revnum_t rev; 1119 1120 SVN_ERR(log_command(b, conn, pool, "get-latest-rev")); 1121 1122 SVN_ERR(trivial_auth_request(conn, pool, b)); 1123 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 1124 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev)); 1125 return SVN_NO_ERROR; 1126} 1127 1128static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1129 apr_array_header_t *params, void *baton) 1130{ 1131 server_baton_t *b = baton; 1132 svn_revnum_t rev; 1133 apr_time_t tm; 1134 const char *timestr; 1135 1136 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", ×tr)); 1137 SVN_ERR(log_command(b, conn, pool, "get-dated-rev %s", timestr)); 1138 1139 SVN_ERR(trivial_auth_request(conn, pool, b)); 1140 SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool)); 1141 SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool)); 1142 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev)); 1143 return SVN_NO_ERROR; 1144} 1145 1146/* Common logic for change_rev_prop() and change_rev_prop2(). */ 1147static svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn, 1148 server_baton_t *b, 1149 svn_revnum_t rev, 1150 const char *name, 1151 const svn_string_t *const *old_value_p, 1152 const svn_string_t *value, 1153 apr_pool_t *pool) 1154{ 1155 authz_baton_t ab; 1156 1157 ab.server = b; 1158 ab.conn = conn; 1159 1160 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE)); 1161 SVN_ERR(log_command(b, conn, pool, "%s", 1162 svn_log__change_rev_prop(rev, name, pool))); 1163 SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repos, rev, b->user, 1164 name, old_value_p, value, 1165 TRUE, TRUE, 1166 authz_check_access_cb_func(b), &ab, 1167 pool)); 1168 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 1169 1170 return SVN_NO_ERROR; 1171} 1172 1173static svn_error_t *change_rev_prop2(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1174 apr_array_header_t *params, void *baton) 1175{ 1176 server_baton_t *b = baton; 1177 svn_revnum_t rev; 1178 const char *name; 1179 svn_string_t *value; 1180 const svn_string_t *const *old_value_p; 1181 svn_string_t *old_value; 1182 svn_boolean_t dont_care; 1183 1184 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc(?s)(b?s)", 1185 &rev, &name, &value, 1186 &dont_care, &old_value)); 1187 1188 /* Argument parsing. */ 1189 if (dont_care) 1190 old_value_p = NULL; 1191 else 1192 old_value_p = (const svn_string_t *const *)&old_value; 1193 1194 /* Input validation. */ 1195 if (dont_care && old_value) 1196 { 1197 svn_error_t *err; 1198 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 1199 "'previous-value' and 'dont-care' cannot both be " 1200 "set in 'change-rev-prop2' request"); 1201 return log_fail_and_flush(err, b, conn, pool); 1202 } 1203 1204 /* Do it. */ 1205 SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool)); 1206 1207 return SVN_NO_ERROR; 1208} 1209 1210static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1211 apr_array_header_t *params, void *baton) 1212{ 1213 server_baton_t *b = baton; 1214 svn_revnum_t rev; 1215 const char *name; 1216 svn_string_t *value; 1217 1218 /* Because the revprop value was at one time mandatory, the usual 1219 optional element pattern "(?s)" isn't used. */ 1220 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc?s", &rev, &name, &value)); 1221 1222 SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool)); 1223 1224 return SVN_NO_ERROR; 1225} 1226 1227static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1228 apr_array_header_t *params, void *baton) 1229{ 1230 server_baton_t *b = baton; 1231 svn_revnum_t rev; 1232 apr_hash_t *props; 1233 authz_baton_t ab; 1234 1235 ab.server = b; 1236 ab.conn = conn; 1237 1238 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev)); 1239 SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool))); 1240 1241 SVN_ERR(trivial_auth_request(conn, pool, b)); 1242 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev, 1243 authz_check_access_cb_func(b), &ab, 1244 pool)); 1245 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 1246 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props)); 1247 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1248 return SVN_NO_ERROR; 1249} 1250 1251static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1252 apr_array_header_t *params, void *baton) 1253{ 1254 server_baton_t *b = baton; 1255 svn_revnum_t rev; 1256 const char *name; 1257 svn_string_t *value; 1258 authz_baton_t ab; 1259 1260 ab.server = b; 1261 ab.conn = conn; 1262 1263 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc", &rev, &name)); 1264 SVN_ERR(log_command(b, conn, pool, "%s", 1265 svn_log__rev_prop(rev, name, pool))); 1266 1267 SVN_ERR(trivial_auth_request(conn, pool, b)); 1268 SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name, 1269 authz_check_access_cb_func(b), &ab, 1270 pool)); 1271 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(?s)", value)); 1272 return SVN_NO_ERROR; 1273} 1274 1275static svn_error_t *commit_done(const svn_commit_info_t *commit_info, 1276 void *baton, apr_pool_t *pool) 1277{ 1278 commit_callback_baton_t *ccb = baton; 1279 1280 *ccb->new_rev = commit_info->revision; 1281 *ccb->date = commit_info->date 1282 ? apr_pstrdup(ccb->pool, commit_info->date): NULL; 1283 *ccb->author = commit_info->author 1284 ? apr_pstrdup(ccb->pool, commit_info->author) : NULL; 1285 *ccb->post_commit_err = commit_info->post_commit_err 1286 ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL; 1287 return SVN_NO_ERROR; 1288} 1289 1290/* Add the LOCK_TOKENS (if any) to the filesystem access context, 1291 * checking path authorizations using the state in SB as we go. 1292 * LOCK_TOKENS is an array of svn_ra_svn_item_t structs. Return a 1293 * client error if LOCK_TOKENS is not a list of lists. If a lock 1294 * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED 1295 * to the client. Use POOL for temporary allocations only. 1296 */ 1297static svn_error_t *add_lock_tokens(svn_ra_svn_conn_t *conn, 1298 const apr_array_header_t *lock_tokens, 1299 server_baton_t *sb, 1300 apr_pool_t *pool) 1301{ 1302 int i; 1303 svn_fs_access_t *fs_access; 1304 1305 SVN_ERR(svn_fs_get_access(&fs_access, sb->fs)); 1306 1307 /* If there is no access context, nowhere to add the tokens. */ 1308 if (! fs_access) 1309 return SVN_NO_ERROR; 1310 1311 for (i = 0; i < lock_tokens->nelts; ++i) 1312 { 1313 const char *path, *token, *full_path; 1314 svn_ra_svn_item_t *path_item, *token_item; 1315 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i, 1316 svn_ra_svn_item_t); 1317 if (item->kind != SVN_RA_SVN_LIST) 1318 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1319 "Lock tokens aren't a list of lists"); 1320 1321 path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t); 1322 if (path_item->kind != SVN_RA_SVN_STRING) 1323 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1324 "Lock path isn't a string"); 1325 1326 token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t); 1327 if (token_item->kind != SVN_RA_SVN_STRING) 1328 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1329 "Lock token isn't a string"); 1330 1331 path = path_item->u.string->data; 1332 full_path = svn_fspath__join(sb->fs_path->data, 1333 svn_relpath_canonicalize(path, pool), 1334 pool); 1335 1336 if (! lookup_access(pool, sb, conn, svn_authz_write, 1337 full_path, TRUE)) 1338 return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL, 1339 sb, conn, pool); 1340 1341 token = token_item->u.string->data; 1342 SVN_ERR(svn_fs_access_add_lock_token2(fs_access, path, token)); 1343 } 1344 1345 return SVN_NO_ERROR; 1346} 1347 1348/* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors. 1349 LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */ 1350static svn_error_t *unlock_paths(const apr_array_header_t *lock_tokens, 1351 server_baton_t *sb, 1352 svn_ra_svn_conn_t *conn, 1353 apr_pool_t *pool) 1354{ 1355 int i; 1356 apr_pool_t *iterpool; 1357 1358 iterpool = svn_pool_create(pool); 1359 1360 for (i = 0; i < lock_tokens->nelts; ++i) 1361 { 1362 svn_ra_svn_item_t *item, *path_item, *token_item; 1363 const char *path, *token, *full_path; 1364 svn_error_t *err; 1365 svn_pool_clear(iterpool); 1366 1367 item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t); 1368 path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t); 1369 token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t); 1370 1371 path = path_item->u.string->data; 1372 token = token_item->u.string->data; 1373 1374 full_path = svn_fspath__join(sb->fs_path->data, 1375 svn_relpath_canonicalize(path, iterpool), 1376 iterpool); 1377 1378 /* The lock may have become defunct after the commit, so ignore such 1379 errors. */ 1380 err = svn_repos_fs_unlock(sb->repos, full_path, token, 1381 FALSE, iterpool); 1382 log_server_error(err, sb, conn, iterpool); 1383 svn_error_clear(err); 1384 } 1385 1386 svn_pool_destroy(iterpool); 1387 1388 return SVN_NO_ERROR; 1389} 1390 1391static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1392 apr_array_header_t *params, void *baton) 1393{ 1394 server_baton_t *b = baton; 1395 const char *log_msg = NULL, 1396 *date = NULL, 1397 *author = NULL, 1398 *post_commit_err = NULL; 1399 apr_array_header_t *lock_tokens; 1400 svn_boolean_t keep_locks; 1401 apr_array_header_t *revprop_list = NULL; 1402 apr_hash_t *revprop_table; 1403 const svn_delta_editor_t *editor; 1404 void *edit_baton; 1405 svn_boolean_t aborted; 1406 commit_callback_baton_t ccb; 1407 svn_revnum_t new_rev; 1408 authz_baton_t ab; 1409 1410 ab.server = b; 1411 ab.conn = conn; 1412 1413 if (params->nelts == 1) 1414 { 1415 /* Clients before 1.2 don't send lock-tokens, keep-locks, 1416 and rev-props fields. */ 1417 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &log_msg)); 1418 lock_tokens = NULL; 1419 keep_locks = TRUE; 1420 revprop_list = NULL; 1421 } 1422 else 1423 { 1424 /* Clients before 1.5 don't send the rev-props field. */ 1425 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "clb?l", &log_msg, 1426 &lock_tokens, &keep_locks, 1427 &revprop_list)); 1428 } 1429 1430 /* The handling for locks is a little problematic, because the 1431 protocol won't let us send several auth requests once one has 1432 succeeded. So we request write access and a username before 1433 adding tokens (if we have any), and subsequently fail if a lock 1434 violates authz. */ 1435 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, 1436 NULL, 1437 (lock_tokens && lock_tokens->nelts))); 1438 1439 /* Authorize the lock tokens and give them to the FS if we got 1440 any. */ 1441 if (lock_tokens && lock_tokens->nelts) 1442 SVN_CMD_ERR(add_lock_tokens(conn, lock_tokens, b, pool)); 1443 1444 if (revprop_list) 1445 SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table)); 1446 else 1447 { 1448 revprop_table = apr_hash_make(pool); 1449 svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG, 1450 svn_string_create(log_msg, pool)); 1451 } 1452 1453 /* Get author from the baton, making sure clients can't circumvent 1454 the authentication via the revision props. */ 1455 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR, 1456 b->user ? svn_string_create(b->user, pool) : NULL); 1457 1458 ccb.pool = pool; 1459 ccb.new_rev = &new_rev; 1460 ccb.date = &date; 1461 ccb.author = &author; 1462 ccb.post_commit_err = &post_commit_err; 1463 /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */ 1464 SVN_CMD_ERR(svn_repos_get_commit_editor5 1465 (&editor, &edit_baton, b->repos, NULL, 1466 svn_path_uri_decode(b->repos_url, pool), 1467 b->fs_path->data, revprop_table, 1468 commit_done, &ccb, 1469 authz_commit_cb, &ab, pool)); 1470 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 1471 SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton, 1472 &aborted, FALSE)); 1473 if (!aborted) 1474 { 1475 SVN_ERR(log_command(b, conn, pool, "%s", 1476 svn_log__commit(new_rev, pool))); 1477 SVN_ERR(trivial_auth_request(conn, pool, b)); 1478 1479 /* In tunnel mode, deltify before answering the client, because 1480 answering may cause the client to terminate the connection 1481 and thus kill the server. But otherwise, deltify after 1482 answering the client, to avoid user-visible delay. */ 1483 1484 if (b->tunnel) 1485 SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool)); 1486 1487 /* Unlock the paths. */ 1488 if (! keep_locks && lock_tokens && lock_tokens->nelts) 1489 SVN_ERR(unlock_paths(lock_tokens, b, conn, pool)); 1490 1491 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)", 1492 new_rev, date, author, post_commit_err)); 1493 1494 if (! b->tunnel) 1495 SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool)); 1496 } 1497 return SVN_NO_ERROR; 1498} 1499 1500static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1501 apr_array_header_t *params, void *baton) 1502{ 1503 server_baton_t *b = baton; 1504 const char *path, *full_path, *hex_digest; 1505 svn_revnum_t rev; 1506 svn_fs_root_t *root; 1507 svn_stream_t *contents; 1508 apr_hash_t *props = NULL; 1509 apr_array_header_t *inherited_props; 1510 svn_string_t write_str; 1511 char buf[4096]; 1512 apr_size_t len; 1513 svn_boolean_t want_props, want_contents; 1514 apr_uint64_t wants_inherited_props; 1515 svn_checksum_t *checksum; 1516 svn_error_t *err, *write_err; 1517 int i; 1518 authz_baton_t ab; 1519 1520 ab.server = b; 1521 ab.conn = conn; 1522 1523 /* Parse arguments. */ 1524 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?B", &path, &rev, 1525 &want_props, &want_contents, 1526 &wants_inherited_props)); 1527 1528 full_path = svn_fspath__join(b->fs_path->data, 1529 svn_relpath_canonicalize(path, pool), pool); 1530 1531 /* Check authorizations */ 1532 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 1533 full_path, FALSE)); 1534 1535 if (!SVN_IS_VALID_REVNUM(rev)) 1536 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 1537 1538 SVN_ERR(log_command(b, conn, pool, "%s", 1539 svn_log__get_file(full_path, rev, 1540 want_contents, want_props, pool))); 1541 1542 /* Fetch the properties and a stream for the contents. */ 1543 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); 1544 SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, 1545 full_path, TRUE, pool)); 1546 hex_digest = svn_checksum_to_cstring_display(checksum, pool); 1547 if (want_props || wants_inherited_props) 1548 SVN_CMD_ERR(get_props(&props, &inherited_props, &ab, root, full_path, 1549 pool)); 1550 if (want_contents) 1551 SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool)); 1552 1553 /* Send successful command response with revision and props. */ 1554 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success", 1555 hex_digest, rev)); 1556 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props)); 1557 1558 if (wants_inherited_props) 1559 { 1560 apr_pool_t *iterpool = svn_pool_create(pool); 1561 1562 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!")); 1563 for (i = 0; i < inherited_props->nelts; i++) 1564 { 1565 svn_prop_inherited_item_t *iprop = 1566 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 1567 1568 svn_pool_clear(iterpool); 1569 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", 1570 iprop->path_or_url)); 1571 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); 1572 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", 1573 iprop->path_or_url)); 1574 } 1575 svn_pool_destroy(iterpool); 1576 } 1577 1578 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1579 1580 /* Now send the file's contents. */ 1581 if (want_contents) 1582 { 1583 err = SVN_NO_ERROR; 1584 while (1) 1585 { 1586 len = sizeof(buf); 1587 err = svn_stream_read(contents, buf, &len); 1588 if (err) 1589 break; 1590 if (len > 0) 1591 { 1592 write_str.data = buf; 1593 write_str.len = len; 1594 SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str)); 1595 } 1596 if (len < sizeof(buf)) 1597 { 1598 err = svn_stream_close(contents); 1599 break; 1600 } 1601 } 1602 write_err = svn_ra_svn__write_cstring(conn, pool, ""); 1603 if (write_err) 1604 { 1605 svn_error_clear(err); 1606 return write_err; 1607 } 1608 SVN_CMD_ERR(err); 1609 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 1610 } 1611 1612 return SVN_NO_ERROR; 1613} 1614 1615static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1616 apr_array_header_t *params, void *baton) 1617{ 1618 server_baton_t *b = baton; 1619 const char *path, *full_path; 1620 svn_revnum_t rev; 1621 apr_hash_t *entries, *props = NULL; 1622 apr_array_header_t *inherited_props; 1623 apr_hash_index_t *hi; 1624 svn_fs_root_t *root; 1625 apr_pool_t *subpool; 1626 svn_boolean_t want_props, want_contents; 1627 apr_uint64_t wants_inherited_props; 1628 apr_uint64_t dirent_fields; 1629 apr_array_header_t *dirent_fields_list = NULL; 1630 svn_ra_svn_item_t *elt; 1631 int i; 1632 authz_baton_t ab; 1633 1634 ab.server = b; 1635 ab.conn = conn; 1636 1637 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?l?B", &path, &rev, 1638 &want_props, &want_contents, 1639 &dirent_fields_list, 1640 &wants_inherited_props)); 1641 1642 if (! dirent_fields_list) 1643 { 1644 dirent_fields = SVN_DIRENT_ALL; 1645 } 1646 else 1647 { 1648 dirent_fields = 0; 1649 1650 for (i = 0; i < dirent_fields_list->nelts; ++i) 1651 { 1652 elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t); 1653 1654 if (elt->kind != SVN_RA_SVN_WORD) 1655 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1656 "Dirent field not a string"); 1657 1658 if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0) 1659 dirent_fields |= SVN_DIRENT_KIND; 1660 else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0) 1661 dirent_fields |= SVN_DIRENT_SIZE; 1662 else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0) 1663 dirent_fields |= SVN_DIRENT_HAS_PROPS; 1664 else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0) 1665 dirent_fields |= SVN_DIRENT_CREATED_REV; 1666 else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0) 1667 dirent_fields |= SVN_DIRENT_TIME; 1668 else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0) 1669 dirent_fields |= SVN_DIRENT_LAST_AUTHOR; 1670 } 1671 } 1672 1673 full_path = svn_fspath__join(b->fs_path->data, 1674 svn_relpath_canonicalize(path, pool), pool); 1675 1676 /* Check authorizations */ 1677 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 1678 full_path, FALSE)); 1679 1680 if (!SVN_IS_VALID_REVNUM(rev)) 1681 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 1682 1683 SVN_ERR(log_command(b, conn, pool, "%s", 1684 svn_log__get_dir(full_path, rev, 1685 want_contents, want_props, 1686 dirent_fields, pool))); 1687 1688 /* Fetch the root of the appropriate revision. */ 1689 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); 1690 1691 /* Fetch the directory's explicit and/or inherited properties 1692 if requested. */ 1693 if (want_props || wants_inherited_props) 1694 SVN_CMD_ERR(get_props(&props, &inherited_props, &ab, root, full_path, 1695 pool)); 1696 1697 /* Begin response ... */ 1698 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev)); 1699 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props)); 1700 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!")); 1701 1702 /* Fetch the directory entries if requested and send them immediately. */ 1703 if (want_contents) 1704 { 1705 /* Use epoch for a placeholder for a missing date. */ 1706 const char *missing_date = svn_time_to_cstring(0, pool); 1707 1708 SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool)); 1709 1710 /* Transform the hash table's FS entries into dirents. This probably 1711 * belongs in libsvn_repos. */ 1712 subpool = svn_pool_create(pool); 1713 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 1714 { 1715 const char *name = svn__apr_hash_index_key(hi); 1716 svn_fs_dirent_t *fsent = svn__apr_hash_index_val(hi); 1717 const char *file_path; 1718 1719 /* The fields in the entry tuple. */ 1720 svn_node_kind_t entry_kind = svn_node_none; 1721 svn_filesize_t entry_size = 0; 1722 svn_boolean_t has_props = FALSE; 1723 /* If 'created rev' was not requested, send 0. We can't use 1724 * SVN_INVALID_REVNUM as the tuple field is not optional. 1725 * See the email thread on dev@, 2012-03-28, subject 1726 * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra", 1727 * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */ 1728 svn_revnum_t created_rev = 0; 1729 const char *cdate = NULL; 1730 const char *last_author = NULL; 1731 1732 svn_pool_clear(subpool); 1733 1734 file_path = svn_fspath__join(full_path, name, subpool); 1735 if (! lookup_access(subpool, b, conn, svn_authz_read, 1736 file_path, FALSE)) 1737 continue; 1738 1739 if (dirent_fields & SVN_DIRENT_KIND) 1740 entry_kind = fsent->kind; 1741 1742 if (dirent_fields & SVN_DIRENT_SIZE) 1743 if (entry_kind != svn_node_dir) 1744 SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path, 1745 subpool)); 1746 1747 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 1748 { 1749 apr_hash_t *file_props; 1750 1751 /* has_props */ 1752 SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path, 1753 subpool)); 1754 has_props = (apr_hash_count(file_props) > 0); 1755 } 1756 1757 if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR) 1758 || (dirent_fields & SVN_DIRENT_TIME) 1759 || (dirent_fields & SVN_DIRENT_CREATED_REV)) 1760 { 1761 /* created_rev, last_author, time */ 1762 SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev, 1763 &cdate, 1764 &last_author, 1765 root, 1766 file_path, 1767 subpool)); 1768 } 1769 1770 /* The client does not properly handle a missing CDATE. For 1771 interoperability purposes, we must fill in some junk. 1772 1773 See libsvn_ra_svn/client.c:ra_svn_get_dir() */ 1774 if (cdate == NULL) 1775 cdate = missing_date; 1776 1777 /* Send the entry. */ 1778 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name, 1779 svn_node_kind_to_word(entry_kind), 1780 (apr_uint64_t) entry_size, 1781 has_props, created_rev, 1782 cdate, last_author)); 1783 } 1784 svn_pool_destroy(subpool); 1785 } 1786 1787 if (wants_inherited_props) 1788 { 1789 apr_pool_t *iterpool = svn_pool_create(pool); 1790 1791 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!")); 1792 for (i = 0; i < inherited_props->nelts; i++) 1793 { 1794 svn_prop_inherited_item_t *iprop = 1795 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 1796 1797 svn_pool_clear(iterpool); 1798 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", 1799 iprop->path_or_url)); 1800 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); 1801 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", 1802 iprop->path_or_url)); 1803 } 1804 svn_pool_destroy(iterpool); 1805 } 1806 1807 /* Finish response. */ 1808 return svn_ra_svn__write_tuple(conn, pool, "!))"); 1809} 1810 1811static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1812 apr_array_header_t *params, void *baton) 1813{ 1814 server_baton_t *b = baton; 1815 svn_revnum_t rev; 1816 const char *target, *full_path, *depth_word; 1817 svn_boolean_t recurse; 1818 apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */ 1819 apr_uint64_t ignore_ancestry; /* Optional; default FALSE */ 1820 /* Default to unknown. Old clients won't send depth, but we'll 1821 handle that by converting recurse if necessary. */ 1822 svn_depth_t depth = svn_depth_unknown; 1823 svn_boolean_t is_checkout; 1824 1825 /* Parse the arguments. */ 1826 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cb?wB?B", &rev, &target, 1827 &recurse, &depth_word, 1828 &send_copyfrom_args, &ignore_ancestry)); 1829 target = svn_relpath_canonicalize(target, pool); 1830 1831 if (depth_word) 1832 depth = svn_depth_from_word(depth_word); 1833 else 1834 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); 1835 1836 full_path = svn_fspath__join(b->fs_path->data, target, pool); 1837 /* Check authorization and authenticate the user if necessary. */ 1838 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); 1839 1840 if (!SVN_IS_VALID_REVNUM(rev)) 1841 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 1842 1843 SVN_ERR(accept_report(&is_checkout, NULL, 1844 conn, pool, b, rev, target, NULL, TRUE, 1845 depth, 1846 (send_copyfrom_args == TRUE) /* send_copyfrom_args */, 1847 (ignore_ancestry == TRUE) /* ignore_ancestry */)); 1848 if (is_checkout) 1849 { 1850 SVN_ERR(log_command(b, conn, pool, "%s", 1851 svn_log__checkout(full_path, rev, 1852 depth, pool))); 1853 } 1854 else 1855 { 1856 SVN_ERR(log_command(b, conn, pool, "%s", 1857 svn_log__update(full_path, rev, depth, 1858 send_copyfrom_args, pool))); 1859 } 1860 1861 return SVN_NO_ERROR; 1862} 1863 1864static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1865 apr_array_header_t *params, void *baton) 1866{ 1867 server_baton_t *b = baton; 1868 svn_revnum_t rev; 1869 const char *target, *depth_word; 1870 const char *switch_url, *switch_path; 1871 svn_boolean_t recurse; 1872 /* Default to unknown. Old clients won't send depth, but we'll 1873 handle that by converting recurse if necessary. */ 1874 svn_depth_t depth = svn_depth_unknown; 1875 apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */ 1876 apr_uint64_t ignore_ancestry; /* Optional; default TRUE */ 1877 1878 /* Parse the arguments. */ 1879 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbc?w?BB", &rev, &target, 1880 &recurse, &switch_url, &depth_word, 1881 &send_copyfrom_args, &ignore_ancestry)); 1882 target = svn_relpath_canonicalize(target, pool); 1883 switch_url = svn_uri_canonicalize(switch_url, pool); 1884 1885 if (depth_word) 1886 depth = svn_depth_from_word(depth_word); 1887 else 1888 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); 1889 1890 SVN_ERR(trivial_auth_request(conn, pool, b)); 1891 if (!SVN_IS_VALID_REVNUM(rev)) 1892 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 1893 1894 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool), 1895 svn_path_uri_decode(switch_url, pool), 1896 &switch_path)); 1897 1898 { 1899 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool); 1900 SVN_ERR(log_command(b, conn, pool, "%s", 1901 svn_log__switch(full_path, switch_path, rev, 1902 depth, pool))); 1903 } 1904 1905 return accept_report(NULL, NULL, 1906 conn, pool, b, rev, target, switch_path, TRUE, 1907 depth, 1908 (send_copyfrom_args == TRUE) /* send_copyfrom_args */, 1909 (ignore_ancestry != FALSE) /* ignore_ancestry */); 1910} 1911 1912static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1913 apr_array_header_t *params, void *baton) 1914{ 1915 server_baton_t *b = baton; 1916 svn_revnum_t rev; 1917 const char *target, *depth_word; 1918 svn_boolean_t recurse; 1919 /* Default to unknown. Old clients won't send depth, but we'll 1920 handle that by converting recurse if necessary. */ 1921 svn_depth_t depth = svn_depth_unknown; 1922 1923 /* Parse the arguments. */ 1924 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cb?(?r)?w", 1925 &target, &recurse, &rev, &depth_word)); 1926 target = svn_relpath_canonicalize(target, pool); 1927 1928 if (depth_word) 1929 depth = svn_depth_from_word(depth_word); 1930 else 1931 depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse); 1932 1933 SVN_ERR(trivial_auth_request(conn, pool, b)); 1934 if (!SVN_IS_VALID_REVNUM(rev)) 1935 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 1936 1937 { 1938 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool); 1939 SVN_ERR(log_command(b, conn, pool, "%s", 1940 svn_log__status(full_path, rev, depth, pool))); 1941 } 1942 1943 return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE, 1944 depth, FALSE, FALSE); 1945} 1946 1947static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1948 apr_array_header_t *params, void *baton) 1949{ 1950 server_baton_t *b = baton; 1951 svn_revnum_t rev; 1952 const char *target, *versus_url, *versus_path, *depth_word; 1953 svn_boolean_t recurse, ignore_ancestry; 1954 svn_boolean_t text_deltas; 1955 /* Default to unknown. Old clients won't send depth, but we'll 1956 handle that by converting recurse if necessary. */ 1957 svn_depth_t depth = svn_depth_unknown; 1958 1959 /* Parse the arguments. */ 1960 if (params->nelts == 5) 1961 { 1962 /* Clients before 1.4 don't send the text_deltas boolean or depth. */ 1963 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbc", &rev, &target, 1964 &recurse, &ignore_ancestry, &versus_url)); 1965 text_deltas = TRUE; 1966 depth_word = NULL; 1967 } 1968 else 1969 { 1970 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbcb?w", 1971 &rev, &target, &recurse, 1972 &ignore_ancestry, &versus_url, 1973 &text_deltas, &depth_word)); 1974 } 1975 target = svn_relpath_canonicalize(target, pool); 1976 versus_url = svn_uri_canonicalize(versus_url, pool); 1977 1978 if (depth_word) 1979 depth = svn_depth_from_word(depth_word); 1980 else 1981 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); 1982 1983 SVN_ERR(trivial_auth_request(conn, pool, b)); 1984 1985 if (!SVN_IS_VALID_REVNUM(rev)) 1986 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 1987 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool), 1988 svn_path_uri_decode(versus_url, pool), 1989 &versus_path)); 1990 1991 { 1992 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool); 1993 svn_revnum_t from_rev; 1994 SVN_ERR(accept_report(NULL, &from_rev, 1995 conn, pool, b, rev, target, versus_path, 1996 text_deltas, depth, FALSE, ignore_ancestry)); 1997 SVN_ERR(log_command(b, conn, pool, "%s", 1998 svn_log__diff(full_path, from_rev, versus_path, 1999 rev, depth, ignore_ancestry, 2000 pool))); 2001 } 2002 return SVN_NO_ERROR; 2003} 2004 2005/* Regardless of whether a client's capabilities indicate an 2006 understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO), 2007 we provide a response. 2008 2009 ASSUMPTION: When performing a 'merge' with two URLs at different 2010 revisions, the client will call this command more than once. */ 2011static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2012 apr_array_header_t *params, void *baton) 2013{ 2014 server_baton_t *b = baton; 2015 svn_revnum_t rev; 2016 apr_array_header_t *paths, *canonical_paths; 2017 svn_mergeinfo_catalog_t mergeinfo; 2018 int i; 2019 apr_hash_index_t *hi; 2020 const char *inherit_word; 2021 svn_mergeinfo_inheritance_t inherit; 2022 svn_boolean_t include_descendants; 2023 apr_pool_t *iterpool; 2024 authz_baton_t ab; 2025 2026 ab.server = b; 2027 ab.conn = conn; 2028 2029 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)wb", &paths, &rev, 2030 &inherit_word, &include_descendants)); 2031 inherit = svn_inheritance_from_word(inherit_word); 2032 2033 /* Canonicalize the paths which mergeinfo has been requested for. */ 2034 canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); 2035 for (i = 0; i < paths->nelts; i++) 2036 { 2037 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t); 2038 const char *full_path; 2039 2040 if (item->kind != SVN_RA_SVN_STRING) 2041 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2042 _("Path is not a string")); 2043 full_path = svn_relpath_canonicalize(item->u.string->data, pool); 2044 full_path = svn_fspath__join(b->fs_path->data, full_path, pool); 2045 APR_ARRAY_PUSH(canonical_paths, const char *) = full_path; 2046 } 2047 2048 SVN_ERR(log_command(b, conn, pool, "%s", 2049 svn_log__get_mergeinfo(canonical_paths, inherit, 2050 include_descendants, 2051 pool))); 2052 2053 SVN_ERR(trivial_auth_request(conn, pool, b)); 2054 SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos, 2055 canonical_paths, rev, 2056 inherit, 2057 include_descendants, 2058 authz_check_access_cb_func(b), &ab, 2059 pool)); 2060 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo, 2061 b->fs_path->data, pool)); 2062 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 2063 iterpool = svn_pool_create(pool); 2064 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) 2065 { 2066 const char *key = svn__apr_hash_index_key(hi); 2067 svn_mergeinfo_t value = svn__apr_hash_index_val(hi); 2068 svn_string_t *mergeinfo_string; 2069 2070 svn_pool_clear(iterpool); 2071 2072 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, value, iterpool)); 2073 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cs", key, 2074 mergeinfo_string)); 2075 } 2076 svn_pool_destroy(iterpool); 2077 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2078 2079 return SVN_NO_ERROR; 2080} 2081 2082/* Send a log entry to the client. */ 2083static svn_error_t *log_receiver(void *baton, 2084 svn_log_entry_t *log_entry, 2085 apr_pool_t *pool) 2086{ 2087 log_baton_t *b = baton; 2088 svn_ra_svn_conn_t *conn = b->conn; 2089 apr_hash_index_t *h; 2090 svn_boolean_t invalid_revnum = FALSE; 2091 char action[2]; 2092 const char *author, *date, *message; 2093 apr_uint64_t revprop_count; 2094 2095 if (log_entry->revision == SVN_INVALID_REVNUM) 2096 { 2097 /* If the stack depth is zero, we've seen the last revision, so don't 2098 send it, just return. */ 2099 if (b->stack_depth == 0) 2100 return SVN_NO_ERROR; 2101 2102 /* Because the svn protocol won't let us send an invalid revnum, we have 2103 to fudge here and send an additional flag. */ 2104 log_entry->revision = 0; 2105 invalid_revnum = TRUE; 2106 b->stack_depth--; 2107 } 2108 2109 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "(!")); 2110 if (log_entry->changed_paths2) 2111 { 2112 for (h = apr_hash_first(pool, log_entry->changed_paths2); h; 2113 h = apr_hash_next(h)) 2114 { 2115 const char *path = svn__apr_hash_index_key(h); 2116 svn_log_changed_path2_t *change = svn__apr_hash_index_val(h); 2117 2118 action[0] = change->action; 2119 action[1] = '\0'; 2120 SVN_ERR(svn_ra_svn__write_tuple( 2121 conn, pool, "cw(?cr)(cbb)", 2122 path, 2123 action, 2124 change->copyfrom_path, 2125 change->copyfrom_rev, 2126 svn_node_kind_to_word(change->node_kind), 2127 /* text_modified and props_modified are never unknown */ 2128 change->text_modified == svn_tristate_true, 2129 change->props_modified == svn_tristate_true)); 2130 } 2131 } 2132 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); 2133 svn_compat_log_revprops_clear(log_entry->revprops); 2134 if (log_entry->revprops) 2135 revprop_count = apr_hash_count(log_entry->revprops); 2136 else 2137 revprop_count = 0; 2138 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!", 2139 log_entry->revision, 2140 author, date, message, 2141 log_entry->has_children, 2142 invalid_revnum, revprop_count)); 2143 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, log_entry->revprops)); 2144 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b", 2145 log_entry->subtractive_merge)); 2146 2147 if (log_entry->has_children) 2148 b->stack_depth++; 2149 2150 return SVN_NO_ERROR; 2151} 2152 2153static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2154 apr_array_header_t *params, void *baton) 2155{ 2156 svn_error_t *err, *write_err; 2157 server_baton_t *b = baton; 2158 svn_revnum_t start_rev, end_rev; 2159 const char *full_path; 2160 svn_boolean_t send_changed_paths, strict_node, include_merged_revisions; 2161 apr_array_header_t *paths, *full_paths, *revprop_items, *revprops; 2162 char *revprop_word; 2163 svn_ra_svn_item_t *elt; 2164 int i; 2165 apr_uint64_t limit, include_merged_revs_param; 2166 log_baton_t lb; 2167 authz_baton_t ab; 2168 2169 ab.server = b; 2170 ab.conn = conn; 2171 2172 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths, 2173 &start_rev, &end_rev, &send_changed_paths, 2174 &strict_node, &limit, 2175 &include_merged_revs_param, 2176 &revprop_word, &revprop_items)); 2177 2178 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 2179 include_merged_revisions = FALSE; 2180 else 2181 include_merged_revisions = (svn_boolean_t) include_merged_revs_param; 2182 2183 if (revprop_word == NULL) 2184 /* pre-1.5 client */ 2185 revprops = svn_compat_log_revprops_in(pool); 2186 else if (strcmp(revprop_word, "all-revprops") == 0) 2187 revprops = NULL; 2188 else if (strcmp(revprop_word, "revprops") == 0) 2189 { 2190 SVN_ERR_ASSERT(revprop_items); 2191 2192 revprops = apr_array_make(pool, revprop_items->nelts, 2193 sizeof(char *)); 2194 for (i = 0; i < revprop_items->nelts; i++) 2195 { 2196 elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t); 2197 if (elt->kind != SVN_RA_SVN_STRING) 2198 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2199 _("Log revprop entry not a string")); 2200 APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data; 2201 } 2202 } 2203 else 2204 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2205 _("Unknown revprop word '%s' in log command"), 2206 revprop_word); 2207 2208 /* If we got an unspecified number then the user didn't send us anything, 2209 so we assume no limit. If it's larger than INT_MAX then someone is 2210 messing with us, since we know the svn client libraries will never send 2211 us anything that big, so play it safe and default to no limit. */ 2212 if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX) 2213 limit = 0; 2214 2215 full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); 2216 for (i = 0; i < paths->nelts; i++) 2217 { 2218 elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t); 2219 if (elt->kind != SVN_RA_SVN_STRING) 2220 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2221 _("Log path entry not a string")); 2222 full_path = svn_relpath_canonicalize(elt->u.string->data, pool), 2223 full_path = svn_fspath__join(b->fs_path->data, full_path, pool); 2224 APR_ARRAY_PUSH(full_paths, const char *) = full_path; 2225 } 2226 SVN_ERR(trivial_auth_request(conn, pool, b)); 2227 2228 SVN_ERR(log_command(b, conn, pool, "%s", 2229 svn_log__log(full_paths, start_rev, end_rev, 2230 (int) limit, send_changed_paths, 2231 strict_node, include_merged_revisions, 2232 revprops, pool))); 2233 2234 /* Get logs. (Can't report errors back to the client at this point.) */ 2235 lb.fs_path = b->fs_path->data; 2236 lb.conn = conn; 2237 lb.stack_depth = 0; 2238 err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev, 2239 (int) limit, send_changed_paths, strict_node, 2240 include_merged_revisions, revprops, 2241 authz_check_access_cb_func(b), &ab, log_receiver, 2242 &lb, pool); 2243 2244 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2245 if (write_err) 2246 { 2247 svn_error_clear(err); 2248 return write_err; 2249 } 2250 SVN_CMD_ERR(err); 2251 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2252 return SVN_NO_ERROR; 2253} 2254 2255static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2256 apr_array_header_t *params, void *baton) 2257{ 2258 server_baton_t *b = baton; 2259 svn_revnum_t rev; 2260 const char *path, *full_path; 2261 svn_fs_root_t *root; 2262 svn_node_kind_t kind; 2263 2264 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev)); 2265 full_path = svn_fspath__join(b->fs_path->data, 2266 svn_relpath_canonicalize(path, pool), pool); 2267 2268 /* Check authorizations */ 2269 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 2270 full_path, FALSE)); 2271 2272 if (!SVN_IS_VALID_REVNUM(rev)) 2273 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 2274 2275 SVN_ERR(log_command(b, conn, pool, "check-path %s@%d", 2276 svn_path_uri_encode(full_path, pool), rev)); 2277 2278 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); 2279 SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool)); 2280 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w", 2281 svn_node_kind_to_word(kind))); 2282 return SVN_NO_ERROR; 2283} 2284 2285static svn_error_t *stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2286 apr_array_header_t *params, void *baton) 2287{ 2288 server_baton_t *b = baton; 2289 svn_revnum_t rev; 2290 const char *path, *full_path, *cdate; 2291 svn_fs_root_t *root; 2292 svn_dirent_t *dirent; 2293 2294 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev)); 2295 full_path = svn_fspath__join(b->fs_path->data, 2296 svn_relpath_canonicalize(path, pool), pool); 2297 2298 /* Check authorizations */ 2299 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 2300 full_path, FALSE)); 2301 2302 if (!SVN_IS_VALID_REVNUM(rev)) 2303 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 2304 2305 SVN_ERR(log_command(b, conn, pool, "stat %s@%d", 2306 svn_path_uri_encode(full_path, pool), rev)); 2307 2308 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); 2309 SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool)); 2310 2311 /* Need to return the equivalent of "(?l)", since that's what the 2312 client is reading. */ 2313 2314 if (dirent == NULL) 2315 { 2316 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()")); 2317 return SVN_NO_ERROR; 2318 } 2319 2320 cdate = (dirent->time == (time_t) -1) ? NULL 2321 : svn_time_to_cstring(dirent->time, pool); 2322 2323 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))", 2324 svn_node_kind_to_word(dirent->kind), 2325 (apr_uint64_t) dirent->size, 2326 dirent->has_props, dirent->created_rev, 2327 cdate, dirent->last_author)); 2328 2329 return SVN_NO_ERROR; 2330} 2331 2332static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2333 apr_array_header_t *params, void *baton) 2334{ 2335 svn_error_t *err, *write_err; 2336 server_baton_t *b = baton; 2337 svn_revnum_t revision; 2338 apr_array_header_t *location_revisions, *loc_revs_proto; 2339 svn_ra_svn_item_t *elt; 2340 int i; 2341 const char *relative_path; 2342 svn_revnum_t peg_revision; 2343 apr_hash_t *fs_locations; 2344 const char *abs_path; 2345 authz_baton_t ab; 2346 2347 ab.server = b; 2348 ab.conn = conn; 2349 2350 /* Parse the arguments. */ 2351 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crl", &relative_path, 2352 &peg_revision, 2353 &loc_revs_proto)); 2354 relative_path = svn_relpath_canonicalize(relative_path, pool); 2355 2356 abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool); 2357 2358 location_revisions = apr_array_make(pool, loc_revs_proto->nelts, 2359 sizeof(svn_revnum_t)); 2360 for (i = 0; i < loc_revs_proto->nelts; i++) 2361 { 2362 elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t); 2363 if (elt->kind != SVN_RA_SVN_NUMBER) 2364 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2365 "Get-locations location revisions entry " 2366 "not a revision number"); 2367 revision = (svn_revnum_t)(elt->u.number); 2368 APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision; 2369 } 2370 SVN_ERR(trivial_auth_request(conn, pool, b)); 2371 SVN_ERR(log_command(b, conn, pool, "%s", 2372 svn_log__get_locations(abs_path, peg_revision, 2373 location_revisions, pool))); 2374 2375 /* All the parameters are fine - let's perform the query against the 2376 * repository. */ 2377 2378 /* We store both err and write_err here, so the client will get 2379 * the "done" even if there was an error in fetching the results. */ 2380 2381 err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path, 2382 peg_revision, location_revisions, 2383 authz_check_access_cb_func(b), &ab, 2384 pool); 2385 2386 /* Now, write the results to the connection. */ 2387 if (!err) 2388 { 2389 if (fs_locations) 2390 { 2391 apr_hash_index_t *iter; 2392 2393 for (iter = apr_hash_first(pool, fs_locations); iter; 2394 iter = apr_hash_next(iter)) 2395 { 2396 const svn_revnum_t *iter_key = svn__apr_hash_index_key(iter); 2397 const char *iter_value = svn__apr_hash_index_val(iter); 2398 2399 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc", 2400 *iter_key, iter_value)); 2401 } 2402 } 2403 } 2404 2405 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2406 if (write_err) 2407 { 2408 svn_error_clear(err); 2409 return write_err; 2410 } 2411 SVN_CMD_ERR(err); 2412 2413 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2414 2415 return SVN_NO_ERROR; 2416} 2417 2418static svn_error_t *gls_receiver(svn_location_segment_t *segment, 2419 void *baton, 2420 apr_pool_t *pool) 2421{ 2422 svn_ra_svn_conn_t *conn = baton; 2423 return svn_ra_svn__write_tuple(conn, pool, "rr(?c)", 2424 segment->range_start, 2425 segment->range_end, 2426 segment->path); 2427} 2428 2429static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn, 2430 apr_pool_t *pool, 2431 apr_array_header_t *params, 2432 void *baton) 2433{ 2434 svn_error_t *err, *write_err; 2435 server_baton_t *b = baton; 2436 svn_revnum_t peg_revision, start_rev, end_rev; 2437 const char *relative_path; 2438 const char *abs_path; 2439 authz_baton_t ab; 2440 2441 ab.server = b; 2442 ab.conn = conn; 2443 2444 /* Parse the arguments. */ 2445 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)(?r)", 2446 &relative_path, &peg_revision, 2447 &start_rev, &end_rev)); 2448 relative_path = svn_relpath_canonicalize(relative_path, pool); 2449 2450 abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool); 2451 2452 if (SVN_IS_VALID_REVNUM(start_rev) 2453 && SVN_IS_VALID_REVNUM(end_rev) 2454 && (end_rev > start_rev)) 2455 { 2456 err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 2457 "Get-location-segments end revision must not be " 2458 "younger than start revision"); 2459 return log_fail_and_flush(err, b, conn, pool); 2460 } 2461 2462 if (SVN_IS_VALID_REVNUM(peg_revision) 2463 && SVN_IS_VALID_REVNUM(start_rev) 2464 && (start_rev > peg_revision)) 2465 { 2466 err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 2467 "Get-location-segments start revision must not " 2468 "be younger than peg revision"); 2469 return log_fail_and_flush(err, b, conn, pool); 2470 } 2471 2472 SVN_ERR(trivial_auth_request(conn, pool, b)); 2473 SVN_ERR(log_command(baton, conn, pool, "%s", 2474 svn_log__get_location_segments(abs_path, peg_revision, 2475 start_rev, end_rev, 2476 pool))); 2477 2478 /* All the parameters are fine - let's perform the query against the 2479 * repository. */ 2480 2481 /* We store both err and write_err here, so the client will get 2482 * the "done" even if there was an error in fetching the results. */ 2483 2484 err = svn_repos_node_location_segments(b->repos, abs_path, 2485 peg_revision, start_rev, end_rev, 2486 gls_receiver, (void *)conn, 2487 authz_check_access_cb_func(b), &ab, 2488 pool); 2489 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2490 if (write_err) 2491 { 2492 svn_error_clear(err); 2493 return write_err; 2494 } 2495 SVN_CMD_ERR(err); 2496 2497 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2498 2499 return SVN_NO_ERROR; 2500} 2501 2502/* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the 2503 client as a string. */ 2504static svn_error_t *svndiff_handler(void *baton, const char *data, 2505 apr_size_t *len) 2506{ 2507 file_revs_baton_t *b = baton; 2508 svn_string_t str; 2509 2510 str.data = data; 2511 str.len = *len; 2512 return svn_ra_svn__write_string(b->conn, b->pool, &str); 2513} 2514 2515/* This implements svn_close_fn_t. Mark the end of the data by writing an 2516 empty string to the client. */ 2517static svn_error_t *svndiff_close_handler(void *baton) 2518{ 2519 file_revs_baton_t *b = baton; 2520 2521 SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, "")); 2522 return SVN_NO_ERROR; 2523} 2524 2525/* This implements the svn_repos_file_rev_handler_t interface. */ 2526static svn_error_t *file_rev_handler(void *baton, const char *path, 2527 svn_revnum_t rev, apr_hash_t *rev_props, 2528 svn_boolean_t merged_revision, 2529 svn_txdelta_window_handler_t *d_handler, 2530 void **d_baton, 2531 apr_array_header_t *prop_diffs, 2532 apr_pool_t *pool) 2533{ 2534 file_revs_baton_t *frb = baton; 2535 svn_stream_t *stream; 2536 2537 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!", 2538 path, rev)); 2539 SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props)); 2540 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!")); 2541 SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs)); 2542 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision)); 2543 2544 /* Store the pool for the delta stream. */ 2545 frb->pool = pool; 2546 2547 /* Prepare for the delta or just write an empty string. */ 2548 if (d_handler) 2549 { 2550 stream = svn_stream_create(baton, pool); 2551 svn_stream_set_write(stream, svndiff_handler); 2552 svn_stream_set_close(stream, svndiff_close_handler); 2553 2554 /* If the connection does not support SVNDIFF1 or if we don't want to use 2555 * compression, use the non-compressing "version 0" implementation */ 2556 if ( svn_ra_svn_compression_level(frb->conn) > 0 2557 && svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1)) 2558 svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 1, 2559 svn_ra_svn_compression_level(frb->conn), pool); 2560 else 2561 svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 0, 2562 svn_ra_svn_compression_level(frb->conn), pool); 2563 } 2564 else 2565 SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, "")); 2566 2567 return SVN_NO_ERROR; 2568} 2569 2570static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2571 apr_array_header_t *params, void *baton) 2572{ 2573 server_baton_t *b = baton; 2574 svn_error_t *err, *write_err; 2575 file_revs_baton_t frb; 2576 svn_revnum_t start_rev, end_rev; 2577 const char *path; 2578 const char *full_path; 2579 apr_uint64_t include_merged_revs_param; 2580 svn_boolean_t include_merged_revisions; 2581 authz_baton_t ab; 2582 2583 ab.server = b; 2584 ab.conn = conn; 2585 2586 /* Parse arguments. */ 2587 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)?B", 2588 &path, &start_rev, &end_rev, 2589 &include_merged_revs_param)); 2590 path = svn_relpath_canonicalize(path, pool); 2591 SVN_ERR(trivial_auth_request(conn, pool, b)); 2592 full_path = svn_fspath__join(b->fs_path->data, path, pool); 2593 2594 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 2595 include_merged_revisions = FALSE; 2596 else 2597 include_merged_revisions = (svn_boolean_t) include_merged_revs_param; 2598 2599 SVN_ERR(log_command(b, conn, pool, "%s", 2600 svn_log__get_file_revs(full_path, start_rev, end_rev, 2601 include_merged_revisions, 2602 pool))); 2603 2604 frb.conn = conn; 2605 frb.pool = NULL; 2606 2607 err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev, 2608 include_merged_revisions, 2609 authz_check_access_cb_func(b), &ab, 2610 file_rev_handler, &frb, pool); 2611 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2612 if (write_err) 2613 { 2614 svn_error_clear(err); 2615 return write_err; 2616 } 2617 SVN_CMD_ERR(err); 2618 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2619 2620 return SVN_NO_ERROR; 2621} 2622 2623static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2624 apr_array_header_t *params, void *baton) 2625{ 2626 server_baton_t *b = baton; 2627 const char *path; 2628 const char *comment; 2629 const char *full_path; 2630 svn_boolean_t steal_lock; 2631 svn_revnum_t current_rev; 2632 svn_lock_t *l; 2633 2634 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment, 2635 &steal_lock, ¤t_rev)); 2636 full_path = svn_fspath__join(b->fs_path->data, 2637 svn_relpath_canonicalize(path, pool), pool); 2638 2639 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, 2640 full_path, TRUE)); 2641 SVN_ERR(log_command(b, conn, pool, "%s", 2642 svn_log__lock_one_path(full_path, steal_lock, pool))); 2643 2644 SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0, 2645 0, /* No expiration time. */ 2646 current_rev, steal_lock, pool)); 2647 2648 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success")); 2649 SVN_ERR(write_lock(conn, pool, l)); 2650 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)")); 2651 2652 return SVN_NO_ERROR; 2653} 2654 2655static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2656 apr_array_header_t *params, void *baton) 2657{ 2658 server_baton_t *b = baton; 2659 apr_array_header_t *path_revs; 2660 const char *comment; 2661 svn_boolean_t steal_lock; 2662 int i; 2663 apr_pool_t *subpool; 2664 const char *path; 2665 const char *full_path; 2666 svn_revnum_t current_rev; 2667 apr_array_header_t *log_paths; 2668 svn_lock_t *l; 2669 svn_error_t *err = SVN_NO_ERROR, *write_err; 2670 2671 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock, 2672 &path_revs)); 2673 2674 subpool = svn_pool_create(pool); 2675 2676 /* Because we can only send a single auth reply per request, we send 2677 a reply before parsing the lock commands. This means an authz 2678 access denial will abort the processing of the locks and return 2679 an error. */ 2680 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE)); 2681 2682 /* Loop through the lock requests. */ 2683 log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path)); 2684 for (i = 0; i < path_revs->nelts; ++i) 2685 { 2686 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i, 2687 svn_ra_svn_item_t); 2688 2689 svn_pool_clear(subpool); 2690 2691 if (item->kind != SVN_RA_SVN_LIST) 2692 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2693 "Lock requests should be list of lists"); 2694 2695 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "c(?r)", &path, 2696 ¤t_rev)); 2697 2698 /* Allocate the full_path out of pool so it will survive for use 2699 * by operational logging, after this loop. */ 2700 full_path = svn_fspath__join(b->fs_path->data, 2701 svn_relpath_canonicalize(path, subpool), 2702 pool); 2703 APR_ARRAY_PUSH(log_paths, const char *) = full_path; 2704 2705 if (! lookup_access(pool, b, conn, svn_authz_write, full_path, TRUE)) 2706 { 2707 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL, 2708 b, conn, pool); 2709 break; 2710 } 2711 2712 err = svn_repos_fs_lock(&l, b->repos, full_path, 2713 NULL, comment, FALSE, 2714 0, /* No expiration time. */ 2715 current_rev, 2716 steal_lock, subpool); 2717 2718 if (err) 2719 { 2720 if (SVN_ERR_IS_LOCK_ERROR(err)) 2721 { 2722 write_err = svn_ra_svn__write_cmd_failure(conn, pool, err); 2723 svn_error_clear(err); 2724 err = NULL; 2725 SVN_ERR(write_err); 2726 } 2727 else 2728 break; 2729 } 2730 else 2731 { 2732 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w!", "success")); 2733 SVN_ERR(write_lock(conn, subpool, l)); 2734 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "!")); 2735 } 2736 } 2737 2738 svn_pool_destroy(subpool); 2739 2740 SVN_ERR(log_command(b, conn, pool, "%s", 2741 svn_log__lock(log_paths, steal_lock, pool))); 2742 2743 /* NOTE: err might contain a fatal locking error from the loop above. */ 2744 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2745 if (!write_err) 2746 SVN_CMD_ERR(err); 2747 svn_error_clear(err); 2748 SVN_ERR(write_err); 2749 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2750 2751 return SVN_NO_ERROR; 2752} 2753 2754static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2755 apr_array_header_t *params, void *baton) 2756{ 2757 server_baton_t *b = baton; 2758 const char *path, *token, *full_path; 2759 svn_boolean_t break_lock; 2760 2761 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b", &path, &token, 2762 &break_lock)); 2763 2764 full_path = svn_fspath__join(b->fs_path->data, 2765 svn_relpath_canonicalize(path, pool), pool); 2766 2767 /* Username required unless break_lock was specified. */ 2768 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, 2769 full_path, ! break_lock)); 2770 SVN_ERR(log_command(b, conn, pool, "%s", 2771 svn_log__unlock_one_path(full_path, break_lock, pool))); 2772 2773 SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock, 2774 pool)); 2775 2776 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2777 2778 return SVN_NO_ERROR; 2779} 2780 2781static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2782 apr_array_header_t *params, void *baton) 2783{ 2784 server_baton_t *b = baton; 2785 svn_boolean_t break_lock; 2786 apr_array_header_t *unlock_tokens; 2787 int i; 2788 apr_pool_t *subpool; 2789 const char *path; 2790 const char *full_path; 2791 apr_array_header_t *log_paths; 2792 const char *token; 2793 svn_error_t *err = SVN_NO_ERROR, *write_err; 2794 2795 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock, 2796 &unlock_tokens)); 2797 2798 /* Username required unless break_lock was specified. */ 2799 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock)); 2800 2801 subpool = svn_pool_create(pool); 2802 2803 /* Loop through the unlock requests. */ 2804 log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path)); 2805 for (i = 0; i < unlock_tokens->nelts; i++) 2806 { 2807 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i, 2808 svn_ra_svn_item_t); 2809 2810 svn_pool_clear(subpool); 2811 2812 if (item->kind != SVN_RA_SVN_LIST) 2813 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2814 "Unlock request should be a list of lists"); 2815 2816 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path, 2817 &token)); 2818 2819 /* Allocate the full_path out of pool so it will survive for use 2820 * by operational logging, after this loop. */ 2821 full_path = svn_fspath__join(b->fs_path->data, 2822 svn_relpath_canonicalize(path, subpool), 2823 pool); 2824 APR_ARRAY_PUSH(log_paths, const char *) = full_path; 2825 2826 if (! lookup_access(subpool, b, conn, svn_authz_write, full_path, 2827 ! break_lock)) 2828 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, 2829 error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, 2830 NULL, NULL, 2831 b, conn, pool), 2832 NULL); 2833 2834 err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock, 2835 subpool); 2836 if (err) 2837 { 2838 if (SVN_ERR_IS_UNLOCK_ERROR(err)) 2839 { 2840 write_err = svn_ra_svn__write_cmd_failure(conn, pool, err); 2841 svn_error_clear(err); 2842 err = NULL; 2843 SVN_ERR(write_err); 2844 } 2845 else 2846 break; 2847 } 2848 else 2849 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success", 2850 path)); 2851 } 2852 2853 svn_pool_destroy(subpool); 2854 2855 SVN_ERR(log_command(b, conn, pool, "%s", 2856 svn_log__unlock(log_paths, break_lock, pool))); 2857 2858 /* NOTE: err might contain a fatal unlocking error from the loop above. */ 2859 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2860 if (! write_err) 2861 SVN_CMD_ERR(err); 2862 svn_error_clear(err); 2863 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2864 2865 return SVN_NO_ERROR; 2866} 2867 2868static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2869 apr_array_header_t *params, void *baton) 2870{ 2871 server_baton_t *b = baton; 2872 const char *path; 2873 const char *full_path; 2874 svn_lock_t *l; 2875 2876 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path)); 2877 2878 full_path = svn_fspath__join(b->fs_path->data, 2879 svn_relpath_canonicalize(path, pool), pool); 2880 2881 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 2882 full_path, FALSE)); 2883 SVN_ERR(log_command(b, conn, pool, "get-lock %s", 2884 svn_path_uri_encode(full_path, pool))); 2885 2886 SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool)); 2887 2888 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 2889 if (l) 2890 SVN_ERR(write_lock(conn, pool, l)); 2891 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2892 2893 return SVN_NO_ERROR; 2894} 2895 2896static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2897 apr_array_header_t *params, void *baton) 2898{ 2899 server_baton_t *b = baton; 2900 const char *path; 2901 const char *full_path; 2902 const char *depth_word; 2903 svn_depth_t depth; 2904 apr_hash_t *locks; 2905 apr_hash_index_t *hi; 2906 svn_error_t *err; 2907 authz_baton_t ab; 2908 2909 ab.server = b; 2910 ab.conn = conn; 2911 2912 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c?(?w)", &path, &depth_word)); 2913 2914 depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity; 2915 if ((depth != svn_depth_empty) && 2916 (depth != svn_depth_files) && 2917 (depth != svn_depth_immediates) && 2918 (depth != svn_depth_infinity)) 2919 { 2920 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 2921 "Invalid 'depth' specified in get-locks request"); 2922 return log_fail_and_flush(err, b, conn, pool); 2923 } 2924 2925 full_path = svn_fspath__join(b->fs_path->data, 2926 svn_relpath_canonicalize(path, pool), pool); 2927 2928 SVN_ERR(trivial_auth_request(conn, pool, b)); 2929 2930 SVN_ERR(log_command(b, conn, pool, "get-locks %s", 2931 svn_path_uri_encode(full_path, pool))); 2932 SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repos, full_path, depth, 2933 authz_check_access_cb_func(b), &ab, 2934 pool)); 2935 2936 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 2937 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi)) 2938 { 2939 svn_lock_t *l = svn__apr_hash_index_val(hi); 2940 2941 SVN_ERR(write_lock(conn, pool, l)); 2942 } 2943 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2944 2945 return SVN_NO_ERROR; 2946} 2947 2948static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn, 2949 server_baton_t *b, 2950 svn_revnum_t rev, 2951 svn_revnum_t low_water_mark, 2952 svn_boolean_t send_deltas, 2953 apr_pool_t *pool) 2954{ 2955 const svn_delta_editor_t *editor; 2956 void *edit_baton; 2957 svn_fs_root_t *root; 2958 svn_error_t *err; 2959 authz_baton_t ab; 2960 2961 ab.server = b; 2962 ab.conn = conn; 2963 2964 SVN_ERR(log_command(b, conn, pool, 2965 svn_log__replay(b->fs_path->data, rev, pool))); 2966 2967 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); 2968 2969 err = svn_fs_revision_root(&root, b->fs, rev, pool); 2970 2971 if (! err) 2972 err = svn_repos_replay2(root, b->fs_path->data, low_water_mark, 2973 send_deltas, editor, edit_baton, 2974 authz_check_access_cb_func(b), &ab, pool); 2975 2976 if (err) 2977 svn_error_clear(editor->abort_edit(edit_baton, pool)); 2978 SVN_CMD_ERR(err); 2979 2980 return svn_ra_svn__write_cmd_finish_replay(conn, pool); 2981} 2982 2983static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2984 apr_array_header_t *params, void *baton) 2985{ 2986 svn_revnum_t rev, low_water_mark; 2987 svn_boolean_t send_deltas; 2988 server_baton_t *b = baton; 2989 2990 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrb", &rev, &low_water_mark, 2991 &send_deltas)); 2992 2993 SVN_ERR(trivial_auth_request(conn, pool, b)); 2994 2995 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark, 2996 send_deltas, pool)); 2997 2998 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2999 3000 return SVN_NO_ERROR; 3001} 3002 3003static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 3004 apr_array_header_t *params, void *baton) 3005{ 3006 svn_revnum_t start_rev, end_rev, rev, low_water_mark; 3007 svn_boolean_t send_deltas; 3008 server_baton_t *b = baton; 3009 apr_pool_t *iterpool; 3010 authz_baton_t ab; 3011 3012 ab.server = b; 3013 ab.conn = conn; 3014 3015 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrrb", &start_rev, 3016 &end_rev, &low_water_mark, 3017 &send_deltas)); 3018 3019 SVN_ERR(trivial_auth_request(conn, pool, b)); 3020 3021 iterpool = svn_pool_create(pool); 3022 for (rev = start_rev; rev <= end_rev; rev++) 3023 { 3024 apr_hash_t *props; 3025 3026 svn_pool_clear(iterpool); 3027 3028 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev, 3029 authz_check_access_cb_func(b), 3030 &ab, 3031 iterpool)); 3032 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops")); 3033 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props)); 3034 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)")); 3035 3036 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark, 3037 send_deltas, iterpool)); 3038 3039 } 3040 svn_pool_destroy(iterpool); 3041 3042 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 3043 3044 return SVN_NO_ERROR; 3045} 3046 3047static svn_error_t * 3048get_deleted_rev(svn_ra_svn_conn_t *conn, 3049 apr_pool_t *pool, 3050 apr_array_header_t *params, 3051 void *baton) 3052{ 3053 server_baton_t *b = baton; 3054 const char *path, *full_path; 3055 svn_revnum_t peg_revision; 3056 svn_revnum_t end_revision; 3057 svn_revnum_t revision_deleted; 3058 3059 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crr", 3060 &path, &peg_revision, &end_revision)); 3061 full_path = svn_fspath__join(b->fs_path->data, 3062 svn_relpath_canonicalize(path, pool), pool); 3063 SVN_ERR(log_command(b, conn, pool, "get-deleted-rev")); 3064 SVN_ERR(trivial_auth_request(conn, pool, b)); 3065 SVN_ERR(svn_repos_deleted_rev(b->fs, full_path, peg_revision, end_revision, 3066 &revision_deleted, pool)); 3067 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted)); 3068 return SVN_NO_ERROR; 3069} 3070 3071static svn_error_t * 3072get_inherited_props(svn_ra_svn_conn_t *conn, 3073 apr_pool_t *pool, 3074 apr_array_header_t *params, 3075 void *baton) 3076{ 3077 server_baton_t *b = baton; 3078 const char *path, *full_path; 3079 svn_revnum_t rev; 3080 svn_fs_root_t *root; 3081 apr_array_header_t *inherited_props; 3082 int i; 3083 apr_pool_t *iterpool = svn_pool_create(pool); 3084 authz_baton_t ab; 3085 3086 ab.server = b; 3087 ab.conn = conn; 3088 3089 /* Parse arguments. */ 3090 SVN_ERR(svn_ra_svn__parse_tuple(params, iterpool, "c(?r)", &path, &rev)); 3091 3092 full_path = svn_fspath__join(b->fs_path->data, 3093 svn_relpath_canonicalize(path, iterpool), 3094 pool); 3095 3096 /* Check authorizations */ 3097 SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read, 3098 full_path, FALSE)); 3099 3100 if (!SVN_IS_VALID_REVNUM(rev)) 3101 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 3102 3103 SVN_ERR(log_command(b, conn, pool, "%s", 3104 svn_log__get_inherited_props(full_path, rev, 3105 iterpool))); 3106 3107 /* Fetch the properties and a stream for the contents. */ 3108 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, iterpool)); 3109 SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool)); 3110 3111 /* Send successful command response with revision and props. */ 3112 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success")); 3113 3114 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!")); 3115 3116 for (i = 0; i < inherited_props->nelts; i++) 3117 { 3118 svn_prop_inherited_item_t *iprop = 3119 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 3120 3121 svn_pool_clear(iterpool); 3122 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", 3123 iprop->path_or_url)); 3124 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); 3125 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", 3126 iprop->path_or_url)); 3127 } 3128 3129 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))")); 3130 svn_pool_destroy(iterpool); 3131 return SVN_NO_ERROR; 3132} 3133 3134static const svn_ra_svn_cmd_entry_t main_commands[] = { 3135 { "reparent", reparent }, 3136 { "get-latest-rev", get_latest_rev }, 3137 { "get-dated-rev", get_dated_rev }, 3138 { "change-rev-prop", change_rev_prop }, 3139 { "change-rev-prop2",change_rev_prop2 }, 3140 { "rev-proplist", rev_proplist }, 3141 { "rev-prop", rev_prop }, 3142 { "commit", commit }, 3143 { "get-file", get_file }, 3144 { "get-dir", get_dir }, 3145 { "update", update }, 3146 { "switch", switch_cmd }, 3147 { "status", status }, 3148 { "diff", diff }, 3149 { "get-mergeinfo", get_mergeinfo }, 3150 { "log", log_cmd }, 3151 { "check-path", check_path }, 3152 { "stat", stat_cmd }, 3153 { "get-locations", get_locations }, 3154 { "get-location-segments", get_location_segments }, 3155 { "get-file-revs", get_file_revs }, 3156 { "lock", lock }, 3157 { "lock-many", lock_many }, 3158 { "unlock", unlock }, 3159 { "unlock-many", unlock_many }, 3160 { "get-lock", get_lock }, 3161 { "get-locks", get_locks }, 3162 { "replay", replay }, 3163 { "replay-range", replay_range }, 3164 { "get-deleted-rev", get_deleted_rev }, 3165 { "get-iprops", get_inherited_props }, 3166 { NULL } 3167}; 3168 3169/* Skip past the scheme part of a URL, including the tunnel specification 3170 * if present. Return NULL if the scheme part is invalid for ra_svn. */ 3171static const char *skip_scheme_part(const char *url) 3172{ 3173 if (strncmp(url, "svn", 3) != 0) 3174 return NULL; 3175 url += 3; 3176 if (*url == '+') 3177 url += strcspn(url, ":"); 3178 if (strncmp(url, "://", 3) != 0) 3179 return NULL; 3180 return url + 3; 3181} 3182 3183/* Check that PATH is a valid repository path, meaning it doesn't contain any 3184 '..' path segments. 3185 NOTE: This is similar to svn_path_is_backpath_present, but that function 3186 assumes the path separator is '/'. This function also checks for 3187 segments delimited by the local path separator. */ 3188static svn_boolean_t 3189repos_path_valid(const char *path) 3190{ 3191 const char *s = path; 3192 3193 while (*s) 3194 { 3195 /* Scan for the end of the segment. */ 3196 while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR) 3197 ++path; 3198 3199 /* Check for '..'. */ 3200#ifdef WIN32 3201 /* On Windows, don't allow sequences of more than one character 3202 consisting of just dots and spaces. Win32 functions treat 3203 paths such as ".. " and "......." inconsistently. Make sure 3204 no one can escape out of the root. */ 3205 if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s)) 3206 return FALSE; 3207#else /* ! WIN32 */ 3208 if (path - s == 2 && s[0] == '.' && s[1] == '.') 3209 return FALSE; 3210#endif 3211 3212 /* Skip all separators. */ 3213 while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR)) 3214 ++path; 3215 s = path; 3216 } 3217 3218 return TRUE; 3219} 3220 3221/* Look for the repository given by URL, using ROOT as the virtual 3222 * repository root. If we find one, fill in the repos, fs, cfg, 3223 * repos_url, and fs_path fields of B. Set B->repos's client 3224 * capabilities to CAPABILITIES, which must be at least as long-lived 3225 * as POOL, and whose elements are SVN_RA_CAPABILITY_*. 3226 */ 3227static svn_error_t *find_repos(const char *url, const char *root, 3228 server_baton_t *b, 3229 svn_ra_svn_conn_t *conn, 3230 const apr_array_header_t *capabilities, 3231 apr_pool_t *pool) 3232{ 3233 const char *path, *full_path, *repos_root, *fs_path, *hooks_env; 3234 svn_stringbuf_t *url_buf; 3235 3236 /* Skip past the scheme and authority part. */ 3237 path = skip_scheme_part(url); 3238 if (path == NULL) 3239 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 3240 "Non-svn URL passed to svn server: '%s'", url); 3241 3242 if (! b->vhost) 3243 { 3244 path = strchr(path, '/'); 3245 if (path == NULL) 3246 path = ""; 3247 } 3248 path = svn_relpath_canonicalize(path, pool); 3249 path = svn_path_uri_decode(path, pool); 3250 3251 /* Ensure that it isn't possible to escape the root by disallowing 3252 '..' segments. */ 3253 if (!repos_path_valid(path)) 3254 return svn_error_create(SVN_ERR_BAD_FILENAME, NULL, 3255 "Couldn't determine repository path"); 3256 3257 /* Join the server-configured root with the client path. */ 3258 full_path = svn_dirent_join(svn_dirent_canonicalize(root, pool), 3259 path, pool); 3260 3261 /* Search for a repository in the full path. */ 3262 repos_root = svn_repos_find_root_path(full_path, pool); 3263 if (!repos_root) 3264 return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL, 3265 "No repository found in '%s'", url); 3266 3267 /* Open the repository and fill in b with the resulting information. */ 3268 SVN_ERR(svn_repos_open2(&b->repos, repos_root, b->fs_config, pool)); 3269 SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities)); 3270 b->fs = svn_repos_fs(b->repos); 3271 fs_path = full_path + strlen(repos_root); 3272 b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool); 3273 url_buf = svn_stringbuf_create(url, pool); 3274 svn_path_remove_components(url_buf, 3275 svn_path_component_count(b->fs_path->data)); 3276 b->repos_url = url_buf->data; 3277 b->authz_repos_name = svn_dirent_is_child(root, repos_root, pool); 3278 if (b->authz_repos_name == NULL) 3279 b->repos_name = svn_dirent_basename(repos_root, pool); 3280 else 3281 b->repos_name = b->authz_repos_name; 3282 b->repos_name = svn_path_uri_encode(b->repos_name, pool); 3283 3284 /* If the svnserve configuration has not been loaded then load it from the 3285 * repository. */ 3286 if (NULL == b->cfg) 3287 { 3288 b->base = svn_repos_conf_dir(b->repos, pool); 3289 3290 SVN_ERR(svn_config_read3(&b->cfg, svn_repos_svnserve_conf(b->repos, pool), 3291 FALSE, /* must_exist */ 3292 FALSE, /* section_names_case_sensitive */ 3293 FALSE, /* option_names_case_sensitive */ 3294 pool)); 3295 SVN_ERR(load_pwdb_config(b, conn, pool)); 3296 SVN_ERR(load_authz_config(b, conn, repos_root, pool)); 3297 } 3298 /* svnserve.conf has been loaded via the --config-file option so need 3299 * to load pwdb and authz. */ 3300 else 3301 { 3302 SVN_ERR(load_pwdb_config(b, conn, pool)); 3303 SVN_ERR(load_authz_config(b, conn, repos_root, pool)); 3304 } 3305 3306#ifdef SVN_HAVE_SASL 3307 /* Should we use Cyrus SASL? */ 3308 SVN_ERR(svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL, 3309 SVN_CONFIG_OPTION_USE_SASL, FALSE)); 3310#endif 3311 3312 /* Use the repository UUID as the default realm. */ 3313 SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool)); 3314 svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL, 3315 SVN_CONFIG_OPTION_REALM, b->realm); 3316 3317 /* Make sure it's possible for the client to authenticate. Note 3318 that this doesn't take into account any authz configuration read 3319 above, because we can't know about access it grants until paths 3320 are given by the client. */ 3321 if (get_access(b, UNAUTHENTICATED) == NO_ACCESS 3322 && (get_access(b, AUTHENTICATED) == NO_ACCESS 3323 || (!b->tunnel_user && !b->pwdb && !b->use_sasl))) 3324 return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 3325 "No access allowed to this repository", 3326 b, conn, pool); 3327 3328 /* Configure hook script environment variables. */ 3329 svn_config_get(b->cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL, 3330 SVN_CONFIG_OPTION_HOOKS_ENV, NULL); 3331 if (hooks_env) 3332 hooks_env = svn_dirent_internal_style(hooks_env, pool); 3333 SVN_ERR(svn_repos_hooks_setenv(b->repos, hooks_env, pool)); 3334 3335 return SVN_NO_ERROR; 3336} 3337 3338/* Compute the authentication name EXTERNAL should be able to get, if any. */ 3339static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool) 3340{ 3341 /* Only offer EXTERNAL for connections tunneled over a login agent. */ 3342 if (!params->tunnel) 3343 return NULL; 3344 3345 /* If a tunnel user was provided on the command line, use that. */ 3346 if (params->tunnel_user) 3347 return params->tunnel_user; 3348 3349 return svn_user_get_name(pool); 3350} 3351 3352static void 3353fs_warning_func(void *baton, svn_error_t *err) 3354{ 3355 fs_warning_baton_t *b = baton; 3356 log_server_error(err, b->server, b->conn, b->pool); 3357 /* TODO: Keep log_pool in the server baton, cleared after every log? */ 3358 svn_pool_clear(b->pool); 3359} 3360 3361/* Return the normalized repository-relative path for the given PATH 3362 * (may be a URL, full path or relative path) and fs contained in the 3363 * server baton BATON. Allocate the result in POOL. 3364 */ 3365static const char * 3366get_normalized_repo_rel_path(void *baton, 3367 const char *path, 3368 apr_pool_t *pool) 3369{ 3370 server_baton_t *sb = baton; 3371 3372 if (svn_path_is_url(path)) 3373 { 3374 /* This is a copyfrom URL. */ 3375 path = svn_uri_skip_ancestor(sb->repos_url, path, pool); 3376 path = svn_fspath__canonicalize(path, pool); 3377 } 3378 else 3379 { 3380 /* This is a base-relative path. */ 3381 if ((path)[0] != '/') 3382 /* Get an absolute path for use in the FS. */ 3383 path = svn_fspath__join(sb->fs_path->data, path, pool); 3384 } 3385 3386 return path; 3387} 3388 3389/* Get the revision root for REVISION in fs given by server baton BATON 3390 * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM. 3391 * Use POOL for allocations. 3392 */ 3393static svn_error_t * 3394get_revision_root(svn_fs_root_t **fs_root, 3395 void *baton, 3396 svn_revnum_t revision, 3397 apr_pool_t *pool) 3398{ 3399 server_baton_t *sb = baton; 3400 3401 if (!SVN_IS_VALID_REVNUM(revision)) 3402 SVN_ERR(svn_fs_youngest_rev(&revision, sb->fs, pool)); 3403 3404 SVN_ERR(svn_fs_revision_root(fs_root, sb->fs, revision, pool)); 3405 3406 return SVN_NO_ERROR; 3407} 3408 3409static svn_error_t * 3410fetch_props_func(apr_hash_t **props, 3411 void *baton, 3412 const char *path, 3413 svn_revnum_t base_revision, 3414 apr_pool_t *result_pool, 3415 apr_pool_t *scratch_pool) 3416{ 3417 svn_fs_root_t *fs_root; 3418 svn_error_t *err; 3419 3420 path = get_normalized_repo_rel_path(baton, path, scratch_pool); 3421 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); 3422 3423 err = svn_fs_node_proplist(props, fs_root, path, result_pool); 3424 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 3425 { 3426 svn_error_clear(err); 3427 *props = apr_hash_make(result_pool); 3428 return SVN_NO_ERROR; 3429 } 3430 else if (err) 3431 return svn_error_trace(err); 3432 3433 return SVN_NO_ERROR; 3434} 3435 3436static svn_error_t * 3437fetch_kind_func(svn_node_kind_t *kind, 3438 void *baton, 3439 const char *path, 3440 svn_revnum_t base_revision, 3441 apr_pool_t *scratch_pool) 3442{ 3443 svn_fs_root_t *fs_root; 3444 3445 path = get_normalized_repo_rel_path(baton, path, scratch_pool); 3446 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); 3447 3448 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); 3449 3450 return SVN_NO_ERROR; 3451} 3452 3453static svn_error_t * 3454fetch_base_func(const char **filename, 3455 void *baton, 3456 const char *path, 3457 svn_revnum_t base_revision, 3458 apr_pool_t *result_pool, 3459 apr_pool_t *scratch_pool) 3460{ 3461 svn_stream_t *contents; 3462 svn_stream_t *file_stream; 3463 const char *tmp_filename; 3464 svn_fs_root_t *fs_root; 3465 svn_error_t *err; 3466 3467 path = get_normalized_repo_rel_path(baton, path, scratch_pool); 3468 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); 3469 3470 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); 3471 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 3472 { 3473 svn_error_clear(err); 3474 *filename = NULL; 3475 return SVN_NO_ERROR; 3476 } 3477 else if (err) 3478 return svn_error_trace(err); 3479 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, 3480 svn_io_file_del_on_pool_cleanup, 3481 scratch_pool, scratch_pool)); 3482 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); 3483 3484 *filename = apr_pstrdup(result_pool, tmp_filename); 3485 3486 return SVN_NO_ERROR; 3487} 3488 3489svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params, 3490 apr_pool_t *pool) 3491{ 3492 svn_error_t *err, *io_err; 3493 apr_uint64_t ver; 3494 const char *uuid, *client_url, *ra_client_string, *client_string; 3495 apr_array_header_t *caplist, *cap_words; 3496 server_baton_t b; 3497 fs_warning_baton_t warn_baton; 3498 svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(pool); 3499 3500 b.tunnel = params->tunnel; 3501 b.tunnel_user = get_tunnel_user(params, pool); 3502 b.read_only = params->read_only; 3503 b.user = NULL; 3504 b.username_case = params->username_case; 3505 b.authz_user = NULL; 3506 b.base = params->base; 3507 b.cfg = params->cfg; 3508 b.pwdb = NULL; 3509 b.authzdb = NULL; 3510 b.realm = NULL; 3511 b.log_file = params->log_file; 3512 b.pool = pool; 3513 b.use_sasl = FALSE; 3514 b.vhost = params->vhost; 3515 3516 /* construct FS configuration parameters */ 3517 b.fs_config = apr_hash_make(pool); 3518 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, 3519 params->cache_txdeltas ? "1" :"0"); 3520 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, 3521 params->cache_fulltexts ? "1" :"0"); 3522 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, 3523 params->cache_revprops ? "1" :"0"); 3524 3525 /* Send greeting. We don't support version 1 any more, so we can 3526 * send an empty mechlist. */ 3527 if (params->compression_level > 0) 3528 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwwww)", 3529 (apr_uint64_t) 2, (apr_uint64_t) 2, 3530 SVN_RA_SVN_CAP_EDIT_PIPELINE, 3531 SVN_RA_SVN_CAP_SVNDIFF1, 3532 SVN_RA_SVN_CAP_ABSENT_ENTRIES, 3533 SVN_RA_SVN_CAP_COMMIT_REVPROPS, 3534 SVN_RA_SVN_CAP_DEPTH, 3535 SVN_RA_SVN_CAP_LOG_REVPROPS, 3536 SVN_RA_SVN_CAP_ATOMIC_REVPROPS, 3537 SVN_RA_SVN_CAP_PARTIAL_REPLAY, 3538 SVN_RA_SVN_CAP_INHERITED_PROPS, 3539 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS, 3540 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE 3541 )); 3542 else 3543 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwww)", 3544 (apr_uint64_t) 2, (apr_uint64_t) 2, 3545 SVN_RA_SVN_CAP_EDIT_PIPELINE, 3546 SVN_RA_SVN_CAP_ABSENT_ENTRIES, 3547 SVN_RA_SVN_CAP_COMMIT_REVPROPS, 3548 SVN_RA_SVN_CAP_DEPTH, 3549 SVN_RA_SVN_CAP_LOG_REVPROPS, 3550 SVN_RA_SVN_CAP_ATOMIC_REVPROPS, 3551 SVN_RA_SVN_CAP_PARTIAL_REPLAY, 3552 SVN_RA_SVN_CAP_INHERITED_PROPS, 3553 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS, 3554 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE 3555 )); 3556 3557 /* Read client response, which we assume to be in version 2 format: 3558 * version, capability list, and client URL; then we do an auth 3559 * request. */ 3560 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "nlc?c(?c)", 3561 &ver, &caplist, &client_url, 3562 &ra_client_string, 3563 &client_string)); 3564 if (ver != 2) 3565 return SVN_NO_ERROR; 3566 3567 client_url = svn_uri_canonicalize(client_url, pool); 3568 SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist)); 3569 3570 /* All released versions of Subversion support edit-pipeline, 3571 * so we do not accept connections from clients that do not. */ 3572 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) 3573 return SVN_NO_ERROR; 3574 3575 /* find_repos needs the capabilities as a list of words (eventually 3576 they get handed to the start-commit hook). While we could add a 3577 new interface to re-retrieve them from conn and convert the 3578 result to a list, it's simpler to just convert caplist by hand 3579 here, since we already have it and turning 'svn_ra_svn_item_t's 3580 into 'const char *'s is pretty easy. 3581 3582 We only record capabilities we care about. The client may report 3583 more (because it doesn't know what the server cares about). */ 3584 { 3585 int i; 3586 svn_ra_svn_item_t *item; 3587 3588 cap_words = apr_array_make(pool, 1, sizeof(const char *)); 3589 for (i = 0; i < caplist->nelts; i++) 3590 { 3591 item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t); 3592 /* ra_svn_set_capabilities() already type-checked for us */ 3593 if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0) 3594 { 3595 APR_ARRAY_PUSH(cap_words, const char *) 3596 = SVN_RA_CAPABILITY_MERGEINFO; 3597 } 3598 /* Save for operational log. */ 3599 if (cap_log->len > 0) 3600 svn_stringbuf_appendcstr(cap_log, " "); 3601 svn_stringbuf_appendcstr(cap_log, item->u.word); 3602 } 3603 } 3604 3605 err = find_repos(client_url, params->root, &b, conn, cap_words, pool); 3606 if (!err) 3607 { 3608 SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE)); 3609 if (current_access(&b) == NO_ACCESS) 3610 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 3611 "Not authorized for access", 3612 &b, conn, pool); 3613 } 3614 if (err) 3615 { 3616 log_error(err, b.log_file, svn_ra_svn_conn_remote_host(conn), 3617 b.user, NULL, pool); 3618 io_err = svn_ra_svn__write_cmd_failure(conn, pool, err); 3619 svn_error_clear(err); 3620 SVN_ERR(io_err); 3621 return svn_ra_svn__flush(conn, pool); 3622 } 3623 3624 /* Log the open. */ 3625 if (ra_client_string == NULL || ra_client_string[0] == '\0') 3626 ra_client_string = "-"; 3627 else 3628 ra_client_string = svn_path_uri_encode(ra_client_string, pool); 3629 if (client_string == NULL || client_string[0] == '\0') 3630 client_string = "-"; 3631 else 3632 client_string = svn_path_uri_encode(client_string, pool); 3633 SVN_ERR(log_command(&b, conn, pool, 3634 "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s", 3635 ver, cap_log->data, 3636 svn_path_uri_encode(b.fs_path->data, pool), 3637 ra_client_string, client_string)); 3638 3639 warn_baton.server = &b; 3640 warn_baton.conn = conn; 3641 warn_baton.pool = svn_pool_create(pool); 3642 svn_fs_set_warning_func(b.fs, fs_warning_func, &warn_baton); 3643 3644 SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool)); 3645 3646 /* We can't claim mergeinfo capability until we know whether the 3647 repository supports mergeinfo (i.e., is not a 1.4 repository), 3648 but we don't get the repository url from the client until after 3649 we've already sent the initial list of server capabilities. So 3650 we list repository capabilities here, in our first response after 3651 the client has sent the url. */ 3652 { 3653 svn_boolean_t supports_mergeinfo; 3654 SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo, 3655 SVN_REPOS_CAPABILITY_MERGEINFO, pool)); 3656 3657 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cc(!", 3658 "success", uuid, b.repos_url)); 3659 if (supports_mergeinfo) 3660 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO)); 3661 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 3662 } 3663 3664 /* Set up editor shims. */ 3665 { 3666 svn_delta_shim_callbacks_t *callbacks = 3667 svn_delta_shim_callbacks_default(pool); 3668 3669 callbacks->fetch_base_func = fetch_base_func; 3670 callbacks->fetch_props_func = fetch_props_func; 3671 callbacks->fetch_kind_func = fetch_kind_func; 3672 callbacks->fetch_baton = &b; 3673 3674 SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks)); 3675 } 3676 3677 return svn_ra_svn__handle_commands2(conn, pool, main_commands, &b, FALSE); 3678} 3679