2017年3月9日

[C#] ファイル名が正しいかどうかチェックする

ファイル名が正しいかどうかをチェックする方法を調べていたところ、Stack Overflowで以下のようなページが見つかりました。

これらの回答を基に、C#で正規表現を使ってファイル名が妥当かどうかをチェックする簡単なメソッドを作成してみました。まずまず動いているようなので安心です。

public static bool IsValidFileName(string fileName)
{
    var validFileNameRegex = new System.Text.RegularExpressions.Regex(
        "^(?!(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\\.[^.]*)?$)" +
        "[^<>:\"/\\\\|?*\\x00-\\x1F]*[^<>:\"/\\\\|?*\\x00-\\x1F\\ .]$",
        System.Text.RegularExpressions.RegexOptions.IgnoreCase);

    System.Text.RegularExpressions.Match regexMatch = validFileNameRegex.Match(fileName);

    return regexMatch.Success;
}

System.IO.Path.GetInvalidFileNameChars()メソッドはファイル名に使用できない文字を含む配列を返すので、以下のようなコードで、ファイル名に使用できない文字が含まれているかどうかを調べることができます。但し、この方法はシンプルではありますが、CONPRNAUXといったファイル名として使用できない文字列が含まれているかどうかを調べていないため、少々不完全です。

public static bool IsValidFileName(string fileName) => 
    fileName.IndexOfAny(System.IO.Path.GetInvalidFileNameChars()) < 0;

System.IO.FileInfoクラスのコンストラクタ―は絶対パスあるいは相対パスを1つ引数に取ります。コンストラクタが例外を投げなければファイル名が正しいと判断することができます。しかし、この方法でも、CONPRNAUXなどの本来禁止されているはずのファイル名が通ってしまうので、やはり不完全です。

public static bool IsValidFileName(string fileName)
{
    System.IO.FileInfo fileInfo = null;

    try {
        fileInfo = new System.IO.FileInfo(fileName);
    } catch (Exception e) when (
        e is ArgumentNullException || 
        e is ArgumentException || 
        e is System.IO.PathTooLongException || 
        e is NotSupportedException) {
    }

    return fileInfo != null;
}

Windows APIのCreateFile()関数をP/Invokeを使って呼び出す方法もあります (やはり、CONPRNAUXなどが通ってしまいます) まず最初に、CreateFile()関数をファイル名とともに呼び出して、ファイルハンドルを取得します。次に、.NET FrameworkのSystem.Runtime.InteropServices.Marshal.GetLastWin32Error()メソッドの戻り値が、ファイル名が正しくないことを示すエラーコードERROR_INVALID_NAME(123)であるかどうかをチェックします。

P/InvokeによりWindows APIのGetLastError()関数を呼び出すのは厳禁です。また、CreateFile()関数の、DllImport属性のSetLastErrorフィールドをtrueにする必要があります。

また、以下の例ではMicrosoft.Win32.SafeHandles.SafeFileHandleクラスを使用しているため、CreateFile()関数で有効なファイルハンドルが返された場合でも、CloseHandle()関数を呼んでハンドルを閉じる必要はありません。

using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

// ...

public const uint GENERIC_READ = 0x80000000;
public const uint OPEN_EXISTING = 3;
public const int ERROR_INVALID_NAME = 123;

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr CreateFile(
    string lpFileName, uint dwDesiredAccess,
    uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
    uint dwFlagsAndAttributes, IntPtr hTemplateFile);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);

public static bool IsValidFileName(string fileName)
{
    IntPtr hFile = CreateFile(fileName, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
    SafeFileHandle hFileSafe = new SafeFileHandle(hFile, true);
    return Marshal.GetLastWin32Error() != ERROR_INVALID_NAME;
}

一番良さそうなのは最初の正規表現による方法だと思います。この手の処理をやってくれるメソッドなりクラスなりを、早く標準の.NET Frameworkに追加して欲しいものです。

0 件のコメント:

コメントを投稿