web-dev-qa-db-fra.com

Existe-t-il un moyen de rendre les chaînes de chemin de fichiers sûres en c #?

Mon programme prendra des chaînes arbitraires sur Internet et les utilisera pour les noms de fichiers. Existe-t-il un moyen simple de supprimer les mauvais caractères de ces chaînes ou dois-je écrire une fonction personnalisée pour cela?

79
Martin Doms

Ugh, je déteste quand les gens essaient de deviner quels personnages sont valides. En plus d'être complètement non-portable (en pensant toujours à Mono), les deux commentaires précédents manquaient plus de 25 caractères invalides.

'Clean just a filename
Dim filename As String = "salmnas dlajhdla kjha;dmas'lkasn"
For Each c In IO.Path.GetInvalidFileNameChars
    filename = filename.Replace(c, "")
Next

'See also IO.Path.GetInvalidPathChars
159
Jonathan Allen

Cette question a été posée plusieursfoisavant et, comme il a été souligné à maintes reprises, IO.Path.GetInvalidFileNameChars n'est pas adéquat.

Premièrement, il existe de nombreux noms comme PRN et CON qui sont réservés et non autorisés pour les noms de fichiers. Il existe d'autres noms non autorisés uniquement dans le dossier racine. Les noms qui se terminent par une période ne sont également pas autorisés.

Deuxièmement, il existe une variété de limites de longueur. Lisez la liste complète pour NTFS ici .

Troisièmement, vous pouvez attacher des systèmes de fichiers ayant d'autres limitations. Par exemple, les noms de fichiers ISO 9660 ne peuvent pas commencer par "-" mais peuvent le contenir.

Quatrièmement, que faites-vous si deux processus choisissent "arbitrairement" le même nom?

En général, utiliser des noms générés de manière externe pour les noms de fichiers est une mauvaise idée. Je suggère de générer vos propres noms de fichiers privés et de stocker des noms lisibles en interne.

31
Dour High Arch

Pour supprimer les caractères non valides:

static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars();

// Builds a string out of valid chars
var validFilename = new string(filename.Where(ch => !invalidFileNameChars.Contains(ch)).ToArray());

Pour remplacer les caractères non valides:

static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars();

// Builds a string out of valid chars and an _ for invalid ones
var validFilename = new string(filename.Select(ch => invalidFileNameChars.Contains(ch) ? '_' : ch).ToArray());

Pour remplacer les caractères non valides (et éviter les conflits de noms potentiels tels que Hell * vs Hell $):

static readonly IList<char> invalidFileNameChars = Path.GetInvalidFileNameChars();

// Builds a string out of valid chars and replaces invalid chars with a unique letter (Moves the Char into the letter range of unicode, starting at "A")
var validFilename = new string(filename.Select(ch => invalidFileNameChars.Contains(ch) ? Convert.ToChar(invalidFileNameChars.IndexOf(ch) + 65) : ch).ToArray());
28
Squirrel

Je suis d'accord avec Grauenwolf et recommande vivement le Path.GetInvalidFileNameChars()

Voici ma contribution en C #:

string file = @"38?/.\}[+=n a882 a.a*/|n^%$ ad#(-))";
Array.ForEach(Path.GetInvalidFileNameChars(), 
      c => file = file.Replace(c.ToString(), String.Empty));

p.s. - C’est plus mystérieux qu’il devrait être - j’essayais d’être concis.

20
Aaron Wagner

Voici ma version:

static string GetSafeFileName(string name, char replace = '_') {
  char[] invalids = Path.GetInvalidFileNameChars();
  return new string(name.Select(c => invalids.Contains(c) ? replace : c).ToArray());
}

Je ne suis pas sûr de savoir comment le résultat de GetInvalidFileNameChars est calculé, mais le mot-clé "Get" suggère que ce n'est pas trivial, alors je cache les résultats. En outre, cela ne fait que traverser la chaîne d'entrée une fois au lieu de plusieurs fois, comme les solutions ci-dessus qui parcourent l'ensemble des caractères non valides, en les remplaçant dans la chaîne source un à la fois. De plus, j'aime bien les solutions basées sur Où, mais je préfère remplacer les caractères non valides au lieu de les supprimer. Enfin, mon remplaçant est exactement un caractère pour éviter de convertir des caractères en chaînes car j'itère sur la chaîne.

Je dis tout cela sans faire le profilage - celui-ci vient de "se sentir" gentil avec moi. :)

12
csells

Voici la fonction que j'utilise maintenant (merci jcollum pour l'exemple en C #):

