]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_subr/cmdline.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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_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. */
79 static const char *input_encoding = NULL;
80
81 /* The stdout encoding. If null, it's the same as the native encoding. */
82 static 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 */
89 static svn_boolean_t shortcut_stdout_to_console = FALSE;
90 static svn_boolean_t shortcut_stderr_to_console = FALSE;
91 #endif
92
93
94 int
95 svn_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
297 svn_error_t *
298 svn_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
311 const char *
312 svn_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
320 svn_error_t *
321 svn_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
334 svn_error_t *
335 svn_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
344 svn_error_t *
345 svn_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
365 svn_error_t *
366 svn_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
380 svn_error_t *
381 svn_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
457 svn_error_t *
458 svn_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
480 const 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
490 int
491 svn_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
510 struct 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. */
530 static svn_error_t *
531 trust_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
564 svn_error_t *
565 svn_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
752 svn_error_t *
753 svn_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
766 void
767 svn_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. */
818 static const char *
819 most_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. */
856 static svn_error_t *
857 string_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.) */
888 static svn_error_t *
889 validate_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
928 svn_error_t *
929 svn_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
977 svn_error_t *
978 svn_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  */
1016 static const char *
1017 next_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
1044 const char *
1045 svn_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
1060 svn_error_t *
1061 svn_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
1127 svn_error_t *
1128 svn_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
1173 svn_boolean_t
1174 svn_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. */
1200 static svn_error_t *
1201 find_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
1262 svn_error_t *
1263 svn_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
1311 svn_error_t *
1312 svn_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
1553 svn_error_t *
1554 svn_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 }