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)
diff --git a/src/channel.c b/src/channel.c
index 34ee02a..e42c957 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -5144,6 +5144,14 @@
 		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)
 	    {
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 04e1313..c9d9287 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -787,6 +787,7 @@
 # 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},
diff --git a/src/proto/terminal.pro b/src/proto/terminal.pro
index 0527aa8..52bb1b8 100644
--- a/src/proto/terminal.pro
+++ b/src/proto/terminal.pro
@@ -50,6 +50,7 @@
 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);
diff --git a/src/structs.h b/src/structs.h
index e3c73cd..9d2ec16 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1938,6 +1938,7 @@
 #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 @@
     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;
 
diff --git a/src/terminal.c b/src/terminal.c
index f1d89ab..4f2b8bb 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -109,6 +109,7 @@
 #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->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 @@
 	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 @@
 	    semsg(_("E181: Invalid attribute: %s"), cmd);
 	    goto theend;
 	}
+# undef OPTARG_HAS
 	cmd = skipwhite(p);
     }
     if (*cmd == NUL)
@@ -933,6 +954,7 @@
 	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 @@
 }
 
 /*
+ * 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 @@
     }
     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;
     }
 
@@ -5546,6 +5577,27 @@
 #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 @@
 		    + 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);
diff --git a/src/testdir/term_util.vim b/src/testdir/term_util.vim
index 1cfac0e..4eaf6d8 100644
--- a/src/testdir/term_util.vim
+++ b/src/testdir/term_util.vim
@@ -61,11 +61,16 @@
 
   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]
diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim
index 814a989..0041965 100644
--- a/src/testdir/test_terminal.vim
+++ b/src/testdir/test_terminal.vim
@@ -1353,30 +1353,90 @@
 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 @@
   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
diff --git a/src/version.c b/src/version.c
index f7d7ede..44d4d04 100644
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2080,
+/**/
     2079,
 /**/
     2078,