]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0300: The vimball plugin needs some love v9.2.0300
authorChristian Brabandt <cb@256bit.org>
Sun, 5 Apr 2026 16:08:12 +0000 (16:08 +0000)
committerChristian Brabandt <cb@256bit.org>
Sun, 5 Apr 2026 16:11:53 +0000 (16:11 +0000)
Problem:  The vimball plugin needs some love
          (syndicate)
Solution: Clean-up, refactor and update the plugin,
          in particular, catch path traversal attacks

This change does the following
- Clean up Indentation and remove calls to Decho
- Increase minimum Vim version to 7.4 for mkdir()
- Use mkdir() consistently
- Update Metadata Header
- Remove check for fnameescape()
- Catch path traversal attacks
- Add vimball basic tests
- Remove mentioning of g:vimball_mkdir in documentation

closes: #19921

Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/autoload/vimball.vim
runtime/doc/pi_vimball.txt
runtime/doc/tags
src/testdir/Make_all.mak
src/testdir/test_plugin_vimball.vim [new file with mode: 0644]
src/version.c

index 6456984411c270b1bb234fe88acc646b5df83d8d..fb4df5eb6659f0bf0bf4249db6b99731cf0f652c 100644 (file)
@@ -6,6 +6,7 @@
 " GetLatestVimScripts: 1502 1 :AutoInstall: vimball.vim
 "  Last Change:
 "   2025 Feb 28 by Vim Project: add support for bzip3 (#16755)
+"   2026 Apr 05 by Vim Project: Detect Path Traversal Attacks
 " Copyright: (c) 2004-2011 by Charles E. Campbell
 "            The VIM LICENSE applies to Vimball.vim, and Vimball.txt
 "            (see |copyright|) except use "Vimball" instead of "Vim".
@@ -18,15 +19,14 @@ if &cp || exists("g:loaded_vimball")
  finish
 endif
 let g:loaded_vimball = "v37"
-if v:version < 702
+if v:version < 704
  echohl WarningMsg
- echo "***warning*** this version of vimball needs vim 7.2"
+ echo "***warning*** this version of vimball needs vim 7.4"
  echohl Normal
  finish
 endif
 let s:keepcpo= &cpo
 set cpo&vim
-"DechoTabOn
 
 " =====================================================================
 " Constants: {{{1
@@ -47,20 +47,6 @@ if !exists("s:USAGE")
    let g:netrw_cygwin= 0
   endif
  endif
-
- " set up g:vimball_mkdir if the mkdir() call isn't defined
- if !exists("*mkdir")
-  if exists("g:netrw_local_mkdir")
-   let g:vimball_mkdir= g:netrw_local_mkdir
-  elseif executable("mkdir")
-   let g:vimball_mkdir= "mkdir"
-  elseif executable("makedir")
-   let g:vimball_mkdir= "makedir"
-  endif
-  if !exists(g:vimball_mkdir)
-   call vimball#ShowMesg(s:WARNING,"(vimball) g:vimball_mkdir undefined")
-  endif
- endif
 endif
 
 " =====================================================================
@@ -81,7 +67,6 @@ endif
 "     filesize
 "     [file]
 fun! vimball#MkVimball(line1,line2,writelevel,...) range
-"  call Dfunc("MkVimball(line1=".a:line1." line2=".a:line2." writelevel=".a:writelevel." vimballname<".a:1.">) a:0=".a:0)
   if a:1 =~ '\.vim$' || a:1 =~ '\.txt$'
    let vbname= substitute(a:1,'\.\a\{3}$','.vmb','')
   else
@@ -90,15 +75,12 @@ fun! vimball#MkVimball(line1,line2,writelevel,...) range
   if vbname !~ '\.vmb$'
    let vbname= vbname.'.vmb'
   endif
-"  call Decho("vbname<".vbname.">")
   if !a:writelevel && a:1 =~ '[\/]'
    call vimball#ShowMesg(s:ERROR,"(MkVimball) vimball name<".a:1."> should not include slashes; use ! to insist")
-"   call Dret("MkVimball : vimball name<".a:1."> should not include slashes")
    return
   endif
   if !a:writelevel && filereadable(vbname)
    call vimball#ShowMesg(s:ERROR,"(MkVimball) file<".vbname."> exists; use ! to insist")
-"   call Dret("MkVimball : file<".vbname."> already exists; use ! to insist")
    return
   endif
 
@@ -120,17 +102,14 @@ fun! vimball#MkVimball(line1,line2,writelevel,...) range
   " record current tab, initialize while loop index
   let curtabnr = tabpagenr()
   let linenr   = a:line1
-"  call Decho("curtabnr=".curtabnr)
 
   while linenr <= a:line2
    let svfile  = getline(linenr)
-"   call Decho("svfile<".svfile.">")
  
    if !filereadable(svfile)
     call vimball#ShowMesg(s:ERROR,"unable to read file<".svfile.">")
-       call s:ChgDir(curdir)
-       call vimball#RestoreSettings()
-"    call Dret("MkVimball")
+    call s:ChgDir(curdir)
+    call vimball#RestoreSettings()
     return
    endif
  
@@ -145,20 +124,18 @@ fun! vimball#MkVimball(line1,line2,writelevel,...) range
  
    let lastline= line("$") + 1
    if lastline == 2 && getline("$") == ""
-       call setline(1,'" Vimball Archiver by Charles E. Campbell')
-       call setline(2,'UseVimball')
-       call setline(3,'finish')
-       let lastline= line("$") + 1
+    call setline(1,'" Vimball Archiver by Charles E. Campbell')
+    call setline(2,'UseVimball')
+    call setline(3,'finish')
+    let lastline= line("$") + 1
    endif
    call setline(lastline  ,substitute(svfile,'$','     [[[1',''))
    call setline(lastline+1,0)
 
    " write the file from the tab
-"   call Decho("exe $r ".fnameescape(svfile))
    exe "$r ".fnameescape(svfile)
 
    call setline(lastline+1,line("$") - lastline - 1)
-"   call Decho("lastline=".lastline." line$=".line("$"))
 
   " restore to normal tab
    exe "tabn ".curtabnr
@@ -170,13 +147,10 @@ fun! vimball#MkVimball(line1,line2,writelevel,...) range
   call s:ChgDir(curdir)
   setlocal ff=unix
   if a:writelevel
-"   call Decho("exe w! ".fnameescape(vbname))
    exe "w! ".fnameescape(vbname)
   else
-"   call Decho("exe w ".fnameescape(vbname))
    exe "w ".fnameescape(vbname)
   endif
-"  call Decho("Vimball<".vbname."> created")
   echo "Vimball<".vbname."> created"
 
   " remove the evidence
@@ -187,7 +161,6 @@ fun! vimball#MkVimball(line1,line2,writelevel,...) range
   " restore options
   call vimball#RestoreSettings()
 
-"  call Dret("MkVimball")
 endfun
 
 " ---------------------------------------------------------------------
@@ -195,17 +168,9 @@ endfun
 "                  (invoked the the UseVimball command embedded in 
 "                  vimballs' prologue)
 fun! vimball#Vimball(really,...)
-"  call Dfunc("vimball#Vimball(really=".a:really.") a:0=".a:0)
-
-  if v:version < 701 || (v:version == 701 && !exists('*fnameescape'))
-   echoerr "your vim is missing the fnameescape() function (pls upgrade to vim 7.2 or later)"
-"   call Dret("vimball#Vimball : needs 7.1 with patch 299 or later")
-   return
-  endif
 
   if getline(1) !~ '^" Vimball Archiver'
    echoerr "(Vimball) The current file does not appear to be a Vimball!"
-"   call Dret("vimball#Vimball")
    return
   endif
 
@@ -215,7 +180,6 @@ fun! vimball#Vimball(really,...)
   let vimballfile = expand("%:tr")
 
   " set up vimball tab
-"  call Decho("setting up vimball tab")
   tabnew
   sil! file Vimball
   let vbtabnr= tabpagenr()
@@ -227,21 +191,18 @@ fun! vimball#Vimball(really,...)
    " If, however, the user did not specify a full path, set the home to be below the current directory
    let home= expand(a:1)
    if has("win32") || has("win95") || has("win64") || has("win16")
-       if home !~ '^\a:[/\\]'
-        let home= getcwd().'/'.a:1
-       endif
+    if home !~ '^\a:[/\\]'
+      let home= getcwd().'/'.a:1
+    endif
    elseif home !~ '^/'
-       let home= getcwd().'/'.a:1
+    let home= getcwd().'/'.a:1
    endif
   else
    let home= vimball#VimballHome()
   endif
-"  call Decho("home<".home.">")
 
   " save current directory and remove older same-named vimball, if any
   let curdir = getcwd()
-"  call Decho("home<".home.">")
-"  call Decho("curdir<".curdir.">")
 
   call s:ChgDir(home)
   let s:ok_unablefind= 1
@@ -260,56 +221,48 @@ fun! vimball#Vimball(really,...)
   endif
 
   " apportion vimball contents to various files
-"  call Decho("exe tabn ".curtabnr)
   exe "tabn ".curtabnr
