]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
runtime(zip): Improve Compatibility for powershell 5 and pwsh 7
authorcurbe454 <asu454@outlook.com>
Thu, 30 Jul 2026 18:14:51 +0000 (18:14 +0000)
committerChristian Brabandt <cb@256bit.org>
Thu, 30 Jul 2026 18:18:03 +0000 (18:18 +0000)
related: #20832

Co-Authored-By: curbe454 <asu454@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/autoload/zip.vim
runtime/doc/pi_zip.txt

index 44bdfc68201c5727988911619250664da601bf9c..1d0ecff45b03cf7cb15a38fec1513f2bf7f24f25 100644 (file)
@@ -25,6 +25,7 @@
 " 2026 Apr 14 by Vim Project: Detect more path traversal attacks on Windows
 " 2026 Apr 15 by Vim Project: Detect more path traversal attacks on Windows
 " 2026 Jun 20 by Vim Project: Fix wrong escaping for the powershell calls
+" 2026 Jul 25 by Vim Project: Improved Compatibility for powershell 5 and pwsh 7
 " License:     Vim License  (see vim's :help license)
 " Copyright:   Copyright (C) 2005-2019 Charles E. Campbell {{{1
 "              Permission is hereby granted to use and distribute this code,
@@ -61,6 +62,9 @@ endif
 if !exists("g:zip_extractcmd")
  let g:zip_extractcmd= g:zip_unzipcmd
 endif
+if !exists("g:zip_pwsh")
+  let g:zip_pwsh=''
+endif
 
 " ---------------------------------------------------------------------
 "  required early
@@ -77,16 +81,55 @@ if v:version < 901
  call s:Mess('WarningMsg', "***warning*** this version of zip needs vim 9.1 or later")
  finish
 endif
-" sanity checks
-if !executable(g:zip_unzipcmd) && &shell !~ 'pwsh'
- call s:Mess('Error', "***error*** (zip#Browse) unzip not available on your system")
- finish
+
+" ---------------------------------------------------------------------
+"  Compatibility checks
+" PowerShell: {{{2
+" Existence of powershell always means a Windows OS. By default the
+" `zip`/`unzip` is not installed on Windows, but the runtime of
+" powershell support relative functionalities about zip compression
+" and extraction.
+" In the following implementations we'll use some powershell scripts
+" to manipulate zip files. All these scripts should available to both
+" Windows PowerShell 5 and pwsh 7+.
+
+let s:ps = ''
+" order: `g:zip_pwsh` > `&shell`, with executable check
+if &shell =~# 'powershell' || g:zip_pwsh =~# 'powershell'
+  if executable('powershell')
+    let s:ps = 'powershell'
+  endif
 endif
-if !dist#vim#IsSafeExecutable('zip', g:zip_unzipcmd) && &shell !~ 'pwsh'
- call s:Mess('Error', "Warning: NOT executing " .. g:zip_unzipcmd .. " from current directory!")
- finish
+if (empty(g:zip_pwsh) && &shell =~# 'pwsh') || g:zip_pwsh =~# 'pwsh'
+  if executable('pwsh')
+    let s:ps = 'pwsh'
+  endif
 endif
 
+fun! s:isPS()
+  return !empty(s:ps)
+endfun
+
+" ---------------------------------------------------------------------
+" sanity checks
+" s:SafeExecutable: {{{2
+fun! s:SafeExecutable(exe)
+  if !executable(a:exe) && !s:isPS()
+    call s:Mess('Error', "***error*** (zip) '".a:exe."' not available on your system")
+    return v:false
+  endif
+  if !dist#vim#IsSafeExecutable('zip', a:exe) && !s:isPS()
+    call s:Mess('Error', "Warning: NOT executing " .. a:exe .. " from current directory!")
+    return v:false
+  endif
+  return v:true
+endfun
+" guarantee default command is exist and not be injected by environment
+" every default command should be checked
+if !s:SafeExecutable(g:zip_zipcmd)  | finish | endif
+if !s:SafeExecutable(g:zip_unzipcmd)  | finish | endif
+if !s:SafeExecutable(g:zip_extractcmd)  | finish | endif
+
 " ----------------
 "  PowerShell: {{{1
 " ----------------
@@ -94,8 +137,8 @@ endif
 function! s:TryExecGnuFallBackToPs(executable, gnu_func_call, ...)
   " Check that a gnu executable is available, run the gnu_func_call if so. If
   " the gnu executable is not available or if gnu_func_call fails, try
-  " ps_func_call if &shell =~ 'pwsh'. If all attempts fail, print errors.
-  " a:executable - one of (g:zip_zipcmd, g:zip_unzipcmd, g:zip_extractcmd)
+  " ps_func_call if `s:isPS()`. If all attempts fail, print errors.
+  " a:executable - (string) name of the executable program
   " a:gnu_func_call - (string) a gnu function call to execute
   " a:1 - (optional string) a PowerShell function call to execute.
   let failures = []
@@ -109,12 +152,12 @@ function! s:TryExecGnuFallBackToPs(executable, gnu_func_call, ...)
   else
     call add(failures, a:executable.' not available on your system')
   endif
-  if &shell =~ 'pwsh' && a:0 == 1
+  if s:isPS() && a:0 == 1
     try
       exe a:1
       return
     catch
-      call add(failures, 'Fallback to PowerShell attempted but failed')
+      call add(failures, 'Fallback to PowerShell attempted but failed: '.a:1)
     endtry
   endif
   for msg in failures
@@ -127,20 +170,22 @@ function! s:ZipBrowsePS(zipfile)
   " Browse the contents of a zip file using PowerShell's
   " Equivalent `unzip -Z1 -- zipfile`
   let cmds = [
+        \ 'Add-Type -AssemblyName System.IO.Compression.FileSystem;',
         \ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:PSEscape(a:zipfile) . ');',
         \ '$zip.Entries | ForEach-Object { $_.FullName };',
         \ '$zip.Dispose()'
         \ ]
-  return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1)
+  return s:ps . ' -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1)
 endfunction
 
 function! s:ZipReadPS(zipfile, fname, tempfile)
   " Read a filename within a zipped file to a temporary file.
   " Equivalent to `unzip -p -- zipfile fname > tempfile`
-  if &shell =~ 'pwsh'
+  if s:isPS()
     call s:Mess('WarningMsg', "***warning*** PowerShell can display, but cannot update, files in archive subfolders")
   endif
   let cmds = [
+        \ 'Add-Type -AssemblyName System.IO.Compression.FileSystem;',
         \ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:PSEscape(a:zipfile) . ');',
         \ '$fileEntry = $zip.Entries | Where-Object { $_.FullName -eq ' . s:PSEscape(a:fname) . ' };',
         \ '$stream = $fileEntry.Open();',
@@ -150,13 +195,13 @@ function! s:ZipReadPS(zipfile, fname, tempfile)
         \ '$stream.Close();',
         \ '$zip.Dispose()'
         \ ]
-  return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '))
+  return s:ps . ' -NoProfile -Command ' . s:Escape(join(cmds, ' '))
 endfunction
 
 function! s:ZipUpdatePS(zipfile, fname)
   " Update a filename within a zipped file
   " Equivalent to `zip -u zipfile fname`
-  if &shell =~ 'pwsh' && a:fname =~ '/'
+  if s:isPS() && a:fname =~ '/'
     call s:Mess('Error', "***error*** PowerShell cannot update files in archive subfolders")
     return ':'
   endif
@@ -171,6 +216,7 @@ function! s:ZipExtractFilePS(zipfile, fname)
     return ':'
   endif
   let cmds = [
+        \ 'Add-Type -AssemblyName System.IO.Compression.FileSystem;',
         \ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:PSEscape(a:zipfile) . ');',
         \ '$fileEntry = $zip.Entries | Where-Object { $_.FullName -eq ' . s:PSEscape(a:fname) . ' };',
         \ '$stream = $fileEntry.Open();',
@@ -180,7 +226,7 @@ function! s:ZipExtractFilePS(zipfile, fname)
         \ '$stream.Close();',
         \ '$zip.Dispose()'
         \ ]
-  return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '))
+  return s:ps . ' -NoProfile -Command ' . s:Escape(join(cmds, ' '))
 endfunction
 
 function! s:ZipDeleteFilePS(zipfile, fname)
@@ -193,7 +239,7 @@ function! s:ZipDeleteFilePS(zipfile, fname)
         \ 'if ($entry) { $entry.Delete(); $zip.Dispose() }',
         \ 'else { $zip.Dispose() }'
         \ ]
-  return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '))
+  return s:ps . ' -NoProfile -Command ' . s:Escape(join(cmds, ' '))
 endfunction
 
 " ----------------
@@ -214,7 +260,7 @@ fun! zip#Browse(zipfile)
   defer s:RestoreOpts(dict)
 
   " sanity checks