public static string MakeSafeFilename(string filename, char replaceChar)
{
    foreach (char c in System.IO.Path.GetInvalidFileNameChars())
    {
        filename = filename.Replace(c, replaceChar);
    }
    return filename;
}

Je viens de mettre cela dans une classe "Helpers" pour plus de commodité.

10
sidewinderguy

Si vous voulez supprimer rapidement tous les caractères spéciaux, ce qui est parfois plus lisible par l'utilisateur pour les noms de fichiers, cela fonctionne bien:

string myCrazyName = "q`w^e!r@t#y$u%i^o&p*a(s)d_f-g+h=j{k}l|z:x\"c<v>b?n[m]q\\w;e'r,t.y/u";
string safeName = Regex.Replace(
    myCrazyName,
    "\W",  /*Matches any nonword character. Equivalent to '[^A-Za-z0-9_]'*/
    "",
    RegexOptions.IgnoreCase);
// safeName == "qwertyuiopasd_fghjklzxcvbnmqwertyu"
6
Keith
static class Utils
{
    public static string MakeFileSystemSafe(this string s)
    {
        return new string(s.Where(IsFileSystemSafe).ToArray());
    }

    public static bool IsFileSystemSafe(char c)
    {
        return !Path.GetInvalidFileNameChars().Contains(c);
    }
}
5
Ronnie Overby

Voici ce que je viens d'ajouter à la classe statique StringExtensions (projet Utils.Silverlight) de ClipFlair ( http://github.com/Zoomicon/ClipFlair ), en fonction des informations recueillies à partir des liens renvoyant aux questions relatives à la pile de flux insérées par Dour High Arch au dessus de:

public static string ReplaceInvalidFileNameChars(this string s, string replacement = "")
{
  return Regex.Replace(s,
    "[" + Regex.Escape(new String(System.IO.Path.GetInvalidPathChars())) + "]",
    replacement, //can even use a replacement string of any length
    RegexOptions.IgnoreCase);
    //not using System.IO.Path.InvalidPathChars (deprecated insecure API)
}
5
George Birbilis

Pourquoi ne pas convertir la chaîne en un équivalent Base64 comme ceci:

string UnsafeFileName = "salmnas dlajhdla kjha;dmas'lkasn";
string SafeFileName = Convert.ToBase64String(Encoding.UTF8.GetBytes(UnsafeFileName));

Si vous voulez le reconvertir pour pouvoir le lire:

UnsafeFileName = Encoding.UTF8.GetString(Convert.FromBase64String(SafeFileName));

J'ai utilisé cela pour enregistrer des fichiers PNG avec un nom unique à partir d'une description aléatoire.

4
Bart Vanseer
private void textBoxFileName_KeyPress(object sender, KeyPressEventArgs e)
{
   e.Handled = CheckFileNameSafeCharacters(e);
}

/// <summary>
/// This is a good function for making sure that a user who is naming a file uses proper characters
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
internal static bool CheckFileNameSafeCharacters(System.Windows.Forms.KeyPressEventArgs e)
{
    if (e.KeyChar.Equals(24) || 
        e.KeyChar.Equals(3) || 
        e.KeyChar.Equals(22) || 
        e.KeyChar.Equals(26) || 
        e.KeyChar.Equals(25))//Control-X, C, V, Z and Y
            return false;
    if (e.KeyChar.Equals('\b'))//backspace
        return false;

    char[] charArray = Path.GetInvalidFileNameChars();
    if (charArray.Contains(e.KeyChar))
       return true;//Stop the character from being entered into the control since it is non-numerical
    else
        return false;            
}
2
ecklerpa

Je trouve que cela est rapide et facile à comprendre:

<Extension()>
Public Function MakeSafeFileName(FileName As String) As String
    Return FileName.Where(Function(x) Not IO.Path.GetInvalidFileNameChars.Contains(x)).ToArray
End Function

Cela fonctionne parce que string est IEnumerable en tant que tableau char et qu'il existe une chaîne de constructeur string qui utilise un tableau char.

1
cjbarth

Beaucoup de personnes suggèrent d’utiliser Path.GetInvalidFileNameChars() qui me semble une mauvaise solution. Je vous encourage à utiliser la liste blanche au lieu de la liste noire, car les pirates informatiques trouveront toujours le moyen de le contourner.

Voici un exemple de code que vous pourriez utiliser:

    string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
    foreach (char c in filename)
    {
        if (!whitelist.Contains(c))
        {
            filename = filename.Replace(c, '-');
        }
    }
0
AnonBird