Author: jamessan
Date: Mon Feb 17 02:13:34 2020
New Revision: 1874093

URL: http://svn.apache.org/viewvc?rev=1874093&view=rev
Log:
Followup to r1874057, escape whitespace instead of quoting filename

As danielsh pointed out, only specific characters can be escaped by a backslash
in quoted shell strings.  Rather than surrounding the escaped path with double
quotes, post-process the quoted path and manually escape any whitespace that is
present.

* subversion/libsvn_subr/cmdline.c
  (escape_path): New function, wrapper around apr_pescape_shell(), which
   handles escaping of whitespace.
  (svn_cmdline__edit_file_externally, svn_cmdline__edit_string_externally):
   Call the new function instead of apr_pescape_shell()
* subversion/tests/cmdline/update_tests.py
  (update_accept_conflicts): Include ';' in renamed path ("p; i"), to ensure
   non-whitespace escaping is handled correctly.

Modified:
    subversion/trunk/subversion/libsvn_subr/cmdline.c
    subversion/trunk/subversion/tests/cmdline/update_tests.py

Modified: subversion/trunk/subversion/libsvn_subr/cmdline.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/cmdline.c?rev=1874093&r1=1874092&r2=1874093&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_subr/cmdline.c (original)
+++ subversion/trunk/subversion/libsvn_subr/cmdline.c Mon Feb 17 02:13:34 2020
@@ -1234,7 +1234,7 @@ svn_cmdline__be_interactive(svn_boolean_
 }
 
 