-  if !executable(g:zip_unzipcmd) && &shell !~ 'pwsh'
+  if !executable(g:zip_unzipcmd) && !s:isPS()
    call s:Mess('Error', "***error*** (zip#Browse) unzip not available on your system")
    return
   endif
@@ -322,7 +368,7 @@ fun! zip#Read(fname,mode)
   endif
   let fname    = fname->substitute('[', '[[]', 'g')->escape('?*\\')
   " sanity check
-  if !executable(substitute(g:zip_unzipcmd,'\s\+.*$','',''))  && &shell !~ 'pwsh'
+  if !executable(substitute(g:zip_unzipcmd,'\s\+.*$','',''))  && !s:isPS()
    call s:Mess('Error', "***error*** (zip#Read) sorry, your system doesn't appear to have the ".g:zip_unzipcmd." program")
    return
   endif
@@ -442,7 +488,7 @@ fun! zip#Write(fname)
   let ps_cmd = s:ZipUpdatePS(zip, fname)
   let ps_cmd = 'call system(''' . substitute(ps_cmd, "'", "''", 'g') . ''')'
   call s:TryExecGnuFallBackToPs(g:zip_zipcmd, gnu_cmd, ps_cmd)
-  if &shell =~ 'pwsh'
+  if s:isPS()
     " Vim flashes 'creation in progress ...' from what I believe is the
     " ProgressAction stream of PowerShell. Unfortunately, this cannot be
     " suppressed (as of 250824) due to an open PowerShell issue.
@@ -540,7 +586,7 @@ fun! zip#Extract()
 
   if v:shell_error != 0
     call s:Mess('Error', "***error*** ".g:zip_extractcmd." ".b:zipfile." ".fname.": failed!")
-  elseif !filereadable(fname) && &shell !~ 'pwsh'
+  elseif !filereadable(fname) && !s:isPS()
     call s:Mess('Error', "***error*** attempted to extract ".fname." but it doesn't appear to be present!")
   else
     echomsg "***note*** successfully extracted ".fname
index dc462cfefc50edf116e941efcf9e1ba4cd6279e4..271fc11e980a5201e13fe5ea0530ea5481f069c8 100644 (file)
@@ -1,4 +1,4 @@
-*pi_zip.txt*   For Vim version 9.2.  Last change: 2026 Jun 20
+*pi_zip.txt*   For Vim version 9.2.  Last change: 2026 Jul 30
 
                                +====================+
                                | Zip File Interface |
@@ -71,16 +71,22 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell        *zip-copyright*
    "0": >
        let g:zip_exec=0
 <
-   FALLBACK TO POWERSHELL CORE~
+   FALLBACK TO POWERSHELL~
 
+                                                       *g:zip_pwsh*
    This plugin will first attempt to use the (more capable) GNU zip/unzip
    commands.  If these commands are not available or fail, and the user is
-   using PowerShell Core (i.e., the 'shell' option matches "pwsh"), the
-   plugin will fall back to a PowerShell Core cmdlet.  The PowerShell Core
-   cmdlets are limited: they cannot write or extract files within
-   subdirectories of a zip archive.  The advantage, however, is that no
-   separate unzip binary needs to be installed.
-
+   using PowerShell, the plugin will fall back to a PowerShell cmdlet.
+   The PowerShell cmdlets are limited: they cannot write or extract files
+   within subdirectories of a zip archive.  The advantage, however, is that
+   no separate unzip binary needs to be installed.  The 'shell' option will
+   be checked whether to choose "pwsh" or "powershell", or set `g:zip_pwsh`.
+   If both are available, this plugin will prefer pwsh. Default value of
+   "g:zip_pwsh" is an empty string, which would not overwrite the 'shell'
+   option configuration.  This functionality is not turned on by default on
+   MS-Windows, since the default 'shell' is "cmd.exe". For example: >
+       let g:zip_pwsh='powershell'
+<
    PREVENTING LOADING~
 
    If for some reason you do not wish to use vim to examine zipped files,
@@ -116,6 +122,7 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell        *zip-copyright*
 ==============================================================================
 4. History                                                     *zip-history* {{{1
    unreleased:
+       Jul 25, 2026 * support both Windows PowerShell 5 and pwsh 7+
        Sep 19, 2025 * support PowerShell Core
        Jul 12, 2025 * drop ../ on write to prevent path traversal attacks
        Mar 11, 2025 * handle filenames with leading '-' correctly