Transfert de données binaires brutes avec apache commons-net FTPClient?

Mise à JOUR: Résolu

Je appeler FTPClient.setFileType() avant je me suis connecté, provoquant le serveur FTP à utiliser le mode par défaut (ASCII) n'importe ce je l'ai mis à. Le client, d'autre part, a été de se comporter comme si le type de fichier a été correctement définie. BINARY mode est maintenant de travailler exactement comme vous le souhaitez, le transport du fichier octet-par-octet, dans tous les cas. Tout ce que j'avais à faire était d'un peu d'écoute du trafic dans wireshark, puis mimicing les commandes FTP en utilisant netcat pour voir ce qui se passait. Pourquoi n'ai-je pas pensé qu'il y a deux jours!? Merci à chacun de vous pour votre aide!

J'ai un fichier xml, codé en utf-16, dont je suis un téléchargement depuis un site FTP à l'aide d'apache commons-net-java 2.0 de la bibliothèque FTPClient. Il offre un support pour les deux modes de transfert: ASCII_FILE_TYPE et BINARY_FILE_TYPE, la différence étant que ASCII remplacera la ligne de séparateurs locales concernées séparateur de ligne ('\r\n' ou tout simplement '\n' -- dans l'hexagone, 0x0d0a ou tout simplement 0x0a). Mon problème est le suivant: j'ai un fichier de test, codé en utf-16, qui contient les éléments suivants:

<?xml version='1.0' encoding='utf-16'?>

<data>

    <blah>blah</blah>

</data>

Voici le hex:

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                                                            .>..

Lorsque j'utilise ASCII mode pour ce fichier, il transfère correctement, octet-par-octet, le résultat est le même md5sum. Grand. Lorsque j'utilise BINARY mode de transfert, ce qui n'est pas censé faire quelque chose, mais shuffle octets à partir d'un InputStream dans un OutputStream, le résultat est que les retours à la ligne (0x0a) sont convertis en retour chariot + saut de ligne paires (0x0d0a). Voici l'hexagone après binaire de transfert:

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.>...

Non seulement de convertir les caractères de saut de ligne (ce qui ne devrait pas), mais il ne respecte pas le codage utf-16 (non pas que je pense à lui pour savoir qu'il doit, c'est juste un idiot FTP pipe). Le résultat est illisible sans traitement supplémentaire pour réaligner les octets. Je voudrais juste utiliser ASCII mode, mais ma demande sera également le déplacement réel des données binaires (fichiers mp3 et d'images jpeg) à travers le même canal. À l'aide de la BINARY mode de transfert sur ces fichiers binaires également en cause l'ont aléatoire 0x0ds injecté dans leur contenu, qui ne peuvent pas être retirés depuis le binaire de données contient souvent légitime 0x0d0a séquences. Si j'utilise ASCII mode sur ces fichiers, puis les "intelligents" FTPClient convertit ces 0x0d0as en 0x0a laisser le fichier incompatible peu importe ce que je fais.

Je suppose que ma question(s) est(sont): est-ce que quelqu'un sait de toute bonne FTP bibliothèques de java, il suffit de déplacer les damnés octets à partir de là, ici, ou vais-je avoir à pirater apache commons-net 2.0 et maintenir mon propre client FTP code juste pour cette application simple? Quelqu'un d'autre a abordé ce comportement bizarre? Toutes les suggestions seraient appréciées.

J'ai vérifié les communes-net code source et il ne regarde pas comme il est responsable pour le comportement bizarre quand BINARY mode est utilisé. Mais le InputStream c'est la lecture à partir de BINARY mode est juste un java.io.BufferedInptuStream enroulé autour d'une prise InputStream. Ces bas niveau java ruisseaux jamais faire tout bizarre octet de manipulation? Je serais choqué si ils l'ont fait, mais je ne vois pas quoi d'autre qui pourrait se passer ici.

EDIT 1:

Voici une minime partie de code qui imite ce que je fais pour télécharger le fichier. Pour compiler, il suffit de faire

javac -classpath /path/to/commons-net-2.0.jar Main.java

À exécuter, vous aurez besoin de les répertoires /tmp/ascii et /tmp/binaire du fichier à télécharger, ainsi que d'un site ftp mis en place avec le fichier assis à l'intérieur. Le code devra également être configuré avec le ftp hôte, nom d'utilisateur et mot de passe. J'ai mis le fichier sur mon test ftp site dans le cadre du test/dossier et a appelé le fichier test.xml. Le fichier de test devrait au moins avoir plus d'une ligne, et être codé en utf-16 (cela peut ne pas être nécessaire, mais vous aidera à recréer ma situation exacte). J'ai utilisé vim est :set fileencoding=utf-16 commande après l'ouverture d'un nouveau fichier et entra dans le texte xml référencé ci-dessus. Enfin, pour l'exécuter, il suffit de faire

java -cp .:/path/to/commons-net-2.0.jar Main

Code:

(NOTE: ce code modifié pour utiliser des FTPClient objet, en lien ci-dessous sous la rubrique "MODIFIER 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;
}
}

