Existe-t-il un moyen de sécuriser le chemin du fichier des chaînes en c #?


Réponses:


171

Ugh, je déteste quand les gens essaient de deviner quels caractères sont valides. En plus d'être complètement non portables (en pensant toujours à Mono), les deux commentaires précédents ont manqué 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

83
La version C #: foreach (var c dans Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
jcollum

8
Comment cette solution gérerait-elle les conflits de noms? Il semble que plus d'une chaîne puisse correspondre à un seul nom de fichier ("Hell?" Et "Hell *" par exemple). Si vous êtes d'accord pour supprimer uniquement les caractères incriminés, très bien; sinon, vous devez faire attention à gérer les conflits de noms.
Stefano Ricciardi

2
qu'en est-il des limites de longueur du nom (et du chemin) du système de fichiers? qu'en est-il des noms de fichiers réservés (PRN CON)? Si vous avez besoin de stocker les données et le nom d'origine, vous pouvez utiliser 2 fichiers avec des noms Guid: guid.txt et guid.dat
Jack

6
Une doublure, pour le plaisir result = Path.GetInvalidFileNameChars (). Aggregate (result, (current, c) => current.Replace (c, '-'));
Paul Knopf

1
@PaulKnopf, êtes-vous sûr que JetBrain n'a pas de droits d'auteur sur ce code;)
Marcus

36

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 des 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 invalides (et éviter les conflits de noms potentiels comme 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());

33

Cette question a été posée plusieurs fois auparavant et, comme cela a déjà été souligné à maintes reprises,IO.Path.GetInvalidFileNameChars ne suffit pas.

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 un point ne sont pas non plus autorisés.

Deuxièmement, il existe diverses limitations de longueur. Lisez la liste complète pour NTFS ici .

Troisièmement, vous pouvez vous attacher à des systèmes de fichiers qui ont 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, l'utilisation de noms générés en 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 par l'homme en interne.


13
Bien que vous soyez techniquement précis, GetInvalidFileNameChars convient à plus de 80% des situations dans lesquelles vous l'utiliserez, c'est donc une bonne réponse. Votre réponse aurait été plus appropriée comme commentaire à la réponse acceptée, je pense.
CubanX

4
Je suis d'accord avec DourHighArch. Enregistrez le fichier en interne comme guide, référencez-le par rapport au "nom convivial" qui est stocké dans une base de données. Ne laissez pas les utilisateurs contrôler vos chemins sur le site Web ou ils essaieront de voler votre web.config. Si vous incorporez la réécriture d'url pour la rendre propre, cela ne fonctionnera que pour les URL conviviales correspondantes dans la base de données.
rtpHarry

22

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

Voici ma contribution C #:

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

ps - c'est plus cryptique qu'il ne devrait l'être - j'essayais d'être concis.


3
Pourquoi Array.ForEachforeach
diable

9
Si vous vouliez être encore plus concis / cryptique:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito

@ BlueRaja-DannyPflughoeft Parce que vous voulez ralentir?
Jonathan Allen

@Johnathan Allen, qu'est-ce qui vous fait penser que foreach est plus rapide que Array.ForEach?
Ryan Buddicom

5
@rbuddicom Array.ForEach accepte un délégué, ce qui signifie qu'il doit appeler une fonction qui ne peut pas être insérée. Pour les chaînes courtes, vous pourriez finir par passer plus de temps sur la surcharge des appels de fonction que sur la logique réelle. .NET Core cherche des moyens de «dé-virtualiser» les appels, réduisant ainsi les frais généraux.
Jonathan Allen

13

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 sais pas comment le résultat de GetInvalidFileNameChars est calculé, mais le "Get" suggère qu'il n'est pas trivial, donc je cache les résultats. En outre, cela ne parcourt la chaîne d'entrée qu'une seule fois au lieu de plusieurs fois, comme les solutions ci-dessus qui itèrent sur l'ensemble des caractères non valides, en les remplaçant dans la chaîne source un par un. Aussi, j'aime les solutions basées sur Where, mais je préfère remplacer les caractères invalides au lieu de les supprimer. Enfin, mon remplacement est exactement un caractère pour éviter de convertir des caractères en chaînes lorsque j'itère sur la chaîne.

Je dis tout cela sans faire le profilage - celui-ci "me sentait" juste. :)


1
Vous pouvez new HashSet<char>(Path.GetInvalidFileNameChars())éviter l'énumération O (n) - micro-optimisation.
TrueWill

12

Voici la fonction que j'utilise maintenant (merci jcollum pour l'exemple 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é.


7

Si vous souhaitez 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"

1
\Wcorrespond en fait à plus que les non-alphanumériques ( [^A-Za-z0-9_]). Tous les caractères Unicode 'mot' (русский 中文 ..., etc.) ne seront pas non plus remplacés. Mais c'est une bonne chose.
Ishmael

Le seul inconvénient est que cela supprime également ., vous devez d'abord extraire l'extension, puis l'ajouter à nouveau après.
awe le

5
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

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 souhaitez le reconvertir pour pouvoir le lire:

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

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


5

Voici ce que je viens d'ajouter à la classe statique StringExtensions de ClipFlair ( http://github.com/Zoomicon/ClipFlair ) (projet Utils.Silverlight), sur la base des informations recueillies à partir des liens vers les questions liées au stackoverflow publiées par Dour High Arch ci-dessus:

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)
}

2
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;            
}

1

Je trouve que l'utilisation de ceci 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 car a stringest en IEnumerabletant que chartableau et qu'il existe une stringchaîne de constructeur qui prend un chartableau.


1

De mes projets plus anciens, j'ai trouvé cette solution qui fonctionne parfaitement depuis 2 ans. Je remplace les caractères illégaux par "!", Puis vérifie les doubles !!, utilise votre propre caractère.

    public string GetSafeFilename(string filename)
    {
        string res = string.Join("!", filename.Split(Path.GetInvalidFileNameChars()));

        while (res.IndexOf("!!") >= 0)
            res = res.Replace("!!", "!");

        return res;
    }

0

Beaucoup de réponses suggèrent d'utiliser Path.GetInvalidFileNameChars()ce qui me semble être une mauvaise solution. Je vous encourage à utiliser la liste blanche au lieu de la liste noire, car les pirates trouveront toujours un moyen de la 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, '-');
        }
    }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.