1251881Speter/*
2251881Speter * cmdline.c :  Helpers for command-line programs.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter#include <stdlib.h>             /* for atexit() */
26251881Speter#include <stdio.h>              /* for setvbuf() */
27251881Speter#include <locale.h>             /* for setlocale() */
28251881Speter
29251881Speter#ifndef WIN32
30251881Speter#include <sys/types.h>
31251881Speter#include <sys/stat.h>
32251881Speter#include <fcntl.h>
33251881Speter#include <unistd.h>
34251881Speter#else
35251881Speter#include <crtdbg.h>
36251881Speter#include <io.h>
37289180Speter#include <conio.h>
38251881Speter#endif
39251881Speter
40251881Speter#include <apr.h>                /* for STDIN_FILENO */
41251881Speter#include <apr_errno.h>          /* for apr_strerror */
42369302Sdim#include <apr_version.h>
43369302Sdim#if APR_VERSION_AT_LEAST(1,5,0)
44362181Sdim#include <apr_escape.h>
45369302Sdim#else
46369302Sdim#include "private/svn_dep_compat.h"
47369302Sdim#endif
48251881Speter#include <apr_general.h>        /* for apr_initialize/apr_terminate */
49251881Speter#include <apr_strings.h>        /* for apr_snprintf */
50369302Sdim#include <apr_env.h>            /* for apr_env_get */
51251881Speter#include <apr_pools.h>
52362181Sdim#include <apr_signal.h>
53251881Speter
54251881Speter#include "svn_cmdline.h"
55251881Speter#include "svn_ctype.h"
56251881Speter#include "svn_dso.h"
57251881Speter#include "svn_dirent_uri.h"
58251881Speter#include "svn_hash.h"
59251881Speter#include "svn_path.h"
60251881Speter#include "svn_pools.h"
61251881Speter#include "svn_error.h"
62251881Speter#include "svn_nls.h"
63251881Speter#include "svn_utf.h"
64251881Speter#include "svn_auth.h"
65251881Speter#include "svn_xml.h"
66251881Speter#include "svn_base64.h"
67251881Speter#include "svn_config.h"
68251881Speter#include "svn_sorts.h"
69251881Speter#include "svn_props.h"
70251881Speter#include "svn_subst.h"
71251881Speter
72251881Speter#include "private/svn_cmdline_private.h"
73251881Speter#include "private/svn_utf_private.h"
74289180Speter#include "private/svn_sorts_private.h"
75251881Speter#include "private/svn_string_private.h"
76251881Speter
77251881Speter#include "svn_private_config.h"
78251881Speter
79251881Speter#include "win32_crashrpt.h"
80251881Speter
81289180Speter#if defined(WIN32) && defined(_MSC_VER) && (_MSC_VER < 1400)
82289180Speter/* Before Visual Studio 2005, the C runtime didn't handle encodings for the
83289180Speter   for the stdio output handling. */
84289180Speter#define CMDLINE_USE_CUSTOM_ENCODING
85289180Speter
86251881Speter/* The stdin encoding. If null, it's the same as the native encoding. */
87251881Speterstatic const char *input_encoding = NULL;
88251881Speter
89251881Speter/* The stdout encoding. If null, it's the same as the native encoding. */
90251881Speterstatic const char *output_encoding = NULL;
91289180Speter#elif defined(WIN32) && defined(_MSC_VER)
92289180Speter/* For now limit this code to Visual C++, as the result is highly dependent
93289180Speter   on the CRT implementation */
94289180Speter#define USE_WIN32_CONSOLE_SHORTCUT
95251881Speter
96289180Speter/* When TRUE, stdout/stderr is directly connected to a console */
97289180Speterstatic svn_boolean_t shortcut_stdout_to_console = FALSE;
98289180Speterstatic svn_boolean_t shortcut_stderr_to_console = FALSE;
99289180Speter#endif
100251881Speter
101289180Speter
102251881Speterint
103251881Spetersvn_cmdline_init(const char *progname, FILE *error_stream)
104251881Speter{
105251881Speter  apr_status_t status;
106251881Speter  apr_pool_t *pool;
107251881Speter  svn_error_t *err;
108251881Speter  char prefix_buf[64];  /* 64 is probably bigger than most program names */
109251881Speter
110251881Speter#ifndef WIN32
111251881Speter  {
112251881Speter    struct stat st;
113251881Speter
114251881Speter    /* The following makes sure that file descriptors 0 (stdin), 1
115251881Speter       (stdout) and 2 (stderr) will not be "reused", because if
116251881Speter       e.g. file descriptor 2 would be reused when opening a file, a
117251881Speter       write to stderr would write to that file and most likely
118251881Speter       corrupt it. */
119251881Speter    if ((fstat(0, &st) == -1 && open("/dev/null", O_RDONLY) == -1) ||
120251881Speter        (fstat(1, &st) == -1 && open("/dev/null", O_WRONLY) == -1) ||
121251881Speter        (fstat(2, &st) == -1 && open("/dev/null", O_WRONLY) == -1))
122251881Speter      {
123251881Speter        if (error_stream)
124251881Speter          fprintf(error_stream, "%s: error: cannot open '/dev/null'\n",
125251881Speter                  progname);
126251881Speter        return EXIT_FAILURE;
127251881Speter      }
128251881Speter  }
129251881Speter#endif
130251881Speter
131251881Speter  /* Ignore any errors encountered while attempting to change stream
132251881Speter     buffering, as the streams should retain their default buffering
133251881Speter     modes. */
134251881Speter  if (error_stream)
135251881Speter    setvbuf(error_stream, NULL, _IONBF, 0);
136251881Speter#ifndef WIN32
137251881Speter  setvbuf(stdout, NULL, _IOLBF, 0);
138251881Speter#endif
139251881Speter
140251881Speter#ifdef WIN32
141289180Speter#ifdef CMDLINE_USE_CUSTOM_ENCODING
142251881Speter  /* Initialize the input and output encodings. */
143251881Speter  {
144251881Speter    static char input_encoding_buffer[16];
145251881Speter    static char output_encoding_buffer[16];
146251881Speter
147251881Speter    apr_snprintf(input_encoding_buffer, sizeof input_encoding_buffer,
148251881Speter                 "CP%u", (unsigned) GetConsoleCP());
149251881Speter    input_encoding = input_encoding_buffer;
150251881Speter
151251881Speter    apr_snprintf(output_encoding_buffer, sizeof output_encoding_buffer,
152251881Speter                 "CP%u", (unsigned) GetConsoleOutputCP());
153251881Speter    output_encoding = output_encoding_buffer;
154251881Speter  }
155289180Speter#endif /* CMDLINE_USE_CUSTOM_ENCODING */
156251881Speter
157251881Speter#ifdef SVN_USE_WIN32_CRASHHANDLER
158289180Speter  if (!getenv("SVN_CMDLINE_DISABLE_CRASH_HANDLER"))
159289180Speter    {
160289180Speter      /* Attach (but don't load) the crash handler */
161289180Speter      SetUnhandledExceptionFilter(svn__unhandled_exception_filter);
162251881Speter
163251881Speter#if _MSC_VER >= 1400
164289180Speter      /* ### This should work for VC++ 2002 (=1300) and later */
165289180Speter      /* Show the abort message on STDERR instead of a dialog to allow
166289180Speter         scripts (e.g. our testsuite) to continue after an abort without
167289180Speter         user intervention. Allow overriding for easier debugging. */
168289180Speter      if (!getenv("SVN_CMDLINE_USE_DIALOG_FOR_ABORT"))
169289180Speter        {
170289180Speter          /* In release mode: Redirect abort() errors to stderr */
171289180Speter          _set_error_mode(_OUT_TO_STDERR);
172251881Speter
173289180Speter          /* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr.
174289180Speter             (Ignored in release builds) */
175289180Speter          _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR);
176289180Speter          _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR);
177289180Speter          _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR);
178289180Speter          _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
179289180Speter          _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
180289180Speter          _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
181289180Speter        }
182289180Speter#endif /* _MSC_VER >= 1400 */
183251881Speter    }
184251881Speter#endif /* SVN_USE_WIN32_CRASHHANDLER */
185251881Speter
186251881Speter#endif /* WIN32 */
187251881Speter
188251881Speter  /* C programs default to the "C" locale. But because svn is supposed
189251881Speter     to be i18n-aware, it should inherit the default locale of its
190251881Speter     environment.  */
191251881Speter  if (!setlocale(LC_ALL, "")
192251881Speter      && !setlocale(LC_CTYPE, ""))
193251881Speter    {
194251881Speter      if (error_stream)
195251881Speter        {
196251881Speter          const char *env_vars[] = { "LC_ALL", "LC_CTYPE", "LANG", NULL };
197251881Speter          const char **env_var = &env_vars[0], *env_val = NULL;
198251881Speter          while (*env_var)
199251881Speter            {
200251881Speter              env_val = getenv(*env_var);
201251881Speter              if (env_val && env_val[0])
202251881Speter                break;
203251881Speter              ++env_var;
204251881Speter            }
205251881Speter
206251881Speter          if (!*env_var)
207251881Speter            {
208251881Speter              /* Unlikely. Can setlocale fail if no env vars are set? */
209251881Speter              --env_var;
210251881Speter              env_val = "not set";
211251881Speter            }
212251881Speter
213251881Speter          fprintf(error_stream,
214251881Speter                  "%s: warning: cannot set LC_CTYPE locale\n"
215251881Speter                  "%s: warning: environment variable %s is %s\n"
216251881Speter                  "%s: warning: please check that your locale name is correct\n",
217251881Speter                  progname, progname, *env_var, env_val, progname);
218251881Speter        }
219251881Speter    }
220251881Speter
221251881Speter  /* Initialize the APR subsystem, and register an atexit() function
222251881Speter     to Uninitialize that subsystem at program exit. */
223251881Speter  status = apr_initialize();
224251881Speter  if (status)
225251881Speter    {
226251881Speter      if (error_stream)
227251881Speter        {
228251881Speter          char buf[1024];
229251881Speter          apr_strerror(status, buf, sizeof(buf) - 1);
230251881Speter          fprintf(error_stream,
231251881Speter                  "%s: error: cannot initialize APR: %s\n",
232251881Speter                  progname, buf);
233251881Speter        }
234251881Speter      return EXIT_FAILURE;
235251881Speter    }
236251881Speter
237251881Speter  strncpy(prefix_buf, progname, sizeof(prefix_buf) - 3);
238251881Speter  prefix_buf[sizeof(prefix_buf) - 3] = '\0';
239251881Speter  strcat(prefix_buf, ": ");
240251881Speter
241251881Speter  /* DSO pool must be created before any other pools used by the
242251881Speter     application so that pool cleanup doesn't unload DSOs too
243251881Speter     early. See docstring of svn_dso_initialize2(). */
244251881Speter  if ((err = svn_dso_initialize2()))
245251881Speter    {
246251881Speter      if (error_stream)
247251881Speter        svn_handle_error2(err, error_stream, TRUE, prefix_buf);
248251881Speter
249251881Speter      svn_error_clear(err);
250251881Speter      return EXIT_FAILURE;
251251881Speter    }
252251881Speter
253251881Speter  if (0 > atexit(apr_terminate))
254251881Speter    {
255251881Speter      if (error_stream)
256251881Speter        fprintf(error_stream,
257251881Speter                "%s: error: atexit registration failed\n",
258251881Speter                progname);
259251881Speter      return EXIT_FAILURE;
260251881Speter    }
261251881Speter
262251881Speter  /* Create a pool for use by the UTF-8 routines.  It will be cleaned
263251881Speter     up by APR at exit time. */
264251881Speter  pool = svn_pool_create(NULL);
265251881Speter  svn_utf_initialize2(FALSE, pool);
266251881Speter
267251881Speter  if ((err = svn_nls_init()))
268251881Speter    {
269251881Speter      if (error_stream)
270251881Speter        svn_handle_error2(err, error_stream, TRUE, prefix_buf);
271251881Speter
272251881Speter      svn_error_clear(err);
273251881Speter      return EXIT_FAILURE;
274251881Speter    }
275251881Speter
276289180Speter#ifdef USE_WIN32_CONSOLE_SHORTCUT
277289180Speter  if (_isatty(STDOUT_FILENO))
278289180Speter    {
279289180Speter      DWORD ignored;
280289180Speter      HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
281289180Speter
282289180Speter       /* stdout is a char device handle, but is it the console? */
283289180Speter       if (GetConsoleMode(stdout_handle, &ignored))
284289180Speter        shortcut_stdout_to_console = TRUE;
285289180Speter
286289180Speter       /* Don't close stdout_handle */
287289180Speter    }
288289180Speter  if (_isatty(STDERR_FILENO))
289289180Speter    {
290289180Speter      DWORD ignored;
291289180Speter      HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
292289180Speter
293289180Speter       /* stderr is a char device handle, but is it the console? */
294289180Speter      if (GetConsoleMode(stderr_handle, &ignored))
295289180Speter          shortcut_stderr_to_console = TRUE;
296289180Speter
297289180Speter      /* Don't close stderr_handle */
298289180Speter    }
299289180Speter#endif
300289180Speter
301251881Speter  return EXIT_SUCCESS;
302251881Speter}
303251881Speter
304251881Speter
305251881Spetersvn_error_t *
306251881Spetersvn_cmdline_cstring_from_utf8(const char **dest,
307251881Speter                              const char *src,
308251881Speter                              apr_pool_t *pool)
309251881Speter{
310289180Speter#ifdef CMDLINE_USE_CUSTOM_ENCODING
311289180Speter  if (output_encoding != NULL)
312251881Speter    return svn_utf_cstring_from_utf8_ex2(dest, src, output_encoding, pool);
313289180Speter#endif
314289180Speter
315289180Speter  return svn_utf_cstring_from_utf8(dest, src, pool);
316251881Speter}
317251881Speter
318251881Speter
319251881Speterconst char *
320251881Spetersvn_cmdline_cstring_from_utf8_fuzzy(const char *src,
321251881Speter                                    apr_pool_t *pool)
322251881Speter{
323251881Speter  return svn_utf__cstring_from_utf8_fuzzy(src, pool,
324251881Speter                                          svn_cmdline_cstring_from_utf8);
325251881Speter}
326251881Speter
327251881Speter
328251881Spetersvn_error_t *
329251881Spetersvn_cmdline_cstring_to_utf8(const char **dest,
330251881Speter                            const char *src,
331251881Speter                            apr_pool_t *pool)
332251881Speter{
333289180Speter#ifdef CMDLINE_USE_CUSTOM_ENCODING
334289180Speter  if (input_encoding != NULL)
335251881Speter    return svn_utf_cstring_to_utf8_ex2(dest, src, input_encoding, pool);
336289180Speter#endif
337289180Speter
338289180Speter  return svn_utf_cstring_to_utf8(dest, src, pool);
339251881Speter}
340251881Speter
341251881Speter
342251881Spetersvn_error_t *
343251881Spetersvn_cmdline_path_local_style_from_utf8(const char **dest,
344251881Speter                                       const char *src,
345251881Speter                                       apr_pool_t *pool)
346251881Speter{
347251881Speter  return svn_cmdline_cstring_from_utf8(dest,
348251881Speter                                       svn_dirent_local_style(src, pool),
349251881Speter                                       pool);
350251881Speter}
351251881Speter
352251881Spetersvn_error_t *
353362181Sdimsvn_cmdline__stdin_readline(const char **result,
354362181Sdim                            apr_pool_t *result_pool,
355362181Sdim                            apr_pool_t *scratch_pool)
356362181Sdim{
357362181Sdim  svn_stringbuf_t *buf = NULL;
358362181Sdim  svn_stream_t *stdin_stream = NULL;
359362181Sdim  svn_boolean_t oob = FALSE;
360362181Sdim
361362181Sdim  SVN_ERR(svn_stream_for_stdin2(&stdin_stream, TRUE, scratch_pool));
362362181Sdim  SVN_ERR(svn_stream_readline(stdin_stream, &buf, APR_EOL_STR, &oob, result_pool));
363362181Sdim
364362181Sdim  *result = buf->data;
365362181Sdim
366362181Sdim  return SVN_NO_ERROR;
367362181Sdim}
368362181Sdim
369362181Sdimsvn_error_t *
370251881Spetersvn_cmdline_printf(apr_pool_t *pool, const char *fmt, ...)
371251881Speter{
372251881Speter  const char *message;
373251881Speter  va_list ap;
374251881Speter
375251881Speter  /* A note about encoding issues:
376251881Speter   * APR uses the execution character set, but here we give it UTF-8 strings,
377251881Speter   * both the fmt argument and any other string arguments.  Since apr_pvsprintf
378251881Speter   * only cares about and produces ASCII characters, this works under the
379251881Speter   * assumption that all supported platforms use an execution character set
380251881Speter   * with ASCII as a subset.
381251881Speter   */
382251881Speter
383251881Speter  va_start(ap, fmt);
384251881Speter  message = apr_pvsprintf(pool, fmt, ap);
385251881Speter  va_end(ap);
386251881Speter
387251881Speter  return svn_cmdline_fputs(message, stdout, pool);
388251881Speter}
389251881Speter
390251881Spetersvn_error_t *
391251881Spetersvn_cmdline_fprintf(FILE *stream, apr_pool_t *pool, const char *fmt, ...)
392251881Speter{
393251881Speter  const char *message;
394251881Speter  va_list ap;
395251881Speter
396251881Speter  /* See svn_cmdline_printf () for a note about character encoding issues. */
397251881Speter
398251881Speter  va_start(ap, fmt);
399251881Speter  message = apr_pvsprintf(pool, fmt, ap);
400251881Speter  va_end(ap);
401251881Speter
402251881Speter  return svn_cmdline_fputs(message, stream, pool);
403251881Speter}
404251881Speter
405251881Spetersvn_error_t *
406251881Spetersvn_cmdline_fputs(const char *string, FILE* stream, apr_pool_t *pool)
407251881Speter{
408251881Speter  svn_error_t *err;
409251881Speter  const char *out;
410251881Speter
411289180Speter#ifdef USE_WIN32_CONSOLE_SHORTCUT
412289180Speter  /* For legacy reasons the Visual C++ runtime converts output to the console
413289180Speter     from the native 'ansi' encoding, to unicode, then back to 'ansi' and then
414289180Speter     onwards to the console which is implemented as unicode.
415289180Speter
416289180Speter     For operations like 'svn status -v' this may cause about 70% of the total
417289180Speter     processing time, with absolutely no gain.
418289180Speter
419289180Speter     For this specific scenario this shortcut exists. It has the nice side
420289180Speter     effect of allowing full unicode output to the console.
421289180Speter
422289180Speter     Note that this shortcut is not used when the output is redirected, as in
423289180Speter     that case the data is put on the pipe/file after the first conversion to
424289180Speter     ansi. In this case the most expensive conversion is already avoided.
425289180Speter   */
426289180Speter  if ((stream == stdout && shortcut_stdout_to_console)
427289180Speter      || (stream == stderr && shortcut_stderr_to_console))
428289180Speter    {
429289180Speter      WCHAR *result;
430289180Speter
431289180Speter      if (string[0] == '\0')
432289180Speter        return SVN_NO_ERROR;
433289180Speter
434289180Speter      SVN_ERR(svn_cmdline_fflush(stream)); /* Flush existing output */
435289180Speter
436289180Speter      SVN_ERR(svn_utf__win32_utf8_to_utf16(&result, string, NULL, pool));
437289180Speter
438289180Speter      if (_cputws(result))
439289180Speter        {
440289180Speter          if (apr_get_os_error())
441289180Speter          {
442289180Speter            return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
443289180Speter          }
444289180Speter        }
445289180Speter
446289180Speter      return SVN_NO_ERROR;
447289180Speter    }
448289180Speter#endif
449289180Speter
450251881Speter  err = svn_cmdline_cstring_from_utf8(&out, string, pool);
451251881Speter
452251881Speter  if (err)
453251881Speter    {
454251881Speter      svn_error_clear(err);
455251881Speter      out = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
456251881Speter    }
457251881Speter
458251881Speter  /* On POSIX systems, errno will be set on an error in fputs, but this might
459251881Speter     not be the case on other platforms.  We reset errno and only
460251881Speter     use it if it was set by the below fputs call.  Else, we just return
461251881Speter     a generic error. */
462251881Speter  errno = 0;
463251881Speter
464251881Speter  if (fputs(out, stream) == EOF)
465251881Speter    {
466251881Speter      if (apr_get_os_error()) /* is errno on POSIX */
467251881Speter        {
468251881Speter          /* ### Issue #3014: Return a specific error for broken pipes,
469251881Speter           * ### with a single element in the error chain. */
470257936Speter          if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error()))
471251881Speter            return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
472251881Speter          else
473251881Speter            return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
474251881Speter        }
475251881Speter      else
476251881Speter        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
477251881Speter    }
478251881Speter
479251881Speter  return SVN_NO_ERROR;
480251881Speter}
481251881Speter
482251881Spetersvn_error_t *
483251881Spetersvn_cmdline_fflush(FILE *stream)
484251881Speter{
485251881Speter  /* See comment in svn_cmdline_fputs about use of errno and stdio. */
486251881Speter  errno = 0;
487251881Speter  if (fflush(stream) == EOF)
488251881Speter    {
489251881Speter      if (apr_get_os_error()) /* is errno on POSIX */
490251881Speter        {
491251881Speter          /* ### Issue #3014: Return a specific error for broken pipes,
492251881Speter           * ### with a single element in the error chain. */
493257936Speter          if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error()))
494251881Speter            return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
495251881Speter          else
496251881Speter            return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
497251881Speter        }
498251881Speter      else
499251881Speter        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
500251881Speter    }
501251881Speter
502251881Speter  return SVN_NO_ERROR;
503251881Speter}
504251881Speter
505251881Speterconst char *svn_cmdline_output_encoding(apr_pool_t *pool)
506251881Speter{
507289180Speter#ifdef CMDLINE_USE_CUSTOM_ENCODING
508251881Speter  if (output_encoding)
509251881Speter    return apr_pstrdup(pool, output_encoding);
510289180Speter#endif
511289180Speter
512289180Speter  return SVN_APR_LOCALE_CHARSET;
513251881Speter}
514251881Speter
515251881Speterint
516251881Spetersvn_cmdline_handle_exit_error(svn_error_t *err,
517251881Speter                              apr_pool_t *pool,
518251881Speter                              const char *prefix)
519251881Speter{
520251881Speter  /* Issue #3014:
521251881Speter   * Don't print anything on broken pipes. The pipe was likely
522251881Speter   * closed by the process at the other end. We expect that
523251881Speter   * process to perform error reporting as necessary.
524251881Speter   *
525251881Speter   * ### This assumes that there is only one error in a chain for
526251881Speter   * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
527251881Speter  if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
528251881Speter    svn_handle_error2(err, stderr, FALSE, prefix);
529251881Speter  svn_error_clear(err);
530251881Speter  if (pool)
531251881Speter    svn_pool_destroy(pool);
532251881Speter  return EXIT_FAILURE;
533251881Speter}
534251881Speter
535289180Speterstruct trust_server_cert_non_interactive_baton {
536289180Speter  svn_boolean_t trust_server_cert_unknown_ca;
537289180Speter  svn_boolean_t trust_server_cert_cn_mismatch;
538289180Speter  svn_boolean_t trust_server_cert_expired;
539289180Speter  svn_boolean_t trust_server_cert_not_yet_valid;
540289180Speter  svn_boolean_t trust_server_cert_other_failure;
541289180Speter};
542289180Speter
543251881Speter/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'.
544251881Speter
545251881Speter   Don't actually prompt.  Instead, set *CRED_P to valid credentials
546289180Speter   iff FAILURES is empty or may be accepted according to the flags
547289180Speter   in BATON. If there are any other failure bits, then set *CRED_P
548289180Speter   to null (that is, reject the cert).
549251881Speter
550251881Speter   Ignore MAY_SAVE; we don't save certs we never prompted for.
551251881Speter
552289180Speter   Ignore REALM and CERT_INFO,
553251881Speter
554251881Speter   Ignore any further films by George Lucas. */
555251881Speterstatic svn_error_t *
556289180Spetertrust_server_cert_non_interactive(svn_auth_cred_ssl_server_trust_t **cred_p,
557289180Speter                                  void *baton,
558289180Speter                                  const char *realm,
559289180Speter                                  apr_uint32_t failures,
560289180Speter                                  const svn_auth_ssl_server_cert_info_t
561289180Speter                                    *cert_info,
562289180Speter                                  svn_boolean_t may_save,
563289180Speter                                  apr_pool_t *pool)
564251881Speter{
565289180Speter  struct trust_server_cert_non_interactive_baton *b = baton;
566289180Speter  apr_uint32_t non_ignored_failures;
567251881Speter  *cred_p = NULL;
568251881Speter
569289180Speter  /* Mask away bits we are instructed to ignore. */
570289180Speter  non_ignored_failures = failures & ~(
571289180Speter        (b->trust_server_cert_unknown_ca ? SVN_AUTH_SSL_UNKNOWNCA : 0)
572289180Speter      | (b->trust_server_cert_cn_mismatch ? SVN_AUTH_SSL_CNMISMATCH : 0)
573289180Speter      | (b->trust_server_cert_expired ? SVN_AUTH_SSL_EXPIRED : 0)
574289180Speter      | (b->trust_server_cert_not_yet_valid ? SVN_AUTH_SSL_NOTYETVALID : 0)
575289180Speter      | (b->trust_server_cert_other_failure ? SVN_AUTH_SSL_OTHER : 0)
576289180Speter  );
577289180Speter
578289180Speter  /* If no failures remain, accept the certificate. */
579289180Speter  if (non_ignored_failures == 0)
580251881Speter    {
581251881Speter      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
582251881Speter      (*cred_p)->may_save = FALSE;
583251881Speter      (*cred_p)->accepted_failures = failures;
584251881Speter    }
585251881Speter
586251881Speter  return SVN_NO_ERROR;
587251881Speter}
588251881Speter
589251881Spetersvn_error_t *
590289180Spetersvn_cmdline_create_auth_baton2(svn_auth_baton_t **ab,
591289180Speter                               svn_boolean_t non_interactive,
592289180Speter                               const char *auth_username,
593289180Speter                               const char *auth_password,
594289180Speter                               const char *config_dir,
595289180Speter                               svn_boolean_t no_auth_cache,
596289180Speter                               svn_boolean_t trust_server_cert_unknown_ca,
597289180Speter                               svn_boolean_t trust_server_cert_cn_mismatch,
598289180Speter                               svn_boolean_t trust_server_cert_expired,
599289180Speter                               svn_boolean_t trust_server_cert_not_yet_valid,
600289180Speter                               svn_boolean_t trust_server_cert_other_failure,
601289180Speter                               svn_config_t *cfg,
602289180Speter                               svn_cancel_func_t cancel_func,
603289180Speter                               void *cancel_baton,
604289180Speter                               apr_pool_t *pool)
605289180Speter
606251881Speter{
607251881Speter  svn_boolean_t store_password_val = TRUE;
608251881Speter  svn_boolean_t store_auth_creds_val = TRUE;
609251881Speter  svn_auth_provider_object_t *provider;
610251881Speter  svn_cmdline_prompt_baton2_t *pb = NULL;
611251881Speter
612251881Speter  /* The whole list of registered providers */
613251881Speter  apr_array_header_t *providers;
614251881Speter
615251881Speter  /* Populate the registered providers with the platform-specific providers */
616251881Speter  SVN_ERR(svn_auth_get_platform_specific_client_providers(&providers,
617251881Speter                                                          cfg, pool));
618251881Speter
619251881Speter  /* If we have a cancellation function, cram it and the stuff it
620251881Speter     needs into the prompt baton. */
621251881Speter  if (cancel_func)
622251881Speter    {
623251881Speter      pb = apr_palloc(pool, sizeof(*pb));
624251881Speter      pb->cancel_func = cancel_func;
625251881Speter      pb->cancel_baton = cancel_baton;
626251881Speter      pb->config_dir = config_dir;
627251881Speter    }
628251881Speter
629251881Speter  if (!non_interactive)
630251881Speter    {
631251881Speter      /* This provider doesn't prompt the user in order to get creds;
632251881Speter         it prompts the user regarding the caching of creds. */
633251881Speter      svn_auth_get_simple_provider2(&provider,
634251881Speter                                    svn_cmdline_auth_plaintext_prompt,
635251881Speter                                    pb, pool);
636251881Speter    }
637251881Speter  else
638251881Speter    {
639251881Speter      svn_auth_get_simple_provider2(&provider, NULL, NULL, pool);
640251881Speter    }
641251881Speter
642251881Speter  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
643251881Speter  svn_auth_get_username_provider(&provider, pool);
644251881Speter  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
645251881Speter
646251881Speter  svn_auth_get_ssl_server_trust_file_provider(&provider, pool);
647251881Speter  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
648251881Speter  svn_auth_get_ssl_client_cert_file_provider(&provider, pool);
649251881Speter  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
650251881Speter
651251881Speter  if (!non_interactive)
652251881Speter    {
653251881Speter      /* This provider doesn't prompt the user in order to get creds;
654251881Speter         it prompts the user regarding the caching of creds. */
655251881Speter      svn_auth_get_ssl_client_cert_pw_file_provider2
656251881Speter        (&provider, svn_cmdline_auth_plaintext_passphrase_prompt,
657251881Speter         pb, pool);
658251881Speter    }
659251881Speter  else
660251881Speter    {
661251881Speter      svn_auth_get_ssl_client_cert_pw_file_provider2(&provider, NULL, NULL,
662251881Speter                                                     pool);
663251881Speter    }
664251881Speter  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
665251881Speter
666251881Speter  if (!non_interactive)
667251881Speter    {
668251881Speter      svn_boolean_t ssl_client_cert_file_prompt;
669251881Speter
670251881Speter      SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt,
671251881Speter                                  SVN_CONFIG_SECTION_AUTH,
672251881Speter                                  SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT,
673251881Speter                                  FALSE));
674251881Speter
675251881Speter      /* Two basic prompt providers: username/password, and just username. */
676251881Speter      svn_auth_get_simple_prompt_provider(&provider,
677251881Speter                                          svn_cmdline_auth_simple_prompt,
678251881Speter                                          pb,
679251881Speter                                          2, /* retry limit */
680251881Speter                                          pool);
681251881Speter      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
682251881Speter
683251881Speter      svn_auth_get_username_prompt_provider
684251881Speter        (&provider, svn_cmdline_auth_username_prompt, pb,
685251881Speter         2, /* retry limit */ pool);
686251881Speter      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
687251881Speter
688251881Speter      /* SSL prompt providers: server-certs and client-cert-passphrases.  */
689251881Speter      svn_auth_get_ssl_server_trust_prompt_provider
690251881Speter        (&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool);
691251881Speter      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
692251881Speter
693251881Speter      svn_auth_get_ssl_client_cert_pw_prompt_provider
694251881Speter        (&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool);
695251881Speter      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
696251881Speter
697251881Speter      /* If configuration allows, add a provider for client-cert path
698251881Speter         prompting, too. */
699251881Speter      if (ssl_client_cert_file_prompt)
700251881Speter        {
701251881Speter          svn_auth_get_ssl_client_cert_prompt_provider
702251881Speter            (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool);
703251881Speter          APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
704251881Speter        }
705251881Speter    }
706289180Speter  else if (trust_server_cert_unknown_ca || trust_server_cert_cn_mismatch ||
707289180Speter           trust_server_cert_expired || trust_server_cert_not_yet_valid ||
708289180Speter           trust_server_cert_other_failure)
709251881Speter    {
710289180Speter      struct trust_server_cert_non_interactive_baton *b;
711289180Speter
712289180Speter      b = apr_palloc(pool, sizeof(*b));
713289180Speter      b->trust_server_cert_unknown_ca = trust_server_cert_unknown_ca;
714289180Speter      b->trust_server_cert_cn_mismatch = trust_server_cert_cn_mismatch;
715289180Speter      b->trust_server_cert_expired = trust_server_cert_expired;
716289180Speter      b->trust_server_cert_not_yet_valid = trust_server_cert_not_yet_valid;
717289180Speter      b->trust_server_cert_other_failure = trust_server_cert_other_failure;
718289180Speter
719251881Speter      /* Remember, only register this provider if non_interactive. */
720251881Speter      svn_auth_get_ssl_server_trust_prompt_provider
721289180Speter        (&provider, trust_server_cert_non_interactive, b, pool);
722251881Speter      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
723251881Speter    }
724251881Speter
725251881Speter  /* Build an authentication baton to give to libsvn_client. */
726251881Speter  svn_auth_open(ab, providers, pool);
727251881Speter
728251881Speter  /* Place any default --username or --password credentials into the
729251881Speter     auth_baton's run-time parameter hash. */
730251881Speter  if (auth_username)
731251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_USERNAME,
732251881Speter                           auth_username);
733251881Speter  if (auth_password)
734251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD,
735251881Speter                           auth_password);
736251881Speter
737251881Speter  /* Same with the --non-interactive option. */
738251881Speter  if (non_interactive)
739251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NON_INTERACTIVE, "");
740251881Speter
741251881Speter  if (config_dir)
742251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_CONFIG_DIR,
743251881Speter                           config_dir);
744251881Speter
745251881Speter  /* Determine whether storing passwords in any form is allowed.
746251881Speter   * This is the deprecated location for this option, the new
747251881Speter   * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
748251881Speter   * override the value we set here. */
749251881Speter  SVN_ERR(svn_config_get_bool(cfg, &store_password_val,
750251881Speter                              SVN_CONFIG_SECTION_AUTH,
751251881Speter                              SVN_CONFIG_OPTION_STORE_PASSWORDS,
752251881Speter                              SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS));
753251881Speter
754251881Speter  if (! store_password_val)
755251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, "");
756251881Speter
757251881Speter  /* Determine whether we are allowed to write to the auth/ area.
758251881Speter   * This is the deprecated location for this option, the new
759251881Speter   * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
760251881Speter   * override the value we set here. */
761251881Speter  SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds_val,
762251881Speter                              SVN_CONFIG_SECTION_AUTH,
763251881Speter                              SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
764251881Speter                              SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS));
765251881Speter
766251881Speter  if (no_auth_cache || ! store_auth_creds_val)
767251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NO_AUTH_CACHE, "");
768251881Speter
769251881Speter#ifdef SVN_HAVE_GNOME_KEYRING
770251881Speter  svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC,
771251881Speter                         &svn_cmdline__auth_gnome_keyring_unlock_prompt);
772251881Speter#endif /* SVN_HAVE_GNOME_KEYRING */
773251881Speter
774251881Speter  return SVN_NO_ERROR;
775251881Speter}
776251881Speter
777251881Spetersvn_error_t *
778251881Spetersvn_cmdline__getopt_init(apr_getopt_t **os,
779251881Speter                         int argc,
780251881Speter                         const char *argv[],
781251881Speter                         apr_pool_t *pool)
782251881Speter{
783251881Speter  apr_status_t apr_err = apr_getopt_init(os, pool, argc, argv);
784251881Speter  if (apr_err)
785251881Speter    return svn_error_wrap_apr(apr_err,
786251881Speter                              _("Error initializing command line arguments"));
787251881Speter  return SVN_NO_ERROR;
788251881Speter}
789251881Speter
790251881Speter
791251881Spetervoid
792251881Spetersvn_cmdline__print_xml_prop(svn_stringbuf_t **outstr,
793251881Speter                            const char* propname,
794251881Speter                            svn_string_t *propval,
795251881Speter                            svn_boolean_t inherited_prop,
796251881Speter                            apr_pool_t *pool)
797251881Speter{
798251881Speter  const char *xml_safe;
799251881Speter  const char *encoding = NULL;
800251881Speter
801251881Speter  if (*outstr == NULL)
802251881Speter    *outstr = svn_stringbuf_create_empty(pool);
803251881Speter
804251881Speter  if (svn_xml_is_xml_safe(propval->data, propval->len))
805251881Speter    {
806251881Speter      svn_stringbuf_t *xml_esc = NULL;
807251881Speter      svn_xml_escape_cdata_string(&xml_esc, propval, pool);
808251881Speter      xml_safe = xml_esc->data;
809251881Speter    }
810251881Speter  else
811251881Speter    {
812251881Speter      const svn_string_t *base64ed = svn_base64_encode_string2(propval, TRUE,
813251881Speter                                                               pool);
814251881Speter      encoding = "base64";
815251881Speter      xml_safe = base64ed->data;
816251881Speter    }
817251881Speter
818251881Speter  if (encoding)
819251881Speter    svn_xml_make_open_tag(
820251881Speter      outstr, pool, svn_xml_protect_pcdata,
821251881Speter      inherited_prop ? "inherited_property" : "property",
822251881Speter      "name", propname,
823289180Speter      "encoding", encoding, SVN_VA_NULL);
824251881Speter  else
825251881Speter    svn_xml_make_open_tag(
826251881Speter      outstr, pool, svn_xml_protect_pcdata,
827251881Speter      inherited_prop ? "inherited_property" : "property",
828289180Speter      "name", propname, SVN_VA_NULL);
829251881Speter
830251881Speter  svn_stringbuf_appendcstr(*outstr, xml_safe);
831251881Speter
832251881Speter  svn_xml_make_close_tag(
833251881Speter    outstr, pool,
834251881Speter    inherited_prop ? "inherited_property" : "property");
835251881Speter
836251881Speter  return;
837251881Speter}
838251881Speter
839289180Speter/* Return the most similar string to NEEDLE in HAYSTACK, which contains
840289180Speter * HAYSTACK_LEN elements.  Return NULL if no string is sufficiently similar.
841289180Speter */
842289180Speter/* See svn_cl__similarity_check() for a more general solution. */
843289180Speterstatic const char *
844289180Spetermost_similar(const char *needle_cstr,
845289180Speter             const char **haystack,
846289180Speter             apr_size_t haystack_len,
847289180Speter             apr_pool_t *scratch_pool)
848289180Speter{
849362181Sdim  const char *max_similar = NULL;
850289180Speter  apr_size_t max_score = 0;
851289180Speter  apr_size_t i;
852289180Speter  svn_membuf_t membuf;
853289180Speter  svn_string_t *needle_str = svn_string_create(needle_cstr, scratch_pool);
854289180Speter
855289180Speter  svn_membuf__create(&membuf, 64, scratch_pool);
856289180Speter
857289180Speter  for (i = 0; i < haystack_len; i++)
858289180Speter    {
859289180Speter      apr_size_t score;
860289180Speter      svn_string_t *hay = svn_string_create(haystack[i], scratch_pool);
861289180Speter
862289180Speter      score = svn_string__similarity(needle_str, hay, &membuf, NULL);
863289180Speter
864289180Speter      /* If you update this factor, consider updating
865289180Speter       * svn_cl__similarity_check(). */
866289180Speter      if (score >= (2 * SVN_STRING__SIM_RANGE_MAX + 1) / 3
867289180Speter          && score > max_score)
868289180Speter        {
869289180Speter          max_score = score;
870289180Speter          max_similar = haystack[i];
871289180Speter        }
872289180Speter    }
873289180Speter
874362181Sdim  return max_similar;
875289180Speter}
876289180Speter
877289180Speter/* Verify that NEEDLE is in HAYSTACK, which contains HAYSTACK_LEN elements. */
878289180Speterstatic svn_error_t *
879289180Speterstring_in_array(const char *needle,
880289180Speter                const char **haystack,
881289180Speter                apr_size_t haystack_len,
882289180Speter                apr_pool_t *scratch_pool)
883289180Speter{
884289180Speter  const char *next_of_kin;
885289180Speter  apr_size_t i;
886289180Speter  for (i = 0; i < haystack_len; i++)
887289180Speter    {
888289180Speter      if (!strcmp(needle, haystack[i]))
889289180Speter        return SVN_NO_ERROR;
890289180Speter    }
891289180Speter
892289180Speter  /* Error. */
893289180Speter  next_of_kin = most_similar(needle, haystack, haystack_len, scratch_pool);
894289180Speter  if (next_of_kin)
895289180Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
896289180Speter                             _("Ignoring unknown value '%s'; "
897289180Speter                               "did you mean '%s'?"),
898289180Speter                             needle, next_of_kin);
899289180Speter  else
900289180Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
901289180Speter                             _("Ignoring unknown value '%s'"),
902289180Speter                             needle);
903289180Speter}
904289180Speter
905289180Speter#include "config_keys.inc"
906289180Speter
907289180Speter/* Validate the FILE, SECTION, and OPTION components of CONFIG_OPTION are
908362181Sdim * known.  Return an error if not.  (An unknown value may be either a typo
909289180Speter * or added in a newer minor version of Subversion.) */
910289180Speterstatic svn_error_t *
911289180Spetervalidate_config_option(svn_cmdline__config_argument_t *config_option,
912289180Speter                       apr_pool_t *scratch_pool)
913289180Speter{
914289180Speter  svn_boolean_t arbitrary_keys = FALSE;
915289180Speter
916289180Speter  /* TODO: some day, we could also verify that OPTION is valid for SECTION;
917289180Speter     i.e., forbid invalid combinations such as config:auth:diff-extensions. */
918289180Speter
919289180Speter#define ARRAYLEN(x) ( sizeof((x)) / sizeof((x)[0]) )
920289180Speter
921289180Speter  SVN_ERR(string_in_array(config_option->file, svn__valid_config_files,
922289180Speter                          ARRAYLEN(svn__valid_config_files),
923289180Speter                          scratch_pool));
924289180Speter  SVN_ERR(string_in_array(config_option->section, svn__valid_config_sections,
925289180Speter                          ARRAYLEN(svn__valid_config_sections),
926289180Speter                          scratch_pool));
927289180Speter
928289180Speter  /* Don't validate option names for sections such as servers[group],
929289180Speter   * config[tunnels], and config[auto-props] that permit arbitrary options. */
930289180Speter    {
931289180Speter      int i;
932289180Speter
933289180Speter      for (i = 0; i < ARRAYLEN(svn__empty_config_sections); i++)
934289180Speter        {
935289180Speter        if (!strcmp(config_option->section, svn__empty_config_sections[i]))
936289180Speter          arbitrary_keys = TRUE;
937289180Speter        }
938289180Speter    }
939289180Speter
940289180Speter  if (! arbitrary_keys)
941289180Speter    SVN_ERR(string_in_array(config_option->option, svn__valid_config_options,
942289180Speter                            ARRAYLEN(svn__valid_config_options),
943289180Speter                            scratch_pool));
944289180Speter
945289180Speter#undef ARRAYLEN
946289180Speter
947289180Speter  return SVN_NO_ERROR;
948289180Speter}
949289180Speter
950251881Spetersvn_error_t *
951251881Spetersvn_cmdline__parse_config_option(apr_array_header_t *config_options,
952251881Speter                                 const char *opt_arg,
953289180Speter                                 const char *prefix,
954251881Speter                                 apr_pool_t *pool)
955251881Speter{
956251881Speter  svn_cmdline__config_argument_t *config_option;
957251881Speter  const char *first_colon, *second_colon, *equals_sign;
958251881Speter  apr_size_t len = strlen(opt_arg);
959251881Speter  if ((first_colon = strchr(opt_arg, ':')) && (first_colon != opt_arg))
960251881Speter    {
961251881Speter      if ((second_colon = strchr(first_colon + 1, ':')) &&
962251881Speter          (second_colon != first_colon + 1))
963251881Speter        {
964251881Speter          if ((equals_sign = strchr(second_colon + 1, '=')) &&
965251881Speter              (equals_sign != second_colon + 1))
966251881Speter            {
967289180Speter              svn_error_t *warning;
968289180Speter
969251881Speter              config_option = apr_pcalloc(pool, sizeof(*config_option));
970251881Speter              config_option->file = apr_pstrndup(pool, opt_arg,
971251881Speter                                                 first_colon - opt_arg);
972251881Speter              config_option->section = apr_pstrndup(pool, first_colon + 1,
973251881Speter                                                    second_colon - first_colon - 1);
974251881Speter              config_option->option = apr_pstrndup(pool, second_colon + 1,
975251881Speter                                                   equals_sign - second_colon - 1);
976251881Speter
977289180Speter              warning = validate_config_option(config_option, pool);
978289180Speter              if (warning)
979289180Speter                {
980289180Speter                  svn_handle_warning2(stderr, warning, prefix);
981289180Speter                  svn_error_clear(warning);
982289180Speter                }
983289180Speter
984251881Speter              if (! (strchr(config_option->option, ':')))
985251881Speter                {
986251881Speter                  config_option->value = apr_pstrndup(pool, equals_sign + 1,
987251881Speter                                                      opt_arg + len - equals_sign - 1);
988251881Speter                  APR_ARRAY_PUSH(config_options, svn_cmdline__config_argument_t *)
989251881Speter                                       = config_option;
990251881Speter                  return SVN_NO_ERROR;
991251881Speter                }
992251881Speter            }
993251881Speter        }
994251881Speter    }
995251881Speter  return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
996251881Speter                          _("Invalid syntax of argument of --config-option"));
997251881Speter}
998251881Speter
999251881Spetersvn_error_t *
1000251881Spetersvn_cmdline__apply_config_options(apr_hash_t *config,
1001251881Speter                                  const apr_array_header_t *config_options,
1002251881Speter                                  const char *prefix,
1003251881Speter                                  const char *argument_name)
1004251881Speter{
1005251881Speter  int i;
1006251881Speter
1007251881Speter  for (i = 0; i < config_options->nelts; i++)
1008251881Speter   {
1009251881Speter     svn_config_t *cfg;
1010251881Speter     svn_cmdline__config_argument_t *arg =
1011251881Speter                          APR_ARRAY_IDX(config_options, i,
1012251881Speter                                        svn_cmdline__config_argument_t *);
1013251881Speter
1014251881Speter     cfg = svn_hash_gets(config, arg->file);
1015251881Speter
1016251881Speter     if (cfg)
1017251881Speter       {
1018251881Speter         svn_config_set(cfg, arg->section, arg->option, arg->value);
1019251881Speter       }
1020251881Speter     else
1021251881Speter       {
1022251881Speter         svn_error_t *err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1023251881Speter             _("Unrecognized file in argument of %s"), argument_name);
1024251881Speter
1025251881Speter         svn_handle_warning2(stderr, err, prefix);
1026251881Speter         svn_error_clear(err);
1027251881Speter       }
1028251881Speter    }
1029251881Speter
1030251881Speter  return SVN_NO_ERROR;
1031251881Speter}
1032251881Speter
1033251881Speter/* Return a copy, allocated in POOL, of the next line of text from *STR
1034251881Speter * up to and including a CR and/or an LF. Change *STR to point to the
1035251881Speter * remainder of the string after the returned part. If there are no
1036251881Speter * characters to be returned, return NULL; never return an empty string.
1037251881Speter */
1038251881Speterstatic const char *
1039251881Speternext_line(const char **str, apr_pool_t *pool)
1040251881Speter{
1041251881Speter  const char *start = *str;
1042251881Speter  const char *p = *str;
1043251881Speter
1044251881Speter  /* n.b. Throughout this fn, we never read any character after a '\0'. */
1045251881Speter  /* Skip over all non-EOL characters, if any. */
1046251881Speter  while (*p != '\r' && *p != '\n' && *p != '\0')
1047251881Speter    p++;
1048251881Speter  /* Skip over \r\n or \n\r or \r or \n, if any. */
1049251881Speter  if (*p == '\r' || *p == '\n')
1050251881Speter    {
1051251881Speter      char c = *p++;
1052251881Speter
1053251881Speter      if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r'))
1054251881Speter        p++;
1055251881Speter    }
1056251881Speter
1057251881Speter  /* Now p points after at most one '\n' and/or '\r'. */
1058251881Speter  *str = p;
1059251881Speter
1060251881Speter  if (p == start)
1061251881Speter    return NULL;
1062251881Speter
1063251881Speter  return svn_string_ncreate(start, p - start, pool)->data;
1064251881Speter}
1065251881Speter
1066251881Speterconst char *
1067251881Spetersvn_cmdline__indent_string(const char *str,
1068251881Speter                           const char *indent,
1069251881Speter                           apr_pool_t *pool)
1070251881Speter{
1071251881Speter  svn_stringbuf_t *out = svn_stringbuf_create_empty(pool);
1072251881Speter  const char *line;
1073251881Speter
1074251881Speter  while ((line = next_line(&str, pool)))
1075251881Speter    {
1076251881Speter      svn_stringbuf_appendcstr(out, indent);
1077251881Speter      svn_stringbuf_appendcstr(out, line);
1078251881Speter    }
1079251881Speter  return out->data;
1080251881Speter}
1081251881Speter
1082251881Spetersvn_error_t *
1083251881Spetersvn_cmdline__print_prop_hash(svn_stream_t *out,
1084251881Speter                             apr_hash_t *prop_hash,
1085251881Speter                             svn_boolean_t names_only,
1086251881Speter                             apr_pool_t *pool)
1087251881Speter{
1088251881Speter  apr_array_header_t *sorted_props;
1089251881Speter  int i;
1090251881Speter
1091251881Speter  sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
1092251881Speter                                pool);
1093251881Speter  for (i = 0; i < sorted_props->nelts; i++)
1094251881Speter    {
1095251881Speter      svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
1096251881Speter      const char *pname = item.key;
1097251881Speter      svn_string_t *propval = item.value;
1098251881Speter      const char *pname_stdout;
1099251881Speter
1100251881Speter      if (svn_prop_needs_translation(pname))
1101251881Speter        SVN_ERR(svn_subst_detranslate_string(&propval, propval,
1102251881Speter                                             TRUE, pool));
1103251881Speter
1104251881Speter      SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool));
1105251881Speter
1106251881Speter      if (out)
1107251881Speter        {
1108251881Speter          pname_stdout = apr_psprintf(pool, "  %s\n", pname_stdout);
1109251881Speter          SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout,
1110251881Speter                                              APR_EOL_STR,  /* 'native' eol */
1111251881Speter                                              FALSE, /* no repair */
1112251881Speter                                              NULL,  /* no keywords */
1113251881Speter                                              FALSE, /* no expansion */
1114251881Speter                                              pool));
1115251881Speter
1116251881Speter          SVN_ERR(svn_stream_puts(out, pname_stdout));
1117251881Speter        }
1118251881Speter      else
1119251881Speter        {
1120251881Speter          /* ### We leave these printfs for now, since if propval wasn't
1121251881Speter             translated above, we don't know anything about its encoding.
1122251881Speter             In fact, it might be binary data... */
1123251881Speter          printf("  %s\n", pname_stdout);
1124251881Speter        }
1125251881Speter
1126251881Speter      if (!names_only)
1127251881Speter        {
1128251881Speter          /* Add an extra newline to the value before indenting, so that
1129251881Speter           * every line of output has the indentation whether the value
1130251881Speter           * already ended in a newline or not. */
1131251881Speter          const char *newval = apr_psprintf(pool, "%s\n", propval->data);
1132251881Speter          const char *indented_newval = svn_cmdline__indent_string(newval,
1133251881Speter                                                                   "    ",
1134251881Speter                                                                   pool);
1135251881Speter          if (out)
1136251881Speter            {
1137251881Speter              SVN_ERR(svn_stream_puts(out, indented_newval));
1138251881Speter            }
1139251881Speter          else
1140251881Speter            {
1141251881Speter              printf("%s", indented_newval);
1142251881Speter            }
1143251881Speter        }
1144251881Speter    }
1145251881Speter
1146251881Speter  return SVN_NO_ERROR;
1147251881Speter}
1148251881Speter
1149251881Spetersvn_error_t *
1150251881Spetersvn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr,
1151251881Speter                                 apr_hash_t *prop_hash,
1152251881Speter                                 svn_boolean_t names_only,
1153251881Speter                                 svn_boolean_t inherited_props,
1154251881Speter                                 apr_pool_t *pool)
1155251881Speter{
1156251881Speter  apr_array_header_t *sorted_props;
1157251881Speter  int i;
1158251881Speter
1159251881Speter  if (*outstr == NULL)
1160251881Speter    *outstr = svn_stringbuf_create_empty(pool);
1161251881Speter
1162251881Speter  sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
1163251881Speter                                pool);
1164251881Speter  for (i = 0; i < sorted_props->nelts; i++)
1165251881Speter    {
1166251881Speter      svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
1167251881Speter      const char *pname = item.key;
1168251881Speter      svn_string_t *propval = item.value;
1169251881Speter
1170251881Speter      if (names_only)
1171251881Speter        {
1172251881Speter          svn_xml_make_open_tag(
1173251881Speter            outstr, pool, svn_xml_self_closing,
1174251881Speter            inherited_props ? "inherited_property" : "property",
1175289180Speter            "name", pname, SVN_VA_NULL);
1176251881Speter        }
1177251881Speter      else
1178251881Speter        {
1179251881Speter          const char *pname_out;
1180251881Speter
1181251881Speter          if (svn_prop_needs_translation(pname))
1182251881Speter            SVN_ERR(svn_subst_detranslate_string(&propval, propval,
1183251881Speter                                                 TRUE, pool));
1184251881Speter
1185251881Speter          SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool));
1186251881Speter
1187251881Speter          svn_cmdline__print_xml_prop(outstr, pname_out, propval,
1188251881Speter                                      inherited_props, pool);
1189251881Speter        }
1190251881Speter    }
1191251881Speter
1192251881Speter    return SVN_NO_ERROR;
1193251881Speter}
1194251881Speter
1195251881Spetersvn_boolean_t
1196362181Sdimsvn_cmdline__stdin_is_a_terminal(void)
1197362181Sdim{
1198362181Sdim#ifdef WIN32
1199362181Sdim  return (_isatty(STDIN_FILENO) != 0);
1200362181Sdim#else
1201362181Sdim  return (isatty(STDIN_FILENO) != 0);
1202362181Sdim#endif
1203362181Sdim}
1204362181Sdim
1205362181Sdimsvn_boolean_t
1206362181Sdimsvn_cmdline__stdout_is_a_terminal(void)
1207362181Sdim{
1208362181Sdim#ifdef WIN32
1209362181Sdim  return (_isatty(STDOUT_FILENO) != 0);
1210362181Sdim#else
1211362181Sdim  return (isatty(STDOUT_FILENO) != 0);
1212362181Sdim#endif
1213362181Sdim}
1214362181Sdim
1215362181Sdimsvn_boolean_t
1216362181Sdimsvn_cmdline__stderr_is_a_terminal(void)
1217362181Sdim{
1218362181Sdim#ifdef WIN32
1219362181Sdim  return (_isatty(STDERR_FILENO) != 0);
1220362181Sdim#else
1221362181Sdim  return (isatty(STDERR_FILENO) != 0);
1222362181Sdim#endif
1223362181Sdim}
1224362181Sdim
1225362181Sdimsvn_boolean_t
1226251881Spetersvn_cmdline__be_interactive(svn_boolean_t non_interactive,
1227251881Speter                            svn_boolean_t force_interactive)
1228251881Speter{
1229251881Speter  /* If neither --non-interactive nor --force-interactive was passed,
1230251881Speter   * be interactive if stdin is a terminal.
1231251881Speter   * If --force-interactive was passed, always be interactive. */
1232251881Speter  if (!force_interactive && !non_interactive)
1233251881Speter    {
1234362181Sdim      return svn_cmdline__stdin_is_a_terminal();
1235251881Speter    }
1236251881Speter  else if (force_interactive)
1237251881Speter    return TRUE;
1238251881Speter
1239251881Speter  return !non_interactive;
1240251881Speter}
1241251881Speter
1242251881Speter
1243362181Sdim/* Helper for the edit_externally functions.  Set *EDITOR to some path to an
1244369302Sdim   editor binary, in native C string on Unix/Linux platforms and in UTF-8
1245369302Sdim   string on Windows platform.  Sources to search include: the EDITOR_CMD
1246369302Sdim   argument (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
1247251881Speter   is not NULL), $VISUAL, $EDITOR.  Return
1248251881Speter   SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */
1249251881Speterstatic svn_error_t *
1250251881Speterfind_editor_binary(const char **editor,
1251251881Speter                   const char *editor_cmd,
1252369302Sdim                   apr_hash_t *config,
1253369302Sdim                   apr_pool_t *pool)
1254251881Speter{
1255251881Speter  const char *e;
1256369302Sdim  const char *e_cfg;
1257251881Speter  struct svn_config_t *cfg;
1258369302Sdim  apr_status_t status;
1259251881Speter
1260251881Speter  /* Use the editor specified on the command line via --editor-cmd, if any. */
1261369302Sdim#ifdef WIN32
1262369302Sdim  /* On Windows, editor_cmd is transcoded to the system active code page
1263369302Sdim     because we use main() as a entry point without APR's (or our own) wrapper
1264369302Sdim     in command line tools. */
1265369302Sdim  if (editor_cmd)
1266369302Sdim    {
1267369302Sdim      SVN_ERR(svn_utf_cstring_to_utf8(&e, editor_cmd, pool));
1268369302Sdim    }
1269369302Sdim  else
1270369302Sdim    {
1271369302Sdim      e = NULL;
1272369302Sdim    }
1273369302Sdim#else
1274251881Speter  e = editor_cmd;
1275369302Sdim#endif
1276251881Speter
1277251881Speter  /* Otherwise look for the Subversion-specific environment variable. */
1278251881Speter  if (! e)
1279369302Sdim    {
1280369302Sdim      status = apr_env_get((char **)&e, "SVN_EDITOR", pool);
1281369302Sdim      if (status || ! *e)
1282369302Sdim        {
1283369302Sdim           e = NULL;
1284369302Sdim        }
1285369302Sdim    }
1286251881Speter
1287251881Speter  /* If not found then fall back on the config file. */
1288251881Speter  if (! e)
1289251881Speter    {
1290251881Speter      cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
1291369302Sdim      svn_config_get(cfg, &e_cfg, SVN_CONFIG_SECTION_HELPERS,
1292251881Speter                     SVN_CONFIG_OPTION_EDITOR_CMD, NULL);
1293369302Sdim#ifdef WIN32
1294369302Sdim      if (e_cfg)
1295369302Sdim        {
1296369302Sdim          /* On Windows, we assume that config values are set in system active
1297369302Sdim             code page, so we need transcode it here. */
1298369302Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&e, e_cfg, pool));
1299369302Sdim        }
1300369302Sdim#else
1301369302Sdim      e = e_cfg;
1302369302Sdim#endif
1303251881Speter    }
1304251881Speter
1305251881Speter  /* If not found yet then try general purpose environment variables. */
1306251881Speter  if (! e)
1307369302Sdim    {
1308369302Sdim      status = apr_env_get((char**)&e, "VISUAL", pool);
1309369302Sdim      if (status || ! *e)
1310369302Sdim        {
1311369302Sdim           e = NULL;
1312369302Sdim        }
1313369302Sdim    }
1314251881Speter  if (! e)
1315369302Sdim    {
1316369302Sdim      status = apr_env_get((char**)&e, "EDITOR", pool);
1317369302Sdim      if (status || ! *e)
1318369302Sdim        {
1319369302Sdim           e = NULL;
1320369302Sdim        }
1321369302Sdim    }
1322251881Speter
1323251881Speter#ifdef SVN_CLIENT_EDITOR
1324251881Speter  /* If still not found then fall back on the hard-coded default. */
1325251881Speter  if (! e)
1326251881Speter    e = SVN_CLIENT_EDITOR;
1327251881Speter#endif
1328251881Speter
1329251881Speter  /* Error if there is no editor specified */
1330251881Speter  if (e)
1331251881Speter    {
1332251881Speter      const char *c;
1333251881Speter
1334251881Speter      for (c = e; *c; c++)
1335251881Speter        if (!svn_ctype_isspace(*c))
1336251881Speter          break;
1337251881Speter
1338251881Speter      if (! *c)
1339251881Speter        return svn_error_create
1340251881Speter          (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
1341251881Speter           _("The EDITOR, SVN_EDITOR or VISUAL environment variable or "
1342251881Speter             "'editor-cmd' run-time configuration option is empty or "
1343251881Speter             "consists solely of whitespace. Expected a shell command."));
1344251881Speter    }
1345251881Speter  else
1346251881Speter    return svn_error_create
1347251881Speter      (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
1348251881Speter       _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are "
1349251881Speter         "set, and no 'editor-cmd' run-time configuration option was found"));
1350251881Speter
1351251881Speter  *editor = e;
1352251881Speter  return SVN_NO_ERROR;
1353251881Speter}
1354251881Speter
1355362181Sdim/* Wrapper around apr_pescape_shell() which also escapes whitespace. */
1356362181Sdimstatic const char *
1357362181Sdimescape_path(apr_pool_t *pool, const char *orig_path)
1358362181Sdim{
1359362181Sdim  apr_size_t len, esc_len;
1360362181Sdim  apr_status_t status;
1361251881Speter
1362362181Sdim  len = strlen(orig_path);
1363362181Sdim  esc_len = 0;
1364362181Sdim
1365362181Sdim  status = apr_escape_shell(NULL, orig_path, len, &esc_len);
1366362181Sdim
1367362181Sdim  if (status == APR_NOTFOUND)
1368362181Sdim    {
1369362181Sdim      /* No special characters found by APR, so just surround it in double
1370362181Sdim         quotes in case there is whitespace, which APR (as of 1.6.5) doesn't
1371362181Sdim         consider special. */
1372362181Sdim      return apr_psprintf(pool, "\"%s\"", orig_path);
1373362181Sdim    }
1374362181Sdim  else
1375362181Sdim    {
1376362181Sdim#ifdef WIN32
1377362181Sdim      const char *p;
1378362181Sdim      /* Following the advice from
1379362181Sdim         https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
1380362181Sdim         1. Surround argument with double-quotes
1381362181Sdim         2. Escape backslashes, if they're followed by a double-quote, and double-quotes
1382362181Sdim         3. Escape any metacharacter, including double-quotes, with ^ */
1383362181Sdim
1384362181Sdim      /* Use APR's buffer size as an approximation for how large the escaped
1385362181Sdim         string should be, plus 4 bytes for the leading/trailing ^" */
1386362181Sdim      svn_stringbuf_t *buf = svn_stringbuf_create_ensure(esc_len + 4, pool);
1387362181Sdim      svn_stringbuf_appendcstr(buf, "^\"");
1388362181Sdim      for (p = orig_path; *p; p++)
1389362181Sdim        {
1390362181Sdim          int nr_backslash = 0;
1391362181Sdim          while (*p && *p == '\\')
1392362181Sdim            {
1393362181Sdim              nr_backslash++;
1394362181Sdim              p++;
1395362181Sdim            }
1396362181Sdim
1397362181Sdim          if (!*p)
1398362181Sdim            /* We've reached the end of the argument, so we need 2n backslash
1399362181Sdim               characters.  That will be interpreted as n backslashes and the
1400362181Sdim               final double-quote character will be interpreted as the final
1401362181Sdim               string delimiter. */
1402362181Sdim            svn_stringbuf_appendfill(buf, '\\', nr_backslash * 2);
1403362181Sdim          else if (*p == '"')
1404362181Sdim            {
1405362181Sdim              /* Double-quote as part of the argument means we need to double
1406362181Sdim                 any preceeding backslashes and then add one to escape the
1407362181Sdim                 double-quote. */
1408362181Sdim              svn_stringbuf_appendfill(buf, '\\', nr_backslash * 2 + 1);
1409362181Sdim              svn_stringbuf_appendbyte(buf, '^');
1410362181Sdim              svn_stringbuf_appendbyte(buf, *p);
1411362181Sdim            }
1412362181Sdim          else
1413362181Sdim            {
1414362181Sdim              /* Since there's no double-quote, we just insert any backslashes
1415362181Sdim                 literally.  No escaping needed. */
1416362181Sdim              svn_stringbuf_appendfill(buf, '\\', nr_backslash);
1417362181Sdim              if (strchr("()%!^<>&|", *p))
1418362181Sdim                svn_stringbuf_appendbyte(buf, '^');
1419362181Sdim              svn_stringbuf_appendbyte(buf, *p);
1420362181Sdim            }
1421362181Sdim        }
1422362181Sdim      svn_stringbuf_appendcstr(buf, "^\"");
1423362181Sdim      return buf->data;
1424362181Sdim#else
1425362181Sdim      char *path, *p, *esc_path;
1426362181Sdim
1427362181Sdim      /* Account for whitespace, since APR doesn't */
1428362181Sdim      for (p = (char *)orig_path; *p; p++)
1429362181Sdim        if (strchr(" \t\n\r", *p))
1430362181Sdim          esc_len++;
1431362181Sdim
1432362181Sdim      path = apr_pcalloc(pool, esc_len);
1433362181Sdim      apr_escape_shell(path, orig_path, len, NULL);
1434362181Sdim
1435362181Sdim      p = esc_path = apr_pcalloc(pool, len + esc_len + 1);
1436362181Sdim      while (*path)
1437362181Sdim        {
1438362181Sdim          if (strchr(" \t\n\r", *path))
1439362181Sdim            *p++ = '\\';
1440362181Sdim          *p++ = *path++;
1441362181Sdim        }
1442362181Sdim
1443362181Sdim      return esc_path;
1444362181Sdim#endif
1445362181Sdim    }
1446362181Sdim}
1447362181Sdim
1448251881Spetersvn_error_t *
1449251881Spetersvn_cmdline__edit_file_externally(const char *path,
1450251881Speter                                  const char *editor_cmd,
1451251881Speter                                  apr_hash_t *config,
1452251881Speter                                  apr_pool_t *pool)
1453251881Speter{
1454251881Speter  const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr;
1455369302Sdim  const char *file_name_local;
1456369302Sdim#ifdef WIN32
1457369302Sdim  const WCHAR *wcmd;
1458369302Sdim#endif
1459251881Speter  char *old_cwd;
1460251881Speter  int sys_err;
1461251881Speter  apr_status_t apr_err;
1462251881Speter
1463251881Speter  svn_dirent_split(&base_dir, &file_name, path, pool);
1464251881Speter
1465369302Sdim  SVN_ERR(find_editor_binary(&editor, editor_cmd, config, pool));
1466251881Speter
1467251881Speter  apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
1468251881Speter  if (apr_err)
1469251881Speter    return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
1470251881Speter
1471251881Speter  /* APR doesn't like "" directories */
1472251881Speter  if (base_dir[0] == '\0')
1473251881Speter    base_dir_apr = ".";
1474251881Speter  else
1475251881Speter    SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
1476251881Speter
1477251881Speter  apr_err = apr_filepath_set(base_dir_apr, pool);
1478251881Speter  if (apr_err)
1479251881Speter    return svn_error_wrap_apr
1480251881Speter      (apr_err, _("Can't change working directory to '%s'"), base_dir);
1481251881Speter
1482369302Sdim  SVN_ERR(svn_path_cstring_from_utf8(&file_name_local,
1483369302Sdim                                     escape_path(pool, file_name), pool));
1484362181Sdim  /* editor is explicitly documented as being interpreted by the user's shell,
1485362181Sdim     and as such should already be quoted/escaped as needed. */
1486369302Sdim#ifndef WIN32
1487369302Sdim  cmd = apr_psprintf(pool, "%s %s", editor, file_name_local);
1488251881Speter  sys_err = system(cmd);
1489369302Sdim#else
1490369302Sdim  cmd = apr_psprintf(pool, "\"%s %s\"", editor, file_name_local);
1491369302Sdim  SVN_ERR(svn_utf__win32_utf8_to_utf16(&wcmd, cmd, NULL, pool));
1492369302Sdim  sys_err = _wsystem(wcmd);
1493369302Sdim#endif
1494251881Speter
1495251881Speter  apr_err = apr_filepath_set(old_cwd, pool);
1496251881Speter  if (apr_err)
1497251881Speter    svn_handle_error2(svn_error_wrap_apr
1498251881Speter                      (apr_err, _("Can't restore working directory")),
1499251881Speter                      stderr, TRUE /* fatal */, "svn: ");
1500251881Speter
1501251881Speter  if (sys_err)
1502251881Speter    /* Extracting any meaning from sys_err is platform specific, so just
1503251881Speter       use the raw value. */
1504251881Speter    return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
1505251881Speter                             _("system('%s') returned %d"), cmd, sys_err);
1506251881Speter
1507251881Speter  return SVN_NO_ERROR;
1508251881Speter}
1509251881Speter
1510251881Speter
1511251881Spetersvn_error_t *
1512251881Spetersvn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */,
1513251881Speter                                    const char **tmpfile_left /* UTF-8! */,
1514251881Speter                                    const char *editor_cmd,
1515251881Speter                                    const char *base_dir /* UTF-8! */,
1516251881Speter                                    const svn_string_t *contents /* UTF-8! */,
1517251881Speter                                    const char *filename,
1518251881Speter                                    apr_hash_t *config,
1519251881Speter                                    svn_boolean_t as_text,
1520251881Speter                                    const char *encoding,
1521251881Speter                                    apr_pool_t *pool)
1522251881Speter{
1523251881Speter  const char *editor;
1524251881Speter  const char *cmd;
1525369302Sdim#ifdef WIN32
1526369302Sdim  const WCHAR *wcmd;
1527369302Sdim#endif
1528251881Speter  apr_file_t *tmp_file;
1529251881Speter  const char *tmpfile_name;
1530251881Speter  const char *tmpfile_native;
1531362181Sdim  const char *base_dir_apr;
1532251881Speter  svn_string_t *translated_contents;
1533362181Sdim  apr_status_t apr_err;
1534251881Speter  apr_size_t written;
1535251881Speter  apr_finfo_t finfo_before, finfo_after;
1536362181Sdim  svn_error_t *err = SVN_NO_ERROR;
1537251881Speter  char *old_cwd;
1538251881Speter  int sys_err;
1539251881Speter  svn_boolean_t remove_file = TRUE;
1540251881Speter
1541369302Sdim  SVN_ERR(find_editor_binary(&editor, editor_cmd, config, pool));
1542251881Speter
1543251881Speter  /* Convert file contents from UTF-8/LF if desired. */
1544251881Speter  if (as_text)
1545251881Speter    {
1546251881Speter      const char *translated;
1547251881Speter      SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated,
1548251881Speter                                           APR_EOL_STR, FALSE,
1549251881Speter                                           NULL, FALSE, pool));
1550251881Speter      translated_contents = svn_string_create_empty(pool);
1551251881Speter      if (encoding)
1552251881Speter        SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data,
1553251881Speter                                              translated, encoding, pool));
1554251881Speter      else
1555251881Speter        SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data,
1556251881Speter                                          translated, pool));
1557251881Speter      translated_contents->len = strlen(translated_contents->data);
1558251881Speter    }
1559251881Speter  else
1560251881Speter    translated_contents = svn_string_dup(contents, pool);
1561251881Speter
1562251881Speter  /* Move to BASE_DIR to avoid getting characters that need quoting
1563251881Speter     into tmpfile_name */
1564251881Speter  apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
1565251881Speter  if (apr_err)
1566251881Speter    return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
1567251881Speter
1568251881Speter  /* APR doesn't like "" directories */
1569251881Speter  if (base_dir[0] == '\0')
1570251881Speter    base_dir_apr = ".";
1571251881Speter  else
1572251881Speter    SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
1573251881Speter  apr_err = apr_filepath_set(base_dir_apr, pool);
1574251881Speter  if (apr_err)
1575251881Speter    {
1576251881Speter      return svn_error_wrap_apr
1577251881Speter        (apr_err, _("Can't change working directory to '%s'"), base_dir);
1578251881Speter    }
1579251881Speter
1580251881Speter  /*** From here on, any problems that occur require us to cd back!! ***/
1581251881Speter
1582251881Speter  /* Ask the working copy for a temporary file named FILENAME-something. */
1583251881Speter  err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
1584251881Speter                                   "" /* dirpath */,
1585251881Speter                                   filename,
1586251881Speter                                   ".tmp",
1587251881Speter                                   svn_io_file_del_none, pool, pool);
1588251881Speter
1589251881Speter  if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS))
1590251881Speter    {
1591251881Speter      const char *temp_dir_apr;
1592251881Speter
1593251881Speter      svn_error_clear(err);
1594251881Speter
1595251881Speter      SVN_ERR(svn_io_temp_dir(&base_dir, pool));
1596251881Speter
1597251881Speter      SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool));
1598251881Speter      apr_err = apr_filepath_set(temp_dir_apr, pool);
1599251881Speter      if (apr_err)
1600251881Speter        {
1601251881Speter          return svn_error_wrap_apr
1602251881Speter            (apr_err, _("Can't change working directory to '%s'"), base_dir);
1603251881Speter        }
1604251881Speter
1605251881Speter      err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
1606251881Speter                                       "" /* dirpath */,
1607251881Speter                                       filename,
1608251881Speter                                       ".tmp",
1609251881Speter                                       svn_io_file_del_none, pool, pool);
1610251881Speter    }
1611251881Speter
1612251881Speter  if (err)
1613251881Speter    goto cleanup2;
1614251881Speter
1615251881Speter  /*** From here on, any problems that occur require us to cleanup
1616251881Speter       the file we just created!! ***/
1617251881Speter
1618251881Speter  /* Dump initial CONTENTS to TMP_FILE. */
1619362181Sdim  err = svn_io_file_write_full(tmp_file, translated_contents->data,
1620362181Sdim                               translated_contents->len, &written,
1621362181Sdim                               pool);
1622251881Speter
1623362181Sdim  err = svn_error_compose_create(err, svn_io_file_close(tmp_file, pool));
1624251881Speter
1625251881Speter  /* Make sure the whole CONTENTS were written, else return an error. */
1626251881Speter  if (err)
1627251881Speter    goto cleanup;
1628251881Speter
1629251881Speter  /* Get information about the temporary file before the user has
1630251881Speter     been allowed to edit its contents. */
1631362181Sdim  err = svn_io_stat(&finfo_before, tmpfile_name, APR_FINFO_MTIME, pool);
1632362181Sdim  if (err)
1633362181Sdim    goto cleanup;
1634251881Speter
1635251881Speter  /* Backdate the file a little bit in case the editor is very fast
1636251881Speter     and doesn't change the size.  (Use two seconds, since some
1637251881Speter     filesystems have coarse granularity.)  It's OK if this call
1638251881Speter     fails, so we don't check its return value.*/
1639362181Sdim  err = svn_io_set_file_affected_time(finfo_before.mtime
1640362181Sdim                                              - apr_time_from_sec(2),
1641362181Sdim                                      tmpfile_name, pool);
1642362181Sdim  svn_error_clear(err);
1643251881Speter
1644251881Speter  /* Stat it again to get the mtime we actually set. */
1645362181Sdim  err = svn_io_stat(&finfo_before, tmpfile_name,
1646362181Sdim                    APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
1647362181Sdim  if (err)
1648362181Sdim    goto cleanup;
1649251881Speter
1650251881Speter  /* Prepare the editor command line.  */
1651369302Sdim  err = svn_utf_cstring_from_utf8(&tmpfile_native,
1652369302Sdim                                  escape_path(pool, tmpfile_name), pool);
1653251881Speter  if (err)
1654251881Speter    goto cleanup;
1655251881Speter
1656362181Sdim  /* editor is explicitly documented as being interpreted by the user's shell,
1657362181Sdim     and as such should already be quoted/escaped as needed. */
1658369302Sdim#ifndef WIN32
1659369302Sdim  cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native);
1660369302Sdim#else
1661369302Sdim  cmd = apr_psprintf(pool, "\"%s %s\"", editor, tmpfile_native);
1662369302Sdim#endif
1663362181Sdim
1664251881Speter  /* If the caller wants us to leave the file around, return the path
1665251881Speter     of the file we'll use, and make a note not to destroy it.  */
1666251881Speter  if (tmpfile_left)
1667251881Speter    {
1668251881Speter      *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool);
1669251881Speter      remove_file = FALSE;
1670251881Speter    }
1671251881Speter
1672251881Speter  /* Now, run the editor command line.  */
1673369302Sdim#ifndef WIN32
1674251881Speter  sys_err = system(cmd);
1675369302Sdim#else
1676369302Sdim  SVN_ERR(svn_utf__win32_utf8_to_utf16(&wcmd, cmd, NULL, pool));
1677369302Sdim  sys_err = _wsystem(wcmd);
1678369302Sdim#endif
1679251881Speter  if (sys_err != 0)
1680251881Speter    {
1681251881Speter      /* Extracting any meaning from sys_err is platform specific, so just
1682251881Speter         use the raw value. */
1683251881Speter      err =  svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
1684251881Speter                               _("system('%s') returned %d"), cmd, sys_err);
1685251881Speter      goto cleanup;
1686251881Speter    }
1687251881Speter
1688251881Speter  /* Get information about the temporary file after the assumed editing. */
1689362181Sdim  err = svn_io_stat(&finfo_after, tmpfile_name,
1690362181Sdim                    APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
1691362181Sdim  if (err)
1692362181Sdim    goto cleanup;
1693251881Speter
1694251881Speter  /* If the file looks changed... */
1695251881Speter  if ((finfo_before.mtime != finfo_after.mtime) ||
1696251881Speter      (finfo_before.size != finfo_after.size))
1697251881Speter    {
1698251881Speter      svn_stringbuf_t *edited_contents_s;
1699251881Speter      err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool);
1700251881Speter      if (err)
1701251881Speter        goto cleanup;
1702251881Speter
1703251881Speter      *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s);
1704251881Speter
1705251881Speter      /* Translate back to UTF8/LF if desired. */
1706251881Speter      if (as_text)
1707251881Speter        {
1708289180Speter          err = svn_subst_translate_string2(edited_contents, NULL, NULL,
1709251881Speter                                            *edited_contents, encoding, FALSE,
1710251881Speter                                            pool, pool);
1711251881Speter          if (err)
1712251881Speter            {
1713251881Speter              err = svn_error_quick_wrap
1714251881Speter                (err,
1715251881Speter                 _("Error normalizing edited contents to internal format"));
1716251881Speter              goto cleanup;
1717251881Speter            }
1718251881Speter        }
1719251881Speter    }
1720251881Speter  else
1721251881Speter    {
1722251881Speter      /* No edits seem to have been made */
1723251881Speter      *edited_contents = NULL;
1724251881Speter    }
1725251881Speter
1726251881Speter cleanup:
1727251881Speter  if (remove_file)
1728251881Speter    {
1729251881Speter      /* Remove the file from disk.  */
1730362181Sdim      err = svn_error_compose_create(
1731362181Sdim              err,
1732362181Sdim              svn_io_remove_file2(tmpfile_name, FALSE, pool));
1733251881Speter    }
1734251881Speter
1735251881Speter cleanup2:
1736251881Speter  /* If we against all probability can't cd back, all further relative
1737251881Speter     file references would be screwed up, so we have to abort. */
1738251881Speter  apr_err = apr_filepath_set(old_cwd, pool);
1739251881Speter  if (apr_err)
1740251881Speter    {
1741251881Speter      svn_handle_error2(svn_error_wrap_apr
1742251881Speter                        (apr_err, _("Can't restore working directory")),
1743251881Speter                        stderr, TRUE /* fatal */, "svn: ");
1744251881Speter    }
1745251881Speter
1746251881Speter  return svn_error_trace(err);
1747251881Speter}
1748289180Speter
1749289180Spetersvn_error_t *
1750289180Spetersvn_cmdline__parse_trust_options(
1751289180Speter                        svn_boolean_t *trust_server_cert_unknown_ca,
1752289180Speter                        svn_boolean_t *trust_server_cert_cn_mismatch,
1753289180Speter                        svn_boolean_t *trust_server_cert_expired,
1754289180Speter                        svn_boolean_t *trust_server_cert_not_yet_valid,
1755289180Speter                        svn_boolean_t *trust_server_cert_other_failure,
1756289180Speter                        const char *opt_arg,
1757289180Speter                        apr_pool_t *scratch_pool)
1758289180Speter{
1759289180Speter  apr_array_header_t *failures;
1760289180Speter  int i;
1761289180Speter
1762289180Speter  *trust_server_cert_unknown_ca = FALSE;
1763289180Speter  *trust_server_cert_cn_mismatch = FALSE;
1764289180Speter  *trust_server_cert_expired = FALSE;
1765289180Speter  *trust_server_cert_not_yet_valid = FALSE;
1766289180Speter  *trust_server_cert_other_failure = FALSE;
1767289180Speter
1768289180Speter  failures = svn_cstring_split(opt_arg, ", \n\r\t\v", TRUE, scratch_pool);
1769289180Speter
1770289180Speter  for (i = 0; i < failures->nelts; i++)
1771289180Speter    {
1772289180Speter      const char *value = APR_ARRAY_IDX(failures, i, const char *);
1773289180Speter      if (!strcmp(value, "unknown-ca"))
1774289180Speter        *trust_server_cert_unknown_ca = TRUE;
1775289180Speter      else if (!strcmp(value, "cn-mismatch"))
1776289180Speter        *trust_server_cert_cn_mismatch = TRUE;
1777289180Speter      else if (!strcmp(value, "expired"))
1778289180Speter        *trust_server_cert_expired = TRUE;
1779289180Speter      else if (!strcmp(value, "not-yet-valid"))
1780289180Speter        *trust_server_cert_not_yet_valid = TRUE;
1781289180Speter      else if (!strcmp(value, "other"))
1782289180Speter        *trust_server_cert_other_failure = TRUE;
1783289180Speter      else
1784289180Speter        return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1785289180Speter                                  _("Unknown value '%s' for %s.\n"
1786289180Speter                                    "Supported values: %s"),
1787289180Speter                                  value,
1788289180Speter                                  "--trust-server-cert-failures",
1789289180Speter                                  "unknown-ca, cn-mismatch, expired, "
1790289180Speter                                  "not-yet-valid, other");
1791289180Speter    }
1792289180Speter
1793289180Speter  return SVN_NO_ERROR;
1794289180Speter}
1795362181Sdim
1796362181Sdim/* Flags to see if we've been cancelled by the client or not. */
1797362181Sdimstatic volatile sig_atomic_t cancelled = FALSE;
1798362181Sdimstatic volatile sig_atomic_t signum_cancelled = 0;
1799362181Sdim
1800362181Sdim/* The signals we handle. */
1801362181Sdimstatic int signal_map [] = {
1802362181Sdim  SIGINT
1803362181Sdim#ifdef SIGBREAK
1804362181Sdim  /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
1805362181Sdim  , SIGBREAK
1806362181Sdim#endif
1807362181Sdim#ifdef SIGHUP
1808362181Sdim  , SIGHUP
1809362181Sdim#endif
1810362181Sdim#ifdef SIGTERM
1811362181Sdim  , SIGTERM
1812362181Sdim#endif
1813362181Sdim};
1814362181Sdim
1815362181Sdim/* A signal handler to support cancellation. */
1816362181Sdimstatic void
1817362181Sdimsignal_handler(int signum)
1818362181Sdim{
1819362181Sdim  int i;
1820362181Sdim
1821362181Sdim  apr_signal(signum, SIG_IGN);
1822362181Sdim  cancelled = TRUE;
1823362181Sdim  for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i)
1824362181Sdim    if (signal_map[i] == signum)
1825362181Sdim      {
1826362181Sdim        signum_cancelled = i + 1;
1827362181Sdim        break;
1828362181Sdim      }
1829362181Sdim}
1830362181Sdim
1831362181Sdim/* An svn_cancel_func_t callback. */
1832362181Sdimstatic svn_error_t *
1833362181Sdimcheck_cancel(void *baton)
1834362181Sdim{
1835362181Sdim  /* Cancel baton should be always NULL in command line client. */
1836362181Sdim  SVN_ERR_ASSERT(baton == NULL);
1837362181Sdim  if (cancelled)
1838362181Sdim    return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
1839362181Sdim  else
1840362181Sdim    return SVN_NO_ERROR;
1841362181Sdim}
1842362181Sdim
1843362181Sdimsvn_cancel_func_t
1844362181Sdimsvn_cmdline__setup_cancellation_handler(void)
1845362181Sdim{
1846362181Sdim  int i;
1847362181Sdim
1848362181Sdim  for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i)
1849362181Sdim    apr_signal(signal_map[i], signal_handler);
1850362181Sdim
1851362181Sdim#ifdef SIGPIPE
1852362181Sdim  /* Disable SIGPIPE generation for the platforms that have it. */
1853362181Sdim  apr_signal(SIGPIPE, SIG_IGN);
1854362181Sdim#endif
1855362181Sdim
1856362181Sdim#ifdef SIGXFSZ
1857362181Sdim  /* Disable SIGXFSZ generation for the platforms that have it, otherwise
1858362181Sdim   * working with large files when compiled against an APR that doesn't have
1859362181Sdim   * large file support will crash the program, which is uncool. */
1860362181Sdim  apr_signal(SIGXFSZ, SIG_IGN);
1861362181Sdim#endif
1862362181Sdim
1863362181Sdim  return check_cancel;
1864362181Sdim}
1865362181Sdim
1866362181Sdimvoid
1867362181Sdimsvn_cmdline__disable_cancellation_handler(void)
1868362181Sdim{
1869362181Sdim  int i;
1870362181Sdim
1871362181Sdim  for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i)
1872362181Sdim    apr_signal(signal_map[i], SIG_DFL);
1873362181Sdim}
1874362181Sdim
1875362181Sdimvoid
1876362181Sdimsvn_cmdline__cancellation_exit(void)
1877362181Sdim{
1878362181Sdim  int signum = 0;
1879362181Sdim
1880362181Sdim  if (cancelled && signum_cancelled)
1881362181Sdim    signum = signal_map[signum_cancelled - 1];
1882362181Sdim  if (signum)
1883362181Sdim    {
1884362181Sdim#ifndef WIN32
1885362181Sdim      apr_signal(signum, SIG_DFL);
1886362181Sdim      /* No APR support for getpid() so cannot use apr_proc_kill(). */
1887362181Sdim      kill(getpid(), signum);
1888362181Sdim#endif
1889362181Sdim    }
1890362181Sdim}
1891