Recibo una cadena de un proceso externo. Quiero usar esa Cadena para hacer un nombre de archivo y luego escribir en ese archivo. Aquí está mi fragmento de código para hacer esto:
String s = ... // comes from external source
File currentFile = new File(System.getProperty("user.home"), s);
PrintWriter currentWriter = new PrintWriter(currentFile);
Si s contiene un carácter no válido, como '/' en un sistema operativo basado en Unix, entonces se lanza (correctamente) una java.io.FileNotFoundException.
¿Cómo puedo codificar de forma segura la cadena para que pueda usarse como nombre de archivo?
Editar: Lo que espero es una llamada API que haga esto por mí.
Puedo hacer esto:
String s = ... // comes from external source
File currentFile = new File(System.getProperty("user.home"), URLEncoder.encode(s, "UTF-8"));
PrintWriter currentWriter = new PrintWriter(currentFile);
Pero no estoy seguro de si URLEncoder es confiable para este propósito.
Respuestas:
Si desea que el resultado se parezca al archivo original, SHA-1 o cualquier otro esquema de hash no es la respuesta. Si se deben evitar las colisiones, la simple sustitución o eliminación de los caracteres "malos" tampoco es la respuesta.
En su lugar, quiere algo como esto. (Nota: esto debe tratarse como un ejemplo ilustrativo, no como algo para copiar y pegar).
Esta solución proporciona una codificación reversible (sin colisiones) donde las cadenas codificadas se parecen a las cadenas originales en la mayoría de los casos. Supongo que está utilizando caracteres de 8 bits.
URLEncoder
funciona, pero tiene la desventaja de que codifica una gran cantidad de caracteres de nombres de archivos legales.Si desea una solución no garantizada para ser reversible, simplemente elimine los caracteres 'malos' en lugar de reemplazarlos con secuencias de escape.
El reverso de la codificación anterior debería ser igualmente sencillo de implementar.
fuente
Mi sugerencia es adoptar un enfoque de "lista blanca", lo que significa que no intente filtrar los personajes malos. En su lugar, defina lo que está bien. Puede rechazar el nombre del archivo o filtrarlo. Si quieres filtrarlo:
Lo que hace es reemplazar cualquier carácter que no sea un número, letra o guión bajo por nada. Alternativamente, puede reemplazarlos con otro carácter (como un guión bajo).
El problema es que si se trata de un directorio compartido, no querrás que se produzca una colisión de nombres de archivo. Incluso si las áreas de almacenamiento del usuario están segregadas por usuario, puede terminar con un nombre de archivo en colisión con solo filtrar los caracteres incorrectos. El nombre que pone un usuario suele ser útil si alguna vez quiere descargarlo también.
Por esta razón, tiendo a permitir que el usuario ingrese lo que quiera, almacene el nombre de archivo según un esquema de mi propia elección (por ejemplo, userId_fileId) y luego almacene el nombre de archivo del usuario en una tabla de base de datos. De esa manera, puede mostrarlo al usuario, almacenar las cosas como desee y no comprometer la seguridad ni borrar otros archivos.
También puede aplicar un hash al archivo (p. Ej., Hash MD5), pero luego no puede enumerar los archivos que puso el usuario (de todos modos, no con un nombre significativo).
EDITAR: expresión regular fija para java
fuente
"\\W+"
para la expresión regular en Java. La barra invertida se aplica primero a la cadena en sí y\W
no es una secuencia de escape válida. Intenté editar la respuesta, pero parece que alguien rechazó mi edición :(Depende de si la codificación debe ser reversible o no.
Reversible
Utilice la codificación URL (
java.net.URLEncoder
) para reemplazar los caracteres especiales con%xx
. ¡Tenga en cuenta que se ocupa de los casos especiales en los que la cadena es igual.
, es igual..
o está vacía! ¹ Muchos programas utilizan la codificación URL para crear nombres de archivos, por lo que esta es una técnica estándar que todos comprenden.Irreversible
Utilice un hash (por ejemplo, SHA-1) de la cadena dada. Los algoritmos hash modernos ( no MD5) pueden considerarse libres de colisiones. De hecho, tendrá un gran avance en criptografía si encuentra una colisión.
¹ Puede manejar los 3 casos especiales con elegancia usando un prefijo como
"myApp-"
. Si coloca el archivo directamente en$HOME
, tendrá que hacerlo de todos modos para evitar conflictos con archivos existentes como ".bashrc".fuente
Esto es lo que uso:
Lo que hace es reemplazar cada carácter que no sea una letra, número, guión bajo o punto con un guión bajo, usando expresiones regulares.
Esto significa que algo como "Cómo convertir £ a $" se convertirá en "Cómo_convertir___a__". Es cierto que este resultado no es muy fácil de usar, pero es seguro y se garantiza que los nombres de directorio / archivo resultantes funcionarán en todas partes. En mi caso, el resultado no se muestra al usuario y, por lo tanto, no es un problema, pero es posible que desee modificar la expresión regular para que sea más permisivo.
Vale la pena señalar que otro problema que encontré fue que a veces obtenía nombres idénticos (ya que se basa en la entrada del usuario), por lo que debe tenerlo en cuenta, ya que no puede tener varios directorios / archivos con el mismo nombre en un solo directorio . Acabo de agregar la hora y fecha actuales, y una cadena aleatoria corta para evitar eso. (una cadena aleatoria real, no un hash del nombre del archivo, ya que los nombres de archivo idénticos darán como resultado hash idénticos)
Además, es posible que deba truncar o acortar la cadena resultante, ya que puede exceder el límite de 255 caracteres que tienen algunos sistemas.
fuente
Para aquellos que buscan una solución general, estos pueden ser criterios comunes:
Para lograr esto, podemos usar expresiones regulares para hacer coincidir caracteres ilegales, codificarlos en porcentaje y luego restringir la longitud de la cadena codificada.
Patrones
El patrón anterior se basa en un subconjunto conservador de caracteres permitidos en la especificación POSIX .
Si desea permitir el carácter de punto, use:
Solo tenga cuidado con cadenas como "." y ".."
Si desea evitar colisiones en sistemas de archivos que no distinguen entre mayúsculas y minúsculas, deberá escapar de las mayúsculas:
O escapar de las minúsculas:
En lugar de utilizar una lista blanca, puede optar por incluir en la lista negra los caracteres reservados para su sistema de archivos específico. Por ejemplo, esta expresión regular se adapta a los sistemas de archivos FAT32:
Longitud
En Android, 127 caracteres es el límite seguro. Muchos sistemas de archivos permiten 255 caracteres.
Si prefiere retener la cola, en lugar de la cabeza de su cuerda, use:
Descodificación
Para convertir el nombre de archivo de nuevo a la cadena original, use:
Limitaciones
Debido a que las cadenas más largas se truncan, existe la posibilidad de una colisión de nombres al codificar o de daños al decodificar.
fuente
Pattern.compile("[^A-Za-z0-9_\\-]")
Intente usar la siguiente expresión regular que reemplaza cada carácter de nombre de archivo no válido con un espacio:
fuente
_
o-
.Elija su veneno de las opciones presentadas por commons-codec , ejemplo:
fuente
sha1
;sha
es obsoleto.Probablemente esta no sea la forma más efectiva, pero muestra cómo hacerlo usando canalizaciones de Java 8:
La solución podría mejorarse creando un colector personalizado que use StringBuilder, por lo que no tiene que convertir cada carácter ligero en una cadena pesada.
fuente
Puede eliminar los caracteres no válidos ('/', '\', '?', '*') Y luego usarlos.
fuente