
CheckExecutable tar
CheckNotMSWindows

runtime plugin/tarPlugin.vim

func s:CopyFile(source)
  if !filecopy($"samples/{a:source}", "X.tar")
    call assert_report($"Can't copy samples/{a:source}")
  endif
endfunc

func Test_tar_basic()
  call s:CopyFile("sample.tar")
  defer delete("X.tar")
  defer delete("./testtar", 'rf')
  e X.tar

  "## Check header
  call assert_match('^" tar\.vim version v\d\+', getline(1))
  call assert_match('^" Browsing tarfile .*/X.tar', getline(2))
  call assert_match('^" Select a file with cursor and press ENTER, "x" to extract a file', getline(3))
  call assert_match('^$', getline(4))
  call assert_equal('testtar/', getline(5))
  call assert_equal('testtar/file1.txt', getline(6))

  "## Check ENTER on header
  :1
  exe ":normal \<cr>"
  call assert_equal("X.tar", @%)

  "## Check ENTER on file
  :6
  exe ":normal \<cr>"
  call assert_equal("tarfile::testtar/file1.txt", @%)


  "## Check editing file
  "## Note: deleting entries not supported on BSD
  if has("mac")
    return
  endif
  if has("bsd")
    return
  endif
  s/.*/some-content/
  call assert_equal("some-content", getline(1))
  w!
  call assert_equal("tarfile::testtar/file1.txt", @%)
  bw!
  close
  bw!

  e X.tar
  :6
  exe "normal \<cr>"
  call assert_equal("some-content", getline(1))
  bw!
  close

  "## Check extracting file
  :5
  normal x
  call assert_true(filereadable("./testtar/file1.txt"))
  bw!
endfunc

func Test_tar_evil()
  " On s390x, tar outputs its full path in warning messages (e.g. /usr/bin/tar: Removing leading '/')
  " which tar.vim doesn't handle, causing path traversal detection to fail.
  CheckNotS390
  call s:CopyFile("evil.tar")
  defer delete("X.tar")
  defer delete("./etc", 'rf')
  e X.tar

  "## Check header
  call assert_match('^" tar\.vim version v\d\+', getline(1))
  call assert_match('^" Browsing tarfile .*/X.tar', getline(2))
  call assert_match('^" Select a file with cursor and press ENTER, "x" to extract a file', getline(3))
  call assert_match('^" Note: Path Traversal Attack detected', getline(4))
  call assert_match('^$', getline(5))
  call assert_equal('/etc/ax-pwn', getline(6))

  "## Check ENTER on header
  :1
  exe ":normal \<cr>"
  call assert_equal("X.tar", @%)
  call assert_equal(1, b:leading_slash)

  "## Press x to extract
  :6
  let mess = execute(":normal x", '')
  call assert_match('(tar#Extract) Path Traversal Attack detected, not extracting!', mess)

  "## Check ENTER on file
  :6
  exe ":normal \<cr>"
  call assert_equal(1, b:leading_slash)
  call assert_equal("tarfile::/etc/ax-pwn", @%)


  "## Check editing file
  "## Note: deleting entries not supported on BSD
  if has("mac")
    return
  endif
  if has("bsd")
    return
  endif
  s/.*/none/
  call assert_equal("none", getline(1))
  w!
  call assert_equal(1, b:leading_slash)
  call assert_equal("tarfile::/etc/ax-pwn", @%)
  bw!
  close
  bw!

  " Writing was aborted
  e X.tar
  call assert_match('^" Note: Path Traversal Attack detected', getline(4))
  :6
  exe "normal \<cr>"
  call assert_equal("something", getline(1))
  bw!
  close

  "## Check extracting file
  :5
  normal x
  call assert_true(filereadable("./etc/ax-pwn"))

  bw!
endfunc

func Test_tar_path_traversal_with_nowrapscan()
  CheckNotS390
  call s:CopyFile("evil.tar")
  defer delete("X.tar")
  " Make sure we still find the tar warning (or leading slashes) even when
  " wrapscan is off
  set nowrapscan
  e X.tar

  "## Check header
  call assert_match('^" tar\.vim version v\d\+', getline(1))
  call assert_match('^" Browsing tarfile .*/X.tar', getline(2))
  call assert_match('^" Select a file with cursor and press ENTER, "x" to extract a file', getline(3))
  call assert_match('^" Note: Path Traversal Attack detected', getline(4))
  call assert_match('^$', getline(5))
  call assert_equal('/etc/ax-pwn', getline(6))

  call assert_equal(1, b:leading_slash)

  bw!
endfunc

func s:CreateTar(archivename, content, outputdir)
  let tempdir = tempname()
  call mkdir(tempdir, 'R')
  call writefile([a:content], tempdir .. '/X.txt')
  call assert_true(filereadable(tempdir .. '/X.txt'))
  call system('tar -C ' .. tempdir .. ' -cf ' .. a:outputdir .. '/' .. a:archivename .. ' X.txt')
  call assert_equal(0, v:shell_error)
endfunc

func s:CreateTgz(archivename, content, outputdir)
  let tempdir = tempname()
  call mkdir(tempdir, 'R')
  call writefile([a:content], tempdir .. '/X.txt')
  call assert_true(filereadable(tempdir .. '/X.txt'))
  call system('tar -C ' .. tempdir .. ' -czf ' .. a:outputdir .. '/' .. a:archivename .. ' X.txt')
  call assert_equal(0, v:shell_error)
endfunc

func s:CreateTbz(archivename, content, outputdir)
  let tempdir = tempname()
  call mkdir(tempdir, 'R')
  call writefile([a:content], tempdir .. '/X.txt')
  call assert_true(filereadable(tempdir .. '/X.txt'))
  call system('tar -C ' .. tempdir .. ' -cjf ' .. a:outputdir .. '/' .. a:archivename .. ' X.txt')
  call assert_equal(0, v:shell_error)
endfunc

func s:CreateTxz(archivename, content, outputdir)
  let tempdir = tempname()
  call mkdir(tempdir, 'R')
  call writefile([a:content], tempdir .. '/X.txt')
  call assert_true(filereadable(tempdir .. '/X.txt'))
  call system('tar -C ' .. tempdir .. ' -cJf ' .. a:outputdir .. '/' .. a:archivename .. ' X.txt')
  call assert_equal(0, v:shell_error)
endfunc

func s:CreateTzst(archivename, content, outputdir)
  let tempdir = tempname()
  call mkdir(tempdir, 'R')
  call writefile([a:content], tempdir .. '/X.txt')
  call assert_true(filereadable(tempdir .. '/X.txt'))
  call system('tar --zstd -C ' .. tempdir .. ' -cf ' .. a:outputdir .. '/' .. a:archivename .. ' X.txt')
  call assert_equal(0, v:shell_error)
endfunc

func s:CreateTlz4(archivename, content, outputdir)
  let tempdir = tempname()
  call mkdir(tempdir, 'R')
  call writefile([a:content], tempdir .. '/X.txt')
  call assert_true(filereadable(tempdir .. '/X.txt'))
  call system('tar -C ' .. tempdir .. ' -cf ' .. tempdir .. '/Xarchive.tar X.txt')
  call assert_equal(0, v:shell_error)
  call assert_true(filereadable(tempdir .. '/Xarchive.tar'))
  call system('lz4 -z ' .. tempdir .. '/Xarchive.tar ' .. a:outputdir .. '/' .. a:archivename)
  call assert_equal(0, v:shell_error)
endfunc

" XXX: Add test for .tar.bz3
func Test_extraction()
  let control = [
  \   #{create: function('s:CreateTar'),
  \     archive: 'Xarchive.tar'},
  \   #{create: function('s:CreateTgz'),
  \     archive: 'Xarchive.tgz'},
  \   #{create: function('s:CreateTgz'),
  \     archive: 'Xarchive.tar.gz'},
  \   #{create: function('s:CreateTbz'),
  \     archive: 'Xarchive.tbz'},
  \   #{create: function('s:CreateTbz'),
  \     archive: 'Xarchive.tar.bz2'},
  \   #{create: function('s:CreateTxz'),
  \     archive: 'Xarchive.txz'},
  \   #{create: function('s:CreateTxz'),
  \     archive: 'Xarchive.tar.xz'},
  \ ]

  if executable('lz4') == 1
    eval control->add(#{
    \   create: function('s:CreateTlz4'),
    \   archive: 'Xarchive.tar.lz4'
    \ })
    eval control->add(#{
    \   create: function('s:CreateTlz4'),
    \   archive: 'Xarchive.tlz4'
    \ })
  endif
  if executable('zstd') == 1
    eval control->add(#{
    \   create: function('s:CreateTzst'),
    \   archive: 'Xarchive.tar.zst'
    \ })
    eval control->add(#{
    \   create: function('s:CreateTzst'),
    \   archive: 'Xarchive.tzst'
    \ })
  endif

  for c in control
    let dir = tempname()
    call mkdir(dir, 'R')
    call call(c.create, [c.archive, 'hello', dir])

    call delete('X.txt')
    execute 'edit ' .. dir .. '/' .. c.archive
    call assert_equal('X.txt', getline(5), 'line 5 wrong in archive: ' .. c.archive)
    :5
    normal x
    call assert_equal(0, v:shell_error, 'vshell error not 0')
    call assert_true(filereadable('X.txt'), 'X.txt not readable for archive: ' .. c.archive)
    call assert_equal(['hello'], readfile('X.txt'), 'X.txt wrong contents for archive: ' .. c.archive)
    call delete('X.txt')
    call delete(dir .. '/' .. c.archive)
    bw!
  endfor
endfunc

func Test_extract_with_dotted_dir()
  call delete('X.txt')
  call writefile(['when they kiss they spit white noise'], 'X.txt')

  let dirname = tempname()
  call mkdir(dirname, 'R')
  let dirname = dirname .. '/foo.bar'
  call mkdir(dirname, 'R')
  let tarpath = dirname .. '/Xarchive.tar.gz'
  call system('tar -czf ' .. tarpath .. ' X.txt')
  call assert_true(filereadable(tarpath))
  call assert_equal(0, v:shell_error)

  call delete('X.txt')
  defer delete(tarpath)

  execute 'e ' .. tarpath
  call assert_equal('X.txt', getline(5))
  :5
  normal x
  call assert_true(filereadable('X.txt'))
  call assert_equal(['when they kiss they spit white noise'], readfile('X.txt'))
  call delete('X.txt')
  bw!
endfunc

func Test_extract_with_dotted_filename()
  call delete('X.txt')
  call writefile(['holiday inn'], 'X.txt')

  let dirname = tempname()
  call mkdir(dirname, 'R')
  let tarpath = dirname .. '/Xarchive.foo.tar.gz'
  call system('tar -czf ' .. tarpath .. ' X.txt')
  call assert_true(filereadable(tarpath))
  call assert_equal(0, v:shell_error)

  call delete('X.txt')
  defer delete(tarpath)

  execute 'e ' .. tarpath
  call assert_equal('X.txt', getline(5))
  :5
  normal x
  call assert_true(filereadable('X.txt'))
  call assert_equal(['holiday inn'], readfile('X.txt'))
  call delete('X.txt')
  bw!
endfunc
