0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

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)
This commit is contained in:
Bram Moolenaar 2019-09-26 23:08:54 +02:00
parent d2c1fb476d
commit d2842ea60b
10 changed files with 220 additions and 35 deletions

View File

@ -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 the tty name of a terminal
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

View File

@ -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_sendkeys()*
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_start()*
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: >

View File

@ -5144,6 +5144,14 @@ get_job_options(typval_T *tv, jobopt_T *opt, int supported, int supported2)
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)
{

View File

@ -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},

View File

@ -50,6 +50,7 @@ void f_term_scrape(typval_T *argvars, typval_T *rettv);
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);

View File

@ -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;

View File

@ -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 (ep != NULL)
{
if (ep < p)
p = ep;
else
ep = NULL;
}
if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
# 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);
@ -3769,6 +3791,15 @@ handle_drop_command(listitem_T *item)
vim_free(tofree);
}
/*
* 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, channel_T *channel, listitem_T *item)
}
func = tv_get_string(&item->li_tv);
if (STRNCMP(func, "Tapi_", 5) != 0)
if (!is_permitted_term_api(func, term->tl_api))
{
ch_log(channel, "Invalid function name: %s", func);
ch_log(channel, "Unpermitted function: %s", func);
return;
}
@ -5545,6 +5576,27 @@ f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
}
#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
*/
@ -5608,7 +5660,7 @@ f_term_start(typval_T *argvars, typval_T *rettv)
+ 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);

View File

@ -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]

View File

@ -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
call WriteApiCall('TryThis')
call ch_logfile('Xlog', 'w')
let buf = RunVimInTerminal('-S Xscript', {})
call WaitForAssert({-> assert_match('Invalid function name: TryThis', string(readfile('Xlog')))})
func! TryThis(bufnum, arg)
let g:called_bufnum3 = a:bufnum
let g:called_arg3 = a:arg
endfunc
call WriteApiCall('TryThis')
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

View File

@ -757,6 +757,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
2080,
/**/
2079,
/**/