This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to annotated tag ANT_1.10.18_RC1 in repository https://gitbox.apache.org/repos/asf/ant.git
commit 9252566cab812c59a5695679ba11f497e85aabb0 Author: Stefan Bodewig <[email protected]> AuthorDate: Sat Aug 22 16:50:56 2026 +0200 add allowFilesToEscapeDest attribute to scp --- manual/Tasks/scp.html | 7 ++ .../tools/ant/taskdefs/optional/ssh/Scp.java | 17 ++++- .../ant/taskdefs/optional/ssh/ScpFromMessage.java | 78 ++++++++++++++++++++-- .../optional/ssh/ScpFromMessageBySftp.java | 52 +++++++++++++-- 4 files changed, 139 insertions(+), 15 deletions(-) diff --git a/manual/Tasks/scp.html b/manual/Tasks/scp.html index e9e4a33eb..a23663850 100644 --- a/manual/Tasks/scp.html +++ b/manual/Tasks/scp.html @@ -206,6 +206,13 @@ <h3>Parameters</h3> <td>Whether to enable compression during transfer. <em>since Ant 1.9.8</em></td> <td>No; defaults to <q>false</q></td> </tr> + <tr> + <td>allowFilesToEscapeDest</td> + <td>Whether to allow the locally received files or directories to + be outside of the toDir directory.<br/> + <em>since Ant 1.10.18</em></td> + <td>No, defaults to <q>false</q>.</td> + </tr> </table> <h3>Parameters specified as nested elements</h3> diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Scp.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Scp.java index 07c96896b..99eb959c3 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Scp.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Scp.java @@ -59,6 +59,7 @@ public class Scp extends SSHBase { private boolean isFromRemote, isToRemote; private boolean isSftp = false; private Integer fileMode, dirMode; + private boolean allowFilesToEscapeDest = false; /** * Sets the file to be transferred. This can either be a remote @@ -221,6 +222,16 @@ public class Scp extends SSHBase { this.dirMode = Integer.parseInt(dirMode, 8); } + /** + * Whether to allow the retrieved files or directories to be outside of the dest directory. + * + * @param b the flag + * @since Ant 1.10.18 + */ + public void setAllowFilesToEscapeDest(boolean b) { + allowFilesToEscapeDest = b; + } + /** * Adds a FileSet transfer to remote host. NOTE: Either * addFileSet() or setFile() are required. But, not both. @@ -312,13 +323,15 @@ public class Scp extends SSHBase { getProject().resolveFile(toPath), fromSshUri.endsWith("*"), preserveLastModified, - compressed); + compressed, + allowFilesToEscapeDest); } else { message = new ScpFromMessageBySftp(getVerbose(), session, file, getProject().resolveFile(toPath), fromSshUri.endsWith("*"), - preserveLastModified); + preserveLastModified, + allowFilesToEscapeDest); } log("Receiving file: " + file); message.setLogListener(this); diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpFromMessage.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpFromMessage.java index 93e3554d9..b7bba0cd2 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpFromMessage.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpFromMessage.java @@ -43,11 +43,13 @@ public class ScpFromMessage extends AbstractSshMessage { private static final int HUNDRED_KILOBYTES = 102400; private static final byte LINE_FEED = 0x0a; private static final int BUFFER_SIZE = 100 * 1024; + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); private String remoteFile; private File localFile; private boolean isRecursive = false; private boolean preserveLastModified = false; + private boolean allowFilesToEscapeDest = false; /** * Constructor for ScpFromMessage @@ -144,6 +146,35 @@ public class ScpFromMessage extends AbstractSshMessage { this.preserveLastModified = preserveLastModified; } + /** + * Constructor for ScpFromMessage. + * @param verbose if true log extra information + * @param session the Scp session to use + * @param aRemoteFile the remote file name + * @param aLocalFile the local file + * @param recursive if true use recursion (-r option to scp) + * @param preserveLastModified whether to preserve file + * @param compressed if true use compression (-C option to scp) + * modification times + * @param allowFilesToEscapeDest if true files and directories retrieved may be places outside of the aLocalFile directory. + * @since Ant 1.10.18 + */ + public ScpFromMessage(boolean verbose, + Session session, + String aRemoteFile, + File aLocalFile, + boolean recursive, + boolean preserveLastModified, + boolean compressed, + boolean allowFilesToEscapeDest) { + super(verbose, compressed, session); + this.remoteFile = aRemoteFile; + this.localFile = aLocalFile; + this.isRecursive = recursive; + this.preserveLastModified = preserveLastModified; + this.allowFilesToEscapeDest = allowFilesToEscapeDest; + } + /** * Carry out the transfer. * @throws IOException on i/o errors @@ -181,6 +212,13 @@ public class ScpFromMessage extends AbstractSshMessage { return preserveLastModified; } + /** + * @since Ant 1.10.18 + */ + protected boolean getAllowFilesToEscapeDest() { + return allowFilesToEscapeDest; + } + private void startRemoteCpProtocol(final InputStream in, final OutputStream out, final File localFile) @@ -220,16 +258,19 @@ public class ScpFromMessage extends AbstractSshMessage { } private File parseAndCreateDirectory(final String serverResponse, - final File localFile) { + final File localFile) throws IOException { int start = serverResponse.indexOf(' '); // appears that the next token is not used and it's zero. start = serverResponse.indexOf(' ', start + 1); final String directoryName = serverResponse.substring(start + 1); if (localFile.isDirectory()) { - final File dir = new File(localFile, directoryName); - dir.mkdir(); - log("Creating: " + dir); - return dir; + final File dir = FILE_UTILS.resolveFile(localFile, directoryName); + if (FILE_UTILS.isLeadingPath(this.localFile, dir) || getAllowFilesToEscapeDest()) { + dir.mkdir(); + log("Creating: " + dir); + return dir; + } + log("Skipping: " + dir + " as target " + FILE_UTILS.getResolvedPath(dir) + " is outside " + FILE_UTILS.getResolvedPath(this.localFile)); } return null; } @@ -249,7 +290,13 @@ public class ScpFromMessage extends AbstractSshMessage { final File transferFile = localFile.isDirectory() ? new File(localFile, filename) : localFile; - fetchFile(transferFile, filesize, out, in); + if (FILE_UTILS.isLeadingPath(this.localFile, transferFile) || getAllowFilesToEscapeDest()) { + fetchFile(transferFile, filesize, out, in); + } else { + log("Skipping: " + filename + " as target " + FILE_UTILS.getResolvedPath(transferFile) + + " is outside " + FILE_UTILS.getResolvedPath(this.localFile)); + consume(filesize, out, in); + } waitForAck(in); sendAck(out); } @@ -307,6 +354,23 @@ public class ScpFromMessage extends AbstractSshMessage { } } + private void consume(long filesize, + final OutputStream out, + final InputStream in) + throws IOException, JSchException { + final byte[] buf = new byte[BUFFER_SIZE]; + sendAck(out); + + while (filesize > 0) { + int length = in.read(buf, 0, + BUFFER_SIZE < filesize ? BUFFER_SIZE : (int) filesize); + if (length < 0) { + throw new EOFException("Unexpected end of stream."); + } + filesize -= length; + } + } + private void setLastModified(final File localFile) throws JSchException { SftpATTRS fileAttributes = null; final ChannelSftp channel = openSftpChannel(); @@ -321,7 +385,7 @@ public class ScpFromMessage extends AbstractSshMessage { } finally { channel.disconnect(); } - FileUtils.getFileUtils().setFileLastModified(localFile, + FILE_UTILS.setFileLastModified(localFile, ((long) fileAttributes.getMTime()) * 1000); } diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpFromMessageBySftp.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpFromMessageBySftp.java index 7e5edabf1..a33fe0638 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpFromMessageBySftp.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpFromMessageBySftp.java @@ -36,6 +36,7 @@ import com.jcraft.jsch.SftpProgressMonitor; public class ScpFromMessageBySftp extends ScpFromMessage { private static final int HUNDRED_KILOBYTES = 102400; + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); private String remoteFile; private final File localFile; @@ -100,6 +101,33 @@ public class ScpFromMessageBySftp extends ScpFromMessage { this.isRecursive = recursive; } + /** + * Constructor for ScpFromMessageBySftp. + * @param verbose if true log extra information + * @param session the Scp session to use + * @param aRemoteFile the remote file name + * @param aLocalFile the local file + * @param recursive if true use recursion + * @param preserveLastModified whether to preserve file + * modification times + * @param allowFilesToEscapeDest if true files and directories retrieved may be places outside of the aLocalFile directory. + * @since Ant 1.10.18 + */ + public ScpFromMessageBySftp(final boolean verbose, + final Session session, + final String aRemoteFile, + final File aLocalFile, + final boolean recursive, + final boolean preserveLastModified, + final boolean allowFilesToEscapeDest) { + super(verbose, session, aRemoteFile, aLocalFile, recursive, + preserveLastModified, false, allowFilesToEscapeDest); + this.verbose = verbose; + this.remoteFile = aRemoteFile; + this.localFile = aLocalFile; + this.isRecursive = recursive; + } + /** * Carry out the transfer. * @throws IOException on i/o errors @@ -132,7 +160,7 @@ public class ScpFromMessageBySftp extends ScpFromMessage { private void getDir(final ChannelSftp channel, final String remoteFile, - final File localFile) throws SftpException { + final File localFile) throws SftpException, IOException { String pwd = remoteFile; final int lastIndexOfFileSeparator = remoteFile.lastIndexOf('/'); if (lastIndexOfFileSeparator != -1) { @@ -158,9 +186,16 @@ public class ScpFromMessageBySftp extends ScpFromMessage { if (".".equals(name) || "..".equals(name)) { continue; } - getDir(channel, - channel.pwd() + "/" + name + "/", - new File(localFile, le.getFilename())); + + File newDir = FILE_UTILS.resolveFile(localFile, name); + if (FILE_UTILS.isLeadingPath(this.localFile, newDir) || getAllowFilesToEscapeDest()) { + getDir(channel, + channel.pwd() + "/" + name + "/", + newDir); + } else { + log("Skipping: " + name + " as target " + FILE_UTILS.getResolvedPath(newDir) + + " is outside " + FILE_UTILS.getResolvedPath(this.localFile)); + } } else { getFile(channel, le, localFile); } @@ -170,7 +205,7 @@ public class ScpFromMessageBySftp extends ScpFromMessage { private void getFile(final ChannelSftp channel, final ChannelSftp.LsEntry le, - File localFile) throws SftpException { + File localFile) throws SftpException, IOException { final String remoteFile = le.getFilename(); if (!localFile.exists()) { final String path = localFile.getAbsolutePath(); @@ -184,6 +219,11 @@ public class ScpFromMessageBySftp extends ScpFromMessage { if (localFile.isDirectory()) { localFile = new File(localFile, remoteFile); + if (!FILE_UTILS.isLeadingPath(this.localFile, localFile) && !getAllowFilesToEscapeDest()) { + log("Skipping: " + remoteFile + " as target " + FILE_UTILS.getResolvedPath(localFile) + + " is outside " + FILE_UTILS.getResolvedPath(this.localFile)); + return; + } } final long startTime = System.currentTimeMillis(); @@ -202,7 +242,7 @@ public class ScpFromMessageBySftp extends ScpFromMessage { logStats(startTime, endTime, (int) totalLength); } if (getPreserveLastModified()) { - FileUtils.getFileUtils().setFileLastModified(localFile, + FILE_UTILS.setFileLastModified(localFile, ((long) le.getAttrs().getMTime()) * 1000); } }
