diff --git a/WHATSNEW b/WHATSNEW index 10f3b2e7e..e2b9d860f 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -157,6 +157,9 @@ Other changes: * The task subelement has been enhanced to allow files with tag mappings to be used. +* New task that supports file transfers over SSH. Requires + jsch, a BSD licensed SSH library that can be found at + http://www.jcraft.com/jsch/index.html Changes from Ant 1.5.1 to Ant 1.5.2 ============================================= diff --git a/build.xml b/build.xml index 5704091ed..07834ca0e 100644 --- a/build.xml +++ b/build.xml @@ -155,7 +155,10 @@ - + + + + @@ -1616,6 +1634,9 @@ --> + + + diff --git a/docs/manual/OptionalTasks/scp.html b/docs/manual/OptionalTasks/scp.html new file mode 100644 index 000000000..fc56228e3 --- /dev/null +++ b/docs/manual/OptionalTasks/scp.html @@ -0,0 +1,133 @@ + + + + +SCP Task + + + + +

SCP

+

Description

+

Copies a file or FileSet to or from a remote machine running SSH daemon. +FileSet only works for copying files from the local machine to a +remote machine.

+ +

Note: This task depends on external libraries not included +in the Ant distribution. See Library Dependencies +for more information.

+ +

Parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
fileThe file to copy. This can be a local path or a + remote path of the form user:password@host:/directory/path.Yes, unless a nested + <fileset> element is used.
todirThe directory to copy to. This can be a local path + or a remote path of the form user:password@host:/directory/pathYes
portThe port to connect to on the remote host.No, defaults to 22.
trustThis trusts all unknown hosts if set to yes/true.No, defaults to No.
knownhostsThis sets the known hosts file to use to validate + the identity of the remote host. This must be a SSH2 format file. + SSH1 format is not supported.No, defaults to + ${user.dir}/.ssh/known_hosts.
failonerrorLog a warning message, but do not stop the build, + when the transfer does not work. + No; defaults to true.
+

Parameters specified as nested elements

+ +

fileset

+

FileSets are used to select +sets of files to copy. + To use a fileset, the todir attribute must be set.

+ +

Examples

+

Copy a single local file to a remote machine

+
+  <scp file="myfile.txt" todir="user:password@somehost:/home/chuck"/>
+
+

Copy a single remote file to a local directory

+
+  <scp file="user:password@somehost:/home/chuck/myfile.txt" todir="../some/other/dir"/>
+
+ +

Copy a remote directory to a local directory

+
+  <scp file="user:password@somehost:/home/chuck/*" todir="/home/sara" />
+
+ +

Copy a local directory to a remote directory

+
+  <scp todir="user:password@somehost:/home/chuck/">
+    <fileset dir="src_dir"/>
+  </scp>
+
+

Copy a set of files to a directory

+
+  <scp todir="user:password@somehost:/home/chuck">
+    <fileset dir="src_dir">
+      <include name="**/*.java"/>
+    </fileset>
+  </scp>
+
+  <scp todir="user:password@somehost:/home/chuck">
+    <fileset dir="src_dir" excludes="**/*.java"/>
+  </scp>
+
+ +

Security Note: Hard coding passwords and/or usernames +in scp task can be a serious security hole. Consider using variable +substituion and include the password on the command line. For example:
+

+    <scp todir="${username}:${password}@host:/dir" ...>
+
+Invoke ant with the following command line: +
+    ant -Dusername=me -Dpassword=mypassword target1 target2
+
+

+ +

Unix Note: File permissions are not retained when files +are copied; they end up with the default UMASK permissions +instead. This is caused by the lack of any means to query or set file +permissions in the current Java runtimes. If you need a permission- +preserving copy function, use <exec executable="scp" ... > +instead. +

+ +

Copyright © 2003 Apache Software Foundation. +All rights Reserved.

+ + + + diff --git a/docs/manual/install.html b/docs/manual/install.html index c5d4e3294..5f15ad2f9 100644 --- a/docs/manual/install.html +++ b/docs/manual/install.html @@ -418,6 +418,12 @@ Installing Ant / Optional Tasks section above.

target="_top">http://gump.covalent.net/jars/latest/xml-commons/ for a nightly snapshot. + + jsch.jar + scp task + http://www.jcraft.com/jsch/index.html +

