You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ant.apache.org by bo...@apache.org on 2026/09/06 18:00:40 UTC
(ant) 03/06: add allowFilesToEscapeDest attribute to scp
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 <bo...@apache.org>
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);
}
}