EDIT 2:

Bon j'ai suivi le CheckedXputStream conseils et voici mes résultats. J'ai fait une copie de apache FTPClient appelé MyFTPClient, et j'ai enveloppé la fois la SocketInputStream et la BufferedInputStream dans un CheckedInputStream à l'aide de CRC32 les sommes de contrôle. En outre, j'ai enveloppé le FileOutputStream que je donne à FTPClient pour stocker le résultat dans un CheckOutputStream avec CRC32 de la somme de contrôle. Le code pour MyFTPClient est affiché ici et j'ai modifié le test ci-dessus, le code à utiliser cette version de la FTPClient (essayé de poster un résumé de l'URL pour le code modifié, mais j'ai besoin de 10 points de réputation pour poster plus d'une URL!), test.xml et test.mp3 et les résultats étaient donc:

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

Ce fait, essentiellement à zéro sens que ce soit, car voici le md5sum de l'corresponsing fichiers:

bf89673ee7ca819961442062eaaf9c3f  ascii/test.mp3
7bd0e8514f1b9ce5ebab91b8daa52c4b  binary/test.mp3
ee172af5ed0204cf9546d176ae00a509  original/test.mp3
104e14b661f3e5dbde494a54334a6dd0  ascii/test.xml
36f482a709130b01d5cddab20a28a8e8  binary/test.xml
104e14b661f3e5dbde494a54334a6dd0  original/test.xml

Je suis à une perte. Je jure je n'ai pas permutées les noms de fichiers/les chemins à tout moment dans ce processus, et j'ai triple vérifiée à chaque étape. Il doit être quelque chose de simple, mais je n'ai pas la moindre idée de l'endroit où chercher la prochaine. Dans l'intérêt de la pratique, je vais procéder par appel à la coque pour faire mes transferts FTP, mais j'ai l'intention de poursuivre jusqu'à ce que j'comprendre ce que l'enfer qui se passe. Je vais mettre à jour ce thread avec mes résultats, et je vais continuer à apprécier toutes les contributions n'importe qui peut avoir. J'espère que ce sera utile à quelqu'un à un certain point!

  • Wow, c'est bizarre. J'ai vérifié le code source pour BufferedInputStream et SocketInputStream (au moins la Java partie) et je ne vois pas ce qui pourrait être en train de changer les octets autour comme ça. Je suggère de faire une copie de FTPClient et de changer le flux d'entrée de la hiérarchie à CheckedInputStream(BufferedInputStream(CheckedInputStream(SocketInputStream()))), et d'utiliser les sommes de contrôle pour voir si vous pouvez identifier où les octets sont en cours de modification. Ce serait une information utile à avoir dans la question. (Encore mieux, placez votre code de test en ligne et un lien vers elle)
  • Aussi, +1 pour une question écrite 😉
  • Je vais essayer cela et je vous remercie. Je n'avais jamais entendu parler de CheckedInputStream. Très cool!!
  • A quelqu'un d'autre (par exemple ici), a tenté de reproduire ce encore?
InformationsquelleAutor Chris Suter | 2010-06-30