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
svn_cmdline_init(const char * progname,FILE * error_stream)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 *
svn_cmdline_cstring_from_utf8(const char ** dest,const char * src,apr_pool_t * pool)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 *
svn_cmdline_cstring_from_utf8_fuzzy(const char * src,apr_pool_t * pool)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 *
svn_cmdline_cstring_to_utf8(const char ** dest,const char * src,apr_pool_t * pool)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 *
svn_cmdline_path_local_style_from_utf8(const char ** dest,const char * src,apr_pool_t * pool)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 *
svn_cmdline_printf(apr_pool_t * pool,const char * fmt,...)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 *
svn_cmdline_fprintf(FILE * stream,apr_pool_t * pool,const char * fmt,...)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 *
svn_cmdline_fputs(const char * string,FILE * stream,apr_pool_t * pool)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 *
svn_cmdline_fflush(FILE * stream)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
svn_cmdline_output_encoding(apr_pool_t * pool)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
svn_cmdline_handle_exit_error(svn_error_t * err,apr_pool_t * pool,const char * prefix)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 *
trust_server_cert_non_interactive(svn_auth_cred_ssl_server_trust_t ** cred_p,void * baton,const char * realm,apr_uint32_t failures,const svn_auth_ssl_server_cert_info_t * cert_info,svn_boolean_t may_save,apr_pool_t * pool)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 *
svn_cmdline_create_auth_baton2(svn_auth_baton_t ** ab,svn_boolean_t non_interactive,const char * auth_username,const char * auth_password,const char * config_dir,svn_boolean_t no_auth_cache,svn_boolean_t trust_server_cert_unknown_ca,svn_boolean_t trust_server_cert_cn_mismatch,svn_boolean_t trust_server_cert_expired,svn_boolean_t trust_server_cert_not_yet_valid,svn_boolean_t trust_server_cert_other_failure,svn_config_t * cfg,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)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 *
svn_cmdline__getopt_init(apr_getopt_t ** os,int argc,const char * argv[],apr_pool_t * pool)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
svn_cmdline__print_xml_prop(svn_stringbuf_t ** outstr,const char * propname,svn_string_t * propval,svn_boolean_t inherited_prop,apr_pool_t * pool)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 *
most_similar(const char * needle_cstr,const char ** haystack,apr_size_t haystack_len,apr_pool_t * scratch_pool)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 *
string_in_array(const char * needle,const char ** haystack,apr_size_t haystack_len,apr_pool_t * scratch_pool)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 *
validate_config_option(svn_cmdline__config_argument_t * config_option,apr_pool_t * scratch_pool)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 *
svn_cmdline__parse_config_option(apr_array_header_t * config_options,const char * opt_arg,const char * prefix,apr_pool_t * pool)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 *
svn_cmdline__apply_config_options(apr_hash_t * config,const apr_array_header_t * config_options,const char * prefix,const char * argument_name)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 *
next_line(const char ** str,apr_pool_t * pool)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 *
svn_cmdline__indent_string(const char * str,const char * indent,apr_pool_t * pool)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 *
svn_cmdline__print_prop_hash(svn_stream_t * out,apr_hash_t * prop_hash,svn_boolean_t names_only,apr_pool_t * pool)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 *
svn_cmdline__print_xml_prop_hash(svn_stringbuf_t ** outstr,apr_hash_t * prop_hash,svn_boolean_t names_only,svn_boolean_t inherited_props,apr_pool_t * pool)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
svn_cmdline__be_interactive(svn_boolean_t non_interactive,svn_boolean_t force_interactive)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 *
find_editor_binary(const char ** editor,const char * editor_cmd,apr_hash_t * config)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 *
svn_cmdline__edit_file_externally(const char * path,const char * editor_cmd,apr_hash_t * config,apr_pool_t * pool)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 *
svn_cmdline__edit_string_externally(svn_string_t ** edited_contents,const char ** tmpfile_left,const char * editor_cmd,const char * base_dir,const svn_string_t * contents,const char * filename,apr_hash_t * config,svn_boolean_t as_text,const char * encoding,apr_pool_t * pool)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 *
svn_cmdline__parse_trust_options(svn_boolean_t * trust_server_cert_unknown_ca,svn_boolean_t * trust_server_cert_cn_mismatch,svn_boolean_t * trust_server_cert_expired,svn_boolean_t * trust_server_cert_not_yet_valid,svn_boolean_t * trust_server_cert_other_failure,const char * opt_arg,apr_pool_t * scratch_pool)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 }
1599