/* nixonFTP
* FTP client version 0.1
* Copyright (C) 2010 NIXON Development Corporation.
* All rights reserved.
* http://members.shaw.ca/nixon.com
*/
package nixonftp;
import nixonftp.error.NXLoginException;
import nixonftp.error.NXProtocolException;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import sun.net.ftp.FtpClient;
import sun.net.ftp.FtpLoginException;
import sun.net.ftp.FtpProtocolException;
public class NXFtpClient extends FtpClient {
private String[] arrCredentials;
private HashMap<String, NXObjectIndex> objectIndex = new HashMap<String, NXObjectIndex>();
private String dirListing = "";
static int FTP_SUCCESS = 1;
static int FTP_TRY_AGAIN = 2;
static int FTP_ERROR = 3;
private String serverName;
String command;
private boolean replyPending = false;
private boolean binaryMode = false;
private boolean enableLog = false;
private NXLog log;
private int lastReplyCode;
private int port;
public NXFtpClient() {
super();
connectTimeout = 15000;
defaultConnectTimeout = 15000;
}
public NXFtpClient(NXLog log, Proxy proxy) {
super(proxy);
this.log = log;
enableLog = true;
connectTimeout = 15000;
defaultConnectTimeout = 15000;
}
public String getResponseString() {
String response = (String) serverResponse.elementAt(0);
return response;
}
@Override
public int readServerResponse() throws IOException {
StringBuffer replyBuf = new StringBuffer(32);
int c;
int continuingCode = -1;
int code;
String response;
serverResponse.setSize(0);
while (true) {
while ((c = serverInput.read()) != -1) {
if (c == '\r') {
if ((c = serverInput.read()) != '\n') {
replyBuf.append('\r');
}
}
replyBuf.append((char) c);
if (c == '\n') {
break;
}
}
response = replyBuf.toString();
replyBuf.setLength(0);
String strLog;
strLog = response;
if (response.length() == 0) {
code = -1;
} else {
try {
code = Integer.parseInt(response.substring(0, 3));
if (code >= 500) {
strLog = '\u3000' + response;
} else if (code >= 400) {
strLog = '\u3001' + response;
} else if (code >= 300) {
strLog = '\u3002' + response;
} else if (code >= 200) {
strLog = '\u3003' + response;
} else {
strLog = '\u3004' + response;
}
} catch (NumberFormatException e) {
code = -1;
} catch (StringIndexOutOfBoundsException e) {
/* this line doesn't contain a response code, so
we just completely ignore it */
continue;
}
}
if (enableLog) {
log.add(strLog);
}
serverResponse.addElement(response);
if (continuingCode != -1) {
/* we've seen a XXX- sequence */
if (code != continuingCode ||
(response.length() >= 4 && response.charAt(3) == '-')) {
continue;
} else {
/* seen the end of code sequence */
continuingCode = -1;
break;
}
} else if (response.length() >= 4 && response.charAt(3) == '-') {
continuingCode = code;
continue;
} else {
break;
}
}
return lastReplyCode = code;
}
public boolean getBinary() {
return binaryMode;
}
protected int issueCommand(String s) throws IOException {
String p = "PASS";
if (s.contains(p)) {
if (enableLog) {
log.add(p);
}
} else {
if (enableLog) {
log.add(s);
}
}
return super.issueCommand(s);
}
@Override
protected void issueCommandCheck(String cmd) throws IOException {
if (issueCommand(cmd) != FTP_SUCCESS) {
String s = getResponseString();
if (s.startsWith("421")) {
//closeServer();
serverSocket = null;
serverInput = null;
serverOutput = null;
connect();
issueCommand(cmd);
} else {
throw new IOException(cmd + ":" + getResponseString());
}
}
}
protected Socket openPassiveDataConnection() throws IOException {
String serverAnswer;
int port;
InetSocketAddress dest = null;
/**
* Here is the idea:
*
* - First we want to try the new (and IPv6 compatible) EPSV command
* But since we want to be nice with NAT software, we'll issue the
* EPSV ALL cmd first.
* EPSV is documented in RFC2428
* - If EPSV fails, then we fall back to the older, yet OK PASV command
* - If PASV fails as well, then we throw an exception and the calling method
* will have to try the EPRT or PORT command
*/
if (issueCommand("PASV") == FTP_SUCCESS) {
// EPSV ALL failed, so Let's try the regular PASV cmd
serverAnswer = getResponseString();
// Let's parse the response String to get the IP & port to connect to
// the String should be in the following format :
//
// 227 Entering Passive Mode (A1,A2,A3,A4,p1,p2)
//
// Note that the two parenthesis are optional
//
// The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2
//
// The regular expression is a bit more complex this time, because the
// parenthesis are optionals and we have to use 3 groups.
Pattern p = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?");
Matcher m = p.matcher(serverAnswer);
if (!m.find()) {
throw new NXProtocolException("PASV failed : " + serverAnswer);
}
// Get port number out of group 2 & 3
port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8);
// IP address is simple
String s = m.group(1).replace(',', '.');
dest = new InetSocketAddress(s, port);
} else if (issueCommand("EPSV ALL") == FTP_SUCCESS) {
// We can safely use EPSV commands
if (issueCommand("EPSV") == FTP_ERROR) {
throw new NXProtocolException("EPSV Failed: " + getResponseString());
}
serverAnswer = getResponseString();
// The response string from a EPSV command will contain the port number
// the format will be :
// 229 Entering Extended Passive Mode (|||58210|)
//
// So we'll use the regular expresions package to parse the output.
Pattern p = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)");
Matcher m = p.matcher(serverAnswer);
if (!m.find()) {
throw new NXProtocolException("EPSV failed : " + serverAnswer);
}
// Yay! Let's extract the port number
String s = m.group(1);
port = Integer.parseInt(s);
InetAddress add = serverSocket.getInetAddress();
if (add != null) {
dest = new InetSocketAddress(add, port);
} else {
// This means we used an Unresolved address to connect in
// the first place. Most likely because the proxy is doing
// the name resolution for us, so let's keep using unresolved
// address.
dest = InetSocketAddress.createUnresolved(serverName, port);
}
}
// Got everything, let's open the socket!
Socket s;
if (proxy != null) {
if (proxy.type() == Proxy.Type.SOCKS) {
s = AccessController.doPrivileged(
new PrivilegedAction<Socket>() {
public Socket run() {
return new Socket(proxy);
}
});
} else {
s = new Socket(Proxy.NO_PROXY);
}
} else {
s = new Socket();
}
if (connectTimeout >= 0) {
s.connect(dest, connectTimeout);
} else {
if (defaultConnectTimeout > 0) {
s.connect(dest, defaultConnectTimeout);
} else {
s.connect(dest);
}
}
if (readTimeout >= 0) {
s.setSoTimeout(readTimeout);
} else if (defaultSoTimeout > 0) {
s.setSoTimeout(defaultSoTimeout);
}
return s;
}
public void setCredentials(String[] argv) {
arrCredentials = argv;
}
public String[] getCredentials() {
return arrCredentials;
}
private int parsePort(String addr) {
int pos = addr.indexOf(":");
int ret = -1;
if (pos != -1) {
ret = Integer.parseInt(addr.substring(pos + 1));
}
return ret;
}
public void connect() throws FtpLoginException, FtpProtocolException, UnknownHostException, IOException {
NXObjectIndex oi = new NXObjectIndex('d', "/", "", "", "", -1, -1, new Date(0));
objectIndex.put("/", oi);
if (arrCredentials[0].equals("")) {
arrCredentials[0] = "127.0.0.1";
}
int port = parsePort(arrCredentials[0]);
String server = arrCredentials[0];
if (port != -1) {
server = arrCredentials[0].substring(0, arrCredentials[0].indexOf(":"));
System.out.println("Using port " + port);
} else {
port = 21;
}
this.port = port;
openServer(server, port);
if (arrCredentials[1].equals("")) {
arrCredentials[1] = "anonymous";
arrCredentials[2] = "ftp@nixon.com";
}
login(arrCredentials[1], arrCredentials[2]);
indexDirectory("/");
}
String getListingAsString() throws IOException, NXProtocolException, NXLoginException {
InputStream ls = list();
BufferedInputStream bls = new BufferedInputStream(ls);
/*OutputStream os = new FileOutputStream("test.text");
BufferedOutputStream bos = new BufferedOutputStream(os);*/
ByteArrayOutputStream baols = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int readCount;
while ((readCount = ls.read(buffer)) > 0) {
baols.write(buffer, 0, readCount);
}
ls.close();
return baols.toString();
}
@SuppressWarnings("empty-statement")
public int indexDirectory(String dir) {
try {
cd(dir);
} catch (Exception ex) {
ex.printStackTrace();
}
NXObjectIndex blankObject = new NXObjectIndex('d', "/", "", "", "", -1, -1, new Date(0));
objectIndex.put(dir, blankObject);
String[] listing = null;
String all = null;
try {
all = getListingAsString();
listing = all.split("\n");
} catch (IOException ex) {
ex.printStackTrace();
}
char type;
String name;
int hardLinks;
String permissions;
String owner = null;
String group = null;
long size = 0;
Date date;
int arrow = 0;
int idxSpace;
String real = null;
for (int x = 0; x < listing.length; x++) {
try {
type = listing[x].charAt(0);
permissions = listing[x].substring(1, 10);
listing[x] = listing[x].substring(10).trim();
idxSpace = listing[x].indexOf(" ");
String strHardLinks = listing[x].substring(0, idxSpace);
hardLinks = Integer.valueOf(strHardLinks);
for (int y = 0; y < 3; y++) {
listing[x] = listing[x].substring(idxSpace).trim();
idxSpace = listing[x].indexOf(" ");
String item = listing[x].substring(0, idxSpace);
listing[x] = listing[x].substring(idxSpace + 1).trim();
idxSpace = 0;
switch (y) {
case 0:
owner = item;
break;
case 1:
group = item;
break;
case 2:
size = Long.valueOf(item);
break;
}
}
String strDate = listing[x].trim().substring(0, 12).trim();
date = parseDate(strDate);
name = listing[x].substring(12).trim();
if (name.equals(".") || name.equals("..")) continue;
if (type == 'l') {
arrow = name.indexOf("->");
if (arrow != -1) {
real = name.substring(arrow + 3);
name = name.substring(0, arrow - 1);
}
}
NXObjectIndex obj = new NXObjectIndex(type, name, permissions, owner, group, size, hardLinks, date);
obj.setRealLocation(real);
NXObjectIndex dirObj = (NXObjectIndex) objectIndex.get(dir);
dirObj.objects.put(name, obj);
} catch (Exception ex) {
System.out.println("Error in IndexDirectory: " + ex.toString());
}
}
return all.length();
}
public void fakeDirectory(NXObjectIndex oi, String path) { //used for links
objectIndex.put(path, oi);
}
public boolean isDirectory(String dir) {
boolean isLinkDir = false;
try {
isLinkDir = this.sendCommand("CWD " + dir);
} catch (FileNotFoundException ex) {
isLinkDir = false;
} catch (IOException ex) {
}
return isLinkDir;
}
private Date parseDate(String date) {
String temp;
int month = 0;
int day;
int year;
int hour;
int minute;
String[] months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
Calendar cal = Calendar.getInstance();
year = cal.get(Calendar.YEAR);
int currMonth = cal.get(Calendar.MONTH);
cal.clear();
temp = date.substring(0, 3); //get month
for (int x = 0; x < months.length; x++) {
if (months[x].equals(temp)) {
month = x;
}
}
temp = date.substring(4, 6);
day = Integer.parseInt(temp.trim());
if (date.contains(":")) { //includes time
hour = Integer.parseInt(date.substring(8, 9));
minute = Integer.parseInt(date.substring(11));
int actualYear;
if (month > currMonth) {
actualYear = year - 1;
} else {
actualYear = year;
}
cal.set(actualYear, month, day, hour, minute);
} else {
year = Integer.parseInt(date.substring(8));
cal.set(year, month, day);
}
return cal.getTime();
}
public boolean sendCommand(String cmd) throws IOException {
issueCommand(cmd);
return isValidResponse();
}
private boolean isValidResponse() {
try {
int respCode = getResponseCode();
return (respCode >= 200 && respCode < 300);
} catch (Exception e) {
return false;
}
}
public int getResponseCode() throws NumberFormatException {
return Integer.parseInt(getResponseString().substring(0, 3));
}
HashMap getObjectIndex() {
return objectIndex;
}
}