view src/testdir/test_shell.vim @ 33776:9503dc55b5ed v9.0.2108

patch 9.0.2108: [security]: overflow with count for :s command Commit: https://github.com/vim/vim/commit/ac63787734fda2e294e477af52b3bd601517fa78 Author: Christian Brabandt <cb@256bit.org> Date: Tue Nov 14 20:45:48 2023 +0100 patch 9.0.2108: [security]: overflow with count for :s command Problem: [security]: overflow with count for :s command Solution: Abort the :s command if the count is too large If the count after the :s command is larger than what fits into a (signed) long variable, abort with e_value_too_large. Adds a test with INT_MAX as count and verify it correctly fails. It seems the return value on Windows using mingw compiler wraps around, so the initial test using :s/./b/9999999999999999999999999990 doesn't fail there, since the count is wrapping around several times and finally is no longer larger than 2147483647. So let's just use 2147483647 in the test, which hopefully will always cause a failure Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Thu, 16 Nov 2023 22:15:10 +0100
parents 30ea99dff3be
children 48d01e3323ca
line wrap: on
line source

" Test for the shell related options ('shell', 'shellcmdflag', 'shellpipe',
" 'shellquote', 'shellredir', 'shellxescape', and 'shellxquote')

source check.vim
source shared.vim

func Test_shell_options()
  if has('win32')
    " FIXME: This test is flaky on MS-Windows.
    let g:test_is_flaky = 1
  endif

  " The expected value of 'shellcmdflag', 'shellpipe', 'shellquote',
  " 'shellredir', 'shellxescape', 'shellxquote' for the supported shells.
  let shells = []
  if has('unix')
    let shells += [['sh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['ksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['mksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['zsh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['zsh-beta', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['bash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['fish', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['ash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['dash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['csh', '-c', '|& tee', '', '>&', '', ''],
          \ ['tcsh', '-c', '|& tee', '', '>&', '', ''],
          \ ['pwsh', '-c', '>%s 2>&1', '', '>%s 2>&1', '', '']]
  endif
  if has('win32')
    let shells += [['cmd', '/c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', ''],
          \ ['cmd.exe', '/c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '('],
          \ ['powershell.exe', '-Command', '2>&1 | Out-File -Encoding default',
          \           '', '2>&1 | Out-File -Encoding default', '"&|<>()@^', '"'],
          \ ['powershell', '-Command', '2>&1 | Out-File -Encoding default', '',
          \               '2>&1 | Out-File -Encoding default', '"&|<>()@^', '"'],
          \ ['pwsh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['pwsh', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['sh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['ksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['mksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['pdksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['zsh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['zsh-beta.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['bash.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['dash.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['csh.exe', '-c', '>&', '', '>&', '"&|<>()@^', '"'],
          \ ['tcsh.exe', '-c', '>&', '', '>&', '"&|<>()@^', '"']]
  endif

  " start a new Vim instance with 'shell' set to each of the supported shells
  " and check the default shell option settings
  let after =<< trim END
    let l = [&shell, &shellcmdflag, &shellpipe, &shellquote]
    let l += [&shellredir, &shellxescape, &shellxquote]
    call writefile([json_encode(l)], 'Xtestout')
    qall!
  END
  for e in shells
    if RunVim([], after, '--cmd "set shell=' .. e[0] .. '"')
      call assert_equal(e, json_decode(readfile('Xtestout')[0]))
    endif
  endfor

  " Test shellescape() for each of the shells.
  for e in shells
    exe 'set shell=' .. e[0]
    if e[0] =~# '.*csh$' || e[0] =~# '.*csh.exe$'
      let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%# \\'\\'' \\\\! \\% \\#'"
      let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\# \\'\\'' \\\\\\! \\\\% \\\\#'"
    elseif e[0] =~# '.*powershell$' || e[0] =~# '.*powershell.exe$'
          \ || e[0] =~# '.*pwsh$' || e[0] =~# '.*pwsh.exe$'
      let str1 = "'cmd \"arg1\" ''arg2'' !%# \\'' \\! \\% \\#'"
      let str2 = "'cmd \"arg1\" ''arg2'' \\!\\%\\# \\'' \\\\! \\\\% \\\\#'"
    elseif e[0] =~# '.*fish$' || e[0] =~# '.*fish.exe$'
      let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%# \\\\'\\'' \\\\! \\\\% \\\\#'"
      let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\# \\\\'\\'' \\\\\\! \\\\\\% \\\\\\#'"
    else
      let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%# \\'\\'' \\! \\% \\#'"
      let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\# \\'\\'' \\\\! \\\\% \\\\#'"
    endif
    call assert_equal(str1, shellescape("cmd \"arg1\" 'arg2' !%# \\' \\! \\% \\#"), e[0])
    call assert_equal(str2, shellescape("cmd \"arg1\" 'arg2' !%# \\' \\! \\% \\#", 1), e[0])

    " Try running an external command with the shell.
    if executable(e[0])
      " set the shell options for the current 'shell'
      let [&shellcmdflag, &shellpipe, &shellquote, &shellredir,
            \ &shellxescape, &shellxquote] = e[1:6]
      new
      try
        r !echo hello
        call assert_equal('hello', substitute(getline(2), '\W', '', 'g'), e[0])
      catch
        call assert_report('Failed to run shell command, shell: ' .. e[0]
              \ .. ', caught ' .. v:exception)
      finally
        bwipe!
      endtry

      " filter buffer contents through an external command
      new
      call setline(1, ['tom', 'sam', 'andy'])
      try
        %!sort
        call assert_equal(['andy', 'sam', 'tom'], getline(1, '$'), e[0])
      catch
        call assert_report($'Failed to filter buffer contents, shell: {e[0]}, caught {v:exception}')
      finally
        bwipe!
      endtry
    endif
  endfor
  set shell& shellcmdflag& shellpipe& shellquote&
  set shellredir& shellxescape& shellxquote&
  call delete('Xtestout')
endfunc

" Test for the 'shell' option
func Test_shell()
  CheckUnix
  let save_shell = &shell
  set shell=
  let caught_e91 = 0
  try
    shell
  catch /E91:/
    let caught_e91 = 1
  endtry
  call assert_equal(1, caught_e91)
  let &shell = save_shell
endfunc

" Test for the 'shellquote' option
func Test_shellquote()
  CheckUnix
  set shellquote=#
  set verbose=20
  redir => v
  silent! !echo Hello
  redir END
  set verbose&
  set shellquote&
  call assert_match(': "#echo Hello#"', v)
endfunc

" Test for the 'shellescape' option
func Test_shellescape()
  let save_shell = &shell
  set shell=bash
  call assert_equal("'text'", shellescape('text'))
  call assert_equal("'te\"xt'", 'te"xt'->shellescape())
  call assert_equal("'te'\\''xt'", shellescape("te'xt"))

  call assert_equal("'te%xt'", shellescape("te%xt"))
  call assert_equal("'te\\%xt'", shellescape("te%xt", 1))
  call assert_equal("'te#xt'", shellescape("te#xt"))
  call assert_equal("'te\\#xt'", shellescape("te#xt", 1))
  call assert_equal("'te!xt'", shellescape("te!xt"))
  call assert_equal("'te\\!xt'", shellescape("te!xt", 1))

  call assert_equal("'te\nxt'", shellescape("te\nxt"))
  call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1))
  set shell=tcsh
  call assert_equal("'te\\!xt'", shellescape("te!xt"))
  call assert_equal("'te\\\\!xt'", shellescape("te!xt", 1))
  call assert_equal("'te\\\nxt'", shellescape("te\nxt"))
  call assert_equal("'te\\\\\nxt'", shellescape("te\nxt", 1))

  let &shell = save_shell
endfunc

" Test for 'shellslash'
func Test_shellslash()
  CheckOption shellslash
  let save_shellslash = &shellslash
  " The shell and cmdflag, and expected slash in tempname with shellslash set or
  " unset.  The assert checks the file separator before the leafname.
  " ".*\\\\[^\\\\]*$"
  let shells = [['cmd', '/c', '\\', '/'],
        \ ['powershell', '-Command', '\\', '/'],
        \ ['pwsh', '-Command', '\\', '/'],
        \ ['pwsh', '-c', '\\', '/'],
        \ ['sh', '-c', '/', '/']]
  for e in shells
    exe 'set shell=' .. e[0] .. ' | set shellcmdflag=' .. e[1]
    set noshellslash
    let file = tempname()
    call assert_match('^.\+' .. e[2] .. '[^' .. e[2] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' nossl')
    set shellslash
    let file = tempname()
    call assert_match('^.\+' .. e[3] .. '[^' .. e[3] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' ssl')
  endfor
  let &shellslash = save_shellslash
endfunc

" Test for 'shellxquote'
func Test_shellxquote()
  CheckUnix

  let save_shell = &shell
  let save_sxq = &shellxquote
  let save_sxe = &shellxescape

  call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell', 'D')
  call setfperm('Xtestshell', "r-x------")
  set shell=./Xtestshell

  set shellxquote=\\"
  call feedkeys(":!pwd\<CR>\<CR>", 'xt')
  call assert_equal(['Cmd: [-c "pwd"]'], readfile('Xlog'))

  set shellxquote=(
  call feedkeys(":!pwd\<CR>\<CR>", 'xt')
  call assert_equal(['Cmd: [-c (pwd)]'], readfile('Xlog'))

  set shellxquote=\\"(
  call feedkeys(":!pwd\<CR>\<CR>", 'xt')
  call assert_equal(['Cmd: [-c "(pwd)"]'], readfile('Xlog'))

  set shellxescape=\"&<<()@^
  set shellxquote=(
  call feedkeys(":!pwd\"&<<{}@^\<CR>\<CR>", 'xt')
  call assert_equal(['Cmd: [-c (pwd^"^&^<^<{}^@^^)]'], readfile('Xlog'))

  let &shell = save_shell
  let &shellxquote = save_sxq
  let &shellxescape = save_sxe
  call delete('Xlog')
endfunc

" Test for using the shell set in the $SHELL environment variable
func Test_set_shell()
  let after =<< trim [CODE]
    call writefile([&shell], "Xtestout")
    quit!
  [CODE]

  if has('win32')
    let $SHELL = 'C:\with space\cmd.exe'
    let expected = '"C:\with space\cmd.exe"'
  else
    let $SHELL = '/bin/with space/sh'
    let expected = '/bin/with\ space/sh'
  endif

  if RunVimPiped([], after, '', '')
    let lines = readfile('Xtestout')
    call assert_equal(expected, lines[0])
  endif
  call delete('Xtestout')
endfunc

func Test_shell_repeat()
  CheckUnix

  let save_shell = &shell

  call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell', 'D')
  call setfperm('Xtestshell', "r-x------")
  set shell=./Xtestshell
  defer delete('Xlog')

  call feedkeys(":!echo coconut\<CR>", 'xt')   " Run command
  call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))

  call feedkeys(":!!\<CR>", 'xt')              " Re-run previous
  call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))

  call writefile(['empty'], 'Xlog')
  call feedkeys(":!\<CR>", 'xt')               " :!
  call assert_equal(['Cmd: [-c ]'], readfile('Xlog'))

  call feedkeys(":!!\<CR>", 'xt')              " :! doesn't clear previous command
  call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))

  call feedkeys(":!echo banana\<CR>", 'xt')    " Make sure setting previous command keeps working after a :! no-op
  call assert_equal(['Cmd: [-c echo banana]'], readfile('Xlog'))
  call feedkeys(":!!\<CR>", 'xt')
  call assert_equal(['Cmd: [-c echo banana]'], readfile('Xlog'))

  let &shell = save_shell
endfunc

func Test_shell_no_prevcmd()
  " this doesn't do anything, just check it doesn't crash
  let after =<< trim END
    exe "normal !!\<CR>"
    call writefile([v:errmsg, 'done'], 'Xtestdone')
    qall!
  END
  if RunVim([], after, '--clean')
    call assert_equal(['E34: No previous command', 'done'], readfile('Xtestdone'))
  endif
  call delete('Xtestdone')
endfunc

" vim: shiftwidth=2 sts=2 expandtab