mirror of
https://github.com/neovim/neovim.git
synced 2026-04-15 03:52:54 +00:00
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>
787 lines
27 KiB
VimL
787 lines
27 KiB
VimL
" tar.vim: Handles browsing tarfiles - AUTOLOAD PORTION
|
|
" Date: Mar 01, 2025
|
|
" Version: 32b (with modifications from the Vim Project)
|
|
" Maintainer: This runtime file is looking for a new maintainer.
|
|
" Former Maintainer: Charles E Campbell
|
|
" License: Vim License (see vim's :help license)
|
|
" Last Change:
|
|
" 2024 Jan 08 by Vim Project: fix a few problems (#138331, #12637, #8109)
|
|
" 2024 Feb 19 by Vim Project: announce adoption
|
|
" 2024 Nov 11 by Vim Project: support permissions (#7379)
|
|
" 2025 Feb 06 by Vim Project: add support for lz4 (#16591)
|
|
" 2025 Feb 28 by Vim Project: add support for bzip3 (#16755)
|
|
" 2025 Mar 01 by Vim Project: fix syntax error in tar#Read()
|
|
" 2025 Mar 02 by Vim Project: escape the filename before using :read
|
|
" 2025 Mar 02 by Vim Project: determine the compression using readblob()
|
|
" instead of shelling out to file(1)
|
|
" 2025 Apr 16 by Vim Project: decouple from netrw by adding s:WinPath()
|
|
" 2025 May 19 by Vim Project: restore working directory after read/write
|
|
" 2025 Jul 13 by Vim Project: warn with path traversal attacks
|
|
" 2026 Feb 06 by Vim Project: consider 'nowrapscan' (#19333)
|
|
" 2026 Feb 07 by Vim Project: make the path traversal detection more robust (#19341)
|
|
" 2026 Apr 06 by Vim Project: fix bugs with lz4 support (#19925)
|
|
"
|
|
" Contains many ideas from Michael Toren's <tar.vim>
|
|
"
|
|
" Copyright: Copyright (C) 2005-2017 Charles E. Campbell {{{1
|
|
" Permission is hereby granted to use and distribute this code,
|
|
" with or without modifications, provided that this copyright
|
|
" notice is copied with it. Like anything else that's free,
|
|
" tar.vim and tarPlugin.vim are provided *as is* and comes
|
|
" with no warranty of any kind, either expressed or implied.
|
|
" By using this plugin, you agree that in no event will the
|
|
" copyright holder be liable for any damages resulting from
|
|
" the use of this software.
|
|
" ---------------------------------------------------------------------
|
|
" Load Once: {{{1
|
|
if &cp || exists("g:loaded_tar")
|
|
finish
|
|
endif
|
|
let g:loaded_tar= "v32b"
|
|
if !has('nvim-0.12') && v:version < 900
|
|
echohl WarningMsg
|
|
echo "***warning*** this version of tar needs vim 9.0"
|
|
echohl Normal
|
|
finish
|
|
endif
|
|
let s:keepcpo= &cpo
|
|
set cpo&vim
|
|
|
|
" ---------------------------------------------------------------------
|
|
" Default Settings: {{{1
|
|
if !exists("g:tar_browseoptions")
|
|
let g:tar_browseoptions= "tf"
|
|
endif
|
|
if !exists("g:tar_readoptions")
|
|
let g:tar_readoptions= "pxf"
|
|
endif
|
|
if !exists("g:tar_cmd")
|
|
let g:tar_cmd= "tar"
|
|
endif
|
|
if !exists("g:tar_writeoptions")
|
|
let g:tar_writeoptions= "uf"
|
|
endif
|
|
if !exists("g:tar_delfile")
|
|
" Note: not supported on BSD
|
|
let g:tar_delfile="--delete -f"
|
|
endif
|
|
if !exists("g:netrw_cygwin")
|
|
if has("win32") || has("win95") || has("win64") || has("win16")
|
|
if &shell =~ '\%(\<bash\>\|\<zsh\>\)\%(\.exe\)\=$'
|
|
let g:netrw_cygwin= 1
|
|
else
|
|
let g:netrw_cygwin= 0
|
|
endif
|
|
else
|
|
let g:netrw_cygwin= 0
|
|
endif
|
|
endif
|
|
if !exists("g:tar_copycmd")
|
|
if !exists("g:netrw_localcopycmd")
|
|
if has("win32") || has("win95") || has("win64") || has("win16")
|
|
if g:netrw_cygwin
|
|
let g:netrw_localcopycmd= "cp"
|
|
else
|
|
let g:netrw_localcopycmd= "copy"
|
|
endif
|
|
elseif has("unix") || has("macunix")
|
|
let g:netrw_localcopycmd= "cp"
|
|
else
|
|
let g:netrw_localcopycmd= ""
|
|
endif
|
|
endif
|
|
let g:tar_copycmd= g:netrw_localcopycmd
|
|
endif
|
|
if !exists("g:tar_extractcmd")
|
|
let g:tar_extractcmd= "tar -pxf"
|
|
endif
|
|
|
|
" set up shell quoting character
|
|
if !exists("g:tar_shq")
|
|
if exists("+shq") && exists("&shq") && &shq != ""
|
|
let g:tar_shq= &shq
|
|
elseif has("win32") || has("win95") || has("win64") || has("win16")
|
|
if exists("g:netrw_cygwin") && g:netrw_cygwin
|
|
let g:tar_shq= "'"
|
|
else
|
|
let g:tar_shq= '"'
|
|
endif
|
|
else
|
|
let g:tar_shq= "'"
|
|
endif
|
|
endif
|
|
|
|
let g:tar_secure=' -- '
|
|
let g:tar_leading_pat='\m^\%([.]\{,2\}/\)\+'
|
|
|
|
" ----------------
|
|
" Functions: {{{1
|
|
" ----------------
|
|
|
|
" ---------------------------------------------------------------------
|
|
" s:Msg: {{{2
|
|
fun! s:Msg(func, severity, msg)
|
|
redraw!
|
|
if a:severity =~? 'error'
|
|
echohl Error
|
|
else
|
|
echohl WarningMsg
|
|
endif
|
|
echo $"***{a:severity}*** ({a:func}) {a:msg}"
|
|
echohl None
|
|
endfunc
|
|
|
|
" ---------------------------------------------------------------------
|
|
" tar#Browse: {{{2
|
|
fun! tar#Browse(tarfile)
|
|
let repkeep= &report
|
|
set report=10
|
|
|
|
" sanity checks
|
|
if !executable(g:tar_cmd)
|
|
call s:Msg('tar#Browse', 'error', $"{g:tar_cmd} not available on your system")
|
|
let &report= repkeep
|
|
return
|
|
endif
|
|
if !filereadable(a:tarfile)
|
|
if a:tarfile !~# '^\a\+://'
|
|
" if it's an url, don't complain, let url-handlers such as vim do its thing
|
|
call s:Msg('tar#Browse', 'error', $"File not readable<{a:tarfile}>")
|
|
endif
|
|
let &report= repkeep
|
|
return
|
|
endif
|
|
if &ma != 1
|
|
set ma
|
|
endif
|
|
let b:tarfile= a:tarfile
|
|
|
|
setlocal noswapfile
|
|
setlocal buftype=nofile
|
|
setlocal bufhidden=hide
|
|
setlocal nobuflisted
|
|
setlocal nowrap
|
|
set ft=tar
|
|
|
|
" give header
|
|
let lastline= line("$")
|
|
call setline(lastline+1,'" tar.vim version '.g:loaded_tar)
|
|
call setline(lastline+2,'" Browsing tarfile '.a:tarfile)
|
|
call setline(lastline+3,'" Select a file with cursor and press ENTER, "x" to extract a file')
|
|
keepj $put =''
|
|
keepj sil! 0d
|
|
keepj $
|
|
|
|
let tarfile= a:tarfile
|
|
if has("win32unix") && executable("cygpath")
|
|
" assuming cygwin
|
|
let tarfile=substitute(system("cygpath -u ".shellescape(tarfile,0)),'\n$','','e')
|
|
endif
|
|
let curlast= line("$")
|
|
|
|
if tarfile =~# '\.\(gz\)$'
|
|
exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
|
|
elseif tarfile =~# '\.\(tgz\)$' || tarfile =~# '\.\(tbz\)$' || tarfile =~# '\.\(txz\)$' ||
|
|
\ tarfile =~# '\.\(tzst\)$' || tarfile =~# '\.\(tlz4\)$'
|
|
let header= s:Header(tarfile)
|
|
|
|
if header =~? 'bzip2'
|
|
exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif header =~? 'bzip3'
|
|
exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif header =~? 'xz'
|
|
exe "sil! r! xz -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif header =~? 'zstd'
|
|
exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif header =~? 'lz4'
|
|
exe "sil! r! lz4 --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif header =~? 'gzip'
|
|
exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
endif
|
|
|
|
elseif tarfile =~# '\.lrp'
|
|
exe "sil! r! cat -- ".shellescape(tarfile,1)."|gzip -d -c -|".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif tarfile =~# '\.\(bz2\|tbz\|tb2\)$'
|
|
exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif tarfile =~# '\.\(bz3\|tb3\)$'
|
|
exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif tarfile =~# '\.\(lzma\|tlz\)$'
|
|
exe "sil! r! lzma -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif tarfile =~# '\.\(xz\|txz\)$'
|
|
exe "sil! r! xz --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif tarfile =~# '\.\(zst\|tzst\)$'
|
|
exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
elseif tarfile =~# '\.\(lz4\|tlz4\)$'
|
|
exe "sil! r! lz4 --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
|
|
else
|
|
if tarfile =~ '^\s*-'
|
|
" A file name starting with a dash is taken as an option. Prepend ./ to avoid that.
|
|
let tarfile = substitute(tarfile, '-', './-', '')
|
|
endif
|
|
exe "sil! r! ".g:tar_cmd." -".g:tar_browseoptions." ".shellescape(tarfile,1)
|
|
endif
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Browse', 'warning', $"please check your g:tar_browseoptions '<{g:tar_browseoptions}>'")
|
|
return
|
|
endif
|
|
|
|
" remove tar: Removing leading '/' from member names
|
|
" Note: the message could be localized
|
|
if search('\m^g\?tar: ', 'w') > 0 || search(g:tar_leading_pat, 'w') > 0
|
|
call append(3,'" Note: Path Traversal Attack detected!')
|
|
let b:leading_slash = 1
|
|
" remove the message output
|
|
sil g/^tar: /d
|
|
endif
|
|
|
|
" set up maps supported for tar
|
|
setlocal noma nomod ro
|
|
noremap <silent> <buffer> <cr> :call <SID>TarBrowseSelect()<cr>
|
|
noremap <silent> <buffer> x :call tar#Extract()<cr>
|
|
if &mouse != ""
|
|
noremap <silent> <buffer> <leftmouse> <leftmouse>:call <SID>TarBrowseSelect()<cr>
|
|
endif
|
|
|
|
let &report= repkeep
|
|
endfun
|
|
|
|
" ---------------------------------------------------------------------
|
|
" TarBrowseSelect: {{{2
|
|
fun! s:TarBrowseSelect()
|
|
let repkeep= &report
|
|
set report=10
|
|
let fname= getline(".")
|
|
let ls= get(b:, 'leading_slash', 0)
|
|
|
|
" sanity check
|
|
if fname =~ '^"'
|
|
let &report= repkeep
|
|
return
|
|
endif
|
|
|
|
" about to make a new window, need to use b:tarfile
|
|
let tarfile= b:tarfile
|
|
let curfile= expand("%")
|
|
if has("win32unix") && executable("cygpath")
|
|
" assuming cygwin
|
|
let tarfile=substitute(system("cygpath -u ".shellescape(tarfile,0)),'\n$','','e')
|
|
endif
|
|
|
|
" open a new window (tar#Read will read a file into it)
|
|
noswapfile new
|
|
if !exists("g:tar_nomax") || g:tar_nomax == 0
|
|
wincmd _
|
|
endif
|
|
let s:tblfile_{winnr()}= curfile
|
|
let b:leading_slash= ls
|
|
call tar#Read("tarfile:".tarfile.'::'.fname)
|
|
filetype detect
|
|
set nomod
|
|
exe 'com! -buffer -nargs=? -complete=file TarDiff :call tar#Diff(<q-args>,"'.fnameescape(fname).'")'
|
|
|
|
let &report= repkeep
|
|
endfun
|
|
|
|
" ---------------------------------------------------------------------
|
|
" tar#Read: {{{2
|
|
fun! tar#Read(fname)
|
|
let repkeep= &report
|
|
set report=10
|
|
let tarfile = substitute(a:fname,'tarfile:\(.\{-}\)::.*$','\1','')
|
|
let fname = substitute(a:fname,'tarfile:.\{-}::\(.*\)$','\1','')
|
|
" be careful not to execute special crafted files
|
|
let escape_file = fname->substitute(g:tar_leading_pat, '', '')->fnameescape()
|
|
|
|
let curdir= getcwd()
|
|
let b:curdir= curdir
|
|
let tmpdir= tempname()
|
|
let b:tmpdir= tmpdir
|
|
if tmpdir =~ '\.'
|
|
let tmpdir= substitute(tmpdir,'\.[^.]*$','','e')
|
|
endif
|
|
call mkdir(tmpdir,"p")
|
|
|
|
" attempt to change to the indicated directory
|
|
try
|
|
exe "lcd ".fnameescape(tmpdir)
|
|
catch /^Vim\%((\a\+)\)\=:E344/
|
|
call s:Msg('tar#Read', 'error', "cannot lcd to temporary directory")
|
|
let &report= repkeep
|
|
return
|
|
endtry
|
|
|
|
" place temporary files under .../_ZIPVIM_/
|
|
if isdirectory("_ZIPVIM_")
|
|
call s:Rmdir("_ZIPVIM_")
|
|
endif
|
|
call mkdir("_ZIPVIM_")
|
|
lcd _ZIPVIM_
|
|
|
|
if has("win32unix") && executable("cygpath")
|
|
" assuming cygwin
|
|
let tarfile=substitute(system("cygpath -u ".shellescape(tarfile,0)),'\n$','','e')
|
|
endif
|
|
|
|
if fname =~ '\.bz2$' && executable("bzcat")
|
|
let decmp= "|bzcat"
|
|
let doro = 1
|
|
elseif fname =~ '\.bz3$' && executable("bz3cat")
|
|
let decmp= "|bz3cat"
|
|
let doro = 1
|
|
elseif fname =~ '\.t\=gz$' && executable("zcat")
|
|
let decmp= "|zcat"
|
|
let doro = 1
|
|
elseif fname =~ '\.lzma$' && executable("lzcat")
|
|
let decmp= "|lzcat"
|
|
let doro = 1
|
|
elseif fname =~ '\.xz$' && executable("xzcat")
|
|
let decmp= "|xzcat"
|
|
let doro = 1
|
|
elseif fname =~ '\.zst$' && executable("zstdcat")
|
|
let decmp= "|zstdcat"
|
|
let doro = 1
|
|
elseif fname =~ '\.lz4$' && executable("lz4cat")
|
|
let decmp= "|lz4cat"
|
|
let doro = 1
|
|
else
|
|
let decmp=""
|
|
let doro = 0
|
|
if fname =~ '\.bz2$\|\.bz3$\|\.gz$\|\.lzma$\|\.xz$\|\.zip$\|\.Z$'
|
|
setlocal bin
|
|
endif
|
|
endif
|
|
|
|
|
|
if tarfile =~# '\.bz2$'
|
|
exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
elseif tarfile =~# '\.bz3$'
|
|
exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
elseif tarfile =~# '\.\(gz\)$'
|
|
exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
elseif tarfile =~# '\(\.tgz\|\.tbz\|\.txz\)'
|
|
let filekind= s:Header(tarfile)
|
|
if filekind =~? "bzip2"
|
|
exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
elseif filekind =~ "bzip3"
|
|
exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
elseif filekind =~? "xz"
|
|
exe "sil! r! xz -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
elseif filekind =~? "zstd"
|
|
exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
elseif filekind =~? "gzip"
|
|
exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
endif
|
|
|
|
elseif tarfile =~# '\.lrp$'
|
|
exe "sil! r! cat -- ".shellescape(tarfile,1)." | gzip -d -c - | ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
elseif tarfile =~# '\.lzma$'
|
|
exe "sil! r! lzma -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
elseif tarfile =~# '\.\(xz\|txz\)$'
|
|
exe "sil! r! xz --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
elseif tarfile =~# '\.\(lz4\|tlz4\)$'
|
|
exe "sil! r! lz4 --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
else
|
|
if tarfile =~ '^\s*-'
|
|
" A file name starting with a dash is taken as an option. Prepend ./ to avoid that.
|
|
let tarfile = substitute(tarfile, '-', './-', '')
|
|
endif
|
|
exe "silent r! ".g:tar_cmd." -".g:tar_readoptions.shellescape(tarfile,1)." ".g:tar_secure.shellescape(fname,1).decmp
|
|
exe "read ".escape_file
|
|
endif
|
|
if get(b:, 'leading_slash', 0)
|
|
sil g/^tar: /d
|
|
endif
|
|
|
|
redraw!
|
|
|
|
if v:shell_error != 0
|
|
lcd ..
|
|
call s:Rmdir("_ZIPVIM_")
|
|
exe "lcd ".fnameescape(curdir)
|
|
call s:Msg('tar#Read', 'error', $"sorry, unable to open or extract {tarfile} with {fname}")
|
|
endif
|
|
|
|
if doro
|
|
" because the reverse process of compressing changed files back into the tarball is not currently supported
|
|
setlocal ro
|
|
endif
|
|
|
|
let b:tarfile= a:fname
|
|
|
|
" cleanup
|
|
keepj sil! 0d
|
|
set nomod
|
|
|
|
let &report= repkeep
|
|
exe "lcd ".fnameescape(curdir)
|
|
silent exe "file tarfile::". fname->fnameescape()
|
|
endfun
|
|
|
|
" ---------------------------------------------------------------------
|
|
" tar#Write: {{{2
|
|
fun! tar#Write(fname)
|
|
let pwdkeep= getcwd()
|
|
let repkeep= &report
|
|
set report=10
|
|
let curdir= b:curdir
|
|
let tmpdir= b:tmpdir
|
|
|
|
" sanity checks
|
|
if !executable(g:tar_cmd)
|
|
redraw!
|
|
let &report= repkeep
|
|
return
|
|
endif
|
|
let tarfile = substitute(b:tarfile,'tarfile:\(.\{-}\)::.*$','\1','')
|
|
let fname = substitute(b:tarfile,'tarfile:.\{-}::\(.*\)$','\1','')
|
|
|
|
if get(b:, 'leading_slash', 0)
|
|
call s:Msg('tar#Write', 'error', $"sorry, not attempting to update {tarfile} with {fname}")
|
|
let &report= repkeep
|
|
return
|
|
endif
|
|
|
|
if !isdirectory(fnameescape(tmpdir))
|
|
call mkdir(fnameescape(tmpdir), 'p')
|
|
endif
|
|
exe $"lcd {fnameescape(tmpdir)}"
|
|
if isdirectory("_ZIPVIM_")
|
|
call s:Rmdir("_ZIPVIM_")
|
|
endif
|
|
call mkdir("_ZIPVIM_")
|
|
lcd _ZIPVIM_
|
|
let dir = fnamemodify(fname, ':p:h')
|
|
if dir !~# '_ZIPVIM_$'
|
|
call mkdir(dir)
|
|
endif
|
|
|
|
" handle compressed archives
|
|
if tarfile =~# '\.bz2'
|
|
call system("bzip2 -d -- ".shellescape(tarfile,0))
|
|
let tarfile = substitute(tarfile,'\.bz2','','e')
|
|
let compress= "bzip2 -- ".shellescape(tarfile,0)
|
|
elseif tarfile =~# '\.bz3'
|
|
call system("bzip3 -d -- ".shellescape(tarfile,0))
|
|
let tarfile = substitute(tarfile,'\.bz3','','e')
|
|
let compress= "bzip3 -- ".shellescape(tarfile,0)
|
|
elseif tarfile =~# '\.gz'
|
|
call system("gzip -d -- ".shellescape(tarfile,0))
|
|
let tarfile = substitute(tarfile,'\.gz','','e')
|
|
let compress= "gzip -- ".shellescape(tarfile,0)
|
|
elseif tarfile =~# '\.tgz'
|
|
call system("gzip -d -- ".shellescape(tarfile,0))
|
|
let tarfile = substitute(tarfile,'\.tgz','.tar','e')
|
|
let compress= "gzip -- ".shellescape(tarfile,0)
|
|
let tgz = 1
|
|
elseif tarfile =~# '\.xz'
|
|
call system("xz -d -- ".shellescape(tarfile,0))
|
|
let tarfile = substitute(tarfile,'\.xz','','e')
|
|
let compress= "xz -- ".shellescape(tarfile,0)
|
|
elseif tarfile =~# '\.zst'
|
|
call system("zstd --decompress --rm -- ".shellescape(tarfile,0))
|
|
let tarfile = substitute(tarfile,'\.zst','','e')
|
|
let compress= "zstd --rm -- ".shellescape(tarfile,0)
|
|
elseif tarfile =~# '\.lz4'
|
|
call system("lz4 --decompress --rm -- ".shellescape(tarfile,0))
|
|
let tarfile = substitute(tarfile,'\.lz4','','e')
|
|
let compress= "lz4 --rm -- ".shellescape(tarfile,0)
|
|
elseif tarfile =~# '\.lzma'
|
|
call system("lzma -d -- ".shellescape(tarfile,0))
|
|
let tarfile = substitute(tarfile,'\.lzma','','e')
|
|
let compress= "lzma -- ".shellescape(tarfile,0)
|
|
endif
|
|
" Note: no support for name.tar.tbz/.txz/.tgz/.tlz4/.tzst
|
|
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Write', 'error', $"sorry, unable to update {tarfile} with {fname}")
|
|
else
|
|
|
|
if fname =~ '/'
|
|
let dirpath = substitute(fname,'/[^/]\+$','','e')
|
|
if has("win32unix") && executable("cygpath")
|
|
let dirpath = substitute(system("cygpath ".shellescape(dirpath, 0)),'\n','','e')
|
|
endif
|
|
call mkdir(dirpath,"p")
|
|
endif
|
|
if tarfile !~ '/'
|
|
let tarfile= curdir.'/'.tarfile
|
|
endif
|
|
if tarfile =~ '^\s*-'
|
|
" A file name starting with a dash may be taken as an option. Prepend ./ to avoid that.
|
|
let tarfile = substitute(tarfile, '-', './-', '')
|
|
endif
|
|
|
|
" don't overwrite a file forcefully
|
|
exe "w ".fnameescape(fname)
|
|
if has("win32unix") && executable("cygpath")
|
|
let tarfile = substitute(system("cygpath ".shellescape(tarfile,0)),'\n','','e')
|
|
endif
|
|
|
|
" delete old file from tarfile
|
|
" Note: BSD tar does not support --delete flag
|
|
call system(g:tar_cmd." ".g:tar_delfile." ".shellescape(tarfile,0).g:tar_secure.shellescape(fname,0))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Write', 'error', $"sorry, unable to update {fnameescape(tarfile)} with {fnameescape(fname)} --delete not supported?")
|
|
else
|
|
" update tarfile with new file
|
|
call system(g:tar_cmd." -".g:tar_writeoptions." ".shellescape(tarfile,0).g:tar_secure.shellescape(fname,0))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Write', 'error', $"sorry, unable to update {fnameescape(tarfile)} with {fnameescape(fname)}")
|
|
elseif exists("compress")
|
|
call system(compress)
|
|
if exists("tgz")
|
|
call rename(tarfile.".gz",substitute(tarfile,'\.tar$','.tgz','e'))
|
|
endif
|
|
endif
|
|
endif
|
|
|
|
" support writing tarfiles across a network
|
|
if s:tblfile_{winnr()} =~ '^\a\+://'
|
|
let tblfile= s:tblfile_{winnr()}
|
|
1split|noswapfile enew
|
|
let binkeep= &l:binary
|
|
let eikeep = &ei
|
|
set binary ei=all
|
|
exe "noswapfile e! ".fnameescape(tarfile)
|
|
call netrw#NetWrite(tblfile)
|
|
let &ei = eikeep
|
|
let &l:binary = binkeep
|
|
q!
|
|
unlet s:tblfile_{winnr()}
|
|
endif
|
|
endif
|
|
|
|
" cleanup and restore current directory
|
|
lcd ..
|
|
call s:Rmdir("_ZIPVIM_")
|
|
exe "lcd ".fnameescape(pwdkeep)
|
|
setlocal nomod
|
|
|
|
let &report= repkeep
|
|
endfun
|
|
|
|
" ---------------------------------------------------------------------
|
|
" tar#Diff: {{{2
|
|
fun! tar#Diff(userfname,fname)
|
|
let fname= a:fname
|
|
if a:userfname != ""
|
|
let fname= a:userfname
|
|
endif
|
|
exe "lcd ".fnameescape(b:tmpdir). '/_ZIPVIM_'
|
|
if filereadable(fname)
|
|
" sets current file (from tarball) for diff'ing
|
|
" splits window vertically
|
|
" opens original file, sets it for diff'ing
|
|
" sets up b:tardiff_otherbuf variables so each buffer knows about the other (for closing purposes)
|
|
diffthis
|
|
wincmd v
|
|
exe "noswapfile e ".fnameescape(fname)
|
|
diffthis
|
|
else
|
|
redraw!
|
|
echo "***warning*** unable to read file<".fname.">"
|
|
endif
|
|
endfun
|
|
|
|
" ---------------------------------------------------------------------
|
|
" tar#Extract: extract a file from a (possibly compressed) tar archive {{{2
|
|
fun! tar#Extract()
|
|
|
|
let repkeep= &report
|
|
set report=10
|
|
let fname= getline(".")
|
|
|
|
" sanity check
|
|
if fname =~ '^"'
|
|
let &report= repkeep
|
|
return
|
|
endif
|
|
|
|
let tarball = expand("%")
|
|
let tarbase = substitute(tarball,'\..*$','','')
|
|
|
|
let extractcmd= s:WinPath(g:tar_extractcmd)
|
|
if filereadable(tarbase.".tar")
|
|
call system(extractcmd." ".shellescape(tarbase).".tar ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ". fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".tgz")
|
|
let extractcmd= substitute(extractcmd,"-","-z","")
|
|
call system(extractcmd." ".shellescape(tarbase).".tgz ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tgz {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".tar.gz")
|
|
let extractcmd= substitute(extractcmd,"-","-z","")
|
|
call system(extractcmd." ".shellescape(tarbase).".tar.gz ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.gz {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".tbz")
|
|
let extractcmd= substitute(extractcmd,"-","-j","")
|
|
call system(extractcmd." ".shellescape(tarbase).".tbz ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tbz {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".tar.bz2")
|
|
let extractcmd= substitute(extractcmd,"-","-j","")
|
|
call system(extractcmd." ".shellescape(tarbase).".tar.bz2 ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.bz2 {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".tar.bz3")
|
|
let extractcmd= substitute(extractcmd,"-","-j","")
|
|
call system(extractcmd." ".shellescape(tarbase).".tar.bz3 ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.bz3 {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".txz")
|
|
let extractcmd= substitute(extractcmd,"-","-J","")
|
|
call system(extractcmd." ".shellescape(tarbase).".txz ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.txz {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".tar.xz")
|
|
let extractcmd= substitute(extractcmd,"-","-J","")
|
|
call system(extractcmd." ".shellescape(tarbase).".tar.xz ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.xz {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".tzst")
|
|
let extractcmd= substitute(extractcmd,"-","--zstd","")
|
|
call system(extractcmd." ".shellescape(tarbase).".tzst ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tzst {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".tar.zst")
|
|
let extractcmd= substitute(extractcmd,"-","--zstd","")
|
|
call system(extractcmd." ".shellescape(tarbase).".tar.zst ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.zst {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".tlz4")
|
|
if has("linux")
|
|
let extractcmd= substitute(extractcmd,"-","-I lz4 -","")
|
|
endif
|
|
call system(extractcmd." ".shellescape(tarbase).".tlz4 ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tlz4 {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
|
|
elseif filereadable(tarbase.".tar.lz4")
|
|
if has("linux")
|
|
let extractcmd= substitute(extractcmd,"-","-I lz4 -","")
|
|
endif
|
|
call system(extractcmd." ".shellescape(tarbase).".tar.lz4 ".shellescape(fname))
|
|
if v:shell_error != 0
|
|
call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.lz4 {fname}: failed!")
|
|
else
|
|
echo "***note*** successfully extracted ".fname
|
|
endif
|
|
endif
|
|
|
|
" restore option
|
|
let &report= repkeep
|
|
endfun
|
|
|
|
" ---------------------------------------------------------------------
|
|
" s:Rmdir: {{{2
|
|
fun! s:Rmdir(fname)
|
|
call delete(a:fname, 'rf')
|
|
endfun
|
|
|
|
" s:FileHeader: {{{2
|
|
fun! s:Header(fname)
|
|
let header= readblob(a:fname, 0, 6)
|
|
" Nvim: see https://github.com/neovim/neovim/pull/34968
|
|
if header[0:2] == 0z425A68 " bzip2 header
|
|
return "bzip2"
|
|
elseif header[0:2] == 0z425A33 " bzip3 header
|
|
return "bzip3"
|
|
elseif header == 0zFD377A58.5A00 " xz header
|
|
return "xz"
|
|
elseif header[0:3] == 0z28B52FFD " zstd header
|
|
return "zstd"
|
|
elseif header[0:3] == 0z04224D18 " lz4 header
|
|
return "lz4"
|
|
elseif (header[0:1] == 0z1F9D ||
|
|
\ header[0:1] == 0z1F8B ||
|
|
\ header[0:1] == 0z1F9E ||
|
|
\ header[0:1] == 0z1FA0 ||
|
|
\ header[0:1] == 0z1F1E)
|
|
return "gzip"
|
|
endif
|
|
return "unknown"
|
|
endfun
|
|
|
|
" ---------------------------------------------------------------------
|
|
" s:WinPath: {{{2
|
|
fun! s:WinPath(path)
|
|
if (!g:netrw_cygwin || &shell !~ '\%(\<bash\>\|\<zsh\>\)\%(\.exe\)\=$') && has("win32")
|
|
" remove cygdrive prefix, if present
|
|
let path = substitute(a:path, '/cygdrive/\(.\)', '\1:', '')
|
|
" remove trailing slash (Win95)
|
|
let path = substitute(path, '\(\\\|/\)$', '', 'g')
|
|
" remove escaped spaces
|
|
let path = substitute(path, '\ ', ' ', 'g')
|
|
" convert slashes to backslashes
|
|
let path = substitute(path, '/', '\', 'g')
|
|
else
|
|
let path = a:path
|
|
endif
|
|
|
|
return path
|
|
endfun
|
|
|
|
" =====================================================================
|
|
" Modelines And Restoration: {{{1
|
|
let &cpo= s:keepcpo
|
|
unlet s:keepcpo
|
|
" vim:ts=8 fdm=marker
|