📄 ftpconnection.java
字号:
package ranab.server.ftp;
import java.io.File;
import java.io.RandomAccessFile;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.net.Socket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.StringTokenizer;
import ranab.io.IoUtils;
import ranab.io.StreamConnector;
/**
* This class handles each ftp connection. Here all the ftp command
* methods take two arguments - a ftp request and a writer object.
* This is the main backbone of the ftp server.
* <br>
* The ftp command method signature is:
* <code>public void doXYZ(FtpRequest request, FtpWriter out) throws IOException</code>.
* <br>
* Here <code>XYZ</code> is the capitalized ftp command.
*
* @author <a href="mailto:rana_b@yahoo.com">Rana Bhattacharyya</a>
*/
public
class FtpConnection extends BaseFtpConnection {
private final static SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
// command state specific temporary variables
private boolean mbReset = false;
private long mlSkipLen = 0;
private boolean mbRenFr = false;
private String mstRenFr = null;
private boolean mbUser = false;
private boolean mbPass = false;
/**
* Set configuration file and the control socket.
*/
public FtpConnection(FtpConfig cfg, Socket soc) {
super(cfg, soc);
}
/**
* Check the user permission to execute this command.
*/
protected boolean hasPermission(FtpRequest request) {
String cmd = request.getCommand();
return mUser.hasLoggedIn() ||
cmd.equals("USER") ||
cmd.equals("PASS") ||
cmd.equals("HELP");
}
/**
* Reset temporary state variables.
*/
private void resetState() {
mbRenFr = false;
mstRenFr = null;
mbReset = false;
mlSkipLen = 0;
mbUser = false;
mbPass = false;
}
////////////////////////////////////////////////////////////
///////////////// all the FTP handlers /////////////////
////////////////////////////////////////////////////////////
/**
* <code>ABOR <CRLF></code><br>
*
* This command tells the server to abort the previous FTP
* service command and any associated transfer of data.
* No action is to be taken if the previous command
* has been completed (including data transfer). The control
* connection is not to be closed by the server, but the data
* connection must be closed.
* Current implementation does not do anything. As here data
* transfers are not multi-threaded.
*/
public void doABOR(FtpRequest request, FtpWriter out) throws IOException {
// reset state variables
resetState();
mDataConnection.reset();
out.write(mFtpStatus.getResponse(226, request, mUser, null));
}
/**
* <code>APPE <SP> <pathname> <CRLF></code><br>
*
* This command causes the server-DTP to accept the data
* transferred via the data connection and to store the data in
* a file at the server site. If the file specified in the
* pathname exists at the server site, then the data shall be
* appended to that file; otherwise the file specified in the
* pathname shall be created at the server site.
*/
public void doAPPE(FtpRequest request, FtpWriter out) throws IOException {
// reset state variables
resetState();
// argument check
if(!request.hasArgument()) {
out.write(mFtpStatus.getResponse(501, request, mUser, null));
return;
}
// get filenames
String fileName = request.getArgument();
fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
File requestedFile = new File(physicalName);
String args[] = {fileName};
// check permission
if(!mUser.getVirtualDirectory().hasWritePermission(physicalName, true)) {
out.write(mFtpStatus.getResponse(450, request, mUser, args));
return;
}
// now transfer file data
out.write(mFtpStatus.getResponse(150, request, mUser, args));
InputStream is = null;
OutputStream os = null;
try {
Socket dataSoc = mDataConnection.getDataSocket();
if (dataSoc == null) {
out.write(mFtpStatus.getResponse(550, request, mUser, args));
return;
}
is = dataSoc.getInputStream();
RandomAccessFile raf = new RandomAccessFile(requestedFile, "rw");
raf.seek(raf.length());
os = mUser.getOutputStream( new FileOutputStream(raf.getFD()) );
StreamConnector msc = new StreamConnector(is, os);
msc.setMaxTransferRate(mUser.getMaxUploadRate());
msc.setObserver(this);
msc.connect();
if(msc.hasException()) {
out.write(mFtpStatus.getResponse(451, request, mUser, args));
}
else {
mConfig.getStatistics().setUpload(requestedFile, mUser, msc.getTransferredSize());
}
out.write(mFtpStatus.getResponse(226, request, mUser, args));
}
catch(IOException ex) {
out.write(mFtpStatus.getResponse(425, request, mUser, args));
}
finally {
IoUtils.close(is);
IoUtils.close(os);
mDataConnection.reset();
}
}
/**
* <code>CDUP <CRLF></code><br>
*
* This command is a special case of CWD, and is included to
* simplify the implementation of programs for transferring
* directory trees between operating systems having different
* syntaxes for naming the parent directory. The reply codes
* shall be identical to the reply codes of CWD.
*/
public void doCDUP(FtpRequest request, FtpWriter out) throws IOException {
// reset state variables
resetState();
// change directory
if (mUser.getVirtualDirectory().changeDirectory("..")) {
String args[] = {mUser.getVirtualDirectory().getCurrentDirectory()};
out.write(mFtpStatus.getResponse(200, request, mUser, args));
}
else {
out.write(mFtpStatus.getResponse(431, request, mUser, null));
}
}
/**
* <code>CWD <SP> <pathname> <CRLF></code><br>
*
* This command allows the user to work with a different
* directory for file storage or retrieval without
* altering his login or accounting information. Transfer
* parameters are similarly unchanged. The argument is a
* pathname specifying a directory.
*/
public void doCWD(FtpRequest request, FtpWriter out) throws IOException {
// reset state variables
resetState();
// get new directory name
String dirName = "/";
if (request.hasArgument()) {
dirName = request.getArgument();
}
// change directory
if (mUser.getVirtualDirectory().changeDirectory(dirName)) {
String args[] = {mUser.getVirtualDirectory().getCurrentDirectory()};
out.write(mFtpStatus.getResponse(200, request, mUser, args));
}
else {
out.write(mFtpStatus.getResponse(431, request, mUser, null));
}
}
/**
* <code>DELE <SP> <pathname> <CRLF></code><br>
*
* This command causes the file specified in the pathname to be
* deleted at the server site.
*/
public void doDELE(FtpRequest request, FtpWriter out) throws IOException {
// reset state variables
resetState();
// argument check
if(!request.hasArgument()) {
out.write(mFtpStatus.getResponse(501, request, mUser, null));
return;
}
// get filenames
String fileName = request.getArgument();
fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
File requestedFile = new File(physicalName);
String[] args = {fileName};
// check permission
if(!mUser.getVirtualDirectory().hasWritePermission(physicalName, true)) {
out.write(mFtpStatus.getResponse(450, request, mUser, args));
return;
}
// now delete
if(requestedFile.delete()) {
out.write(mFtpStatus.getResponse(250, request, mUser, args));
mConfig.getStatistics().setDelete(requestedFile, mUser);
}
else {
out.write(mFtpStatus.getResponse(450, request, mUser, args));
}
}
/**
* <code>HELP [<SP> <string>] <CRLF></code><br>
*
* This command shall cause the server to send helpful
* information regarding its implementation status over the
* control connection to the user. The command may take an
* argument (e.g., any command name) and return more specific
* information as a response.
*/
public void doHELP(FtpRequest request, FtpWriter out) throws IOException {
// print global help
if(!request.hasArgument()) {
out.write(mFtpStatus.getResponse(214, null, mUser, null));
return;
}
// print command specific help
String ftpCmd = request.getArgument().toUpperCase();
String args[] = null;
FtpRequest tempRequest = new FtpRequest(ftpCmd);
out.write(mFtpStatus.getResponse(214, tempRequest, mUser, args));
return;
}
/**
* <code>LIST [<SP> <pathname>] <CRLF></code><br>
*
* This command causes a list to be sent from the server to the
* passive DTP. If the pathname specifies a directory or other
* group of files, the server should transfer a list of files
* in the specified directory. If the pathname specifies a
* file then the server should send current information on the
* file. A null argument implies the user's current working or
* default directory. The data transfer is over the data
* connection
*/
public void doLIST(FtpRequest request, FtpWriter out) throws IOException {
// reset state variables
resetState();
out.write(mFtpStatus.getResponse(150, request, mUser, null));
Writer os = null;
try {
Socket dataSoc = mDataConnection.getDataSocket();
if (dataSoc == null) {
out.write(mFtpStatus.getResponse(550, request, mUser, null));
return;
}
os = new OutputStreamWriter(dataSoc.getOutputStream());
if (!mUser.getVirtualDirectory().printList(request.getArgument(), os)) {
out.write(mFtpStatus.getResponse(501, request, mUser, null));
}
else {
os.flush();
out.write(mFtpStatus.getResponse(226, request, mUser, null));
}
}
catch(IOException ex) {
out.write(mFtpStatus.getResponse(425, request, mUser, null));
}
finally {
IoUtils.close(os);
mDataConnection.reset();
}
}
/**
* <code>MDTM <SP> <pathname> <CRLF></code><br>
*
* Returns the date and time of when a file was modified.
*/
public void doMDTM(FtpRequest request, FtpWriter out) throws IOException {
// argument check
if(!request.hasArgument()) {
out.write(mFtpStatus.getResponse(501, request, mUser, null));
return;
}
// reset state variables
resetState();
// get filenames
String fileName = request.getArgument();
fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
File reqFile = new File(physicalName);
// now print date
if(reqFile.exists()) {
String args[] = {DATE_FMT.format(new Date(reqFile.lastModified()))};
out.write(mFtpStatus.getResponse(213, request, mUser, args));
}
else {
out.write(mFtpStatus.getResponse(550, request, mUser, null));
}
}
/**
* <code>MKD <SP> <pathname> <CRLF></code><br>
*
* This command causes the directory specified in the pathname
* to be created as a directory (if the pathname is absolute)
* or as a subdirectory of the current working directory (if
* the pathname is relative).
*/
public void doMKD(FtpRequest request, FtpWriter out) throws IOException {
// reset state variables
resetState();
// argument check
if(!request.hasArgument()) {
out.write(mFtpStatus.getResponse(501, request, mUser, null));
return;
}
// get filenames
String fileName = request.getArgument();
fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
String args[] = {fileName};
// check permission
if(!mUser.getVirtualDirectory().hasCreatePermission(physicalName, true)) {
out.write(mFtpStatus.getResponse(450, request, mUser, args));
return;
}
// now create directory
if(new File(physicalName).mkdirs()) {
out.write(mFtpStatus.getResponse(250, request, mUser, args));
}
else {
out.write(mFtpStatus.getResponse(450, request, mUser, args));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -