diff --git a/WHATSNEW b/WHATSNEW index b319bf0eb..79546bbac 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -410,6 +410,8 @@ Other changes: * Handling of ' ', '#' in CLASSPATH and '#' in -lib (cannot use ' ' in -lib on UNIX at the moment). Bugzilla Report 39295. +* now optionally supports the sftp protocol. Bugzilla Report 39373. + Changes from Ant 1.6.4 to Ant 1.6.5 =================================== diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/AbstractSshMessage.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/AbstractSshMessage.java index ae676db8c..85e381003 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/AbstractSshMessage.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/AbstractSshMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2005 The Apache Software Foundation + * Copyright 2003-2006 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,10 @@ import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.SftpATTRS; +import com.jcraft.jsch.SftpException; +import com.jcraft.jsch.SftpProgressMonitor; import java.io.IOException; import java.io.OutputStream; @@ -74,6 +78,17 @@ public abstract class AbstractSshMessage { return channel; } + /** + * Open an ssh sftp channel. + * @return the channel + * @throws JSchException on error + */ + protected ChannelSftp openSftpChannel() throws JSchException { + ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); + + return channel; + } + /** * Send an ack. * @param out the output stream to use @@ -213,4 +228,39 @@ public abstract class AbstractSshMessage { return percent; } + private ProgressMonitor monitor = null; + + protected SftpProgressMonitor getProgressMonitor(){ + if (monitor == null) { + monitor = new ProgressMonitor(); + } + return monitor; + } + + private class ProgressMonitor implements SftpProgressMonitor { + private long initFileSize = 0; + private long totalLength = 0; + private int percentTransmitted = 0; + + public void init(int op, String src, String dest, long max) { + initFileSize = max; + totalLength = 0; + percentTransmitted = 0; + } + + public boolean count(long len) { + totalLength += len; + percentTransmitted = trackProgress(initFileSize, + totalLength, + percentTransmitted); + return true; + } + + public void end() { + } + + public long getTotalLength() { + return totalLength; + } + } } 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 42848348a..e74cb6fcb 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 @@ -1,5 +1,5 @@ /* - * Copyright 2003-2005 The Apache Software Foundation + * Copyright 2003-2006 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,6 +44,7 @@ public class Scp extends SSHBase { private String toUri; private List fileSets = null; private boolean isFromRemote, isToRemote; + private boolean isSftp = false; /** * Sets the file to be transferred. This can either be a remote @@ -141,6 +142,15 @@ public class Scp extends SSHBase { this.isToRemote = true; } + /** + * Setting this to true to use sftp protocol. + * + * @param yesOrNo if true sftp protocol will be used. + */ + public void setSftp(boolean yesOrNo) { + isSftp = yesOrNo; + } + /** * Adds a FileSet tranfer to remote host. NOTE: Either * addFileSet() or setFile() are required. But, not both. @@ -213,10 +223,18 @@ public class Scp extends SSHBase { Session session = null; try { session = openSession(); - ScpFromMessage message = - new ScpFromMessage(getVerbose(), session, file, - getProject().resolveFile(toPath), - fromSshUri.endsWith("*")); + ScpFromMessage message = null; + if (!isSftp){ + message = + new ScpFromMessage(getVerbose(), session, file, + getProject().resolveFile(toPath), + fromSshUri.endsWith("*")); + } else{ + message = + new ScpFromMessageBySftp(getVerbose(), session, file, + getProject().resolveFile(toPath), + fromSshUri.endsWith("*")); + } log("Receiving file: " + file); message.setLogListener(this); message.execute(); @@ -243,8 +261,14 @@ public class Scp extends SSHBase { } if (!list.isEmpty()) { session = openSession(); - ScpToMessage message = new ScpToMessage(getVerbose(), session, - list, file); + ScpToMessage message = null; + if (!isSftp){ + message = new ScpToMessage(getVerbose(), session, + list, file); + } else{ + message = new ScpToMessageBySftp(getVerbose(), session, + list, file); + } message.setLogListener(this); message.execute(); } @@ -262,9 +286,17 @@ public class Scp extends SSHBase { Session session = null; try { session = openSession(); - ScpToMessage message = - new ScpToMessage(getVerbose(), session, - getProject().resolveFile(fromPath), file); + ScpToMessage message = null; + if (!isSftp){ + message = + new ScpToMessage(getVerbose(), session, + getProject().resolveFile(fromPath), file); + } else{ + message = + new ScpToMessageBySftp(getVerbose(), session, + getProject().resolveFile(fromPath), + file); + } message.setLogListener(this); message.execute(); } finally { 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 8c51b2b60..9dc2a0934 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 @@ -1,5 +1,5 @@ /* - * Copyright 2003-2005 The Apache Software Foundation + * Copyright 2003-2006 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,24 @@ public class ScpFromMessage extends AbstractSshMessage { private File localFile; private boolean isRecursive = false; + /** + * Constructor for ScpFromMessage + * @param session the ssh session to use + */ + public ScpFromMessage(Session session) { + super(session); + } + + /** + * Constructor for ScpFromMessage + * @param verbose if true do verbose logging + * @param session the ssh session to use + * @since Ant 1.6.2 + */ + public ScpFromMessage(boolean verbose, Session session) { + super(verbose, session); + } + /** * Constructor for ScpFromMessage. * @param verbose if true log extra information 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 new file mode 100644 index 000000000..77fb244dd --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpFromMessageBySftp.java @@ -0,0 +1,173 @@ +/* + * Copyright 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.ssh; + +import java.io.File; +import java.io.IOException; +import java.io.EOFException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.FileOutputStream; +import java.io.ByteArrayOutputStream; + +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.SftpException; +import com.jcraft.jsch.SftpATTRS; +import com.jcraft.jsch.SftpProgressMonitor; + +/** + * A helper object representing an scp download. + */ +public class ScpFromMessageBySftp extends ScpFromMessage { + + private String remoteFile; + private File localFile; + private boolean isRecursive = false; + private boolean verbose = false; + + /** + * 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 + * @since Ant 1.7 + */ + public ScpFromMessageBySftp(boolean verbose, + Session session, + String aRemoteFile, + File aLocalFile, + boolean recursive) { + super(verbose, session); + this.verbose = verbose; + this.remoteFile = aRemoteFile; + this.localFile = aLocalFile; + this.isRecursive = recursive; + } + + /** + * Constructor for ScpFromMessageBySftp. + * @param session the Scp session to use + * @param aRemoteFile the remote file name + * @param aLocalFile the local file + * @param recursive if true use recursion + */ + public ScpFromMessageBySftp(Session session, + String aRemoteFile, + File aLocalFile, + boolean recursive) { + this(false, session, aRemoteFile, aLocalFile, recursive); + } + + /** + * Carry out the transfer. + * @throws IOException on i/o errors + * @throws JSchException on errors detected by scp + */ + public void execute() throws IOException, JSchException { + ChannelSftp channel = openSftpChannel(); + try { + channel.connect(); + try { + SftpATTRS attrs = channel.stat(remoteFile); + if (attrs.isDir() && !remoteFile.endsWith("/")) { + remoteFile=remoteFile+"/"; + } + } catch(SftpException ee) { + } + getDir(channel, remoteFile, localFile); + } catch(SftpException e) { + throw new JSchException(e.toString()); + } finally { + if (channel != null) { + channel.disconnect(); + } + } + log("done\n"); + } + + private void getDir(ChannelSftp channel, + String remoteFile, + File localFile) throws IOException, SftpException { + String pwd=remoteFile; + if (remoteFile.lastIndexOf('/')!=-1) { + if (remoteFile.length()>1) { + pwd=remoteFile.substring(0, remoteFile.lastIndexOf('/')); + } + } + channel.cd(pwd); + if (!localFile.exists()) { + localFile.mkdirs(); + } + java.util.Vector files = channel.ls(remoteFile); + for(int i = 0; i < files.size(); i++){ + ChannelSftp.LsEntry le = (ChannelSftp.LsEntry) files.elementAt(i); + String name = le.getFilename(); + if (le.getAttrs().isDir()) { + if (name.equals(".") || name.equals("..")) { + continue; + } + getDir(channel, + channel.pwd() + "/" + name + "/", + new File(localFile, le.getFilename())); + } else{ + getFile(channel, le, localFile); + } + } + channel.cd(".."); + } + + private void getFile(ChannelSftp channel, + ChannelSftp.LsEntry le, + File localFile) throws IOException, SftpException { + String remoteFile = le.getFilename(); + if (!localFile.exists()) { + String path = localFile.getAbsolutePath(); + int i = 0; + if ((i = path.lastIndexOf(File.pathSeparator)) != -1) { + if (path.length()>File.pathSeparator.length()) { + new File(path.substring(0, i)).mkdirs(); + } + } + } + + if (localFile.isDirectory()) { + localFile=new File(localFile, remoteFile); + } + + long startTime = System.currentTimeMillis(); + long totalLength = le.getAttrs().getSize(); + + SftpProgressMonitor monitor = null; + boolean trackProgress = getVerbose() && totalLength > 102400; + if (trackProgress){ + monitor = getProgressMonitor(); + } + try{ + log("Receiving: " + remoteFile + " : " + le.getAttrs().getSize()); + channel.get(remoteFile, localFile.getAbsolutePath(), monitor); + } finally{ + long endTime = System.currentTimeMillis(); + logStats(startTime, endTime, (int)totalLength); + } + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpToMessage.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpToMessage.java index 6772c55b3..00fa72646 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpToMessage.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpToMessage.java @@ -39,6 +39,24 @@ public class ScpToMessage extends AbstractSshMessage { private String remotePath; private List directoryList; + /** + * Constructor for ScpToMessage + * @param session the ssh session to use + */ + public ScpToMessage(Session session) { + super(session); + } + + /** + * Constructor for ScpToMessage + * @param verbose if true do verbose logging + * @param session the ssh session to use + * @since Ant 1.7 + */ + public ScpToMessage(boolean verbose, Session session) { + super(verbose, session); + } + /** * Constructor for a local file to remote. * @param verbose if true do verbose logging diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpToMessageBySftp.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpToMessageBySftp.java new file mode 100644 index 000000000..886d9eb4b --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpToMessageBySftp.java @@ -0,0 +1,240 @@ +/* + * Copyright 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.ssh; + +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.SftpException; +import com.jcraft.jsch.SftpProgressMonitor; +import com.jcraft.jsch.SftpATTRS; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Iterator; + +public class ScpToMessageBySftp extends ScpToMessage/*AbstractSshMessage*/ { + + private File localFile; + private String remotePath; + private List directoryList; + + /** + * Constructor for a local file to remote. + * @param verbose if true do verbose logging + * @param session the scp session to use + * @param aLocalFile the local file + * @param aRemotePath the remote path + * @since Ant 1.7 + */ + public ScpToMessageBySftp(boolean verbose, + Session session, + File aLocalFile, + String aRemotePath) { + this(verbose, session, aRemotePath); + + this.localFile = aLocalFile; + } + + /** + * Constructor for a local directories to remote. + * @param verbose if true do verbose logging + * @param session the scp session to use + * @param aDirectoryList a list of directories + * @param aRemotePath the remote path + * @since Ant 1.7 + */ + public ScpToMessageBySftp(boolean verbose, + Session session, + List aDirectoryList, + String aRemotePath) { + this(verbose, session, aRemotePath); + + this.directoryList = aDirectoryList; + } + + /** + * Constructor for ScpToMessage. + * @param verbose if true do verbose logging + * @param session the scp session to use + * @param aRemotePath the remote path + * @since Ant 1.6.2 + */ + private ScpToMessageBySftp(boolean verbose, + Session session, + String aRemotePath) { + super(verbose, session); + this.remotePath = aRemotePath; + } + + /** + * Constructor for ScpToMessage. + * @param session the scp session to use + * @param aLocalFile the local file + * @param aRemotePath the remote path + */ + public ScpToMessageBySftp(Session session, + File aLocalFile, + String aRemotePath) { + this(false, session, aLocalFile, aRemotePath); + } + + /** + * Constructor for ScpToMessage. + * @param session the scp session to use + * @param aDirectoryList a list of directories + * @param aRemotePath the remote path + */ + public ScpToMessageBySftp(Session session, + List aDirectoryList, + String aRemotePath) { + this(false, session, aDirectoryList, aRemotePath); + } + + /** + * Carry out the transfer. + * @throws IOException on i/o errors + * @throws JSchException on errors detected by scp + */ + public void execute() throws IOException, JSchException { + if (directoryList != null) { + doMultipleTransfer(); + } + if (localFile != null) { + doSingleTransfer(); + } + log("done.\n"); + } + + private void doSingleTransfer() throws IOException, JSchException { + ChannelSftp channel = openSftpChannel(); + try { + channel.connect(); + try{ + sendFileToRemote(channel, localFile, remotePath); + } + catch(SftpException e){ + throw new JSchException(e.toString()); + } + } finally { + if (channel != null) { + channel.disconnect(); + } + } + } + + private void doMultipleTransfer() throws IOException, JSchException { + ChannelSftp channel = openSftpChannel(); + try { + channel.connect(); + + try{ + channel.cd(remotePath); + for (Iterator i = directoryList.iterator(); i.hasNext();) { + Directory current = (Directory) i.next(); + sendDirectory(channel, current); + } + } + catch(SftpException e){ + throw new JSchException(e.toString()); + } + } finally { + if (channel != null) { + channel.disconnect(); + } + } + } + + private void sendDirectory(ChannelSftp channel, + Directory current) + throws IOException, SftpException { + for (Iterator fileIt = current.filesIterator(); fileIt.hasNext();) { + sendFileToRemote(channel, (File) fileIt.next(), null); + } + for (Iterator dirIt = current.directoryIterator(); dirIt.hasNext();) { + Directory dir = (Directory) dirIt.next(); + sendDirectoryToRemote(channel, dir); + } + } + + private void sendDirectoryToRemote(ChannelSftp channel, + Directory directory) + throws IOException, SftpException { + String dir=directory.getDirectory().getName(); + try{ + channel.stat(dir); + } + catch(SftpException e){ + // dir does not exist. + if (e.id==ChannelSftp.SSH_FX_NO_SUCH_FILE) { + channel.mkdir(dir); + } + } + channel.cd(dir); + sendDirectory(channel, directory); + channel.cd(".."); + } + + private void sendFileToRemote(ChannelSftp channel, + File localFile, + String remotePath) + throws IOException, SftpException { + long filesize = localFile.length(); + + if (remotePath==null) { + remotePath=localFile.getName(); + } + + long startTime = System.currentTimeMillis(); + long totalLength = filesize; + + // only track progress for files larger than 100kb in verbose mode + boolean trackProgress = getVerbose() && filesize > 102400; + + SftpProgressMonitor monitor = null; + if (trackProgress){ + monitor = getProgressMonitor(); + } + + try{ + if (this.getVerbose()) { + log("Sending: " + localFile.getName() + " : " + filesize); + } + channel.put(localFile.getAbsolutePath(), remotePath, monitor); + } + finally { + if (this.getVerbose()) { + long endTime = System.currentTimeMillis(); + logStats(startTime, endTime, (int) totalLength); + } + } + } + + public File getLocalFile() { + return localFile; + } + + public String getRemotePath() { + return remotePath; + } +}