Skip to content

Instantly share code, notes, and snippets.

@mattn
Last active May 9, 2022 02:44
Show Gist options
  • Save mattn/d47e7d3bfe5ade4be86062b565a4bfca to your computer and use it in GitHub Desktop.
Save mattn/d47e7d3bfe5ade4be86062b565a4bfca to your computer and use it in GitHub Desktop.
diff --git a/src/channel.c b/src/channel.c
index 2d68287..859a7d0 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -4649,6 +4649,83 @@ job_check_ended(void)
}
}
+#ifndef USE_ARGV
+ char_u *
+escape_arg(char_u *arg)
+{
+ int slen, dlen, escaping, i;
+ char_u *s, *d;
+ char_u *escaped_arg;
+ int has_spaces = FALSE;
+
+ /* First count the number of extra bytes required. */
+ slen = STRLEN(arg);
+ dlen = slen;
+ for (s = arg; *s != NUL; mb_ptr_adv(s))
+ {
+ if (*s == '"' || *s == '\\' || *s == '^')
+ ++dlen;
+ if (*s == ' ' || *s == '\t')
+ has_spaces = TRUE;
+ }
+
+ if (dlen == slen)
+ return vim_strsave(arg);
+
+ if (has_spaces)
+ dlen += 2;
+
+ /* Allocate memory for the result and fill it. */
+ escaped_arg = alloc(dlen + 1);
+ if (escaped_arg == NULL)
+ return NULL;
+ memset(escaped_arg, 0, dlen+1);
+
+ d = escaped_arg;
+
+ if (has_spaces)
+ *d++ = '"';
+
+ for (s = arg; *s != NUL;)
+ {
+ switch (*s)
+ {
+ case '"':
+ for (i = 0; i < escaping; i++)
+ *d++ = '\\';
+ escaping = 0;
+ *d++ = '\\';
+ *d++ = *s++;
+ break;
+ case '^':
+ if (has_spaces)
+ *d++ = *s++;
+ *d++ = *s++;
+ break;
+ case '\\':
+ escaping++;
+ *d++ = *s++;
+ break;
+ default:
+ escaping = 0;
+ MB_COPY_CHAR(s, d);
+ break;
+ }
+ }
+
+ /* add terminating quote and finish with a NUL */
+ if (has_spaces)
+ {
+ for (i = 0; i < escaping; i++)
+ *d++ = '\\';
+ *d++ = '"';
+ }
+ *d = NUL;
+
+ return escaped_arg;
+}
+#endif
+
/*
* "job_start()" function
*/
@@ -4776,27 +4853,12 @@ job_start(typval_T *argvars)
#ifdef USE_ARGV
argv[argc++] = (char *)s;
#else
- /* Only escape when needed, double quotes are not always allowed. */
- if (li != l->lv_first && vim_strpbrk(s, (char_u *)" \t\"") != NULL)
- {
-# ifdef WIN32
- int old_ssl = p_ssl;
- /* This is using CreateProcess, not cmd.exe. Always use
- * double quote and backslashes. */
- p_ssl = 0;
-# endif
- s = vim_strsave_shellescape(s, FALSE, TRUE);
-# ifdef WIN32
- p_ssl = old_ssl;
-# endif
- if (s == NULL)
- goto theend;
- ga_concat(&ga, s);
- vim_free(s);
- }
- else
- ga_concat(&ga, s);
+ s = escape_arg(s);
+ if (s == NULL)
+ goto theend;
+ ga_concat(&ga, s);
+ vim_free(s);
if (li->li_next != NULL)
ga_append(&ga, ' ');
#endif
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim
index fbcd496..6ebbcec 100644
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -1494,12 +1494,7 @@ func Test_read_nonl_line()
endif
let g:linecount = 0
- if has('win32')
- " workaround: 'shellescape' does improper escaping double quotes
- let arg = 'import sys;sys.stdout.write(\"1\n2\n3\")'
- else
- let arg = 'import sys;sys.stdout.write("1\n2\n3")'
- endif
+ let arg = 'import sys;sys.stdout.write("1\n2\n3")'
call job_start([s:python, '-c', arg], {'callback': 'MyLineCountCb'})
call WaitFor('3 <= g:linecount')
call assert_equal(3, g:linecount)
@@ -1541,5 +1536,53 @@ func Test_close_lambda()
call s:run_server('Ch_test_close_lambda')
endfunc
+func s:test_list_args(cmd, out, remove_lf)
+ try
+ let s:out = ''
+ call job_start([s:python, '-c', a:cmd], {'callback': {ch,msg->execute('let s:out.=msg')}, 'out_mode': 'raw'})
+ call WaitFor('"" != s:out')
+ if has('win32')
+ let s:out = substitute(s:out, '\r', '', 'g')
+ endif
+ if a:remove_lf
+ let s:out = substitute(s:out, '\n$', '', 'g')
+ endif
+ call assert_equal(a:out, s:out)
+ finally
+ unlet s:out
+ endtry
+endfunc
+
+func Test_list_args()
+ if !has('job')
+ return
+ endif
+
+ call s:test_list_args('import sys;sys.stdout.write("hello world")', "hello world", 0)
+ call s:test_list_args('import sys;sys.stdout.write("hello\nworld")', "hello\nworld", 0)
+ call s:test_list_args('import sys;sys.stdout.write(''hello\nworld'')', "hello\nworld", 0)
+ call s:test_list_args('import sys;sys.stdout.write(''hello"world'')', "hello\"world", 0)
+ call s:test_list_args('import sys;sys.stdout.write(''hello^world'')', "hello^world", 0)
+ call s:test_list_args('import sys;sys.stdout.write("hello&&world")', "hello&&world", 0)
+ call s:test_list_args('import sys;sys.stdout.write(''hello\\world'')', "hello\\world", 0)
+ call s:test_list_args('import sys;sys.stdout.write(''hello\\\\world'')', "hello\\\\world", 0)
+ call s:test_list_args('import sys;sys.stdout.write("hello\"world\"")', 'hello"world"', 0)
+ call s:test_list_args('import sys;sys.stdout.write("h\"ello worl\"d")', 'h"ello worl"d', 0)
+ call s:test_list_args('import sys;sys.stdout.write("h\"e\\\"llo wor\\\"l\"d")', 'h"e\"llo wor\"l"d', 0)
+ call s:test_list_args('import sys;sys.stdout.write("h\"e\\\"llo world")', 'h"e\"llo world', 0)
+ call s:test_list_args('import sys;sys.stdout.write("hello\tworld")', "hello\tworld", 0)
+
+ " tests which not contain spaces in the argument
+ call s:test_list_args('print("hello\nworld")', "hello\nworld", 1)
+ call s:test_list_args('print(''hello\nworld'')', "hello\nworld", 1)
+ call s:test_list_args('print(''hello"world'')', "hello\"world", 1)
+ call s:test_list_args('print(''hello^world'')', "hello^world", 1)
+ call s:test_list_args('print("hello&&world")', "hello&&world", 1)
+ call s:test_list_args('print(''hello\\world'')', "hello\\world", 1)
+ call s:test_list_args('print(''hello\\\\world'')', "hello\\\\world", 1)
+ call s:test_list_args('print("hello\"world\"")', 'hello"world"', 1)
+ call s:test_list_args('print("hello\tworld")', "hello\tworld", 1)
+endfunc
+
" Uncomment this to see what happens, output is in src/testdir/channellog.
" call ch_logfile('channellog', 'w')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment