changeset 18170:4ac8161e92e0 v8.1.2080

patch 8.1.2080: the terminal API is limited and can't be disabled Commit: https://github.com/vim/vim/commit/d2842ea60bd608b7f9ec93c77d3f36a8e3bf5fe9 Author: Bram Moolenaar <Bram@vim.org> Date: Thu Sep 26 23:08:54 2019 +0200 patch 8.1.2080: the terminal API is limited and can't be disabled Problem: The terminal API is limited and can't be disabled. Solution: Add term_setapi() to set the function prefix. (Ozaki Kiichi, closes #2907)
author Bram Moolenaar <Bram@vim.org>
date Thu, 26 Sep 2019 23:15:05 +0200
parents 6ad8949a205d
children b6145168fb4b
files runtime/doc/eval.txt runtime/doc/terminal.txt src/channel.c src/evalfunc.c src/proto/terminal.pro src/structs.h src/terminal.c src/testdir/term_util.vim src/testdir/test_terminal.vim src/version.c
diffstat 10 files changed, 220 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt*	For Vim version 8.1.  Last change: 2019 Sep 19
+*eval.txt*	For Vim version 8.1.  Last change: 2019 Sep 26
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -2819,6 +2819,7 @@ term_gettty({buf}, [{input}])	String	get
 term_list()			List	get the list of terminal buffers
 term_scrape({buf}, {row})	List	get row of a terminal screen
 term_sendkeys({buf}, {keys})	none	send keystrokes to a terminal
+term_setapi({buf}, {expr})	none	set |terminal-api| function name prefix
 term_setansicolors({buf}, {colors})
 				none	set ANSI palette in GUI color mode
 term_setkill({buf}, {how})	none	set signal to stop job in terminal
@@ -3262,9 +3263,14 @@ bufnr([{expr} [, {create}]])
 		The result is the number of a buffer, as it is displayed by
 		the ":ls" command.  For the use of {expr}, see |bufname()|
 		above.
+
 		If the buffer doesn't exist, -1 is returned.  Or, if the
 		{create} argument is present and not zero, a new, unlisted,
-		buffer is created and its number is returned.
+		buffer is created and its number is returned.  Example: >
+			let newbuf = bufnr('Scratch001', 1)
+<		Using an empty name uses the current buffer. To create a new
+		buffer with an empty name use |bufadd()|.
+
 		bufnr("$") is the last buffer: >
 			:let last_buffer = bufnr("$")
 <		The result is a Number, which is the highest buffer number
--- a/runtime/doc/terminal.txt
+++ b/runtime/doc/terminal.txt
@@ -1,4 +1,4 @@
-*terminal.txt*	For Vim version 8.1.  Last change: 2019 Sep 20
+*terminal.txt*	For Vim version 8.1.  Last change: 2019 Sep 26
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -222,7 +222,7 @@ Command syntax ~
 					Vim width (no window left or right of
 					the terminal window) this value is
 					ignored.
-			++eof={text}	when using [range]: text to send after
+			++eof={text}	When using [range]: text to send after
 					the last line was written. Cannot
 					contain white space.  A CR is
 					appended.  For MS-Windows the default
@@ -234,6 +234,10 @@ Command syntax ~
 			++type={pty}	(MS-Windows only): Use {pty} as the
 					virtual console.  See 'termwintype'
 					for the values.
+			++api={expr}	Permit the function name starting with
+					{expr} to be called as |terminal-api|
+					function.  If {expr} is empty then no
+					function can be called.
 
 			If you want to use more options use the |term_start()|
 			function.
@@ -701,6 +705,15 @@ term_sendkeys({buf}, {keys})				*term_se
 			GetBufnr()->term_sendkeys(keys)
 
 
+term_setapi({buf}, {expr})				*term_setapi()*
+		Set the function name prefix to be used for the |terminal-api|
+		function in terminal {buf}.  For example: >
+		    :call term_setapi(buf, "Myapi_")
+		    :call term_setapi(buf, "")
+<
+		The default is "Tapi_".  When {expr} is an empty string then
+		no |terminal-api| function can be used for {buf}.
+
 term_setansicolors({buf}, {colors})			*term_setansicolors()*
 		Set the ANSI color palette used by terminal {buf}.
 		{colors} must be a List of 16 valid color names or hexadecimal
@@ -843,6 +856,9 @@ term_start({cmd} [, {options}])			*term_
 				     color modes.  See |g:terminal_ansi_colors|.
 		   "tty_type"	     (MS-Windows only): Specify which pty to
 				     use.  See 'termwintype' for the values.
+		   "term_api"	     function name prefix for the
+				     |terminal-api| function.  See
+				     |term_setapi()|.
 
 		Can also be used as a |method|: >
 			GetCommand()->term_start()
@@ -902,9 +918,9 @@ Currently supported commands:
 		Call a user defined function with {argument}.
 		The function is called with two arguments: the buffer number
 		of the terminal and {argument}, the decoded JSON argument. 
-		The function name must start with "Tapi_" to avoid
+		By default, the function name must start with "Tapi_" to avoid
 		accidentally calling a function not meant to be used for the
-		terminal API.
+		terminal API.  This can be changed with |term_setapi()|.
 		The user function should sanity check the argument.
 		The function can use |term_sendkeys()| to send back a reply.
 		Example in JSON: >
--- a/src/channel.c
+++ b/src/channel.c
@@ -5144,6 +5144,14 @@ get_job_options(typval_T *tv, jobopt_T *
 		memcpy(opt->jo_ansi_colors, rgb, sizeof(rgb));
 	    }
 # endif
+	    else if (STRCMP(hi->hi_key, "term_api") == 0)
+	    {
+		if (!(supported2 & JO2_TERM_API))
+		    break;
+		opt->jo_set2 |= JO2_TERM_API;
+		opt->jo_term_api = tv_get_string_buf_chk(item,
+							 opt->jo_term_api_buf);
+	    }
 #endif
 	    else if (STRCMP(hi->hi_key, "env") == 0)
 	    {
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -787,6 +787,7 @@ static funcentry_T global_functions[] =
 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
     {"term_setansicolors", 2, 2, FEARG_1, f_term_setansicolors},
 # endif
+    {"term_setapi",	2, 2, FEARG_1,	  f_term_setapi},
     {"term_setkill",	2, 2, FEARG_1,	  f_term_setkill},
     {"term_setrestore",	2, 2, FEARG_1,	  f_term_setrestore},
     {"term_setsize",	3, 3, FEARG_1,	  f_term_setsize},
--- a/src/proto/terminal.pro
+++ b/src/proto/terminal.pro
@@ -50,6 +50,7 @@ void f_term_scrape(typval_T *argvars, ty
 void f_term_sendkeys(typval_T *argvars, typval_T *rettv);
 void f_term_getansicolors(typval_T *argvars, typval_T *rettv);
 void f_term_setansicolors(typval_T *argvars, typval_T *rettv);
+void f_term_setapi(typval_T *argvars, typval_T *rettv);
 void f_term_setrestore(typval_T *argvars, typval_T *rettv);
 void f_term_setkill(typval_T *argvars, typval_T *rettv);
 void f_term_start(typval_T *argvars, typval_T *rettv);
--- a/src/structs.h
+++ b/src/structs.h
@@ -1938,6 +1938,7 @@ struct channel_S {
 #define JO2_ANSI_COLORS	    0x8000	// "ansi_colors"
 #define JO2_TTY_TYPE	    0x10000	// "tty_type"
 #define JO2_BUFNR	    0x20000	// "bufnr"
+#define JO2_TERM_API	    0x40000	// "term_api"
 
 #define JO_MODE_ALL	(JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE)
 #define JO_CB_ALL \
@@ -2007,6 +2008,8 @@ typedef struct
     long_u	jo_ansi_colors[16];
 # endif
     int		jo_tty_type;	    // first character of "tty_type"
+    char_u	*jo_term_api;
+    char_u	jo_term_api_buf[NUMBUFLEN];
 #endif
 } jobopt_T;
 
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -109,6 +109,7 @@ struct terminal_S {
 #define TL_FINISH_OPEN	    'o'	/* ++open */
     char_u	*tl_opencmd;
     char_u	*tl_eof_chars;
+    char_u	*tl_api;	// prefix for terminal API function
 
     char_u	*tl_arg0_cmd;	// To format the status bar
 
@@ -641,6 +642,11 @@ term_start(
 	term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
     }
 
+    if (opt->jo_term_api != NULL)
+	term->tl_api = vim_strsave(opt->jo_term_api);
+    else
+	term->tl_api = vim_strsave((char_u *)"Tapi_");
+
     /* System dependent: setup the vterm and maybe start the job in it. */
     if (argv == NULL
 	    && argvar->v_type == VAR_STRING
@@ -708,44 +714,58 @@ ex_terminal(exarg_T *eap)
 	cmd += 2;
 	p = skiptowhite(cmd);
 	ep = vim_strchr(cmd, '=');
-	if (ep != NULL && ep < p)
-	    p = ep;
-
-	if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
+	if (ep != NULL)
+	{
+	    if (ep < p)
+		p = ep;
+	    else
+		ep = NULL;
+	}
+
+# define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \
+				 && STRNICMP(cmd, name, sizeof(name) - 1) == 0)
+	if (OPTARG_HAS("close"))
 	    opt.jo_term_finish = 'c';
-	else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
+	else if (OPTARG_HAS("noclose"))
 	    opt.jo_term_finish = 'n';
-	else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
+	else if (OPTARG_HAS("open"))
 	    opt.jo_term_finish = 'o';
-	else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
+	else if (OPTARG_HAS("curwin"))
 	    opt.jo_curwin = 1;
-	else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
+	else if (OPTARG_HAS("hidden"))
 	    opt.jo_hidden = 1;
-	else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
+	else if (OPTARG_HAS("norestore"))
 	    opt.jo_term_norestore = 1;
-	else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
-		&& ep != NULL)
+	else if (OPTARG_HAS("kill") && ep != NULL)
 	{
 	    opt.jo_set2 |= JO2_TERM_KILL;
 	    opt.jo_term_kill = ep + 1;
 	    p = skiptowhite(cmd);
 	}
-	else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
-		&& ep != NULL && isdigit(ep[1]))
+	else if (OPTARG_HAS("api"))
+	{
+	    opt.jo_set2 |= JO2_TERM_API;
+	    if (ep != NULL)
+	    {
+		opt.jo_term_api = ep + 1;
+		p = skiptowhite(cmd);
+	    }
+	    else
+		opt.jo_term_api = NULL;
+	}
+	else if (OPTARG_HAS("rows") && ep != NULL && isdigit(ep[1]))
 	{
 	    opt.jo_set2 |= JO2_TERM_ROWS;
 	    opt.jo_term_rows = atoi((char *)ep + 1);
 	    p = skiptowhite(cmd);
 	}
-	else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
-		&& ep != NULL && isdigit(ep[1]))
+	else if (OPTARG_HAS("cols") && ep != NULL && isdigit(ep[1]))
 	{
 	    opt.jo_set2 |= JO2_TERM_COLS;
 	    opt.jo_term_cols = atoi((char *)ep + 1);
 	    p = skiptowhite(cmd);
 	}
-	else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
-								 && ep != NULL)
+	else if (OPTARG_HAS("eof") && ep != NULL)
 	{
 	    char_u *buf = NULL;
 	    char_u *keys;
@@ -785,6 +805,7 @@ ex_terminal(exarg_T *eap)
 	    semsg(_("E181: Invalid attribute: %s"), cmd);
 	    goto theend;
 	}
+# undef OPTARG_HAS
 	cmd = skipwhite(p);
     }
     if (*cmd == NUL)
@@ -933,6 +954,7 @@ free_unused_terminals()
 	free_scrollback(term);
 
 	term_free_vterm(term);
+	vim_free(term->tl_api);
 	vim_free(term->tl_title);
 #ifdef FEAT_SESSION
 	vim_free(term->tl_command);