-/* Helper for the next two functions.  Set *EDITOR to some path to an
+/* Helper for the edit_externally functions.  Set *EDITOR to some path to an
    editor binary.  Sources to search include: the EDITOR_CMD argument
    (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
    is not NULL), $VISUAL, $EDITOR.  Return
@@ -1300,6 +1300,42 @@ find_editor_binary(const char **editor,
   return SVN_NO_ERROR;
 }
 
+/* Wrapper around apr_pescape_shell() which also escapes whitespace. */
+static const char *
+escape_path(apr_pool_t *pool, const char *orig_path)
+{
+  apr_size_t len, esc_len;
+  const char *path;
+  char *p, *esc_path;
+
+  path = apr_pescape_shell(pool, orig_path);
+
+  len = esc_len = 0;
+
+  /* Now that apr has done its escaping, we can check whether there's any
+     whitespace that also needs to be escaped.  This must be done after the
+     fact, otherwise apr_pescape_shell() would escape the backslashes we're
+     inserting. */
+  for (p = (char *)path; *p; p++)
+    {
+      len++;
+      if (*p == ' ' || *p == '\t')
+        esc_len++;
+    }
+
+  if (esc_len == 0)
+    return path;
+
+  p = esc_path = apr_pcalloc(pool, len + esc_len + 1);
+  while (*path)
+    {
+      if (*path == ' ' || *path == '\t')
+        *p++ = '\\';
+      *p++ = *path++;
+    }
+
+  return esc_path;
+}
 
 svn_error_t *
 svn_cmdline__edit_file_externally(const char *path,
@@ -1333,8 +1369,7 @@ svn_cmdline__edit_file_externally(const
 
   /* editor is explicitly documented as being interpreted by the user's shell,
      and as such should already be quoted/escaped as needed. */
-  cmd = apr_psprintf(pool, "%s \"%s\"", editor,
-                     apr_pescape_shell(pool, file_name));
+  cmd = apr_psprintf(pool, "%s %s", editor, escape_path(pool, file_name));
   sys_err = system(cmd);
 
   apr_err = apr_filepath_set(old_cwd, pool);
@@ -1496,8 +1531,7 @@ svn_cmdline__edit_string_externally(svn_
 
   /* editor is explicitly documented as being interpreted by the user's shell,
      and as such should already be quoted/escaped as needed. */
-  cmd = apr_psprintf(pool, "%s \"%s\"", editor,
-                     apr_pescape_shell(pool, tmpfile_native));
+  cmd = apr_psprintf(pool, "%s %s", editor, escape_path(pool, tmpfile_native));
 
   /* If the caller wants us to leave the file around, return the path
      of the file we'll use, and make a note not to destroy it.  */

Modified: subversion/trunk/subversion/tests/cmdline/update_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/update_tests.py?rev=1874093&r1=1874092&r2=1874093&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/update_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/update_tests.py Mon Feb 17 
02:13:34 2020
@@ -3649,12 +3649,12 @@ def update_accept_conflicts(sbox):
   alpha_path = sbox.ospath('A/B/E/alpha')
   beta_path = sbox.ospath('A/B/E/beta')
   pi_path = sbox.ospath('A/D/G/pi')
-  p_i_path = sbox.ospath('A/D/G/p i')
+  p_i_path = sbox.ospath('A/D/G/p; i')
   rho_path = sbox.ospath('A/D/G/rho')
 
-  # Rename pi to "p i" so we can exercise SVN_EDITOR's handling of paths with
-  # whitespace
-  sbox.simple_move('A/D/G/pi', 'A/D/G/p i')
+  # Rename pi to "p; i" so we can exercise SVN_EDITOR's handling of paths with
+  # special characters
+  sbox.simple_move('A/D/G/pi', 'A/D/G/p; i')
   sbox.simple_commit()
   sbox.simple_update()
 
@@ -3676,7 +3676,7 @@ def update_accept_conflicts(sbox):
   mu_path_backup = os.path.join(wc_backup, 'A', 'mu')
   alpha_path_backup = os.path.join(wc_backup, 'A', 'B', 'E', 'alpha')
   beta_path_backup = os.path.join(wc_backup, 'A', 'B', 'E', 'beta')
-  p_i_path_backup = os.path.join(wc_backup, 'A', 'D', 'G', 'p i')
+  p_i_path_backup = os.path.join(wc_backup, 'A', 'D', 'G', 'p; i')
   rho_path_backup = os.path.join(wc_backup, 'A', 'D', 'G', 'rho')
   svntest.main.file_append(iota_path_backup,
                            'My appended text for iota\n')
@@ -3700,7 +3700,7 @@ def update_accept_conflicts(sbox):
     'A/mu' : Item(verb='Sending'),
     'A/B/E/alpha': Item(verb='Sending'),
     'A/B/E/beta': Item(verb='Sending'),
-    'A/D/G/p i' : Item(verb='Sending'),
+    'A/D/G/p; i' : Item(verb='Sending'),
     'A/D/G/rho' : Item(verb='Sending'),
     })
 
@@ -3710,8 +3710,8 @@ def update_accept_conflicts(sbox):
   expected_status.tweak('A/mu', wc_rev=3)
   expected_status.tweak('A/B/E/alpha', wc_rev=3)
   expected_status.tweak('A/B/E/beta', wc_rev=3)
-  expected_status.rename({'A/D/G/pi': 'A/D/G/p i'})
-  expected_status.tweak('A/D/G/p i', wc_rev=3)
+  expected_status.rename({'A/D/G/pi': 'A/D/G/p; i'})
+  expected_status.tweak('A/D/G/p; i', wc_rev=3)
   expected_status.tweak('A/D/G/rho', wc_rev=3)
 
   # Commit.
@@ -3806,15 +3806,15 @@ def update_accept_conflicts(sbox):
                                                'My appended text for alpha\n'))
   expected_disk.tweak('A/B/E/beta', contents=("This is the file 'beta'.\n"
                                               'Their appended text for 
beta\n'))
-  expected_disk.rename({'A/D/G/pi': 'A/D/G/p i'})
-  expected_disk.tweak('A/D/G/p i', contents=("This is the file 'pi'.\n"
-                                             '<<<<<<< .mine\n'
-                                             'My appended text for pi\n'
-                                             '||||||| .r2\n'
-                                             '=======\n'
-                                             'Their appended text for pi\n'
-                                             '>>>>>>> .r3\n'
-                                             'foo\n'))
+  expected_disk.rename({'A/D/G/pi': 'A/D/G/p; i'})
+  expected_disk.tweak('A/D/G/p; i', contents=("This is the file 'pi'.\n"
+                                              '<<<<<<< .mine\n'
+                                              'My appended text for pi\n'
+                                              '||||||| .r2\n'
+                                              '=======\n'
+                                              'Their appended text for pi\n'
+                                              '>>>>>>> .r3\n'
+                                              'foo\n'))
   expected_disk.tweak('A/D/G/rho', contents=("This is the file 'rho'.\n"
                                              '<<<<<<< .mine\n'
                                              'My appended text for rho\n'
@@ -3831,16 +3831,16 @@ def update_accept_conflicts(sbox):
 
   # Set the expected status for the test
   expected_status = svntest.actions.get_virginal_state(wc_backup, 3)
-  expected_status.rename({'A/D/G/pi': 'A/D/G/p i'})
+  expected_status.rename({'A/D/G/pi': 'A/D/G/p; i'})
   expected_status.tweak('iota', 'A/B/lambda', 'A/mu',
                         'A/B/E/alpha', 'A/B/E/beta',
-                        'A/D/G/p i', 'A/D/G/rho', wc_rev=3)
+                        'A/D/G/p; i', 'A/D/G/rho', wc_rev=3)
   expected_status.tweak('iota', status='C ')
   expected_status.tweak('A/B/lambda', status='C ')
   expected_status.tweak('A/mu', status='M ')
   expected_status.tweak('A/B/E/alpha', status='M ')
   expected_status.tweak('A/B/E/beta', status='  ')
-  expected_status.tweak('A/D/G/p i', status='M ')
+  expected_status.tweak('A/D/G/p; i', status='M ')
   expected_status.tweak('A/D/G/rho', status='C ')
 
   # Set the expected output for the test


Reply via email to