neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

tar.vim (27073B)


      1 " tar.vim: Handles browsing tarfiles -  AUTOLOAD PORTION
      2 " Date:		Mar 01, 2025
      3 " Version:	32b  (with modifications from the Vim Project)
      4 " Maintainer:	This runtime file is looking for a new maintainer.
      5 " Former Maintainer: Charles E Campbell
      6 " License:	Vim License  (see vim's :help license)
      7 " Last Change:
      8 "   2024 Jan 08 by Vim Project: fix a few problems (#138331, #12637, #8109)
      9 "   2024 Feb 19 by Vim Project: announce adoption
     10 "   2024 Nov 11 by Vim Project: support permissions (#7379)
     11 "   2025 Feb 06 by Vim Project: add support for lz4 (#16591)
     12 "   2025 Feb 28 by Vim Project: add support for bzip3 (#16755)
     13 "   2025 Mar 01 by Vim Project: fix syntax error in tar#Read()
     14 "   2025 Mar 02 by Vim Project: escape the filename before using :read
     15 "   2025 Mar 02 by Vim Project: determine the compression using readblob()
     16 "                               instead of shelling out to file(1)
     17 "   2025 Apr 16 by Vim Project: decouple from netrw by adding s:WinPath()
     18 "   2025 May 19 by Vim Project: restore working directory after read/write
     19 "   2025 Jul 13 by Vim Project: warn with path traversal attacks
     20 "   2026 Feb 06 by Vim Project: consider 'nowrapscan' (#19333)
     21 "   2026 Feb 07 by Vim Project: make the path traversal detection more robust (#19341)
     22 "
     23 "	Contains many ideas from Michael Toren's <tar.vim>
     24 "
     25 " Copyright:    Copyright (C) 2005-2017 Charles E. Campbell {{{1
     26 "               Permission is hereby granted to use and distribute this code,
     27 "               with or without modifications, provided that this copyright
     28 "               notice is copied with it. Like anything else that's free,
     29 "               tar.vim and tarPlugin.vim are provided *as is* and comes
     30 "               with no warranty of any kind, either expressed or implied.
     31 "               By using this plugin, you agree that in no event will the
     32 "               copyright holder be liable for any damages resulting from
     33 "               the use of this software.
     34 " ---------------------------------------------------------------------
     35 " Load Once: {{{1
     36 if &cp || exists("g:loaded_tar")
     37 finish
     38 endif
     39 let g:loaded_tar= "v32b"
     40 if !has('nvim-0.12') && v:version < 900
     41 echohl WarningMsg
     42 echo "***warning*** this version of tar needs vim 9.0"
     43 echohl Normal
     44 finish
     45 endif
     46 let s:keepcpo= &cpo
     47 set cpo&vim
     48 
     49 " ---------------------------------------------------------------------
     50 "  Default Settings: {{{1
     51 if !exists("g:tar_browseoptions")
     52 let g:tar_browseoptions= "tf"
     53 endif
     54 if !exists("g:tar_readoptions")
     55 let g:tar_readoptions= "pxf"
     56 endif
     57 if !exists("g:tar_cmd")
     58 let g:tar_cmd= "tar"
     59 endif
     60 if !exists("g:tar_writeoptions")
     61 let g:tar_writeoptions= "uf"
     62 endif
     63 if !exists("g:tar_delfile")
     64 " Note: not supported on BSD
     65 let g:tar_delfile="--delete -f"
     66 endif
     67 if !exists("g:netrw_cygwin")
     68 if has("win32") || has("win95") || has("win64") || has("win16")
     69  if &shell =~ '\%(\<bash\>\|\<zsh\>\)\%(\.exe\)\=$'
     70   let g:netrw_cygwin= 1
     71  else
     72   let g:netrw_cygwin= 0
     73  endif
     74 else
     75  let g:netrw_cygwin= 0
     76 endif
     77 endif
     78 if !exists("g:tar_copycmd")
     79 if !exists("g:netrw_localcopycmd")
     80  if has("win32") || has("win95") || has("win64") || has("win16")
     81   if g:netrw_cygwin
     82    let g:netrw_localcopycmd= "cp"
     83   else
     84    let g:netrw_localcopycmd= "copy"
     85   endif
     86  elseif has("unix") || has("macunix")
     87   let g:netrw_localcopycmd= "cp"
     88  else
     89   let g:netrw_localcopycmd= ""
     90  endif
     91 endif
     92 let g:tar_copycmd= g:netrw_localcopycmd
     93 endif
     94 if !exists("g:tar_extractcmd")
     95 let g:tar_extractcmd= "tar -pxf"
     96 endif
     97 
     98 " set up shell quoting character
     99 if !exists("g:tar_shq")
    100 if exists("+shq") && exists("&shq") && &shq != ""
    101  let g:tar_shq= &shq
    102 elseif has("win32") || has("win95") || has("win64") || has("win16")
    103  if exists("g:netrw_cygwin") && g:netrw_cygwin
    104   let g:tar_shq= "'"
    105  else
    106   let g:tar_shq= '"'
    107  endif
    108 else
    109  let g:tar_shq= "'"
    110 endif
    111 endif
    112 
    113 let g:tar_secure=' -- '
    114 let g:tar_leading_pat='\m^\%([.]\{,2\}/\)\+'
    115 
    116 " ----------------
    117 "  Functions: {{{1
    118 " ----------------
    119 
    120 " ---------------------------------------------------------------------
    121 " s:Msg: {{{2
    122 fun! s:Msg(func, severity, msg)
    123  redraw!
    124  if a:severity =~? 'error'
    125    echohl Error 
    126  else
    127    echohl WarningMsg
    128  endif
    129  echo $"***{a:severity}*** ({a:func}) {a:msg}"
    130  echohl None
    131 endfunc
    132 
    133 " ---------------------------------------------------------------------
    134 " tar#Browse: {{{2
    135 fun! tar#Browse(tarfile)
    136  let repkeep= &report
    137  set report=10
    138 
    139  " sanity checks
    140  if !executable(g:tar_cmd)
    141   call s:Msg('tar#Browse', 'error', $"{g:tar_cmd} not available on your system")
    142   let &report= repkeep
    143   return
    144  endif
    145  if !filereadable(a:tarfile)
    146   if a:tarfile !~# '^\a\+://'
    147    " if it's an url, don't complain, let url-handlers such as vim do its thing
    148    call s:Msg('tar#Browse', 'error', $"File not readable<{a:tarfile}>")
    149   endif
    150   let &report= repkeep
    151   return
    152  endif
    153  if &ma != 1
    154   set ma
    155  endif
    156  let b:tarfile= a:tarfile
    157 
    158  setlocal noswapfile
    159  setlocal buftype=nofile
    160  setlocal bufhidden=hide
    161  setlocal nobuflisted
    162  setlocal nowrap
    163  set ft=tar
    164 
    165  " give header
    166  let lastline= line("$")
    167  call setline(lastline+1,'" tar.vim version '.g:loaded_tar)
    168  call setline(lastline+2,'" Browsing tarfile '.a:tarfile)
    169  call setline(lastline+3,'" Select a file with cursor and press ENTER, "x" to extract a file')
    170  keepj $put =''
    171  keepj sil! 0d
    172  keepj $
    173 
    174  let tarfile= a:tarfile
    175  if has("win32unix") && executable("cygpath")
    176   " assuming cygwin
    177   let tarfile=substitute(system("cygpath -u ".shellescape(tarfile,0)),'\n$','','e')
    178  endif
    179  let curlast= line("$")
    180 
    181  if tarfile =~# '\.\(gz\)$'
    182   exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    183 
    184  elseif tarfile =~# '\.\(tgz\)$' || tarfile =~# '\.\(tbz\)$' || tarfile =~# '\.\(txz\)$' ||
    185                          \ tarfile =~# '\.\(tzst\)$' || tarfile =~# '\.\(tlz4\)$'
    186   let header= s:Header(tarfile)
    187 
    188   if header =~? 'bzip2'
    189    exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    190   elseif header =~? 'bzip3'
    191    exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    192   elseif header =~? 'xz'
    193    exe "sil! r! xz -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    194   elseif header =~? 'zstd'
    195    exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    196   elseif header =~? 'lz4'
    197    exe "sil! r! lz4 --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    198   elseif header =~? 'gzip'
    199    exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    200   endif
    201 
    202  elseif tarfile =~# '\.lrp'
    203   exe "sil! r! cat -- ".shellescape(tarfile,1)."|gzip -d -c -|".g:tar_cmd." -".g:tar_browseoptions." - "
    204  elseif tarfile =~# '\.\(bz2\|tbz\|tb2\)$'
    205   exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    206  elseif tarfile =~# '\.\(bz3\|tb3\)$'
    207   exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    208  elseif tarfile =~# '\.\(lzma\|tlz\)$'
    209   exe "sil! r! lzma -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    210  elseif tarfile =~# '\.\(xz\|txz\)$'
    211   exe "sil! r! xz --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    212  elseif tarfile =~# '\.\(zst\|tzst\)$'
    213   exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    214  elseif tarfile =~# '\.\(lz4\|tlz4\)$'
    215   exe "sil! r! lz4 --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - "
    216  else
    217   if tarfile =~ '^\s*-'
    218    " A file name starting with a dash is taken as an option.  Prepend ./ to avoid that.
    219    let tarfile = substitute(tarfile, '-', './-', '')
    220   endif
    221   exe "sil! r! ".g:tar_cmd." -".g:tar_browseoptions." ".shellescape(tarfile,1)
    222  endif
    223  if v:shell_error != 0
    224   call s:Msg('tar#Browse', 'warning', $"please check your g:tar_browseoptions '<{g:tar_browseoptions}>'")
    225   return
    226  endif
    227 
    228  " remove tar: Removing leading '/' from member names
    229  " Note: the message could be localized
    230  if search('\m^g\?tar: ', 'w') > 0 || search(g:tar_leading_pat, 'w') > 0
    231    call append(3,'" Note: Path Traversal Attack detected!')
    232    let b:leading_slash = 1
    233    " remove the message output
    234    sil g/^tar: /d
    235  endif
    236 
    237  " set up maps supported for tar
    238  setlocal noma nomod ro
    239  noremap <silent> <buffer>	<cr>		:call <SID>TarBrowseSelect()<cr>
    240  noremap <silent> <buffer>	x	 	:call tar#Extract()<cr>
    241  if &mouse != ""
    242   noremap <silent> <buffer>	<leftmouse>	<leftmouse>:call <SID>TarBrowseSelect()<cr>
    243  endif
    244 
    245  let &report= repkeep
    246 endfun
    247 
    248 " ---------------------------------------------------------------------
    249 " TarBrowseSelect: {{{2
    250 fun! s:TarBrowseSelect()
    251  let repkeep= &report
    252  set report=10
    253  let fname= getline(".")
    254  let ls= get(b:, 'leading_slash', 0)
    255 
    256  " sanity check
    257  if fname =~ '^"'
    258   let &report= repkeep
    259   return
    260  endif
    261 
    262  " about to make a new window, need to use b:tarfile
    263  let tarfile= b:tarfile
    264  let curfile= expand("%")
    265  if has("win32unix") && executable("cygpath")
    266   " assuming cygwin
    267   let tarfile=substitute(system("cygpath -u ".shellescape(tarfile,0)),'\n$','','e')
    268  endif
    269 
    270  " open a new window (tar#Read will read a file into it)
    271  noswapfile new
    272  if !exists("g:tar_nomax") || g:tar_nomax == 0
    273   wincmd _
    274  endif
    275  let s:tblfile_{winnr()}= curfile
    276  let b:leading_slash= ls
    277  call tar#Read("tarfile:".tarfile.'::'.fname)
    278  filetype detect
    279  set nomod
    280  exe 'com! -buffer -nargs=? -complete=file TarDiff	:call tar#Diff(<q-args>,"'.fnameescape(fname).'")'
    281 
    282  let &report= repkeep
    283 endfun
    284 
    285 " ---------------------------------------------------------------------
    286 " tar#Read: {{{2
    287 fun! tar#Read(fname)
    288  let repkeep= &report
    289  set report=10
    290  let tarfile = substitute(a:fname,'tarfile:\(.\{-}\)::.*$','\1','')
    291  let fname   = substitute(a:fname,'tarfile:.\{-}::\(.*\)$','\1','')
    292  " be careful not to execute special crafted files
    293  let escape_file = fname->substitute(g:tar_leading_pat, '', '')->fnameescape()
    294 
    295  let curdir= getcwd()
    296  let b:curdir= curdir
    297  let tmpdir= tempname()
    298  let b:tmpdir= tmpdir
    299  if tmpdir =~ '\.'
    300   let tmpdir= substitute(tmpdir,'\.[^.]*$','','e')
    301  endif
    302  call mkdir(tmpdir,"p")
    303 
    304  " attempt to change to the indicated directory
    305  try
    306   exe "lcd ".fnameescape(tmpdir)
    307  catch /^Vim\%((\a\+)\)\=:E344/
    308   call s:Msg('tar#Read', 'error', "cannot lcd to temporary directory")
    309   let &report= repkeep
    310   return
    311  endtry
    312 
    313  " place temporary files under .../_ZIPVIM_/
    314  if isdirectory("_ZIPVIM_")
    315   call s:Rmdir("_ZIPVIM_")
    316  endif
    317  call mkdir("_ZIPVIM_")
    318  lcd _ZIPVIM_
    319 
    320  if has("win32unix") && executable("cygpath")
    321   " assuming cygwin
    322   let tarfile=substitute(system("cygpath -u ".shellescape(tarfile,0)),'\n$','','e')
    323  endif
    324 
    325  if  fname =~ '\.bz2$' && executable("bzcat")
    326   let decmp= "|bzcat"
    327   let doro = 1
    328  elseif  fname =~ '\.bz3$' && executable("bz3cat")
    329   let decmp= "|bz3cat"
    330   let doro = 1
    331  elseif  fname =~ '\.t\=gz$'  && executable("zcat")
    332   let decmp= "|zcat"
    333   let doro = 1
    334  elseif  fname =~ '\.lzma$' && executable("lzcat")
    335   let decmp= "|lzcat"
    336   let doro = 1
    337  elseif  fname =~ '\.xz$' && executable("xzcat")
    338   let decmp= "|xzcat"
    339   let doro = 1
    340  elseif  fname =~ '\.zst$' && executable("zstdcat")
    341   let decmp= "|zstdcat"
    342   let doro = 1
    343  elseif  fname =~ '\.lz4$' && executable("lz4cat")
    344   let decmp= "|lz4cat"
    345   let doro = 1
    346  else
    347   let decmp=""
    348   let doro = 0
    349   if fname =~ '\.bz2$\|\.bz3$\|\.gz$\|\.lzma$\|\.xz$\|\.zip$\|\.Z$'
    350    setlocal bin
    351   endif
    352  endif
    353 
    354 
    355  if tarfile =~# '\.bz2$'
    356   exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    357   exe "read ".escape_file
    358  elseif tarfile =~# '\.bz3$'
    359   exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    360   exe "read ".escape_file
    361  elseif tarfile =~# '\.\(gz\)$'
    362   exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    363   exe "read ".escape_file
    364  elseif tarfile =~# '\(\.tgz\|\.tbz\|\.txz\)'
    365   let filekind= s:Header(tarfile)
    366   if filekind =~? "bzip2"
    367    exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    368    exe "read ".escape_file
    369   elseif filekind =~ "bzip3"
    370    exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    371    exe "read ".escape_file
    372   elseif filekind =~? "xz"
    373    exe "sil! r! xz -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    374    exe "read ".escape_file
    375   elseif filekind =~? "zstd"
    376    exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    377    exe "read ".escape_file
    378   elseif filekind =~? "gzip"
    379    exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    380    exe "read ".escape_file
    381   endif
    382 
    383  elseif tarfile =~# '\.lrp$'
    384   exe "sil! r! cat -- ".shellescape(tarfile,1)." | gzip -d -c - | ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    385   exe "read ".escape_file
    386  elseif tarfile =~# '\.lzma$'
    387   exe "sil! r! lzma -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    388   exe "read ".escape_file
    389  elseif tarfile =~# '\.\(xz\|txz\)$'
    390   exe "sil! r! xz --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    391   exe "read ".escape_file
    392  elseif tarfile =~# '\.\(lz4\|tlz4\)$'
    393   exe "sil! r! lz4 --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp
    394   exe "read ".escape_file
    395  else
    396   if tarfile =~ '^\s*-'
    397    " A file name starting with a dash is taken as an option.  Prepend ./ to avoid that.
    398    let tarfile = substitute(tarfile, '-', './-', '')
    399   endif
    400   exe "silent r! ".g:tar_cmd." -".g:tar_readoptions.shellescape(tarfile,1)." ".g:tar_secure.shellescape(fname,1).decmp
    401   exe "read ".escape_file
    402  endif
    403  if get(b:, 'leading_slash', 0)
    404    sil g/^tar: /d
    405  endif
    406 
    407   redraw!
    408 
    409  if v:shell_error != 0
    410   lcd ..
    411   call s:Rmdir("_ZIPVIM_")
    412   exe "lcd ".fnameescape(curdir)
    413   call s:Msg('tar#Read', 'error', $"sorry, unable to open or extract {tarfile} with {fname}")
    414  endif
    415 
    416  if doro
    417   " because the reverse process of compressing changed files back into the tarball is not currently supported
    418   setlocal ro
    419  endif
    420 
    421  let b:tarfile= a:fname
    422 
    423  " cleanup
    424  keepj sil! 0d
    425  set nomod
    426 
    427  let &report= repkeep
    428  exe "lcd ".fnameescape(curdir)
    429  silent exe "file tarfile::". fname->fnameescape()
    430 endfun
    431 
    432 " ---------------------------------------------------------------------
    433 " tar#Write: {{{2
    434 fun! tar#Write(fname)
    435  let pwdkeep= getcwd()
    436  let repkeep= &report
    437  set report=10
    438  let curdir= b:curdir
    439  let tmpdir= b:tmpdir
    440 
    441  " sanity checks
    442  if !executable(g:tar_cmd)
    443   redraw!
    444   let &report= repkeep
    445   return
    446  endif
    447  let tarfile = substitute(b:tarfile,'tarfile:\(.\{-}\)::.*$','\1','')
    448  let fname   = substitute(b:tarfile,'tarfile:.\{-}::\(.*\)$','\1','')
    449 
    450  if get(b:, 'leading_slash', 0)
    451   call s:Msg('tar#Write', 'error', $"sorry, not attempting to update {tarfile} with {fname}")
    452   let &report= repkeep
    453   return
    454  endif
    455 
    456  if !isdirectory(fnameescape(tmpdir))
    457    call mkdir(fnameescape(tmpdir), 'p')
    458  endif
    459  exe $"lcd {fnameescape(tmpdir)}"
    460  if isdirectory("_ZIPVIM_")
    461    call s:Rmdir("_ZIPVIM_")
    462  endif
    463  call mkdir("_ZIPVIM_")
    464  lcd _ZIPVIM_
    465  let dir = fnamemodify(fname, ':p:h')
    466  if dir !~# '_ZIPVIM_$'
    467    call mkdir(dir)
    468  endif
    469 
    470  " handle compressed archives
    471  if tarfile =~# '\.bz2'
    472   call system("bzip2 -d -- ".shellescape(tarfile,0))
    473   let tarfile = substitute(tarfile,'\.bz2','','e')
    474   let compress= "bzip2 -- ".shellescape(tarfile,0)
    475  elseif tarfile =~# '\.bz3'
    476   call system("bzip3 -d -- ".shellescape(tarfile,0))
    477   let tarfile = substitute(tarfile,'\.bz3','','e')
    478   let compress= "bzip3 -- ".shellescape(tarfile,0)
    479  elseif tarfile =~# '\.gz'
    480   call system("gzip -d -- ".shellescape(tarfile,0))
    481   let tarfile = substitute(tarfile,'\.gz','','e')
    482   let compress= "gzip -- ".shellescape(tarfile,0)
    483  elseif tarfile =~# '\.tgz'
    484   call system("gzip -d -- ".shellescape(tarfile,0))
    485   let tarfile = substitute(tarfile,'\.tgz','.tar','e')
    486   let compress= "gzip -- ".shellescape(tarfile,0)
    487   let tgz     = 1
    488  elseif tarfile =~# '\.xz'
    489   call system("xz -d -- ".shellescape(tarfile,0))
    490   let tarfile = substitute(tarfile,'\.xz','','e')
    491   let compress= "xz -- ".shellescape(tarfile,0)
    492  elseif tarfile =~# '\.zst'
    493   call system("zstd --decompress --rm -- ".shellescape(tarfile,0))
    494   let tarfile = substitute(tarfile,'\.zst','','e')
    495   let compress= "zstd --rm -- ".shellescape(tarfile,0)
    496  elseif tarfile =~# '\.lz4'
    497   call system("lz4 --decompress --rm -- ".shellescape(tarfile,0))
    498   let tarfile = substitute(tarfile,'\.lz4','','e')
    499   let compress= "lz4 --rm -- ".shellescape(tarfile,0)
    500  elseif tarfile =~# '\.lzma'
    501   call system("lzma -d -- ".shellescape(tarfile,0))
    502   let tarfile = substitute(tarfile,'\.lzma','','e')
    503   let compress= "lzma -- ".shellescape(tarfile,0)
    504  endif
    505  " Note: no support for name.tar.tbz/.txz/.tgz/.tlz4/.tzst
    506 
    507  if v:shell_error != 0
    508   call s:Msg('tar#Write', 'error', $"sorry, unable to update {tarfile} with {fname}")
    509  else
    510 
    511   if fname =~ '/'
    512    let dirpath = substitute(fname,'/[^/]\+$','','e')
    513    if has("win32unix") && executable("cygpath")
    514     let dirpath = substitute(system("cygpath ".shellescape(dirpath, 0)),'\n','','e')
    515    endif
    516    call mkdir(dirpath,"p")
    517   endif
    518   if tarfile !~ '/'
    519    let tarfile= curdir.'/'.tarfile
    520   endif
    521   if tarfile =~ '^\s*-'
    522    " A file name starting with a dash may be taken as an option.  Prepend ./ to avoid that.
    523    let tarfile = substitute(tarfile, '-', './-', '')
    524   endif
    525 
    526   " don't overwrite a file forcefully
    527   exe "w ".fnameescape(fname)
    528   if has("win32unix") && executable("cygpath")
    529    let tarfile = substitute(system("cygpath ".shellescape(tarfile,0)),'\n','','e')
    530   endif
    531 
    532   " delete old file from tarfile
    533   " Note: BSD tar does not support --delete flag
    534   call system(g:tar_cmd." ".g:tar_delfile." ".shellescape(tarfile,0).g:tar_secure.shellescape(fname,0))
    535   if v:shell_error != 0
    536    call s:Msg('tar#Write', 'error', $"sorry, unable to update {fnameescape(tarfile)} with {fnameescape(fname)} --delete not supported?")
    537   else
    538    " update tarfile with new file
    539    call system(g:tar_cmd." -".g:tar_writeoptions." ".shellescape(tarfile,0).g:tar_secure.shellescape(fname,0))
    540    if v:shell_error != 0
    541     call s:Msg('tar#Write', 'error', $"sorry, unable to update {fnameescape(tarfile)} with {fnameescape(fname)}")
    542    elseif exists("compress")
    543     call system(compress)
    544     if exists("tgz")
    545      call rename(tarfile.".gz",substitute(tarfile,'\.tar$','.tgz','e'))
    546     endif
    547    endif
    548   endif
    549 
    550   " support writing tarfiles across a network
    551   if s:tblfile_{winnr()} =~ '^\a\+://'
    552    let tblfile= s:tblfile_{winnr()}
    553    1split|noswapfile enew
    554    let binkeep= &l:binary
    555    let eikeep = &ei
    556    set binary ei=all
    557    exe "noswapfile e! ".fnameescape(tarfile)
    558    call netrw#NetWrite(tblfile)
    559    let &ei       = eikeep
    560    let &l:binary = binkeep
    561    q!
    562    unlet s:tblfile_{winnr()}
    563   endif
    564  endif
    565 
    566  " cleanup and restore current directory
    567  lcd ..
    568  call s:Rmdir("_ZIPVIM_")
    569  exe "lcd ".fnameescape(pwdkeep)
    570  setlocal nomod
    571 
    572  let &report= repkeep
    573 endfun
    574 
    575 " ---------------------------------------------------------------------
    576 " tar#Diff: {{{2
    577 fun! tar#Diff(userfname,fname)
    578  let fname= a:fname
    579  if a:userfname != ""
    580   let fname= a:userfname
    581  endif
    582  exe "lcd ".fnameescape(b:tmpdir). '/_ZIPVIM_'
    583  if filereadable(fname)
    584   " sets current file (from tarball) for diff'ing
    585   " splits window vertically
    586   " opens original file, sets it for diff'ing
    587   " sets up b:tardiff_otherbuf variables so each buffer knows about the other (for closing purposes)
    588   diffthis
    589   wincmd v
    590   exe "noswapfile e ".fnameescape(fname)
    591   diffthis
    592  else
    593   redraw!
    594   echo "***warning*** unable to read file<".fname.">"
    595  endif
    596 endfun
    597 
    598 " ---------------------------------------------------------------------
    599 " tar#Extract: extract a file from a (possibly compressed) tar archive {{{2
    600 fun! tar#Extract()
    601 
    602  let repkeep= &report
    603  set report=10
    604  let fname= getline(".")
    605 
    606  " sanity check
    607  if fname =~ '^"'
    608   let &report= repkeep
    609   return
    610  endif
    611 
    612  let tarball = expand("%")
    613  let tarbase = substitute(tarball,'\..*$','','')
    614 
    615  let extractcmd= s:WinPath(g:tar_extractcmd)
    616  if filereadable(tarbase.".tar")
    617   call system(extractcmd." ".shellescape(tarbase).".tar ".shellescape(fname))
    618   if v:shell_error != 0
    619    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar {fname}: failed!")
    620   else
    621    echo "***note*** successfully extracted ". fname
    622   endif
    623 
    624  elseif filereadable(tarbase.".tgz")
    625   let extractcmd= substitute(extractcmd,"-","-z","")
    626   call system(extractcmd." ".shellescape(tarbase).".tgz ".shellescape(fname))
    627   if v:shell_error != 0
    628    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tgz {fname}: failed!")
    629   else
    630    echo "***note*** successfully extracted ".fname
    631   endif
    632 
    633  elseif filereadable(tarbase.".tar.gz")
    634   let extractcmd= substitute(extractcmd,"-","-z","")
    635   call system(extractcmd." ".shellescape(tarbase).".tar.gz ".shellescape(fname))
    636   if v:shell_error != 0
    637    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.gz {fname}: failed!")
    638   else
    639    echo "***note*** successfully extracted ".fname
    640   endif
    641 
    642  elseif filereadable(tarbase.".tbz")
    643   let extractcmd= substitute(extractcmd,"-","-j","")
    644   call system(extractcmd." ".shellescape(tarbase).".tbz ".shellescape(fname))
    645   if v:shell_error != 0
    646    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tbz {fname}: failed!")
    647   else
    648    echo "***note*** successfully extracted ".fname
    649   endif
    650 
    651  elseif filereadable(tarbase.".tar.bz2")
    652   let extractcmd= substitute(extractcmd,"-","-j","")
    653   call system(extractcmd." ".shellescape(tarbase).".tar.bz2 ".shellescape(fname))
    654   if v:shell_error != 0
    655    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.bz2 {fname}: failed!")
    656   else
    657    echo "***note*** successfully extracted ".fname
    658   endif
    659 
    660  elseif filereadable(tarbase.".tar.bz3")
    661   let extractcmd= substitute(extractcmd,"-","-j","")
    662   call system(extractcmd." ".shellescape(tarbase).".tar.bz3 ".shellescape(fname))
    663   if v:shell_error != 0
    664    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.bz3 {fname}: failed!")
    665   else
    666    echo "***note*** successfully extracted ".fname
    667   endif
    668 
    669  elseif filereadable(tarbase.".txz")
    670   let extractcmd= substitute(extractcmd,"-","-J","")
    671   call system(extractcmd." ".shellescape(tarbase).".txz ".shellescape(fname))
    672   if v:shell_error != 0
    673    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.txz {fname}: failed!")
    674   else
    675    echo "***note*** successfully extracted ".fname
    676   endif
    677 
    678  elseif filereadable(tarbase.".tar.xz")
    679   let extractcmd= substitute(extractcmd,"-","-J","")
    680   call system(extractcmd." ".shellescape(tarbase).".tar.xz ".shellescape(fname))
    681   if v:shell_error != 0
    682    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.xz {fname}: failed!")
    683   else
    684    echo "***note*** successfully extracted ".fname
    685   endif
    686 
    687  elseif filereadable(tarbase.".tzst")
    688   let extractcmd= substitute(extractcmd,"-","--zstd","")
    689   call system(extractcmd." ".shellescape(tarbase).".tzst ".shellescape(fname))
    690   if v:shell_error != 0
    691    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tzst {fname}: failed!")
    692   else
    693    echo "***note*** successfully extracted ".fname
    694   endif
    695 
    696  elseif filereadable(tarbase.".tar.zst")
    697   let extractcmd= substitute(extractcmd,"-","--zstd","")
    698   call system(extractcmd." ".shellescape(tarbase).".tar.zst ".shellescape(fname))
    699   if v:shell_error != 0
    700    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.zst {fname}: failed!")
    701   else
    702    echo "***note*** successfully extracted ".fname
    703   endif
    704 
    705  elseif filereadable(tarbase.".tlz4")
    706   let extractcmd= substitute(extractcmd,"-","-I lz4","")
    707   call system(extractcmd." ".shellescape(tarbase).".tlz4 ".shellescape(fname))
    708   if v:shell_error != 0
    709    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tlz4 {fname}: failed!")
    710   else
    711    echo "***note*** successfully extracted ".fname
    712   endif
    713 
    714  elseif filereadable(tarbase.".tar.lz4")
    715   let extractcmd= substitute(extractcmd,"-","-I lz4","")
    716   call system(extractcmd." ".shellescape(tarbase).".tar.lz4".shellescape(fname))
    717   if v:shell_error != 0
    718    call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.lz4 {fname}: failed!")
    719   else
    720    echo "***note*** successfully extracted ".fname
    721   endif
    722  endif
    723 
    724  " restore option
    725  let &report= repkeep
    726 endfun
    727 
    728 " ---------------------------------------------------------------------
    729 " s:Rmdir: {{{2
    730 fun! s:Rmdir(fname)
    731  call delete(a:fname, 'rf')
    732 endfun
    733 
    734 " s:FileHeader: {{{2
    735 fun! s:Header(fname)
    736  let header= readblob(a:fname, 0, 6)
    737  " Nvim: see https://github.com/neovim/neovim/pull/34968
    738  if header[0:2] == 0z425A68 " bzip2 header
    739    return "bzip2"
    740  elseif header[0:2] == 0z425A33 " bzip3 header
    741    return "bzip3"
    742  elseif header == 0zFD377A58.5A00 " xz header
    743    return "xz"
    744  elseif header[0:3] == 0z28B52FFD " zstd header
    745    return "zstd"
    746  elseif header[0:3] == 0z04224D18 " lz4 header
    747    return "lz4"
    748  elseif (header[0:1] == 0z1F9D ||
    749       \  header[0:1] == 0z1F8B ||
    750       \  header[0:1] == 0z1F9E ||
    751       \  header[0:1] == 0z1FA0 ||
    752       \  header[0:1] == 0z1F1E)
    753    return "gzip"
    754  endif
    755  return "unknown"
    756 endfun
    757 
    758 " ---------------------------------------------------------------------
    759 " s:WinPath: {{{2
    760 fun! s:WinPath(path)
    761  if (!g:netrw_cygwin || &shell !~ '\%(\<bash\>\|\<zsh\>\)\%(\.exe\)\=$') && has("win32")
    762    " remove cygdrive prefix, if present
    763    let path = substitute(a:path, '/cygdrive/\(.\)', '\1:', '')
    764    " remove trailing slash (Win95)
    765    let path = substitute(path, '\(\\\|/\)$', '', 'g')
    766    " remove escaped spaces
    767    let path = substitute(path, '\ ', ' ', 'g')
    768    " convert slashes to backslashes
    769    let path = substitute(path, '/', '\', 'g')
    770  else
    771    let path = a:path
    772  endif
    773 
    774  return path
    775 endfun
    776 
    777 " =====================================================================
    778 " Modelines And Restoration: {{{1
    779 let &cpo= s:keepcpo
    780 unlet s:keepcpo
    781 " vim:ts=8 fdm=marker