@@ -3770,6 +3792,15 @@ handle_drop_command(listitem_T *item)
 }
 
 /*
+ * Return TRUE if "func" starts with "pat" and "pat" isn't empty.
+ */
+    static int
+is_permitted_term_api(char_u *func, char_u *pat)
+{
+    return pat != NULL && *pat != NUL && STRNICMP(func, pat, STRLEN(pat)) == 0;
+}
+
+/*
  * Handles a function call from the job running in a terminal.
  * "item" is the function name, "item->li_next" has the arguments.
  */
@@ -3788,9 +3819,9 @@ handle_call_command(term_T *term, channe
     }
     func = tv_get_string(&item->li_tv);
 
-    if (STRNCMP(func, "Tapi_", 5) != 0)
-    {
-	ch_log(channel, "Invalid function name: %s", func);
+    if (!is_permitted_term_api(func, term->tl_api))
+    {
+	ch_log(channel, "Unpermitted function: %s", func);
 	return;
     }
 
@@ -5546,6 +5577,27 @@ f_term_setansicolors(typval_T *argvars, 
 #endif
 
 /*
+ * "term_setapi(buf, api)" function
+ */
+    void
+f_term_setapi(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    buf_T	*buf = term_get_buf(argvars, "term_setapi()");
+    term_T	*term;
+    char_u	*api;
+
+    if (buf == NULL)
+	return;
+    term = buf->b_term;
+    vim_free(term->tl_api);
+    api = tv_get_string_chk(&argvars[1]);
+    if (api != NULL)
+	term->tl_api = vim_strsave(api);
+    else
+	term->tl_api = NULL;
+}
+
+/*
  * "term_setrestore(buf, command)" function
  */
     void
@@ -5608,7 +5660,7 @@ f_term_start(typval_T *argvars, typval_T
 		    + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
 		    + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
 		    + JO2_NORESTORE + JO2_TERM_KILL
-		    + JO2_ANSI_COLORS + JO2_TTY_TYPE) == FAIL)
+		    + JO2_ANSI_COLORS + JO2_TTY_TYPE + JO2_TERM_API) == FAIL)
 	return;
 
     buf = term_start(&argvars[0], NULL, &opt, 0);