diff --git a/docs/manual/optionaltasklist.html b/docs/manual/optionaltasklist.html index 34758bcc4..fd4a739ea 100644 --- a/docs/manual/optionaltasklist.html +++ b/docs/manual/optionaltasklist.html @@ -55,6 +55,7 @@ Rpm
ServerDeploy
Setproxy
+Scp
Script
Sound
SourceOffSite
diff --git a/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/src/main/org/apache/tools/ant/taskdefs/defaults.properties index 01e34c7ae..4d47ccdf6 100644 --- a/src/main/org/apache/tools/ant/taskdefs/defaults.properties +++ b/src/main/org/apache/tools/ant/taskdefs/defaults.properties @@ -181,6 +181,7 @@ symlink=org.apache.tools.ant.taskdefs.optional.unix.Symlink chgrp=org.apache.tools.ant.taskdefs.optional.unix.Chgrp chown=org.apache.tools.ant.taskdefs.optional.unix.Chown attrib=org.apache.tools.ant.taskdefs.optional.windows.Attrib +scp=org.apache.tools.ant.taskdefs.optional.ssh.Scp # deprecated ant tasks (kept for back compatibility) starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut 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 new file mode 100644 index 000000000..0cbde846b --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/AbstractSshMessage.java @@ -0,0 +1,121 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "Ant" and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.taskdefs.optional.ssh; + +import com.jcraft.jsch.Session; +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelExec; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.InputStream; +import java.text.NumberFormat; + +public abstract class AbstractSshMessage { + + protected Session session; + protected LogListener listener = new LogListener() {; + public void log(String message) { + // do nothing; + } + }; + + public AbstractSshMessage(Session session) { + this.session = session; + } + + protected Channel openExecChannel( String command ) { + ChannelExec channel = (ChannelExec) session.openChannel( "exec" ); + channel.setCommand( command ); + + return channel; + } + + protected void sendAck(OutputStream out) throws IOException { + byte[] buf = new byte[1]; + buf[0] = 0; + out.write( buf ); + out.flush(); + } + + protected void waitForAck(InputStream in) throws IOException { + int b = 0; + do { + b = in.read(); + } while (b > 0); + } + + public abstract void execute() throws IOException; + + public void setLogListener( LogListener aListener ) { + listener = aListener; + } + + protected void log( String message ) { + listener.log( message ); + } + + protected void logStats( long timeStarted, + long timeEnded, + int totalLength) { + double duration = (timeEnded - timeStarted) / 1000.0; + NumberFormat format = NumberFormat.getNumberInstance(); + format.setMaximumFractionDigits( 2 ); + format.setMinimumFractionDigits( 1 ); + listener.log( "File transfer time: " + format.format( duration ) + + " Average Rate: " + format.format( totalLength / duration ) + + " B/s" ); + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Directory.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Directory.java new file mode 100644 index 000000000..28608d25d --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Directory.java @@ -0,0 +1,155 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "Ant" and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.taskdefs.optional.ssh; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.StringTokenizer; +import java.io.File; + +public class Directory { + + private File directory; + private ArrayList childDirectories; + private ArrayList files; + private Directory parent; + + public Directory( File directory ) { + this( directory, null ); + } + + public Directory( File directory , Directory parent ) { + this.parent = parent; + this.childDirectories = new ArrayList(); + this.files = new ArrayList(); + this.directory = directory; + } + + public void addDirectory( Directory directory ) { + if( !childDirectories.contains( directory ) ) + childDirectories.add( directory ); + } + + public void addFile( File file ) { + files.add( file ); + } + + public Iterator directoryIterator() { + return childDirectories.iterator(); + } + + public Iterator filesIterator() { + return files.iterator(); + } + + public Directory getParent() { + return parent; + } + + public boolean isRoot() { + return parent == null; + } + + public File getDirectory() { + return directory; + } + + public Directory getChild( File dir ) { + for( int i = 0; i < childDirectories.size(); i++ ) { + Directory current = (Directory) childDirectories.get(i); + if( current.getDirectory().equals( dir ) ) { + return current; + } + } + + return null; + } + + public boolean equals(Object obj) { + if( obj == this ) return true; + + if( !(obj instanceof Directory) ) return false; + + Directory d = (Directory)obj; + + return this.directory.equals( d.directory ); + } + + public int hashCode() { + return directory.hashCode(); + } + + public String[] getPath() { + return getPath( directory.getAbsolutePath() ); + } + + public static String[] getPath( String thePath ) { + StringTokenizer tokenizer = new StringTokenizer( thePath, + File.separator ); + String[] path = new String[ tokenizer.countTokens() ]; + + int i = 0; + while( tokenizer.hasMoreTokens() ) { + path[i] = tokenizer.nextToken(); + i++; + } + + return path; + } + + public int fileSize() { + return files.size(); + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/LogListener.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/LogListener.java new file mode 100644 index 000000000..7e6f3f60e --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/LogListener.java @@ -0,0 +1,59 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "Ant" and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.taskdefs.optional.ssh; + +public interface LogListener { + void log( String message ); +} 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 new file mode 100644 index 000000000..a943048ca --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Scp.java @@ -0,0 +1,383 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "Ant" and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.taskdefs.optional.ssh; + +import com.jcraft.jsch.*; + +import java.io.*; +import java.util.List; +import java.util.LinkedList; +import java.util.Iterator; +import java.util.ArrayList; + +import org.apache.tools.ant.Task; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; + +/** + * Ant task for sending files to remote machine over ssh/scp. + * + * @author charliehubbard76@yahoo.com + * @since Ant 1.6 + */ +public class Scp extends Task implements LogListener { + + private String fromUri; + private String toUri; + private String knownHosts; + private boolean trust = false; + private int port = 22; + private List fileSets = null; + private boolean failOnError = true; + + public void setFailonerror( boolean failure ) { + failOnError = failure; + } + + /** + * Sets the file to be transferred. This can either be a remote + * file or a local file. Remote files take the form:
+ * user:password@host:/directory/path/file.example
+ * Files to transfer can also include a wildcard to include all + * files in a remote directory. For example:
+ * user:password@host:/directory/path/*
+ * @param aFromUri a string representing the file to transfer. + */ + public void setFile(String aFromUri) { + this.fromUri = aFromUri; + } + + /** + * Sets the location where files will be transferred to. + * This can either be a remote directory or a local directory. + * Remote directories take the form of:
+ * user:password@host:/directory/path/
+ * This parameter is required. + + * @param aToUri a string representing the target of the copy. + */ + public void setTodir(String aToUri) { + this.toUri = aToUri; + } + + /** + * Sets the path to the file that has the identities of + * all known hosts. This is used by SSH protocol to validate + * the identity of the host. The default is + * {$user.dir}/.ssh/known_hosts. + + * @param knownHosts a path to the known hosts file. + */ + public void setKnownhosts( String knownHosts ) { + this.knownHosts = knownHosts; + } + + /** + * Setting this to true trusts hosts whose identity is unknown. + * + * @param yesOrNo if true trust the identity of unknown hosts. + */ + public void setTrust( boolean yesOrNo ) { + this.trust = yesOrNo; + } + + /** + * Changes the port used to connect to the remote host. + * + * @param port port number of remote host. + */ + public void setPort( int port ) { + this.port = port; + } + + /** + * Adds a FileSet tranfer to remote host. NOTE: Either + * addFileSet() or setFile() are required. But, not both. + * + * @param set FileSet to send to remote host. + */ + public void addFileset( FileSet set ) { + if( fileSets == null ) { + fileSets = new LinkedList(); + } + fileSets.add( set ); + } + + public void init() throws BuildException { + super.init(); + this.toUri = null; + this.fromUri = null; + this.knownHosts = System.getProperty("user.dir") + "/.ssh/known_hosts"; + this.trust = false; + this.port = 22; + this.fileSets = null; + } + + public void execute() throws BuildException { + if (toUri == null) { + throw new BuildException("The 'todir' attribute is required."); + } + + if ( fromUri == null && fileSets == null ) { + throw new BuildException("Either the 'file' attribute or one " + + "FileSet is required."); + } + + boolean isFromRemote = false; + if( fromUri != null ) + isFromRemote = isRemoteUri(fromUri); + boolean isToRemote = isRemoteUri(toUri); + try { + if (isFromRemote && !isToRemote) { + download( fromUri, toUri ); + } else if (!isFromRemote && isToRemote) { + if( fileSets != null ) { + upload( fileSets, toUri ); + } else { + upload( fromUri, toUri ); + } + } else if (isFromRemote && isToRemote) { + // not implemented yet. + } else { + throw new BuildException("'todir' and 'file' attributes " + + "must have syntax like the following: " + + "user:password@host:/path"); + } + } catch (Exception e) { + if( failOnError ) { + throw new BuildException(e); + } else { + e.printStackTrace(); + } + } + } + + private void download( String fromSshUri, String toPath ) + throws JSchException, IOException { + String[] fromValues = parseUri(fromSshUri); + + Session session = null; + try { + session = openSession(fromValues[0], + fromValues[1], + fromValues[2], + port ); + ScpFromMessage message = new ScpFromMessage( session, + fromValues[3], + new File( toPath ), + fromSshUri.endsWith("*") ); + log("Receiving file: " + fromValues[3] ); + message.setLogListener( this ); + message.execute(); + } finally { + if( session != null ) + session.disconnect(); + } + } + + private void upload( List fileSet, String toSshUri ) + throws IOException, JSchException { + String[] toValues = parseUri(toSshUri); + + Session session = null; + try { + session = openSession( toValues[0], + toValues[1], + toValues[2], + port ); + List list = new ArrayList( fileSet.size() ); + for( Iterator i = fileSet.iterator(); i.hasNext(); ) { + FileSet set = (FileSet) i.next(); + list.add( createDirectory( set ) ); + } + ScpToMessage message = new ScpToMessage( session, + list, + toValues[3] ); + message.setLogListener( this ); + message.execute(); + } finally { + if( session != null ) + session.disconnect(); + } + } + + private void upload( String fromPath, String toSshUri ) + throws IOException, JSchException { + String[] toValues = parseUri(toSshUri); + + Session session = null; + try { + session = openSession( toValues[0], + toValues[1], + toValues[2], + port ); + ScpToMessage message = new ScpToMessage( session, + new File( fromPath ), + toValues[3] ); + message.setLogListener( this ); + message.execute(); + } finally { + if( session != null ) + session.disconnect(); + } + } + + private Session openSession( String user, String password, + String host, int port ) + throws JSchException { + JSch jsch = new JSch(); + if( knownHosts != null ) { + log( "Using known hosts: " + knownHosts, Project.MSG_DEBUG ); + jsch.setKnownHosts( knownHosts ); + } + Session session = jsch.getSession( user, host, port ); + + UserInfo userInfo = new DefaultUserInfo( password, trust ); + session.setUserInfo(userInfo); + log("Connecting to " + host + ":" + port ); + session.connect(); + return session; + } + + private String[] parseUri(String uri) { + int indexOfAt = uri.indexOf('@'); + int indexOfColon = uri.indexOf(':'); + int indexOfPath = uri.indexOf(':', indexOfColon + 1); + + String[] values = new String[4]; + values[0] = uri.substring(0, indexOfColon); + values[1] = uri.substring(indexOfColon + 1, indexOfAt); + values[2] = uri.substring(indexOfAt + 1, indexOfPath); + values[3] = uri.substring(indexOfPath + 1); + + return values; + } + + private boolean isRemoteUri(String uri) { + boolean isRemote = true; + int indexOfAt = uri.indexOf('@'); + if (indexOfAt < 0) { + isRemote = false; + } + return isRemote; + } + + private Directory createDirectory( FileSet set ) { + DirectoryScanner scanner = set.getDirectoryScanner( project ); + Directory root = new Directory( scanner.getBasedir() ); + String[] files = scanner.getIncludedFiles(); + for (int j = 0; j < files.length; j++) { + String[] path = Directory.getPath( files[j] ); + Directory current = root; + File currentParent = scanner.getBasedir(); + for( int i = 0; i < path.length; i++ ) { + File file = new File( currentParent, path[i] ); + if( file.isDirectory() ) { + current.addDirectory( new Directory( file ) ); + current = current.getChild( file ); + currentParent = current.getDirectory(); + } else if( file.isFile() ) { + current.addFile( file ); + } + } + } + + return root; + } + + + public class DefaultUserInfo implements UserInfo { + private String password = null; + private boolean firstTime = true; + private boolean trustAllCertificates; + + public DefaultUserInfo(String password, boolean trustAllCertificates) { + this.password = password; + this.trustAllCertificates = trustAllCertificates; + } + + public String getPassphrase() { + return null; + } + + public String getPassword() { + return password; + } + + public boolean promptPassword( String passwordPrompt ) { + log( passwordPrompt, Project.MSG_DEBUG ); + if( firstTime ) { + firstTime = false; + return true; + } + return firstTime; + } + + public boolean promptPassphrase( String passPhrasePrompt ) { + return true; + } + + public boolean promptYesNo( String prompt ) { + log( prompt, Project.MSG_DEBUG ); + return trustAllCertificates; + } + + public void showMessage( String message ) { + log( message, Project.MSG_DEBUG ); + } + } +} 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 new file mode 100644 index 000000000..50ad8537e --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpFromMessage.java @@ -0,0 +1,198 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "Ant" and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.taskdefs.optional.ssh; + +import com.jcraft.jsch.*; + +import java.io.*; +import java.util.StringTokenizer; +import java.text.NumberFormat; + +public class ScpFromMessage extends AbstractSshMessage { + + private String remoteFile; + private File localFile; + private boolean isRecursive = false; + + public ScpFromMessage( Session session, + String aRemoteFile, + File aLocalFile, + boolean recursive ) { + super(session); + this.remoteFile = aRemoteFile; + this.localFile = aLocalFile; + this.isRecursive = recursive; + } + + public void execute() throws IOException { + String command = "scp -f "; + if( isRecursive ) + command += "-r "; + command += remoteFile; + Channel channel = openExecChannel( command ); + try { + // get I/O streams for remote scp + OutputStream out = channel.getOutputStream(); + InputStream in = channel.getInputStream(); + + channel.connect(); + + sendAck(out); + startRemoteCpProtocol( in, out, localFile); + } finally { + if( channel != null ) + channel.disconnect(); + } + log( "done\n" ); + } + + private void startRemoteCpProtocol(InputStream in, + OutputStream out, + File localFile) throws IOException { + File startFile = localFile; + while (true) { + // C0644 filesize filename - header for a regular file + // T time 0 time 0\n - present if perserve time. + // D directory - this is the header for a directory. + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + while( true ) { + int read = in.read(); + if( read < 0 ) return; + if( (byte)read == (byte)0x0a ) break; + stream.write( read ); + } + String serverResponse = stream.toString("UTF-8"); + if( serverResponse.charAt(0) == 'C' ) { + parseAndFetchFile( serverResponse, startFile, out, in ); + } else if( serverResponse.charAt( 0 ) == 'D' ) { + startFile = parseAndCreateDirectory( serverResponse, + startFile ); + sendAck( out ); + } else if( serverResponse.charAt(0) == 'E' ) { + startFile = startFile.getParentFile(); + sendAck( out ); + } else if( serverResponse.charAt( 0 ) == '\01' + || serverResponse.charAt( 0 ) == '\02' ) { + // this indicates an error. + throw new IOException( serverResponse.substring(1) ); + } + } + } + + private File parseAndCreateDirectory(String serverResponse, + File localFile) { + StringTokenizer token = new StringTokenizer( serverResponse ); + String command = token.nextToken(); + token.nextToken(); // appears that this is not used and it's zero. + String directoryName = token.nextToken(); + if( localFile.isDirectory() ) { + File dir = new File( localFile, directoryName ); + dir.mkdir(); + + return dir; + } + return null; + } + + private void parseAndFetchFile(String serverResponse, + File localFile, + OutputStream out, + InputStream in) throws IOException { + StringTokenizer token = new StringTokenizer( serverResponse ); + String command = token.nextToken(); + int filesize = Integer.parseInt( token.nextToken() ); + String filename = token.nextToken(); + NumberFormat formatter = NumberFormat.getIntegerInstance(); + log( "Receiving: " + filename + " : " + formatter.format( filesize ) ); + File transferFile = ( localFile.isDirectory() ) + ? new File( localFile, filename ) + : localFile; + fetchFile( transferFile, filesize, out, in); + waitForAck(in); + sendAck(out); + } + + private void fetchFile( File localFile, + int filesize, + OutputStream out, + InputStream in) throws IOException { + byte[] buf = new byte[1024]; + sendAck(out); + + // read a content of lfile + FileOutputStream fos = new FileOutputStream( localFile ); + int length; + int totalLength = 0; + long startTime = System.currentTimeMillis(); + try { + while (true) { + length = in.read( buf, 0, + (buf.length < filesize) ? buf.length : filesize ); + if( length < 0 ) + throw new EOFException("Unexpected end of stream."); + fos.write( buf, 0, length ); + filesize -= length; + totalLength += length; + if (filesize == 0) break; + } + } finally { + long endTime = System.currentTimeMillis(); + logStats( startTime, endTime, totalLength ); + fos.flush(); + fos.close(); + } + } + +} 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 new file mode 100644 index 000000000..afda37a96 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/ScpToMessage.java @@ -0,0 +1,209 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "Ant" and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.taskdefs.optional.ssh; + +import com.jcraft.jsch.Session; +import com.jcraft.jsch.Channel; + +import java.io.*; +import java.util.*; +import java.text.NumberFormat; + +public class ScpToMessage extends AbstractSshMessage { + + private File localFile; + private String remotePath; + private List directoryList; + + public ScpToMessage(Session session, + File aLocalFile, + String aRemotePath ) { + super(session); + + this.localFile = aLocalFile; + this.remotePath = aRemotePath; + } + + public ScpToMessage( Session session, + List aDirectoryList, + String aRemotePath ) { + super( session ); + + this.directoryList = aDirectoryList; + this.remotePath = aRemotePath; + } + + public void execute() throws IOException { + if( directoryList != null ) { + doMultipleTransfer(); + } + if( localFile != null ) { + doSingleTransfer(); + } + log("done.\n"); + } + + private void doSingleTransfer() throws IOException { + String cmd = "scp -t " + remotePath; + Channel channel = openExecChannel( cmd ); + try { + + OutputStream out = channel.getOutputStream(); + InputStream in = channel.getInputStream(); + + channel.connect(); + + waitForAck(in); + sendFileToRemote(localFile, in, out); + waitForAck(in); + } finally { + if( channel != null ) + channel.disconnect(); + } + } + + private void doMultipleTransfer() throws IOException { + Channel channel = openExecChannel( "scp -d -t " + remotePath ); + try { + OutputStream out = channel.getOutputStream(); + InputStream in = channel.getInputStream(); + + channel.connect(); + + waitForAck(in); + for( Iterator i = directoryList.iterator(); i.hasNext(); ) { + Directory current = (Directory)i.next(); + sendDirectory( current, in, out ); + } + waitForAck(in); + } finally { + if( channel != null ) + channel.disconnect(); + } + } + + private void sendDirectory(Directory current, + InputStream in, + OutputStream out) throws IOException { + for( Iterator fileIt = current.filesIterator(); fileIt.hasNext(); ) { + sendFileToRemote( (File)fileIt.next(), in, out ); + } + for( Iterator dirIt = current.directoryIterator(); dirIt.hasNext(); ) { + Directory dir = (Directory) dirIt.next(); + sendDirectoryToRemote( dir, in ,out ); + } + } + + private void sendDirectoryToRemote( Directory directory, + InputStream in, + OutputStream out ) throws IOException { + String command = "D0755 0 "; + command += directory.getDirectory().getName(); + command += "\n"; + + out.write( command.getBytes() ); + out.flush(); + + waitForAck(in); + sendDirectory( directory, in, out ); + out.write( "E\n".getBytes() ); + } + + private void sendFileToRemote( File localFile, + InputStream in, + OutputStream out ) throws IOException { + // send "C0644 filesize filename", where filename should not include '/' + int filesize = (int) localFile.length(); + String command = "C0644 " + filesize + " "; + command += localFile.getName(); + command += "\n"; + + out.write( command.getBytes() ); + out.flush(); + + waitForAck(in); + + // send a content of lfile + FileInputStream fis = new FileInputStream(localFile); + byte[] buf = new byte[1024]; + long startTime = System.currentTimeMillis(); + int totalLength = 0; + try { + NumberFormat formatter = NumberFormat.getIntegerInstance(); + log( "Sending: " + localFile.getName() + " : " + + formatter.format( localFile.length() ) ); + while (true) { + int len = fis.read(buf, 0, buf.length); + if (len <= 0) break; + out.write(buf, 0, len); + totalLength += len; + } + out.flush(); + sendAck(out); + } finally { + long endTime = System.currentTimeMillis(); + logStats( startTime, endTime, totalLength ); + fis.close(); + } + } + + public File getLocalFile() { + return localFile; + } + + public String getRemotePath() { + return remotePath; + } +} diff --git a/src/testcases/org/apache/tools/ant/taskdefs/optional/ssh/ScpTest.java b/src/testcases/org/apache/tools/ant/taskdefs/optional/ssh/ScpTest.java new file mode 100644 index 000000000..329fe9090 --- /dev/null +++ b/src/testcases/org/apache/tools/ant/taskdefs/optional/ssh/ScpTest.java @@ -0,0 +1,202 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "Ant" and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.taskdefs.optional.ssh; + +import junit.framework.TestCase; + +import java.io.*; +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.condition.FilesMatch; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.selectors.FilenameSelector; + +/** + * This is a unit test for the Scp task in Ant. It must be + * configured with command line options in order for it to work. + * Here are the options: + * + * scp.tmp This is a local path to a temporary + * directory for this task to use. + * scp.host This is the remote location of the form: + * "user:password@host:/path/to/directory" + * scp.port The port of the listening SSH service. + * Defaults to 22. (optional) + * scp.known.hosts The file containing the public keys of known + * hosts. Must be a SSH2 version file, but + * supports RSA and DSA keys. If it is not present + * this task setTrust() to true. (optional) + */ +public class ScpTest extends TestCase { + + private File tempDir = new File( System.getProperty("scp.tmp") ); + private String sshHostUri = System.getProperty("scp.host"); + private int port = Integer.parseInt( System.getProperty( "scp.port", "22" ) ); + private String knownHosts = System.getProperty("scp.known.hosts"); + + private List cleanUpList = new ArrayList(); + + public ScpTest(String testname) { + super(testname); + } + + protected void setUp() { + cleanUpList.clear(); + } + + protected void tearDown() { + for( Iterator i = cleanUpList.iterator(); i.hasNext(); ) { + File file = (File) i.next(); + file.delete(); + } + } + + public void testSingleFileUploadAndDownload() throws IOException { + File uploadFile = createTemporaryFile(); + + Scp scpTask = createTask(); + scpTask.setFile( uploadFile.getPath() ); + scpTask.setTodir( sshHostUri ); + scpTask.execute(); + + File testFile = new File( tempDir.getPath() + File.separator + + "download-testSingleFileUploadAndDownload.test" ); + addCleanup( testFile ); + assertTrue( "Assert that the testFile does not exist.", + !testFile.exists() ); + + scpTask.setFile( sshHostUri + "/" + uploadFile.getName() ); + scpTask.setTodir( testFile.getPath() ); + scpTask.execute(); + + assertTrue( "Assert that the testFile exists.", testFile.exists() ); + compareFiles( uploadFile, testFile ); + } + + public void testMultiUploadAndDownload() throws IOException { + List uploadList = new ArrayList(); + for( int i = 0; i < 5; i++ ) { + uploadList.add( createTemporaryFile() ); + } + + Scp scp = createTask(); + FilenameSelector selector = new FilenameSelector(); + selector.setName( "scp*" ); + FileSet fileset = new FileSet(); + fileset.setDir( tempDir ); + fileset.addFilename( selector ); + scp.addFileset( fileset ); + scp.setTodir( sshHostUri ); + scp.execute(); + + File multi = new File( tempDir, "multi" ); + multi.mkdir(); + addCleanup( multi ); + + Scp scp2 = createTask(); + scp2.setFile( sshHostUri + "/scp*" ); + scp2.setTodir( multi.getPath() ); + scp2.execute(); + + FilesMatch match = new FilesMatch(); + for( Iterator i = uploadList.iterator(); i.hasNext(); ) { + File f = (File)i.next(); + match.setFile1( f ); + File f2 = new File( multi, f.getName() ); + match.setFile2( f2 ); + assertTrue("Assert file '" + f.getPath() + "' and file '" + + f2.getPath() + "'", match.eval() ); + } + } + + public void addCleanup( File file ) { + cleanUpList.add( file ); + } + + private void compareFiles(File src, File dest) { + FilesMatch match = new FilesMatch(); + match.setFile1( src ); + match.setFile2( dest ); + + assertTrue( "Assert files are equal.", match.eval() ); + } + + private File createTemporaryFile() throws IOException { + File uploadFile; + uploadFile = File.createTempFile( "scp", "test", tempDir ); + FileWriter writer = new FileWriter( uploadFile ); + writer.write("Can you hear me now?\n"); + writer.close(); + addCleanup( uploadFile ); + return uploadFile; + } + + private Scp createTask() { + Scp scp = new Scp(); + Project p = new Project(); + p.init(); + scp.setProject( p ); + if( knownHosts != null ) { + scp.setKnownhosts( knownHosts ); + } else { + scp.setTrust( true ); + } + scp.setPort( port ); + return scp; + } +}