Learn how easy it is to sync an existing GitHub or Google Code repo to a SourceForge project! See Demo

Close

#16 try chmod configurable for SftpClient

General Request
open
nobody
J2SSH (7)
5
1 day ago
2006-03-03
M_S_Hater
No

Adapt SshClient.java and SftpClient.java make it
configurable to allow NOT to try a chmod for a new
file/directory on remote server. This can give errors
if chmod is not allowed (dropbox on remote server with
minimal rights).

Adapted code SshClient.java :

/*
* SSHTools - Java SSH2 API
*
* Copyright (C) 2002-2003 Lee David Painter and
Contributors.
*
* Contributions made by:
*
* Brett Smith
* Richard Pernavas
* Erwin Bolwidt
*
* This program is free software; you can
redistribute it and/or
* modify it under the terms of the GNU Library
General Public License
* as published by the Free Software Foundation;
either version 2 of
* the License, or (at your option) any later version.
*
* You may also distribute it and/or modify it under
the terms of the
* Apache style J2SSH Software License. A copy of
which should have
* been provided with the distribution.
*
* This program is distributed in the hope that it
will be useful,
* but WITHOUT ANY WARRANTY; without even the implied
warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the
* License document supplied with your distribution
for more details.
*
*/
package com.sshtools.j2ssh;

import
com.sshtools.j2ssh.authentication.AuthenticationProtoco
lClient;
import
com.sshtools.j2ssh.authentication.AuthenticationProtoco
lState;
import
com.sshtools.j2ssh.authentication.PublicKeyAuthenticati
onClient;
import
com.sshtools.j2ssh.authentication.SshAuthenticationClie
nt;
import
com.sshtools.j2ssh.configuration.SshConnectionPropertie
s;
import com.sshtools.j2ssh.connection.Channel;
import
com.sshtools.j2ssh.connection.ChannelEventAdapter;
import
com.sshtools.j2ssh.connection.ChannelEventListener;
import com.sshtools.j2ssh.connection.ChannelFactory;
import
com.sshtools.j2ssh.connection.ConnectionProtocol;
import com.sshtools.j2ssh.forwarding.ForwardingClient;
import com.sshtools.j2ssh.net.TransportProvider;
import com.sshtools.j2ssh.net.TransportProviderFactory;
import com.sshtools.j2ssh.session.SessionChannelClient;
import com.sshtools.j2ssh.sftp.SftpSubsystemClient;
import
com.sshtools.j2ssh.transport.ConsoleKnownHostsKeyVerifi
cation;
import
com.sshtools.j2ssh.transport.HostKeyVerification;
import
com.sshtools.j2ssh.transport.TransportProtocolClient;
import
com.sshtools.j2ssh.transport.TransportProtocolState;
import
com.sshtools.j2ssh.transport.publickey.SshPublicKey;
import com.sshtools.j2ssh.util.State;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.File;
import java.io.IOException;

import java.net.UnknownHostException;

import java.util.Iterator;
import java.util.List;
import java.util.Vector;