--- a/src/testdir/term_util.vim
+++ b/src/testdir/term_util.vim
@@ -61,11 +61,16 @@ func RunVimInTerminal(arguments, options
 
   let cmd = GetVimCommandCleanTerm() .. a:arguments
 
-  let buf = term_start(cmd, {
+  let options = {
 	\ 'curwin': 1,
 	\ 'term_rows': rows,
 	\ 'term_cols': cols,
-	\ })
+	\ }
+  " Accept other options whose name starts with 'term_'.
+  call extend(options, filter(copy(a:options), 'v:key =~# "^term_"'))
+
+  let buf = term_start(cmd, options)
+
   if &termwinsize == ''
     " in the GUI we may end up with a different size, try to set it.
     if term_getsize(buf) != [rows, cols]
--- a/src/testdir/test_terminal.vim
+++ b/src/testdir/test_terminal.vim
@@ -1353,30 +1353,90 @@ endfunc
 func Test_terminal_api_call()
   CheckRunVimInTerminal
 
+call ch_logfile('logfile', 'w')
+  unlet! g:called_bufnum
+  unlet! g:called_arg
+
   call WriteApiCall('Tapi_TryThis')
+
+  " Default
   let buf = RunVimInTerminal('-S Xscript', {})
   call WaitFor({-> exists('g:called_bufnum')})
   call assert_equal(buf, g:called_bufnum)
   call assert_equal(['hello', 123], g:called_arg)
+  call StopVimInTerminal(buf)
 
+  unlet! g:called_bufnum
+  unlet! g:called_arg
+
+  " Enable explicitly
+  let buf = RunVimInTerminal('-S Xscript', {'term_api': 'Tapi_Try'})
+  call WaitFor({-> exists('g:called_bufnum')})
+  call assert_equal(buf, g:called_bufnum)
+  call assert_equal(['hello', 123], g:called_arg)
   call StopVimInTerminal(buf)
+
+  unlet! g:called_bufnum
+  unlet! g:called_arg
+
+  func! ApiCall_TryThis(bufnum, arg)
+    let g:called_bufnum2 = a:bufnum
+    let g:called_arg2 = a:arg
+  endfunc
+
+  call WriteApiCall('ApiCall_TryThis')
+
+  " Use prefix match
+  let buf = RunVimInTerminal('-S Xscript', {'term_api': 'ApiCall_'})
+  call WaitFor({-> exists('g:called_bufnum2')})
+  call assert_equal(buf, g:called_bufnum2)
+  call assert_equal(['hello', 123], g:called_arg2)
+  call StopVimInTerminal(buf)
+
+  unlet! g:called_bufnum2
+  unlet! g:called_arg2
+
   call delete('Xscript')
-  unlet g:called_bufnum
-  unlet g:called_arg
+  delfunction! ApiCall_TryThis
+  unlet! g:called_bufnum2
+  unlet! g:called_arg2
 endfunc
 
 func Test_terminal_api_call_fails()
   CheckRunVimInTerminal
 
+  func! TryThis(bufnum, arg)
+    let g:called_bufnum3 = a:bufnum
+    let g:called_arg3 = a:arg
+  endfunc
+
   call WriteApiCall('TryThis')
-  call ch_logfile('Xlog', 'w')
-  let buf = RunVimInTerminal('-S Xscript', {})
-  call WaitForAssert({-> assert_match('Invalid function name: TryThis', string(readfile('Xlog')))})
+
+  unlet! g:called_bufnum3
+  unlet! g:called_arg3
 
+  " Not permitted
+  call ch_logfile('Xlog', 'w')
+  let buf = RunVimInTerminal('-S Xscript', {'term_api': ''})
+  call WaitForAssert({-> assert_match('Unpermitted function: TryThis', string(readfile('Xlog')))})
+  call assert_false(exists('g:called_bufnum3'))
+  call assert_false(exists('g:called_arg3'))
   call StopVimInTerminal(buf)
+
+  " No match
+  call ch_logfile('Xlog', 'w')
+  let buf = RunVimInTerminal('-S Xscript', {'term_api': 'TryThat'})
+  call WaitFor({-> string(readfile('Xlog')) =~ 'Unpermitted function: TryThis'})
+  call assert_false(exists('g:called_bufnum3'))
+  call assert_false(exists('g:called_arg3'))
+  call StopVimInTerminal(buf)
+
   call delete('Xscript')
-  call ch_logfile('', '')
+  call ch_logfile('')
   call delete('Xlog')
+  delfunction! TryThis
+  unlet! g:called_bufnum3
+  unlet! g:called_arg3
 endfunc
 
 let s:caught_e937 = 0
@@ -2061,3 +2121,34 @@ func Test_terminal_altscreen()
   exe buf . "bwipe!"
   call delete('Xtext')
 endfunc
+
+func Test_terminal_setapi_and_call()
+  if !CanRunVimInTerminal()
+    return
+  endif
+
+  call WriteApiCall('Tapi_TryThis')
+  call ch_logfile('Xlog', 'w')
+
+  unlet! g:called_bufnum
+  unlet! g:called_arg
+
+  let buf = RunVimInTerminal('-S Xscript', {'term_api': 0})
+  call WaitForAssert({-> assert_match('Unpermitted function: Tapi_TryThis', string(readfile('Xlog')))})
+  call assert_false(exists('g:called_bufnum'))
+  call assert_false(exists('g:called_arg'))
+
+  call term_setapi(buf, 'Tapi_TryThis')
+  call term_sendkeys(buf, ":set notitle\<CR>")
+  call term_sendkeys(buf, ":source Xscript\<CR>")
+  call WaitFor({-> exists('g:called_bufnum')})
+  call assert_equal(buf, g:called_bufnum)
+  call assert_equal(['hello', 123], g:called_arg)
+  call StopVimInTerminal(buf)
+
+  call delete('Xscript')
+  call ch_logfile('')
+  call delete('Xlog')
+  unlet! g:called_bufnum
+  unlet! g:called_arg
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2080,
+/**/
     2079,
 /**/
     2078,