web-dev-qa-db-fra.com

Comment vérifier si une chaîne donnée est un nom de fichier légal/valide sous Windows?

Je souhaite inclure une fonctionnalité de changement de nom de fichier batch dans mon application. Un utilisateur peut taper un modèle de nom de fichier de destination et (après avoir remplacé certains caractères génériques dans le modèle), je dois vérifier s'il s'agit d'un nom de fichier légal sous Windows. J'ai essayé d'utiliser une expression régulière telle que [a-zA-Z0-9_]+, mais celle-ci n'inclut pas de nombreux caractères nationaux spécifiques provenant de différentes langues (par exemple, des trémas, etc.). Quelle est la meilleure façon de faire une telle vérification?

150
tomash

Vous pouvez obtenir une liste des caractères non valides à partir de Path.GetInvalidPathChars et GetInvalidFileNameChars .

UPD: Voir La suggestion de Steve Cooper sur la manière de les utiliser dans une expression régulière.

UPD2: Notez que, selon la section Remarks de MSDN, "le tableau renvoyé par cette méthode ne contient pas nécessairement l'ensemble des caractères non valides dans les noms de fichier et de répertoire." La réponse fournie par sixlettervaliables entre dans plus de détails.

96
Eugene Katz

De MSDN "Nommer un fichier ou un répertoire" , voici les conventions générales concernant le nom de fichier légal sous Windows:

Vous pouvez utiliser n'importe quel caractère de la page de code actuelle (Unicode/ANSI supérieur à 127), sauf:

  • <>:"/\|?*
  • Caractères dont les représentations entières sont comprises entre 0 et 31 (espace inférieur à ASCII)
  • Tout autre caractère que le système de fichiers cible n'autorise pas (par exemple, points ou espaces de fin)
  • N'importe lequel des noms DOS: CON, PRN, AUX, NUL, COM0, COM1, COM2, COM3, COM4, ​​COM5, COM6, COM7, COM8, COM9, LPT0, LPT1, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9 (et éviter le fichier AUX.txt, etc.)
  • Le nom du fichier est toutes les périodes

