Estou recebendo uma string de um processo externo. Quero usar essa string para criar um nome de arquivo e, em seguida, gravar nesse arquivo. Este é o meu snippet de código para fazer isso:
String s = ... // comes from external source
File currentFile = new File(System.getProperty("user.home"), s);
PrintWriter currentWriter = new PrintWriter(currentFile);
Se s contiver um caractere inválido, como '/' em um sistema operacional baseado em Unix, uma java.io.FileNotFoundException é (corretamente) lançada.
Como posso codificar com segurança a String para que possa ser usada como um nome de arquivo?
Edit: O que estou esperando é uma chamada de API que faça isso para mim.
Eu posso fazer isso:
String s = ... // comes from external source
File currentFile = new File(System.getProperty("user.home"), URLEncoder.encode(s, "UTF-8"));
PrintWriter currentWriter = new PrintWriter(currentFile);
Mas não tenho certeza se o URLEncoder é confiável para essa finalidade.
Respostas:
Se você quiser que o resultado se pareça com o arquivo original, SHA-1 ou qualquer outro esquema de hash não é a resposta. Se as colisões devem ser evitadas, então a simples substituição ou remoção de caracteres "ruins" também não é a resposta.
Em vez disso, você quer algo assim. (Observação: isso deve ser tratado como um exemplo ilustrativo, não algo para copiar e colar.)
Esta solução fornece uma codificação reversível (sem colisões) onde as strings codificadas se assemelham às strings originais na maioria dos casos. Presumo que você esteja usando caracteres de 8 bits.
URLEncoder
funciona, mas tem a desvantagem de codificar muitos caracteres de nomes de arquivos legais.Se você quiser uma solução reversível não garantida, simplesmente remova os caracteres 'ruins' em vez de substituí-los por sequências de escape.
O reverso da codificação acima deve ser igualmente simples de implementar.
fonte
Minha sugestão é adotar uma abordagem de "lista branca", ou seja, não tente filtrar personagens ruins. Em vez disso, defina o que está OK. Você pode rejeitar o nome do arquivo ou filtrá-lo. Se você deseja filtrar:
O que isso faz é substituir qualquer caractere que não seja um número, letra ou sublinhado por nada. Alternativamente, você pode substituí-los por outro caractere (como um sublinhado).
O problema é que, se este for um diretório compartilhado, você não deseja colisão de nomes de arquivo. Mesmo se as áreas de armazenamento do usuário forem segregadas por usuário, você pode acabar com um nome de arquivo colidindo apenas ao filtrar os caracteres ruins. O nome que um usuário insere costuma ser útil se ele também quiser fazer o download.
Por este motivo, tendo a permitir que o usuário insira o que deseja, armazene o nome do arquivo com base em um esquema de minha própria escolha (por exemplo, userId_fileId) e, em seguida, armazene o nome do arquivo do usuário em uma tabela de banco de dados. Dessa forma, você pode exibi-lo de volta para o usuário, armazenar as coisas como quiser e não comprometer a segurança ou apagar outros arquivos.
Você também pode fazer o hash do arquivo (por exemplo, hash MD5), mas não pode listar os arquivos que o usuário colocou (não com um nome significativo de qualquer maneira).
EDIT: Regex fixo para java
fonte
"\\W+"
para regexp em Java. A barra invertida se aplica primeiro à própria string e\W
não é uma sequência de escape válida. Tentei editar a resposta, mas parece que alguém rejeitou minha edição :(Depende se a codificação deve ser reversível ou não.
Reversível
Use a codificação de URL (
java.net.URLEncoder
) para substituir caracteres especiais por%xx
. Observe que você cuida dos casos especiais onde a string é igual.
, igual..
ou vazia! ¹ Muitos programas usam codificação de URL para criar nomes de arquivo, portanto, esta é uma técnica padrão que todos entendem.Irreversível
Use um hash (por exemplo, SHA-1) da string fornecida. Algoritmos hash modernos ( não MD5) podem ser considerados livres de colisão. Na verdade, você terá um avanço na criptografia se encontrar uma colisão.
¹ Você pode lidar com todos os 3 casos especiais elegantemente usando um prefixo como
"myApp-"
. Se você colocar o arquivo diretamente em$HOME
, terá que fazer isso de qualquer maneira para evitar conflitos com arquivos existentes, como ".bashrc".fonte
Aqui está o que eu uso:
O que isso faz é substituir cada caractere que não seja uma letra, número, sublinhado ou ponto por um sublinhado, usando regex.
Isso significa que algo como "Como converter £ em $" se tornará "How_to_convert___to__". É certo que esse resultado não é muito amigável, mas é seguro e os nomes de diretório / arquivo resultantes funcionam em qualquer lugar. No meu caso, o resultado não é mostrado ao usuário e, portanto, não é um problema, mas você pode querer alterar o regex para ser mais permissivo.
Vale a pena observar que outro problema que encontrei foi que às vezes eu recebia nomes idênticos (já que é baseado na entrada do usuário), então você deve estar ciente disso, já que não pode haver vários diretórios / arquivos com o mesmo nome em um único diretório . Eu apenas acrescentei a hora e a data atuais e uma string curta aleatória para evitar isso. (uma string real aleatória, não um hash do nome do arquivo, uma vez que nomes de arquivos idênticos resultarão em hashes idênticos)
Além disso, pode ser necessário truncar ou encurtar a string resultante, pois ela pode exceder o limite de 255 caracteres que alguns sistemas têm.
fonte
Para quem procura uma solução geral, estes podem ser os critérios comuns:
Para conseguir isso, podemos usar regex para corresponder a caracteres ilegais, codificá- los por cento e , em seguida, restringir o comprimento da string codificada.
Padrões
O padrão acima é baseado em um subconjunto conservador de caracteres permitidos na especificação POSIX .
Se você quiser permitir o caractere de ponto, use:
Apenas tome cuidado com strings como "." e ".."
Se você quiser evitar colisões em sistemas de arquivos que não diferenciam maiúsculas de minúsculas, será necessário escapar de maiúsculas:
Ou escape de letras minúsculas:
Em vez de usar uma lista de permissões, você pode optar por criar uma lista negra de caracteres reservados para seu sistema de arquivos específico. EX: Este regex é adequado para sistemas de arquivos FAT32:
comprimento
No Android, 127 caracteres é o limite seguro. Muitos sistemas de arquivos permitem 255 caracteres.
Se você preferir manter a cauda, em vez da ponta da corda, use:
Decodificação
Para converter o nome do arquivo de volta à string original, use:
Limitações
Como as strings mais longas são truncadas, existe a possibilidade de uma colisão de nomes durante a codificação ou corrupção durante a decodificação.
fonte
Pattern.compile("[^A-Za-z0-9_\\-]")
Tente usar o seguinte regex que substitui cada caractere de nome de arquivo inválido por um espaço:
fonte
_
ou-
.Escolha seu veneno a partir das opções apresentadas pelo commons-codec , por exemplo:
fonte
sha1
;sha
está obsoleto.Provavelmente, essa não é a maneira mais eficaz, mas mostra como fazer isso usando pipelines Java 8:
A solução pode ser melhorada com a criação de um coletor personalizado que usa StringBuilder, para que você não precise converter cada caractere leve em uma string pesada.
fonte
Você pode remover os caracteres inválidos ('/', '\', '?', '*') E então usá-los.
fonte