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