Quelques éléments facultatifs à vérifier:

  • Les chemins de fichiers (y compris le nom du fichier) ne doivent pas comporter plus de 260 caractères (n'utilisant pas le préfixe \?\)
  • Chemins de fichiers Unicode (y compris le nom du fichier) contenant plus de 32 000 caractères lors de l'utilisation de \?\ (notez que le préfixe peut développer des composants de répertoire et provoquer le dépassement de la limite de 32 000)
116
user7116

Pour les cadres .Net antérieurs à 3.5, cela devrait fonctionner:

La correspondance d'expression régulière devrait vous aider. Voici un extrait utilisant la constante System.IO.Path.InvalidPathChars;

bool IsValidFilename(string testName)
{
    Regex containsABadCharacter = new Regex("[" 
          + Regex.Escape(System.IO.Path.InvalidPathChars) + "]");
    if (containsABadCharacter.IsMatch(testName)) { return false; };

    // other checks for UNC, drive-path format, etc

    return true;
}

Pour .Net Frameworks après 3.0, cela devrait fonctionner:

http://msdn.Microsoft.com/en-us/library/system.io.path.getinvalidpathchars(v=vs.90).aspx

La correspondance d'expression régulière devrait vous aider. Voici un extrait utilisant la constante System.IO.Path.GetInvalidPathChars();

bool IsValidFilename(string testName)
{
    Regex containsABadCharacter = new Regex("["
          + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]");
    if (containsABadCharacter.IsMatch(testName)) { return false; };

    // other checks for UNC, drive-path format, etc

    return true;
}

Une fois que vous savez cela, vous devriez également vérifier différents formats, par exemple c:\my\drive et \\server\share\dir\file.ext.

62
Steve Cooper

Essayez de l'utiliser et piéger pour l'erreur. L'ensemble autorisé peut changer d'un système de fichiers à l'autre ou d'une version à l'autre de Windows. En d'autres termes, si vous voulez savoir si Windows aime le nom, remettez-le-lui et laissez-le vous dire.

25
Dewayne Christensen

Cette classe nettoie les noms de fichiers et les chemins; l'utiliser comme 

var myCleanPath = PathSanitizer.SanitizeFilename(myBadPath, ' ');

Voici le code;

/// <summary>
/// Cleans paths of invalid characters.
/// </summary>
public static class PathSanitizer
{
    /// <summary>
    /// The set of invalid filename characters, kept sorted for fast binary search
    /// </summary>
    private readonly static char[] invalidFilenameChars;
    /// <summary>
    /// The set of invalid path characters, kept sorted for fast binary search
    /// </summary>
    private readonly static char[] invalidPathChars;

    static PathSanitizer()
    {
        // set up the two arrays -- sorted once for speed.
        invalidFilenameChars = System.IO.Path.GetInvalidFileNameChars();
        invalidPathChars = System.IO.Path.GetInvalidPathChars();
        Array.Sort(invalidFilenameChars);
        Array.Sort(invalidPathChars);

    }

    /// <summary>
    /// Cleans a filename of invalid characters
    /// </summary>
    /// <param name="input">the string to clean</param>
    /// <param name="errorChar">the character which replaces bad characters</param>
    /// <returns></returns>
    public static string SanitizeFilename(string input, char errorChar)
    {
        return Sanitize(input, invalidFilenameChars, errorChar);
    }

    /// <summary>
    /// Cleans a path of invalid characters
    /// </summary>
    /// <param name="input">the string to clean</param>
    /// <param name="errorChar">the character which replaces bad characters</param>
    /// <returns></returns>
    public static string SanitizePath(string input, char errorChar)
    {
        return Sanitize(input, invalidPathChars, errorChar);
    }

    /// <summary>
    /// Cleans a string of invalid characters.
    /// </summary>
    /// <param name="input"></param>
    /// <param name="invalidChars"></param>
    /// <param name="errorChar"></param>
    /// <returns></returns>
    private static string Sanitize(string input, char[] invalidChars, char errorChar)
    {
        // null always sanitizes to null
        if (input == null) { return null; }
        StringBuilder result = new StringBuilder();
        foreach (var characterToTest in input)
        {
            // we binary search for the character in the invalid set. This should be lightning fast.
            if (Array.BinarySearch(invalidChars, characterToTest) >= 0)
            {
                // we found the character in the array of 
                result.Append(errorChar);
            }
            else
            {
                // the character was not found in invalid, so it is valid.
                result.Append(characterToTest);
            }
        }

        // we're done.
        return result.ToString();
    }

}
23
Steve Cooper

C'est ce que j'utilise:

    public static bool IsValidFileName(this string expression, bool platformIndependent)
    {
        string sPattern = @"^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\"";|/]+$";
        if (platformIndependent)
        {
           sPattern = @"^(([a-zA-Z]:|\\)\\)?(((\.)|(\.\.)|([^\\/:\*\?""\|<>\. ](([^\\/:\*\?""\|<>\. ])|([^\\/:\*\?""\|<>]*[^\\/:\*\?""\|<>\. ]))?))\\)*[^\\/:\*\?""\|<>\. ](([^\\/:\*\?""\|<>\. ])|([^\\/:\*\?""\|<>]*[^\\/:\*\?""\|<>\. ]))?$";
        }
        return (Regex.IsMatch(expression, sPattern, RegexOptions.CultureInvariant));
    }

Le premier modèle crée une expression régulière contenant les noms de fichier non valide/non valide et les caractères uniquement pour les plates-formes Windows. Le second fait la même chose mais garantit que le nom est légal pour toutes les plateformes.

22
Scott Dorman

Un cas à garder à l'esprit, ce qui m'a surpris lorsque je l'ai découvert: Windows autorise les espaces en majuscules dans les noms de fichiers! Par exemple, les noms suivants sont tous légaux et distincts sur Windows (moins les guillemets):

"file.txt"
" file.txt"
"  file.txt"

À retenir: faites attention lorsque vous écrivez du code qui élimine les espaces de début/de fin à partir d'une chaîne de nom de fichier.

18
Jon Schneider

Simplifier la réponse d'Eugene Katz:

bool IsFileNameCorrect(string fileName){
    return !fileName.Any(f=>Path.GetInvalidFileNameChars().Contains(f))
}

Ou

bool IsFileNameCorrect(string fileName){
    return fileName.All(f=>!Path.GetInvalidFileNameChars().Contains(f))
}
8
tmt

