src.com.ibm.as400.access.FTP Maven / Gradle / Ivy
Show all versions of jt400-jdk8 Show documentation
///////////////////////////////////////////////////////////////////////////////
//
// JTOpen (IBM Toolbox for Java - OSS version)
//
// Filename: FTP.java
//
// The source code contained herein is licensed under the IBM Public License
// Version 1.0, which has been approved by the Open Source Initiative.
// Copyright (C) 1997-2006 International Business Machines Corporation and
// others. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////
package com.ibm.as400.access;
import java.io.*;
import java.net.*;
import java.util.*;
import java.beans.*;
/**
* Represents a generic FTP client. Methods
* on this class allow you to connect to an FTP server,
* send commands to the system, list files on the system,
* get files from the system, and put files to the system.
*
* Most methods that communicate with the system return a boolean
* to indicate if the request was successful. The message returned
* from the system is also available. getLastMessage() is used
* to retrieve the message from the previous request.
*
* By default, FTP commands are sent via port 21. The initial
* data transfer type is ASCII. Passive mode is used.
*
* No encryption is provided by this class. The user and password
* flow un-encrypted to the system. This class is not SSL enabled.
*
* The forward slash is the separator character for paths sent
* to the FTP server.
*
* Trace information is available by using the com.ibm.as400.access.Trace
* class. When trace is turned on via that class, FTP will
* produce debug information.
*
* The following example copies a set of files from a directory
* on the system.
*
*
* FTP client = new FTP("mysystem", "myUID", "myPWD");
* client.cd("/myDir");
* client.setDataTransferType(FTP.BINARY);
* String [] entries = client.ls();
*
* for (int i = 0; i < entries.length; i++)
* {
* System.out.println("Copying " + entries[i]);
* try
* {
* client.get(entries[i], "c:\\ftptest\\" + entries[i]);
* }
* catch (Exception e)
* {
* System.out.println(" copy failed, likely this is a directory");
* }
* }
*
* client.disconnect();
*
*
*
**/
public class FTP implements java.io.Serializable
{
static final long serialVersionUID = 4L;
private static final boolean DEBUG = false;
// **********************************************************
// * *
// * Don't forget to update readObject() if transient *
// * variables are added! *
// * *
// **********************************************************
// **********************************************************
// * *
// * Don't forget to update AS400FTP if a method is added *
// * *
// **********************************************************
// connect to this system, with the specified user and password
// Do not just grab "clearPassword"!!!! Always use getPassword()
private String server_;
private String user_;
transient private String clearPassword_ = null;
transient private byte[] encryptedPassword_ = null;
transient private byte[] mask_ = null;
transient private byte[] adder_ = null;
transient private boolean encrypted_ = false;
private final static int PARKED = 0; //waiting to try first connection
private final static int ACTIVE = 1; //connection active
private final static int FAILED = 2; //connection no longer active,
// reconnect must be off
transient private int connectionState_ = PARKED;
// flag used to make sure we don't go into an
// infininte loop calling connect
transient private boolean inConnect_ = false;
transient String lastMessage_ = "";
// default port
private int port_ = 21;
// socket for sending commands / receiving replies
transient private Socket controlSocket_;
// replies arrive via this reader
transient private BufferedReader reader_;
// requests are sent via this writer.
transient private PrintWriter ps_;
transient private boolean externallyConnected_ = false; // @D2a
boolean reuseSocket_ = true; // default behavior is to reuse socket
// Lists of listeners
transient PropertyChangeSupport changes_ = null;
transient VetoableChangeSupport vetos_ = null;
transient Vector listeners_ = null;
transient private Object listenerLock_ = new Object();
// amount of data to transfer at one time
private int bufferSize_ = 4096;
private int mode_ = PASSIVE_MODE;
/**
* Transfer files in ASCII mode.
**/
public static final int ASCII = 0;
/**
* Transfer files in binary mode.
**/
public static final int BINARY = 1;
/**
* Use active mode transfers with the system.
**/
public static final int ACTIVE_MODE = 10;
/**
* Use passive mode transfers with the system.
* This is the default.
**/
public static final int PASSIVE_MODE = 11;
private transient FTPThread activeModeObject_;
private transient Thread activeModeThread_;
// -----------------------------------------------------------------------
/**
* Constructs an FTP object.
* The system name, user and password must be set before
* requests are sent to the system.
**/
public FTP()
{
checkSocketProperty(); // see if "reuseSocket" property is set
}
// -----------------------------------------------------------------------
/**
* Constructs an FTP object.
* The user and password must be set before requests are
* sent to the system.
* @param server The system to which to connect.
**/
public FTP(String server)
{
try
{
setServer(server);
checkSocketProperty(); // see if "reuseSocket" property is set
}
catch (PropertyVetoException e) { Trace.log(Trace.ERROR, e); }
}
// -----------------------------------------------------------------------
/**
* Constructs an FTP object.
* @param server The system to which to connect.
* @param user The userid to use during the login.
* @param password The password to use during the login.
**/
public FTP(String server, String user, String password)
{
try
{
setServer(server);
setUser(user);
setPassword(password);
checkSocketProperty(); // see if "reuseSocket" property is set
}
catch (PropertyVetoException e) { Trace.log(Trace.ERROR, e); }
}
// -----------------------------------------------------------------------
/**
* Adds a listener to be notified when the value of any bound property
* is changed. It can be removed with removePropertyChangeListener.
*
* @param listener The PropertyChangeListener.
**/
public void addPropertyChangeListener(PropertyChangeListener listener)
{
if (listener == null)
{
throw new NullPointerException("listener");
}
if (changes_ == null)
{
synchronized(listenerLock_)
{
if (changes_ == null) {
changes_ = new PropertyChangeSupport(this);
}
}
}
changes_.addPropertyChangeListener(listener);
}
// -----------------------------------------------------------------------
/**
* Adds a listener to be notified when an FTP event is fired.
*
* @param listener The object listener.
**/
public void addFTPListener(FTPListener listener)
{
if (listener == null)
{
throw new NullPointerException("listener");
}
if (listeners_ == null)
{
synchronized(listenerLock_)
{
if (listeners_ == null) {
listeners_ = new Vector();
}
}
}
listeners_.addElement(listener);
}
// ---------------------------------------------------------------------------
// @D5 new method
/**
* Starts the process of appending data to a file on the system. FTP
* opens the data connection to the system, then opens the file on
* the system and returns an output stream to the caller. The caller
* then writes the file's data to the output stream.
*
Throws SecurityException if userid or password is invalid.
* @param fileName The file to put.
* @return An output stream to the file. The caller uses the output
* stream to write data to the file.
* Null is returned if the connection to the system fails.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized OutputStream append(String fileName)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering append(file)");
if (fileName == null)
throw new NullPointerException("file");
if (fileName.length() == 0)
throw new IllegalArgumentException("file");
return doAppendOrPut(fileName, "APPE");
}
// ---------------------------------------------------------------------------
// @D5 new method
/**
* Appends data to a file on the system.
* @param sourceFileName The file to put.
* @param targetFileName The file on the system.
* @return true if the copy was successful; false otherwise.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized boolean append(String sourceFileName, String targetFileName)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering append(String, String)");
if (sourceFileName == null)
throw new NullPointerException("source");
if (sourceFileName.length() == 0)
throw new IllegalArgumentException("source");
if (targetFileName == null)
throw new NullPointerException("target");
if (targetFileName.length() == 0)
throw new IllegalArgumentException("target");
boolean result = append(new java.io.File(sourceFileName), targetFileName);
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving append(String, String)");
return result;
}
// ---------------------------------------------------------------------------
/**
* Appends data to a file on the system.
*
Throws SecurityException if userid or password is invalid.
* @param sourceFileName The file to put.
* @param targetFileName The file on the system.
* @return true if the copy was successful; false otherwise.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized boolean append(java.io.File sourceFileName, String targetFileName)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering append(File, String)");
if (sourceFileName == null)
throw new NullPointerException("source");
if (targetFileName == null)
throw new NullPointerException("target");
if (targetFileName.length() == 0)
throw new IllegalArgumentException("target");
connect();
byte[] buffer = new byte[bufferSize_];
FileInputStream f = null;
OutputStream out = null;
try
{
f = new FileInputStream(sourceFileName);
out = append(targetFileName);
int length = f.read(buffer);
while (length > 0)
{
out.write(buffer,0,length);
length = f.read(buffer);
}
}
finally
{
try
{
if (out != null) out.close();
}
finally
{
if (f != null) f.close();
}
}
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving append(String, String)");
return true;
}
// -----------------------------------------------------------------------
/**
* Adds a listener to be notified when the value of any
* constrained property is changed.
*
* @param listener The VetoableChangeListener.
**/
public void addVetoableChangeListener(VetoableChangeListener listener)
{
if (listener == null)
{
throw new NullPointerException("listener");
}
if (vetos_ == null)
{
synchronized(listenerLock_)
{
if (vetos_ == null){
vetos_ = new VetoableChangeSupport(this);
}
}
}
vetos_.addVetoableChangeListener(listener);
}
// ---------------------------------------------------------------------------
/**
* Sets the current directory on the system to directory.
* The method is the same as setCurrentDirectory().
* The message returned from the system is saved. Use getLastMessage()
* to retrieve it.
*
Throws SecurityException if userid or password is invalid.
* @param directory The current directory to set on the system.
* @return true if directory changed; false otherwise.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized boolean cd(String directory)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering cd()");
if (directory == null)
throw new NullPointerException("directory");
if (directory.length() == 0)
throw new IllegalArgumentException("directory");
connect();
issueCommand("CWD " + directory);
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving cd()");
return lastMessage_.startsWith("250");
}
// ---------------------------------------------------------------------------
/**
* Connects to the system. The connection is via
* port port. The user and password must be set
* before calling this method.
* Calling connect is optional. Methods that communicate
* with the system such as get, put, cd, and ls call connect()
* if necessary.
* The message returned from the system is saved. Use getLastMessage()
* to retrieve it.
*
Throws SecurityException if userid or password is invalid.
* @return true if connection is successful; false otherwise.
* @exception UnknownHostException If a path to the system cannot be found.
* @exception IOException If an error occurs while connecting to the system.
* @exception IllegalStateException If called before user and password are set.
**/
public synchronized boolean connect()
throws UnknownHostException, IOException, IllegalStateException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering connect()");
inConnect_ = true;
// possible state 1 -- we were connected but the connection
// failed or timed out. If reconnect is off simply return false
if (connectionState_ == FAILED)
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving connect(), state=failed");
inConnect_ = false;
return false;
}
// possible state 2 -- we are connected.
if (connectionState_ == ACTIVE)
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving connect(), state=active");
inConnect_ = false;
return true;
}
//possible state 3 -- this is the first try at connecting
//or the connection failed and the user wants us to retry.
//Attempt to connect to the system.
if ((server_ == null) || (server_.length() == 0))
{
inConnect_ = false;
throw new IllegalStateException("server");
}
if ((user_ == null) || (user_.length() == 0))
{
inConnect_ = false;
throw new IllegalStateException("user");
}
String password_ = getPassword();
if ((password_ == null) || (password_.length() == 0))
{
inConnect_ = false;
throw new IllegalStateException("password");
}
controlSocket_ = new Socket(server_, port_);
reader_ = new BufferedReader(new InputStreamReader(controlSocket_.getInputStream()));
ps_ = new PrintWriter(controlSocket_.getOutputStream(), true);
readReply();
login(user_, password_);
if (lastMessage_.startsWith("230"))
connectionState_ = ACTIVE;
else
{
ps_.close();
reader_.close();
controlSocket_.close();
connectionState_ = PARKED;
inConnect_ = false;
throw new java.lang.SecurityException();
}
inConnect_ = false;
if (connectionState_ == ACTIVE)
fireEvent(FTPEvent.FTP_CONNECTED);
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving connect(), state = " + connectionState_ );
return (connectionState_ == ACTIVE);
}
// ---------------------------------------------------------------------------
//
// Decode the password so because it must be in the clear going
// to the system.
//
private byte[] decode(byte[] data, byte[] mask, byte[] adder)
{
return decode(data, 0, data.length, mask, adder);
}
private byte[] decode(byte[] data, int offset, int len, byte[] mask, byte[] adder)
{
int i;
byte[] b = new byte[len];
for (i=0; iquit command to the system.
* The message returned from the system is saved. Use getLastMessage()
* to retrieve it.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized void disconnect()
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering disconnect()");
if (connectionState_ == ACTIVE)
{
try
{
issueCommand("QUIT");
}
catch (Exception e) {}
try
{
if (activeModeThread_ != null)
{
activeModeThread_.interrupt();
}
}
catch (Exception e) {}
ps_.close();
reader_.close();
controlSocket_.close();
connectionState_ = PARKED;
fireEvent(FTPEvent.FTP_DISCONNECTED);
}
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving disconnect()");
}
// ---------------------------------------------------------------------------
// @D5 new method. This code used to be the put() method that did work.
/**
* Starts the process of putting a file or appending data to a file on
* the system. FTP opens the data connection to the system, then opens
* the file on the system and returns an output stream to the caller. The
* caller then writes the file's data to the output stream.
*
Throws SecurityException if userid or password is invalid.
* @param fileName The file to put.
* @param command "APPE" to append data, "STOR" to simply put
* @return An output stream to the file. The caller uses the output
* stream to write data to the file.
* Null is returned if the connection to the system fails.
* @exception IOException If an error occurs while communicating with the system.
**/
OutputStream doAppendOrPut(String fileName, String command)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering doAppendOrPut(file)");
connect();
Socket dataSocket = null;
if (mode_ == PASSIVE_MODE)
{
dataSocket = getDataSocket();
}
else
{
initiateActiveMode();
}
String result = issueCommand(command + " " + fileName);
if (result.startsWith("4") || result.startsWith("5"))
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"put failed " + result);
if (dataSocket != null) { // passive_mode
dataSocket.close();
}
throw new IOException(result);
}
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving put(file)");
if (dataSocket == null)
{
dataSocket = activeModeObject_.getSocket();
}
fireEvent(FTPEvent.FTP_PUT);
return new FTPOutputStream(dataSocket, this);
}
// ---------------------------------------------------------------------------
//
// Encode the password so it is not in the clear sitting in memory
//
private byte[] encode(byte[] b, byte[] mask, byte[] adder)
{
int i;
byte[] buf = new byte[b.length];
for (i=0; iThrows SecurityException if userid or password is invalid.
*
* @param fileName The file to get.
* @return An input stream to the file. The caller uses the input
* stream to read the data from the file.
* Null is returned if the connection to the system fails.
* @exception IOException If an error occurs while communicating with the system.
* @exception FileNotFoundException If the name is a directory or the name is not found.
**/
public synchronized InputStream get(String fileName)
throws IOException, FileNotFoundException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering get(file)");
if (fileName == null)
throw new NullPointerException("file");
if (fileName.length() == 0)
throw new IllegalArgumentException("file");
connect();
Socket dataSocket = null;
if (mode_ == PASSIVE_MODE)
{
dataSocket = getDataSocket();
}
else // active_mode
{
initiateActiveMode();
}
issueCommand("RETR " + fileName);
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving get(file)");
// 150 appears to be success.
// Some possible error messages:
// 426-MEMBER IN FILE IN LIBRARY NOT FOUND.
// 501 UNKNOWN EXTENSION IN DATABASE FILE NAME.
// 550 FILE IN LIBRARY NOT FOUND.
if (lastMessage_.startsWith("426") ||
lastMessage_.startsWith("501") || // member not found
lastMessage_.startsWith("550")) // unknown extension
{
if (dataSocket != null) { // passive_mode
dataSocket.close();
}
throw new FileNotFoundException();
}
if (dataSocket == null)
{
dataSocket = activeModeObject_.getSocket();
}
fireEvent(FTPEvent.FTP_RETRIEVED);
return new FTPInputStream(dataSocket, this);
}
// ---------------------------------------------------------------------------
/**
* Gets a file from the system.
* The source file is on the system, accessed via FTP so the path
* separator character (if any) must be a forward slash.
* The target file is on the client, accessed via java.io
* so the path separator character (if any) must be client specific.
* @param sourceFileName The file to get on the system.
* @param targetFileName The file on the target file system.
* @return true if the copy was successful; false otherwise.
* @exception IOException If an error occurs while communicating with the system.
* @exception FileNotFoundException If the source file or the targe file
* cannot be accessed.
**/
public synchronized boolean get(String sourceFileName, String targetFileName)
throws IOException, FileNotFoundException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering get(String, String");
if (sourceFileName == null)
throw new NullPointerException("source");
if (sourceFileName.length() == 0)
throw new IllegalArgumentException("source");
if (targetFileName == null)
throw new NullPointerException("target");
if (targetFileName.length() == 0)
throw new IllegalArgumentException("target");
boolean result = get(sourceFileName, new java.io.File(targetFileName));
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving get(String, String");
return result;
}
// ---------------------------------------------------------------------------
/**
* Gets a file from the system.
* The source file is on the system, accessed via FTP so the path
* separator character (if any) must be a forward slash.
* The target file is an instance of Java.io.file.
*
Throws SecurityException if userid or password is invalid.
* @param sourceFileName The file to get on the system.
* @param targetFile The file on the target file system.
* @return true if the copy was successful; false otherwise.
* @exception IOException If an error occurs while communicating with the system.
* @exception FileNotFoundException If the source file or the targe file
* cannot be accessed.
**/
public synchronized boolean get(String sourceFileName, java.io.File targetFile)
throws IOException, FileNotFoundException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering get(String, file)");
if (sourceFileName == null)
throw new NullPointerException("source");
if (sourceFileName.length() == 0)
throw new IllegalArgumentException("source");
if (targetFile == null)
throw new NullPointerException("target");
boolean result = true;
connect();
byte[] buffer = new byte[bufferSize_];
InputStream in = get(sourceFileName);
if (in != null)
{
FileOutputStream f = null;
try
{
f = new FileOutputStream(targetFile);
int length = in.read(buffer);
while (length > 0)
{
f.write(buffer,0,length);
length = in.read(buffer);
}
}
finally
{
try { in.close(); } catch (Throwable t) { Trace.log(Trace.ERROR, t); }
if (f != null) f.close();
}
// readReply();
}
else
result = false;
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving get(String, file");
return result;
}
// ---------------------------------------------------------------------------
/**
* Returns the size of the buffer used when transferring files.
* When this class copies data between the source file and target file,
* a buffer of this size is used. The default buffer size is 4096 bytes.
* @return The buffer size used when transferring files.
**/
public int getBufferSize()
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering getBufferSize(), (no leaving entry)");
return bufferSize_;
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
/**
* Returns the current directory on the system.
* @return The current directory on the system.
* Null is returned if the connection to the system fails.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized String getCurrentDirectory()
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering pwd(), (no leaving entry)");
return pwd();
}
// ---------------------------------------------------------------------------
/**
* Returns the text of the last reply returned from the system.
* Empty string is returned if no request has been sent.
* @return The text of the last reply returned from the system.
**/
public synchronized String getLastMessage()
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering lastMessage(), (no leaving entry)");
return lastMessage_;
}
/**
* Returns the current transfer mode.
* @return The transfer mode. Valid values are ACTIVE_MODE or PASSIVE_MODE.
* The default is PASSIVE_MODE.
**/
public int getMode()
{
return mode_;
}
// ---------------------------------------------------------------------------
//
// Returns the password as a string. This method should *NEVER*
// be public! If necessary, this method decrypts the password
//
private String getPassword()
{
if (encrypted_)
{
if (encryptedPassword_ == null)
return null;
else
return new String(decode(encryptedPassword_, mask_, adder_));
}
else
return clearPassword_;
}
// ---------------------------------------------------------------------------
/**
* Returns the port used to connect to the system.
* @return The port used to connect to the system.
**/
public int getPort()
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering getPort(), (no leaving entry)");
return port_;
}
// ---------------------------------------------------------------------------
/**
* Returns the name of the system. Null is returned if no system has
* been set.
* @return The name of the system to which this object connects.
**/
public String getServer()
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering getServer(), (no leaving entry)");
return server_;
}
// ---------------------------------------------------------------------------
/**
* Returns the user. Null is returned if no user has
* been set.
* @return The name of the user.
**/
public String getUser()
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering getUser(), (no leaving entry)");
return user_;
}
// ---------------------------------------------------------------------------
/**
* Indicates whether the socket is reused for multiple file transfers, when in active mode.
* @return true if the socket is reused; false if a new socket is created.
* @see #setMode
**/
public boolean isReuseSocket()
{
return reuseSocket_;
}
// ---------------------------------------------------------------------------
/**
* Sends a command to the system, returning the reply from the system.
*
* The command is not altered before sending it to the system, so it
* must be recognized by the system. Many FTP applications change
* commands so they are recognized by the system. For example, the
* command to get a list of files from the system is NLST, not ls. Many
* FTP applications convert ls to NLST before sending the command to
* the system. This method will not do the conversion.
*
Throws SecurityException if userid or password is invalid.
* @param cmd The command to send to the system.
* @return The reply to the command.
* Null is returned if the connection to the system fails.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized String issueCommand(String cmd) throws IOException {
int retryCount = 10;
if (Trace.isTraceOn()) {
String traceString = cmd;
if (cmd.startsWith("PASS")) {
traceString = "PASS "
+ "********************".substring(0, cmd.length() - 5);
}
Trace.log(Trace.DIAGNOSTIC, "entering issueCommand(), command is: "
+ traceString);
}
while (retryCount > 0) {
// make sure we are not in connect to prevent an infinite loop
// (connect calls this method to issue the user and pass commands)
if (!inConnect_)
connect();
ps_.print(cmd + "\r\n");
ps_.flush();
readReply();
// if (echo)
// lastMessage_ = cmd + "\n" + reply;
// else
// lastMessage_ = reply;
// If get 425, not able to open data connection. Try again
if ((lastMessage_ != null) && (lastMessage_.indexOf("425 ") == 0)) {
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC, "retrying issueCommand(), message is: "
+ lastMessage_);
retryCount --;
} else {
retryCount = 0;
}
}
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC, "leaving issueCommand(), message is: "
+ lastMessage_);
return lastMessage_;
}
private void initiateActiveMode() throws IOException
{
if (!activeModeThread_.isAlive())
{
activeModeObject_.setLocalAddress(controlSocket_.getLocalAddress());
activeModeThread_.start();
activeModeObject_.waitUntilStarted();
}
activeModeObject_.issuePortCommand();
}
// ---------------------------------------------------------------------------
/**
* Lists the contents of the current working directory. If details
* is true, additional information is returned. If false, only the
* file name is returned. An array of length zero is returned if the
* directory is empty.
* @param details True if file details will be returned.
* @return The contents of the directory as an array of Strings.
* @exception IOException If an error occurs while communicating with the system.
**/
String[] list(boolean details, String criteria) // @D4c
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering list");
connect();
Socket dataSocket = null;
if (mode_ == PASSIVE_MODE)
{
dataSocket = getDataSocket();
}
else // active_mode
{
initiateActiveMode();
}
String FTPCommand; // @D4a
if (details)
{
FTPCommand = "LIST"; // @D4c
}
else
{
FTPCommand = "NLST"; // @D4c
}
if (criteria != null) // @D4a
FTPCommand = FTPCommand + " " + criteria; // @D4a
issueCommand(FTPCommand); // @D4c
String[] result = new String[0];
if (lastMessage_.startsWith("125") || lastMessage_.startsWith("150"))
{
if (dataSocket == null)
{
dataSocket = activeModeObject_.getSocket();
}
InputStream in = dataSocket.getInputStream();
Vector v = new Vector();
BufferedReader rdr = new BufferedReader(new InputStreamReader(in));
String s;
while ((s = rdr.readLine()) != null)
{
v.addElement(s);
}
result = new String[v.size()];
v.copyInto(result);
rdr.close();
in.close();
dataSocket.close();
readReply();
fireEvent(FTPEvent.FTP_LISTED);
}
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving list()");
return result;
}
// ---------------------------------------------------------------------------
/**
* Logs on to the system
* @param user The user.
* @param password The password.
* @return String containing the reply from the system.
* @exception IOException If an error occurs while communicating with the system.
*/
private String login(String user, String password)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering login");
// connect();
// user_ = user;
// password_ = password;
String s1 = issueCommand("USER " + user);
String s2 = issueCommand("PASS " + password);
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving login");
return s1 + "\n" + s2;
}
// ---------------------------------------------------------------------------
/**
* Lists the contents of the current working directory. If the directory
* is empty, an empty list is returned.
* @return The contents of the directory as an array of Strings.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized String[] ls()
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering ls(), (no leaving entry)");
return list(false, null); // @D4c
}
// ---------------------------------------------------------------------------
// @D4 new method
/**
* Lists the contents of the current working directory. If the directory
* is empty or no files match the search criteria, an empty list is returned.
* @return The contents of the directory as an array of Strings.
* @param criteria The search criteria.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized String[] ls(String criteria)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering ls(), (no leaving entry)");
return list(false, criteria);
}
// ---------------------------------------------------------------------------
/**
* Sends the NOOP (no operation) command to the system. This
* request is most useful to see if the connection to the system
* is still active.
*
Throws SecurityException if userid or password is invalid.
* @return true if the request was successful, false otherwise.
* @exception IOException If an error occurs while communicating with the system.
**/
// $$$ change this to package scoped after testing
public synchronized boolean noop()
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering noop");
if (! inConnect_)
connect();
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving noop()");
return issueCommand("NOOP").startsWith("200");
}
// ---------------------------------------------------------------------------
/**
* Starts the process of putting a file to the system. FTP
* opens the data connection to the system, then opens the file on
* the system and returns an output stream to the caller. The caller
* then writes the file's data to the output stream.
*
Throws SecurityException if userid or password is invalid.
* @param fileName The file to put.
* @return An output stream to the file. The caller uses the output
* stream to write data to the file.
* Null is returned if the connection to the system fails.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized OutputStream put(String fileName)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering put(file)");
if (fileName == null)
throw new NullPointerException("file");
if (fileName.length() == 0)
throw new IllegalArgumentException("file");
return doAppendOrPut(fileName, "STOR");
// @D5d the rest of this method is now in worker method doAppendOrPut()
}
// ---------------------------------------------------------------------------
/**
* Puts a file to the system.
* @param sourceFileName The file to put.
* @param targetFileName The file on the system.
* @return true if the copy was successful; false otherwise.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized boolean put(String sourceFileName, String targetFileName)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering put(String, String)");
if (sourceFileName == null)
throw new NullPointerException("source");
if (sourceFileName.length() == 0)
throw new IllegalArgumentException("source");
if (targetFileName == null)
throw new NullPointerException("target");
if (targetFileName.length() == 0)
throw new IllegalArgumentException("target");
boolean result = put(new java.io.File(sourceFileName), targetFileName);
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving put(String, String)");
return result;
}
// ---------------------------------------------------------------------------
/**
* Puts a file to the system.
*
Throws SecurityException if userid or password is invalid.
* @param sourceFileName The file to put.
* @param targetFileName The file on the system.
* @return true if the copy was successful; false otherwise.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized boolean put(java.io.File sourceFileName, String targetFileName)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering put(File, String)");
if (sourceFileName == null)
throw new NullPointerException("source");
if (targetFileName == null)
throw new NullPointerException("target");
if (targetFileName.length() == 0)
throw new IllegalArgumentException("target");
connect();
byte[] buffer = new byte[bufferSize_];
FileInputStream f = null;
OutputStream out = null;
try
{
f = new FileInputStream(sourceFileName);
out = put(targetFileName);
int length = f.read(buffer);
while (length > 0)
{
out.write(buffer,0,length);
length = f.read(buffer);
}
}
finally
{
try
{
if (out != null) out.close();
}
finally
{
if (f != null) f.close();
}
}
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving put(String, String)");
return true;
}
// ---------------------------------------------------------------------------
/**
* Returns the current directory on the system. PWD is the ftp
* command Print Working Directory.
*
Throws SecurityException if userid or password is invalid.
* @return The current directory on the system.
* Null is returned if the connection to the system fails.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized String pwd()
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering pwd(), (no leaving entry)");
connect();
return issueCommand("PWD");
}
// ---------------------------------------------------------------------------
/**
* Removes this listener from being notified when a bound property changes.
*
* @param listener The PropertyChangeListener.
**/
public void removePropertyChangeListener(PropertyChangeListener listener)
{
if (listener == null)
{
throw new NullPointerException("listener");
}
if (changes_ != null) {
changes_.removePropertyChangeListener(listener);
}
}
// ---------------------------------------------------------------------------
/**
* Removes a listener from the list.
*
* @param listener The FTP listener.
**/
public void removeFTPListener(FTPListener listener)
{
if (listener == null)
{
throw new NullPointerException("listener");
}
if (listeners_ != null) {
listeners_.removeElement(listener);
}
}
// ---------------------------------------------------------------------------
/**
* Removes this listener from being notified when a constrained property changes.
*
* @param listener The VetoableChangeListener.
**/
public void removeVetoableChangeListener(VetoableChangeListener listener)
{
if (listener == null)
{
throw new NullPointerException("listener");
}
if (vetos_ != null) {
vetos_.removeVetoableChangeListener(listener);
}
}
// ---------------------------------------------------------------------------
/**
* During object deserialization, this method is called. When it is called
* we need to initialize transient data.
**/
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
connectionState_ = PARKED;
inConnect_ = false;
lastMessage_ = "";
changes_ = null;
vetos_ = null;
listeners_ = null;
listenerLock_ = new Object();
externallyConnected_ = false; // @D2a
}
// ---------------------------------------------------------------------------
/**
* Receives a reply from the system. This method is called only
* after calling the get() or put() methods where the calling program
* copies the data.
* @return The reply from the system.
* @exception IOException If an error occurs while communicating with the system.
**/
//
// Protocol is a reply is a three digit number followed by a dash or
// space, followed by text. A request can result in multiple replies.
// The three digit number of the last reply must match number of the
// first replay -- AND -- the character following the number must
// be a space. For example, a request could result in the following:
// 123-First line
// Second line
// 123-Third line
// 456-Fourth line
// 123 Last line
//
String readReply()
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering readReply()");
String currentLine = reader_.readLine();
if ((currentLine == null) || (currentLine.length() == 0))
throw new IOException();
String code = currentLine.substring(0, 3);
StringBuffer buf = new StringBuffer(currentLine);
boolean Continue = true;
while (Continue)
{
if ((currentLine.length() > 3) &&
(currentLine.substring(0, 3).equals(code)) &&
(currentLine.charAt(3) == ' '))
{ Continue = false; }
else
{
currentLine = reader_.readLine();
buf.append("\n" + currentLine);
}
}
lastMessage_ = buf.toString();
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving readReply()");
return lastMessage_;
}
// ---------------------------------------------------------------------------
/**
* Sets the buffer size used when transferring files. The default
* buffer size is 4096 bytes.
* @param bufferSize The size of the buffer used when transferring files.
* @exception PropertyVetoException If the change is vetoed.
**/
public synchronized void setBufferSize(int bufferSize)
throws PropertyVetoException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering setBufferSize()");
if (bufferSize < 1)
throw new IllegalArgumentException("bufferSize");
// Remember the old value.
int oldValue = bufferSize_;
// Fire a vetoable change event.
if (vetos_ != null) {
vetos_.fireVetoableChange("bufferSize", new Integer(oldValue), new Integer(bufferSize));
}
bufferSize_ = bufferSize;
// Fire the property change event.
if (changes_ != null) {
changes_.firePropertyChange("bufferSize", new Integer(oldValue), new Integer(bufferSize));
}
}
// ---------------------------------------------------------------------------
/**
* Sets the current directory on the system to directory.
* The method is the same as cd().
* The message returned from the system is saved. Use getLastMessage()
* to retrieve it.
* @param directory The current directory to set on the system.
* @return true if directory changed; false otherwise.
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized boolean setCurrentDirectory(String directory)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering setCurrentDirectory(), (no leaving entry)");
return cd(directory);
}
// ---------------------------------------------------------------------------
/**
* Sets the data transfer type. Valid values are:
*
* - {@link #ASCII ASCII}
*
- {@link #BINARY BINARY}
*
*
* If a connection does not
* already exist, a connection is made to the system.
* The message returned from the system is saved. Use getLastMessage()
* to retrieve it.
*
Throws SecurityException if userid or password is invalid.
* @param transferType ASCII or BINARY
* @exception IOException If an error occurs while communicating with the system.
**/
public synchronized void setDataTransferType(int transferType)
throws IOException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering setDataTransferType()");
if ((transferType == ASCII) ||
(transferType == BINARY))
{
connect();
if (transferType == ASCII)
issueCommand("TYPE A");
else
issueCommand("TYPE I");
}
else
throw new IllegalArgumentException("transferType");
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"leaving setDataTransferType()");
}
/**
* Sets the transfer mode.
* @param mode The mode. Valid values are {@link #ACTIVE_MODE ACTIVE_MODE} or {@link #PASSIVE_MODE PASSIVE_MODE}.
**/
public void setMode(int mode)
{
if (mode != ACTIVE_MODE && mode != PASSIVE_MODE)
{
throw new IllegalArgumentException("mode");
}
if (mode == ACTIVE_MODE && activeModeThread_ == null)
{
activeModeObject_ = new FTPThread(this);
activeModeThread_ = new Thread(activeModeObject_);
activeModeThread_.setDaemon(true);
// Don't start the thread yet, because we need to set our local InetAddress into it first,
// and we don't know what that is until we have connect()ed.
}
mode_ = mode;
}
// ---------------------------------------------------------------------------
/**
* Sets the password. The password cannot be changed once
* a connection is made to the system.
* @param password The password for the user.
**/
public synchronized void setPassword(String password)
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering setPassword(), (no leaving entry)");
if (password == null)
throw new NullPointerException("password");
if (password.length() == 0)
throw new IllegalArgumentException("password");
try
{
Date d = new Date();
Random r = new Random(d.getTime());
mask_ = new byte[7];
r.nextBytes(mask_);
adder_ = new byte[9];
r.nextBytes(adder_);
encryptedPassword_ = encode(password.getBytes(), mask_, adder_);
encrypted_ = true;
}
catch (Exception e)
{
encrypted_ = false;
clearPassword_ = password;
}
}
// ---------------------------------------------------------------------------
/**
* Sets the port to use when connecting to the system.
* The port cannot be changed once
* a connection is made to the system.
* @param port The port to use when connecting to the system.
* @exception PropertyVetoException If the change is vetoed.
**/
public synchronized void setPort(int port)
throws PropertyVetoException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering setPort()");
if (port < 1)
throw new IllegalArgumentException("port");
if (connectionState_ != PARKED)
throw new IllegalStateException("connected");
// Remember the old value.
int oldValue = port_;
// Fire a vetoable change event.
if (vetos_ != null) {
vetos_.fireVetoableChange("port", new Integer(oldValue), new Integer(port));
}
port_ = port;
// Fire the property change event.
if (changes_ != null) {
changes_.firePropertyChange("port", new Integer(oldValue), new Integer(port));
}
}
/**
* Indicates whether to reuse a socket for multiple file transfers, when in active mode.
* By default, the "reuse socket" attribute is set to the value of the com.ibm.as400.access.FTP.reuseSocket property.
* If the property is not set, the default is true (sockets are reused).
* The "reuse socket" attribute (of an FTP object) cannot be reset after that object has connected to the system.
* @param reuse If true, the socket is reused. If false, a new socket is created for each subsequent file transfer.
* @see #setMode
**/
public void setReuseSocket(boolean reuse)
{
if (connectionState_ != PARKED)
throw new IllegalStateException("connected");
reuseSocket_ = reuse;
}
// ---------------------------------------------------------------------------
/**
* Sets the name of the system. The system name cannot be changed once
* a connection is made to the system.
* @param server The name of the system to which this object connects.
* @exception PropertyVetoException If the change is vetoed.
**/
public synchronized void setServer(String server)
throws PropertyVetoException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering setServer(), (no leaving entry)");
if (server == null)
throw new NullPointerException("server");
if (server.length() == 0)
throw new IllegalArgumentException("server");
if (connectionState_ != PARKED)
throw new IllegalStateException("connected");
// Remember the old system.
String oldServer = server_;
// Fire a vetoable change event for system.
if (vetos_ != null) {
vetos_.fireVetoableChange("server", oldServer, server);
}
server_ = server;
// Fire the property change event.
if (changes_ != null) {
changes_.firePropertyChange("server", oldServer, server);
}
}
// ---------------------------------------------------------------------------
/**
* Sets the user identifier used when connecting to the system.
* If the client is connected to the system, this method
* will disconnect the connection
* @param user The user identifier used when connecting to the system.
* @exception PropertyVetoException If the change is vetoed.
* @exception IllegalStateException If connection already established to the system.
**/
public synchronized void setUser(String user)
throws PropertyVetoException
{
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC,"entering getUser(), (no leaving entry)");
if (user == null)
throw new NullPointerException("user");
if (user.length() == 0)
throw new IllegalArgumentException("user");
if (connectionState_ != PARKED)
throw new IllegalStateException("connected");
// Remember the old value.
String oldUser = user_;
// Fire a vetoable change event.
if (vetos_ != null) {
vetos_.fireVetoableChange("user", oldUser, user);
}
try
{
disconnect();
}
catch (Exception e) {}
user_ = user;
// Fire the property change event.
if (changes_ != null) {
changes_.firePropertyChange("user", oldUser, user);
}
}
/**
* Renames one or more files on the system, according to a specified pattern.
*
* For example:
*
* - ren("*.txt","*.DONE") renames "file1.txt" to "file1.DONE", and so on
*
- ren("*.txt","*_DONE.*) renames "file1.txt" to "file1_DONE.txt", and so on
*
- ren("*.txt","*_1055am") renames "file1.txt" to "file1.txt_1055am", and so on
*
*
* @param fromName A pattern specifying the file(s) to be renamed.
* The syntax of the pattern is similar to the syntax for {@link #ls ls()}.
* @param toName The new file name, or a simple pattern
* describing how to construct the new name out of fromName.
* toName can contain up to two asterisks, one on each side of the ".".
* @return The number of files renamed.
* @throws IOException If an error occurs while communicating with the system.
**/
public synchronized int ren(String fromName, String toName)
throws IOException
{
int renamedCount = 0;
if (Trace.isTraceOn())
Trace.log(
Trace.DIAGNOSTIC,
"entering ren(), from file name is "
+ fromName
+ ", to file name is "
+ toName
+ ".");
if (fromName == null)
throw new NullPointerException("fromName");
if (toName == null)
throw new NullPointerException("toName");
if (fromName.trim().length() == 0)
throw new IllegalArgumentException("fromName");
if (toName.trim().length() == 0)
throw new IllegalArgumentException("toName");
String[] entries = ls(fromName);
for (int i=0; igenerateNewName("file.txt","*_DONE.*) returns "file_DONE.txt"
* generateNewName("file.txt","*_1055am") returns "file.txt_1055am"
*
*
* @param fromName The original file name
* @param toName The new file name, or a simple pattern
* describing how to construct the new name out of fromName.
* toName can contain up to two asterisks, one on each side of the ".".
* @return The new file name.
* @throws NullPointerException if the fromName or toName
* parameters are null.
* @throws IllegalArgumentException if fromName or toName
* is null, or if the toName contains an invalid expression.
**/
public static String generateNewName(String fromName, String toName)
{
if (fromName == null)
throw new NullPointerException("fromName");
if (toName == null)
throw new NullPointerException("toName");
fromName = fromName.trim();
if (fromName.length() < 1)
throw new IllegalArgumentException("fromName");
toName = toName.trim();
if (toName.length() < 1)
throw new IllegalArgumentException("toName");
if (toName.indexOf('*') < 0)
return toName;
if (toName.indexOf('.') < 0)
return newNamePart(fromName, toName);
String[] fromParts = splitName(fromName, '.');
String[] toParts = splitName(toName, '.');
String theFront = newNamePart(fromParts[0], toParts[0]);
String theBack = newNamePart(fromParts[1], toParts[1]);
String theNewName = theFront;
if (theBack.length() > 0)
theNewName = theNewName + "." + theBack;
if (DEBUG) System.out.println("generateNewName(): " + fromName +", " + toName + ", -> " + theNewName);
return theNewName;
}
/**
* Returns a new name constructed out of an old name
* using a very simple pattern containing at most one
* asterisk. The asterisk is replaced with the value
* of the old name.
*
* For example:
*
* - newNamePart("abc","xyz") returns "xyz"
*
- newNamePart("abc","*_DONE") returns "abc_DONE"
* - newNamePart("abc","DONE_*") returns "DONE_abc"
* - newNamePart("abc","DONE_*_DONE") returns "DONE_abc_DONE"
*
* @param fromName the old name.
* @param toName the new name, containing no more than 1 asterisk.
* @return the constructed name.
**/
static String newNamePart(String fromName, String toName)
{
int indexA = toName.indexOf('*');
// Return immediately if there is no asterisk in the to name.
if (indexA < 0)
return toName;
// Cannot be more than 1 asterisk in the to name.
if ((indexA < (toName.length() - 1))
&& (toName.indexOf('*', indexA + 1) > 0))
throw new IllegalArgumentException("toName");
StringBuffer nameBuff = new StringBuffer();
// LHS of asterisk
if (indexA > 0)
nameBuff.append(toName.substring(0, indexA));
// Asterisk is replaced with original name
nameBuff.append(fromName);
// RHS of asterisk
if (indexA < (toName.length() - 1))
nameBuff.append(toName.substring(indexA + 1));
return nameBuff.toString();
}
/**
* Utility method used to split a string into exactly
* two parts at the last occurrence of a
* given character. If the character is not found then
* the original string is returned as the first part.
*
* @param stringValue the striung value to split.
* @param c the character at which to split the string.
* @return String array with exactly 2 elements.
*/
static String[] splitName(String stringValue, char c) {
String[] pieces = new String[2];
int splitIndex = stringValue.lastIndexOf(c);
if (splitIndex >= 0) {
pieces[0] = stringValue.substring(0, splitIndex);
if (splitIndex < (stringValue.length() - 1)) {
pieces[1] = stringValue.substring(splitIndex + 1);
} else {
pieces[1] = "";
}
} else {
pieces[0] = stringValue;
pieces[1] = "";
}
return pieces;
}
/**
* Utility method used to establish "passive" mode.
*
* @return The socket.
*/
final Socket getDataSocket()
throws IOException
{
// Try the extended passive command.
String response = issueCommand("EPSV");
if (response.startsWith("229"))
{
// System supports EPSV.
// Note: The response will be something like this (without the double-quotes):
// "229 Entering Extended Passive Mode (|||52900|)."
// Note: For some languages we will see a delimiter other than "|".
// Locate the left-parenthesis.
int begin = response.indexOf('(');
if (begin == -1) {
Trace.log(Trace.ERROR, "FTP server response does not contain a left parenthesis.");
throw new InternalErrorException(InternalErrorException.SYNTAX_ERROR, response,null);
}
// Locate the first digit of the port number.
int ii;
final int responseLength = response.length();
for (ii=begin+1; ii