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

StackOverflow https://stackoverflow.com/questions/62771

  •  09-06-2019
  •  | 
  •  

Question

Je souhaite inclure une fonctionnalité de renommage de fichier de traitement par lots dans mon application. Un utilisateur peut taper un motif de nom de fichier de destination et (après avoir remplacé certains caractères génériques dans le motif), je dois vérifier si ce sera 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 de différentes langues (par exemple, des trémas, etc.). Quelle est la meilleure façon de faire une telle vérification?

Était-ce utile?

La solution

Vous pouvez obtenir une liste des caractères non valides auprès de < code> Path.GetInvalidPathChars et GetInvalidFileNameChars .

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

UPD2: Notez que, conformément à la section Remarques de MSDN, "Le tableau renvoyé par cette méthode ne contient pas nécessairement le jeu complet de caractères non valides dans les noms de fichier et de répertoire." La réponse fournie par sixlettervaliables va dans plus de détails.

Autres conseils

De MSDN & Nommer un fichier ou 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), à l'exception de:

  • < > : " / \ | ? *
  • Caractères dont la représentation entière est comprise entre 0 et 31 (inférieur à l'espace 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, LPT2, LPT3, LPT4, LPT5, LPT6 , LPT7, LPT8, LPT9 (et éviter le fichier AUX.txt, etc.)
  • Le nom du fichier est composé de 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 d'accès aux fichiers Unicode (y compris le nom de fichier) comportant 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)

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

La correspondance d’expressions régulières 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 les frameworks .Net 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’expressions régulières devrait vous aider. Voici un extrait utilisant la System.IO.Path.GetInvalidPathChars () constant;

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: \ mon \ lecteur et \\ serveur \ partage \ dir \ fichier.ext

Essayez de l'utiliser, et piègez 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.

Cette classe nettoie les noms de fichiers et les chemins; utilisez-le 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();
    }

}

Voici 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\\?*:\"";|/]+<*>quot;;
        if (platformIndependent)
        {
           sPattern = @"^(([a-zA-Z]:|\\)\\)?(((\.)|(\.\.)|([^\\/:\*\?""\|<>\. ](([^\\/:\*\?""\|<>\. ])|([^\\/:\*\?""\|<>]*[^\\/:\*\?""\|<>\. ]))?))\\)*[^\\/:\*\?""\|<>\. ](([^\\/:\*\?""\|<>\. ])|([^\\/:\*\?""\|<>]*[^\\/:\*\?""\|<>\. ]))?<*>quot;;
        }
        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 / illégal et les caractères pour les plates-formes Windows uniquement. Le second fait la même chose, mais garantit que le nom est légal pour toutes les plateformes.

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 de fichiers légaux et distincts sous Windows (sans les guillemets) sont légaux et distincts:

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

Une conclusion à tirer de ceci: faites attention lorsque vous écrivez du code qui supprime les espaces de début / de fin d'une chaîne de nom de fichier.

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

Microsoft Windows: le noyau Windows interdit l'utilisation de caractères compris entre 1 et 31 (c'est-à-dire, 0x01-0x1F) et de caractères " *: < > ? \ |. Bien que NTFS permette à chaque composant de chemin (répertoire ou nom de fichier) de comporter 255 caractères et jusqu'à environ 32 767 caractères, le noyau Windows ne prend en charge que les chemins allant jusqu'à 259 caractères. En outre, Windows interdit l'utilisation des noms de périphériques MS-DOS AUX, CLOCK $, COM1, COM2, COM3, COM4, ??COM5, COM6, COM7, COM8, COM9, CON, LPT1, 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 de chemins UNC longs (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, autorise l’utilisation de " *: < > ? \ | même en NTFS.

Source: http://en.wikipedia.org/wiki/Filename

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.

J'utilise ceci pour supprimer les caractères non valides dans les noms de fichiers sans générer 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);
}

De plus, CON, PRN, AUX, NUL, COM # et quelques autres ne sont jamais des noms de fichiers légaux dans les répertoires avec les extensions.

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 sur lequel 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 créer le fichier.

Ainsi, vous saurez non seulement si le chemin d'accès contient uniquement des caractères Windows valides, mais s'il représente en fait un chemin pouvant être écrit par ce processus.

Pour compléter les autres réponses, voici quelques exemples de cas supplémentaires à prendre en compte.

De MSDN , voici une liste de caractères qui ne correspondent pas. t autorisé:

  
    
      
        

Utilisez un nom pour presque tous les caractères de la page de code actuelle, 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.
  •         
      
    
  

Le système de fichiers de destination est également important.

Sous NTFS, certains fichiers ne peuvent pas être créés dans des répertoires spécifiques. PAR EXEMPLE. $ Démarrage en racine

C’est une question à laquelle on a déjà répondu, mais pour l’intérêt de "Autres options", voici une question non idéale:

(non idéal car utiliser Exceptions comme contrôle de flux est un "problème" en général)

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

Les expressions régulières sont excessives dans 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.

beaucoup de ces réponses ne fonctionneront pas si le nom du fichier est trop long & amp; s'exécutant sur un environnement pré-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 voulez 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"));
}

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 rompre le fichier. nommer dans un tableau de pièces partout où il y a un caractère invalide. 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 d'accès 1 000 000 fois dans LinqPad.

Utiliser Split () ne représente que ~ 850 ms.

Utilisation de Regex (" [" + Regex.Escape (nouvelle chaîne (System.IO.Path.GetInvalidPathChars ()) +) "]) est d'environ 6 secondes.

Les expressions rationnelles les plus compliquées sont BEAUCOUP pire, à l'instar de 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 faire le travail probablement en raison des frais généraux liés à la gestion des exceptions).

Certes, il n'est pas très souvent nécessaire de valider un million de noms de fichiers. Une seule itération convient toutefois pour la plupart de ces méthodes. Mais c'est quand même assez efficace si vous ne recherchez que des caractères non valides.

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

Cela n'est pas parfait car Path.GetInvalidPathChars ne renvoie pas l'ensemble des caractères non valides dans les noms de fichiers et de répertoires et, bien entendu, de nombreuses 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é.

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

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

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

Ce chèque

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

filtre les noms contenant 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 points tout en cohérence avec Path.GetFullPath . (La création d’un fichier avec des espaces de début a réussi sur mon système).

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

Une ligne 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)) + "]");

Les noms de fichiers Windows ne sont pas très restrictifs, il est donc possible que ce ne soit pas aussi un problème. Les caractères non autorisés par Windows sont:

\ / : * ? " < > |

Vous pouvez facilement écrire une expression pour vérifier si ces caractères sont présents. Une meilleure solution serait d’essayer de nommer les fichiers comme le souhaite l’utilisateur et de les alerter en cas de blocage du nom de fichier.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top