Microsoft Windows: le noyau Windows interdit l'utilisation de caractères compris entre 1 et 31 (0x01-0x1F) et de caractères "*: <>?\|. Bien que NTFS autorise chaque composant de chemin d'accès (répertoire ou nom de fichier) à 255 caractères Les noyaux Windows ne prennent en charge que les noms de périphériques MS-DOS suivants: AUX, CLOCK $, COM1, COM2, COM3, COM4, ​​COM5, COM6, le noyau Windows ne prend en charge que les chemins d'accès. COM7, COM8, COM9, CON, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9, NUL et PRN, ainsi que ces noms avec toute extension (par exemple, AUX.txt), sauf lors de l'utilisation Longs chemins UNC (ex. \.\C:\nul.txt ou \?\D:\aux\con). (En fait, CLOCK $ peut être utilisé si une extension est fournie.) Ces restrictions s’appliquent uniquement à Windows - Linux, par exemple, permet d'utiliser "*: <>?\| même en NTFS.

Source: http://fr.wikipedia.org/wiki/Nom de fichier

8
Martin Faartoft

Plutôt que d'inclure explicitement tous les caractères possibles, vous pouvez utiliser une expression régulière pour vérifier la présence de caractères illégaux et signaler une erreur. Idéalement, votre application doit nommer les fichiers exactement comme l'utilisateur le souhaite et ne crier qu'aux fautes si une erreur se produit.

7
ConroyP

J'utilise ceci pour me débarrasser des caractères non valides dans les noms de fichiers sans lever d'exceptions:

private static readonly Regex InvalidFileRegex = new Regex(
    string.Format("[{0}]", Regex.Escape(@"<>:""/\|?*")));

public static string SanitizeFileName(string fileName)
{
    return InvalidFileRegex.Replace(fileName, string.Empty);
}
6
JoelFan

De plus, CON, PRN, AUX, NUL, COM # et quelques autres ne sont jamais des noms de fichiers légaux dans aucun répertoire avec aucune extension.

5
Roland Rabien

La question est de savoir si vous essayez de déterminer si un nom de chemin est un chemin de fenêtre légal ou s'il est légal sur le système où le code est exécuté. ? Je pense que ce dernier point est plus important, alors personnellement, je décomposerais probablement le chemin complet et essaierais d’utiliser _mkdir pour créer le répertoire auquel le fichier appartient, puis de le créer.

De cette façon, vous savez non seulement si le chemin contient uniquement des caractères Windows valides, mais également s'il représente un chemin pouvant être écrit par ce processus.

5
kfh

Pour compléter les autres réponses, voici quelques exemples de cas Edge supplémentaires que vous pouvez envisager.

4
Joe

De MSDN , voici une liste de caractères non autorisés:

Utilisez presque tous les caractères de la page de codes actuelle pour un nom, y compris les caractères Unicode et les caractères du jeu de caractères étendu (128 à 255), à l'exception des suivants:

  • Les caractères réservés suivants ne sont pas autorisés: <>: "/\|? *
  • Les caractères dont les représentations entières sont comprises entre zéro et 31 ne sont pas autorisés.
  • Tout autre caractère que le système de fichiers cible n'autorise pas.
3
Mark Biek

Les expressions régulières sont excessives pour cette situation. Vous pouvez utiliser la méthode String.IndexOfAny() en combinaison avec Path.GetInvalidPathChars() et Path.GetInvalidFileNameChars().

Notez également que les deux méthodes Path.GetInvalidXXX() clonent un tableau interne et renvoient le clone. Donc, si vous faites cela souvent (des milliers et des milliers de fois), vous pouvez mettre en cache une copie du tableau de caractères non valides pour la réutiliser.

2
s n

De plus, le système de fichiers de destination est important.

Sous NTFS, certains fichiers ne peuvent pas être créés dans des répertoires spécifiques . E.G. $ Boot en racine 

2
Dominik Weber

C’est une question à laquelle on a déjà répondu, mais pour les raisons suivantes, voici une question qui n’est pas idéale:

(non idéal car utiliser Exceptions comme contrôle de flux est une "mauvaise chose", en général)

public static bool IsLegalFilename(string name)
{
    try 
    {
        var fileInfo = new FileInfo(name);
        return true;
    }
    catch
    {
        return false;
    }
}
2
JerKimball

