5

Sto sperimentando un caso limite che stiamo vedendo in produzione. Abbiamo un modello di business in cui i clienti generano file di testo e poi li FTP ai nostri server. Noi ingeriamo questi file e li elaboriamo sul nostro back-end Java (in esecuzione su macchine CentOS). La maggior parte (95% +) dei nostri clienti sa di generare questi file in UTF-8 che è ciò che vogliamo. Tuttavia abbiamo alcuni client testardi (ma grandi account) che generano questi file sulla macchina Windows con il set di caratteri CP1252. Nessun problema, abbiamo configurato le nostre librerie di terze parti (che sono ciò che fa per noi la maggior parte delle "elaborazioni") per gestire l'input in qualsiasi set di caratteri attraverso qualche magico voo doo.Java non può vedere il file sul file system che contiene caratteri non validi

Occasionalmente, viene visualizzato un file che contiene caratteri UTF-8 non validi (CP1252) nel suo nome. Quando il nostro software cerca di leggere questi file in dal server FTP il normale metodo di file di induttanze di lettura e getta una FileNotFoundException:

File f = getFileFromFTPServer(); 
FileReader fReader = new FileReader(f); 

String line = fReader.readLine(); 
// ...etc. 

Le eccezioni simile a questa:

java.io.FileNotFoundException: /path/to/file/some-text-blah?blah.xml (No such file or directory) at java.io.FileInputStream.open(Native Method) at 
java.io.FileInputStream.(FileInputStream.java:120) at java.io.FileReader.(FileReader.java:55) at com.myorg.backend.app.InputFileProcessor.run(InputFileProcessor.java:60) at 
java.lang.Thread.run(Thread.java:662) 

Quindi quello che penso sta succedendo è che poiché il file nome contiene caratteri illegali, non siamo nemmeno in grado di leggerlo in primo luogo. Se potessimo, quindi indipendentemente dal contenuto del file, il nostro software dovrebbe essere in grado di gestirlo correttamente. Quindi questo è davvero un problema con la lettura di nomi di file con caratteri UTF-8 non validi.

Come test case, ho creato una "app" Java molto semplice da installare su uno dei nostri server e testare alcune cose (il codice sorgente è fornito di seguito). Ho quindi effettuato l'accesso a un computer Windows e ho creato un file di test e lo ho denominato test£.txt. Notare il carattere dopo "test" nel nome del file. Questo è Alt-0163. L'ho trasmesso via FTP al nostro server e quando ho eseguito l'operazione ls -ltr nella directory principale, sono stato sorpreso di vederlo elencato come test?.txt.

Prima di andare avanti, ecco il Java "app" che ho scritto per il test/riprodurre questo problema:

public Driver { 
    public static void main(String[] args) { 
     Driver d = new Driver(); 
     d.run(args[0]);  // I know this is bad, but its fine for our purposes here 
    } 

    private void run(String fileName) { 
     InputStreamReader isr = null; 
     BufferedReader buffReader = null; 
     FileInputStream fis = null; 
     String firstLineOfFile = "default"; 

     System.out.println("Processing " + fileName); 

     try { 
      System.out.println("Attempting UTF-8..."); 

      fis = new FileInputStream(fileName); 
      isr = new InputStreamReader(fis, Charset.forName("UTF-8")); 
      buffReader = new BufferedReader(isr); 

      firstLineOfFile = buffReader.readLine(); 

      System.out.println("UTF-8 worked and first line of file is : " + firstLineOfFile); 
     } 
     catch(IOException io1) { 
      // UTF-8 failed; try CP1252. 
      try { 
       System.out.println("UTF-8 failed. Attempting Windows-1252...(" + io1.getMessage() + ")"); 

       fis = new FileInputStream(fileName); 
       // I've also tried variations "WINDOWS-1252", "Windows-1252", "CP1252", "Cp1252", "cp1252" 
       isr = new InputStreamReader(fis, Charset.forName("windows-1252")); 
       buffReader = new BufferedReader(isr); 

       firstLineOfFile = buffReader.readLine(); 

       System.out.println("Windows-1252 worked and first line of file is : " + firstLineOfFile); 
      } 
      catch(IOException io2) { 
       // Both UTF-8 and CP1252 failed... 
       System.out.println("Both UTF-8 and Windows-1252 failed. Could not read file. (" + io2.getMessage() + ")"); 
      } 
     } 
    } 
} 

Quando eseguo questo dal terminale (java -cp . com/Driver t*), ottengo il seguente output:

Processing test�.txt 
Attempting UTF-8... 
UTF-8 failed. Attempting Windows-1252...(test�.txt (No such file or directory)) 
Both UTF-8 and Windows-1252 failed. Could not read file.(test�.txt (No such file or directory)) 

test�.txt?!?! Ho fatto alcune ricerche e ho scoperto che "�" è il carattere di sostituzione Unicode \uFFFD. Quindi io indovina quello che succede è che il server FTP CentOS non sa come gestire Alt-0163 (£) e quindi lo sostituisce con \uFFFD (�). Ma non capisco perché ls -ltr visualizza un file chiamato test?.txt ...

