Chr*_*ter 27 java ftp binary-data apache-commons-net
更新:解决了
我FTPClient.setFileType() 在登录之前打电话,导致FTP服务器使用默认模式(ASCII),无论我将其设置为什么.另一方面,客户端的行为就像文件类型已正确设置一样.BINARY模式现在完全按照需要工作,在所有情况下逐字节传输文件.我所要做的只是在wireshark中进行一些流量嗅探,然后使用netcat模拟FTP命令以查看发生了什么.为什么我两天前没有想到这个??谢谢,大家帮忙!
我有一个xml文件,utf-16编码,我使用apache的commons-net-2.0 java库的FTPClient从FTP站点下载.它提供了支持两种传输模式:ASCII_FILE_TYPE和BINARY_FILE_TYPE,不同之处在于ASCII将合适的地方行分隔符替换行分隔符('\r\n'或只是'\n'-十六进制,0x0d0a或只是0x0a).我的问题是:我有一个测试文件,utf-16编码,包含以下内容:
<?xml version='1.0' encoding='utf-16'?>
<data>
<blah>blah</blah>
</data>
这是十六进制:
0000000: 003c 003f 0078 006d 006c 0020 0076 0065 .<.?.x.m.l. .v.e
0000010: 0072 0073 0069 006f 006e 003d 0027 0031 .r.s.i.o.n.=.'.1
0000020: 002e 0030 0027 0020 0065 006e 0063 006f ...0.'. .e.n.c.o
0000030: 0064 0069 006e 0067 003d 0027 0075 0074 .d.i.n.g.=.'.u.t
0000040: 0066 002d 0031 0036 0027 003f 003e 000a .f.-.1.6.'.?.>..
0000050: 003c 0064 0061 0074 0061 003e 000a 0009 .<.d.a.t.a.>....
0000060: 003c 0062 006c 0061 0068 003e 0062 006c .<.b.l.a.h.>.b.l
0000070: 0061 0068 003c 002f 0062 006c 0061 0068 .a.h.<./.b.l.a.h
0000080: 003e 000a 003c 002f 0064 0061 0074 0061 .>...<./.d.a.t.a
0000090: 003e 000a .>..
当我ASCII为这个文件使用mode时,它正确地传输,逐字节; 结果有相同的md5sum.大.当我使用BINARY传输模式时,除了将字节从一个字节InputStream转换为a之外不应做任何事情OutputStream,结果是newlines(0x0a)被转换为回车符+换行符对(0x0d0a).这是二进制传输后的十六进制:
0000000: 003c 003f 0078 006d 006c 0020 0076 0065 .<.?.x.m.l. .v.e
0000010: 0072 0073 0069 006f 006e 003d 0027 0031 .r.s.i.o.n.=.'.1
0000020: 002e 0030 0027 0020 0065 006e 0063 006f ...0.'. .e.n.c.o
0000030: 0064 0069 006e 0067 003d 0027 0075 0074 .d.i.n.g.=.'.u.t
0000040: 0066 002d 0031 0036 0027 003f 003e 000d .f.-.1.6.'.?.>..
0000050: 0a00 3c00 6400 6100 7400 6100 3e00 0d0a ..<.d.a.t.a.>...
0000060: 0009 003c 0062 006c 0061 0068 003e 0062 ...<.b.l.a.h.>.b
0000070: 006c 0061 0068 003c 002f 0062 006c 0061 .l.a.h.<./.b.l.a
0000080: 0068 003e 000d 0a00 3c00 2f00 6400 6100 .h.>....<./.d.a.
0000090: 7400 6100 3e00 0d0a t.a.>...
它不仅转换换行符(它不应该),但它不尊重utf-16编码(不是我希望它知道它应该,它只是一个愚蠢的FTP管道).如果没有进一步处理来重新排列字节,结果是不可读的.我只是使用ASCII模式,但我的应用程序也将在同一个管道上移动真正的二进制数据(mp3文件和jpeg图像).BINARY在这些二进制文件上使用传输模式也会导致它们随机0x0d注入其内容,由于二进制数据通常包含合法0x0d0a序列,因此无法安全删除.如果我ASCII对这些文件使用模式,那么"聪明"0x0d0a0x0a
我想我的问题是(是):有没有人知道java的任何好的FTP库,只是将该死的字节从那里移到这里,或者我将不得不破解apache commons-net-2.0并维护我自己的FTP客户端代码只是为这个简单的应用程序 还有其他人处理过这种奇怪的行为吗?任何建议,将不胜感激.
我查看了commons-net源代码,看起来它不像BINARY是在使用模式时导致奇怪的行为.但InputStream它从in BINARY模式读取只是java.io.BufferedInptuStream一个套接字InputStream.这些较低级别的java流是否会进行任何奇怪的字节操作?如果他们这样做我会感到震惊,但我不知道还有什么可以在这里发生.
编辑1:
这是一段最小的代码,模仿我正在下载的文件.要编译,就这样做
javac -classpath /path/to/commons-net-2.0.jar Main.java
Run Code Online (Sandbox Code Playgroud)
要运行,您需要目录/ tmp/ascii和/ tmp/binary来下载文件,以及设置文件的ftp站点.代码还需要配置适当的ftp主机,用户名和密码.我把文件放在test /文件夹下的测试ftp站点上,并调用文件test.xml.测试文件至少应该有多行,并且是utf-16编码的(这可能不是必需的,但有助于重新创建我的确切情况).我:set fileencoding=utf-16在打开一个新文件并输入上面引用的xml文本后使用了vim的命令.最后,要运行,就这样做
java -cp .:/path/to/commons-net-2.0.jar Main
Run Code Online (Sandbox Code Playgroud)
码:
(注意:此代码已修改为使用自定义FTPClient对象,在"EDIT 2"下面链接)
import java.io.*;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
import java.util.zip.CRC32;
import org.apache.commons.net.ftp.*;
public class Main implements java.io.Serializable
{
public static void main(String[] args) throws Exception
{
Main main = new Main();
main.doTest();
}
private void doTest() throws Exception
{
String host = "ftp.host.com";
String user = "user";
String pass = "pass";
String asciiDest = "/tmp/ascii";
String binaryDest = "/tmp/binary";
String remotePath = "test/";
String remoteFilename = "test.xml";
System.out.println("TEST.XML ASCII");
MyFTPClient client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE);
File path = new File("/tmp/ascii");
downloadFTPFileToPath(client, "test/", "test.xml", path);
System.out.println("");
System.out.println("TEST.XML BINARY");
client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);
path = new File("/tmp/binary");
downloadFTPFileToPath(client, "test/", "test.xml", path);
System.out.println("");
System.out.println("TEST.MP3 ASCII");
client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE);
path = new File("/tmp/ascii");
downloadFTPFileToPath(client, "test/", "test.mp3", path);
System.out.println("");
System.out.println("TEST.MP3 BINARY");
client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);
path = new File("/tmp/binary");
downloadFTPFileToPath(client, "test/", "test.mp3", path);
}
public static File downloadFTPFileToPath(MyFTPClient ftp, String remoteFileLocation, String remoteFileName, File path)
throws Exception
{
// path to remote resource
String remoteFilePath = remoteFileLocation + "/" + remoteFileName;
// create local result file object
File resultFile = new File(path, remoteFileName);
// local file output stream
CheckedOutputStream fout = new CheckedOutputStream(new FileOutputStream(resultFile), new CRC32());
// try to read data from remote server
if (ftp.retrieveFile(remoteFilePath, fout)) {
System.out.println("FileOut: " + fout.getChecksum().getValue());
return resultFile;
} else {
throw new Exception("Failed to download file completely: " + remoteFilePath);
}
}
public static MyFTPClient createFTPClient(String url, String user, String pass, int type)
throws Exception
{
MyFTPClient ftp = new MyFTPClient();
ftp.connect(url);
if (!ftp.setFileType( type )) {
throw new Exception("Failed to set ftpClient object to BINARY_FILE_TYPE");
}
// check for successful connection
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
throw new Exception("Failed to connect properly to FTP");
}
// attempt login
if (!ftp.login(user, pass)) {
String msg = "Failed to login to FTP";
ftp.disconnect();
throw new Exception(msg);
}
// success! return connected MyFTPClient.
return ftp;
}
}
Run Code Online (Sandbox Code Playgroud)
编辑2:
好的,我按照CheckedXputStream建议,这是我的结果.我做了Apache的副本FTPClient叫MyFTPClient,我都包裹了SocketInputStream和BufferedInputStream在CheckedInputStream使用CRC32校验和.此外,我将FileOutputStream我提供的内容包装起来FTPClient以将输出存储在CheckOutputStream带CRC32校验和的内容中.MyFTPClient的代码发布在这里,我修改了上面的测试代码以使用这个版本的FTPClient(尝试将gist URL发布到修改后的代码,但我需要10个信誉点才能发布多个URL!),test.xml并且test.mp3结果如下:
14:00:08,644 DEBUG [main,TestMain] TEST.XML ASCII
14:00:08,919 DEBUG [main,MyFTPClient] Socket CRC32: 2739864033
14:00:08,919 DEBUG [main,MyFTPClient] Buffer CRC32: 2739864033
14:00:08,954 DEBUG [main,FTPUtils] FileOut CRC32: 866869773
14:00:08,955 DEBUG [main,TestMain] TEST.XML BINARY
14:00:09,270 DEBUG [main,MyFTPClient] Socket CRC32: 2739864033
14:00:09,270 DEBUG [main,MyFTPClient] Buffer CRC32: 2739864033
14:00:09,310 DEBUG [main,FTPUtils] FileOut CRC32: 2739864033
14:00:09,310 DEBUG [main,TestMain] TEST.MP3 ASCII
14:00:10,635 DEBUG [main,MyFTPClient] Socket CRC32: 60615183
14:00:10,635 DEBUG [main,MyFTPClient] Buffer CRC32: 60615183
14:00:10,636 DEBUG [main,FTPUtils] FileOut CRC32: 2352009735
14:00:10,636 DEBUG [main,TestMain] TEST.MP3 BINARY
14:00:11,482 DEBUG [main,MyFTPClient] Socket CRC32: 60615183
14:00:11,482 DEBUG [main,MyFTPClient] Buffer CRC32: 60615183
14:00:11,483 DEBUG [main,FTPUtils] FileOut CRC32: 60615183
Run Code Online (Sandbox Code Playgroud)
这基本上没有任何意义,因为这里是相应文件的md5sums:
bf89673ee7ca819961442062eaaf9c3f ascii/test.mp3
7bd0e8514f1b9ce5ebab91b8daa52c4b binary/test.mp3
ee172af5ed0204cf9546d176ae00a509 original/test.mp3
104e14b661f3e5dbde494a54334a6dd0 ascii/test.xml
36f482a709130b01d5cddab20a28a8e8 binary/test.xml
104e14b661f3e5dbde494a54334a6dd0 original/test.xml
Run Code Online (Sandbox Code Playgroud)
我不知所措.我发誓我在这个过程中的任何时候都没有置换文件名/路径,而且我已经对每一步进行了三重检查.它必须是简单的东西,但我没有最模糊的想法在哪里看下一步.为了实用性,我将通过调用shell来进行FTP传输,但我打算继续这样做,直到我明白到底是怎么回事.我会用我的调查结果更新这个帖子,我会继续感谢任何人可能有的贡献.希望这在某些方面对某人有用!
小智 31
登录ftp服务器后
ftp.setFileType(FTP.BINARY_FILE_TYPE);
Run Code Online (Sandbox Code Playgroud)
以下这行不解决它:
//ftp.setFileTransferMode(org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);
Run Code Online (Sandbox Code Playgroud)
在我看来,好像您的应用程序代码可能颠倒了 ASCII 和 BINARY 模式的选择。ASCII 不变,执行行尾字符转换的 BINARY与FTP 的工作方式完全相反。
如果这不是问题,请编辑您的问题以添加代码的相关部分。
编辑
其他几种可能(但 IMO 不太可能)的解释:
| 归档时间: |
|
| 查看次数: |
29266 次 |
| 最近记录: |