1/* 2 * cmdline.c : Helpers for command-line programs. 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#include <stdlib.h> /* for atexit() */ 26#include <stdio.h> /* for setvbuf() */ 27#include <locale.h> /* for setlocale() */ 28 29#ifndef WIN32 30#include <sys/types.h> 31#include <sys/stat.h> 32#include <fcntl.h> 33#include <unistd.h> 34#else 35#include <crtdbg.h> 36#include <io.h> 37#include <conio.h> 38#endif 39 40#include <apr.h> /* for STDIN_FILENO */ 41#include <apr_errno.h> /* for apr_strerror */ 42#include <apr_version.h> 43#if APR_VERSION_AT_LEAST(1,5,0) 44#include <apr_escape.h> 45#else 46#include "private/svn_dep_compat.h" 47#endif 48#include <apr_general.h> /* for apr_initialize/apr_terminate */ 49#include <apr_strings.h> /* for apr_snprintf */ 50#include <apr_env.h> /* for apr_env_get */ 51#include <apr_pools.h> 52#include <apr_signal.h> 53 54#include "svn_cmdline.h" 55#include "svn_ctype.h" 56#include "svn_dso.h" 57#include "svn_dirent_uri.h" 58#include "svn_hash.h" 59#include "svn_path.h" 60#include "svn_pools.h" 61#include "svn_error.h" 62#include "svn_nls.h" 63#include "svn_utf.h" 64#include "svn_auth.h" 65#include "svn_xml.h" 66#include "svn_base64.h" 67#include "svn_config.h" 68#include "svn_sorts.h" 69#include "svn_props.h" 70#include "svn_subst.h" 71 72#include "private/svn_cmdline_private.h" 73#include "private/svn_utf_private.h" 74#include "private/svn_sorts_private.h" 75#include "private/svn_string_private.h" 76 77#include "svn_private_config.h" 78 79#include "win32_crashrpt.h" 80 81#if defined(WIN32) && defined(_MSC_VER) && (_MSC_VER < 1400) 82/* Before Visual Studio 2005, the C runtime didn't handle encodings for the 83 for the stdio output handling. */ 84#define CMDLINE_USE_CUSTOM_ENCODING 85 86/* The stdin encoding. If null, it's the same as the native encoding. */ 87static const char *input_encoding = NULL; 88 89/* The stdout encoding. If null, it's the same as the native encoding. */ 90static const char *output_encoding = NULL; 91#elif defined(WIN32) && defined(_MSC_VER) 92/* For now limit this code to Visual C++, as the result is highly dependent 93 on the CRT implementation */ 94#define USE_WIN32_CONSOLE_SHORTCUT 95 96/* When TRUE, stdout/stderr is directly connected to a console */ 97static svn_boolean_t shortcut_stdout_to_console = FALSE; 98static svn_boolean_t shortcut_stderr_to_console = FALSE; 99#endif 100 101 102int 103svn_cmdline_init(const char *progname, FILE *error_stream) 104{ 105 apr_status_t status; 106 apr_pool_t *pool; 107 svn_error_t *err; 108 char prefix_buf[64]; /* 64 is probably bigger than most program names */ 109 110#ifndef WIN32 111 { 112 struct stat st; 113 114 /* The following makes sure that file descriptors 0 (stdin), 1 115 (stdout) and 2 (stderr) will not be "reused", because if 116 e.g. file descriptor 2 would be reused when opening a file, a 117 write to stderr would write to that file and most likely 118 corrupt it. */ 119 if ((fstat(0, &st) == -1 && open("/dev/null", O_RDONLY) == -1) || 120 (fstat(1, &st) == -1 && open("/dev/null", O_WRONLY) == -1) || 121 (fstat(2, &st) == -1 && open("/dev/null", O_WRONLY) == -1)) 122 { 123 if (error_stream) 124 fprintf(error_stream, "%s: error: cannot open '/dev/null'\n", 125 progname); 126 return EXIT_FAILURE; 127 } 128 } 129#endif 130 131 /* Ignore any errors encountered while attempting to change stream 132 buffering, as the streams should retain their default buffering 133 modes. */ 134 if (error_stream) 135 setvbuf(error_stream, NULL, _IONBF, 0); 136#ifndef WIN32 137 setvbuf(stdout, NULL, _IOLBF, 0); 138#endif 139 140#ifdef WIN32 141#ifdef CMDLINE_USE_CUSTOM_ENCODING 142 /* Initialize the input and output encodings. */ 143 { 144 static char input_encoding_buffer[16]; 145 static char output_encoding_buffer[16]; 146 147 apr_snprintf(input_encoding_buffer, sizeof input_encoding_buffer, 148 "CP%u", (unsigned) GetConsoleCP()); 149 input_encoding = input_encoding_buffer; 150 151 apr_snprintf(output_encoding_buffer, sizeof output_encoding_buffer, 152 "CP%u", (unsigned) GetConsoleOutputCP()); 153 output_encoding = output_encoding_buffer; 154 } 155#endif /* CMDLINE_USE_CUSTOM_ENCODING */ 156 157#ifdef SVN_USE_WIN32_CRASHHANDLER 158 if (!getenv("SVN_CMDLINE_DISABLE_CRASH_HANDLER")) 159 { 160 /* Attach (but don't load) the crash handler */ 161 SetUnhandledExceptionFilter(svn__unhandled_exception_filter); 162 163#if _MSC_VER >= 1400 164 /* ### This should work for VC++ 2002 (=1300) and later */ 165 /* Show the abort message on STDERR instead of a dialog to allow 166 scripts (e.g. our testsuite) to continue after an abort without 167 user intervention. Allow overriding for easier debugging. */ 168 if (!getenv("SVN_CMDLINE_USE_DIALOG_FOR_ABORT")) 169 { 170 /* In release mode: Redirect abort() errors to stderr */ 171 _set_error_mode(_OUT_TO_STDERR); 172 173 /* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr. 174 (Ignored in release builds) */ 175 _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR); 176 _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR); 177 _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR); 178 _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); 179 _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); 180 _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); 181 } 182#endif /* _MSC_VER >= 1400 */ 183 } 184#endif /* SVN_USE_WIN32_CRASHHANDLER */ 185 186#endif /* WIN32 */ 187 188 /* C programs default to the "C" locale. But because svn is supposed 189 to be i18n-aware, it should inherit the default locale of its 190 environment. */ 191 if (!setlocale(LC_ALL, "") 192 && !setlocale(LC_CTYPE, "")) 193 { 194 if (error_stream) 195 { 196 const char *env_vars[] = { "LC_ALL", "LC_CTYPE", "LANG", NULL }; 197 const char **env_var = &env_vars[0], *env_val = NULL; 198 while (*env_var) 199 { 200 env_val = getenv(*env_var); 201 if (env_val && env_val[0]) 202 break; 203 ++env_var; 204 } 205 206 if (!*env_var) 207 { 208 /* Unlikely. Can setlocale fail if no env vars are set? */ 209 --env_var; 210 env_val = "not set"; 211 } 212 213 fprintf(error_stream, 214 "%s: warning: cannot set LC_CTYPE locale\n" 215 "%s: warning: environment variable %s is %s\n" 216 "%s: warning: please check that your locale name is correct\n", 217 progname, progname, *env_var, env_val, progname); 218 } 219 } 220 221 /* Initialize the APR subsystem, and register an atexit() function 222 to Uninitialize that subsystem at program exit. */ 223 status = apr_initialize(); 224 if (status) 225 { 226 if (error_stream) 227 { 228 char buf[1024]; 229 apr_strerror(status, buf, sizeof(buf) - 1); 230 fprintf(error_stream, 231 "%s: error: cannot initialize APR: %s\n", 232 progname, buf); 233 } 234 return EXIT_FAILURE; 235 } 236 237 strncpy(prefix_buf, progname, sizeof(prefix_buf) - 3); 238 prefix_buf[sizeof(prefix_buf) - 3] = '\0'; 239 strcat(prefix_buf, ": "); 240 241 /* DSO pool must be created before any other pools used by the 242 application so that pool cleanup doesn't unload DSOs too 243 early. See docstring of svn_dso_initialize2(). */ 244 if ((err = svn_dso_initialize2())) 245 { 246 if (error_stream) 247 svn_handle_error2(err, error_stream, TRUE, prefix_buf); 248 249 svn_error_clear(err); 250 return EXIT_FAILURE; 251 } 252 253 if (0 > atexit(apr_terminate)) 254 { 255 if (error_stream) 256 fprintf(error_stream, 257 "%s: error: atexit registration failed\n", 258 progname); 259 return EXIT_FAILURE; 260 } 261 262 /* Create a pool for use by the UTF-8 routines. It will be cleaned 263 up by APR at exit time. */ 264 pool = svn_pool_create(NULL); 265 svn_utf_initialize2(FALSE, pool); 266 267 if ((err = svn_nls_init())) 268 { 269 if (error_stream) 270 svn_handle_error2(err, error_stream, TRUE, prefix_buf); 271 272 svn_error_clear(err); 273 return EXIT_FAILURE; 274 } 275 276#ifdef USE_WIN32_CONSOLE_SHORTCUT 277 if (_isatty(STDOUT_FILENO)) 278 { 279 DWORD ignored; 280 HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); 281 282 /* stdout is a char device handle, but is it the console? */ 283 if (GetConsoleMode(stdout_handle, &ignored)) 284 shortcut_stdout_to_console = TRUE; 285 286 /* Don't close stdout_handle */ 287 } 288 if (_isatty(STDERR_FILENO)) 289 { 290 DWORD ignored; 291 HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); 292 293 /* stderr is a char device handle, but is it the console? */ 294 if (GetConsoleMode(stderr_handle, &ignored)) 295 shortcut_stderr_to_console = TRUE; 296 297 /* Don't close stderr_handle */ 298 } 299#endif 300 301 return EXIT_SUCCESS; 302} 303 304 305svn_error_t * 306svn_cmdline_cstring_from_utf8(const char **dest, 307 const char *src, 308 apr_pool_t *pool) 309{ 310#ifdef CMDLINE_USE_CUSTOM_ENCODING 311 if (output_encoding != NULL) 312 return svn_utf_cstring_from_utf8_ex2(dest, src, output_encoding, pool); 313#endif 314 315 return svn_utf_cstring_from_utf8(dest, src, pool); 316} 317 318 319const char * 320svn_cmdline_cstring_from_utf8_fuzzy(const char *src, 321 apr_pool_t *pool) 322{ 323 return svn_utf__cstring_from_utf8_fuzzy(src, pool, 324 svn_cmdline_cstring_from_utf8); 325} 326 327 328svn_error_t * 329svn_cmdline_cstring_to_utf8(const char **dest, 330 const char *src, 331 apr_pool_t *pool) 332{ 333#ifdef CMDLINE_USE_CUSTOM_ENCODING 334 if (input_encoding != NULL) 335 return svn_utf_cstring_to_utf8_ex2(dest, src, input_encoding, pool); 336#endif 337 338 return svn_utf_cstring_to_utf8(dest, src, pool); 339} 340 341 342svn_error_t * 343svn_cmdline_path_local_style_from_utf8(const char **dest, 344 const char *src, 345 apr_pool_t *pool) 346{ 347 return svn_cmdline_cstring_from_utf8(dest, 348 svn_dirent_local_style(src, pool), 349 pool); 350} 351 352svn_error_t * 353svn_cmdline__stdin_readline(const char **result, 354 apr_pool_t *result_pool, 355 apr_pool_t *scratch_pool) 356{ 357 svn_stringbuf_t *buf = NULL; 358 svn_stream_t *stdin_stream = NULL; 359 svn_boolean_t oob = FALSE; 360 361 SVN_ERR(svn_stream_for_stdin2(&stdin_stream, TRUE, scratch_pool)); 362 SVN_ERR(svn_stream_readline(stdin_stream, &buf, APR_EOL_STR, &oob, result_pool)); 363 364 *result = buf->data; 365 366 return SVN_NO_ERROR; 367} 368 369svn_error_t * 370svn_cmdline_printf(apr_pool_t *pool, const char *fmt, ...) 371{ 372 const char *message; 373 va_list ap; 374 375 /* A note about encoding issues: 376 * APR uses the execution character set, but here we give it UTF-8 strings, 377 * both the fmt argument and any other string arguments. Since apr_pvsprintf 378 * only cares about and produces ASCII characters, this works under the 379 * assumption that all supported platforms use an execution character set 380 * with ASCII as a subset. 381 */ 382 383 va_start(ap, fmt); 384 message = apr_pvsprintf(pool, fmt, ap); 385 va_end(ap); 386 387 return svn_cmdline_fputs(message, stdout, pool); 388} 389 390svn_error_t * 391svn_cmdline_fprintf(FILE *stream, apr_pool_t *pool, const char *fmt, ...) 392{ 393 const char *message; 394 va_list ap; 395 396 /* See svn_cmdline_printf () for a note about character encoding issues. */ 397 398 va_start(ap, fmt); 399 message = apr_pvsprintf(pool, fmt, ap); 400 va_end(ap); 401 402 return svn_cmdline_fputs(message, stream, pool); 403} 404 405svn_error_t * 406svn_cmdline_fputs(const char *string, FILE* stream, apr_pool_t *pool) 407{ 408 svn_error_t *err; 409 const char *out; 410 411#ifdef USE_WIN32_CONSOLE_SHORTCUT 412 /* For legacy reasons the Visual C++ runtime converts output to the console 413 from the native 'ansi' encoding, to unicode, then back to 'ansi' and then 414 onwards to the console which is implemented as unicode. 415 416 For operations like 'svn status -v' this may cause about 70% of the total 417 processing time, with absolutely no gain. 418 419 For this specific scenario this shortcut exists. It has the nice side 420 effect of allowing full unicode output to the console. 421 422 Note that this shortcut is not used when the output is redirected, as in 423 that case the data is put on the pipe/file after the first conversion to 424 ansi. In this case the most expensive conversion is already avoided. 425 */ 426 if ((stream == stdout && shortcut_stdout_to_console) 427 || (stream == stderr && shortcut_stderr_to_console)) 428 { 429 WCHAR *result; 430 431 if (string[0] == '\0') 432 return SVN_NO_ERROR; 433 434 SVN_ERR(svn_cmdline_fflush(stream)); /* Flush existing output */ 435 436 SVN_ERR(svn_utf__win32_utf8_to_utf16(&result, string, NULL, pool)); 437 438 if (_cputws(result)) 439 { 440 if (apr_get_os_error()) 441 { 442 return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); 443 } 444 } 445 446 return SVN_NO_ERROR; 447 } 448#endif 449 450 err = svn_cmdline_cstring_from_utf8(&out, string, pool); 451 452 if (err) 453 { 454 svn_error_clear(err); 455 out = svn_cmdline_cstring_from_utf8_fuzzy(string, pool); 456 } 457 458 /* On POSIX systems, errno will be set on an error in fputs, but this might 459 not be the case on other platforms. We reset errno and only 460 use it if it was set by the below fputs call. Else, we just return 461 a generic error. */ 462 errno = 0; 463 464 if (fputs(out, stream) == EOF) 465 { 466 if (apr_get_os_error()) /* is errno on POSIX */ 467 { 468 /* ### Issue #3014: Return a specific error for broken pipes, 469 * ### with a single element in the error chain. */ 470 if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error())) 471 return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); 472 else 473 return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); 474 } 475 else 476 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); 477 } 478 479 return SVN_NO_ERROR; 480} 481 482svn_error_t * 483svn_cmdline_fflush(FILE *stream) 484{ 485 /* See comment in svn_cmdline_fputs about use of errno and stdio. */ 486 errno = 0; 487 if (fflush(stream) == EOF) 488 { 489 if (apr_get_os_error()) /* is errno on POSIX */ 490 { 491 /* ### Issue #3014: Return a specific error for broken pipes, 492 * ### with a single element in the error chain. */ 493 if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error())) 494 return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); 495 else 496 return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); 497 } 498 else 499 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); 500 } 501 502 return SVN_NO_ERROR; 503} 504 505const char *svn_cmdline_output_encoding(apr_pool_t *pool) 506{ 507#ifdef CMDLINE_USE_CUSTOM_ENCODING 508 if (output_encoding) 509 return apr_pstrdup(pool, output_encoding); 510#endif 511 512 return SVN_APR_LOCALE_CHARSET; 513} 514 515int 516svn_cmdline_handle_exit_error(svn_error_t *err, 517 apr_pool_t *pool, 518 const char *prefix) 519{ 520 /* Issue #3014: 521 * Don't print anything on broken pipes. The pipe was likely 522 * closed by the process at the other end. We expect that 523 * process to perform error reporting as necessary. 524 * 525 * ### This assumes that there is only one error in a chain for 526 * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */ 527 if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR) 528 svn_handle_error2(err, stderr, FALSE, prefix); 529 svn_error_clear(err); 530 if (pool) 531 svn_pool_destroy(pool); 532 return EXIT_FAILURE; 533} 534 535struct trust_server_cert_non_interactive_baton { 536 svn_boolean_t trust_server_cert_unknown_ca; 537 svn_boolean_t trust_server_cert_cn_mismatch; 538 svn_boolean_t trust_server_cert_expired; 539 svn_boolean_t trust_server_cert_not_yet_valid; 540 svn_boolean_t trust_server_cert_other_failure; 541}; 542 543/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. 544 545 Don't actually prompt. Instead, set *CRED_P to valid credentials 546 iff FAILURES is empty or may be accepted according to the flags 547 in BATON. If there are any other failure bits, then set *CRED_P 548 to null (that is, reject the cert). 549 550 Ignore MAY_SAVE; we don't save certs we never prompted for. 551 552 Ignore REALM and CERT_INFO, 553 554 Ignore any further films by George Lucas. */ 555static svn_error_t * 556trust_server_cert_non_interactive(svn_auth_cred_ssl_server_trust_t **cred_p, 557 void *baton, 558 const char *realm, 559 apr_uint32_t failures, 560 const svn_auth_ssl_server_cert_info_t 561 *cert_info, 562 svn_boolean_t may_save, 563 apr_pool_t *pool) 564{ 565 struct trust_server_cert_non_interactive_baton *b = baton; 566 apr_uint32_t non_ignored_failures; 567 *cred_p = NULL; 568 569 /* Mask away bits we are instructed to ignore. */ 570 non_ignored_failures = failures & ~( 571 (b->trust_server_cert_unknown_ca ? SVN_AUTH_SSL_UNKNOWNCA : 0) 572 | (b->trust_server_cert_cn_mismatch ? SVN_AUTH_SSL_CNMISMATCH : 0) 573 | (b->trust_server_cert_expired ? SVN_AUTH_SSL_EXPIRED : 0) 574 | (b->trust_server_cert_not_yet_valid ? SVN_AUTH_SSL_NOTYETVALID : 0) 575 | (b->trust_server_cert_other_failure ? SVN_AUTH_SSL_OTHER : 0) 576 ); 577 578 /* If no failures remain, accept the certificate. */ 579 if (non_ignored_failures == 0) 580 { 581 *cred_p = apr_pcalloc(pool, sizeof(**cred_p)); 582 (*cred_p)->may_save = FALSE; 583 (*cred_p)->accepted_failures = failures; 584 } 585 586 return SVN_NO_ERROR; 587} 588 589svn_error_t * 590svn_cmdline_create_auth_baton2(svn_auth_baton_t **ab, 591 svn_boolean_t non_interactive, 592 const char *auth_username, 593 const char *auth_password, 594 const char *config_dir, 595 svn_boolean_t no_auth_cache, 596 svn_boolean_t trust_server_cert_unknown_ca, 597 svn_boolean_t trust_server_cert_cn_mismatch, 598 svn_boolean_t trust_server_cert_expired, 599 svn_boolean_t trust_server_cert_not_yet_valid, 600 svn_boolean_t trust_server_cert_other_failure, 601 svn_config_t *cfg, 602 svn_cancel_func_t cancel_func, 603 void *cancel_baton, 604 apr_pool_t *pool) 605 606{ 607 svn_boolean_t store_password_val = TRUE; 608 svn_boolean_t store_auth_creds_val = TRUE; 609 svn_auth_provider_object_t *provider; 610 svn_cmdline_prompt_baton2_t *pb = NULL; 611 612 /* The whole list of registered providers */ 613 apr_array_header_t *providers; 614 615 /* Populate the registered providers with the platform-specific providers */ 616 SVN_ERR(svn_auth_get_platform_specific_client_providers(&providers, 617 cfg, pool)); 618 619 /* If we have a cancellation function, cram it and the stuff it 620 needs into the prompt baton. */ 621 if (cancel_func) 622 { 623 pb = apr_palloc(pool, sizeof(*pb)); 624 pb->cancel_func = cancel_func; 625 pb->cancel_baton = cancel_baton; 626 pb->config_dir = config_dir; 627 } 628 629 if (!non_interactive) 630 { 631 /* This provider doesn't prompt the user in order to get creds; 632 it prompts the user regarding the caching of creds. */ 633 svn_auth_get_simple_provider2(&provider, 634 svn_cmdline_auth_plaintext_prompt, 635 pb, pool); 636 } 637 else 638 { 639 svn_auth_get_simple_provider2(&provider, NULL, NULL, pool); 640 } 641 642 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 643 svn_auth_get_username_provider(&provider, pool); 644 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 645 646 svn_auth_get_ssl_server_trust_file_provider(&provider, pool); 647 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 648 svn_auth_get_ssl_client_cert_file_provider(&provider, pool); 649 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 650 651 if (!non_interactive) 652 { 653 /* This provider doesn't prompt the user in order to get creds; 654 it prompts the user regarding the caching of creds. */ 655 svn_auth_get_ssl_client_cert_pw_file_provider2 656 (&provider, svn_cmdline_auth_plaintext_passphrase_prompt, 657 pb, pool); 658 } 659 else 660 { 661 svn_auth_get_ssl_client_cert_pw_file_provider2(&provider, NULL, NULL, 662 pool); 663 } 664 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 665 666 if (!non_interactive) 667 { 668 svn_boolean_t ssl_client_cert_file_prompt; 669 670 SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt, 671 SVN_CONFIG_SECTION_AUTH, 672 SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT, 673 FALSE)); 674 675 /* Two basic prompt providers: username/password, and just username. */ 676 svn_auth_get_simple_prompt_provider(&provider, 677 svn_cmdline_auth_simple_prompt, 678 pb, 679 2, /* retry limit */ 680 pool); 681 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 682 683 svn_auth_get_username_prompt_provider 684 (&provider, svn_cmdline_auth_username_prompt, pb, 685 2, /* retry limit */ pool); 686 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 687 688 /* SSL prompt providers: server-certs and client-cert-passphrases. */ 689 svn_auth_get_ssl_server_trust_prompt_provider 690 (&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool); 691 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 692 693 svn_auth_get_ssl_client_cert_pw_prompt_provider 694 (&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool); 695 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 696 697 /* If configuration allows, add a provider for client-cert path 698 prompting, too. */ 699 if (ssl_client_cert_file_prompt) 700 { 701 svn_auth_get_ssl_client_cert_prompt_provider 702 (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool); 703 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 704 } 705 } 706 else if (trust_server_cert_unknown_ca || trust_server_cert_cn_mismatch || 707 trust_server_cert_expired || trust_server_cert_not_yet_valid || 708 trust_server_cert_other_failure) 709 { 710 struct trust_server_cert_non_interactive_baton *b; 711 712 b = apr_palloc(pool, sizeof(*b)); 713 b->trust_server_cert_unknown_ca = trust_server_cert_unknown_ca; 714 b->trust_server_cert_cn_mismatch = trust_server_cert_cn_mismatch; 715 b->trust_server_cert_expired = trust_server_cert_expired; 716 b->trust_server_cert_not_yet_valid = trust_server_cert_not_yet_valid; 717 b->trust_server_cert_other_failure = trust_server_cert_other_failure; 718 719 /* Remember, only register this provider if non_interactive. */ 720 svn_auth_get_ssl_server_trust_prompt_provider 721 (&provider, trust_server_cert_non_interactive, b, pool); 722 APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 723 } 724 725 /* Build an authentication baton to give to libsvn_client. */ 726 svn_auth_open(ab, providers, pool); 727 728 /* Place any default --username or --password credentials into the 729 auth_baton's run-time parameter hash. */ 730 if (auth_username) 731 svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_USERNAME, 732 auth_username); 733 if (auth_password) 734 svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD, 735 auth_password); 736 737 /* Same with the --non-interactive option. */ 738 if (non_interactive) 739 svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NON_INTERACTIVE, ""); 740 741 if (config_dir) 742 svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_CONFIG_DIR, 743 config_dir); 744 745 /* Determine whether storing passwords in any form is allowed. 746 * This is the deprecated location for this option, the new 747 * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may 748 * override the value we set here. */ 749 SVN_ERR(svn_config_get_bool(cfg, &store_password_val, 750 SVN_CONFIG_SECTION_AUTH, 751 SVN_CONFIG_OPTION_STORE_PASSWORDS, 752 SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS)); 753 754 if (! store_password_val) 755 svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, ""); 756 757 /* Determine whether we are allowed to write to the auth/ area. 758 * This is the deprecated location for this option, the new 759 * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may 760 * override the value we set here. */ 761 SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds_val, 762 SVN_CONFIG_SECTION_AUTH, 763 SVN_CONFIG_OPTION_STORE_AUTH_CREDS, 764 SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS)); 765 766 if (no_auth_cache || ! store_auth_creds_val) 767 svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NO_AUTH_CACHE, ""); 768 769#ifdef SVN_HAVE_GNOME_KEYRING 770 svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC, 771 &svn_cmdline__auth_gnome_keyring_unlock_prompt); 772#endif /* SVN_HAVE_GNOME_KEYRING */ 773 774 return SVN_NO_ERROR; 775} 776 777svn_error_t * 778svn_cmdline__getopt_init(apr_getopt_t **os, 779 int argc, 780 const char *argv[], 781 apr_pool_t *pool) 782{ 783 apr_status_t apr_err = apr_getopt_init(os, pool, argc, argv); 784 if (apr_err) 785 return svn_error_wrap_apr(apr_err, 786 _("Error initializing command line arguments")); 787 return SVN_NO_ERROR; 788} 789 790 791void 792svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr, 793 const char* propname, 794 svn_string_t *propval, 795 svn_boolean_t inherited_prop, 796 apr_pool_t *pool) 797{ 798 const char *xml_safe; 799 const char *encoding = NULL; 800 801 if (*outstr == NULL) 802 *outstr = svn_stringbuf_create_empty(pool); 803 804 if (svn_xml_is_xml_safe(propval->data, propval->len)) 805 { 806 svn_stringbuf_t *xml_esc = NULL; 807 svn_xml_escape_cdata_string(&xml_esc, propval, pool); 808 xml_safe = xml_esc->data; 809 } 810 else 811 { 812 const svn_string_t *base64ed = svn_base64_encode_string2(propval, TRUE, 813 pool); 814 encoding = "base64"; 815 xml_safe = base64ed->data; 816 } 817 818 if (encoding) 819 svn_xml_make_open_tag( 820 outstr, pool, svn_xml_protect_pcdata, 821 inherited_prop ? "inherited_property" : "property", 822 "name", propname, 823 "encoding", encoding, SVN_VA_NULL); 824 else 825 svn_xml_make_open_tag( 826 outstr, pool, svn_xml_protect_pcdata, 827 inherited_prop ? "inherited_property" : "property", 828 "name", propname, SVN_VA_NULL); 829 830 svn_stringbuf_appendcstr(*outstr, xml_safe); 831 832 svn_xml_make_close_tag( 833 outstr, pool, 834 inherited_prop ? "inherited_property" : "property"); 835 836 return; 837} 838 839/* Return the most similar string to NEEDLE in HAYSTACK, which contains 840 * HAYSTACK_LEN elements. Return NULL if no string is sufficiently similar. 841 */ 842/* See svn_cl__similarity_check() for a more general solution. */ 843static const char * 844most_similar(const char *needle_cstr, 845 const char **haystack, 846 apr_size_t haystack_len, 847 apr_pool_t *scratch_pool) 848{ 849 const char *max_similar = NULL; 850 apr_size_t max_score = 0; 851 apr_size_t i; 852 svn_membuf_t membuf; 853 svn_string_t *needle_str = svn_string_create(needle_cstr, scratch_pool); 854 855 svn_membuf__create(&membuf, 64, scratch_pool); 856 857 for (i = 0; i < haystack_len; i++) 858 { 859 apr_size_t score; 860 svn_string_t *hay = svn_string_create(haystack[i], scratch_pool); 861 862 score = svn_string__similarity(needle_str, hay, &membuf, NULL); 863 864 /* If you update this factor, consider updating 865 * svn_cl__similarity_check(). */ 866 if (score >= (2 * SVN_STRING__SIM_RANGE_MAX + 1) / 3 867 && score > max_score) 868 { 869 max_score = score; 870 max_similar = haystack[i]; 871 } 872 } 873 874 return max_similar; 875} 876 877/* Verify that NEEDLE is in HAYSTACK, which contains HAYSTACK_LEN elements. */ 878static svn_error_t * 879string_in_array(const char *needle, 880 const char **haystack, 881 apr_size_t haystack_len, 882 apr_pool_t *scratch_pool) 883{ 884 const char *next_of_kin; 885 apr_size_t i; 886 for (i = 0; i < haystack_len; i++) 887 { 888 if (!strcmp(needle, haystack[i])) 889 return SVN_NO_ERROR; 890 } 891 892 /* Error. */ 893 next_of_kin = most_similar(needle, haystack, haystack_len, scratch_pool); 894 if (next_of_kin) 895 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 896 _("Ignoring unknown value '%s'; " 897 "did you mean '%s'?"), 898 needle, next_of_kin); 899 else 900 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 901 _("Ignoring unknown value '%s'"), 902 needle); 903} 904 905#include "config_keys.inc" 906 907/* Validate the FILE, SECTION, and OPTION components of CONFIG_OPTION are 908 * known. Return an error if not. (An unknown value may be either a typo 909 * or added in a newer minor version of Subversion.) */ 910static svn_error_t * 911validate_config_option(svn_cmdline__config_argument_t *config_option, 912 apr_pool_t *scratch_pool) 913{ 914 svn_boolean_t arbitrary_keys = FALSE; 915 916 /* TODO: some day, we could also verify that OPTION is valid for SECTION; 917 i.e., forbid invalid combinations such as config:auth:diff-extensions. */ 918 919#define ARRAYLEN(x) ( sizeof((x)) / sizeof((x)[0]) ) 920 921 SVN_ERR(string_in_array(config_option->file, svn__valid_config_files, 922 ARRAYLEN(svn__valid_config_files), 923 scratch_pool)); 924 SVN_ERR(string_in_array(config_option->section, svn__valid_config_sections, 925 ARRAYLEN(svn__valid_config_sections), 926 scratch_pool)); 927 928 /* Don't validate option names for sections such as servers[group], 929 * config[tunnels], and config[auto-props] that permit arbitrary options. */ 930 { 931 int i; 932 933 for (i = 0; i < ARRAYLEN(svn__empty_config_sections); i++) 934 { 935 if (!strcmp(config_option->section, svn__empty_config_sections[i])) 936 arbitrary_keys = TRUE; 937 } 938 } 939 940 if (! arbitrary_keys) 941 SVN_ERR(string_in_array(config_option->option, svn__valid_config_options, 942 ARRAYLEN(svn__valid_config_options), 943 scratch_pool)); 944 945#undef ARRAYLEN 946 947 return SVN_NO_ERROR; 948} 949 950svn_error_t * 951svn_cmdline__parse_config_option(apr_array_header_t *config_options, 952 const char *opt_arg, 953 const char *prefix, 954 apr_pool_t *pool) 955{ 956 svn_cmdline__config_argument_t *config_option; 957 const char *first_colon, *second_colon, *equals_sign; 958 apr_size_t len = strlen(opt_arg); 959 if ((first_colon = strchr(opt_arg, ':')) && (first_colon != opt_arg)) 960 { 961 if ((second_colon = strchr(first_colon + 1, ':')) && 962 (second_colon != first_colon + 1)) 963 { 964 if ((equals_sign = strchr(second_colon + 1, '=')) && 965 (equals_sign != second_colon + 1)) 966 { 967 svn_error_t *warning; 968 969 config_option = apr_pcalloc(pool, sizeof(*config_option)); 970 config_option->file = apr_pstrndup(pool, opt_arg, 971 first_colon - opt_arg); 972 config_option->section = apr_pstrndup(pool, first_colon + 1, 973 second_colon - first_colon - 1); 974 config_option->option = apr_pstrndup(pool, second_colon + 1, 975 equals_sign - second_colon - 1); 976 977 warning = validate_config_option(config_option, pool); 978 if (warning) 979 { 980 svn_handle_warning2(stderr, warning, prefix); 981 svn_error_clear(warning); 982 } 983 984 if (! (strchr(config_option->option, ':'))) 985 { 986 config_option->value = apr_pstrndup(pool, equals_sign + 1, 987 opt_arg + len - equals_sign - 1); 988 APR_ARRAY_PUSH(config_options, svn_cmdline__config_argument_t *) 989 = config_option; 990 return SVN_NO_ERROR; 991 } 992 } 993 } 994 } 995 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 996 _("Invalid syntax of argument of --config-option")); 997} 998 999svn_error_t * 1000svn_cmdline__apply_config_options(apr_hash_t *config, 1001 const apr_array_header_t *config_options, 1002 const char *prefix, 1003 const char *argument_name) 1004{ 1005 int i; 1006 1007 for (i = 0; i < config_options->nelts; i++) 1008 { 1009 svn_config_t *cfg; 1010 svn_cmdline__config_argument_t *arg = 1011 APR_ARRAY_IDX(config_options, i, 1012 svn_cmdline__config_argument_t *); 1013 1014 cfg = svn_hash_gets(config, arg->file); 1015 1016 if (cfg) 1017 { 1018 svn_config_set(cfg, arg->section, arg->option, arg->value); 1019 } 1020 else 1021 { 1022 svn_error_t *err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1023 _("Unrecognized file in argument of %s"), argument_name); 1024 1025 svn_handle_warning2(stderr, err, prefix); 1026 svn_error_clear(err); 1027 } 1028 } 1029 1030 return SVN_NO_ERROR; 1031} 1032 1033/* Return a copy, allocated in POOL, of the next line of text from *STR 1034 * up to and including a CR and/or an LF. Change *STR to point to the 1035 * remainder of the string after the returned part. If there are no 1036 * characters to be returned, return NULL; never return an empty string. 1037 */ 1038static const char * 1039next_line(const char **str, apr_pool_t *pool) 1040{ 1041 const char *start = *str; 1042 const char *p = *str; 1043 1044 /* n.b. Throughout this fn, we never read any character after a '\0'. */ 1045 /* Skip over all non-EOL characters, if any. */ 1046 while (*p != '\r' && *p != '\n' && *p != '\0') 1047 p++; 1048 /* Skip over \r\n or \n\r or \r or \n, if any. */ 1049 if (*p == '\r' || *p == '\n') 1050 { 1051 char c = *p++; 1052 1053 if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r')) 1054 p++; 1055 } 1056 1057 /* Now p points after at most one '\n' and/or '\r'. */ 1058 *str = p; 1059 1060 if (p == start) 1061 return NULL; 1062 1063 return svn_string_ncreate(start, p - start, pool)->data; 1064} 1065 1066const char * 1067svn_cmdline__indent_string(const char *str, 1068 const char *indent, 1069 apr_pool_t *pool) 1070{ 1071 svn_stringbuf_t *out = svn_stringbuf_create_empty(pool); 1072 const char *line; 1073 1074 while ((line = next_line(&str, pool))) 1075 { 1076 svn_stringbuf_appendcstr(out, indent); 1077 svn_stringbuf_appendcstr(out, line); 1078 } 1079 return out->data; 1080} 1081 1082svn_error_t * 1083svn_cmdline__print_prop_hash(svn_stream_t *out, 1084 apr_hash_t *prop_hash, 1085 svn_boolean_t names_only, 1086 apr_pool_t *pool) 1087{ 1088 apr_array_header_t *sorted_props; 1089 int i; 1090 1091 sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically, 1092 pool); 1093 for (i = 0; i < sorted_props->nelts; i++) 1094 { 1095 svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); 1096 const char *pname = item.key; 1097 svn_string_t *propval = item.value; 1098 const char *pname_stdout; 1099 1100 if (svn_prop_needs_translation(pname)) 1101 SVN_ERR(svn_subst_detranslate_string(&propval, propval, 1102 TRUE, pool)); 1103 1104 SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool)); 1105 1106 if (out) 1107 { 1108 pname_stdout = apr_psprintf(pool, " %s\n", pname_stdout); 1109 SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout, 1110 APR_EOL_STR, /* 'native' eol */ 1111 FALSE, /* no repair */ 1112 NULL, /* no keywords */ 1113 FALSE, /* no expansion */ 1114 pool)); 1115 1116 SVN_ERR(svn_stream_puts(out, pname_stdout)); 1117 } 1118 else 1119 { 1120 /* ### We leave these printfs for now, since if propval wasn't 1121 translated above, we don't know anything about its encoding. 1122 In fact, it might be binary data... */ 1123 printf(" %s\n", pname_stdout); 1124 } 1125 1126 if (!names_only) 1127 { 1128 /* Add an extra newline to the value before indenting, so that 1129 * every line of output has the indentation whether the value 1130 * already ended in a newline or not. */ 1131 const char *newval = apr_psprintf(pool, "%s\n", propval->data); 1132 const char *indented_newval = svn_cmdline__indent_string(newval, 1133 " ", 1134 pool); 1135 if (out) 1136 { 1137 SVN_ERR(svn_stream_puts(out, indented_newval)); 1138 } 1139 else 1140 { 1141 printf("%s", indented_newval); 1142 } 1143 } 1144 } 1145 1146 return SVN_NO_ERROR; 1147} 1148 1149svn_error_t * 1150svn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr, 1151 apr_hash_t *prop_hash, 1152 svn_boolean_t names_only, 1153 svn_boolean_t inherited_props, 1154 apr_pool_t *pool) 1155{ 1156 apr_array_header_t *sorted_props; 1157 int i; 1158 1159 if (*outstr == NULL) 1160 *outstr = svn_stringbuf_create_empty(pool); 1161 1162 sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically, 1163 pool); 1164 for (i = 0; i < sorted_props->nelts; i++) 1165 { 1166 svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); 1167 const char *pname = item.key; 1168 svn_string_t *propval = item.value; 1169 1170 if (names_only) 1171 { 1172 svn_xml_make_open_tag( 1173 outstr, pool, svn_xml_self_closing, 1174 inherited_props ? "inherited_property" : "property", 1175 "name", pname, SVN_VA_NULL); 1176 } 1177 else 1178 { 1179 const char *pname_out; 1180 1181 if (svn_prop_needs_translation(pname)) 1182 SVN_ERR(svn_subst_detranslate_string(&propval, propval, 1183 TRUE, pool)); 1184 1185 SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool)); 1186 1187 svn_cmdline__print_xml_prop(outstr, pname_out, propval, 1188 inherited_props, pool); 1189 } 1190 } 1191 1192 return SVN_NO_ERROR; 1193} 1194 1195svn_boolean_t 1196svn_cmdline__stdin_is_a_terminal(void) 1197{ 1198#ifdef WIN32 1199 return (_isatty(STDIN_FILENO) != 0); 1200#else 1201 return (isatty(STDIN_FILENO) != 0); 1202#endif 1203} 1204 1205svn_boolean_t 1206svn_cmdline__stdout_is_a_terminal(void) 1207{ 1208#ifdef WIN32 1209 return (_isatty(STDOUT_FILENO) != 0); 1210#else 1211 return (isatty(STDOUT_FILENO) != 0); 1212#endif 1213} 1214 1215svn_boolean_t 1216svn_cmdline__stderr_is_a_terminal(void) 1217{ 1218#ifdef WIN32 1219 return (_isatty(STDERR_FILENO) != 0); 1220#else 1221 return (isatty(STDERR_FILENO) != 0); 1222#endif 1223} 1224 1225svn_boolean_t 1226svn_cmdline__be_interactive(svn_boolean_t non_interactive, 1227 svn_boolean_t force_interactive) 1228{ 1229 /* If neither --non-interactive nor --force-interactive was passed, 1230 * be interactive if stdin is a terminal. 1231 * If --force-interactive was passed, always be interactive. */ 1232 if (!force_interactive && !non_interactive) 1233 { 1234 return svn_cmdline__stdin_is_a_terminal(); 1235 } 1236 else if (force_interactive) 1237 return TRUE; 1238 1239 return !non_interactive; 1240} 1241 1242 1243/* Helper for the edit_externally functions. Set *EDITOR to some path to an 1244 editor binary, in native C string on Unix/Linux platforms and in UTF-8 1245 string on Windows platform. Sources to search include: the EDITOR_CMD 1246 argument (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG 1247 is not NULL), $VISUAL, $EDITOR. Return 1248 SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */ 1249static svn_error_t * 1250find_editor_binary(const char **editor, 1251 const char *editor_cmd, 1252 apr_hash_t *config, 1253 apr_pool_t *pool) 1254{ 1255 const char *e; 1256 const char *e_cfg; 1257 struct svn_config_t *cfg; 1258 apr_status_t status; 1259 1260 /* Use the editor specified on the command line via --editor-cmd, if any. */ 1261#ifdef WIN32 1262 /* On Windows, editor_cmd is transcoded to the system active code page 1263 because we use main() as a entry point without APR's (or our own) wrapper 1264 in command line tools. */ 1265 if (editor_cmd) 1266 { 1267 SVN_ERR(svn_utf_cstring_to_utf8(&e, editor_cmd, pool)); 1268 } 1269 else 1270 { 1271 e = NULL; 1272 } 1273#else 1274 e = editor_cmd; 1275#endif 1276 1277 /* Otherwise look for the Subversion-specific environment variable. */ 1278 if (! e) 1279 { 1280 status = apr_env_get((char **)&e, "SVN_EDITOR", pool); 1281 if (status || ! *e) 1282 { 1283 e = NULL; 1284 } 1285 } 1286 1287 /* If not found then fall back on the config file. */ 1288 if (! e) 1289 { 1290 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 1291 svn_config_get(cfg, &e_cfg, SVN_CONFIG_SECTION_HELPERS, 1292 SVN_CONFIG_OPTION_EDITOR_CMD, NULL); 1293#ifdef WIN32 1294 if (e_cfg) 1295 { 1296 /* On Windows, we assume that config values are set in system active 1297 code page, so we need transcode it here. */ 1298 SVN_ERR(svn_utf_cstring_to_utf8(&e, e_cfg, pool)); 1299 } 1300#else 1301 e = e_cfg; 1302#endif 1303 } 1304 1305 /* If not found yet then try general purpose environment variables. */ 1306 if (! e) 1307 { 1308 status = apr_env_get((char**)&e, "VISUAL", pool); 1309 if (status || ! *e) 1310 { 1311 e = NULL; 1312 } 1313 } 1314 if (! e) 1315 { 1316 status = apr_env_get((char**)&e, "EDITOR", pool); 1317 if (status || ! *e) 1318 { 1319 e = NULL; 1320 } 1321 } 1322 1323#ifdef SVN_CLIENT_EDITOR 1324 /* If still not found then fall back on the hard-coded default. */ 1325 if (! e) 1326 e = SVN_CLIENT_EDITOR; 1327#endif 1328 1329 /* Error if there is no editor specified */ 1330 if (e) 1331 { 1332 const char *c; 1333 1334 for (c = e; *c; c++) 1335 if (!svn_ctype_isspace(*c)) 1336 break; 1337 1338 if (! *c) 1339 return svn_error_create 1340 (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL, 1341 _("The EDITOR, SVN_EDITOR or VISUAL environment variable or " 1342 "'editor-cmd' run-time configuration option is empty or " 1343 "consists solely of whitespace. Expected a shell command.")); 1344 } 1345 else 1346 return svn_error_create 1347 (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL, 1348 _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are " 1349 "set, and no 'editor-cmd' run-time configuration option was found")); 1350 1351 *editor = e; 1352 return SVN_NO_ERROR; 1353} 1354 1355/* Wrapper around apr_pescape_shell() which also escapes whitespace. */ 1356static const char * 1357escape_path(apr_pool_t *pool, const char *orig_path) 1358{ 1359 apr_size_t len, esc_len; 1360 apr_status_t status; 1361 1362 len = strlen(orig_path); 1363 esc_len = 0; 1364 1365 status = apr_escape_shell(NULL, orig_path, len, &esc_len); 1366 1367 if (status == APR_NOTFOUND) 1368 { 1369 /* No special characters found by APR, so just surround it in double 1370 quotes in case there is whitespace, which APR (as of 1.6.5) doesn't 1371 consider special. */ 1372 return apr_psprintf(pool, "\"%s\"", orig_path); 1373 } 1374 else 1375 { 1376#ifdef WIN32 1377 const char *p; 1378 /* Following the advice from 1379 https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way 1380 1. Surround argument with double-quotes 1381 2. Escape backslashes, if they're followed by a double-quote, and double-quotes 1382 3. Escape any metacharacter, including double-quotes, with ^ */ 1383 1384 /* Use APR's buffer size as an approximation for how large the escaped 1385 string should be, plus 4 bytes for the leading/trailing ^" */ 1386 svn_stringbuf_t *buf = svn_stringbuf_create_ensure(esc_len + 4, pool); 1387 svn_stringbuf_appendcstr(buf, "^\""); 1388 for (p = orig_path; *p; p++) 1389 { 1390 int nr_backslash = 0; 1391 while (*p && *p == '\\') 1392 { 1393 nr_backslash++; 1394 p++; 1395 } 1396 1397 if (!*p) 1398 /* We've reached the end of the argument, so we need 2n backslash 1399 characters. That will be interpreted as n backslashes and the 1400 final double-quote character will be interpreted as the final 1401 string delimiter. */ 1402 svn_stringbuf_appendfill(buf, '\\', nr_backslash * 2); 1403 else if (*p == '"') 1404 { 1405 /* Double-quote as part of the argument means we need to double 1406 any preceeding backslashes and then add one to escape the 1407 double-quote. */ 1408 svn_stringbuf_appendfill(buf, '\\', nr_backslash * 2 + 1); 1409 svn_stringbuf_appendbyte(buf, '^'); 1410 svn_stringbuf_appendbyte(buf, *p); 1411 } 1412 else 1413 { 1414 /* Since there's no double-quote, we just insert any backslashes 1415 literally. No escaping needed. */ 1416 svn_stringbuf_appendfill(buf, '\\', nr_backslash); 1417 if (strchr("()%!^<>&|", *p)) 1418 svn_stringbuf_appendbyte(buf, '^'); 1419 svn_stringbuf_appendbyte(buf, *p); 1420 } 1421 } 1422 svn_stringbuf_appendcstr(buf, "^\""); 1423 return buf->data; 1424#else 1425 char *path, *p, *esc_path; 1426 1427 /* Account for whitespace, since APR doesn't */ 1428 for (p = (char *)orig_path; *p; p++) 1429 if (strchr(" \t\n\r", *p)) 1430 esc_len++; 1431 1432 path = apr_pcalloc(pool, esc_len); 1433 apr_escape_shell(path, orig_path, len, NULL); 1434 1435 p = esc_path = apr_pcalloc(pool, len + esc_len + 1); 1436 while (*path) 1437 { 1438 if (strchr(" \t\n\r", *path)) 1439 *p++ = '\\'; 1440 *p++ = *path++; 1441 } 1442 1443 return esc_path; 1444#endif 1445 } 1446} 1447 1448svn_error_t * 1449svn_cmdline__edit_file_externally(const char *path, 1450 const char *editor_cmd, 1451 apr_hash_t *config, 1452 apr_pool_t *pool) 1453{ 1454 const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr; 1455 const char *file_name_local; 1456#ifdef WIN32 1457 const WCHAR *wcmd; 1458#endif 1459 char *old_cwd; 1460 int sys_err; 1461 apr_status_t apr_err; 1462 1463 svn_dirent_split(&base_dir, &file_name, path, pool); 1464 1465 SVN_ERR(find_editor_binary(&editor, editor_cmd, config, pool)); 1466 1467 apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); 1468 if (apr_err) 1469 return svn_error_wrap_apr(apr_err, _("Can't get working directory")); 1470 1471 /* APR doesn't like "" directories */ 1472 if (base_dir[0] == '\0') 1473 base_dir_apr = "."; 1474 else 1475 SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool)); 1476 1477 apr_err = apr_filepath_set(base_dir_apr, pool); 1478 if (apr_err) 1479 return svn_error_wrap_apr 1480 (apr_err, _("Can't change working directory to '%s'"), base_dir); 1481 1482 SVN_ERR(svn_path_cstring_from_utf8(&file_name_local, 1483 escape_path(pool, file_name), pool)); 1484 /* editor is explicitly documented as being interpreted by the user's shell, 1485 and as such should already be quoted/escaped as needed. */ 1486#ifndef WIN32 1487 cmd = apr_psprintf(pool, "%s %s", editor, file_name_local); 1488 sys_err = system(cmd); 1489#else 1490 cmd = apr_psprintf(pool, "\"%s %s\"", editor, file_name_local); 1491 SVN_ERR(svn_utf__win32_utf8_to_utf16(&wcmd, cmd, NULL, pool)); 1492 sys_err = _wsystem(wcmd); 1493#endif 1494 1495 apr_err = apr_filepath_set(old_cwd, pool); 1496 if (apr_err) 1497 svn_handle_error2(svn_error_wrap_apr 1498 (apr_err, _("Can't restore working directory")), 1499 stderr, TRUE /* fatal */, "svn: "); 1500 1501 if (sys_err) 1502 /* Extracting any meaning from sys_err is platform specific, so just 1503 use the raw value. */ 1504 return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 1505 _("system('%s') returned %d"), cmd, sys_err); 1506 1507 return SVN_NO_ERROR; 1508} 1509 1510 1511svn_error_t * 1512svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */, 1513 const char **tmpfile_left /* UTF-8! */, 1514 const char *editor_cmd, 1515 const char *base_dir /* UTF-8! */, 1516 const svn_string_t *contents /* UTF-8! */, 1517 const char *filename, 1518 apr_hash_t *config, 1519 svn_boolean_t as_text, 1520 const char *encoding, 1521 apr_pool_t *pool) 1522{ 1523 const char *editor; 1524 const char *cmd; 1525#ifdef WIN32 1526 const WCHAR *wcmd; 1527#endif 1528 apr_file_t *tmp_file; 1529 const char *tmpfile_name; 1530 const char *tmpfile_native; 1531 const char *base_dir_apr; 1532 svn_string_t *translated_contents; 1533 apr_status_t apr_err; 1534 apr_size_t written; 1535 apr_finfo_t finfo_before, finfo_after; 1536 svn_error_t *err = SVN_NO_ERROR; 1537 char *old_cwd; 1538 int sys_err; 1539 svn_boolean_t remove_file = TRUE; 1540 1541 SVN_ERR(find_editor_binary(&editor, editor_cmd, config, pool)); 1542 1543 /* Convert file contents from UTF-8/LF if desired. */ 1544 if (as_text) 1545 { 1546 const char *translated; 1547 SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated, 1548 APR_EOL_STR, FALSE, 1549 NULL, FALSE, pool)); 1550 translated_contents = svn_string_create_empty(pool); 1551 if (encoding) 1552 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data, 1553 translated, encoding, pool)); 1554 else 1555 SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data, 1556 translated, pool)); 1557 translated_contents->len = strlen(translated_contents->data); 1558 } 1559 else 1560 translated_contents = svn_string_dup(contents, pool); 1561 1562 /* Move to BASE_DIR to avoid getting characters that need quoting 1563 into tmpfile_name */ 1564 apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); 1565 if (apr_err) 1566 return svn_error_wrap_apr(apr_err, _("Can't get working directory")); 1567 1568 /* APR doesn't like "" directories */ 1569 if (base_dir[0] == '\0') 1570 base_dir_apr = "."; 1571 else 1572 SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool)); 1573 apr_err = apr_filepath_set(base_dir_apr, pool); 1574 if (apr_err) 1575 { 1576 return svn_error_wrap_apr 1577 (apr_err, _("Can't change working directory to '%s'"), base_dir); 1578 } 1579 1580 /*** From here on, any problems that occur require us to cd back!! ***/ 1581 1582 /* Ask the working copy for a temporary file named FILENAME-something. */ 1583 err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name, 1584 "" /* dirpath */, 1585 filename, 1586 ".tmp", 1587 svn_io_file_del_none, pool, pool); 1588 1589 if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS)) 1590 { 1591 const char *temp_dir_apr; 1592 1593 svn_error_clear(err); 1594 1595 SVN_ERR(svn_io_temp_dir(&base_dir, pool)); 1596 1597 SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool)); 1598 apr_err = apr_filepath_set(temp_dir_apr, pool); 1599 if (apr_err) 1600 { 1601 return svn_error_wrap_apr 1602 (apr_err, _("Can't change working directory to '%s'"), base_dir); 1603 } 1604 1605 err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name, 1606 "" /* dirpath */, 1607 filename, 1608 ".tmp", 1609 svn_io_file_del_none, pool, pool); 1610 } 1611 1612 if (err) 1613 goto cleanup2; 1614 1615 /*** From here on, any problems that occur require us to cleanup 1616 the file we just created!! ***/ 1617 1618 /* Dump initial CONTENTS to TMP_FILE. */ 1619 err = svn_io_file_write_full(tmp_file, translated_contents->data, 1620 translated_contents->len, &written, 1621 pool); 1622 1623 err = svn_error_compose_create(err, svn_io_file_close(tmp_file, pool)); 1624 1625 /* Make sure the whole CONTENTS were written, else return an error. */ 1626 if (err) 1627 goto cleanup; 1628 1629 /* Get information about the temporary file before the user has 1630 been allowed to edit its contents. */ 1631 err = svn_io_stat(&finfo_before, tmpfile_name, APR_FINFO_MTIME, pool); 1632 if (err) 1633 goto cleanup; 1634 1635 /* Backdate the file a little bit in case the editor is very fast 1636 and doesn't change the size. (Use two seconds, since some 1637 filesystems have coarse granularity.) It's OK if this call 1638 fails, so we don't check its return value.*/ 1639 err = svn_io_set_file_affected_time(finfo_before.mtime 1640 - apr_time_from_sec(2), 1641 tmpfile_name, pool); 1642 svn_error_clear(err); 1643 1644 /* Stat it again to get the mtime we actually set. */ 1645 err = svn_io_stat(&finfo_before, tmpfile_name, 1646 APR_FINFO_MTIME | APR_FINFO_SIZE, pool); 1647 if (err) 1648 goto cleanup; 1649 1650 /* Prepare the editor command line. */ 1651 err = svn_utf_cstring_from_utf8(&tmpfile_native, 1652 escape_path(pool, tmpfile_name), pool); 1653 if (err) 1654 goto cleanup; 1655 1656 /* editor is explicitly documented as being interpreted by the user's shell, 1657 and as such should already be quoted/escaped as needed. */ 1658#ifndef WIN32 1659 cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native); 1660#else 1661 cmd = apr_psprintf(pool, "\"%s %s\"", editor, tmpfile_native); 1662#endif 1663 1664 /* If the caller wants us to leave the file around, return the path 1665 of the file we'll use, and make a note not to destroy it. */ 1666 if (tmpfile_left) 1667 { 1668 *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool); 1669 remove_file = FALSE; 1670 } 1671 1672 /* Now, run the editor command line. */ 1673#ifndef WIN32 1674 sys_err = system(cmd); 1675#else 1676 SVN_ERR(svn_utf__win32_utf8_to_utf16(&wcmd, cmd, NULL, pool)); 1677 sys_err = _wsystem(wcmd); 1678#endif 1679 if (sys_err != 0) 1680 { 1681 /* Extracting any meaning from sys_err is platform specific, so just 1682 use the raw value. */ 1683 err = svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 1684 _("system('%s') returned %d"), cmd, sys_err); 1685 goto cleanup; 1686 } 1687 1688 /* Get information about the temporary file after the assumed editing. */ 1689 err = svn_io_stat(&finfo_after, tmpfile_name, 1690 APR_FINFO_MTIME | APR_FINFO_SIZE, pool); 1691 if (err) 1692 goto cleanup; 1693 1694 /* If the file looks changed... */ 1695 if ((finfo_before.mtime != finfo_after.mtime) || 1696 (finfo_before.size != finfo_after.size)) 1697 { 1698 svn_stringbuf_t *edited_contents_s; 1699 err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool); 1700 if (err) 1701 goto cleanup; 1702 1703 *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s); 1704 1705 /* Translate back to UTF8/LF if desired. */ 1706 if (as_text) 1707 { 1708 err = svn_subst_translate_string2(edited_contents, NULL, NULL, 1709 *edited_contents, encoding, FALSE, 1710 pool, pool); 1711 if (err) 1712 { 1713 err = svn_error_quick_wrap 1714 (err, 1715 _("Error normalizing edited contents to internal format")); 1716 goto cleanup; 1717 } 1718 } 1719 } 1720 else 1721 { 1722 /* No edits seem to have been made */ 1723 *edited_contents = NULL; 1724 } 1725 1726 cleanup: 1727 if (remove_file) 1728 { 1729 /* Remove the file from disk. */ 1730 err = svn_error_compose_create( 1731 err, 1732 svn_io_remove_file2(tmpfile_name, FALSE, pool)); 1733 } 1734 1735 cleanup2: 1736 /* If we against all probability can't cd back, all further relative 1737 file references would be screwed up, so we have to abort. */ 1738 apr_err = apr_filepath_set(old_cwd, pool); 1739 if (apr_err) 1740 { 1741 svn_handle_error2(svn_error_wrap_apr 1742 (apr_err, _("Can't restore working directory")), 1743 stderr, TRUE /* fatal */, "svn: "); 1744 } 1745 1746 return svn_error_trace(err); 1747} 1748 1749svn_error_t * 1750svn_cmdline__parse_trust_options( 1751 svn_boolean_t *trust_server_cert_unknown_ca, 1752 svn_boolean_t *trust_server_cert_cn_mismatch, 1753 svn_boolean_t *trust_server_cert_expired, 1754 svn_boolean_t *trust_server_cert_not_yet_valid, 1755 svn_boolean_t *trust_server_cert_other_failure, 1756 const char *opt_arg, 1757 apr_pool_t *scratch_pool) 1758{ 1759 apr_array_header_t *failures; 1760 int i; 1761 1762 *trust_server_cert_unknown_ca = FALSE; 1763 *trust_server_cert_cn_mismatch = FALSE; 1764 *trust_server_cert_expired = FALSE; 1765 *trust_server_cert_not_yet_valid = FALSE; 1766 *trust_server_cert_other_failure = FALSE; 1767 1768 failures = svn_cstring_split(opt_arg, ", \n\r\t\v", TRUE, scratch_pool); 1769 1770 for (i = 0; i < failures->nelts; i++) 1771 { 1772 const char *value = APR_ARRAY_IDX(failures, i, const char *); 1773 if (!strcmp(value, "unknown-ca")) 1774 *trust_server_cert_unknown_ca = TRUE; 1775 else if (!strcmp(value, "cn-mismatch")) 1776 *trust_server_cert_cn_mismatch = TRUE; 1777 else if (!strcmp(value, "expired")) 1778 *trust_server_cert_expired = TRUE; 1779 else if (!strcmp(value, "not-yet-valid")) 1780 *trust_server_cert_not_yet_valid = TRUE; 1781 else if (!strcmp(value, "other")) 1782 *trust_server_cert_other_failure = TRUE; 1783 else 1784 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1785 _("Unknown value '%s' for %s.\n" 1786 "Supported values: %s"), 1787 value, 1788 "--trust-server-cert-failures", 1789 "unknown-ca, cn-mismatch, expired, " 1790 "not-yet-valid, other"); 1791 } 1792 1793 return SVN_NO_ERROR; 1794} 1795 1796/* Flags to see if we've been cancelled by the client or not. */ 1797static volatile sig_atomic_t cancelled = FALSE; 1798static volatile sig_atomic_t signum_cancelled = 0; 1799 1800/* The signals we handle. */ 1801static int signal_map [] = { 1802 SIGINT 1803#ifdef SIGBREAK 1804 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ 1805 , SIGBREAK 1806#endif 1807#ifdef SIGHUP 1808 , SIGHUP 1809#endif 1810#ifdef SIGTERM 1811 , SIGTERM 1812#endif 1813}; 1814 1815/* A signal handler to support cancellation. */ 1816static void 1817signal_handler(int signum) 1818{ 1819 int i; 1820 1821 apr_signal(signum, SIG_IGN); 1822 cancelled = TRUE; 1823 for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i) 1824 if (signal_map[i] == signum) 1825 { 1826 signum_cancelled = i + 1; 1827 break; 1828 } 1829} 1830 1831/* An svn_cancel_func_t callback. */ 1832static svn_error_t * 1833check_cancel(void *baton) 1834{ 1835 /* Cancel baton should be always NULL in command line client. */ 1836 SVN_ERR_ASSERT(baton == NULL); 1837 if (cancelled) 1838 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal")); 1839 else 1840 return SVN_NO_ERROR; 1841} 1842 1843svn_cancel_func_t 1844svn_cmdline__setup_cancellation_handler(void) 1845{ 1846 int i; 1847 1848 for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i) 1849 apr_signal(signal_map[i], signal_handler); 1850 1851#ifdef SIGPIPE 1852 /* Disable SIGPIPE generation for the platforms that have it. */ 1853 apr_signal(SIGPIPE, SIG_IGN); 1854#endif 1855 1856#ifdef SIGXFSZ 1857 /* Disable SIGXFSZ generation for the platforms that have it, otherwise 1858 * working with large files when compiled against an APR that doesn't have 1859 * large file support will crash the program, which is uncool. */ 1860 apr_signal(SIGXFSZ, SIG_IGN); 1861#endif 1862 1863 return check_cancel; 1864} 1865 1866void 1867svn_cmdline__disable_cancellation_handler(void) 1868{ 1869 int i; 1870 1871 for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i) 1872 apr_signal(signal_map[i], SIG_DFL); 1873} 1874 1875void 1876svn_cmdline__cancellation_exit(void) 1877{ 1878 int signum = 0; 1879 1880 if (cancelled && signum_cancelled) 1881 signum = signal_map[signum_cancelled - 1]; 1882 if (signum) 1883 { 1884#ifndef WIN32 1885 apr_signal(signum, SIG_DFL); 1886 /* No APR support for getpid() so cannot use apr_proc_kill(). */ 1887 kill(getpid(), signum); 1888#endif 1889 } 1890} 1891