144 votes

Comment puis-je coder en toute sécurité une chaîne en Java à utiliser comme nom de fichier?

Je suis la réception d'une chaîne à partir d'un processus externe. Je veux utiliser cette Chaîne pour en faire un nom de fichier, puis écrire dans ce fichier. Voici mon extrait de code pour faire ceci:

    String s = ... // comes from external source
    File currentFile = new File(System.getProperty("user.home"), s);
    PrintWriter currentWriter = new PrintWriter(currentFile);

Si s contient un caractère non valide, tels que " / " dans un Unix OS, puis un java.io.FileNotFoundException est (à juste titre) jeté.

Comment je peux encoder la Chaîne de sorte qu'il peut être utilisé comme un nom de fichier?

Edit: Ce que j'espère c'est un appel d'API qui fait cela pour moi.

Je peux faire ceci:

    String s = ... // comes from external source
    File currentFile = new File(System.getProperty("user.home"), URLEncoder.encode(s, "UTF-8"));
    PrintWriter currentWriter = new PrintWriter(currentFile);

Mais je ne suis pas sûr de savoir si URLEncoder il est fiable à cet effet.

127voto

cletus Points 276888

Ma suggestion est de prendre une "liste blanche", signifie ne pas essayer de filtrer les mauvais caractères. Au lieu de définir ce qu'est OK. Vous pouvez soit rejeter le nom de fichier ou de le filtrer. Si vous voulez filtrer:

String name = s.replaceAll("\\W+", "");

Ce que ce n'est remplace n'importe quel caractère qui n'est pas un nombre, une lettre ou un trait de soulignement avec rien. Sinon, vous pouvez les remplacer par un autre personnage (comme un trait de soulignement).

Le problème est que si c'est un répertoire partagé alors vous ne voulez pas de nom de fichier de collision. Même si l'utilisateur les zones de stockage sont séparés par l'utilisateur vous pouvez vous retrouver avec une collision de nom de fichier juste en filtrant les mauvais caractères. Le nom d'un utilisateur en est souvent utile si jamais ils décident de le télécharger.

Pour cette raison, j'ai tendance à permettre à l'utilisateur de saisir ce qu'ils veulent, stocker le nom de fichier basé sur un schéma de mon choix (par exemple userId_fileId) et ensuite le stocker de l'utilisateur du nom de fichier dans une table de base de données. De cette façon, vous pouvez l'afficher à l'utilisateur, de stocker des choses que vous voulez et vous n'avez pas de compromettre la sécurité ou effacer d'autres fichiers.

Vous pouvez également hachage du fichier (par exemple hachage MD5), mais vous ne pouvez pas afficher la liste des fichiers de l'utilisateur (avec un nom explicite de toute façon).

EDIT:correction d'une regex pour java

37voto

vog Points 3334

Elle dépend de l'encodage doit être réversible ou non.

Réversible

Utiliser le codage d'URL (java.net.URLEncoder) pour remplacer les caractères spéciaux avec des %xx. Notez que vous prenez soin de le cas spéciaux où la chaîne est égale à ., est égale à .. ou est vide!1 Beaucoup de programmes utilisent le codage d'URL pour créer des noms de fichier, c'est donc une technique standard qui tout le monde comprend.

Irréversible

Utiliser une table de hachage (par exemple SHA-1) de la chaîne. Moderne algorithmes de hachage (MD5 pas) peut être considéré comme exempt de collision. En fait, vous aurez une rupture dans la cryptographie si vous trouvez une collision.


1 Vous pouvez gérer tous les 3 cas particuliers élégamment à l'aide d'un préfixe tel que "myApp-". Si vous placez le fichier directement dans $HOME, vous aurez à faire de toute façon à éviter les conflits avec les fichiers existants tels que ".bashrc".
public static String encodeFilename(String s)
{
    try
    {
        return "myApp-" + java.net.URLEncoder.encode(s, "UTF-8");
    }
    catch (java.io.UnsupportedEncodingException e)
    {
        throw new RuntimeException("UTF-8 is an unknown encoding!?");
    }
}

18voto

Stephen C Points 255558

Si vous voulez ressembler le fichier d'origine, SHA-1 ou de tout autre régime de hachage n'est pas la réponse. Au lieu de cela, vous voulez quelque chose de ce genre.

