批量测试FlashFXP的站点文件
GzlLm@msn.com
一、起因
个人FTP站点资源太多,开始的时候整理比较简单,但是随着有些论坛的关闭,一些FTP的转型等原因,导致站点管理器里面的站点信息相对的混乱,特别是有很多密码不正确,或者ftp地址更改,端口更改等情况,如果想一一测试所有的站点,费时又费事,于是就考虑花半天的时间自己写个小program来解决这个问题。
二、计划
FlashFTP的站点文件为Sites.dat文件,所有的站点信息都记录在这个文件中,包括分组信息。sites.dat文件结构比较简单,用ultraedit直接打开的话,是看到一个文本文件转换成十六进制编辑模式就能看到一点不同了,一个站点名,包括其目录名都在一个"[]"中,例如目录abc下的子目录def下的站点sitename(以上信息部分摘录于互联网)。了解了sites.dat的结构,再需要考虑的是ip的转换,现在的ip地址的表现方式有几种,域名可以等同于ip地址,不过如果ip地址经过了如下方式的编码,程序在解析的时候就会出问题:
public String encodeIP(String ip) {
long ip1 = Long.parseLong(ip.split(".")[0]) * 256 * 256 * 256;
long ip2 = Long.parseLong(ip.split(".")[1]) * 256 * 256;
long ip3 = Long.parseLong(ip.split(".")[2]) * 256;
long ip4 = Long.parseLong(ip.split(".")[3]);
return Long.toString(ip1 + ip2 + ip3 + ip4);
}
这样编码的ip地址会变成全部数字的字符串,可以考虑使用:
public String decodeIP(String ip) {
long ip1 = Long.parseLong(ip) / 256 / 256 / 256;
long ip2 = (Long.parseLong(ip) - ip1 * 256 * 256 * 256) / 256 / 256;
long ip3 = (Long.parseLong(ip) - ip1 * 256 * 256 * 256 - ip2 * 256 * 256) / 256;
long ip4 = (Long.parseLong(ip) - ip1 * 256 * 256 * 256 - ip2 * 256
* 256 - ip3 * 256) % 256;
return ip1 + "." + ip2 + "." + ip3 + "." + ip4;
}
进行解析,不过有些地址在解析的时候会出现问题,例如以0开头的ip地址字符串等,根据以上情况,直接调用ping命令来获取ip地址,我觉得应该算是一个很简便的方法了,在这个问题上追求的不是速度,而是准确。
另外为了小程序维护分发的方便,不考虑使用第三方的FTP的包,而直接使用
sun.net.ftp.FtpClient;
sun.net.ftp.FtpLoginException;
sun.net.ftp.FtpProtocolException;
以上三个类
三、实现
1、备份原来的sites.dat文件,加上时间戳,避免文件备份被覆盖
File fbak = new File(Tester.SITE_FILENAME + "."
+ Calendar.getInstance().getTime().getTime() + ".bak");
2、sites.dat文件的获取。遍历一边sites.dat文件,获取到站点信息包括获取到正确的ip地址,获取ip地址的方法如下:
public String getRealIp(String ip) {
String realip = ip;
Process process = null;
BufferedReader bufferedReader = null;
try {
process = Runtime.getRuntime().exec("ping " + ip);
bufferedReader = new BufferedReader(new InputStreamReader(process
.getInputStream()));
String linedata = null;
while ((linedata = bufferedReader.readLine()) != null) {
if (linedata.startsWith("Pinging")) {
realip = linedata.split(" ")[1];
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
bufferedReader = null;
process = null;
}
return realip;
}
3、站点名称有些麻烦,一个站点名,包括其目录名都在一个"[]"中例如目录abc下的子目录def下的站点sitename
用String[] name=new String[]{"abc","def","sitename"}来表示
那么在sites.dat文件中应该写入的内容如下result
代码:
StringBuffer result = new StringBuffer();
result.append("[");
int len = name.length;
for (int j = 0; j < len - 1; j++) {
if (name[j] != null && !name[j].equals(""))
result.append((char) 0x
}
result.append(name[len - 1] + "]");
还一个要注意的是站点的注释中
回车要用0x01来替换
我自己处理的时候没有在网上看到上面的信息,所以直接用了点傻的方法,直接读取全部的包括了分组信息的站点名,然后在构造站点信息数据的类中,再进一步的分解成为组名称和站点名称。
以下子为SiteDate的构造器:
public SiteData(String fullName) {
this.setFullName(fullName);
if (fullName.startsWith("[")) {// 这个site属于一个分组
int lastindex = fullName.lastIndexOf("");
String tempStr = fullName.substring(lastindex + 1);
this.setGroupName(fullName.substring(0, lastindex + 1)
+ tempStr.substring(0, tempStr.indexOf(" ") + 1));
this.setSiteName(getRealSiteName(tempStr.substring(tempStr
.indexOf(" ") + 1)));
} else {
this.setGroupName("[");
this.setSiteName(getRealSiteName(fullName.substring(fullName
.indexOf("[") + 1)));
}
}
public String getRealSiteName(String siteName) {
String siterealname = siteName;
if (siterealname.indexOf("|") >= 2
&& siterealname.indexOf("|") != siterealname.lastIndexOf("|")) {
siterealname = siteName.substring(0, siterealname.indexOf("|") - 2)
+ siteName.substring(siterealname.lastIndexOf("|") + 1);
}
return siterealname;
}
4、再就是对站点数据文件中的站点密码信息解密,采用了网友kanbol已
* 经写了一个解码flashfxp密码的方法:
private static final char[] FlashFXP_Key = "yA36zA48dEhfrvghGRg57h5UlDv3"
.toCharArray();
public String decodePWD(String input) {
if (input.equals("") || input == null)
return "";
StringBuffer result = new StringBuffer();
char[] inputArr = input.toCharArray();
for (int i = 0; i < inputArr.length - 2; i = i + 2) {
String source = "" + inputArr[i + 2] + inputArr[i + 3];
String key = ""
+ Integer.toHexString((byte) (FlashFXP_Key[i / 2 % 28]));
String magic_number = "" + inputArr[i] + inputArr[i + 1];
int chr = (Integer.parseInt(source, 16) ^ Integer.parseInt(key, 16))
- Integer.parseInt(magic_number, 16);
if (chr < 0) {
chr = chr + 255;
}
result.append((char) chr);
}
return result.toString();
}
5、最后就是测试站点信息,并将反馈的结果修改原来的站点的文件名(这里需要注意,只能修改原来的站点名,不能修改组的名字,否则会将分组信息打乱)。测试的时候处理连接到服务器的各种异常是关键:
try {
fc = new FtpClient(hostname, port);
System.out.print("登录...");
fc.login(uid, pwd);
System.out.print("退出服务器...");
fc.sendServer("QUITrn");
System.out.println("n测试成功:" + sd.getFullName());
System.out.println();
} catch (FtpLoginException e) {
msg = "nFtpLoginException|" + hostname + "登录失败!请检查用户名或密码是否正确n";
System.out.println(msg);
retResult = 1;
// return false;
} catch (ConnectException e) {
msg = "nConnectException|" + hostname + "连接失败!找不到主机n";
System.out.println(msg);
retResult = 0;
} catch (SocketException e) {
msg = "nSocketException|" + hostname + "连接失败!网络连接失败n";
System.out.println(msg);
retResult = 4;
} catch (UnknownHostException e) {
msg = "nUnknownHostException|" + hostname + "解析成为IP地址失败!n";
System.out.println(msg);
retResult = 3;
// return false;
} catch (FtpProtocolException e) {
msg = "nFtpProtocolException|连接主机:" + hostname + "失败!请检查端口是否正确n";
System.out.println(msg);
retResult = 6;
// return false;
} catch (IOException e) {
e.printStackTrace();
msg = "nIOException|连接主机:" + hostname + "失败!请检查端口是否正确n";
System.out.println(msg);
retResult = 5;
// return false;
} catch (SecurityException e) {
msg = "nSecurityException|无权限与主机:" + hostname + "连接!请检查是否有访问权限n";
System.out.println(msg);
retResult = 2;
// return false;
} finally {
fc = null;
}
6、根据以上的几种异常和反馈结果,来重新命名站点文件,并写入新的站点文件中。
switch (returnint) {
case -1:
break;
case 0:
preStr = "00|ConnectFalse|";
break;
case 1:
preStr = "01|PasswordError|";
break;
case 2:
preStr = "02|NoPrivileg|";
break;
case 3:
preStr = "03|UnknownHost|";
break;
case 4:
preStr = "04|SocketError|";
break;
case 5:
preStr = "05|IOException|";
break;
case 6:
preStr = "06|FtpProtocolError|";
break;
default:
preStr = "*|UnknowError|";
break;
}
四、扩展
已经登录FTP了,想获取文件啥的,就看个人的考虑了,原先曾经考虑做一个所有ftp的文件列表,存放到本地,在需要的时候可以进行本地文件查询(以前就出现过我在一个ftp上看到一个文件,当时没有想到下载,不过等我需要的时候,却怎么也找不到那个ftp,如果上天再给我一个重来一次的机会,我会download,如果非要给下载的文件大小加一个限制,我希望是10000G……),不过这样就超出我这个小程序的预期目的了,所以就不多此一举了,够用就行。







































