From 0e3e15d161d700ddccb829de7ff5de727073db5f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 31 Jul 2026 09:27:15 +0800 Subject: [PATCH] vim-patch:b0e0b22: runtime(zip): Improve Compatibility for powershell 5 and pwsh 7 related: vim/vim#20832 https://github.com/vim/vim/commit/b0e0b22535079e478c3acfb47a61709c3c59ff2d Co-authored-by: curbe454 Co-Authored-By: curbe454 --- .../pack/dist/opt/old-zip/autoload/zip.vim | 90 ++++++++++++++----- runtime/pack/dist/opt/old-zip/doc/pi_zip.txt | 21 +++-- 2 files changed, 82 insertions(+), 29 deletions(-) diff --git a/runtime/pack/dist/opt/old-zip/autoload/zip.vim b/runtime/pack/dist/opt/old-zip/autoload/zip.vim index 458b8a1f20..561cda4072 100644 --- a/runtime/pack/dist/opt/old-zip/autoload/zip.vim +++ b/runtime/pack/dist/opt/old-zip/autoload/zip.vim @@ -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 diff --git a/runtime/pack/dist/opt/old-zip/doc/pi_zip.txt b/runtime/pack/dist/opt/old-zip/doc/pi_zip.txt index c13ba221bc..eff5a546b2 100644 --- a/runtime/pack/dist/opt/old-zip/doc/pi_zip.txt +++ b/runtime/pack/dist/opt/old-zip/doc/pi_zip.txt @@ -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