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\\\\"") 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) + 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 !g:netrw_cygwin && has("win32") - let args = substitute(args,'/','\\','g') - let tgt = substitute(tgt, '/','\\','g') - 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 + let fname= netrw#fs#ComposePath(b:netrw_curdir, fname) endif - if !g:netrw_cygwin && has("win32") - let fname= substitute(fname,'/','\\','g') - 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,'\',s:machine,'') - else - let sshcmd = substitute(a:sshcmd,'\',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,'\',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("