vim-patch:b0e0b22: runtime(zip): Improve Compatibility for powershell 5 and pwsh 7

related: vim/vim#20832

b0e0b22535

Co-authored-by: curbe454 <asu454@outlook.com>
Co-Authored-By: curbe454 <asu454@outlook.com>
This commit is contained in:
zeertzjq
2026-07-31 09:27:15 +08:00
parent 6cdab150bc
commit 0e3e15d161
2 changed files with 82 additions and 29 deletions

View 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,15 +81,54 @@ if !has('nvim-0.10') && v:version < 901
call s:Mess('WarningMsg', "***warning*** this version of zip needs vim 9.1 or later")
finish
endif
" ---------------------------------------------------------------------
" 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 (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
if !executable(g:zip_unzipcmd) && &shell !~ 'pwsh'
call s:Mess('Error', "***error*** (zip#Browse) unzip not available on your system")
finish
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
endif
" 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

View File

@@ -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