diff options
| author | zeertzjq <zeertzjq@outlook.com> | 2026-04-23 13:05:04 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-23 13:05:04 +0800 |
| commit | db2f6a8a918f33abf10959949fb9e2a2f82dae4f (patch) | |
| tree | 77cf8846218056bfcf4a543e6939c084b01feabb | |
| parent | fd2ea2742536a3bd7711190d94224008d7ca1e84 (diff) | |
| parent | 313e0f928168d9ef4573b4de5db3faf02bb1cbd3 (diff) | |
Merge pull request #37882 from zeertzjq/vim-a2d87ba
vim-patch: netrw updates
| -rw-r--r-- | runtime/pack/dist/opt/netrw/autoload/netrw.vim | 337 | ||||
| -rw-r--r-- | runtime/pack/dist/opt/netrw/autoload/netrw/fs.vim | 3 | ||||
| -rw-r--r-- | runtime/pack/dist/opt/netrw/autoload/netrw/msg.vim | 2 | ||||
| -rw-r--r-- | runtime/pack/dist/opt/netrw/doc/netrw.txt | 4 | ||||
| -rw-r--r-- | test/old/testdir/test_plugin_netrw.vim | 577 |
5 files changed, 730 insertions, 193 deletions
diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw.vim b/runtime/pack/dist/opt/netrw/autoload/netrw.vim index db3c1b8348..0f40231caf 100644 --- a/runtime/pack/dist/opt/netrw/autoload/netrw.vim +++ b/runtime/pack/dist/opt/netrw/autoload/netrw.vim @@ -18,6 +18,16 @@ " 2025 Nov 28 by Vim Project fix undefined variable in *NetrwMenu #18829 " 2025 Dec 26 by Vim Project fix use of g:netrw_cygwin #19015 " 2026 Jan 19 by Vim Project do not create swapfiles #18854 +" 2026 Feb 15 by Vim Project fix global variable initialization for MS-Windows #19287 +" 2026 Feb 21 by Vim Project better absolute path detection on MS-Windows #19477 +" 2026 Feb 27 by Vim Project Make the hostname validation more strict +" 2026 Mar 01 by Vim Project include portnumber in hostname checking #19533 +" 2026 Apr 01 by Vim Project use fnameescape() with netrw#FileUrlEdit() +" 2026 Apr 05 by Vim Project Fix netrw#RFC2396() #19913 +" 2026 Apr 15 by Vim Project Add missing escape() +" 2026 Apr 19 by Vim Project expand ~ on Windows #20003 +" 2026 Apr 21 by Vim Project fix shell-injection via tempfile suffix (sftp://, file://) +" 2026 Apr 21 by Vim Project drop unused g:netrw_tmpfile_escape " Copyright: Copyright (C) 2016 Charles E. Campbell {{{1 " Permission is hereby granted to use and distribute this code, " with or without modifications, provided that this copyright @@ -265,8 +275,8 @@ if !exists("g:netrw_localcopycmd") let g:netrw_localcopycmdopt = '' if has("win32") && !g:netrw_cygwin - let g:netrw_localcopycmd = expand("$COMSPEC", v:true) - let g:netrw_localcopycmdopt = '/c copy' + let g:netrw_localcopycmd = $COMSPEC + let g:netrw_localcopycmdopt = ' /c copy' endif endif @@ -275,30 +285,31 @@ if !exists("g:netrw_localcopydircmd") let g:netrw_localcopydircmdopt = '-R' if has("win32") && !g:netrw_cygwin - let g:netrw_localcopydircmd = "cp" - call s:NetrwInit("g:netrw_localcopydircmdopt", "-R") + let g:netrw_localcopydircmd = "xcopy" + let g:netrw_localcopydircmdopt = " /E /I /H /C /Y" endif endif -if has("win32") - if g:netrw_cygwin - call s:NetrwInit("g:netrw_localmkdir","mkdir") +if !exists("g:netrw_localmkdir") + if has("win32") + if g:netrw_cygwin + let g:netrw_localmkdir= "mkdir" + else + let g:netrw_localmkdir = $COMSPEC + let g:netrw_localmkdiropt= " /c mkdir" + endif else - call s:NetrwInit("g:netrw_localmkdir",expand("$COMSPEC", v:true)) - call s:NetrwInit("g:netrw_localmkdiropt"," /c mkdir") + let g:netrw_localmkdir= "mkdir" endif -else - call s:NetrwInit("g:netrw_localmkdir","mkdir") endif -call s:NetrwInit("g:netrw_remote_mkdir","mkdir") if !exists("g:netrw_localmovecmd") if has("win32") if g:netrw_cygwin let g:netrw_localmovecmd= "mv" else - let g:netrw_localmovecmd = expand("$COMSPEC", v:true) - call s:NetrwInit("g:netrw_localmovecmdopt"," /c move") + let g:netrw_localmovecmd = $COMSPEC + let g:netrw_localmovecmdopt= " /c move" endif elseif has("unix") || has("macunix") let g:netrw_localmovecmd= "mv" @@ -391,7 +402,6 @@ else call s:NetrwInit("g:netrw_glob_escape",'*[]?`{~$\') endif call s:NetrwInit("g:netrw_menu_escape",'.&? \') -call s:NetrwInit("g:netrw_tmpfile_escape",' &;') call s:NetrwInit("s:netrw_map_escape","<|\n\r\\\<C-V>\"") if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4') let s:treedepthstring= "│ " @@ -522,8 +532,8 @@ function netrw#Explore(indx,dosplit,style,...) NetrwKeepj norm! 0 if a:0 > 0 - if a:1 =~ '^\~' && (has("unix") || g:netrw_cygwin) - let dirname= simplify(substitute(a:1,'\~',expand("$HOME"),'')) + if a:1 =~ '^\~' && (has("unix") || has("win32") || g:netrw_cygwin) + let dirname= simplify(substitute(a:1,'^\~',escape(expand("$HOME"),'\&~'),'')) elseif a:1 == '.' let dirname= simplify(exists("b:netrw_curdir")? b:netrw_curdir : getcwd()) if dirname !~ '/$' @@ -980,7 +990,7 @@ function netrw#Obtain(islocal,fname,...) " obtain a file from local b:netrw_curdir to (local) tgtdir if exists("b:netrw_curdir") && getcwd() != b:netrw_curdir let topath = netrw#fs#ComposePath(tgtdir,"") - if has("win32") + if has("win32") && !g:netrw_cygwin " transfer files one at time for fname in fnamelist call system(g:netrw_localcopycmd.g:netrw_localcopycmdopt." ".netrw#os#Escape(fname)." ".netrw#os#Escape(topath)) @@ -1812,14 +1822,14 @@ function netrw#NetRead(mode,...) "......................................... " NetRead: (sftp) NetRead Method #9 {{{3 elseif b:netrw_method == 9 - call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".netrw#os#Escape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile) + call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".netrw#os#Escape(g:netrw_machine.":".b:netrw_fname,1)." ".netrw#os#Escape(tmpfile,1)) let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) let b:netrw_lastfile = choice "......................................... " NetRead: (file) NetRead Method #10 {{{3 elseif b:netrw_method == 10 && exists("g:netrw_file_cmd") - call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_file_cmd." ".netrw#os#Escape(b:netrw_fname,1)." ".tmpfile) + call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_file_cmd." ".netrw#os#Escape(b:netrw_fname,1)." ".netrw#os#Escape(tmpfile,1)) let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) let b:netrw_lastfile = choice @@ -2588,13 +2598,29 @@ endfunction " s:NetrwValidateHostname: Validate that the hostname is valid {{{2 " Input: -" hostname +" hostname, may include an optional username and port number, e.g. +" user@hostname:port +" allow a alphanumeric hostname or an IPv(4/6) address " Output: " true if g:netrw_machine is valid according to RFC1123 #Section 2 function s:NetrwValidateHostname(hostname) - " RFC1123#section-2 mandates, a valid hostname starts with letters or digits - " so reject everyhing else - return a:hostname =~? '^[a-z0-9]' + " Username: + let user_pat = '\%([a-zA-Z0-9._-]\+@\)\?' + " Hostname: 1-64 chars, alphanumeric/dots/hyphens. + " No underscores. No leading/trailing dots/hyphens. + let host_pat = '[a-zA-Z0-9]\%([-a-zA-Z0-9.]\{0,62}[a-zA-Z0-9]\)\?' + " Port: 16 bit unsigned integer + let port_pat = '\%(:\d\{1,5\}\)\?$' + + " IPv4: 1-3 digits separated by dots + let ipv4_pat = '\%(\d\{1,3}\.\)\{3\}\d\{1,3\}' + + " IPv6: Hex, colons, and optional brackets + let ipv6_pat = '\[\?\%([a-fA-F0-9:]\{2,}\)\+\]\?' + + return a:hostname =~? '^'.user_pat.host_pat.port_pat || + \ a:hostname =~? '^'.user_pat.ipv4_pat.port_pat || + \ a:hostname =~? '^'.user_pat.ipv6_pat.port_pat endfunction " NetUserPass: set username and password for subsequent ftp transfer {{{2 @@ -3237,7 +3263,7 @@ function s:NetrwFile(fname) endif if !g:netrw_cygwin && has("win32") - if fname =~ '^\' || fname =~ '^\a:\' + if isabsolutepath(fname) " windows, but full path given let ret= fname else @@ -4735,38 +4761,16 @@ function s:NetrwMakeDir(usrhost) " requested new local directory is neither a pre-existing file or " directory, so make it! - if exists("*mkdir") - if has("unix") - call mkdir(fullnewdir,"p",xor(0777, system("umask"))) - else - call mkdir(fullnewdir,"p") - endif + if has("unix") + call mkdir(fullnewdir,"p",xor(0777, system("umask"))) else - let netrw_origdir= netrw#fs#Cwd(1) - if s:NetrwLcd(b:netrw_curdir) - return - endif - call netrw#os#Execute("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.netrw#os#Escape(newdirname,1)) - if v:shell_error != 0 - let @@= ykeep - call netrw#msg#Notify('ERROR', printf('consider setting g:netrw_localmkdir<%s> to something that works', g:netrw_localmkdir)) - return - endif - if !g:netrw_keepdir - if s:NetrwLcd(netrw_origdir) - return - endif - endif + call mkdir(fullnewdir,"p") endif - if v:shell_error == 0 - " refresh listing - let svpos= winsaveview() - call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) - call winrestview(svpos) - else - call netrw#msg#Notify('ERROR', printf('unable to make directory<%s>', newdirname)) - endif + " on success refresh listing + let svpos= winsaveview() + call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) + call winrestview(svpos) elseif !exists("b:netrw_method") || b:netrw_method == 4 " Remote mkdir: using ssh @@ -5307,6 +5311,8 @@ function s:NetrwMarkFileCompress(islocal) if a:islocal if g:netrw_keepdir let fname= netrw#os#Escape(netrw#fs#ComposePath(curdir,fname)) + else + let fname= netrw#os#Escape(fname) endif call system(exe." ".fname) if v:shell_error @@ -5350,17 +5356,14 @@ function s:NetrwMarkFileCopy(islocal,...) let curdir = s:NetrwGetCurdir(a:islocal) let curbufnr = bufnr("%") - if b:netrw_curdir !~ '/$' - if !exists("b:netrw_curdir") - let b:netrw_curdir= curdir - endif - let b:netrw_curdir= b:netrw_curdir."/" + if !exists("b:netrw_curdir") + let b:netrw_curdir= curdir endif " sanity check if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) call netrw#msg#Notify('ERROR', 'there are no marked files in this window (:help netrw-mf)') - return + return 0 endif if !exists("s:netrwmftgt") @@ -5368,7 +5371,7 @@ function s:NetrwMarkFileCopy(islocal,...) return 0 endif - if a:islocal && s:netrwmftgt_islocal + if a:islocal && s:netrwmftgt_islocal " Copy marked files, local directory to local directory if !executable(g:netrw_localcopycmd) call netrw#msg#Notify('ERROR', printf('g:netrw_localcopycmd<%s> not executable on your system, aborting', g:netrw_localcopycmd)) @@ -5376,69 +5379,85 @@ function s:NetrwMarkFileCopy(islocal,...) endif " copy marked files while within the same directory (ie. allow renaming) - if simplify(s:netrwmftgt) ==# simplify(b:netrw_curdir) - if len(s:netrwmarkfilelist_{bufnr('%')}) == 1 - " only one marked file - let args = netrw#os#Escape(b:netrw_curdir.s:netrwmarkfilelist_{bufnr('%')}[0]) - let oldname = s:netrwmarkfilelist_{bufnr('%')}[0] - elseif a:0 == 1 - " this happens when the next case was used to recursively call s:NetrwMarkFileCopy() - let args = netrw#os#Escape(b:netrw_curdir.a:1) - let oldname = a:1 - else - " copy multiple marked files inside the same directory - let s:recursive= 1 - for oldname in s:netrwmarkfilelist_{bufnr("%")} - let ret= s:NetrwMarkFileCopy(a:islocal,oldname) - if ret == 0 - break - endif - endfor - unlet s:recursive - call s:NetrwUnmarkList(curbufnr,curdir) - return ret - endif + if simplify(s:netrwmftgt."/") ==# simplify(b:netrw_curdir."/") + " copy multiple marked files inside the same directory + for oldname in s:netrwmarkfilelist_{curbufnr} + call inputsave() + let newname= input(printf("Copy %s to: ", oldname), oldname, 'file') + call inputrestore() - call inputsave() - let newname= input(printf("Copy %s to: ", oldname), oldname, 'file') - call inputrestore() + if empty(newname) + return 0 + endif - if empty(newname) - return 0 - endif + let tgt = netrw#fs#ComposePath(s:netrwmftgt, newname) + let oldname = netrw#fs#ComposePath(b:netrw_curdir, oldname) + if tgt ==# oldname + continue + endif - let args = netrw#os#Escape(oldname) - let tgt = netrw#os#Escape(s:netrwmftgt.'/'.newname) + let ret = filecopy(oldname, tgt) + if ret == v:false + call netrw#msg#Notify('ERROR', $'copy failed, unable to filecopy() <{oldname}> to <{tgt}>') + break + endif + endfor + call s:NetrwUnmarkList(curbufnr,curdir) + NetrwKeepj call s:NetrwRefreshDir(a:islocal, b:netrw_curdir) + return ret else - let args = join(map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),"netrw#os#Escape(b:netrw_curdir.\"/\".v:val)")) - let tgt = netrw#os#Escape(s:netrwmftgt) - endif - - if !g:netrw_cygwin && has("win32") - let args = substitute(args,'/','\\','g') - let tgt = substitute(tgt, '/','\\','g') + let args = [] + for arg in s:netrwmarkfilelist_{curbufnr} + call add(args, netrw#fs#ComposePath(b:netrw_curdir, arg)) + endfor + let tgt = s:netrwmftgt endif - if args =~ "'" |let args= substitute(args,"'\\(.*\\)'",'\1','')|endif - if tgt =~ "'" |let tgt = substitute(tgt ,"'\\(.*\\)'",'\1','')|endif - if args =~ '//'|let args= substitute(args,'//','/','g')|endif - if tgt =~ '//'|let tgt = substitute(tgt ,'//','/','g')|endif - let copycmd = g:netrw_localcopycmd let copycmdopt = g:netrw_localcopycmdopt - if isdirectory(s:NetrwFile(args)) + " on Windows, no builtin command supports copying multiple files at once + " (powershell's Copy-Item cmdlet does but requires , as file separator) + if len(s:netrwmarkfilelist_{curbufnr}) > 1 && has("win32") && !g:netrw_cygwin + " copy multiple marked files + for file in args + let dest = netrw#fs#ComposePath(tgt, fnamemodify(file, ':t')) + let ret = filecopy(file, dest) + if ret == v:false + call netrw#msg#Notify('ERROR', $'copy failed, unable to filecopy() <{file}> to <{dest}>') + break + endif + endfor + call s:NetrwUnmarkList(curbufnr,curdir) + NetrwKeepj call s:NetrwRefreshDir(a:islocal, b:netrw_curdir) + return ret + endif + + if len(args) == 1 && isdirectory(s:NetrwFile(args[0])) let copycmd = g:netrw_localcopydircmd let copycmdopt = g:netrw_localcopydircmdopt - if has('win32') && !g:netrw_cygwin + if has('win32') && g:netrw_localcopydircmd == "xcopy" " window's xcopy doesn't copy a directory to a target properly. Instead, it copies a directory's " contents to a target. One must append the source directory name to the target to get xcopy to " do the right thing. - let tgt= tgt.'\'.substitute(a:1,'^.*[\\/]','','') + let tgt = netrw#fs#ComposePath(tgt, fnamemodify(simplify(netrw#fs#PathJoin(args[0],".")),":t")) endif endif - call system(printf("%s %s '%s' '%s'", copycmd, copycmdopt, args, tgt)) + " prepare arguments for shell call + let args = join(map(args,'netrw#os#Escape(v:val)')) + let tgt = netrw#os#Escape(tgt) + + " enforce noshellslash for system calls + if exists('+shellslash') && &shellslash + for var in ['copycmd', 'args', 'tgt'] + let {var} = substitute({var}, '/', '\', 'g') + endfor + endif + + " shell call + let shell_cmd = printf("%s %s %s %s", copycmd, copycmdopt, args, tgt) + call system(shell_cmd) if v:shell_error != 0 if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && g:netrw_keepdir call netrw#msg#Notify('ERROR', printf("copy failed; perhaps due to vim's current directory<%s> not matching netrw's (%s) (see :help netrw-cd)", getcwd(), b:netrw_curdir)) @@ -5450,11 +5469,11 @@ function s:NetrwMarkFileCopy(islocal,...) elseif a:islocal && !s:netrwmftgt_islocal " Copy marked files, local directory to remote directory - NetrwKeepj call s:NetrwUpload(s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt) + NetrwKeepj call s:NetrwUpload(s:netrwmarkfilelist_{curbufnr},s:netrwmftgt) elseif !a:islocal && s:netrwmftgt_islocal " Copy marked files, remote directory to local directory - NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt) + NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{curbufnr},s:netrwmftgt) elseif !a:islocal && !s:netrwmftgt_islocal " Copy marked files, remote directory to remote directory @@ -5463,24 +5482,16 @@ function s:NetrwMarkFileCopy(islocal,...) if tmpdir !~ '/' let tmpdir= curdir."/".tmpdir endif - if exists("*mkdir") - call mkdir(tmpdir) - else - call netrw#os#Execute("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.netrw#os#Escape(tmpdir,1)) - if v:shell_error != 0 - call netrw#msg#Notify('WARNING', printf("consider setting g:netrw_localmkdir<%s> to something that works", g:netrw_localmkdir)) - return - endif - endif + call mkdir(tmpdir) if isdirectory(s:NetrwFile(tmpdir)) if s:NetrwLcd(tmpdir) return endif - NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},tmpdir) - let localfiles= map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),'substitute(v:val,"^.*/","","")') + NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{curbufnr},tmpdir) + let localfiles= map(deepcopy(s:netrwmarkfilelist_{curbufnr}),'substitute(v:val,"^.*/","","")') NetrwKeepj call s:NetrwUpload(localfiles,s:netrwmftgt) if getcwd() == tmpdir - for fname in s:netrwmarkfilelist_{bufnr('%')} + for fname in s:netrwmarkfilelist_{curbufnr} call netrw#fs#Remove(fname) endfor if s:NetrwLcd(curdir) @@ -5502,18 +5513,13 @@ function s:NetrwMarkFileCopy(islocal,...) " ------- " remove markings from local buffer call s:NetrwUnmarkList(curbufnr,curdir) " remove markings from local buffer - if exists("s:recursive") - else - endif " see s:LocalFastBrowser() for g:netrw_fastbrowse interpretation (refreshing done for both slow and medium) if g:netrw_fastbrowse <= 1 NetrwKeepj call s:LocalBrowseRefresh() else " refresh local and targets for fast browsing - if !exists("s:recursive") - " remove markings from local buffer - NetrwKeepj call s:NetrwUnmarkList(curbufnr,curdir) - endif + " remove markings from local buffer + NetrwKeepj call s:NetrwUnmarkList(curbufnr,curdir) " refresh buffers if s:netrwmftgt_islocal @@ -5641,6 +5647,8 @@ function s:NetrwMarkFileExe(islocal,enbloc) if a:islocal if g:netrw_keepdir let fname= netrw#os#Escape(netrw#fs#WinPath(netrw#fs#ComposePath(curdir,fname))) + else + let fname= netrw#os#Escape(netrw#fs#WinPath(fname)) endif else let fname= netrw#os#Escape(netrw#fs#WinPath(b:netrw_curdir.fname)) @@ -5882,28 +5890,35 @@ function s:NetrwMarkFileMove(islocal) call netrw#msg#Notify('ERROR', printf('g:netrw_localmovecmd<%s> not executable on your system, aborting', g:netrw_localmovecmd)) return endif + let tgt = netrw#os#Escape(s:netrwmftgt) - if !g:netrw_cygwin && has("win32") - let tgt= substitute(tgt, '/','\\','g') - if g:netrw_localmovecmd =~ '\s' - let movecmd = substitute(g:netrw_localmovecmd,'\s.*$','','') - let movecmdargs = substitute(g:netrw_localmovecmd,'^.\{-}\(\s.*\)$','\1','') - let movecmd = netrw#fs#WinPath(movecmd).movecmdargs - else - let movecmd = netrw#fs#WinPath(g:netrw_localmovecmd) - endif + if has("win32") && !g:netrw_cygwin && g:netrw_localmovecmd =~ '\s' && g:netrw_localmovecmdopt == "" + let movecmd = substitute(g:netrw_localmovecmd,'\s.*$','','') + let movecmdargs = substitute(g:netrw_localmovecmd,'^.\{-}\(\s.*\)$','\1','') else - let movecmd = netrw#fs#WinPath(g:netrw_localmovecmd) + let movecmd = g:netrw_localmovecmd + let movecmdargs = g:netrw_localmovecmdopt endif - for fname in s:netrwmarkfilelist_{bufnr("%")} + + " build args list + let args = [] + for fname in s:netrwmarkfilelist_{curbufnr} if g:netrw_keepdir " Jul 19, 2022: fixing file move when g:netrw_keepdir is 1 - let fname= b:netrw_curdir."/".fname - endif - if !g:netrw_cygwin && has("win32") - let fname= substitute(fname,'/','\\','g') + let fname= netrw#fs#ComposePath(b:netrw_curdir, fname) endif - let ret= system(movecmd.g:netrw_localmovecmdopt." ".netrw#os#Escape(fname)." ".tgt) + call add(args, netrw#os#Escape(fname)) + endfor + + " enforce noshellslash for system calls + if exists('+shellslash') && &shellslash + let tgt = substitute(tgt, '/', '\', 'g') + call map(args, "substitute(v:val, '/', '\\', 'g')") + endif + + for fname in args + let shell_cmd = printf("%s %s %s %s", movecmd, movecmdargs, fname, tgt) + let ret= system(shell_cmd) if v:shell_error != 0 if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir call netrw#msg#Notify('ERROR', printf("move failed; perhaps due to vim's current directory<%s> not matching netrw's (%s) (see :help netrw-cd)", getcwd(), b:netrw_curdir)) @@ -5916,7 +5931,7 @@ function s:NetrwMarkFileMove(islocal) elseif a:islocal && !s:netrwmftgt_islocal " move: local -> remote - let mflist= s:netrwmarkfilelist_{bufnr("%")} + let mflist= s:netrwmarkfilelist_{curbufnr} NetrwKeepj call s:NetrwMarkFileCopy(a:islocal) for fname in mflist let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','') @@ -5926,7 +5941,7 @@ function s:NetrwMarkFileMove(islocal) elseif !a:islocal && s:netrwmftgt_islocal " move: remote -> local - let mflist= s:netrwmarkfilelist_{bufnr("%")} + let mflist= s:netrwmarkfilelist_{curbufnr} NetrwKeepj call s:NetrwMarkFileCopy(a:islocal) for fname in mflist let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','') @@ -5936,7 +5951,7 @@ function s:NetrwMarkFileMove(islocal) elseif !a:islocal && !s:netrwmftgt_islocal " move: remote -> remote - let mflist= s:netrwmarkfilelist_{bufnr("%")} + let mflist= s:netrwmarkfilelist_{curbufnr} NetrwKeepj call s:NetrwMarkFileCopy(a:islocal) for fname in mflist let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','') @@ -8280,7 +8295,7 @@ function netrw#FileUrlEdit(fname) endif exe "sil doau BufReadPre ".fname2396e - exe 'NetrwKeepj keepalt edit '.plainfname + exe 'NetrwKeepj keepalt edit '. fnameescape(plainfname) exe 'sil! NetrwKeepj keepalt bdelete '.fnameescape(a:fname) exe "sil doau BufReadPost ".fname2396e @@ -8823,8 +8838,7 @@ endfunction " netrw#RFC2396: converts %xx into characters {{{2 function netrw#RFC2396(fname) - let fname = escape(substitute(a:fname,'%\(\x\x\)','\=printf("%c","0x".submatch(1))','ge')," \t") - return fname + return substitute(a:fname, '%\(\x\x\)', '\=printf("%c","0x".submatch(1))', 'ge') endfunction " netrw#UserMaps: supports user-specified maps {{{2 @@ -8956,14 +8970,17 @@ function s:GetTempfile(fname) endif " use fname's suffix for the temporary file + " Restrict the suffix to word characters so shell metacharacters in a + " remote filename (e.g. sftp://host/foo.txt;id) cannot ride along into + " the tempfile name and out into a downstream shell command. if a:fname != "" - if a:fname =~ '\.[^./]\+$' + if a:fname =~ '\.\w\+$' if a:fname =~ '\.tar\.gz$' || a:fname =~ '\.tar\.bz2$' || a:fname =~ '\.tar\.xz$' - let suffix = ".tar".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e') + let suffix = ".tar".substitute(a:fname,'^.*\(\.\w\+\)$','\1','e') elseif a:fname =~ '.txz$' - let suffix = ".txz".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e') + let suffix = ".txz".substitute(a:fname,'^.*\(\.\w\+\)$','\1','e') else - let suffix = substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e') + let suffix = substitute(a:fname,'^.*\(\.\w\+\)$','\1','e') endif let tmpfile= substitute(tmpfile,'\.tmp$','','e') let tmpfile .= suffix @@ -8977,15 +8994,15 @@ endfunction " s:MakeSshCmd: transforms input command using USEPORT HOSTNAME into {{{2 " a correct command for use with a system() call function s:MakeSshCmd(sshcmd) - if s:user == "" - let sshcmd = substitute(a:sshcmd,'\<HOSTNAME\>',s:machine,'') - else - let sshcmd = substitute(a:sshcmd,'\<HOSTNAME\>',s:user."@".s:machine,'') + let machine = shellescape(s:machine, 1) + if s:user != '' + let machine = shellescape(s:user, 1).'@'.machine endif + let sshcmd = substitute(a:sshcmd,'\<HOSTNAME\>',machine,'') if exists("g:netrw_port") && g:netrw_port != "" - let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.g:netrw_port,'') + let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.shellescape(g:netrw_port,1),'') elseif exists("s:port") && s:port != "" - let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.s:port,'') + let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.shellescape(s:port,1),'') else let sshcmd= substitute(sshcmd,"USEPORT ",'','') endif diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw/fs.vim b/runtime/pack/dist/opt/netrw/autoload/netrw/fs.vim index f2f98596a5..8c002a88c5 100644 --- a/runtime/pack/dist/opt/netrw/autoload/netrw/fs.vim +++ b/runtime/pack/dist/opt/netrw/autoload/netrw/fs.vim @@ -24,6 +24,7 @@ endfunction " netrw#fs#ComposePath: Appends a new part to a path taking different systems into consideration {{{ function! netrw#fs#ComposePath(base, subdir) + const slash = !exists('+shellslash') || &shellslash ? '/' : '\' if has('amiga') let ec = a:base[strdisplaywidth(a:base)-1] if ec != '/' && ec != ':' @@ -40,7 +41,7 @@ function! netrw#fs#ComposePath(base, subdir) if a:base =~ '[/\\]$' let ret = a:base . a:subdir else - let ret = a:base . '/' . a:subdir + let ret = a:base . slash . a:subdir endif elseif a:base =~ '^\a\{3,}://' diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw/msg.vim b/runtime/pack/dist/opt/netrw/autoload/netrw/msg.vim index 5f8c13a8d0..f6c21d173c 100644 --- a/runtime/pack/dist/opt/netrw/autoload/netrw/msg.vim +++ b/runtime/pack/dist/opt/netrw/autoload/netrw/msg.vim @@ -32,7 +32,7 @@ endfunction " netrw#msg#Notify('ERROR'|'WARNING'|'NOTE', ["message1","message2",...]) " (this function can optionally take a list of messages) function! netrw#msg#Notify(level, msg) - if has('nvim') + if has('nvim') && !v:testing " Convert string to corresponding vim.log.level value if a:level ==# 'ERROR' let level = 4 diff --git a/runtime/pack/dist/opt/netrw/doc/netrw.txt b/runtime/pack/dist/opt/netrw/doc/netrw.txt index eaa73bbbeb..65136816a1 100644 --- a/runtime/pack/dist/opt/netrw/doc/netrw.txt +++ b/runtime/pack/dist/opt/netrw/doc/netrw.txt @@ -2851,10 +2851,6 @@ your browsing preferences. (see also: |netrw-settings|) such as listing, file removal, etc. default: ssh - *g:netrw_tmpfile_escape* =' &;' - escape() is applied to all temporary files - to escape these characters. - *g:netrw_timefmt* specify format string to vim's strftime(). The default, "%c", is "the preferred date and time representation for the current diff --git a/test/old/testdir/test_plugin_netrw.vim b/test/old/testdir/test_plugin_netrw.vim index ab11c3da24..60e2f89cb7 100644 --- a/test/old/testdir/test_plugin_netrw.vim +++ b/test/old/testdir/test_plugin_netrw.vim @@ -1,32 +1,228 @@ -let s:netrw_path = $VIMRUNTIME . '/pack/dist/opt/netrw/autoload/netrw.vim' -let s:netrw_test_dir = 'samples' -let s:netrw_test_path = s:netrw_test_dir . '/netrw.vim' +const s:testdir = expand("<script>:h") +const s:runtimedir = simplify(s:testdir . '/../../../runtime') +const s:netrw_path = s:runtimedir . '/pack/dist/opt/netrw/autoload/netrw.vim' +const s:netrw_test_path = s:testdir . '/samples/netrw.vim' +const s:testScript =<< trim END + +" Testing functions: {{{1 +function! TestNetrwCaptureRemotePath(dirname) + call s:RemotePathAnalysis(a:dirname) + return {"method": s:method, "user": s:user, "machine": s:machine, "port": s:port, "path": s:path, "fname": s:fname} +endfunction + +" Test directory creation via s:NetrwMakeDir() +" Precondition: inputsave() and inputrestore() must be disabled in s:NetrwMakeDir + +function s:test_inputsave() + if exists("s:inputguards_disabled") && s:inputguards_disabled + return + endif + call inputsave() +endfunction + +function s:test_inputrestore() + if exists("s:inputguards_disabled") && s:inputguards_disabled + return + endif + call inputrestore() +endfunction + +function s:test_input(prompt, text = v:null, completion = v:null) " Nvim: use v:null instead of v:none + + if exists("s:inputdefaults_disabled") && s:inputdefaults_disabled || a:text == v:null + return input(a:prompt) + elseif a:completion == v:null + return input(a:prompt, a:text) + endif + + return input(a:prompt, a:text, a:completion) +endfunction + +function Test_NetrwMakeDir(parentdir = $HOME, dirname = "NetrwMakeDir", symlink = 0) abort + if a:symlink + " Plainly delegate, this device is necessary because feedkeys() can't + " access script functions directly. + call s:NetrwMakeDir('') + " wipe out the test buffer + bw + " reenable the guards + let s:inputguards_disabled = 0 + else + " Use feedkeys() to simulate user input (directory name) + new + let b:netrw_curdir = a:parentdir + let s:inputguards_disabled = 1 + call feedkeys($"\<Cmd>call Test_NetrwMakeDir('{a:parentdir}', '{a:dirname}', 1)\<CR>{a:dirname}\<CR>", "x") + endif +endfunction + +" Test file copy operations via s:NetrwMarkFileCopy() +function Test_NetrwMarkFileCopy(source_dir, target_dir, marked_files) abort + " set up + new + let b:netrw_curdir= a:source_dir + let s:netrwmftgt = a:target_dir + let s:netrwmarkfilelist_{bufnr("%")} = a:marked_files + let s:netrwmftgt_islocal = 1 + " delegate + call s:NetrwMarkFileCopy(1) + " wipe out the test buffer + bw +endfunction + +" Corner case: copy into the same dir triggers a user prompt +function Test_NetrwMarkFileCopy_SameDir(dir = $HOME, symlink = 0) abort + const filename = "filename.txt" + const file = netrw#fs#PathJoin(a:dir, filename) + + const newfilename = "newfilename.txt" + const newfile = netrw#fs#PathJoin(a:dir, newfilename) + + if a:symlink + " Plainly delegate, this device is necessary because feedkeys() can't + " access script functions directly. + " set up + new + let b:netrw_curdir = a:dir + let s:netrwmftgt = a:dir + let s:netrwmarkfilelist_{bufnr("%")} = [filename] + let s:netrwmftgt_islocal = 1 + + " delegate + call s:NetrwMarkFileCopy(1) + + " validate + call assert_equalfile(file, newfile, "File copy in same dir failed") + + " tear down + call delete(file) + call delete(newfile) + " wipe out the test buffer + bw + " reenable the guards + let s:inputguards_disabled = 0 + let s:inputdefaults_disabled = 0 + else + " Use feedkeys() to simulate user input (directory name) + let s:inputguards_disabled = 1 + let s:inputdefaults_disabled = 1 + + call writefile([$"NetrwMarkFileCopy test file"], file) + + call feedkeys($"\<Cmd>call Test_NetrwMarkFileCopy_SameDir('{a:dir}', 1)\<CR>{newfilename}\<CR>", "x") + endif +endfunction + +" Test file copy operations via s:NetrwMarkFileMove() +function Test_NetrwMarkFileMove(source_dir, target_dir, marked_files) abort + " set up + new + let b:netrw_curdir= a:source_dir + let s:netrwmftgt = a:target_dir + let s:netrwmarkfilelist_{bufnr("%")} = a:marked_files + let s:netrwmftgt_islocal = 1 + " delegate + call s:NetrwMarkFileMove(1) + " wipe out the test buffer + bw +endfunction + +" Test how netrw fixes paths according with settings +" (g:netrw_keepdir, g:netrw_cygwin, tree style ...) +function Test_NetrwFile(fname) abort + return s:NetrwFile(a:fname) +endfunction + +" Test hostname validation +function Test_NetrwValidateHostname(hostname) abort + return s:NetrwValidateHostname(a:hostname) +endfunction + +" }}} +END "make copy of netrw script and add function to print local variables" func s:appendDebugToNetrw(netrw_path, netrw_test_path) - let netrwScript = readfile(a:netrw_path) - let netrwScript += [ - \ '\n', - \ '"-- test helpers ---"', - \ 'function! TestNetrwCaptureRemotePath(dirname)', - \ ' call s:RemotePathAnalysis(a:dirname)', - \ ' return {"method": s:method, "user": s:user, "machine": s:machine, "port": s:port, "path": s:path, "fname": s:fname}', - \ 'endfunction' - \ ] + " load the netrw script + execute "split" a:netrw_test_path + execute "read" a:netrw_path + + " replace input guards for convenient testing versions + %substitute@call inputsave()@call s:test_inputsave()@g + %substitute@call inputrestore()@call s:test_inputrestore()@g + %substitute@\<input(@s:test_input(@g + + call cursor(1,1) + let pos = search("Settings Restoration:")-1 + " insert the test functions before the end guard + call assert_false(append(pos, s:testScript)) + + " save the modified script content + write + bwipe! - call writefile(netrwScript, a:netrw_test_path) - execute 'source' a:netrw_test_path endfunction -func s:setup() +func SetUp() + + " prepare modified netrw script call s:appendDebugToNetrw(s:netrw_path, s:netrw_test_path) + + " source the modified script + exe "source" s:netrw_test_path + + " Rig the package. The modified script guard prevents loading it again. + let &runtimepath=s:runtimedir + let &packpath=s:runtimedir + packadd netrw + + " use proper path + if has('win32') + let $HOME = substitute($HOME, '/', '\\', 'g') + endif + endfunction -func s:cleanup() +func TearDown() + " cleanup call delete(s:netrw_test_path) endfunction +func SetShell(shell) + " select different shells + if a:shell == "default" + set shell& shellcmdflag& shellxquote& shellpipe& shellredir& + if has("win32") + " Nvim: default 'shell' is "sh" due to $SHELL being set in Makefile, + " but here 'shell' should be cmd.exe. + set shell=cmd.exe + endif + elseif a:shell == "powershell" " help dos-powershell + " powershell desktop is windows only + if !has("win32") + throw 'Skipped: powershell desktop is missing' + endif + set shell=powershell shellcmdflag=-NoProfile\ -Command shellxquote=\" + set shellpipe=2>&1\ \|\ Out-File\ -Encoding\ default shellredir=2>&1\ \|\ Out-File\ -Encoding\ default + elseif a:shell == "pwsh" " help dos-powershell + " powershell core works crossplatform + if !executable("pwsh") + throw 'Skipped: powershell core is missing' + endif + set shell=pwsh shellcmdflag=-NoProfile\ -c shellpipe=>%s\ 2>&1 shellredir=>%s\ 2>&1 + if has("win32") + set shellxquote=\" + else + set shellxquote= + endif + else + call assert_report("Trying to select an unknown shell") + endif + " Nvim: 'shellxquote' needs to be empty for the tests for work. + set shellxquote= +endfunc + func s:combine \( usernames \, methods @@ -62,19 +258,16 @@ endfunction func Test_netrw_parse_remote_simple() - call s:setup() let result = TestNetrwCaptureRemotePath('scp://user@localhost:2222/test.txt') call assert_equal(result.method, 'scp') call assert_equal(result.user, 'user') call assert_equal(result.machine, 'localhost') call assert_equal(result.port, '2222') call assert_equal(result.path, 'test.txt') - call s:cleanup() endfunction "testing different combinations" func Test_netrw_parse_regular_usernames() - call s:setup() " --- sample data for combinations ---" let usernames = ["root", "toor", "user01", "skillIssue"] @@ -86,47 +279,377 @@ func Test_netrw_parse_regular_usernames() call s:combine(usernames, methods, hosts, ports, dirs, files) - call s:cleanup() endfunc "Host myserver " HostName 192.168.1.42 " User alice func Test_netrw_parse_ssh_config_entries() - call s:setup() let result = TestNetrwCaptureRemotePath('scp://myserver//etc/nginx/nginx.conf') call assert_equal(result.method, 'scp') call assert_equal(result.user, '') call assert_equal(result.machine, 'myserver') call assert_equal(result.port, '') call assert_equal(result.path, '/etc/nginx/nginx.conf') - call s:cleanup() endfunction "username containing special-chars" func Test_netrw_parse_special_char_user() - call s:setup() let result = TestNetrwCaptureRemotePath('scp://user-01@localhost:2222/test.txt') call assert_equal(result.method, 'scp') call assert_equal(result.user, 'user-01') call assert_equal(result.machine, 'localhost') call assert_equal(result.port, '2222') call assert_equal(result.path, 'test.txt') - call s:cleanup() endfunction -func Test_netrw_wipe_empty_buffer_fastpath() +func Test_netrw_empty_buffer_fastpath_wipe() + " SetUp() may have opened some buffers + let previous = bufnr('$') let g:netrw_fastbrowse=0 - packadd netrw call setline(1, 'foobar') let bufnr = bufnr('%') tabnew Explore call search('README.txt', 'W') exe ":norm \<cr>" - call assert_equal(4, bufnr('$')) + call assert_equal(previous + 2, bufnr('$')) call assert_true(bufexists(bufnr)) bw unlet! netrw_fastbrowse endfunction + +" Test UNC paths on windows +func Test_netrw_check_UNC_paths() + CheckMSWindows + + let test_paths = [ + \ '\\Server2\Share\Test\Foo.txt', + \ '//Server2/Share/Test/Foo.txt', + \ '\\Server2\Share\Test\', + \ '//Server2/Share/Test/', + \ '\\wsl.localhost\Ubuntu\home\user\_vimrc', + \ '//wsl.localhost/Ubuntu/home/user/_vimrc', + \ '\\wsl.localhost\Ubuntu\home\user', + \ '//wsl.localhost/Ubuntu/home/user'] + + " The paths must be interpreted as absolute ones + for path in test_paths + call assert_equal( + \ path, + \ Test_NetrwFile(path), + \ $"UNC path: {path} misinterpreted") + endfor + +endfunction + +" --------------------------------- +" Testing file management functions +" --------------------------------- + +" Browser directory creation +func s:netrw_mkdir() + + " create a testdir in the fake $HOME + call Test_NetrwMakeDir($HOME, "NetrwMakeDir") + + " Check the test directory was created + let test_dir = netrw#fs#PathJoin($HOME, "NetrwMakeDir") + call WaitForAssert({-> assert_true( + \ isdirectory(test_dir), + \ "Unable to create a dir via s:NetrwMakeDir()") + \ }) + + " remove the test directory + call delete(test_dir, 'd') +endfunc + +func Test_netrw_mkdir_default() + call SetShell('default') + call s:netrw_mkdir() +endfunc + +func Test_netrw_mkdir_powershell() + call SetShell('powershell') + call s:netrw_mkdir() +endfunc + +func Test_netrw_mkdir_pwsh() + call SetShell('pwsh') + call s:netrw_mkdir() +endfunc + +func s:netrw_filecopy(count = 1) + " setup + let marked_files = [] + let source_dir = netrw#fs#PathJoin($HOME, "src") + let target_dir = netrw#fs#PathJoin($HOME, "target") + + call mkdir(source_dir, "R") + call mkdir(target_dir, "R") + + for i in range(a:count) + call add(marked_files, $"testfile{i}.txt") + call writefile( + \ [$"NetrwMarkFileCopy test file {i}"], + \ netrw#fs#PathJoin(source_dir, marked_files[-1])) + endfor + + " delegate + call Test_NetrwMarkFileCopy(source_dir, target_dir, marked_files) + + " verify + for file in marked_files + call assert_equalfile( + \ netrw#fs#PathJoin(source_dir, file), + \ netrw#fs#PathJoin(target_dir, file), + \ "File copy failed for " . file) + endfor +endfunc + +" Browser file copy +func s:test_netrw_filecopy() + + " if shellslash is available, check both settings + if exists('+shellslash') + set shellslash& + call s:netrw_filecopy(1) + call s:netrw_filecopy(10) + set shellslash! + endif + + call s:netrw_filecopy(1) + call s:netrw_filecopy(10) + +endfunc + +func Test_netrw_filecopy_default() + call SetShell('default') + call s:test_netrw_filecopy() +endfunc + +func Test_netrw_filecopy_powershell() + call SetShell('powershell') + call s:test_netrw_filecopy() +endfunc + +func Test_netrw_filecopy_pwsh() + call SetShell('pwsh') + call s:test_netrw_filecopy() +endfunc + +" Browser recursive directory copy +func s:netrw_dircopy(count = 1) + + " setup + let marked_dirname = "test_dir" + let marked_dir = netrw#fs#PathJoin($HOME, marked_dirname) + let target_dir = netrw#fs#PathJoin($HOME, "target") + + call mkdir(marked_dir, "R") + call mkdir(target_dir, "R") + + let dir_content = [] + for i in range(a:count) + call add(dir_content, $"testfile{i}.txt") + call writefile( + \ [$"NetrwMarkFileCopy test dir content {i}"], + \ netrw#fs#PathJoin(marked_dir, dir_content[-1])) + endfor + + " delegate + call Test_NetrwMarkFileCopy($HOME, target_dir, [marked_dirname]) + + " verify + for file in dir_content + call assert_equalfile( + \ netrw#fs#PathJoin(marked_dir, file), + \ netrw#fs#PathJoin(target_dir, marked_dirname, file), + \ "File copy failed for " . file) + endfor + +endfunc + +func s:test_netrw_dircopy() + + " if shellslash is available, check both settings + if exists('+shellslash') + set shellslash& + call s:netrw_dircopy(10) + set shellslash! + endif + + call s:netrw_dircopy(10) + +endfunc + +func Test_netrw_dircopy_default() + call SetShell('default') + call s:test_netrw_dircopy() +endfunc + +func Test_netrw_dircopy_powershell() + call SetShell('powershell') + call s:test_netrw_dircopy() +endfunc + +func Test_netrw_dircopy_pwsh() + call SetShell('pwsh') + call s:test_netrw_dircopy() +endfunc + +" Copy file into the same directory with a different name +func Test_netrw_dircopy_rename_default() + call SetShell('default') + call Test_NetrwMarkFileCopy_SameDir() +endfunc + +func Test_netrw_dircopy_rename_powershell() + call SetShell('powershell') + call Test_NetrwMarkFileCopy_SameDir() +endfunc + +func Test_netrw_dircopy_rename_pwsh() + call SetShell('pwsh') + call Test_NetrwMarkFileCopy_SameDir() +endfunc + +" Browser file move +func s:netrw_filemove(count = 1) + " setup + let marked_files = [] + let source_dir = netrw#fs#PathJoin($HOME, "src") + let target_dir = netrw#fs#PathJoin($HOME, "target") + + call mkdir(source_dir, "R") + call mkdir(target_dir, "R") + + for i in range(a:count) + call add(marked_files, $"testfile{i}.txt") + call writefile( + \ [$"NetrwMarkFileMove test file {i}"], + \ netrw#fs#PathJoin(source_dir, marked_files[-1])) + endfor + + " delegate + call Test_NetrwMarkFileMove(source_dir, target_dir, marked_files) + + " verify + for i in range(a:count) + call assert_equal( + \ [$"NetrwMarkFileMove test file {i}"], + \ readfile(netrw#fs#PathJoin(target_dir, $"testfile{i}.txt")), + \ $"File move failed for testfile{i}.txt") + endfor +endfunc + +func s:test_netrw_filemove() + + " if shellslash is available, check both settings + if exists('+shellslash') + set shellslash& + call s:netrw_filemove(10) + set shellslash! + endif + + call s:netrw_filemove(10) + +endfunc + +func Test_netrw_filemove_default() + call SetShell('default') + call s:test_netrw_filemove() +endfunc + +func Test_netrw_filemove_powershell() + call SetShell('powershell') + call s:test_netrw_filemove() +endfunc + +func Test_netrw_filemove_pwsh() + call SetShell('pwsh') + call s:test_netrw_filemove() +endfunc + +func Test_netrw_reject_evil_hostname() + let msg = execute(':e scp://x;touch RCE;x/dir/') + let msg = split(msg, "\n")[-1] + call assert_match('Rejecting invalid hostname', msg) +endfunc + +func Test_netrw_hostname() + let valid_hostnames = [ + \ 'localhost', + \ '127.0.0.1', + \ '::1', + \ '0:0:0:0:0:0:0:1', + \ 'user@localhost', + \ 'usuario@127.0.0.1', + \ 'utilisateur@::1', + \ 'benutzer@0:0:0:0:0:0:0:1', + \ 'localhost:22', + \ '127.0.0.1:80', + \ '[::1]:443', + \ '[0:0:0:0:0:0:0:1]:5432', + \ 'user@localhost:22', + \ 'usuario@127.0.0.1:80', + \ 'utilisateur@[::1]:443', + \ 'benutzer@[0:0:0:0:0:0:0:1]:5432'] + + for hostname in valid_hostnames + call assert_true(Test_NetrwValidateHostname(hostname), $"Valid hostname {hostname} was rejected") + endfor +endfunc + +func Test_netrw_FileUrlEdit_pipe_injection() + CheckUnix + CheckExecutable id + let fname = 'Xtestfile' + let url = 'file:///tmp/file.md%7C!id>'..fname + sil call netrw#FileUrlEdit(url) + call assert_false(filereadable(fname), 'Command injection via pipe in file URL') +endfunc + +" The remote filename after '.' was allowed to contain shell metacharacters +" and rode unescaped into the tempfile name passed to sftp/file_cmd, giving a +" shell injection on :e sftp://host/foo.txt;<cmd>. +func Test_netrw_tempfile_suffix_injection() + CheckUnix + CheckExecutable id + let save_sftp = g:netrw_sftp_cmd + let save_file = exists('g:netrw_file_cmd') ? g:netrw_file_cmd : v:null + let g:netrw_sftp_cmd = 'true' + let g:netrw_file_cmd = 'true' + let fname = 'Xrce_marker' + try + call delete(fname) + sil! call netrw#NetRead(2, 'sftp://localhost/foo.txt;id>'..fname) + call assert_false(filereadable(fname), 'Command injection via sftp:// tempfile suffix') + + call delete(fname) + sil! call netrw#NetRead(2, 'file://localhost/foo.txt;id>'..fname) + call assert_false(filereadable(fname), 'Command injection via file:// tempfile suffix') + finally + call delete(fname) + let g:netrw_sftp_cmd = save_sftp + if save_file is v:null + unlet! g:netrw_file_cmd + else + let g:netrw_file_cmd = save_file + endif + endtry +endfunc + +func Test_netrw_RFC2396() + let fname = 'a%20b' + call assert_equal('a b', netrw#RFC2396(fname)) +endfunc + +func Test_netrw_Home_tilde() + Explore ~ + call assert_match('Netrw Directory Listing', getline(2)) + bw! +endfunc + +" vim:ts=8 sts=2 sw=2 et |