char fileSep = '/'; // ... or do this portably.
char escape = '%'; // ... or some other legal char.
String s = ...
int len = s.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
    char ch = s.charAt(i);
    if (ch < ' ' || ch >= 0x7F || ch == fileSep || ... // add other illegal chars
        || (ch == '.' && i == 0) // we don't want to collide with "." or ".."!
        || ch == escape) {
        sb.append(escape);
        if (ch < 0x10) {
            sb.append('0');
        }
        sb.append(Integer.toHexString(ch));
    } else {
        sb.append(ch);
    }
}
File currentFile = new File(System.getProperty("user.home"), sb.toString());
PrintWriter currentWriter = new PrintWriter(currentFile);

Cette solution permet un encodage réversible (sans collision) où les chaînes codées ressembler à l'original des chaînes dans la plupart des cas. Je suis en supposant que vous utilisez des caractères 8 bits.

URLEncoder a l'inconvénient qu'il encode tout un tas de juridique caractères de nom de fichier.

Edit: Si vous voulez un non-garantis-à-être-réversible solution, alors il suffit de supprimer le "mauvais" personnages plutôt que de les remplacer avec et la séquence d'échappement.

Edit 2: les collisions avec des "." et ".." entrées de répertoire.

13voto

SharkAlley Points 2239

Pour ceux qui recherchent une solution générale, ceux-ci pourraient être commun critéres:

  • Le nom de fichier doit ressembler à la chaîne.
  • Le codage doit être réversible si possible.
  • La probabilité de collisions doit être réduite au minimum.

Pour parvenir à cela, nous pouvons utiliser des regex pour correspondre à des caractères illégaux, % - encoder , puis de contraindre la longueur de la chaîne codée.

private static final Pattern PATTERN = Pattern.compile("[^A-Za-z0-9_]");

private static final int MAX_LENGTH = 127;

public static String escapeUrlAsFilename(String url){

    StringBuffer sb = new StringBuffer();

    // Apply the regex.
    Matcher m = PATTERN.matcher(url);

    while (m.find()) {
        m.appendReplacement(sb,

                // Convert matched character to percent-encoded.
                "%"+Integer.toHexString(m.group().charAt(0)).toUpperCase()   
        );
    }
    m.appendTail(sb);

    String encoded = sb.toString();

    // Truncate the string.
    int end = Math.min(encoded.length(),MAX_LENGTH);
    return encoded.substring(0,end);
}

Modèles

Le schéma ci-dessus est basée sur un conservateur sous-ensemble de caractères autorisés dans la POSIX spec.

Si vous souhaitez autoriser le point de caractères, utilisez:

private static final Pattern PATTERN = Pattern.compile("[^A-Za-z0-9_\\.]");

Méfiez-vous des chaînes de caractères comme "." et ".."

Si vous voulez éviter les collisions à la casse des systèmes de fichiers, vous aurez besoin d'échapper capitales:

private static final Pattern PATTERN = Pattern.compile("[^a-z0-9_]");

Ou d'échapper à des lettres minuscules:

private static final Pattern PATTERN = Pattern.compile("[^A-Z0-9_]");

Plutôt que d'utiliser une liste blanche, vous pouvez choisir de la liste noire de caractères réservés pour votre système de fichiers. E. G. Cette expression convient pour les systèmes de fichiers FAT32:

private static final Pattern PATTERN = Pattern.compile("[%\\.\"\\*/:<>\\?\\\\\\|\\+,\\.;=\\[\\]]");

Longueur

Sur Android, 127 caractères est la limite de sécurité. De nombreux systèmes de fichiers permettent de 255 caractères.

Si vous préférez conserver la queue, plutôt que la tête de votre chaîne de caractères, utilisez:

// Truncate the string.
int start = Math.max(0,encoded.length()-MAX_LENGTH);
return encoded.substring(start,encoded.length());

Décodage

Pour convertir le nom de fichier dos à la chaîne d'origine, utilisez:

URLDecoder.decode(filename, "UTF-8");

Limitations

Parce que plus les chaînes de caractères sont tronqués, il ya la possibilité d'une collision de nom lors de l'encodage ou de la corruption lors du décodage.

4voto

hd1 Points 11522

Choisissez votre poison parmi les options présentées par commons-codec , exemple:

 String safeFileName = DigestUtils.sha(filename);
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X