Transfert de binaire brut avec apache commons-Net FTPClient?

mise à jour: résolu

je m'appelle FTPClient.setFileType()avant je me suis connecté, ce qui fait que le serveur FTP utilise le mode par défaut (ASCII) je l'ai mis à. Le client, d'un autre côté, se comportait comme si le type de fichier avait été correctement réglé. BINARY le mode fonctionne maintenant exactement comme désiré, transportant le fichier byte-for-byte dans tous les cas. Tout ce que j'ai eu à faire était un peu de circulation reniflant dans wireshark et puis imitant le Commandes FTP utilisant netcat pour voir ce qui se passait. Pourquoi n'y ai-je pas pensé il y a deux jours!? Merci à chacun de vous pour votre aide!

j'ai un fichier xml, encodé utf-16, que je télécharge à partir d'un site FTP en utilisant le logiciel FTPClient de la bibliothèque java commons-net-2.0 d'apache. Il offre la prise en charge de deux modes de transfert: ASCII_FILE_TYPE et BINARY_FILE_TYPE, la différence étant que ASCII remplacera les séparateurs de ligne par le séparateur de ligne local approprié ('\r\n' ou '\n' -- en hexadécimal, 0x0d0a ou 0x0a). Mon problème est le suivant: j'ai un fichier de test, codé en utf-16, qui contient le code suivant:

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

quand 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, qui n'est pas censé faire autre chose que mélanger les octets d'un InputStream dans un OutputStream, le résultat est que les retours à la ligne (0x0a) sont convertis en retour chariot + couples newline (0x0d0a). Voici l'hexagone après binaire 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 0d0at.a.>...

non seulement il convertit les caractères newline (ce qu'il ne devrait pas faire), mais il ne respecte pas l'encodage utf-16 (non pas que je m'attendrais à ce qu'il sache qu'il devrait, c'est juste un tuyau FTP stupide). 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 données binaires (fichiers mp3 et images jpeg) à travers le même tuyau. À l'aide de la BINARY le mode de transfert sur ces fichiers binaires provoque aussi qu'ils aient aléatoire 0x0ds injecté dans leur contenu, qui ne peut pas être retiré en toute sécurité puisque les données binaires souvent contient légitimes 0x0d0a séquences. Si j'utilise ASCII mode sur ces fichiers, puis le FTPClient "clever" convertit ces 0x0d0a s en0x0a laisser le fichier incompatible peu importe ce que je fais.

je suppose que ma(mes) question(s) est (Sont): est-ce que quelqu'un connaît de bonnes bibliothèques FTP pour java qui déplacent simplement les maudits octets de là à ici, ou est-ce que je vais devoir pirater apache commons-net-2.0 et maintenir mon propre code client FTP juste pour cette application simple? Quelqu'un d'autre a traité de ce comportement bizarre? Toutes les suggestions seraient appréciées.

j'ai vérifié le code source de commons-net et il ne semble pas être responsable du comportement bizarre quand BINARY le mode est utilisé. Mais l' InputStream c'est la lecture de BINARY mode est juste un java.io.BufferedInptuStream enroulé autour d'une prise InputStream. Est-ce que ces flux java de niveau inférieur font parfois des manipulations de byte bizarres? Je serais choqué si c'était le cas, mais je ne vois pas ce qui pourrait se passer d'autre. ici.

EDIT 1:

voici un petit morceau 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

pour exécuter, vous aurez besoin de répertoires /tmp/ascii et /tmp/binaire pour télécharger le fichier, ainsi que d'un site ftp configuré avec le fichier qui s'y trouve. 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 site ftp de test sous le dossier de test/ et appelé le fichier de test.XML. Le fichier de test devrait au moins avoir plus d'une ligne, et être encodé utf-16 (cela peut ne pas être nécessaire, mais cela aidera à recréer Ma situation exacte). J'ai utilisé vim :set fileencoding=utf-16 commande après avoir ouvert un nouveau fichier et entré le texte xml référencé ci-dessus. Enfin, pour courir, il suffit de faire

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

Code:

(NOTE: ce code a été modifié pour utiliser custom FTPClient object, lié ci-dessous sous "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;
    }

}

