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