Files
neovim/test/old/testdir/test_plugin_tar.vim
zeertzjq 056304ef5b vim-patch:9.2.0306: runtime(tar): some issues with lz4 support (#38826)
Problem:  runtime(tar): some issues with lz4 support
Solution: Fix bugs (see below) (Aaron Burrow)

The tar plugin allows users to extract files from tar archives that are
compressed with lz4. But, tar#Extract() builds malformed extraction commands
for lz4-compressed tar archives. This commit fixes three issues in that code.
The first affects archives with a .tlz4 extension and the other two affect
archives with .tar.lz4 extension (but one of these is symmetric to the issue
that .tlz4 archives had).

(1) When trying to extract .tlz4 archives the command created by
tar#Extract looked like this:

    tar -I lz4pxf foo.tlz4 foo

This isn't right.  It should be something like this:

    tar -I lz4 -pxf foo.tlz4 foo

This was happening because tar.plugin is just substituting on the
first - in "tar -pxf".  This works fine if we just add a simple flag for
extraction (eg, z for .tgz), but for lz4 we need to add "-I lz4".

I don't believe that there is an obvious good way to fix this without
reworking the way the command is generated.  Probably we should collect
the command and flags separately and the flags should be stored in a
set. Then put everything together into a string just before issuing it
as an extraction command.  Unfortunately, this might break things for users
because they have access to tar_extractcmd.

This patch just makes the substitution a little bit more clever so that it
does the right thing when substituting on a string like "tar -pxf".

(2) .tar.lz4 extractions had the same issue, which my patch fixes in
the same way.

(3) .tar.lz4 extractions had another issue.  There was a space missing
in the command generated by tar#Extract.  This meant that commands
looked like this (notice the lack of space between the archive and output
file names):

    tar -I lz4pxf foo.tar.lz4foo

This patch just puts a space where it should be.

Finally, I should note that ChatGPT 5.4 initially identified this issue
in the code and generated the test cases.  I reviewed the test cases,
wrote the patch, and actually ran vim against the tests (both with and
without the patch).

closes: vim/vim#19925

78954f86c2

Co-authored-by: Aaron Burrow <burrows@fastmail.com>
2026-04-06 13:43:28 +00:00

207 lines
4.9 KiB
VimL

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_match('testtar/', getline(5))
call assert_match('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_match('/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)
"## 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_match('/etc/ax-pwn', getline(6))
call assert_equal(1, b:leading_slash)
bw!
endfunc
func Test_tar_lz4_extract()
CheckExecutable lz4
call delete('X.txt')
call delete('Xarchive.tar')
call delete('Xarchive.tar.lz4')
call writefile(['hello'], 'X.txt')
call system('tar -cf Xarchive.tar X.txt')
call assert_equal(0, v:shell_error)
call system('lz4 -z Xarchive.tar Xarchive.tar.lz4')
call assert_equal(0, v:shell_error)
call delete('X.txt')
call delete('Xarchive.tar')
defer delete('Xarchive.tar.lz4')
e Xarchive.tar.lz4
call assert_match('X.txt', getline(5))
:5
normal x
call assert_true(filereadable('X.txt'))
call assert_equal(['hello'], readfile('X.txt'))
call delete('X.txt')
bw!
endfunc
func Test_tlz4_extract()
CheckExecutable lz4
call delete('X.txt')
call delete('Xarchive.tar')
call delete('Xarchive.tlz4')
call writefile(['goodbye'], 'X.txt')
call system('tar -cf Xarchive.tar X.txt')
call assert_equal(0, v:shell_error)
call system('lz4 -z Xarchive.tar Xarchive.tlz4')
call assert_equal(0, v:shell_error)
call delete('X.txt')
call delete('Xarchive.tar')
defer delete('Xarchive.tlz4')
e Xarchive.tlz4
call assert_match('X.txt', getline(5))
:5
normal x
call assert_true(filereadable('X.txt'))
call assert_equal(['goodbye'], readfile('X.txt'))
call delete('X.txt')
bw!
endfunc