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);
         }
     }

Reply via email to