Si vous essayez uniquement de vérifier si une chaîne contenant votre nom de fichier/chemin contient des caractères non valides, la méthode la plus rapide que j'ai trouvée consiste à utiliser Split() pour scinder le nom du fichier en un tableau de pièces contenant un caractère non valide. Si le résultat est uniquement un tableau de 1, il n'y a pas de caractères non valides. :-)

var nameToTest = "Best file name \"ever\".txt";
bool isInvalidName = nameToTest.Split(System.IO.Path.GetInvalidFileNameChars()).Length > 1;

var pathToTest = "C:\\My Folder <secrets>\\";
bool isInvalidPath = pathToTest.Split(System.IO.Path.GetInvalidPathChars()).Length > 1;

J'ai essayé d'exécuter ceci et d'autres méthodes mentionnées ci-dessus sur un nom de fichier/chemin 1 000 000 de fois dans LinqPad.

Utiliser Split() n’est que ~ 850ms.

Utiliser Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]") dure environ 6 secondes.

Les expressions rationnelles les plus compliquées sont BEAUCOUP moins bonnes, comme certaines des autres options, telles que l’utilisation des différentes méthodes de la classe Path pour obtenir le nom du fichier et laisser sa validation interne s’acquitter de ses tâches (probablement en raison de la surcharge de traitement des exceptions).

Certes, il est rare que vous ayez besoin de valider un million de noms de fichiers. Par conséquent, une seule itération convient à la plupart de ces méthodes. Mais c'est quand même assez efficace si vous ne recherchez que des personnages non valides.

1
Nick Albrecht

bon nombre de ces réponses ne fonctionneront pas si le nom du fichier est trop long et s'exécute dans un environnement antérieur à Windows 10. De même, réfléchissez à ce que vous voulez faire avec les points - autoriser le début ou la fin est valide sur le plan technique, mais peut créer des problèmes si vous ne souhaitez pas que le fichier soit difficile à voir ou à supprimer, respectivement.

C'est un attribut de validation que j'ai créé pour rechercher un nom de fichier valide. 

public class ValidFileNameAttribute : ValidationAttribute
{
    public ValidFileNameAttribute()
    {
        RequireExtension = true;
        ErrorMessage = "{0} is an Invalid Filename";
        MaxLength = 255; //superseeded in modern windows environments
    }
    public override bool IsValid(object value)
    {
        //http://stackoverflow.com/questions/422090/in-c-sharp-check-that-filename-is-possibly-valid-not-that-it-exists
        var fileName = (string)value;
        if (string.IsNullOrEmpty(fileName)) { return true;  }
        if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 ||
            (!AllowHidden && fileName[0] == '.') ||
            fileName[fileName.Length - 1]== '.' ||
            fileName.Length > MaxLength)
        {
            return false;
        }
        string extension = Path.GetExtension(fileName);
        return (!RequireExtension || extension != string.Empty)
            && (ExtensionList==null || ExtensionList.Contains(extension));
    }
    private const string _sepChar = ",";
    private IEnumerable<string> ExtensionList { get; set; }
    public bool AllowHidden { get; set; }
    public bool RequireExtension { get; set; }
    public int MaxLength { get; set; }
    public string AllowedExtensions {
        get { return string.Join(_sepChar, ExtensionList); } 
        set {
            if (string.IsNullOrEmpty(value))
            { ExtensionList = null; }
            else {
                ExtensionList = value.Split(new char[] { _sepChar[0] })
                    .Select(s => s[0] == '.' ? s : ('.' + s))
                    .ToList();
            }
    } }

    public override bool RequiresValidationContext => false;
}

et les tests

[TestMethod]
public void TestFilenameAttribute()
{
    var rxa = new ValidFileNameAttribute();
    Assert.IsFalse(rxa.IsValid("pptx."));
    Assert.IsFalse(rxa.IsValid("pp.tx."));
    Assert.IsFalse(rxa.IsValid("."));
    Assert.IsFalse(rxa.IsValid(".pp.tx"));
    Assert.IsFalse(rxa.IsValid(".pptx"));
    Assert.IsFalse(rxa.IsValid("pptx"));
    Assert.IsFalse(rxa.IsValid("a/abc.pptx"));
    Assert.IsFalse(rxa.IsValid("a\\abc.pptx"));
    Assert.IsFalse(rxa.IsValid("c:abc.pptx"));
    Assert.IsFalse(rxa.IsValid("c<abc.pptx"));
    Assert.IsTrue(rxa.IsValid("abc.pptx"));
    rxa = new ValidFileNameAttribute { AllowedExtensions = ".pptx" };
    Assert.IsFalse(rxa.IsValid("abc.docx"));
    Assert.IsTrue(rxa.IsValid("abc.pptx"));
}
1
Brent