EDIT 2:

Ok Je suivi de la CheckedXputStream conseils et voici mes résultats. J'ai fait une copie de apache FTPClientMyFTPClient, et j'ai enveloppé la fois le SocketInputStream et BufferedInputStream dans un CheckedInputStream en utilisant CRC32 les sommes de contrôle. En outre, j'ai enveloppé le FileOutputStream que je donne à FTPClient pour stocker la sortie dans un CheckOutputStreamCRC32 somme de contrôle. Le code de MyFTPClient est affiché ici et j'ai modifié le code de test ci-dessus pour utiliser cette version du FTPClient (j'ai essayé de poster une URL gist code modifié, mais j'ai besoin de 10 points de réputation pour afficher 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

Cela fait, fondamentalement aucun sens que ce soit parce que voici les md5sums des fichiers correspondants:

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 perdu. J' jure Je n'ai permuté les noms de fichiers/chemins à aucun moment dans ce processus, et j'ai vérifié trois fois chaque étape. Ce doit être quelque chose de simple, mais je n'ai pas la moindre idée où pour regarder ensuite. Pour des raisons pratiques, je vais demander à shell de faire mes transferts FTP, mais j'ai l'intention de poursuivre jusqu'à ce que je comprenne ce qui se passe. Je vais mettre à jour ce fil avec mes conclusions, et je vais continuer à apprécier toute contribution que quelqu'un peut avoir. Espérons que cela sera utile à quelqu'un à un moment donné!

25
demandé sur skaffman 2010-06-30 04:41:26
la source

3 ответов

Après la connexion au serveur ftp

ftp.setFileType(FTP.BINARY_FILE_TYPE);

La ligne ci-dessous n'est pas le résoudre:

//ftp.setFileTransferMode(org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);
27
répondu Sven 2011-09-30 08:21:35
la source

il me semble que votre code d'application pourrait avoir la sélection de l'ASCII et le mode binaire inversé. ASCII passe par inchangé, binaire effectuer des traductions de caractères de fin de ligne est le exact opposé de la façon dont FTP est censé fonctionner.

si ce n'est pas le problème, veuillez modifier votre question pour ajouter la partie pertinente de votre code.

EDIT

quelques autres possibles (mais peu probables)) explications:

  • le serveur FTP est défectueux / mal configuré. (Pouvez-vous télécharger le fichier en mode ASCII / binaire en utilisant un utilitaire FTP en ligne de commande autre que Java?)
  • Vous parlez au serveur FTP via un proxy qui est cassé ou mal configuré.
  • vous avez réussi d'une manière ou d'une autre à obtenir une copie douteuse (piratée) du fichier JAR du client FTP D'Apache. (Oui, oui, très peu probable ...)
4
répondu Stephen C 2010-06-30 09:34:14
la source

J'ai trouvé que Apache retrieveFile (...) ne fonctionnait parfois pas avec des tailles de fichier dépassant une certaine limite. Pour surmonter cela j'utiliserais retrieveFileStream() à la place. Avant de télécharger, j'ai défini le type de fichier approprié et le Mode PassiveMode

Donc le code ressemblera

    ....
    ftpClientConnection.setFileType(FTP.BINARY_FILE_TYPE);
    ftpClientConnection.enterLocalPassiveMode();
    ftpClientConnection.setAutodetectUTF8(true);

    //Create an InputStream to the File Data and use FileOutputStream to write it
    InputStream inputStream = ftpClientConnection.retrieveFileStream(ftpFile.getName());
    FileOutputStream fileOutputStream = new FileOutputStream(directoryName + "/" + ftpFile.getName());
    //Using org.apache.commons.io.IOUtils
    IOUtils.copy(inputStream, fileOutputStream);
    fileOutputStream.flush();
    IOUtils.closeQuietly(fileOutputStream);
    IOUtils.closeQuietly(inputStream);
    boolean commandOK = ftpClientConnection.completePendingCommand();
    ....
2
répondu Vivek Kumar 2013-05-15 16:57:38
la source

Autres questions sur