-"  call Decho("linenr=".linenr." line$=".line("$"))
   while 1 < linenr && linenr < line("$")
    let fname   = substitute(getline(linenr),'\t\[\[\[1$','','')
    let fname   = substitute(fname,'\\','/','g')
+   let fname   = resolve(simplify(fname))
    let fsize   = substitute(getline(linenr+1),'^\(\d\+\).\{-}$','\1','')+0
    let fenc    = substitute(getline(linenr+1),'^\d\+\s*\(\S\{-}\)$','\1','')
    let filecnt = filecnt + 1
-"   call Decho("fname<".fname."> fsize=".fsize." filecnt=".filecnt. " fenc=".fenc)
+   if fname =~ '\.\.'
+     echomsg "(Vimball) Path Traversal Attack detected, aborting..."
+     exe "tabn ".curtabnr
+     bw! Vimball
+     call s:ChgDir(curdir)
+     return
+   endif
 
    if a:really
     echomsg "extracted <".fname.">: ".fsize." lines"
    else
     echomsg "would extract <".fname.">: ".fsize." lines"
    endif
-"   call Decho("using L#".linenr.": will extract file<".fname.">")
-"   call Decho("using L#".(linenr+1).": fsize=".fsize)
 
    " Allow AsNeeded/ directory to take place of plugin/ directory
    " when AsNeeded/filename is filereadable or was present in VimballRecord
    if fname =~ '\<plugin/'
