/**
* File: FTPConnection.java
* Author: Bret Taylor <btaylor@cs.stanford.edu>
* URL: http://www.stanford.edu/~bstaylor/cs/ftpconnection/FTPConnection.java.shtml
*
* see also <a href="http://www.nsftools.com/tips/JavaFtp.htm">http://www.nsftools.com/tips/JavaFtp.htm</a>
* ---------------------------------------------
* $Id: FTPConnection.java,v 1.4 2009/02/11 04:03:24 brad Exp $
* ---------------------------------------------
* Parts of this code were adopted from a variety of other FTP classes the
* author has encountered that he was not completely satisfied with. If you
* think more thanks are due to any particular author than is given, please
* let him know. With that caveat, this class can be freely distributed and
* modified as long as Bret Taylor is given credit in the source code comments.
*
*
* Modified by Julian Robichaux -- http://www.nsftools.com
* Added constructors, logout(), listFiles(), listSubdirectories(), getAndParseDirList(),
* processFileListCommand(), and overloaded getFullServerReply().
* Also added StringBuffer parameter options to transferData() and executeDataCommand()
* and did a few other little things.
*
*
* Modified by Brad Broerman -- http://www.bbroerman.net
* Moved call to set Binary Mode to be before call to PORT. I was getting odd error codes
* from my FTP server the other way around. Additionally, I changed the methods to print commands
* and use a "\n" line terminator, instead of the default one added by println. On windows clients,
* it was sending CR+LF, which was causing an error after setting the PORT. One final addition was to
* change the methods that read or write to local files to use PrivilegedExceptionAction method to invoke
* the actual send/receive to allow it to be used in an applet without throwing security exceptions.
*
*/
import java.io.*;
import java.net.*;
import java.util.*;
import java.security.*;
/**
* <p>A wrapper for the network and command protocols needed for the most common
* FTP commands. Standard usage looks something like this:</p>
* <pre> FTPConnection connection = new FTPConnection();
* try {
* if (connection.connect(host)) {
* if (connection.login(username, password)) {
* connection.downloadFile(serverFileName);
* connection.uploadFile(localFileName);
* }
* connection.disconnect();
* }
* } catch (UnknownHostException e) {
* // handle unknown host
* } catch (IOException e) {
* // handle I/O exception
* }</pre>
* <p>Most FTP commands are wrapped by easy-to-use methods, but in case clients
* need more flexibility, you can execute commands directly using the methods
* <a href="#executeCommand(java.lang.String)">executeCommand</a> and
* <a href="#executeDataCommand(java.lang.String,
* java.io.OutputStream)">executeDataCommand</a>,
* the latter of which is used for commands that require an open data port.</p>
*
* @author Bret Taylor
* @author Julian Robichaux
* @author Brad Broerman
* @version 1.02
*/
public class FTPConnection extends Object {
/**
* If this flag is on, we print out debugging information to stdout during
* execution. Useful for debugging the FTP class and seeing the server's
* responses directly.
*/
private static boolean PRINT_DEBUG_INFO = false;
/**
* This is the abort flag. If this is set to TRUE while
* a transfer is occurring, the transfer is aborted.
* We need a reference type, so we'll make this an array...
*/
public static boolean[] abortTransfer = new boolean[1];
/**
* The socket through which we are connected to the FTP server.
*/
private Socket connectionSocket = null;
/**
* The socket output stream.
*/
private PrintStream outputStream = null;
/**
* The socket input stream.
*/
private BufferedReader inputStream = null;
/**
* The offset at which we resume a file transfer.
*/
private long restartPoint = 0L;
/*
* The size of the file currently being transferred.
*/
private long currFileSize = 0L;
/**
* Added by Julian: If this flag is on, we're currently logged in to something.
*/
private boolean loggedIn = false;
/**
* Added by Julian: This is the line terminator to use for multi-line responses. Also used
* to terminate commands.
*/
public String lineTerm = "\n";
/**
* Added by Julian: This is the size of the data blocks we use for transferring
* files.
*/
private static int BLOCK_SIZE = 4098;
/**
*
* @author Brad Broerman
*
* Used by the FTP class for updating a percentage bar, or some other indicator
* as defined by the instantiating class.
*/
public interface CallBack {
final static int GET = 0;
final static int PUT = 1;
void init( int direction, String src, String dest, long size );
void update(long count);
void end(boolean success);
}
/**
* This is the reference to our callback function.
*/
CallBack callb = null;
/**
* Added by Julian: After you create an FTPConnection object, you will call the
* connect() and login() methods to access your server. Please don't forget to
* logout() and disconnect() when you're done (it's only polite...).
*/
public FTPConnection ()
{
// default constructor (obviously) -- this is just good to have...
}
/**
* Added by Julian: Allows you to specify if you want to send debug output to
* the console (true if you do, false if you don't).
*/
public FTPConnection (boolean debugOut)
{
PRINT_DEBUG_INFO = debugOut;
}
/**
* Prints debugging information to stdout if the private flag
* <code>PRINT_DEBUG_INFO</code> is turned on.
*/
private void debugPrint(String message) {
if (PRINT_DEBUG_INFO) System.err.println(message);
}
/**
* This method sets the callback to the instantiating class.
*/
public void setUpdateCallBack(CallBack inCallb )
{
this.callb = inCallb;
}
/**
* Connects to the given FTP host on port 21, the default FTP port.
*/
public boolean connect(String host)
throws UnknownHostException, IOException
{
return connect(host, 21);
}
/**
* Connects to the given FTP host on the given port.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean connect(final String host, final int port)
throws UnknownHostException, IOException
{
Boolean returnCode = false;
try
{
returnCode = (Boolean)java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction()
{
public Object run() throws IOException
{
connectionSocket = new Socket(host, port);
outputStream = new PrintStream(connectionSocket.getOutputStream());
inputStream = new BufferedReader(new
InputStreamReader(connectionSocket.getInputStream()));
return new Boolean(true);
}
});
} catch( Exception e) {
}
if ( returnCode.booleanValue() != true && !isPositiveCompleteResponse(getServerReply())){
disconnect();
return false;
}
return true;
}
/**
* Disconnects from the host to which we are currently connected.
*/
public void disconnect()
{
if (outputStream != null) {
try {
if (loggedIn) { logout(); };
outputStream.close();
inputStream.close();
connectionSocket.close();
} catch (IOException e) {}
outputStream = null;
inputStream = null;
connectionSocket = null;
}
}
/**
* Wrapper for the commands <code>user [username]</code> and <code>pass
* [password]</code>.
*/
public boolean login(String username, String password)
throws IOException
{
int response = executeCommand("user " + username);
if( response == 220 ) {
response = getServerReply();
}
if(!isPositiveIntermediateResponse(response)) return false;
response = executeCommand("pass " + password);
loggedIn = isPositiveCompleteResponse(response);
return loggedIn;
}
/**
* Added by Julian: Logout before you disconnect (this is good form).
*/
public boolean logout()
throws IOException
{
int response = executeCommand("quit");
loggedIn = !isPositiveCompleteResponse(response);
return !loggedIn;
}
/**
* Wrapper for the command <code>cwd [directory]</code>.
*/
public boolean changeDirectory(String directory)
throws IOException
{
int response = executeCommand("cwd " + directory);
return isPositiveCompleteResponse(response);
}
/**
* Wrapper for the commands <code>rnfr [oldName]</code> and <code>rnto
* [newName]</code>.
*/
public boolean renameFile(String oldName, String newName)
throws IOException
{
int response = executeCommand("rnfr " + oldName);
if (!isPositiveIntermediateResponse(response)) return false;
response = executeCommand("rnto " + newName);
return isPositiveCompleteResponse(response);
}
/**
* Wrapper for the command <code>mkd [directory]</code>.
*/
public boolean makeDirectory(String directory)
throws IOException
{
int response = executeCommand("mkd " + directory);
return isPositiveCompleteResponse(response);
}
/**
* Wrapper for the command <code>rmd [directory]</code>.
*/
public boolean removeDirectory(String directory)
throws IOException
{
int response = executeCommand("rmd " + directory);
return isPositiveCompleteResponse(response);
}
/**
* Wrapper for the command <code>cdup</code>.
*/
public boolean parentDirectory()
throws IOException
{
int response = executeCommand("cdup");
return isPositiveCompleteResponse(response);
}
/**
* Wrapper for the command <code>dele [fileName]</code>.
*/
public boolean deleteFile(String fileName)
throws IOException
{
int response = executeCommand("dele " + fileName);
return isPositiveCompleteResponse(response);
}
/**
* Wrapper for the command <code>pwd</code>.
*/
public String getCurrentDirectory()
throws IOException
{
String response = getExecutionResponse("pwd");
StringTokenizer strtok = new StringTokenizer(response);
// Get rid of the first token, which is the return code
if (strtok.countTokens() < 2) return null;
strtok.nextToken();
String directoryName = strtok.nextToken();
// Most servers surround the directory name with quotation marks
int strlen = directoryName.length();
if (strlen == 0) return null;
if (directoryName.charAt(0) == '\"') {
directoryName = directoryName.substring(1);
strlen--;
}
if (directoryName.charAt(strlen - 1) == '\"')
return directoryName.substring(0, strlen - 1);
return directoryName;
}
/**
* Wrapper for the command <code>syst</code>.
*/
public String getSystemType()
throws IOException
{
return excludeCode(getExecutionResponse("syst"));
}
/**
* Wrapper for the command <code>mdtm [fileName]</code>. If the file does
* not exist, we return -1;
*/
/**
* @param fileName
* @return
* @throws IOException
*/
public long getModificationTime(String fileName)
throws IOException
{
String response = excludeCode(getExecutionResponse("mdtm " + fileName));
try {
return Long.parseLong(response);
} catch (Exception e) {
return -1L;
}
}
/**
* Wrapper for the command <code>size [fileName]</code>. If the file does
* not exist, we return -1;
*/
public long getFileSize(String fileName)
throws IOException
{
String response = excludeCode(getExecutionResponse("size " + fileName));
try {
return Long.parseLong(response);
} catch (Exception e) {
return -1L;
}
}
/**
* Wrapper for the command <code>retr [fileName]</code>.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public boolean downloadFile(String fileName)
throws IOException
{
final String fFileName = fileName;
Boolean returnCode = null;
abortTransfer[0] = false;
/**
* Added by Brad: Will need to get the file size from the server, so that we can update the
* percentage bar.
*
*/
currFileSize = getFileSize(fileName);
this.callb.init( CallBack.GET, fileName, fileName, currFileSize);
/**
* Added by Brad: Now, get the file. We need to use doPriviledged from the Java Security subsystem
* to keep from getting an exception, even from a signed applet.
*/
try
{
returnCode = (Boolean)java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction()
{
public Object run() throws IOException
{
return readDataToFile("retr " + fFileName, fFileName);
}
} );
}
catch ( PrivilegedActionException e )
{
this.callb.end(false);
throw (IOException) e.getException();
}
this.callb.end(returnCode.booleanValue());
return returnCode.booleanValue();
}
/**
* Wrapper for the command <code>retr [serverPath]</code>. The local file
* path to which we will write is given by <code>localPath</code>.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public boolean downloadFile(String serverPath, String localPath)
throws IOException
{
final String fServerPath = serverPath;
final String fLocalPath = localPath;
Boolean returnCode = null;
abortTransfer[0] = false;
/**
* Added by Brad: Will need to get the file size from the server, so that we can update the
* percentage bar.
*/
currFileSize = getFileSize(serverPath);
this.callb.init( CallBack.GET, serverPath, localPath, currFileSize);
/**
* Added by Brad: Now, get the file. We need to use doPriviledged from the Java Security subsystem
* to keep from getting an exception, even from a signed applet.
*/
try
{
returnCode = (Boolean)java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction()
{
public Object run() throws IOException
{
return readDataToFile("retr " + fServerPath, fLocalPath);
}
} );
}
catch ( PrivilegedActionException e )
{
this.callb.end(false);
throw (IOException) e.getException();
}
this.callb.end(returnCode.booleanValue());
return returnCode.booleanValue();
}
/**
* Wrapper for the command <code>stor [fileName]</code>.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public boolean uploadFile(String fileName) throws IOException
{
final String fFileName = fileName;
Boolean returnCode = null;
abortTransfer[0] = false;
/**
* Note: Unlike the downloadFile functions above, we need to set the
* file size in the writeDataFromFile method, after we open the file.
*/
/**
* Added by Brad: Now, get the file. We need to use doPriviledged from the Java Security subsystem
* to keep from getting an exception, even from a signed applet.
*/
try
{
returnCode = (Boolean)java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction()
{
public Object run() throws IOException
{
String finalfilename = fFileName.substring(fFileName.lastIndexOf("\\")+1);
return writeDataFromFile("stor ", finalfilename, fFileName);
}
} );
}
catch ( PrivilegedActionException e )
{
throw (IOException) e.getException();
}
return returnCode.booleanValue();
}
/**
* Wrapper for the command <code>stor [localPath]</code>. The server file
* path to which we will write is given by <code>serverPath</code>.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public boolean uploadFile(String serverPath, String localPath)
throws IOException
{
final String fServerPath = serverPath;
final String fLocalPath = localPath;
Boolean returnCode = null;
abortTransfer[0] = false;
/**
* Note: Unlike the downloadFile functions above, we need to set the
* file size in the writeDataFromFile method, after we open the file.
*/
/**
* Added by Brad: Now, get the file. We need to use doPriviledged from the Java Security subsystem
* to keep from getting an exception, even from a signed applet.
*/
try
{
returnCode = (Boolean)java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction()
{
public Object run() throws IOException
{
return writeDataFromFile("stor ", fServerPath, fLocalPath);
}
} );
}
catch ( PrivilegedActionException e )
{
throw (IOException) e.getException();
}
return returnCode.booleanValue();
}
/**
* Set the restart point for the next download or upload operation. This
* lets clients resume interrupted uploads or downloads.
*/
public void setRestartPoint(int point)
{
restartPoint = point;
debugPrint("Restart noted");
}
/**
* Gets server reply code from the control port after an ftp command has
* been executed. It knows the last line of the response because it begins
* with a 3 digit number and a space, (a dash instead of a space would be a
* continuation).
*/
private int getServerReply()
throws IOException
{
return Integer.parseInt(getFullServerReply().substring(0, 3));
}
private int getServerReply(String reply)
throws IOException
{
return Integer.parseInt(reply.substring(0, 3));
}
/**
* Gets server reply string from the control port after an ftp command has
* been executed. This consists only of the last line of the response,
* and only the part after the response code.
*/
private String getFullServerReply()
throws IOException
{
String reply;
do {
reply = inputStream.readLine();
debugPrint(reply);
} while(!(Character.isDigit(reply.charAt(0)) &&
Character.isDigit(reply.charAt(1)) &&
Character.isDigit(reply.charAt(2)) &&
reply.charAt(3) == ' '));
return reply;
}
/**
* Added by Julian: Returns the last line of the server reply, but also
* returns the full multi-line reply in a StringBuffer parameter.
*/
@SuppressWarnings("unused")
private String getFullServerReply(StringBuffer fullReply)
throws IOException
{
String reply;
fullReply.setLength(0);
do {
reply = inputStream.readLine();
debugPrint(reply);
fullReply.append(reply + lineTerm);
} while(!(Character.isDigit(reply.charAt(0)) &&
Character.isDigit(reply.charAt(1)) &&
Character.isDigit(reply.charAt(2)) &&
reply.charAt(3) == ' '));
// remove any trailing line terminators from the fullReply
if (fullReply.length() > 0)
{
fullReply.setLength(fullReply.length() - lineTerm.length());
}
return reply;
}
/**
* Added by Julian: Gets a list of files in the current directory.
*/
public String listFiles()
throws IOException
{
return listFiles("");
}
/**
* Added by Julian: Gets a list of files in either the current
* directory, or one specified as a parameter. The 'params' parameter
* can be either a directory name, a file mask, or both (such as
* '/DirName/*.txt').
*/
public String listFiles(String params)
throws IOException
{
StringBuffer files = new StringBuffer();
StringBuffer dirs = new StringBuffer();
if (!getAndParseDirList(params, files, dirs))
{
debugPrint("Error getting file list");
}
return files.toString();
}
/**
* Added by Julian: Gets a list of subdirectories in the current directory.
*/
public String listSubdirectories()
throws IOException
{
return listSubdirectories("");
}
/**
* Added by Julian: Gets a list of subdirectories in either the current
* directory, or one specified as a parameter. The 'params' parameter
* can be either a directory name, a name mask, or both (such as
* '/DirName/Sub*').
*/
public String listSubdirectories(String params)
throws IOException
{
StringBuffer files = new StringBuffer();
StringBuffer dirs = new StringBuffer();
if (!getAndParseDirList(params, files, dirs))
{
debugPrint("Error getting dir list");
}
return dirs.toString();
}
/**
* Added by Julian: Sends and gets the results of a file list command,
* like LIST or NLST.
*/
private String processFileListCommand(String command)
throws IOException
{
StringBuffer reply = new StringBuffer();
String replyString;
// file listings require you to issue a PORT command,
// like a file transfer
boolean success = executeDataCommand(command, reply);
if (!success)
{
return "";
}
replyString = reply.toString();
// strip the trailing line terminator from the reply
if (reply.length() > 0)
{
return replyString.substring(0, reply.length() - 1);
} else {
return replyString;
}
}
/**
* Added by Julian: Gets a directory list from the server and parses
* the elements into a list of files and a list of subdirectories.
*/
private boolean getAndParseDirList(String params, StringBuffer files, StringBuffer dirs)
throws IOException
{
// reset the return variables (we're using StringBuffers instead of
// Strings because you can't change a String value and pass it back
// to the calling routine -- changing a String creates a new object)
files.setLength(0);
dirs.setLength(0);
// get the NLST and the LIST -- don't worry if the commands
// don't work, because we'll just end up sending nothing back
// if that's the case
String shortList = processFileListCommand("nlst " + params);
String longList = processFileListCommand("list " + params);
// tokenize the lists we got, using a newline as a separator
StringTokenizer sList = new StringTokenizer(shortList, "\n");
StringTokenizer lList = new StringTokenizer(longList, "\n");
// other variables we'll need
String sString;
String lString;
// assume that both lists have the same number of elements
while ((sList.hasMoreTokens()) && (lList.hasMoreTokens())) {
sString = sList.nextToken();
lString = lList.nextToken();
if (lString.length() > 0)
{
if (lString.startsWith("d"))
{
dirs.append(sString.trim() + lineTerm);
debugPrint("Dir: " + sString);
} else if (lString.startsWith("-")) {
files.append(sString.trim() + lineTerm);
debugPrint("File: " + sString);
} else {
// actually, symbolic links will start with an "l"
// (lowercase L), but we're not going to mess with
// those
debugPrint("Unknown: " + lString);
}
}
}
// strip off any trailing line terminators and return the values
if (files.length() > 0) { files.setLength(files.length() - lineTerm.length()); }
if (dirs.length() > 0) { dirs.setLength(dirs.length() - lineTerm.length()); }
return true;
}
/**
* Executes the given FTP command on our current connection, returning the
* three digit response code from the server. This method only works for
* commands that do not require an additional data port.
*/
public int executeCommand(String command)
throws IOException
{
outputStream.print(command + lineTerm);
return getServerReply();
}
/**
* Executes the given FTP command on our current connection, returning the
* last line of the server's response. Useful for commands that return
* one line of information.
*/
public String getExecutionResponse(String command)
throws IOException
{
outputStream.print(command + lineTerm);
return getFullServerReply();
}
/**
* Executes the given ftpd command on the server and writes the results
* returned on the data port to the file with the given name, returning true
* if the server indicates that the operation was successful.
*/
public boolean readDataToFile(String command, String fileName)
throws IOException
{
// Open the local file
RandomAccessFile outfile = new RandomAccessFile(fileName, "rw");
// Do restart if desired
if (restartPoint != 0) {
debugPrint("Seeking to " + restartPoint);
outfile.seek(restartPoint);
}
// Convert the RandomAccessFile to an OutputStream
FileOutputStream fileStream = new FileOutputStream(outfile.getFD());
boolean success = executeDataCommand(command, fileStream);
outfile.close();
return success;
}
/**
* Executes the given ftpd command on the server and writes the contents
* of the given file to the server on an opened data port, returning true
* if the server indicates that the operation was successful.
*/
public boolean writeDataFromFile(String command, String serverFileName, String localFileName)
throws IOException
{
String finalCommand = command + serverFileName;
// Open the local file
RandomAccessFile infile = new RandomAccessFile(localFileName, "r");
// Added by Brad: Get the current file size.
currFileSize = infile.length();
this.callb.init( CallBack.PUT, serverFileName, localFileName, currFileSize);
// Do restart if desired
if (restartPoint != 0) {
debugPrint("Seeking to " + restartPoint);
infile.seek(restartPoint);
}
// Convert the RandomAccessFile to an InputStream
FileInputStream fileStream = new FileInputStream(infile.getFD());
boolean success = executeDataCommand(finalCommand, fileStream);
this.callb.end(success);
infile.close();
return success;
}
/**
* Executes the given ftpd command on the server and writes the results
* returned on the data port to the given OutputStream, returning true
* if the server indicates that the operation was successful.
*/
public boolean executeDataCommand(String command, OutputStream out)
throws IOException
{
ServerSocket serverSocket = null;
Socket clientSocket = new Socket();
// Try Passive mode: Open a data socket, and connect to server.
if(!setupPassiveDataPort(command, clientSocket))
{
// Active mode: Open a data socket on this computer
serverSocket = new ServerSocket(0);
if (!setupActiveDataPort(command, serverSocket)) return false;
clientSocket = serverSocket.accept();
}
// Transfer the data
InputStream in = clientSocket.getInputStream();
transferData(in, out);
// Clean up the data structures
in.close();
clientSocket.close();
if( null != serverSocket ) serverSocket.close();
return isPositiveCompleteResponse(getServerReply());
}
/**
* Executes the given ftpd command on the server and writes the contents
* of the given InputStream to the server on an opened data port, returning
* true if the server indicates that the operation was successful.
*/
public boolean executeDataCommand(String command, InputStream in)
throws IOException
{
ServerSocket serverSocket = null;
Socket clientSocket = new Socket();
// Try Passive mode: Open a data socket, and connect to server.
if(!setupPassiveDataPort(command, clientSocket))
{
// Active mode: Open a data socket on this computer
serverSocket = new ServerSocket(0);
if (!setupActiveDataPort(command, serverSocket)) return false;
clientSocket = serverSocket.accept();
}
// Transfer the data
OutputStream out = clientSocket.getOutputStream();
transferData(in, out);
// Clean up the data structures
out.close();
clientSocket.close();
if( null != serverSocket ) serverSocket.close();
return isPositiveCompleteResponse(getServerReply());
}
/**
* Added by Julian: Executes the given ftpd command on the server
* and writes the results returned on the data port to the given
* StringBuffer, returning true if the server indicates that the
* operation was successful.
*/
public boolean executeDataCommand(String command, StringBuffer sb)
throws IOException
{
ServerSocket serverSocket = null;
Socket clientSocket = new Socket();
// Try Passive mode: Open a data socket, and connect to server.
if(!setupPassiveDataPort(command, clientSocket))
{
// Active mode: Open a data socket on this computer
serverSocket = new ServerSocket(0);
if (!setupActiveDataPort(command, serverSocket)) return false;
clientSocket = serverSocket.accept();
}
// Transfer the data
InputStream in = clientSocket.getInputStream();
transferData(in, sb);
// Clean up the data structures
in.close();
clientSocket.close();
if( null != serverSocket ) serverSocket.close();
return isPositiveCompleteResponse(getServerReply());
}
/**
* Transfers the data from the given input stream to the given output
* stream until we reach the end of the stream.
*/
private void transferData(InputStream in, OutputStream out)
throws IOException
{
byte b[] = new byte[BLOCK_SIZE];
int amount;
// Read the data into the file
while ((amount = in.read(b)) > 0 && !abortTransfer[0]) {
out.write(b, 0, amount);
/**
* Added by Brad: Keep track of the amount sent, and update the
* calling class
*/
this.callb.update(amount);
}
}
/**
* Added by Julian: Transfers the data from the given input stream
* to the given StringBuffer until we reach the end of the stream.
*/
private void transferData(InputStream in, StringBuffer sb)
throws IOException
{
byte b[] = new byte[BLOCK_SIZE];
int amount;
// Read the data into the StringBuffer
while ((amount = in.read(b)) > 0 && !abortTransfer[0]) {
sb.append(new String(b, 0, amount));
this.callb.update(amount);
}
}
/**
* Executes the given ftpd command on the server and writes the results
* returned on the data port to the given FilterOutputStream, returning true
* if the server indicates that the operation was successful.
*/
private boolean setupActiveDataPort(String command, ServerSocket serverSocket)
throws IOException
{
// Set binary type transfer
outputStream.print("type i" + lineTerm);
if (!isPositiveCompleteResponse(getServerReply())) {
debugPrint("Could not set transfer type");
return false;
}
// Send our local data port to the server
if (!openPort(serverSocket)) return false;
// If we have a restart point, send that information
if (restartPoint != 0) {
outputStream.print("rest " + restartPoint + lineTerm);
debugPrint("rest " + restartPoint);
restartPoint = 0;
// Interpret server response here
getServerReply();
}
// Send the command
outputStream.print(command + lineTerm);
debugPrint(command);
return isPositivePreliminaryResponse(getServerReply());
}
/**
* Executes the given ftpd command on the server and writes the results
* returned on the data port to the given FilterOutputStream, returning true
* if the server indicates that the operation was successful.
*/
private boolean setupPassiveDataPort(String command, Socket clientSocket)
throws IOException
{
// Set binary type transfer
outputStream.print("type i" + lineTerm);
if (!isPositiveCompleteResponse(getServerReply())) {
debugPrint("Could not set transfer type");
return false;
}
if(!openPasvPort(clientSocket)) return false;
// If we have a restart point, send that information
if (restartPoint != 0) {
outputStream.print("rest " + restartPoint + lineTerm);
debugPrint("rest " + restartPoint);
restartPoint = 0;
// Interpret server response here
getServerReply();
}
// Send the command
outputStream.print(command + lineTerm);
debugPrint(command);
return isPositivePreliminaryResponse(getServerReply());
}
/**
* Get IP address and port number from serverSocket and send them via the
* <code>port</code> command to the ftp server, returning true if we get a
* valid response from the server, returning true if the server indicates
* that the operation was successful.
*/
private boolean openPort(ServerSocket serverSocket)
throws IOException
{
int localport = serverSocket.getLocalPort();
// get local ip address
InetAddress localip;
try {
localip = InetAddress.getLocalHost();
} catch(UnknownHostException e) {
debugPrint("Can't get local host");
return false;
}
// get ip address in high byte order
byte[] addrbytes = localip.getAddress();
// tell server what port we are listening on
short addrshorts[] = new short[4];
// problem: bytes greater than 127 are printed as negative numbers
for(int i = 0; i <= 3; i++) {
addrshorts[i] = addrbytes[i];
if (addrshorts[i] < 0)
addrshorts[i] += 256;
}
debugPrint("port " + addrshorts[0] + "," + addrshorts[1] +
"," + addrshorts[2] + "," + addrshorts[3] + "," +
((localport & 0xff00) >> 8) + "," +
(localport & 0x00ff));
outputStream.print("port " + addrshorts[0] + "," + addrshorts[1] +
"," + addrshorts[2] + "," + addrshorts[3] + "," +
((localport & 0xff00) >> 8) + "," +
(localport & 0x00ff) + lineTerm);
return isPositiveCompleteResponse(getServerReply());
}
/**
* Set passive mode through the <code> pasv </code> command to the ftp server.
* return true if we get a valid response from the server. We need to parse the
* full response text to get the IP address and port for the socket to open.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private boolean openPasvPort(Socket clientSocket)
throws IOException
{
outputStream.print("pasv " + lineTerm);
String response = getFullServerReply();
if( isPositiveCompleteResponse( getServerReply(response) ) )
{
// Find the portion of the response in parens, at the end.
int st = response.indexOf("(");
int en = response.indexOf(")", st);
if( st > -1 && en > -1 )
{
String portStr = response.substring(st+1, en);
String[] portions = portStr.split("\\,");
if( portions.length < 5 )
{
return false;
}
String host = portions[0] + "." + portions[1] + "." + portions[2] + "." + portions[3];
int port = Integer.parseInt( portions[4] ) * 256 + Integer.parseInt( portions[5] ) ;
Boolean returnCode = false;
try
{
final Socket f_clientSock = clientSocket;
final String f_host = host;
final int f_port = port;
returnCode = (Boolean)java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction()
{
public Object run() throws IOException
{
f_clientSock.connect( new InetSocketAddress( f_host, f_port ));
return new Boolean(true);
}
});
} catch( Exception e ) {
returnCode = false;
}
return returnCode;
}
else
{
debugPrint("Invalid server response to PASV.");
return false;
}
}
else
{
debugPrint("Can't set passive mode.");
return false;
}
}
/**
* True if the given response code is in the 100-199 range.
*/
private boolean isPositivePreliminaryResponse(int response)
{
return (response >= 100 && response < 200);
}
/**
* True if the given response code is in the 300-399 range.
*/
private boolean isPositiveIntermediateResponse(int response)
{
System.err.println( "Checking for postive intermediate response: " + response );
return (response >= 300 && response < 400);
}
/**
* True if the given response code is in the 200-299 range.
*/
private boolean isPositiveCompleteResponse(int response)
{
System.err.println("Checking for postive complete response: " + response );
return (response >= 200 && response < 300);
}
/**
* True if the given response code is in the 400-499 range.
*/
@SuppressWarnings("unused")
private boolean isTransientNegativeResponse(int response)
{
return (response >= 400 && response < 500);
}
/**
* True if the given response code is in the 500-599 range.
*/
@SuppressWarnings("unused")
private boolean isPermanentNegativeResponse(int response)
{
return (response >= 500 && response < 600);
}
/**
* Eliminates the response code at the beginning of the response string.
*/
private String excludeCode(String response)
{
if (response.length() < 5) return response;
return response.substring(4);
}
}