diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2019-06-26 21:02:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-26 21:02:24 +0200 |
commit | 5b47e4dc3858285b0d8fe95f0e7c197edca782e8 (patch) | |
tree | 284598f6113c153e8fe1c185b699acff84f03595 | |
parent | 154765e4bf4ecc0362dbbb12b94cf5fe2418216f (diff) | |
parent | 959ff84e04d41806963cb476d6a731f4ef1c0215 (diff) | |
download | rneovim-5b47e4dc3858285b0d8fe95f0e7c197edca782e8.tar.gz rneovim-5b47e4dc3858285b0d8fe95f0e7c197edca782e8.tar.bz2 rneovim-5b47e4dc3858285b0d8fe95f0e7c197edca782e8.zip |
Merge #10341 from jamessan/modeline-backports
Modeline fix backports
-rw-r--r-- | runtime/doc/options.txt | 75 | ||||
-rw-r--r-- | src/nvim/buffer.c | 6 | ||||
-rw-r--r-- | src/nvim/eval.c | 19 | ||||
-rw-r--r-- | src/nvim/ex_cmds.lua | 2 | ||||
-rw-r--r-- | src/nvim/generators/gen_options.lua | 1 | ||||
-rw-r--r-- | src/nvim/option.c | 177 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 1 | ||||
-rw-r--r-- | src/nvim/options.lua | 21 | ||||
-rw-r--r-- | src/nvim/testdir/test49.vim | 3 | ||||
-rw-r--r-- | src/nvim/testdir/test_alot.vim | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_autocmd.vim | 23 | ||||
-rw-r--r-- | src/nvim/testdir/test_functions.vim | 16 | ||||
-rw-r--r-- | src/nvim/testdir/test_glob2regpat.vim | 4 | ||||
-rw-r--r-- | src/nvim/testdir/test_modeline.vim | 173 | ||||
-rw-r--r-- | src/nvim/testdir/test_vimscript.vim | 1 |
15 files changed, 443 insertions, 80 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 534b2025cd..e8471d561a 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -478,14 +478,17 @@ backslash in front of the ':' will be removed. Example: /* vi:set dir=c\:\tmp: */ ~ This sets the 'dir' option to "c:\tmp". Only a single backslash before the ':' is removed. Thus to include "\:" you have to specify "\\:". - + *E992* No other commands than "set" are supported, for security reasons (somebody might create a Trojan horse text file with modelines). And not all options -can be set. For some options a flag is set, so that when it's used the -|sandbox| is effective. Still, there is always a small risk that a modeline -causes trouble. E.g., when some joker sets 'textwidth' to 5 all your lines -are wrapped unexpectedly. So disable modelines before editing untrusted text. -The mail ftplugin does this, for example. +can be set. For some options a flag is set, so that when the value is used +the |sandbox| is effective. Some options can only be set from the modeline +when 'modelineexpr' is set (the default is off). + +Still, there is always a small risk that a modeline causes trouble. E.g., +when some joker sets 'textwidth' to 5 all your lines are wrapped unexpectedly. +So disable modelines before editing untrusted text. The mail ftplugin does +this, for example. Hint: If you would like to do something else than setting an option, you could define an autocommand that checks the file for a specific string. For @@ -2439,7 +2442,7 @@ A jump table for the options with a short description can be found at |Q_op|. The expression will be evaluated in the |sandbox| if set from a modeline, see |sandbox-option|. This option can't be set from a |modeline| when the 'diff' option is - on. + on or the 'modelineexpr' option is off. It is not allowed to change text or jump to another window while evaluating 'foldexpr' |textlock|. @@ -2554,6 +2557,7 @@ A jump table for the options with a short description can be found at |Q_op|. The expression will be evaluated in the |sandbox| if set from a modeline, see |sandbox-option|. + This option cannot be set in a modeline when 'modelineexpr' is off. It is not allowed to change text or jump to another window while evaluating 'foldtext' |textlock|. @@ -2589,16 +2593,8 @@ A jump table for the options with a short description can be found at |Q_op|. The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. That stops the option from working, since changing the buffer text is not allowed. - - *'formatoptions'* *'fo'* -'formatoptions' 'fo' string (default: "tcqj", Vi default: "vt") - local to buffer - This is a sequence of letters which describes how automatic - formatting is to be done. See |fo-table|. When the 'paste' option is - on, no formatting is done (like 'formatoptions' is empty). Commas can - be inserted for readability. - To avoid problems with flags that are added in the future, use the - "+=" and "-=" feature of ":set" |add-option-flags|. + This option cannot be set in a modeline when 'modelineexpr' is off. + NOTE: This option is set to "" when 'compatible' is set. *'formatlistpat'* *'flp'* 'formatlistpat' 'flp' string (default: "^\s*\d\+[\]:.)}\t ]\s*") @@ -2613,6 +2609,16 @@ A jump table for the options with a short description can be found at |Q_op|. The default recognizes a number, followed by an optional punctuation character and white space. + *'formatoptions'* *'fo'* +'formatoptions' 'fo' string (default: "tcqj", Vi default: "vt") + local to buffer + This is a sequence of letters which describes how automatic + formatting is to be done. See |fo-table|. When the 'paste' option is + on, no formatting is done (like 'formatoptions' is empty). Commas can + be inserted for readability. + To avoid problems with flags that are added in the future, use the + "+=" and "-=" feature of ":set" |add-option-flags|. + *'formatprg'* *'fp'* 'formatprg' 'fp' string (default "") global or local to buffer |global-local| @@ -2643,6 +2649,9 @@ A jump table for the options with a short description can be found at |Q_op|. - system signals low battery life - Nvim exits abnormally + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. + *'gdefault'* *'gd'* *'nogdefault'* *'nogd'* 'gdefault' 'gd' boolean (default off) global @@ -2978,6 +2987,7 @@ A jump table for the options with a short description can be found at |Q_op|. 'guitabtooltip' is used for the tooltip, see below. The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. + This option cannot be set in a modeline when 'modelineexpr' is off. Only used when the GUI tab pages line is displayed. 'e' must be present in 'guioptions'. For the non-GUI tab pages line 'tabline' is @@ -3106,6 +3116,7 @@ A jump table for the options with a short description can be found at |Q_op|. When this option contains printf-style '%' items, they will be expanded according to the rules used for 'statusline'. See 'titlestring' for example settings. + This option cannot be set in a modeline when 'modelineexpr' is off. *'ignorecase'* *'ic'* *'noignorecase'* *'noic'* 'ignorecase' 'ic' boolean (default off) @@ -3209,6 +3220,7 @@ A jump table for the options with a short description can be found at |Q_op|. The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. + This option cannot be set in a modeline when 'modelineexpr' is off. It is not allowed to change text or jump to another window while evaluating 'includeexpr' |textlock|. @@ -3277,6 +3289,7 @@ A jump table for the options with a short description can be found at |Q_op|. The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. + This option cannot be set in a modeline when 'modelineexpr' is off. It is not allowed to change text or jump to another window while evaluating 'indentexpr' |textlock|. @@ -3879,10 +3892,23 @@ A jump table for the options with a short description can be found at |Q_op|. < If you have less than 512 Mbyte |:mkspell| may fail for some languages, no matter what you set 'mkspellmem' to. + This option cannot be set from a |modeline| or in the |sandbox|. + *'modeline'* *'ml'* *'nomodeline'* *'noml'* 'modeline' 'ml' boolean (Vim default: on (off for root), Vi default: off) local to buffer + If 'modeline' is on 'modelines' gives the number of lines that is + checked for set commands. If 'modeline' is off or 'modelines' is zero + no lines are checked. See |modeline|. + + *'modelineexpr'* *'mle'* *'nomodelineexpr'* *'nomle'* +'modelineexpr' 'mle' boolean (default: off) + global + When on allow some options that are an expression to be set in the + modeline. Check the option for whether it is affected by + 'modelineexpr'. Also see |modeline|. + *'modelines'* *'mls'* 'modelines' 'mls' number (default 5) global @@ -4622,6 +4648,8 @@ A jump table for the options with a short description can be found at |Q_op|. When this option is not empty, it determines the content of the ruler string, as displayed for the 'ruler' option. The format of this option is like that of 'statusline'. + This option cannot be set in a modeline when 'modelineexpr' is off. + The default ruler width is 17 characters. To make the ruler 15 characters wide, put "%15(" at the start and "%)" at the end. Example: > @@ -5252,7 +5280,8 @@ A jump table for the options with a short description can be found at |Q_op|. "Pattern not found", "Back at original", etc. q use "recording" instead of "recording @a" F don't give the file info when editing a file, like `:silent` - was used for the command + was used for the command; note that this also affects messages + from autocommands This gives you the opportunity to avoid that a change between buffers requires you to hit <Enter>, but still gives as useful a message as @@ -5527,7 +5556,7 @@ A jump table for the options with a short description can be found at |Q_op|. After this option has been set successfully, Vim will source the files "spell/LANG.vim" in 'runtimepath'. "LANG" is the value of 'spelllang' - up to the first comma, dot or underscore. + up to the first character that is not an ASCII letter and not a dash. Also see |set-spc-auto|. @@ -5773,6 +5802,7 @@ A jump table for the options with a short description can be found at |Q_op|. The 'statusline' option will be evaluated in the |sandbox| if set from a modeline, see |sandbox-option|. + This option cannot be set in a modeline when 'modelineexpr' is off. It is not allowed to change text or jump to another window while evaluating 'statusline' |textlock|. @@ -5927,6 +5957,8 @@ A jump table for the options with a short description can be found at |Q_op|. the text to be displayed. Use "%1T" for the first label, "%2T" for the second one, etc. Use "%X" items for closing labels. + This option cannot be set in a modeline when 'modelineexpr' is off. + Keep in mind that only one of the tab pages is the current one, others are invisible and you can't jump to their windows. @@ -6203,8 +6235,11 @@ A jump table for the options with a short description can be found at |Q_op|. global When this option is not empty, it will be used for the title of the window. This happens only when the 'title' option is on. + When this option contains printf-style '%' items, they will be expanded according to the rules used for 'statusline'. + This option cannot be set in a modeline when 'modelineexpr' is off. + Example: > :auto BufEnter * let &titlestring = hostname() . "/" . expand("%:p") :set title titlestring=%<%F%=%l/%L-%P titlelen=70 @@ -6238,6 +6273,8 @@ A jump table for the options with a short description can be found at |Q_op|. undo file that exists is used. When it cannot be read an error is given, no further entry is used. See |undo-persistence|. + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. *'undofile'* *'noundofile'* *'udf'* *'noudf'* 'undofile' 'udf' boolean (default off) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index f74c66f106..e3e77d65b0 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -4924,9 +4924,15 @@ chk_modeline ( *e = NUL; /* truncate the set command */ if (*s != NUL) { /* skip over an empty "::" */ + const int secure_save = secure; save_SID = current_SID; current_SID = SID_MODELINE; + // Make sure no risky things are executed as a side effect. + secure = 1; + retval = do_set(s, OPT_MODELINE | OPT_LOCAL | flags); + + secure = secure_save; current_SID = save_SID; if (retval == FAIL) /* stop if error found */ break; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5191328b5d..9163673a32 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -241,13 +241,14 @@ typedef enum { ///< the value (prevents error message). } GetLvalFlags; -// function flags +// flags used in uf_flags #define FC_ABORT 0x01 // abort function on error #define FC_RANGE 0x02 // function accepts range #define FC_DICT 0x04 // Dict function, uses "self" #define FC_CLOSURE 0x08 // closure, uses outer scope variables #define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 #define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 +#define FC_SANDBOX 0x40 // function defined in the sandbox // The names of packages that once were loaded are remembered. static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL }; @@ -5853,6 +5854,9 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) if (prof_def_func()) { func_do_profile(fp); } + if (sandbox) { + flags |= FC_SANDBOX; + } fp->uf_varargs = true; fp->uf_flags = flags; fp->uf_calls = 0; @@ -20315,6 +20319,9 @@ void ex_function(exarg_T *eap) if (prof_def_func()) func_do_profile(fp); fp->uf_varargs = varargs; + if (sandbox) { + flags |= FC_SANDBOX; + } fp->uf_flags = flags; fp->uf_calls = 0; fp->uf_script_ID = current_SID; @@ -21305,6 +21312,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, char_u *save_sourcing_name; linenr_T save_sourcing_lnum; scid_T save_current_SID; + bool using_sandbox = false; funccall_T *fc; int save_did_emsg; static int depth = 0; @@ -21462,6 +21470,12 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, save_sourcing_name = sourcing_name; save_sourcing_lnum = sourcing_lnum; sourcing_lnum = 1; + + if (fp->uf_flags & FC_SANDBOX) { + using_sandbox = true; + sandbox++; + } + // need space for new sourcing_name: // * save_sourcing_name // * "["number"].." or "function " @@ -21622,6 +21636,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, if (do_profiling_yes) { script_prof_restore(&wait_start); } + if (using_sandbox) { + sandbox--; + } if (p_verbose >= 12 && sourcing_name != NULL) { ++no_wait_return; diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 79ca5363e0..fae21d8c91 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -1004,7 +1004,7 @@ return { }, { command='function', - flags=bit.bor(EXTRA, BANG, CMDWIN), + flags=bit.bor(EXTRA, BANG, SBOXOK, CMDWIN), addr_type=ADDR_LINES, func='ex_function', }, diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index fdc00d5dc0..d9c65e17c5 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -79,6 +79,7 @@ local get_flags = function(o) {'pri_mkrc'}, {'deny_in_modelines', 'P_NO_ML'}, {'deny_duplicates', 'P_NODUP'}, + {'modelineexpr', 'P_MLE'}, }) do local key_name = flag_desc[1] local def_name = flag_desc[2] or ('P_' .. key_name:upper()) diff --git a/src/nvim/option.c b/src/nvim/option.c index a09bd2915d..2b5b8793ca 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -251,6 +251,7 @@ typedef struct vimoption { #define P_RWINONLY 0x10000000U ///< only redraw current window #define P_NDNAME 0x20000000U ///< only normal dir name chars allowed #define P_UI_OPTION 0x40000000U ///< send option to remote ui +#define P_MLE 0x80000000U ///< under control of 'modelineexpr' #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ @@ -1209,7 +1210,7 @@ do_set ( } len++; if (opt_idx == -1) { - key = find_key_option(arg + 1); + key = find_key_option(arg + 1, true); } } else { len = 0; @@ -1223,7 +1224,7 @@ do_set ( } opt_idx = findoption_len((const char *)arg, (size_t)len); if (opt_idx == -1) { - key = find_key_option(arg); + key = find_key_option(arg, false); } } @@ -1290,6 +1291,11 @@ do_set ( errmsg = (char_u *)_("E520: Not allowed in a modeline"); goto skip; } + if ((flags & P_MLE) && !p_mle) { + errmsg = (char_u *)_( + "E992: Not allowed in a modeline when 'modelineexpr' is off"); + goto skip; + } /* In diff mode some options are overruled. This avoids that * 'foldmethod' becomes "marker" instead of "diff" and that * "wrap" gets set. */ @@ -1364,6 +1370,10 @@ do_set ( && nextchar != NUL && !ascii_iswhite(afterchar)) errmsg = e_trailing; } else { + + int value_is_replaced = !prepending && !adding && !removing; + int value_checked = false; + if (flags & P_BOOL) { /* boolean */ if (nextchar == '=' || nextchar == ':') { errmsg = e_invarg; @@ -1783,12 +1793,32 @@ do_set ( // buffer is closed by autocommands. saved_newval = (newval != NULL) ? xstrdup((char *)newval) : 0; - // Handle side effects, and set the global value for - // ":set" on local options. Note: when setting 'syntax' - // or 'filetype' autocommands may be triggered that can - // cause havoc. - errmsg = did_set_string_option(opt_idx, (char_u **)varp, - new_value_alloced, oldval, errbuf, opt_flags); + { + uint32_t *p = insecure_flag(opt_idx, opt_flags); + const int secure_saved = secure; + + // When an option is set in the sandbox, from a + // modeline or in secure mode, then deal with side + // effects in secure mode. Also when the value was + // set with the P_INSECURE flag and is not + // completely replaced. + if ((opt_flags & OPT_MODELINE) + || sandbox != 0 + || (!value_is_replaced && (*p & P_INSECURE))) { + secure = 1; + } + + // Handle side effects, and set the global value + // for ":set" on local options. Note: when setting + // 'syntax' or 'filetype' autocommands may be + // triggered that can cause havoc. + errmsg = did_set_string_option(opt_idx, (char_u **)varp, + new_value_alloced, oldval, + errbuf, sizeof(errbuf), + opt_flags, &value_checked); + + secure = secure_saved; + } if (errmsg == NULL) { if (!starting) { @@ -1815,8 +1845,7 @@ do_set ( } if (opt_idx >= 0) - did_set_option(opt_idx, opt_flags, - !prepending && !adding && !removing); + did_set_option(opt_idx, opt_flags, value_is_replaced, value_checked); } skip: @@ -1881,7 +1910,9 @@ static void did_set_option ( int opt_idx, int opt_flags, /* possibly with OPT_MODELINE */ - int new_value /* value was replaced completely */ + int new_value, /* value was replaced completely */ + int value_checked /* value was checked to be safe, no need to + set P_INSECURE */ ) { options[opt_idx].flags |= P_WAS_SET; @@ -1890,20 +1921,22 @@ did_set_option ( * set the P_INSECURE flag. Otherwise, if a new value is stored reset the * flag. */ uint32_t *p = insecure_flag(opt_idx, opt_flags); - if (secure - || sandbox != 0 - || (opt_flags & OPT_MODELINE)) + if (!value_checked && (secure + || sandbox != 0 + || (opt_flags & OPT_MODELINE))) { *p = *p | P_INSECURE; - else if (new_value) + } else if (new_value) { *p = *p & ~P_INSECURE; + } } -static char_u *illegal_char(char_u *errbuf, int c) +static char_u *illegal_char(char_u *errbuf, size_t errbuflen, int c) { - if (errbuf == NULL) + if (errbuf == NULL) { return (char_u *)""; - sprintf((char *)errbuf, _("E539: Illegal character <%s>"), - (char *)transchar(c)); + } + vim_snprintf((char *)errbuf, errbuflen, _("E539: Illegal character <%s>"), + (char *)transchar(c)); return errbuf; } @@ -1913,10 +1946,12 @@ static char_u *illegal_char(char_u *errbuf, int c) */ static int string_to_key(char_u *arg) { - if (*arg == '<') - return find_key_option(arg + 1); - if (*arg == '^') + if (*arg == '<') { + return find_key_option(arg + 1, true); + } + if (*arg == '^') { return Ctrl_chr(arg[1]); + } return *arg; } @@ -2392,10 +2427,12 @@ static char *set_string_option(const int opt_idx, const char *const value, char *const saved_oldval = xstrdup(oldval); char *const saved_newval = xstrdup(s); + int value_checked = false; char *const r = (char *)did_set_string_option( - opt_idx, (char_u **)varp, (int)true, (char_u *)oldval, NULL, opt_flags); + opt_idx, (char_u **)varp, (int)true, (char_u *)oldval, + NULL, 0, opt_flags, &value_checked); if (r == NULL) { - did_set_option(opt_idx, opt_flags, true); + did_set_option(opt_idx, opt_flags, true, value_checked); } // call autocommand after handling side effects @@ -2431,13 +2468,16 @@ static bool valid_filetype(char_u *val) * Returns NULL for success, or an error message for an error. */ static char_u * -did_set_string_option ( - int opt_idx, /* index in options[] table */ - char_u **varp, /* pointer to the option variable */ - int new_value_alloced, /* new value was allocated */ - char_u *oldval, /* previous value of the option */ - char_u *errbuf, /* buffer for errors, or NULL */ - int opt_flags /* OPT_LOCAL and/or OPT_GLOBAL */ +did_set_string_option( + int opt_idx, // index in options[] table + char_u **varp, // pointer to the option variable + int new_value_alloced, // new value was allocated + char_u *oldval, // previous value of the option + char_u *errbuf, // buffer for errors, or NULL + size_t errbuflen, // length of errors buffer + int opt_flags, // OPT_LOCAL and/or OPT_GLOBAL + int *value_checked // value was checked to be safe, no + // need to set P_INSECURE ) { char_u *errmsg = NULL; @@ -2655,8 +2695,20 @@ did_set_string_option ( if (!valid_filetype(*varp)) { errmsg = e_invarg; } else { + int secure_save = secure; + + // Reset the secure flag, since the value of 'keymap' has + // been checked to be safe. + secure = 0; + // load or unload key mapping tables errmsg = keymap_init(); + + secure = secure_save; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; } if (errmsg == NULL) { @@ -2742,7 +2794,7 @@ did_set_string_option ( while (*s && *s != ':') { if (vim_strchr((char_u *)COM_ALL, *s) == NULL && !ascii_isdigit(*s) && *s != '-') { - errmsg = illegal_char(errbuf, *s); + errmsg = illegal_char(errbuf, errbuflen, *s); break; } ++s; @@ -2794,7 +2846,7 @@ did_set_string_option ( for (s = p_shada; *s; ) { /* Check it's a valid character */ if (vim_strchr((char_u *)"!\"%'/:<@cfhnrs", *s) == NULL) { - errmsg = illegal_char(errbuf, *s); + errmsg = illegal_char(errbuf, errbuflen, *s); break; } if (*s == 'n') { /* name is always last one */ @@ -2814,9 +2866,9 @@ did_set_string_option ( if (!ascii_isdigit(*(s - 1))) { if (errbuf != NULL) { - sprintf((char *)errbuf, - _("E526: Missing number after <%s>"), - transchar_byte(*(s - 1))); + vim_snprintf((char *)errbuf, errbuflen, + _("E526: Missing number after <%s>"), + transchar_byte(*(s - 1))); errmsg = errbuf; } else errmsg = (char_u *)""; @@ -2994,7 +3046,7 @@ did_set_string_option ( if (!*s) break; if (vim_strchr((char_u *)".wbuksid]tU", *s) == NULL) { - errmsg = illegal_char(errbuf, *s); + errmsg = illegal_char(errbuf, errbuflen, *s); break; } if (*++s != NUL && *s != ',' && *s != ' ') { @@ -3008,9 +3060,9 @@ did_set_string_option ( } } else { if (errbuf != NULL) { - sprintf((char *)errbuf, - _("E535: Illegal character after <%c>"), - *--s); + vim_snprintf((char *)errbuf, errbuflen, + _("E535: Illegal character after <%c>"), + *--s); errmsg = errbuf; } else errmsg = (char_u *)""; @@ -3167,12 +3219,20 @@ did_set_string_option ( errmsg = e_invarg; } else { value_changed = STRCMP(oldval, *varp) != 0; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; } } else if (gvarp == &p_syn) { if (!valid_filetype(*varp)) { errmsg = e_invarg; } else { value_changed = STRCMP(oldval, *varp) != 0; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; } } else if (varp == &curwin->w_p_winhl) { if (!parse_winhl_opt(curwin)) { @@ -3198,7 +3258,7 @@ did_set_string_option ( if (p != NULL) { for (s = *varp; *s; ++s) if (vim_strchr(p, *s) == NULL) { - errmsg = illegal_char(errbuf, *s); + errmsg = illegal_char(errbuf, errbuflen, *s); break; } } @@ -3262,6 +3322,11 @@ did_set_string_option ( // already set to this value. if (!(opt_flags & OPT_MODELINE) || value_changed) { static int ft_recursive = 0; + int secure_save = secure; + + // Reset the secure flag, since the value of 'filetype' has + // been checked to be safe. + secure = 0; ft_recursive++; did_filetype = true; @@ -3274,6 +3339,7 @@ did_set_string_option ( if (varp != &(curbuf->b_p_ft)) { varp = NULL; } + secure = secure_save; } } if (varp == &(curwin->w_s->b_p_spl)) { @@ -3291,11 +3357,13 @@ did_set_string_option ( * '.encoding'. */ for (p = q; *p != NUL; ++p) - if (vim_strchr((char_u *)"_.,", *p) != NULL) + if (!ASCII_ISALNUM(*p) && *p != '-') break; - vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim", - (int)(p - q), q); - source_runtime(fname, DIP_ALL); + if (p > q) { + vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim", + (int)(p - q), q); + source_runtime(fname, DIP_ALL); + } } } @@ -3554,7 +3622,7 @@ char_u *check_stl_option(char_u *s) continue; } if (vim_strchr(STL_ALL, *s) == NULL) { - return illegal_char(errbuf, *s); + return illegal_char(errbuf, sizeof(errbuf), *s); } if (*s == '{') { s++; @@ -4893,19 +4961,20 @@ char *set_option_value(const char *const name, const long number, return NULL; } -/* - * Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number. - */ -int find_key_option_len(const char_u *arg, size_t len) +// Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number. +// When "has_lt" is true there is a '<' before "*arg_arg". +// Returns 0 when the key is not recognized. +int find_key_option_len(const char_u *arg_arg, size_t len, bool has_lt) { - int key; + int key = 0; int modifiers; + const char_u *arg = arg_arg; // Don't use get_special_key_code() for t_xx, we don't want it to call // add_termcap_entry(). if (len >= 4 && arg[0] == 't' && arg[1] == '_') { key = TERMCAP2KEY(arg[2], arg[3]); - } else { + } else if (has_lt) { arg--; // put arg at the '<' modifiers = 0; key = find_special_key(&arg, len + 1, &modifiers, true, true, false); @@ -4916,9 +4985,9 @@ int find_key_option_len(const char_u *arg, size_t len) return key; } -static int find_key_option(const char_u *arg) +static int find_key_option(const char_u *arg, bool has_lt) { - return find_key_option_len(arg, STRLEN(arg)); + return find_key_option_len(arg, STRLEN(arg), has_lt); } /* diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 82f7fc2107..11a59e95ad 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -495,6 +495,7 @@ EXTERN long p_mmd; // 'maxmapdepth' EXTERN long p_mmp; // 'maxmempattern' EXTERN long p_mis; // 'menuitems' EXTERN char_u *p_msm; // 'mkspellmem' +EXTERN long p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' EXTERN char_u *p_mouse; // 'mouse' EXTERN char_u *p_mousem; // 'mousemodel' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 0cc6f58c5f..7ca1b4d64e 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -8,6 +8,7 @@ -- defaults={condition=nil, if_true={vi=224, vim=0}, if_false=nil}, -- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil, -- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil, +-- modelineexpr=nil, -- expand=nil, nodefault=nil, no_mkrc=nil, vi_def=true, vim=true, -- alloced=nil, -- save_pv_indir=nil, @@ -286,6 +287,7 @@ return { deny_duplicates=true, vi_def=true, expand=true, + secure=true, varname='p_cdpath', defaults={if_true={vi=",,"}} }, @@ -856,6 +858,7 @@ return { type='string', scope={'window'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, redraw={'current_window'}, defaults={if_true={vi="0"}} @@ -931,6 +934,7 @@ return { type='string', scope={'window'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, redraw={'current_window'}, defaults={if_true={vi="foldtext()"}} @@ -940,6 +944,7 @@ return { type='string', scope={'buffer'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, varname='p_fex', defaults={if_true={vi=""}} @@ -1053,6 +1058,7 @@ return { full_name='guitablabel', abbreviation='gtl', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, redraw={'current_window'}, enable_if=false, }, @@ -1143,6 +1149,7 @@ return { full_name='iconstring', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, varname='p_iconstring', defaults={if_true={vi=""}} }, @@ -1209,6 +1216,7 @@ return { full_name='includeexpr', abbreviation='inex', type='string', scope={'buffer'}, vi_def=true, + modelineexpr=true, alloced=true, varname='p_inex', defaults={if_true={vi=""}} @@ -1225,6 +1233,7 @@ return { type='string', scope={'buffer'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, varname='p_inde', defaults={if_true={vi=""}} @@ -1539,6 +1548,14 @@ return { defaults={if_true={vi=false, vim=true}} }, { + full_name='modelineexpr', abbreviation='mle', + type='bool', scope={'global'}, + vi_def=true, + secure=true, + varname='p_mle', + defaults={if_true={vi=false}} + }, + { full_name='modelines', abbreviation='mls', type='number', scope={'global'}, vi_def=true, @@ -1898,6 +1915,7 @@ return { type='string', scope={'global'}, vi_def=true, alloced=true, + modelineexpr=true, redraw={'statuslines'}, varname='p_ruf', defaults={if_true={vi=""}} @@ -2293,6 +2311,7 @@ return { type='string', scope={'global', 'window'}, vi_def=true, alloced=true, + modelineexpr=true, redraw={'statuslines'}, varname='p_stl', defaults={if_true={vi=""}} @@ -2352,6 +2371,7 @@ return { full_name='tabline', abbreviation='tal', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, redraw={'all_windows'}, varname='p_tal', defaults={if_true={vi=""}} @@ -2511,6 +2531,7 @@ return { full_name='titlestring', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, varname='p_titlestring', defaults={if_true={vi=""}} }, diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim index 467abcd9b9..837e55ebca 100644 --- a/src/nvim/testdir/test49.vim +++ b/src/nvim/testdir/test49.vim @@ -1,6 +1,6 @@ " Vim script language tests " Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com> -" Last Change: 2016 Feb 07 +" Last Change: 2019 May 24 "------------------------------------------------------------------------------- " Test environment {{{1 @@ -9005,5 +9005,4 @@ Xcheck 50443995 "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker -" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "") "------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 0602ff6a45..9d81d21623 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -28,6 +28,7 @@ source test_lambda.vim source test_mapping.vim source test_menu.vim source test_messages.vim +source test_modeline.vim source test_move.vim source test_partial.vim source test_popup.vim diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 253d6750ed..12b6a38ef3 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -652,6 +652,29 @@ func Test_OptionSet_diffmode_close() "delfunc! AutoCommandOptionSet endfunc +func Test_OptionSet_modeline() + throw 'skipped: Nvim does not support test_override()' + call test_override('starting', 1) + au! OptionSet + augroup set_tabstop + au OptionSet tabstop call timer_start(1, {-> execute("echo 'Handler called'", "")}) + augroup END + call writefile(['vim: set ts=7 sw=5 :', 'something'], 'XoptionsetModeline') + set modeline + let v:errmsg = '' + call assert_fails('split XoptionsetModeline', 'E12:') + call assert_equal(7, &ts) + call assert_equal('', v:errmsg) + + augroup set_tabstop + au! + augroup END + bwipe! + set ts& + call delete('XoptionsetModeline') + call test_override('starting', 0) +endfunc + " Test for Bufleave autocommand that deletes the buffer we are about to edit. func Test_BufleaveWithDelete() new | edit Xfile1 diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 7dc9f31ce7..292f69d704 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1037,3 +1037,19 @@ func Test_func_range_with_edit() call delete('Xfuncrange2') bwipe! endfunc + +sandbox function Fsandbox() + normal ix +endfunc + +func Test_func_sandbox() + sandbox let F = {-> 'hello'} + call assert_equal('hello', F()) + + sandbox let F = {-> execute("normal ix\<Esc>")} + call assert_fails('call F()', 'E48:') + unlet F + + call assert_fails('call Fsandbox()', 'E48:') + delfunc Fsandbox +endfunc diff --git a/src/nvim/testdir/test_glob2regpat.vim b/src/nvim/testdir/test_glob2regpat.vim index fdf17946b6..e6e41f13e7 100644 --- a/src/nvim/testdir/test_glob2regpat.vim +++ b/src/nvim/testdir/test_glob2regpat.vim @@ -1,12 +1,12 @@ " Test glob2regpat() -func Test_invalid() +func Test_glob2regpat_invalid() call assert_fails('call glob2regpat(1.33)', 'E806:') call assert_fails('call glob2regpat("}")', 'E219:') call assert_fails('call glob2regpat("{")', 'E220:') endfunc -func Test_valid() +func Test_glob2regpat_valid() call assert_equal('^foo\.', glob2regpat('foo.*')) call assert_equal('^foo.$', glob2regpat('foo?')) call assert_equal('\.vim$', glob2regpat('*.vim')) diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim new file mode 100644 index 0000000000..1e196e07f0 --- /dev/null +++ b/src/nvim/testdir/test_modeline.vim @@ -0,0 +1,173 @@ +" Tests for parsing the modeline. + +func Test_modeline_invalid() + " This was reading allocated memory in the past. + call writefile(['vi:0', 'nothing'], 'Xmodeline') + let modeline = &modeline + set modeline + call assert_fails('set Xmodeline', 'E518:') + + let &modeline = modeline + bwipe! + call delete('Xmodeline') + endfunc + +func Test_modeline_filetype() + call writefile(['vim: set ft=c :', 'nothing'], 'Xmodeline_filetype') + let modeline = &modeline + set modeline + filetype plugin on + split Xmodeline_filetype + call assert_equal("c", &filetype) + call assert_equal(1, b:did_ftplugin) + call assert_equal("ccomplete#Complete", &ofu) + + bwipe! + call delete('Xmodeline_filetype') + let &modeline = modeline + filetype plugin off +endfunc + +func Test_modeline_syntax() + call writefile(['vim: set syn=c :', 'nothing'], 'Xmodeline_syntax') + let modeline = &modeline + set modeline + syntax enable + split Xmodeline_syntax + call assert_equal("c", &syntax) + call assert_equal("c", b:current_syntax) + + bwipe! + call delete('Xmodeline_syntax') + let &modeline = modeline + syntax off +endfunc + +func Test_modeline_keymap() + if !has('keymap') + return + endif + call writefile(['vim: set keymap=greek :', 'nothing'], 'Xmodeline_keymap') + let modeline = &modeline + set modeline + split Xmodeline_keymap + call assert_equal("greek", &keymap) + call assert_match('greek\|grk', b:keymap_name) + + bwipe! + call delete('Xmodeline_keymap') + let &modeline = modeline + set keymap= iminsert=0 imsearch=-1 +endfunc + +func s:modeline_fails(what, text, error) + if !exists('+' . a:what) + return + endif + let fname = "Xmodeline_fails_" . a:what + call writefile(['vim: set ' . a:text . ' :', 'nothing'], fname) + let modeline = &modeline + set modeline + filetype plugin on + syntax enable + call assert_fails('split ' . fname, a:error) + call assert_equal("", &filetype) + call assert_equal("", &syntax) + + bwipe! + call delete(fname) + let &modeline = modeline + filetype plugin off + syntax off +endfunc + +func Test_modeline_filetype_fails() + call s:modeline_fails('filetype', 'ft=evil$CMD', 'E474:') +endfunc + +func Test_modeline_syntax_fails() + call s:modeline_fails('syntax', 'syn=evil$CMD', 'E474:') +endfunc + +func Test_modeline_keymap_fails() + call s:modeline_fails('keymap', 'keymap=evil$CMD', 'E474:') +endfunc + +func Test_modeline_fails_always() + call s:modeline_fails('backupdir', 'backupdir=Something()', 'E520:') + call s:modeline_fails('cdpath', 'cdpath=Something()', 'E520:') + call s:modeline_fails('charconvert', 'charconvert=Something()', 'E520:') + call s:modeline_fails('completefunc', 'completefunc=Something()', 'E520:') + call s:modeline_fails('cscopeprg', 'cscopeprg=Something()', 'E520:') + call s:modeline_fails('diffexpr', 'diffexpr=Something()', 'E520:') + call s:modeline_fails('directory', 'directory=Something()', 'E520:') + call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:') + call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:') + call s:modeline_fails('exrc', 'exrc=Something()', 'E520:') + call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:') + call s:modeline_fails('fsync', 'fsync=Something()', 'E520:') + call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:') + call s:modeline_fails('helpfile', 'helpfile=Something()', 'E520:') + call s:modeline_fails('imactivatefunc', 'imactivatefunc=Something()', 'E520:') + call s:modeline_fails('imstatusfunc', 'imstatusfunc=Something()', 'E520:') + call s:modeline_fails('imstyle', 'imstyle=Something()', 'E520:') + call s:modeline_fails('keywordprg', 'keywordprg=Something()', 'E520:') + call s:modeline_fails('langmap', 'langmap=Something()', 'E520:') + call s:modeline_fails('luadll', 'luadll=Something()', 'E520:') + call s:modeline_fails('makeef', 'makeef=Something()', 'E520:') + call s:modeline_fails('makeprg', 'makeprg=Something()', 'E520:') + call s:modeline_fails('mkspellmem', 'mkspellmem=Something()', 'E520:') + call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:') + call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:') + call s:modeline_fails('modelineexpr', 'modelineexpr=Something()', 'E520:') + call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:') + call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:') + call s:modeline_fails('perldll', 'perldll=Something()', 'E520:') + call s:modeline_fails('printdevice', 'printdevice=Something()', 'E520:') + call s:modeline_fails('patchexpr', 'patchexpr=Something()', 'E520:') + call s:modeline_fails('printexpr', 'printexpr=Something()', 'E520:') + call s:modeline_fails('pythondll', 'pythondll=Something()', 'E520:') + call s:modeline_fails('pythonhome', 'pythonhome=Something()', 'E520:') + call s:modeline_fails('pythonthreedll', 'pythonthreedll=Something()', 'E520:') + call s:modeline_fails('pythonthreehome', 'pythonthreehome=Something()', 'E520:') + call s:modeline_fails('pyxversion', 'pyxversion=Something()', 'E520:') + call s:modeline_fails('rubydll', 'rubydll=Something()', 'E520:') + call s:modeline_fails('runtimepath', 'runtimepath=Something()', 'E520:') + call s:modeline_fails('secure', 'secure=Something()', 'E520:') + call s:modeline_fails('shell', 'shell=Something()', 'E520:') + call s:modeline_fails('shellcmdflag', 'shellcmdflag=Something()', 'E520:') + call s:modeline_fails('shellpipe', 'shellpipe=Something()', 'E520:') + call s:modeline_fails('shellquote', 'shellquote=Something()', 'E520:') + call s:modeline_fails('shellredir', 'shellredir=Something()', 'E520:') + call s:modeline_fails('shellxquote', 'shellxquote=Something()', 'E520:') + call s:modeline_fails('spellfile', 'spellfile=Something()', 'E520:') + call s:modeline_fails('spellsuggest', 'spellsuggest=Something()', 'E520:') + call s:modeline_fails('tcldll', 'tcldll=Something()', 'E520:') + call s:modeline_fails('titleold', 'titleold=Something()', 'E520:') + call s:modeline_fails('viewdir', 'viewdir=Something()', 'E520:') + call s:modeline_fails('viminfo', 'viminfo=Something()', 'E520:') + call s:modeline_fails('viminfofile', 'viminfofile=Something()', 'E520:') + call s:modeline_fails('winptydll', 'winptydll=Something()', 'E520:') + call s:modeline_fails('undodir', 'undodir=Something()', 'E520:') + " only check a few terminal options + " Skip these since nvim doesn't support termcodes as options + "call s:modeline_fails('t_AB', 't_AB=Something()', 'E520:') + "call s:modeline_fails('t_ce', 't_ce=Something()', 'E520:') + "call s:modeline_fails('t_sr', 't_sr=Something()', 'E520:') + "call s:modeline_fails('t_8b', 't_8b=Something()', 'E520:') +endfunc + +func Test_modeline_fails_modelineexpr() + call s:modeline_fails('balloonexpr', 'balloonexpr=Something()', 'E992:') + call s:modeline_fails('foldexpr', 'foldexpr=Something()', 'E992:') + call s:modeline_fails('foldtext', 'foldtext=Something()', 'E992:') + call s:modeline_fails('formatexpr', 'formatexpr=Something()', 'E992:') + call s:modeline_fails('guitablabel', 'guitablabel=Something()', 'E992:') + call s:modeline_fails('iconstring', 'iconstring=Something()', 'E992:') + call s:modeline_fails('includeexpr', 'includeexpr=Something()', 'E992:') + call s:modeline_fails('indentexpr', 'indentexpr=Something()', 'E992:') + call s:modeline_fails('rulerformat', 'rulerformat=Something()', 'E992:') + call s:modeline_fails('statusline', 'statusline=Something()', 'E992:') + call s:modeline_fails('tabline', 'tabline=Something()', 'E992:') + call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:') +endfunc diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 5b16f6d205..c856fd720c 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1297,5 +1297,4 @@ endfunc "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker -" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "") "------------------------------------------------------------------------------- |