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