-       let anfname= substitute(fname,'\<plugin/','AsNeeded/','')
-       if filereadable(anfname) || (exists("s:VBRstring") && s:VBRstring =~# anfname)
-"       call Decho("using anfname<".anfname."> instead of <".fname.">")
-        let fname= anfname
-       endif
+     let anfname= substitute(fname,'\<plugin/','AsNeeded/','')
+     if filereadable(anfname) || (exists("s:VBRstring") && s:VBRstring =~# anfname)
+       let fname= anfname
+     endif
    endif
 
    " make directories if they don't exist yet
    if a:really
-"    call Decho("making directories if they don't exist yet (fname<".fname.">)")
     let fnamebuf= substitute(fname,'\\','/','g')
-       let dirpath = substitute(home,'\\','/','g')
-"      call Decho("init: fnamebuf<".fnamebuf.">")
-"      call Decho("init: dirpath <".dirpath.">")
+    let dirpath = substitute(home,'\\','/','g')
     while fnamebuf =~ '/'
      let dirname  = dirpath."/".substitute(fnamebuf,'/.*$','','')
-        let dirpath  = dirname
+     let dirpath  = dirname
      let fnamebuf = substitute(fnamebuf,'^.\{-}/\(.*\)$','\1','')
-"       call Decho("dirname<".dirname.">")
-"       call Decho("dirpath<".dirpath.">")
      if !isdirectory(dirname)
-"      call Decho("making <".dirname.">")
-      if exists("g:vimball_mkdir")
-          call system(g:vimball_mkdir." ".shellescape(dirname))
-      else
-       call mkdir(dirname)
-      endif
-         call s:RecordInVar(home,"rmdir('".dirname."')")
+      call mkdir(dirname)
+      call s:RecordInVar(home,"rmdir('".dirname."')")
      endif
     endwhile
    endif
@@ -319,13 +272,11 @@ fun! vimball#Vimball(really,...)
    " (skip over path/filename and qty-lines)
    let linenr   = linenr + 2
    let lastline = linenr + fsize - 1
-"   call Decho("exe ".linenr.",".lastline."yank a")
    " no point in handling a zero-length file
    if lastline >= linenr
     exe "silent ".linenr.",".lastline."yank a"
 
     " copy "a" buffer into tab
-"   call Decho('copy "a buffer into tab#'.vbtabnr)
     exe "tabn ".vbtabnr
     setlocal ma
     sil! %d
@@ -336,38 +287,31 @@ fun! vimball#Vimball(really,...)
     " write tab to file
     if a:really
      let fnamepath= home."/".fname
-"    call Decho("exe w! ".fnameescape(fnamepath))
-       if fenc != ""
-        exe "silent w! ++enc=".fnameescape(fenc)." ".fnameescape(fnamepath)
-       else
-        exe "silent w! ".fnameescape(fnamepath)
-       endif
-       echo "wrote ".fnameescape(fnamepath)
-       call s:RecordInVar(home,"call delete('".fnamepath."')")
+    if fenc != ""
+      exe "silent w! ++enc=".fnameescape(fenc)." ".fnameescape(fnamepath)
+    else
+      exe "silent w! ".fnameescape(fnamepath)
+    endif
+    echo "wrote ".fnameescape(fnamepath)
+    call s:RecordInVar(home,"call delete('".fnamepath."')")
     endif
 
     " return to tab with vimball
-"   call Decho("exe tabn ".curtabnr)
     exe "tabn ".curtabnr
 
     " set up help if it's a doc/*.txt file
-"   call Decho("didhelp<".didhelp."> fname<".fname.">")
     if a:really && didhelp == "" && fname =~ 'doc/[^/]\+\.\(txt\|..x\)$'
-       let didhelp= substitute(fname,'^\(.*\<doc\)[/\\][^.]*\.\(txt\|..x\)$','\1','')
-"      call Decho("didhelp<".didhelp.">")
+      let didhelp= substitute(fname,'^\(.*\<doc\)[/\\][^.]*\.\(txt\|..x\)$','\1','')
     endif
    endif
 
    " update for next file
-"   call Decho("update linenr= [linenr=".linenr."] + [fsize=".fsize."] = ".(linenr+fsize))
    let linenr= linenr + fsize
   endwhile
 
   " set up help
-"  call Decho("about to set up help: didhelp<".didhelp.">")
   if didhelp != ""
    let htpath= home."/".didhelp
-"   call Decho("exe helptags ".htpath)
    exe "helptags ".fnameescape(htpath)
    echo "did helptags"
   endif
@@ -388,8 +332,6 @@ fun! vimball#Vimball(really,...)
   exe "sil! tabc! ".vbtabnr
   call vimball#RestoreSettings()
   call s:ChgDir(curdir)
-
-"  call Dret("vimball#Vimball")
 endfun
 
 " ---------------------------------------------------------------------
@@ -399,23 +341,18 @@ endfun
 "  Usage:  RmVimball  (assume current file is a vimball; remove)
 "          RmVimball vimballname
 fun! vimball#RmVimball(...)
-"  call Dfunc("vimball#RmVimball() a:0=".a:0)
   if exists("g:vimball_norecord")
-"   call Dret("vimball#RmVimball : (g:vimball_norecord)")
    return
   endif
 
   if a:0 == 0
    let curfile= expand("%:tr")
-"   call Decho("case a:0=0: curfile<".curfile."> (used expand(%:tr))")
   else
    if a:1 =~ '[\/]'
     call vimball#ShowMesg(s:USAGE,"RmVimball vimballname [path]")
-"    call Dret("vimball#RmVimball : suspect a:1<".a:1.">")
     return
    endif
    let curfile= a:1
-"   call Decho("case a:0=".a:0.": curfile<".curfile.">")
   endif
   if curfile =~ '\.vmb$'
    let curfile= substitute(curfile,'\.vmb','','')
@@ -428,75 +365,60 @@ fun! vimball#RmVimball(...)
    let home= vimball#VimballHome()
   endif
   let curdir = getcwd()
-"  call Decho("home   <".home.">")
-"  call Decho("curfile<".curfile.">")
-"  call Decho("curdir <".curdir.">")
 
   call s:ChgDir(home)
   if filereadable(".VimballRecord")
-"   call Decho(".VimballRecord is readable")
-"   call Decho("curfile<".curfile.">")
    keepalt keepjumps 1split 
    sil! keepalt keepjumps e .VimballRecord
    let keepsrch= @/
-"   call Decho('search for ^\M'.curfile.'.\m: ')
-"   call Decho('search for ^\M'.curfile.'.\m{vba|vmb}: ')
-"   call Decho('search for ^\M'.curfile.'\m[-0-9.]*\.{vba|vmb}: ')
    if search('^\M'.curfile."\m: ".'cw')
-       let foundit= 1
+    let foundit= 1
    elseif search('^\M'.curfile.".\mvmb: ",'cw')
-       let foundit= 2
+    let foundit= 2
    elseif search('^\M'.curfile.'\m[-0-9.]*\.vmb: ','cw')
-       let foundit= 2
+    let foundit= 2
    elseif search('^\M'.curfile.".\mvba: ",'cw')
-       let foundit= 1
+    let foundit= 1
    elseif search('^\M'.curfile.'\m[-0-9.]*\.vba: ','cw')
-       let foundit= 1
+    let foundit= 1
    else
     let foundit = 0
    endif
    if foundit
-       if foundit == 1
-        let exestring  = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vba: ','','')
-       else
-        let exestring  = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vmb: ','','')
-       endif
+    if foundit == 1
+     let exestring  = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vba: ','','')
+    else
+     let exestring  = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vmb: ','','')
+    endif
     let s:VBRstring= substitute(exestring,'call delete(','','g')
     let s:VBRstring= substitute(s:VBRstring,"[')]",'','g')
-"      call Decho("exe ".exestring)
-       sil! keepalt keepjumps exe exestring
-       sil! keepalt keepjumps d
-       let exestring= strlen(substitute(exestring,'call delete(.\{-})|\=',"D","g"))
-"      call Decho("exestring<".exestring.">")
-       echomsg "removed ".exestring." files"
+    sil! keepalt keepjumps exe exestring
+    sil! keepalt keepjumps d
+    let exestring= strlen(substitute(exestring,'call delete(.\{-})|\=',"D","g"))
+    echomsg "removed ".exestring." files"
    else
     let s:VBRstring= ''
-       let curfile    = substitute(curfile,'\.vmb','','')
-"    call Decho("unable to find <".curfile."> in .VimballRecord")
-       if !exists("s:ok_unablefind")
+    let curfile    = substitute(curfile,'\.vmb','','')
+    if !exists("s:ok_unablefind")
      call vimball#ShowMesg(s:WARNING,"(RmVimball) unable to find <".curfile."> in .VimballRecord")
-       endif
+    endif
    endif
    sil! keepalt keepjumps g/^\s*$/d
    sil! keepalt keepjumps wq!
    let @/= keepsrch
   endif
   call s:ChgDir(curdir)
-
-"  call Dret("vimball#RmVimball")
 endfun
 
 " ---------------------------------------------------------------------
 " vimball#Decompress: attempts to automatically decompress vimballs {{{2
 fun! vimball#Decompress(fname,...)
-"  call Dfunc("Decompress(fname<".a:fname.">) a:0=".a:0)
-
   " decompression:
   if     expand("%") =~ '.*\.gz'  && executable("gunzip")
    " handle *.gz with gunzip
    silent exe "!gunzip ".shellescape(a:fname)
    if v:shell_error != 0
-       call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) gunzip may have failed with <".a:fname.">")
+    call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) gunzip may have failed with <".a:fname.">")
    endif
    let fname= substitute(a:fname,'\.gz$','','')
    exe "e ".escape(fname,' \')
@@ -506,7 +428,7 @@ fun! vimball#Decompress(fname,...)
    " handle *.gz with gzip -d
    silent exe "!gzip -d ".shellescape(a:fname)
    if v:shell_error != 0
-       call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "gzip -d" may have failed with <'.a:fname.">")
+    call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "gzip -d" may have failed with <'.a:fname.">")
    endif
    let fname= substitute(a:fname,'\.gz$','','')
    exe "e ".escape(fname,' \')
@@ -516,7 +438,7 @@ fun! vimball#Decompress(fname,...)
    " handle *.bz2 with bunzip2
    silent exe "!bunzip2 ".shellescape(a:fname)
    if v:shell_error != 0
-       call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip2 may have failed with <".a:fname.">")
+    call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip2 may have failed with <".a:fname.">")
    endif
    let fname= substitute(a:fname,'\.bz2$','','')
    exe "e ".escape(fname,' \')
@@ -526,7 +448,7 @@ fun! vimball#Decompress(fname,...)
    " handle *.bz2 with bzip2 -d
    silent exe "!bzip2 -d ".shellescape(a:fname)
    if v:shell_error != 0
-       call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip2 -d" may have failed with <'.a:fname.">")
+    call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip2 -d" may have failed with <'.a:fname.">")
    endif
    let fname= substitute(a:fname,'\.bz2$','','')
    exe "e ".escape(fname,' \')
@@ -536,7 +458,7 @@ fun! vimball#Decompress(fname,...)
    " handle *.bz3 with bunzip3
    silent exe "!bunzip3 ".shellescape(a:fname)
    if v:shell_error != 0
-       call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip3 may have failed with <".a:fname.">")
+    call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip3 may have failed with <".a:fname.">")
    endif
    let fname= substitute(a:fname,'\.bz3$','','')
    exe "e ".escape(fname,' \')
@@ -546,7 +468,7 @@ fun! vimball#Decompress(fname,...)
    " handle *.bz3 with bzip3 -d
    silent exe "!bzip3 -d ".shellescape(a:fname)
    if v:shell_error != 0
-       call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip3 -d" may have failed with <'.a:fname.">")
+    call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip3 -d" may have failed with <'.a:fname.">")
    endif
    let fname= substitute(a:fname,'\.bz3$','','')
    exe "e ".escape(fname,' \')
@@ -556,7 +478,7 @@ fun! vimball#Decompress(fname,...)
    " handle *.zip with unzip
    silent exe "!unzip ".shellescape(a:fname)
    if v:shell_error != 0
-       call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) unzip may have failed with <".a:fname.">")
+    call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) unzip may have failed with <".a:fname.">")
    endif
    let fname= substitute(a:fname,'\.zip$','','')
    exe "e ".escape(fname,' \')
@@ -564,14 +486,11 @@ fun! vimball#Decompress(fname,...)
   endif
 
   if a:0 == 0| setlocal noma bt=nofile fmr=[[[,]]] fdm=marker | endif
-
-"  call Dret("Decompress")
 endfun
 
 " ---------------------------------------------------------------------
 " vimball#ShowMesg: {{{2
 fun! vimball#ShowMesg(level,msg)
-"  call Dfunc("vimball#ShowMesg(level=".a:level." msg<".a:msg.">)")
 
   let rulerkeep   = &ruler
   let showcmdkeep = &showcmd
@@ -596,13 +515,10 @@ fun! vimball#ShowMesg(level,msg)
 
   let &ruler   = rulerkeep
   let &showcmd = showcmdkeep
-
-"  call Dret("vimball#ShowMesg")
 endfun
 " =====================================================================
 " s:ChgDir: change directory (in spite of Windoze) {{{2
 fun! s:ChgDir(newdir)
-"  call Dfunc("ChgDir(newdir<".a:newdir.">)")
   if (has("win32") || has("win95") || has("win64") || has("win16"))
    try
     exe 'silent cd '.fnameescape(substitute(a:newdir,'/','\\','g'))
@@ -618,33 +534,22 @@ fun! s:ChgDir(newdir)
     exe 'silent cd '.fnameescape(a:newdir)
    endtry
   endif
-"  call Dret("ChgDir : curdir<".getcwd().">")
 endfun
 
 " ---------------------------------------------------------------------
 " s:RecordInVar: record a un-vimball command in the .VimballRecord file {{{2
 fun! s:RecordInVar(home,cmd)
-"  call Dfunc("RecordInVar(home<".a:home."> cmd<".a:cmd.">)")
-  if a:cmd =~ '^rmdir'
-"   if !exists("s:recorddir")
-"    let s:recorddir= substitute(a:cmd,'^rmdir',"call s:Rmdir",'')
-"   else
-"    let s:recorddir= s:recorddir."|".substitute(a:cmd,'^rmdir',"call s:Rmdir",'')
-"   endif
-  elseif !exists("s:recordfile")
+  if !exists("s:recordfile")
    let s:recordfile= a:cmd
   else
    let s:recordfile= s:recordfile."|".a:cmd
   endif
-"  call Dret("RecordInVar : s:recordfile<".(exists("s:recordfile")? s:recordfile : "")."> s:recorddir<".(exists("s:recorddir")? s:recorddir : "").">")
 endfun
 
 " ---------------------------------------------------------------------
 " s:RecordInFile: {{{2
 fun! s:RecordInFile(home)
-"  call Dfunc("s:RecordInFile()")
   if exists("g:vimball_norecord")
-"   call Dret("s:RecordInFile : g:vimball_norecord")
    return
   endif
 
@@ -654,22 +559,19 @@ fun! s:RecordInFile(home)
    keepalt keepjumps 1split 
 
    let cmd= expand("%:tr").": "
-"   call Decho("cmd<".cmd.">")
 
    sil! keepalt keepjumps e .VimballRecord
    setlocal ma
    $
    if exists("s:recordfile") && exists("s:recorddir")
-       let cmd= cmd.s:recordfile."|".s:recorddir
+    let cmd= cmd.s:recordfile."|".s:recorddir
    elseif exists("s:recorddir")
-       let cmd= cmd.s:recorddir
+    let cmd= cmd.s:recorddir
    elseif exists("s:recordfile")
-       let cmd= cmd.s:recordfile
+    let cmd= cmd.s:recordfile
    else
-"    call Dret("s:RecordInFile : neither recordfile nor recorddir exist")
-       return
+    return
    endif
-"   call Decho("cmd<".cmd.">")
 
    " put command into buffer, write .VimballRecord `file
    keepalt keepjumps put=cmd
@@ -678,35 +580,28 @@ fun! s:RecordInFile(home)
    call s:ChgDir(curdir)
 
    if exists("s:recorddir")
-"      call Decho("unlet s:recorddir<".s:recorddir.">")
-       unlet s:recorddir
+    unlet s:recorddir
    endif
    if exists("s:recordfile")
-"      call Decho("unlet s:recordfile<".s:recordfile.">")
-       unlet s:recordfile
+    unlet s:recordfile
    endif
-  else
-"   call Decho("s:record[file|dir] doesn't exist")
   endif
-
-"  call Dret("s:RecordInFile")
 endfun
 
 " ---------------------------------------------------------------------
 " vimball#VimballHome: determine/get home directory path (usually from rtp) {{{2
 fun! vimball#VimballHome()
-"  call Dfunc("vimball#VimballHome()")
   if exists("g:vimball_home")
    let home= g:vimball_home
   else
    " go to vim plugin home
    for home in split(&rtp,',') + ['']
     if isdirectory(home) && filewritable(home) | break | endif
-       let basehome= substitute(home,'[/\\]\.vim$','','')
+    let basehome= substitute(home,'[/\\]\.vim$','','')
     if isdirectory(basehome) && filewritable(basehome)
-        let home= basehome."/.vim"
-        break
-       endif
+     let home= basehome."/.vim"
+     break
+    endif
    endfor
    if home == ""
     " just pick the first directory
@@ -717,18 +612,9 @@ fun! vimball#VimballHome()
    endif
   endif
   " insure that the home directory exists
-"  call Decho("picked home<".home.">")
   if !isdirectory(home)
-   if exists("g:vimball_mkdir")
-"      call Decho("home<".home."> isn't a directory -- making it now with g:vimball_mkdir<".g:vimball_mkdir.">")
-"    call Decho("system(".g:vimball_mkdir." ".shellescape(home).")")
-    call system(g:vimball_mkdir." ".shellescape(home))
-   else
-"      call Decho("home<".home."> isn't a directory -- making it now with mkdir()")
-    call mkdir(home)
-   endif
+   call mkdir(home)
   endif
-"  call Dret("vimball#VimballHome <".home.">")
   return home
 endfun
 
@@ -758,13 +644,11 @@ fun! vimball#SaveSettings()
   endif
   " vimballs should be in unix format
   setlocal ff=unix
-"  call Dret("SaveSettings")
 endfun
 
 " ---------------------------------------------------------------------
 " vimball#RestoreSettings: {{{2
 fun! vimball#RestoreSettings()
-"  call Dfunc("RestoreSettings()")
   let @a      = s:regakeep
   if exists("+acd")
    let &acd   = s:acdkeep
@@ -780,14 +664,12 @@ fun! vimball#RestoreSettings()
   let &l:ff   = s:ffkeep
   if s:makeep[0] != 0
    " restore mark a
-"   call Decho("restore mark-a: makeep=".string(makeep))
    call setpos("'a",s:makeep)
   endif
   if exists("+acd")
    unlet s:acdkeep
   endif
   unlet s:regakeep s:eikeep s:fenkeep s:hidkeep s:ickeep s:repkeep s:vekeep s:makeep s:lzkeep s:pmkeep s:ffkeep
-"  call Dret("RestoreSettings")
 endfun
 
 let &cpo = s:keepcpo
@@ -795,4 +677,4 @@ unlet s:keepcpo
 
 " ---------------------------------------------------------------------
 " Modelines: {{{1
-" vim: fdm=marker
+" vim: fdm=marker et
index fdf71730685d337cca98f2a5829fd8fee303eece..07fc68f7ebf42991015c925dbf7b788a0460b0e6 100644 (file)
@@ -1,4 +1,4 @@
-*pi_vimball.txt*       For Vim version 9.2.  Last change: 2026 Feb 14
+*pi_vimball.txt*       For Vim version 9.2.  Last change: 2026 Apr 05
 
                               ----------------
                               Vimball Archiver
@@ -93,21 +93,6 @@ MAKING A VIMBALL                                             *:MkVimball*
        make.
 
 
-MAKING DIRECTORIES VIA VIMBALLS                                *g:vimball_mkdir*
-
-       First, the |mkdir()| command is tried (not all systems support it).
-
-       If it doesn't exist, then if g:vimball_mkdir doesn't exist, it is set
-       as follows: >
-         |g:netrw_localmkdir|, if it exists
-         "mkdir"             , if it is executable
-         "makedir"           , if it is executable
-         Otherwise           , it is undefined.
-<      One may explicitly specify the directory making command using
-       g:vimball_mkdir.  This command is used to make directories that
-       are needed as indicated by the vimball.
-
-
 CONTROLLING THE VIMBALL EXTRACTION DIRECTORY           *g:vimball_home*
 
        You may override the use of the 'runtimepath' by specifying a
index 2e9fc21ed4e985230df45b5fbe9265c32433bbd7..c0b6ace10924b041b27a2bc8307f615e5e52a3f1 100644 (file)
@@ -8038,7 +8038,6 @@ g:vim_indent_cont indent.txt      /*g:vim_indent_cont*
 g:vim_json_conceal     syntax.txt      /*g:vim_json_conceal*
 g:vim_json_warnings    syntax.txt      /*g:vim_json_warnings*
 g:vimball_home pi_vimball.txt  /*g:vimball_home*
-g:vimball_mkdir        pi_vimball.txt  /*g:vimball_mkdir*
 g:vimsyn_comment_strings       syntax.txt      /*g:vimsyn_comment_strings*
 g:vimsyn_embed syntax.txt      /*g:vimsyn_embed*
 g:vimsyn_folding       syntax.txt      /*g:vimsyn_folding*
index 28a5ec663a9f8e860d47686786366e0b8d69ab59..c0e4e68860ae217b1884059b80176868678c0347 100644 (file)
@@ -255,6 +255,7 @@ NEW_TESTS = \
        test_plugin_termdebug \
        test_plugin_tohtml \
        test_plugin_tutor \
+       test_plugin_vimball \
        test_plugin_zip \
        test_plus_arg_edit \
        test_popup \
@@ -531,6 +532,7 @@ NEW_TESTS_RES = \
        test_plugin_termdebug.res \
        test_plugin_tohtml.res \
        test_plugin_tutor.res \
+       test_plugin_vimball.res \
        test_plugin_zip.res \
        test_plus_arg_edit.res \
        test_popup.res \
diff --git a/src/testdir/test_plugin_vimball.vim b/src/testdir/test_plugin_vimball.vim
new file mode 100644 (file)
index 0000000..2d5b4ba
--- /dev/null
@@ -0,0 +1,85 @@
+" Test for the vimball plugin
+
+let s:testdir = expand("<script>:h")
+let s:default_vimball = ['" Vimball Archiver by Charles E. Campbell',
+                       \ 'UseVimball',
+                       \ 'finish',
+                       \ 'XVimball/Xtest.txt   [[[1',
+                       \ '2',
+                       \ 'Hello Vimball',
+                       \ '123']
+
+func SetUp()
+  ru plugin/vimballPlugin.vim
+  let g:vimball_home = s:testdir
+endfunc
+
+func TearDown()
+  call delete('Xtest.vmb')
+  call delete('.VimballRecord')
+endfunc
+
+func s:setup()
+  call mkdir('XVimball', 'p')
+  call writefile(['Hello Vimball', '123'], 'XVimball/Xtest.txt')
+endfunc
+
+func s:teardown()
+  call delete('XVimball', 'rf')
+  call delete('Xtest.vmb')
+  bw! Xtest.vmb
+  bw! XVimball/Xtest.txt
+  if bufloaded('.VimballRecord')
+    bw! .VimballRecord
+  endif
+endfunc
+
+func s:Mkvimball()
+  call s:setup()
+  new
+  0put ='XVimball/Xtest.txt'
+  $d
+  1,1MkVimball! Xtest
+  bw!
+endfunc
+
+func Test_vimball_basic()
+  call s:Mkvimball()
+  call assert_true(filereadable('Xtest.vmb'), 'vimball file should be created')
+  call assert_equal(s:default_vimball, readfile('Xtest.vmb'))
+
+  call delete('XVimball', 'rf')
+  sp Xtest.vmb
+  let mess = execute(':mess')
+  call assert_match('\*\*\*vimball\*\*\* Source this file to extract it!', mess)
+  so %
+  call feedkeys("\<cr>", "t")
+  unlet mess
+  let mess = execute(':mess')->split('\n')
+  call assert_equal('extracted <XVimball/Xtest.txt>: 2 lines', mess[-2])
+
+  call assert_true(filereadable('XVimball/Xtest.txt'), 'extracted file should exist')
+  call assert_equal(['Hello Vimball', '123'], readfile('XVimball/Xtest.txt'))
+  " Vimball extraction has been recorded
+  call assert_true(filereadable('.VimballRecord'))
+  let record = readfile('.VimballRecord')
+  call assert_equal(1, record->len())
+  call assert_match('^Xtest.vmb: rmdir.*call delete(', record[0])
+  call s:teardown()
+endfunc
+
+func Test_vimball_path_traversal()
+  call s:Mkvimball()
+  call delete('XVimball', 'rf')
+  sp Xtest.vmb
+  " try to write into upper dir
+  4s#XVimball#../&#
+  so %
+  call feedkeys("\<cr>", "it")
+
+  let mess = execute(':mess')->split('\n')[-1]
+  call assert_match('(Vimball) Path Traversal Attack detected, aborting\.\.\.', mess)
+  call assert_false(filereadable('../XVimball/Xtest.txt'))
+  call s:teardown()
+endfunc
index 4d0ec69447f6f117adab29f673b0daa43299baa6..4884c3b5349904e641cc31bd355b2b992dcbaf2f 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    300,
 /**/
     299,
 /**/