Ma tentative:

using System.IO;

static class PathUtils
{
  public static string IsValidFullPath([NotNull] string fullPath)
  {
    if (string.IsNullOrWhiteSpace(fullPath))
      return "Path is null, empty or white space.";

    bool pathContainsInvalidChars = fullPath.IndexOfAny(Path.GetInvalidPathChars()) != -1;
    if (pathContainsInvalidChars)
      return "Path contains invalid characters.";

    string fileName = Path.GetFileName(fullPath);
    if (fileName == "")
      return "Path must contain a file name.";

    bool fileNameContainsInvalidChars = fileName.IndexOfAny(Path.GetInvalidFileNameChars()) != -1;
    if (fileNameContainsInvalidChars)
      return "File name contains invalid characters.";

    if (!Path.IsPathRooted(fullPath))
      return "The path must be absolute.";

    return "";
  }
}

Ce n'est pas parfait car Path.GetInvalidPathChars ne renvoie pas le jeu complet de caractères non valides dans les noms de fichiers et de répertoires et, bien sûr, il y a beaucoup plus de subtilités.

J'utilise donc cette méthode en complément:

public static bool TestIfFileCanBeCreated([NotNull] string fullPath)
{
  if (string.IsNullOrWhiteSpace(fullPath))
    throw new ArgumentException("Value cannot be null or whitespace.", "fullPath");

  string directoryName = Path.GetDirectoryName(fullPath);
  if (directoryName != null) Directory.CreateDirectory(directoryName);
  try
  {
    using (new FileStream(fullPath, FileMode.CreateNew)) { }
    File.Delete(fullPath);
    return true;
  }
  catch (IOException)
  {
    return false;
  }
}

Il essaie de créer le fichier et renvoie false s'il y a une exception. Bien sûr, je dois créer le fichier, mais je pense que c'est le moyen le plus sûr de le faire. Veuillez également noter que je ne supprime pas les répertoires créés.

Vous pouvez également utiliser la première méthode pour effectuer une validation de base, puis gérer soigneusement les exceptions lorsque le chemin est utilisé.

1
Maxence

À mon avis, la seule réponse appropriée à cette question est d'essayer d'utiliser le chemin et de laisser le système d'exploitation et le système de fichiers le valider. Sinon, vous ne faites que réimplémenter (et probablement mal) toutes les règles de validation déjà utilisées par le système d’exploitation et le système de fichiers. Si ces règles sont modifiées à l’avenir, vous devrez modifier votre code pour les faire correspondre.

0
Igor Levicki

Ce chèque

static bool IsValidFileName(string name)
{
    return
        !string.IsNullOrWhiteSpace(name) &&
        name.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 &&
        !Path.GetFullPath(name).StartsWith(@"\\.\");
}

filtre les noms avec des caractères non valides (<>:"/\|?* et ASCII 0-31), ainsi que les périphériques DOS réservés (CON, NUL, COMx). Il permet les espaces de début et les noms de tous les points, compatibles avec Path.GetFullPath. (La création d'un fichier avec des espaces de début réussit sur mon système).


Utilisé .NET Framework 4.7.1, testé sur Windows 7.

0
Vlad

J'ai eu cette idée de quelqu'un. - Je ne sais pas qui. Laissez l'OS faire le gros du travail.

public bool IsPathFileNameGood(string fname)
{
    bool rc = Constants.Fail;
    try
    {
        this._stream = new StreamWriter(fname, true);
        rc = Constants.Pass;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Problem opening file");
        rc = Constants.Fail;
    }
    return rc;
}
0
KenR

Une doublure pour vérifier les caractères illicites dans la chaîne:

public static bool IsValidFilename(string testName) => !Regex.IsMatch(testName, "[" + Regex.Escape(new string(System.IO.Path.InvalidPathChars)) + "]");
0
Zananok

Je suggère simplement d'utiliser le Path.GetFullPath ()

string tagetFileFullNameToBeChecked;
try
{
  Path.GetFullPath(tagetFileFullNameToBeChecked)
}
catch(AugumentException ex)
{
  // invalid chars found
}
0
Tony Sun