5

Estoy experimentando con una carcasa de borde que estamos viendo en producción. Tenemos un modelo comercial donde los clientes generan archivos de texto y luego los transfieren a nuestros servidores FTP. Ingestamos esos archivos y los procesamos en nuestro backend Java (ejecutándose en máquinas CentOS). La mayoría (95% +) de nuestros clientes saben que generan estos archivos en UTF-8, que es lo que queremos. Sin embargo, tenemos algunos clientes obstinados (pero cuentas grandes) que generan estos archivos en la máquina Windows con el juego de caracteres CP1252. Sin embargo, no hay problema, configuramos nuestras librerías de terceros (que son lo que hace la mayor parte del trabajo de "procesamiento") para manejar la entrada en cualquier conjunto de caracteres a través de un voo doo mágico.Java no puede ver el archivo en el sistema de archivos que contiene caracteres ilegales

Ocasionalmente, vemos un archivo que tiene caracteres UTF-8 ilegales (CP1252) en su nombre. Cuando nuestro software intenta leer estos archivos desde el servidor FTP el método normal de las estrangulaciones de lectura de archivos y lanza un FileNotFoundException:

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

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

Las excepciones ser algo como esto:

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) 

Así que lo que piensan está sucediendo es que debido a que el archivo nombre en sí mismo contiene caracteres ilegales, nunca llegamos a leerlo en primer lugar. Si pudiéramos, independientemente del contenido del archivo, nuestro software debería poder procesarlo correctamente. Así que esto es realmente un problema con la lectura de nombres de archivo con caracteres UTF-8 ilegales en ellos.

Como caso de prueba, creé una "aplicación" de Java muy simple para implementar en uno de nuestros servidores y probar algunas cosas (el código fuente se proporciona a continuación). Luego inicié sesión en una máquina con Windows y creé un archivo de prueba y lo llamé test£.txt. Observe el personaje después de "prueba" en el nombre del archivo. Este es Alt-0163. Envié esto por FTP a nuestro servidor, y cuando ejecuté ls -ltr en su directorio padre, me sorprendió verlo listado como test?.txt.

Antes de ir más lejos, aquí es el de Java "aplicación" que escribí para la prueba/reproducción de este tema:

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() + ")"); 
      } 
     } 
    } 
} 

Cuando ejecuto esto desde el terminal (java -cp . com/Driver t*), me sale el siguiente resultado:

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?!?! Investigué un poco y descubrí que el "�" es el personaje de reemplazo de Unicode \uFFFD. Entonces, , supongo lo que está sucediendo es que el servidor FTP de CentOS no sabe cómo manejar Alt-0163 (£) y lo reemplaza con \uFFFD (�). Pero no entiendo por qué ls -ltr muestra un archivo llamado test?.txt ...

En cualquier caso, parece que la solución es agregar algo de lógica que busque la existencia de este carácter en el nombre del archivo, y si se encuentra , cambia el nombre del archivo a otra cosa (como quizás hacer un String-wise replaceAll("\uFFFD", "_") o algo así) que el sistema puede leer y procesar.

El problema es que Java ni siquiera ver este archivo en el sistema de archivos. CentOS sabe que el archivo está allí (test?.txt), pero cuando ese archivo pasa a Java, Java lo interpreta como test�.txt y por alguna razón No such file or directory ...

¿Cómo puedo conseguir Java para ver este archivo para que pueda realizar una File::renameTo(String) en él? Perdón por la historia de fondo aquí, pero creo que es relevante ya que cada detalle cuenta en este escenario. ¡Gracias por adelantado!

+0

por lo que no puede listar los archivos en el directorio, luego ver cuáles tienen "caracteres impares" en su nombre y cambiarles el nombre a "indicación de fecha y hora + al azar.algo" con file.renameTo? –

+0

@MarkusMikkolainen - ¿Estás hablando de hacer esto manualmente? Si no, ¿a qué idioma/guión se está refiriendo? – IAmYourFaja

+0

Sugiero que use objetos File en lugar de pasar nombres de archivos. eso probablemente evitará cualquier corrupción de nombre de archivo. –

Respuesta

5

Bienvenido al maravilloso mundo de las codificaciones de texto. Tiene varios niveles de problemas y necesita ordenarlos individualmente.

Primero, ¿cuál es el nombre del archivo en el disco? ¿Contiene secuencias de escape UTF-8 válidas o es algo más?

El problema aquí es que se necesita el nombre de archivo correcto o el sistema de archivos de Windows simplemente no será capaz de encontrar el archivo. Además de eso, Windows podría tratar de convertir los caracteres ilegales en el nombre del archivo a Unicode \uFFFD, así que no importa lo que intentes, no podrás cargar el archivo (ya que no hay ningún archivo con \uFFFD en el disco))

¿Cómo puede ser eso? Esto sucede porque la asignación no es bidireccional. Cuando Windows carga el nombre del archivo desde el disco, reemplaza test�.txt con test\uFFFD.txt y le da ese nombre. Cuando le dice a Windows que abra test\uFFFD.txt, no podrá encontrar el archivo porque no hay ningún archivo con ese nombre (solo hay test�.txt). No hay forma de que descubra cuál es el verdadero nombre del archivo.

Soluciones? Puede abrir un mensaje de dos y renombrar el archivo con un patrón ren test*.txt test.txt. Dado que el patrón coincide con un solo archivo, eso funcionará. Pero no podrá hacer lo mismo desde, digamos, el Explorador de Windows porque tampoco puede encontrar el archivo.

Siguiente paso: FTP. FTP es un protocolo para humanos; no es adecuado para el intercambio automático de datos. Deshágase de FTP. No sé cuánto te costará, pero siempre vale la pena. Use SFTP, scp o FTAPI.

Una fuente de los problemas podría ser que las transferencias FTP nombres de archivo como ASCII. No hay diéresis permitidas en el protocolo FTP ... o más bien, FTP no espera ninguna. Si tiene suerte, su cliente FTP se negará a transferir el archivo, pero la mayoría simplemente falla. Pero cuando existen, FTP simplemente hará ... algo. Lo que sea que sea eso. Los efectos habituales aquí son que los archivos con Unicode en el nombre están codificados dos veces como UTF-8 o Unicode se reemplaza por ? (\u003f).

O el cliente FTP de Java podría usar new String(bytes) para crear una Cadena desde el nombre del archivo FTP que violaría los bytes pobres con la codificación por defecto del Sistema, no es bonita.

Soluciones:

  1. utilizar un servidor FTP que rechaza los archivos con caracteres no válidos en sus nombres o que sustituye estos personajes a algo que no confunda el sistema de archivos/OS.
  2. Utilice un sistema de archivos que maneje correctamente los archivos con nombres extraños. Eso generalmente significa deshacerse de Windows en el servidor.
  3. Asegúrese de que los usuarios solo puedan cargar en un solo directorio y que este directorio solo contenga un solo archivo. De esta forma, puede usar un script de shell pequeño y patrones para cambiarle el nombre a algo que pueda leer.
1

Es un error en el archivo java old-skool File api, ¿tal vez solo en un mac? De todos modos, la nueva API java.nio funciona mucho mejor.Tengo varios archivos que contienen caracteres Unicode que no se pudieron cargar usando clases java.io ... Después de convertir todo mi código para usar java.nio.Path TODO comenzó a funcionar. Y he sustituido FileUtils Apache (que tiene el mismo problema) con java.nio.Files ...

Asegúrese de leer y escribir el contenido del archivo usando una charset apropiado, por ejemplo: Files.readAllLines (myPath, StandardCharsets.UTF_8)

Cuestiones relacionadas