In ogni caso, sembra che la soluzione sia aggiungere una logica che cerca l'esistenza di questo carattere nel nome del file, e se trovato , rinomina il file in qualcos'altro (come forse fa un replaceAll("\uFFFD", "_") String-wise o qualcosa del genere) che il sistema può leggere ed elaborare.

Il problema è che Java non è nemmeno vedere questo file sul file system. CentOS sa che il file è lì (test?.txt), ma quando quel file viene passato in Java, Java lo interpreta come test�.txt e per qualche motivo No such file or directory ...

Come posso ottenere Java per vedere questo file in modo che possa eseguire un File::renameTo(String) su di esso? Mi dispiace per il passato qui, ma ritengo che sia rilevante dal momento che ogni dettaglio conta in questo scenario. Grazie in anticipo!

+0

quindi non puoi elencare i file nella directory, quindi vedere quali sono i "caratteri dispari" nel loro nome e rinominarli in "timestamp + random.something" con file.renameTo? –

+0

@MarkusMikkolainen - stai parlando di farlo manualmente? Se non a quale lingua/sceneggiatura ti stai riferendo? – IAmYourFaja

+0

Suggerisco di utilizzare gli oggetti File invece di passare i nomi dei file. questo probabilmente impedirà qualsiasi corruzione del nome file. –

risposta

5

Benvenuti nel meraviglioso mondo delle codifiche di testo. Hai diversi livelli di problemi e devi separarli singolarmente.

Innanzitutto, qual è il nome del file sul disco? Contiene sequenze di escape UTF-8 valide o è qualcos'altro?

Il problema qui è che è necessario il nome file corretto o il file system di Windows semplicemente non sarà in grado di trovare il file. Oltre a ciò, Windows potrebbe provare a convertire i caratteri non validi nel nome del file in Unicode \uFFFD quindi non importa cosa si prova, non sarà possibile caricare il file (poiché non vi è alcun file con \uFFFD in esso sul disco).

Come può essere? Questo accade perché la mappatura non è bidirezionale. Quando Windows carica il nome del file dal disco, sostituisce test�.txt con test\uFFFD.txt e ti dà quel nome. Quando si dice a Windows di aprire test\uFFFD.txt, non sarà in grado di trovare il file perché non c'è alcun file con tale nome (c'è solo test�.txt). Non c'è modo per voi di scoprire qual è il vero nome del file.

Soluzioni? È possibile aprire un prompt dos e rinominare il file con un pattern ren test*.txt test.txt. Poiché il modello corrisponde solo a un singolo file, funzionerà. Ma non sarai in grado di fare lo stesso da, ad esempio, in Esplora risorse perché non riesce a trovare il file.

Passaggio successivo: FTP. FTP è un protocollo per gli esseri umani - non è adatto allo scambio automatico di dati. Sbarazzarsi di FTP. Non so quanto ti costerà, ma ne vale sempre la pena. Usa SFTP, scp o FTAPI.

Un'origine dei problemi potrebbe essere che FTP trasferisce nomi di file come ASCII. Nessuna umlaut è consentita nel protocollo FTP ... o meglio, FTP non si aspetta nulla. Se sei fortunato, il tuo client FTP si rifiuterà di trasferire il file, ma più semplicemente bug out. Ma quando esistono, FTP farà ... qualcosa. Qualunque cosa possa essere. Gli effetti usuali qui sono che i file con Unicode nel nome sono codificati due volte come UTF-8 o Unicode è sostituito con ? (\u003f).

Oppure il client FTP Java potrebbe utilizzare new String(bytes) per creare una stringa dal nome del file FTP che violentasse i poveri byte con la codifica predefinita del sistema - non carina.

Solutions:

  1. utilizzare un server FTP che rifiuta i file con caratteri non validi nel nome o che sostituisce questi personaggi a qualcosa che non confonde il file system/OS.
  2. Utilizzare un file system che gestisca correttamente i file con nomi strani. Questo di solito significa sbarazzarsi di Windows sul server.
  3. Assicurarsi che gli utenti possano caricare solo in una singola directory e che questa directory possa contenere solo un singolo file. In questo modo, puoi usare un piccolo script e schemi di shell per rinominarlo in qualcosa che puoi leggere.
1

Si tratta di un errore nel vecchio file java di file java, forse solo su un mac? Ad ogni modo, la nuova java.nio api funziona molto meglio.Ho diversi file contenenti caratteri Unicode che non sono riusciti a caricare usando le classi java.io ... Dopo aver convertito tutto il mio codice per usare java.nio.Path TUTTO ha iniziato a funzionare. E ho sostituito fileutils Apache (che ha lo stesso problema) con java.nio.Files ...

Assicurarsi di leggere e scrivere il contenuto del file usando un set di caratteri appropriato, ad esempio: Files.readAllLines (myPath, StandardCharsets.UTF_8)