/**
* <p>
* Implements an SSH client with methods to connect to
a remote server and
* perform all necersary SSH functions such as SCP,
SFTP, executing commands,
* starting the users shell and perform port
forwarding.
* </p>
*
* <p>
* There are several steps to perform prior to
performing the desired task.
* This involves the making the initial connection,
authenticating the user
* and creating a session to execute a command, shell
or subsystem and/or
* configuring the port forwarding manager.
* </p>
*
* <p>
* To create a connection use the following code:<br>
* <blockquote><pre>
* // Create a instance and connect SshClient
* ssh = new SshClient();
* ssh.connect("hostname");
* </pre></blockquote>
* Once this code has executed and returned
* the connection is ready for authentication:<br>
* <blockquote><pre>
* PasswordAuthenticationClient pwd = new
PasswordAuthenticationClient();
* pwd.setUsername("foo");
* pwd.setPassword("xxxxx");
* // Authenticate the user
* int result = ssh.authenticate(pwd);
* if(result==AuthenticationProtocolState.COMPLETED) {
* // Authentication complete
* }
* </pre></blockquote>
* Once authenticated the user's shell can be
started:<br>
* <blockquote><pre>
* // Open a session channel
* SessionChannelClient session =
* ssh.openSessionChannel();
*
* // Request a pseudo terminal, if you do not you may
not see the prompt
* if(session.requestPseudoTerminal("ansi", 80, 24, 0,
0, "") {
* // Start the users shell
* if(session.startShell()) {
* // Do something with the session output
* session.getOutputStream().write("echo
message\n");
* ....
* }
* }
* </pre></blockquote>
* </p>
*
* @author Lee David Painter
* @version $Revision: 1.75 $
*
* @since 0.2.0
*/
public class SshClient {
private static Log log = LogFactory.getLog
(SshClient.class);

/**
* The SSH Authentication protocol implementation
for this SSH client. The
* SSH Authentication protocol runs over the SSH
Transport protocol as a
* transport protocol service.
*/
protected AuthenticationProtocolClient
authentication;

/**
* The SSH Connection protocol implementation for
this SSH client. The
* connection protocol runs over the SSH Transport
protocol as a transport
* protocol service and is started by the
authentication protocol after a
* successful authentication.
*/
protected ConnectionProtocol connection;

/** Provides a high level management interface for
SSH port forwarding. */
protected ForwardingClient forwarding;

/** The SSH Transport protocol implementation for
this SSH Client. */
protected TransportProtocolClient transport;

/** The current state of the authentication for
the current connection. */
protected int authenticationState =
AuthenticationProtocolState.READY;

/**
* The timeout in milliseconds for the underlying
transport provider
* (typically a Socket).
*/
protected int socketTimeout = 0;

/**
* A Transport protocol event handler instance
that receives notifications
* of transport layer events such as Socket
timeouts and disconnection.
*/
protected SshEventAdapter eventHandler = null;

/** The currently active channels for this SSH
Client connection. */
protected Vector activeChannels = new Vector();

/**
* An channel event listener implemention to
maintain the active channel
* list.
*/
protected ActiveChannelEventListener
activeChannelListener = new ActiveChannelEventListener
();

/**
* Flag indicating whether the forwarding instance
is created when the
* connection is made.
*/
protected boolean useDefaultForwarding = true;

/** The currently active Sftp clients */
private Vector activeSftpClients = new Vector();

/**
* <p>
* Contructs an unitilialized SshClient ready for
connecting.
* </p>
*/
public SshClient() {
}

/**
* <p>
* Returns the server's authentication banner.
* </p>
*
* <p>
* In some jurisdictions, sending a warning
message before authentication
* may be relevant for getting legal protection.
Many UNIX machines, for
* example, normally display text from
`/etc/issue', or use "tcp wrappers"
* or similar software to display a banner before
issuing a login prompt.
* </p>
*
* <p>
* The server may or may not send this message.
Call this method to
* retrieve the message, specifying a timeout
limit to wait for the
* message.
* </p>
*
* @param timeout The number of milliseconds to
wait for the banner message
* before returning
*
* @return The server's banner message
*
* @exception IOException If an IO error occurs
reading the message
*
* @since 0.2.0
*/
public String getAuthenticationBanner(int timeout)
throws IOException {
if (authentication == null) {
return "";
} else {
return authentication.getBannerMessage
(timeout);
}
}

/**
* <p>
* Returns the list of available authentication
methods for a given user.
* </p>
*
* <p>
* A client may request a list of authentication
methods that may continue
* by using the "none" authentication method.This
method calls the "none"
* method and returns the available authentication
methods.
* </p>
*
* @param username The name of the account for
which you require the
* available authentication methods
*
* @return A list of Strings, for
example "password", "publickey" &
* "keyboard-interactive"
*
* @exception IOException If an IO error occurs
during the operation
*
* @since 0.2.0
*/
public List getAvailableAuthMethods(String
username)
throws IOException {
if (authentication != null) {
return authentication.getAvailableAuths
(username,
connection.getServiceName());
} else {
return null;
}
}

/**
* <p>
* Returns the connection state of the client.
* </p>
*
* @return true if the client is connected, false
otherwise
*
* @since 0.2.0
*/
public boolean isConnected() {
State state = (transport == null) ? null :
transport.getState();
int value = (state == null) ?
TransportProtocolState.DISCONNECTED
: state.getValue();

return ((value ==
TransportProtocolState.CONNECTED) ||
(value ==
TransportProtocolState.PERFORMING_KEYEXCHANGE));
}

/**
* <p>
* Evaluate whether the client has successfully
authenticated.
* </p>
*
* @return true if the client is authenticated,
otherwise false
*/
public boolean isAuthenticated() {
return authenticationState ==
AuthenticationProtocolState.COMPLETE;
}

/**
* <p>
* Returns the identification string sent by the
server during protocol
* negotiation. For example "SSH-2.0-OpenSSH_p3.4".
* </p>
*
* @return The server's identification string.
*
* @since 0.2.0
*/
public String getServerId() {
return transport.getRemoteId();
}

/**
* <p>
* Returns the server's public key supplied during
key exchange.
* </p>
*
* @return the server's public key
*
* @since 0.2.0
*/
public SshPublicKey getServerHostKey() {
return transport.getServerHostKey();
}

/**
* <p>
* Returns the transport protocol's connection
state.
* </p>
*
* @return The transport protocol's state
*
* @since 0.2.0
*/
public TransportProtocolState getConnectionState()
{
return transport.getState();
}

/**
* <p>
* Returns the default port forwarding manager.
* </p>
*
* @return This connection's forwarding client
*
* @since 0.2.0
*/
public ForwardingClient getForwardingClient() {
return forwarding;
}

/**
* <p>
* Return's a rough guess at the server's EOL
setting. This is simply
* derived from the identification string and
should not be used as a cast
* iron proof on the EOL setting.
* </p>
*
* @return The transport protocol's EOL constant
*
* @since 0.2.0
*/
public int getRemoteEOL() {
return transport.getRemoteEOL();
}

/**
* <p>
* Set the event handler for the underlying
transport protocol.
* </p>
* <blockquote>
* <pre>
* ssh.setEventHandler(new
TransportProtocolEventHandler() {
*
* public void onSocketTimeout(TransportProtocol
transport) {<br>
* // Do something to handle the socket
timeout<br>
* }
*
* public void onDisconnect(TransportProtocol
transport) {
* // Perhaps some clean up?
* }
* });
* </pre>
* </blockquote>
*
* @param eventHandler The event handler instance
to receive transport
* protocol events
*
* @see
com.sshtools.j2ssh.transport.TransportProtocolEventHand
ler
* @since 0.2.0
*/
public void addEventHandler(SshEventAdapter
eventHandler) {
// If were connected then add, otherwise store
for later connection
if (transport != null) {
transport.addEventHandler(eventHandler);
authentication.addEventListener
(eventHandler);
} else {
this.eventHandler = eventHandler;
}
}

/**
* <p>
* Set's the socket timeout (in milliseconds) for
the underlying transport
* provider. This MUST be called prior to connect.
* </p>
* <blockquote>
* SshClient ssh = new SshClient();
* ssh.setSocketTimeout(30000);
* ssh.connect("hostname");
* </blockquote>
*
* @param milliseconds The number of milliseconds
without activity before
* the timeout event occurs
*
* @since 0.2.0
*/
public void setSocketTimeout(int milliseconds) {
this.socketTimeout = milliseconds;
}

/**
* <p>
* Return's a rough guess at the server's EOL
setting. This is simply
* derived from the identification string and
should not be used as a cast
* iron proof on the EOL setting.
* </p>
*
* @return The EOL string
*
* @since 0.2.0
*/
public String getRemoteEOLString() {
return ((transport.getRemoteEOL() ==
TransportProtocolClient.EOL_CRLF)
? "\r\n" : "\n");
}

/**
* Get the connection properties for this
connection.
*
* @return
*/
public SshConnectionProperties
getConnectionProperties() {
return transport.getProperties();
}

/**
* <p>
* Authenticate the user on the remote host.
* </p>
*
* <p>
* To authenticate the user, create an
<code>SshAuthenticationClient</code>
* instance and configure it with the
authentication details.
* </p>
* <code> PasswordAuthenticationClient pwd = new
* PasswordAuthenticationClient(); pwd.setUsername
("root");
* pwd.setPassword("xxxxxxxxx"); int result =
ssh.authenticate(pwd);
* </code>
*
* <p>
* The method returns a result value will one of
the public static values
* defined in
<code>AuthenticationProtocolState</code>. These are<br>
* <br>
* COMPLETED - The authentication succeeded.<br>
* PARTIAL - The authentication succeeded but a
further authentication
* method is required.<br>
* FAILED - The authentication failed.<br>
* CANCELLED - The user cancelled authentication
(can only be returned
* when the user is prompted for information.<br>
* </p>
*
* @param auth A configured
SshAuthenticationClient instance ready for
* authentication
*
* @return The authentication result
*
* @exception IOException If an IO error occurs
during authentication
*
* @since 0.2.0
*/
public int authenticate(SshAuthenticationClient
auth)
throws IOException {
// Do the authentication
authenticationState =
authentication.authenticate(auth, connection);

if ((authenticationState ==
AuthenticationProtocolState.COMPLETE) &&
useDefaultForwarding) {
// Use some method to synchronize
forwardings on the ForwardingClient
forwarding.synchronizeConfiguration
(transport.getProperties());
}

return authenticationState;
}

/**
* <p>
* Determine whether a private/public key pair
will be accepted for public
* key authentication.
* </p>
*
* <p>
* When using public key authentication, the
signing of data could take
* some time depending upon the available machine
resources. By calling
* this method, you can determine whether the
server will accept a key for
* authentication by providing the public key. The
server will verify the
* key against the user's authorized keys and
return true should the
* public key be authorized. The caller can then
proceed with the private
* key operation.
* </p>
*
* @param username The username for authentication
* @param key The public key for which
authentication will be attempted
*
* @return true if the server will accept the key,
otherwise false
*
* @exception IOException If an IO error occurs
during the operation
* @throws SshException
*
* @since 0.2.0
*/
public boolean acceptsKey(String username,
SshPublicKey key)
throws IOException {
if (authenticationState !=
AuthenticationProtocolState.COMPLETE) {
PublicKeyAuthenticationClient pk = new
PublicKeyAuthenticationClient();

return pk.acceptsKey(authentication,
username,
connection.getServiceName(), key);
} else {
throw new SshException("Authentication has
been completed!");
}
}

/**
* <p>
* Connect the client to the server using default
connection properties.
* </p>
*
* <p>
* This call attempts to connect to the hostname
specified on the standard
* SSH port of 22 and uses all the default
connection properties. This
* call is the equivilent of calling:
* </p>
* <blockquote><pre>
* SshConnectionProperties properties = new
*
SshConnectionProperties();
* properties.setHostname("hostname");
* ssh.connect(properties);
* </pre></blockquote>
*
* @param hostname The hostname of the server to
connect
*
* @exception IOException If an IO error occurs
during the connect
* operation
*
* @see #connect
(com.sshtools.j2ssh.configuration.SshConnectionProperti
es)
* @since 0.2.0
*/
public void connect(String hostname) throws
IOException {
connect(hostname, 22, new
ConsoleKnownHostsKeyVerification());
}

/**
* <p>
* Connect the client to the server using the
default connection
* properties.
* </p>
*
* <p>
* This call attempts to connect to the hostname
specified on the standard
* SSH port of 22 and uses all the default
connection properties. When
* this method returns the connection has been
established, the server's
* identity been verified and the connection is
ready for user
* authentication. Host key verification will be
performed using the host
* key verification instance provided:
* </p>
* <blockquote><pre>
* // Connect and consult $HOME/.ssh/known_hosts
* ssh.connect("hostname", new
ConsoleKnownHostsKeyVerification());
* // Connect and allow any host
* ssh.connect("hostname", new
* IgnoreHostKeyVerification());
* </pre></blockquote>
*
* <p>
* You can provide your own host key verification
process by implementing
* the <code>HostKeyVerification</code> interface.
* </p>
*
* @param hostname The hostname of the server to
connect
* @param hosts The host key verification instance
to consult for host key
* validation
*
* @exception IOException If an IO error occurs
during the connect
* operation
*
* @see #connect
(com.sshtools.j2ssh.configuration.SshConnectionProperti
es,
*
com.sshtools.j2ssh.transport.HostKeyVerification)
* @since 0.2.0
*/
public void connect(String hostname,
HostKeyVerification hosts)
throws IOException {
connect(hostname, 22, hosts);
}

/**
* <p>
* Connect the client to the server on a specified
port with default
* connection properties.
* </p>
*
* <p>
* This call attempts to connect to the hostname
and port specified. This
* call is the equivilent of calling:
* </p>
* <blockquote></pre>
* SshConnectionProperties properties = new
*
SshConnectionProperties();
* properties.setHostname("hostname");
* properties.setPort(10022);
* ssh.connect(properties);
* </pre></blockquote>
*
* @param hostname The hostname of the server to
connect
* @param port The port to connect
*
* @exception IOException If an IO error occurs
during the connect
* operation
*
* @see #connect
(com.sshtools.j2ssh.configuration.SshConnectionProperti
es)
* @since 0.2.0
*/
public void connect(String hostname, int port)
throws IOException {
connect(hostname, port, new
ConsoleKnownHostsKeyVerification());
}

/**
* <p>
* Connect the client to the server on a specified
port with default
* connection properties.
* </p>
*
* <p>
* This call attempts to connect to the hostname
and port specified. When
* this method returns the connection has been
established, the server's
* identity been verified and the connection is
ready for user
* authentication. Host key verification will be
performed using the host
* key verification instance provided:
* </p>
* <blockquote><pre>
* // Connect and consult $HOME/.ssh/known_hosts
* ssh.connect("hostname", new
ConsoleKnownHostsKeyVerification());
* // Connect and allow any host
* ssh.connect("hostname", new
* IgnoreHostKeyVerification());
* </pre></blockquote>
*
* <p>
* You can provide your own host key verification
process by implementing
* the <code>HostKeyVerification</code> interface.
* </p>
*
* @param hostname The hostname of the server to
connect
* @param port The port to connect
* @param hosts The host key verification instance
to consult for host key
* validation
*
* @exception IOException If an IO error occurs
during the connect
* operation
*
* @see #connect
(com.sshtools.j2ssh.configuration.SshConnectionProperti
es,
*
com.sshtools.j2ssh.transport.HostKeyVerification)
* @since 0.2.0
*/
public void connect(String hostname, int port,
HostKeyVerification hosts)
throws IOException {
SshConnectionProperties properties = new
SshConnectionProperties();
properties.setHost(hostname);
properties.setPort(port);
connect(properties, hosts);
}

/**
* <p>
* Connect the client to the server with the
specified properties.
* </p>
*
* <p>
* This call attempts to connect to using the
connection properties
* specified. When this method returns the
connection has been
* established, the server's identity been
verified and the connection is
* ready for user authentication. To use this
method first create a
* properties instance and set the required fields.
* </p>
* <blockquote><pre>
* SshConnectionProperties properties = new
* SshConnectionProperties
();
* properties.setHostname("hostname");
* properties.setPort(10022);
* properties.setPrefCSEncryption("blowfish-cbc");
* ssh.connect(properties);
* </pre></blockquote>
*
* <p>
* Host key verification will be performed using
* <code>ConsoleKnownHostsKeyVerification</code>
and so this call is the
* equivilent of calling:
* </p>
* <blockquote><pre>
* ssh.connect("hostname", new
ConsoleKnownHostsKeyVerification());
* </pre></blockquote>
*
* <p>
* If the key is not matched to any keys already
in the
* $HOME/.ssh/known_hosts file, the user will be
prompted via the console
* to confirm the identity of the remote server.
The user will receive the
* following prompt.
* </p>
* <code> The host shell.sourceforge.net is
currently unknown to the system
* The host key fingerprint is: 1024: 4c 68 3 d4
5c 58 a6 1d 9d 17 13 24
* 14 48 ba 99 Do you want to allow this host key?
[Yes|No|Always]:
* </code>
*
* <p>
* Selecting the "always" option will write the
key to the known_hosts
* file.
* </p>
*
* @param properties The connection properties
*
* @exception IOException If an IO error occurs
during the connect
* operation
*
* @since 0.2.0
*/
public void connect(SshConnectionProperties
properties)
throws IOException {
connect(properties, new
ConsoleKnownHostsKeyVerification());
}

/**
* <p>
* Connect the client to the server with the
specified properties.
* </p>
*
* <p>
* This call attempts to connect to using the
connection properties
* specified. When this method returns the
connection has been
* established, the server's identity been
verified and the connection is
* ready for user authentication. To use this
method first create a
* properties instance and set the required fields.
* </p>
* <blockquote><pre>
* SshConnectionProperties properties = new
*
SshConnectionProperties();
* properties.setHostname("hostname");
* properties.setPort(22); // Defaults
to 22
* // Set the prefered client->server encryption
* ssh.setPrefCSEncryption("blowfish-cbc");
* // Set the prefered server->client encrpytion
* ssh.setPrefSCEncrpyion("3des-cbc");
* ssh.connect(properties);
* </pre></blockquote>
*
* <p>
* Host key verification will be performed using
the host key verification
* instance provided:<br>
* <blockquote><pre>
* // Connect and consult $HOME/.ssh/known_hosts
* ssh.connect("hostname", new
ConsoleKnownHostsKeyVerification());
* // Connect and allow any host
* ssh.connect("hostname", new
* IgnoreHostKeyVerification());
* </pre></blockquote>
* You can provide your own host key verification
process by implementing the
* <code>HostKeyVerification</code> interface.
* </p>
*
* @param properties The connection properties
* @param hostVerification The host key
verification instance to consult
* for host key validation
*
* @exception UnknownHostException If the host is
unknown
* @exception IOException If an IO error occurs
during the connect
* operation
*
* @since 0.2.0
*/
public void connect(SshConnectionProperties
properties,
HostKeyVerification hostVerification)
throws UnknownHostException, IOException {
TransportProvider provider =
TransportProviderFactory.connectTransportProvider
(properties /*, connectTimeout*/,
socketTimeout);

// Start the transport protocol
transport = new TransportProtocolClient
(hostVerification);
transport.addEventHandler(eventHandler);
transport.startTransportProtocol(provider,
properties);

// Start the authentication protocol
authentication = new
AuthenticationProtocolClient();
authentication.addEventListener(eventHandler);
transport.requestService(authentication);
connection = new ConnectionProtocol();

if (useDefaultForwarding) {
forwarding = new ForwardingClient
(connection);
}
}

/**
* <p>
* Sets the timeout value for the key exchange.
* </p>
*
* <p>
* When this time limit is reached the transport
protocol will initiate a
* key re-exchange. The default value is one hour
with the minumin timeout
* being 60 seconds.
* </p>
*
* @param seconds The number of seconds beofre key
re-exchange
*
* @exception IOException If the timeout value is
invalid
*
* @since 0.2.0
*/
public void setKexTimeout(long seconds) throws
IOException {
transport.setKexTimeout(seconds);
}

/**
* <p>
* Sets the key exchance transfer limit in
kilobytes.
* </p>
*
* <p>
* Once this amount of data has been transfered
the transport protocol will
* initiate a key re-exchange. The default value
is one gigabyte of data
* with the mimimun value of 10 kilobytes.
* </p>
*
* @param kilobytes The data transfer limit in
kilobytes
*
* @exception IOException If the data transfer
limit is invalid
*/
public void setKexTransferLimit(long kilobytes)
throws IOException {
transport.setKexTransferLimit(kilobytes);
}

/**
* <p>
* Set's the send ignore flag to send random data
packets.
* </p>
*
* <p>
* If this flag is set to true, then the transport
protocol will send
* additional SSH_MSG_IGNORE packets with random
data.
* </p>
*
* @param sendIgnore true if you want to turn on
random packet data,
* otherwise false
*
* @since 0.2.0
*/
public void setSendIgnore(boolean sendIgnore) {
transport.setSendIgnore(sendIgnore);
}

/**
* <p>
* Turn the default forwarding manager on/off.
* </p>
*
* <p>
* If this flag is set to false before connection,
the client will not
* create a port forwarding manager. Use this to
provide you own
* forwarding implementation.
* </p>
*
* @param useDefaultForwarding Set to false if you
not wish to use the
* default forwarding manager.
*
* @since 0.2.0
*/
public void setUseDefaultForwarding(boolean
useDefaultForwarding) {
this.useDefaultForwarding =
useDefaultForwarding;
}

/**
* <p>
* Disconnect the client.
* </p>
*
* @since 0.2.0
*/
public void disconnect() {
if (connection != null) {
connection.stop();
}

if (transport != null) {
transport.disconnect("Terminating
connection");
}
}

/**
* <p>
* Returns the number of bytes transmitted to the
remote server.
* </p>
*
* @return The number of bytes transmitted
*
* @since 0.2.0
*/
public long getOutgoingByteCount() {
return transport.getOutgoingByteCount();
}

/**
* <p>
* Returns the number of bytes received from the
remote server.
* </p>
*
* @return The number of bytes received
*
* @since 0.2.0
*/
public long getIncomingByteCount() {
return transport.getIncomingByteCount();
}

/**
* <p>
* Returns the number of active channels for this
client.
* </p>
*
* <p>
* This is the total count of sessions, port
forwarding, sftp, scp and
* custom channels currently open.
* </p>
*
* @return The number of active channels
*
* @since 0.2.0
*/
public int getActiveChannelCount() {
synchronized (activeChannels) {
return activeChannels.size();
}
}

/**
* <p>
* Returns the list of active channels.
* </p>
*
* @return The list of active channels
*
* @since 0.2.0
*/
public List getActiveChannels() {
synchronized (activeChannels) {
return (List) activeChannels.clone();
}
}

/**
* <p>
* Returns true if there is an active session
channel of the specified
* type.
* </p>
*
* <p>
* When a session is created, it is assigned a
default type. For instance,
* when a session is created it as a type
of "uninitialized"; however when
* a shell is started on the session, the type is
set to "shell". This
* also occurs for commands where the type is set
to the command which is
* executed and subsystems where the type is set
to the subsystem name.
* This allows each session to be saved in the
active session channel's
* list and recalled later. It is also possible to
set the session
* channel's type using the setSessionType method
of the
* <code>SessionChannelClient</code> class.
* </p>
* <blockquote><pre>
* if(ssh.hasActiveSession("shell")) {
* SessionChannelClient session =
* ssh.getActiveSession("shell");
* }
* </pre></blockquote>
*
* @param type The string specifying the channel
type
*
* @return true if an active session channel
exists, otherwise false
*
* @since 0.2.0
*/
public boolean hasActiveSession(String type) {
Iterator it = activeChannels.iterator();
Object obj;

while (it.hasNext()) {
obj = it.next();

if (obj instanceof SessionChannelClient) {
if (((SessionChannelClient)
obj).getSessionType().equals(type)) {
return true;
}
}
}

return false;
}

/**
* <p>
* Returns the active session channel of the given
type.
* </p>
*
* @param type The type fo session channel
*
* @return The session channel instance
*
* @exception IOException If the session type does
not exist
*
* @since 0.2.0
*/
public SessionChannelClient getActiveSession
(String type)
throws IOException {
Iterator it = activeChannels.iterator();
Object obj;

while (it.hasNext()) {
obj = it.next();

if (obj instanceof SessionChannelClient) {
if (((SessionChannelClient)
obj).getSessionType().equals(type)) {
return (SessionChannelClient) obj;
}
}
}

throw new IOException("There are no active " +
type + " sessions");
}

/**
* Determine whether the channel supplied is an
active channel
*
* @param channel
*
* @return
*/
public boolean isActiveChannel(Channel channel) {
return activeChannels.contains(channel);
}

/**
* <p>
* Open's a session channel on the remote server.
* </p>
*
* <p>
* A session channel may be used to start the
user's shell, execute a
* command or start a subsystem such as SFTP.
* </p>
*
* @return An new session channel
*
* @exception IOException If authentication has
not been completed, the
* server refuses to open the channel
or a general IO error
* occurs
*
* @see
com.sshtools.j2ssh.session.SessionChannelClient
* @since 0.2.0
*/
public SessionChannelClient openSessionChannel()
throws IOException {
return openSessionChannel(null);
}

/**
*
* <p>
* Open's a session channel on the remote server.
* </p>
*
* <p>
* A session channel may be used to start the
user's shell, execute a
* command or start a subsystem such as SFTP.
* </p>
*
* @param eventListener an event listner interface
to add to the channel
*
* @return
*
* @throws IOException
* @throws SshException
*/
public SessionChannelClient openSessionChannel(
ChannelEventListener eventListener) throws
IOException {
if (authenticationState !=
AuthenticationProtocolState.COMPLETE) {
throw new SshException("Authentication has
not been completed!");
}

SessionChannelClient session = new
SessionChannelClient();
session.addEventListener
(activeChannelListener);

if (!connection.openChannel(session,
eventListener)) {
throw new SshException("The server refused
to open a session");
}

return session;
}

/**
* <p>
* Open an SFTP client for file transfer
operations.
* </p>
* <blockquote><pre>
* SftpClient sftp = ssh.openSftpClient();
* sftp.cd("foo");
* sftp.put("somefile.txt");
* sftp.quit();
* </pre></blockquote>
*
* @return Returns an initialized SFTP client
*
* @exception IOException If an IO error occurs
during the operation
*
* @see SftpClient
* @since 0.2.0
*/
public SftpClient openSftpClient() throws
IOException {
return openSftpClient(null);
}

/**
* <p>
* Open an SFTP client for file transfer
operations.
* </p>
* with configurable try chmod
* <blockquote><pre>
* SftpClient sftp = ssh.openSftpClient();
* sftp.cd("foo");
* sftp.put("somefile.txt");
* sftp.quit();
* </pre></blockquote>
*
* @return Returns an initialized SFTP client
*
* @exception IOException If an IO error occurs
during the operation
*
* @see SftpClient
* @since 0.2.0
*/
public SftpClient openSftpClient(boolean tryChmod)
throws IOException {
return openSftpClient(null, tryChmod);
}

/**
* <p>
* Open an SFTP client for file transfer
operations. Adds the supplied
* event listener to the underlying channel.
* </p>
*
* @param eventListener
*
* @return
*
* @throws IOException
*/
public SftpClient openSftpClient
(ChannelEventListener eventListener)
throws IOException {
SftpClient sftp = new SftpClient(this,
eventListener);
activeSftpClients.add(sftp);

return sftp;
}

/**
* <p>
* Open an SFTP client for file transfer
operations. Adds the supplied
* event listener to the underlying channel.
* with configurable try chmod
* </p>
*
* @param eventListener
*
* @return
*
* @throws IOException
*/
public SftpClient openSftpClient
(ChannelEventListener eventListener, boolean tryChmod)
throws IOException {
SftpClient sftp = new SftpClient(this,
eventListener, tryChmod);
activeSftpClients.add(sftp);

return sftp;
}

/**
* Determine if there are existing sftp clients
open
*
* @return
*/
public boolean hasActiveSftpClient() {
synchronized (activeSftpClients) {
return activeSftpClients.size() > 0;
}
}

/**
* Get an active sftp client
*
* @return
*
* @throws IOException
* @throws SshException
*/
public SftpClient getActiveSftpClient() throws
IOException {
synchronized (activeSftpClients) {
if (activeSftpClients.size() > 0) {
return (SftpClient)
activeSftpClients.get(0);
} else {
throw new SshException("There are no
active SFTP clients");
}
}
}

/**
* <p>
* Open an SCP client for file transfer operations
where SFTP is not
* supported.
* </p>
*
* <p>
* Sets the local working directory to the user's
home directory
* </p>
* <blockquote><pre>
* ScpClient scp = ssh.openScpClient();
* scp.put("somefile.txt");
* </pre></blockquote>
*
* @return An initialized SCP client
*
* @exception IOException If an IO error occurs
during the operation
*
* @see ScpClient
* @since 0.2.0
*/
public ScpClient openScpClient() throws
IOException {
return new ScpClient(new File
(System.getProperty("user.home")), this,
false, activeChannelListener);
}

/**
* <p>
* Open an SCP client for file transfer operations
where SFTP is not
* supported.
* </p>
*
* <p>
* This method sets a local current working
directory.
* </p>
* <blockquote><pre>
* ScpClient scp = ssh.openScpClient("foo");
* scp.put("somefile.txt");
* </pre></blockquote>
*
* @param cwd The local directory as the base for
all local files
*
* @return An intialized SCP client
*
* @exception IOException If an IO error occurs
during the operation
*
* @see SftpClient
* @since 0.2.0
*/
public ScpClient openScpClient(File cwd) throws
IOException {
return new ScpClient(cwd, this, false,
activeChannelListener);
}

/**
* <p>
* Open's an Sftp channel.
* </p>
*
* <p>
* Use this sftp channel if you require a lower
level api into the SFTP
* protocol.
* </p>
*
* @return an initialized sftp subsystem instance
*
* @throws IOException if an IO error occurs or
the channel cannot be
* opened
*
* @since 0.2.0
*/
public SftpSubsystemClient openSftpChannel()
throws IOException {
return openSftpChannel(null);
}

/**
* Open an SftpSubsystemChannel. For advanced use
only
*
* @param eventListener
*
* @return
*
* @throws IOException
* @throws SshException
*/
public SftpSubsystemClient openSftpChannel(
ChannelEventListener eventListener) throws
IOException {
SessionChannelClient session =
openSessionChannel(eventListener);
SftpSubsystemClient sftp = new
SftpSubsystemClient();

if (!openChannel(sftp)) {
throw new SshException("The SFTP subsystem
failed to start");
}

// Initialize SFTP
if (!sftp.initialize()) {
throw new SshException(
"The SFTP Subsystem could not be
initialized");
}

return sftp;
}

/**
* <p>
* Open's a channel.
* </p>
*
* <p>
* Call this method to open a custom channel. This
method is used by all
* other channel opening methods. For example the
openSessionChannel
* method could be implemented as:<br>
* <blockquote><pre>
* SessionChannelClient session =
* new SessionChannelClient();
* if(ssh.openChannel(session)) {
* // Channel is now open
* }
* </pre></blockquote>
* </p>
*
* @param channel
*
* @return true if the channel was opened,
otherwise false
*
* @exception IOException if an IO error occurs
* @throws SshException
*
* @since 0.2.0
*/
public boolean openChannel(Channel channel) throws
IOException {
if (authenticationState !=
AuthenticationProtocolState.COMPLETE) {
throw new SshException("Authentication has
not been completed!");
}

// Open the channel providing our channel
listener so we can track
return connection.openChannel(channel,
activeChannelListener);
}

/**
* <p>
* Instructs the underlying connection protocol to
allow channels of the
* given type to be opened by the server.
* </p>
*
* <p>
* The client does not allow channels to be opened
by default. Call this
* method to allow the server to open channels by
providing a
* <code>ChannelFactory</code> implementation to
create instances upon
* request.
* </p>
*
* @param channelName The channel type name
* @param cf The factory implementation that will
create instances of the
* channel when a channel open request is
recieved.
*
* @exception IOException if an IO error occurs
*
* @since 0.2.0
*/
public void allowChannelOpen(String channelName,
ChannelFactory cf)
throws IOException {
connection.addChannelFactory(channelName, cf);
}

/**
* <p>
* Stops the specified channel type from being
opended.
* </p>
*
* @param channelName The channel type name
*
* @throws IOException if an IO error occurs
*
* @since 0.2.1
*/
public void denyChannelOpen(String channelName)
throws IOException {
connection.removeChannelFactory(channelName);
}

/**
* <p>
* Send a global request to the server.
* </p>
*
* <p>
* The SSH specification provides a global request
mechanism which is used
* for starting/stopping remote forwarding. This
is a general mechanism
* which can be used for other purposes if the
server supports the global
* requests.
* </p>
*
* @param requestName The name of the global
request
* @param wantReply true if the server should send
an explict reply
* @param requestData the global request data
*
* @return true if the global request succeeded or
wantReply==false,
* otherwise false
*
* @throws IOException if an IO error occurs
*
* @since 0.2.0
*/
public byte[] sendGlobalRequest(String
requestName, boolean wantReply,
byte[] requestData) throws IOException {
return connection.sendGlobalRequest
(requestName, wantReply, requestData);
}

/**
* <p>
* Implements the
<code>ChannelEventListener</code> interface to provide
* real time tracking of active channels.
* </p>
*/
class ActiveChannelEventListener extends
ChannelEventAdapter {
/**
* <p>
* Adds the channel to the active channel list.
* </p>
*
* @param channel The channel being opened
*/
public void onChannelOpen(Channel channel) {
synchronized (activeChannels) {
activeChannels.add(channel);
}
}

/**
* <p>
* Removes the closed channel from the clients
active channels list.
* </p>
*
* @param channel The channle being closed
*/
public void onChannelClose(Channel channel) {
synchronized (activeChannels) {
activeChannels.remove(channel);
}
}
}
}

Adapted code SftpClient.java :

/*
* SSHTools - Java SSH2 API
*
* Copyright (C) 2002-2003 Lee David Painter and
Contributors.
*
* Contributions made by:
*
* Brett Smith
* Richard Pernavas
* Erwin Bolwidt
*
* This program is free software; you can
redistribute it and/or
* modify it under the terms of the GNU Library
General Public License
* as published by the Free Software Foundation;
either version 2 of
* the License, or (at your option) any later version.
*
* You may also distribute it and/or modify it under
the terms of the
* Apache style J2SSH Software License. A copy of
which should have
* been provided with the distribution.
*
* This program is distributed in the hope that it
will be useful,
* but WITHOUT ANY WARRANTY; without even the implied
warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the
* License document supplied with your distribution
for more details.
*
*/
package com.sshtools.j2ssh;

import com.sshtools.j2ssh.connection.*;
import com.sshtools.j2ssh.io.*;
import com.sshtools.j2ssh.sftp.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;

/**
* <p>
* Implements a Secure File Transfer (SFTP) client.
* </p>
*
* @author Lee David Painter
* @version $Revision: 1.44 $
*
* @since 0.2.0
*/
public class SftpClient {
SftpSubsystemClient sftp;
String cwd;
String lcwd;
private int BLOCKSIZE = 65535;
boolean tryChmod = true ;

// Default permissions is determined by
default_permissions ^ umask
int umask = 0022;
int default_permissions = 0777;

/**
* <p>
* Constructs the SFTP client.
* </p>
*
* @param ssh the <code>SshClient</code> instance
*
* @throws IOException if an IO error occurs
*/
SftpClient(SshClient ssh) throws IOException {
this(ssh, null);
}

/**
* <p>
* Constructs the SFTP client.
* </p>
*
* @param ssh the <code>SshClient</code> instance
* with configurable try chmod
*
* @throws IOException if an IO error occurs
*/
SftpClient(SshClient ssh, boolean tryChmod) throws
IOException {
this(ssh, null, tryChmod);
}

/**
* <p>
* Constructs the SFTP client with a given channel
event listener.
* </p>
*
* @param ssh the <code>SshClient</code> instance
* @param eventListener an event listener
implementation
*
* @throws IOException if an IO error occurs
*/
SftpClient(SshClient ssh, ChannelEventListener
eventListener)
throws IOException {
if (!ssh.isConnected()) {
throw new IOException("SshClient is not
connected");
}

this.sftp = ssh.openSftpChannel(eventListener);

// Get the users default directory
cwd = sftp.getDefaultDirectory();
lcwd = System.getProperty("user.home");
}

/**
* <p>
* Constructs the SFTP client with a given channel
event listener.
* </p>
*
* @param ssh the <code>SshClient</code> instance
* @param eventListener an event listener
implementation
* with configurable try chmod
*
* @throws IOException if an IO error occurs
*/
SftpClient(SshClient ssh, ChannelEventListener
eventListener, boolean tryChmod)
throws IOException {
if (!ssh.isConnected()) {
throw new IOException("SshClient is not
connected");
}

this.sftp = ssh.openSftpChannel(eventListener);
this.tryChmod = tryChmod ;

// Get the users default directory
cwd = sftp.getDefaultDirectory();
lcwd = System.getProperty("user.home");
}

/**
* Sets the umask used by this client.
* @param umask
* @return the previous umask value
*/
public int umask(int umask) {
int old = umask;
this.umask = umask;

return old;
}

/**
* <p>
* Changes the working directory on the remote
server.
* </p>
*
* @param dir the new working directory
*
* @throws IOException if an IO error occurs or
the file does not exist
* @throws FileNotFoundException
*
* @since 0.2.0
*/
public void cd(String dir) throws IOException {
try {
String actual;

if (dir.equals("")) {
actual = sftp.getDefaultDirectory();
} else {
actual = resolveRemotePath(dir);
actual = sftp.getAbsolutePath(actual);
}

FileAttributes attr = sftp.getAttributes
(actual);

if (!attr.isDirectory()) {
throw new IOException(dir + " is not a
directory");
}

cwd = actual;
} catch (IOException ex) {
throw new FileNotFoundException(dir + "
could not be found");
}
}

private File resolveLocalPath(String path) throws
IOException {
File f = new File(path);

if (!f.isAbsolute()) {
f = new File(lcwd, path);
}

return f;
}

private String resolveRemotePath(String path)
throws IOException {
verifyConnection();

String actual;

if (!path.startsWith("/")) {
actual = cwd + (cwd.endsWith
("/") ? "" : "/") + path;
} else {
actual = path;
}

return actual;
}

private void verifyConnection() throws
SshException {
if (sftp.isClosed()) {
throw new SshException("The SFTP
connection has been closed");
}
}

/**
* <p>
* Creates a new directory on the remote server.
This method will throw an
* exception if the directory already exists. To
create directories and
* disregard any errors use the
<code>mkdirs</code> method.
* </p>
*
* @param dir the name of the new directory
*
* @throws IOException if an IO error occurs or if
the directory already
* exists
*
* @since 0.2.0
*/
public void mkdir(String dir) throws IOException {
String actual = resolveRemotePath(dir);

try {
FileAttributes attrs = stat(actual);

if (!attrs.isDirectory()) {
throw new IOException("File already
exists named " + dir);
}
} catch (IOException ex) {
sftp.makeDirectory(actual);
if (tryChmod) chmod(default_permissions ^
umask, actual);
}
}

/**
* <p>
* Create a directory or set of directories. This
method will not fail even
* if the directories exist. It is advisable to
test whether the directory
* exists before attempting an operation by using
the <code>stat</code>
* method to return the directories attributes.
* </p>
*
* @param dir the path of directories to create.
*/
public void mkdirs(String dir) {
StringTokenizer tokens = new StringTokenizer
(dir, "/");
String path = dir.startsWith("/") ? "/" : "";

while (tokens.hasMoreElements()) {
path += (String) tokens.nextElement();

try {
stat(path);
} catch (IOException ex) {
try {
mkdir(path);
} catch (IOException ex2) {
}
}

path += "/";
}
}

/**
* <p>
* Returns the absolute path name of the current
remote working directory.
* </p>
*
* @return the absolute path of the remote working
directory.
*
* @since 0.2.0
*/
public String pwd() {
return cwd;
}

/**
* <p>
* List the contents of the current remote working
directory.
* </p>
*
* <p>
* Returns a list of <code>SftpFile</code>
instances for the current
* working directory.
* </p>
*
* @return a list of SftpFile for the current
working directory
*
* @throws IOException if an IO error occurs
*
* @see com.sshtools.j2ssh.sftp.SftpFile
* @since 0.2.0
*/
public List ls() throws IOException {
return ls(cwd);
}

/**
* <p>
* List the contents remote directory.
* </p>
*
* <p>
* Returns a list of <code>SftpFile</code>
instances for the remote
* directory.
* </p>
*
* @param path the path on the remote server to
list
*
* @return a list of SftpFile for the remote
directory
*
* @throws IOException if an IO error occurs
*
* @see com.sshtools.j2ssh.sftp.SftpFile
* @since 0.2.0
*/
public List ls(String path) throws IOException {
String actual = resolveRemotePath(path);
FileAttributes attrs = sftp.getAttributes
(actual);

if (!attrs.isDirectory()) {
throw new IOException(path + " is not a
directory");
}

SftpFile file = sftp.openDirectory(actual);
Vector children = new Vector();

while (sftp.listChildren(file, children) > -1)
{
;
}

file.close();

return children;
}

/**
* <p>
* Changes the local working directory.
* </p>
*
* @param path the path to the new working
directory
*
* @throws IOException if an IO error occurs
*
* @since 0.2.0
*/
public void lcd(String path) throws IOException {
File actual;

if (!isLocalAbsolutePath(path)) {
actual = new File(lcwd, path);
} else {
actual = new File(path);
}

if (!actual.isDirectory()) {
throw new IOException(path + " is not a
directory");
}

lcwd = actual.getCanonicalPath();
}

private static boolean isLocalAbsolutePath(String
path) {
return (new File(path)).isAbsolute();
}

/**
* <p>
* Returns the absolute path to the local working
directory.
* </p>
*
* @return the absolute path of the local working
directory.
*
* @since 0.2.0
*/
public String lpwd() {
return lcwd;
}

/**
* <p>
* Download the remote file to the local computer.
* </p>
*
* @param path the path to the remote file
* @param progress
*
* @return
*
* @throws IOException if an IO error occurs of
the file does not exist
* @throws TransferCancelledException
*
* @since 0.2.0
*/
public FileAttributes get(String path,
FileTransferProgress progress)
throws IOException, TransferCancelledException
{
String localfile;

if (path.lastIndexOf("/") > -1) {
localfile = path.substring(path.lastIndexOf
("/") + 1);
} else {
localfile = path;
}

return get(path, localfile, progress);
}

/**
*
*
* @param path
*
* @return
*
* @throws IOException
*/
public FileAttributes get(String path) throws
IOException {
return get(path, (FileTransferProgress) null);
}

private void transferFile(InputStream in,
OutputStream out)
throws IOException, TransferCancelledException
{
transferFile(in, out, null);
}

private void transferFile(InputStream in,
OutputStream out,
FileTransferProgress progress)
throws IOException, TransferCancelledException
{
try {
long bytesSoFar = 0;
byte[] buffer = new byte[BLOCKSIZE];
int read;

while ((read = in.read(buffer)) > -1) {
if ((progress != null) &&
progress.isCancelled()) {
throw new
TransferCancelledException();
}

if (read > 0) {
out.write(buffer, 0, read);

//out.flush();
bytesSoFar += read;

if (progress != null) {
progress.progressed
(bytesSoFar);
}
}
}
} finally {
try {
in.close();
out.close();
} catch (IOException ex) {
}
}
}

/**
* <p>
* Download the remote file to the local computer.
If the paths provided
* are not absolute the current working directory
is used.
* </p>
*
* @param remote the path/name of the remote file
* @param local the path/name to place the file on
the local computer
* @param progress
*
* @return
*
* @throws IOException if an IO error occurs or
the file does not exist
* @throws TransferCancelledException
*
* @since 0.2.0
*/
public FileAttributes get(String remote, String
local,
FileTransferProgress progress)
throws IOException, TransferCancelledException
{
File localPath = resolveLocalPath(local);

if (!localPath.exists()) {
localPath.getParentFile().mkdirs();
localPath.createNewFile();
}

FileOutputStream out = new FileOutputStream
(localPath);

return get(remote, out, progress);
}

/**
*
*
* @param remote
* @param local
*
* @return
*
* @throws IOException
*/
public FileAttributes get(String remote, String
local)
throws IOException {
return get(remote, local, null);
}

/**
* <p>
* Download the remote file writing it to the
specified
* <code>OutputStream</code>. The OutputStream is
closed by this mehtod
* even if the operation fails.
* </p>
*
* @param remote the path/name of the remote file
* @param local the OutputStream to write
* @param progress
*
* @return
*
* @throws IOException if an IO error occurs or
the file does not exist
* @throws TransferCancelledException
*
* @since 0.2.0
*/
public FileAttributes get(String remote,
OutputStream local,
FileTransferProgress progress)
throws IOException, TransferCancelledException
{
String remotePath = resolveRemotePath(remote);
FileAttributes attrs = stat(remotePath);

if (progress != null) {
progress.started(attrs.getSize().longValue
(), remotePath);
}

SftpFileInputStream in = new
SftpFileInputStream(sftp.openFile(
remotePath,
SftpSubsystemClient.OPEN_READ));
transferFile(in, local, progress);

if (progress != null) {
progress.completed();
}

return attrs;
}

/**
*
*
* @param remote
* @param local
*
* @return
*
* @throws IOException
*/
public FileAttributes get(String remote,
OutputStream local)
throws IOException {
return get(remote, local, null);
}

/**
* <p>
* Returns the state of the SFTP client. The
client is closed if the
* underlying session channel is closed. Invoking
the <code>quit</code>
* method of this object will close the underlying
session channel.
* </p>
*
* @return true if the client is still connected,
otherwise false
*
* @since 0.2.0
*/
public boolean isClosed() {
return sftp.isClosed();
}

/**
* <p>
* Upload a file to the remote computer.
* </p>
*
* @param local the path/name of the local file
* @param progress
*
* @return
*
* @throws IOException if an IO error occurs or
the file does not exist
* @throws TransferCancelledException
*
* @since 0.2.0
*/
public void put(String local, FileTransferProgress
progress)
throws IOException, TransferCancelledException
{
File f = new File(local);
put(local, f.getName(), progress);
}

/**
*
*
* @param local
*
* @return
*
* @throws IOException
*/
public void put(String local) throws IOException {
put(local, (FileTransferProgress) null);
}

/**
* <p>
* Upload a file to the remote computer. If the
paths provided are not
* absolute the current working directory is used.
* </p>
*
* @param local the path/name of the local file
* @param remote the path/name of the destination
file
* @param progress
*
* @return
*
* @throws IOException if an IO error occurs or
the file does not exist
* @throws TransferCancelledException
*
* @since 0.2.0
*/
public void put(String local, String remote,
FileTransferProgress progress)
throws IOException, TransferCancelledException
{
File localPath = resolveLocalPath(local);
FileInputStream in = new FileInputStream
(localPath);

try {
FileAttributes attrs = stat(remote);

if (attrs.isDirectory()) {
File f = new File(local);
remote += ((remote.endsWith
("/") ? "" : "/") + f.getName());
}
} catch (IOException ex) {
}

put(in, remote, progress);
}

/**
*
*
* @param local
* @param remote
*
* @return
*
* @throws IOException
*/
public void put(String local, String remote)
throws IOException {
put(local, remote, null);
}

/**
* <p>
* Upload a file to the remote computer reading
from the specified <code>
* InputStream</code>. The InputStream is closed,
even if the operation
* fails.
* </p>
*
* @param in the InputStream being read
* @param remote the path/name of the destination
file
* @param progress
*
* @return
*
* @throws IOException if an IO error occurs
* @throws TransferCancelledException
*
* @since 0.2.0
*/
public void put(InputStream in, String remote,
FileTransferProgress progress)
throws IOException, TransferCancelledException
{
String remotePath = resolveRemotePath(remote);
SftpFileOutputStream out;
FileAttributes attrs;
boolean newfile = false;

try {
attrs = stat(remotePath);
out = new SftpFileOutputStream
(sftp.openFile(remotePath,

SftpSubsystemClient.OPEN_CREATE |

SftpSubsystemClient.OPEN_TRUNCATE |

SftpSubsystemClient.OPEN_WRITE));
} catch (IOException ex) {
attrs = new FileAttributes();
newfile = true;
attrs.setPermissions(new UnsignedInteger32
(default_permissions ^
umask));
out = new SftpFileOutputStream
(sftp.openFile(remotePath,

SftpSubsystemClient.OPEN_CREATE |

SftpSubsystemClient.OPEN_WRITE, attrs));
}

if (progress != null) {
progress.started(in.available(),
remotePath);
}

transferFile(in, out, progress);

if (progress != null) {
progress.completed();
}

// Set the permissions here since at creation
they dont always work
if (newfile) {
if (tryChmod) chmod(default_permissions ^
umask, remotePath);
}
}

/**
*
*
* @param in
* @param remote
*
* @return
*
* @throws IOException
*/
public void put(InputStream in, String remote)
throws IOException {
put(in, remote, null);
}

/**
* <p>
* Sets the user ID to owner for the file or
directory.
* </p>
*
* @param uid numeric user id of the new owner
* @param path the path to the remote
file/directory
*
* @throws IOException if an IO error occurs or
the file does not exist
*
* @since 0.2.0
*/
public void chown(int uid, String path) throws
IOException {
String actual = resolveRemotePath(path);
FileAttributes attrs = sftp.getAttributes
(actual);
attrs.setUID(new UnsignedInteger32(uid));
sftp.setAttributes(actual, attrs);
}

/**
* <p>
* Sets the group ID for the file or directory.
* </p>
*
* @param gid the numeric group id for the new
group
* @param path the path to the remote
file/directory
*
* @throws IOException if an IO error occurs or
the file does not exist
*
* @since 0.2.0
*/
public void chgrp(int gid, String path) throws
IOException {
String actual = resolveRemotePath(path);
FileAttributes attrs = sftp.getAttributes
(actual);
attrs.setGID(new UnsignedInteger32(gid));
sftp.setAttributes(actual, attrs);
}

/**
* <p>
* Changes the access permissions or modes of the
specified file or
* directory.
* </p>
*
* <p>
* Modes determine who can read, change or execute
a file.
* </p>
* <blockquote><pre>Absolute modes are octal
numbers specifying the complete list of
* attributes for the files; you specify
attributes by OR'ing together
* these bits.
*
* 0400 Individual read
* 0200 Individual write
* 0100 Individual execute (or list
directory)
* 0040 Group read
* 0020 Group write
* 0010 Group execute
* 0004 Other read
* 0002 Other write
* 0001 Other execute </pre></blockquote>
*
* @param permissions the absolute mode of the
file/directory
* @param path the path to the file/directory on
the remote server
*
* @throws IOException if an IO error occurs or
the file if not found
*
* @since 0.2.0
*/
public void chmod(int permissions, String path)
throws IOException {
String actual = resolveRemotePath(path);
sftp.changePermissions(actual, permissions);
}

public void umask(String umask) throws IOException
{
try {
this.umask = Integer.parseInt(umask, 8);
} catch (NumberFormatException ex) {
throw new IOException(
"umask must be 4 digit octal number
e.g. 0022");
}
}

/**
* <p>
* Rename a file on the remote computer.
* </p>
*
* @param oldpath the old path
* @param newpath the new path
*
* @throws IOException if an IO error occurs
*
* @since 0.2.0
*/
public void rename(String oldpath, String newpath)
throws IOException {
String from = resolveRemotePath(oldpath);
String to = resolveRemotePath(newpath);
sftp.renameFile(from, to);
}

/**
* <p>
* Remove a file or directory from the remote
computer.
* </p>
*
* @param path the path of the remote
file/directory
*
* @throws IOException if an IO error occurs
*
* @since 0.2.0
*/
public void rm(String path) throws IOException {
String actual = resolveRemotePath(path);
FileAttributes attrs = sftp.getAttributes
(actual);

if (attrs.isDirectory()) {
sftp.removeDirectory(actual);
} else {
sftp.removeFile(actual);
}
}

/**
*
*
* @param path
* @param force
* @param recurse
*
* @throws IOException
*/
public void rm(String path, boolean force, boolean
recurse)
throws IOException {
String actual = resolveRemotePath(path);
FileAttributes attrs = sftp.getAttributes
(actual);
SftpFile file;

if (attrs.isDirectory()) {
List list = ls(path);

if (!force && (list.size() > 0)) {
throw new IOException(
"You cannot delete non-empty
directory, use force=true to overide");
} else {
for (Iterator it = list.iterator();
it.hasNext();) {
file = (SftpFile) it.next();

if (file.isDirectory() && !
file.getFilename().equals(".") &&
!file.getFilename().equals
("..")) {
if (recurse) {
rm(file.getAbsolutePath(),
force, recurse);
} else {
throw new IOException(
"Directory has
contents, cannot delete without recurse=true");
}
} else if (file.isFile()) {
sftp.removeFile
(file.getAbsolutePath());
}
}
}

sftp.removeDirectory(actual);
} else {
sftp.removeFile(actual);
}
}

/**
* <p>
* Create a symbolic link on the remote computer.
* </p>
*
* @param path the path to the existing file
* @param link the new link
*
* @throws IOException if an IO error occurs or
the operation is not
* supported on the remote platform
*
* @since 0.2.0
*/
public void symlink(String path, String link)
throws IOException {
String actualPath = resolveRemotePath(path);
String actualLink = resolveRemotePath(link);
sftp.createSymbolicLink(actualPath,
actualLink);
}

/**
* <p>
* Returns the attributes of the file from the
remote computer.
* </p>
*
* @param path the path of the file on the remote
computer
*
* @return the attributes
*
* @throws IOException if an IO error occurs or
the file does not exist
*
* @see com.sshtools.j2ssh.sftp.FileAttributes
* @since 0.2.0
*/
public FileAttributes stat(String path) throws
IOException {
String actual = resolveRemotePath(path);

return sftp.getAttributes(actual);
}

/**
*
*
* @param path
*
* @return
*
* @throws IOException
*/
public String getAbsolutePath(String path) throws
IOException {
String actual = resolveRemotePath(path);

return sftp.getAbsolutePath(path);
}

/**
* <p>
* Close the SFTP client.
* </p>
*
* @throws IOException
*
* @since 0.2.0
*/
public void quit() throws IOException {
sftp.close();
}

/**
*
*
* @param localdir
* @param remotedir
* @param recurse
* @param sync
* @param commit
* @param progress
*
* @return
*
* @throws IOException
*/
public DirectoryOperation copyLocalDirectory
(String localdir,
String remotedir, boolean recurse, boolean
sync, boolean commit,
FileTransferProgress progress) throws
IOException {
DirectoryOperation op = new DirectoryOperation
();

// Record the previous
String pwd = pwd();
String lpwd = lpwd();
File local = resolveLocalPath(localdir);
remotedir = resolveRemotePath(remotedir);
remotedir += (remotedir.endsWith
("/") ? "" : "/");
remotedir += local.getName();
remotedir += (remotedir.endsWith
("/") ? "" : "/");

// Setup the remote directory if were
committing
if (commit) {
try {
FileAttributes attrs = stat(remotedir);
} catch (IOException ex) {
mkdir(remotedir);
}
}

// List the local files and verify against the
remote server
File[] ls = local.listFiles();

if (ls != null) {
for (int i = 0; i < ls.length; i++) {
if (ls[i].isDirectory() && !ls
[i].getName().equals(".") &&
!ls[i].getName().equals(".."))
{
if (recurse) {
File f = new File(local, ls
[i].getName());
op.addDirectoryOperation
(copyLocalDirectory(
f.getAbsolutePath(),
remotedir, recurse, sync,
commit, progress), f);
}
} else if (ls[i].isFile()) {
try {
FileAttributes attrs = stat
(remotedir +
ls[i].getName());

if ((ls[i].length() ==
attrs.getSize().longValue()) &&
((ls[i].lastModified
() / 1000) == attrs.getModifiedTime()

.longValue())) {
op.addUnchangedFile(ls[i]);
} else {
op.addUpdatedFile(ls[i]);
}
} catch (IOException ex1) {
op.addNewFile(ls[i]);
}

if (commit) {
put(ls[i].getAbsolutePath(),
remotedir + ls[i].getName
(), progress);

FileAttributes attrs = stat
(remotedir +
ls[i].getName());
attrs.setTimes(new
UnsignedInteger32(
ls[i].lastModified() /
1000),
new UnsignedInteger32(ls
[i].lastModified() / 1000));
sftp.setAttributes(remotedir +
ls[i].getName(), attrs);
}
}
}
}

if (sync) {
// List the contents of the new local
directory and remove any
// files/directories that were not updated
try {
List files = ls(remotedir);
SftpFile file;
File f;

for (Iterator it = files.iterator();
it.hasNext();) {
file = (SftpFile) it.next();

// Create a local file object to
test for its existence
f = new File(local,
file.getFilename());

if (!op.containsFile(file) &&
!file.getFilename().equals
(".") &&
!file.getFilename().equals
("..")) {
op.addDeletedFile(file);

if (commit) {
if (file.isDirectory()) {
// Recurse through the
directory, deleting stuff
recurseMarkForDeletion
(file, op);

if (commit) {
rm
(file.getAbsolutePath(), true, true);
}
} else if (file.isFile()) {
rm(file.getAbsolutePath
());
}
}
}
}
} catch (IOException ex2) {
// Ignorew since if it does not exist
we cant delete it
}
}

// Return the operation details
return op;
}

/**
*
*
* @param eventListener
*/
public void addEventListener(ChannelEventListener
eventListener) {
sftp.addEventListener(eventListener);
}

private void recurseMarkForDeletion(SftpFile file,
DirectoryOperation op)
throws IOException {
List list = ls(file.getAbsolutePath());
op.addDeletedFile(file);

for (Iterator it = list.iterator(); it.hasNext
();) {
file = (SftpFile) it.next();

if (file.isDirectory() && !file.getFilename
().equals(".") &&
!file.getFilename().equals("..")) {
recurseMarkForDeletion(file, op);
} else if (file.isFile()) {
op.addDeletedFile(file);
}
}
}

private void recurseMarkForDeletion(File file,
DirectoryOperation op)
throws IOException {
File[] list = file.listFiles();
op.addDeletedFile(file);

if (list != null) {
for (int i = 0; i < list.length; i++) {
file = list[i];

if (file.isDirectory() && !file.getName
().equals(".") &&
!file.getName().equals("..")) {
recurseMarkForDeletion(file, op);
} else if (file.isFile()) {
op.addDeletedFile(file);
}
}
}
}

/**
*
*
* @param remotedir
* @param localdir
* @param recurse
* @param sync
* @param commit
* @param progress
*
* @return
*
* @throws IOException
*/
public DirectoryOperation copyRemoteDirectory
(String remotedir,
String localdir, boolean recurse, boolean
sync, boolean commit,
FileTransferProgress progress) throws
IOException {
// Create an operation object to hold the
information
DirectoryOperation op = new DirectoryOperation
();

// Record the previous working directoies
String pwd = pwd();
String lpwd = lpwd();
cd(remotedir);

// Setup the local cwd
String base = remotedir;
int idx = base.lastIndexOf('/');

if (idx != -1) {
base = base.substring(idx + 1);
}

File local = new File(localdir, base);

// File local =
new File(localdir, remotedir);
if (!local.isAbsolute()) {
local = new File(lpwd(), localdir);
}

if (!local.exists() && commit) {
local.mkdir();
}

List files = ls();
SftpFile file;
File f;

for (Iterator it = files.iterator(); it.hasNext
();) {
file = (SftpFile) it.next();

if (file.isDirectory() && !file.getFilename
().equals(".") &&
!file.getFilename().equals("..")) {
if (recurse) {
f = new File(local,
file.getFilename());
op.addDirectoryOperation
(copyRemoteDirectory(
file.getFilename(),
local.getAbsolutePath(),
recurse, sync, commit,
progress), f);
}
} else if (file.isFile()) {
f = new File(local, file.getFilename
());

if (f.exists() &&
(f.length() ==
file.getAttributes().getSize().longValue()) &&
((f.lastModified() / 1000) ==
file.getAttributes()

.getModifiedTime()

.longValue())) {
if (commit) {
op.addUnchangedFile(f);
} else {
op.addUnchangedFile(file);
}

continue;
}

if (f.exists()) {
if (commit) {
op.addUpdatedFile(f);
} else {
op.addUpdatedFile(file);
}
} else {
if (commit) {
op.addNewFile(f);
} else {
op.addNewFile(file);
}
}

if (commit) {
FileAttributes attrs = get
(file.getFilename(),
f.getAbsolutePath(),
progress);
f.setLastModified
(attrs.getModifiedTime().longValue() * 1000);
}
}
}

if (sync) {
// List the contents of the new local
directory and remove any
// files/directories that were not updated
File[] contents = local.listFiles();

if (contents != null) {
for (int i = 0; i < contents.length;
i++) {
if (!op.containsFile(contents[i]))
{
op.addDeletedFile(contents[i]);

if (contents[i].isDirectory()
&&
!contents[i].getName
().equals(".") &&
!contents[i].getName
().equals("..")) {
recurseMarkForDeletion
(contents[i], op);

if (commit) {

IOUtil.recurseDeleteDirectory(contents[i]);
}
} else if (commit) {
contents[i].delete();
}
}
}
}
}

cd(pwd);

return op;
}
}

Discussion