c#のIPAddressクラスでIPv4のフォーマット検証はできない話

c#にはIPAddressクラスがあります。プリニティブ型と同様にParse, TryParseメソッドが用意されています。しかし、これをIPv4のフォーマットの検証に使うと思わぬ落とし穴にはまることになります。
文字列を4つの1バイト数値と3つのピリオドで成る文字列かどうか判断したいときにTryParseを使うとします。以下式は変換が正常に完了します。

public IPAddress Validate(string ipString) // ipString="128.0.0.1"
{
    IPAddress address;
    if(IPAddress.TryParse(ipString, out address)) {
        return null;
    }
    return address;
}

しかし以下の式でも、予想に反して変換が完了してIPAddress型が取得できます。

public IPAddress Validate(string ipString) // ipString="65536"
{
    IPAddress address;
    if(IPAddress.TryParse(ipString, out address)) {
        return null;
    }
    return address; // 0.0.255.255 と解釈される
}

これはどういうことかというとMSDNのParseのページに書いてありますが、

静的 Parse メソッドは、ピリオド区切りの 10 進表記 (IPv4 の場合) またはコロン区切りの 16 進表記 (IPv6 の場合) で表された IP アドレスから IPAddress のインスタンスを作成します。
IP アドレスの構築方法は、ipString 内のピリオドで区切られた部分の数によって決まります。1 つの部分から成るアドレスは、ネットワーク アドレスに直接格納されます。クラス A のアドレスを指定する場合に便利な 2 つの部分から成るアドレスでは、先頭の部分がネットワーク アドレスの第 1 バイト、末尾の部分が右端の 3 バイトに配置されます。クラス B のアドレスを指定する場合に便利な 3 つの部分から成るアドレスでは、先頭の部分がネットワーク アドレスの第 1 バイト、2 番目の部分が第 2 バイト、最後の部分が右端の 2 バイトに配置されます。 たとえば、

部分の数と ipString の例 IPAddress の IPv4 アドレス
1 -- "65536" 0.0.255.255
2 -- "20.2" 20.0.0.2
2 -- "20.65535" 20.0.255.255
3 -- "128.1.2" 128.1.0.2

どうやら

  • ピリオドが足りない場合先頭、後方に詰める。
  • 1バイト以上は後ろから16進数に解釈される。

となるようです。

なので、ユーザーが入力した文字列のチェックにIPAddress.Parseを使うと全然使えないということになります。なので、"xxx.xxx.xxx.xxx"を厳密に期待している場合、正規表現と数値の範囲を行うしかりません。形式を厳密にチェックしようとするとこんな感じですかね?

public bool CheckIpString(string str) {

    string str = "255.255.255.256";

    if (string.IsNullOrEmpty(str)) {
        throw new ArgumentException("str is null or empty.");
    }

    if (str.Length < 7 || str.Length > 15) {
        throw new FormatException("str is illegal fortmat (" + str + ")"); 
    }

    Match m = Regex.Match(str, @"^(\d+)\.(\d+)\.(\d+)\.(\d+)$");
    if (m.Success) {
        for(int i = 1; i < 5; i++) {
            if (!this..isInByteRange(m.Groups[i].Value)) {
                throw new FormatException("str is illegal fortmat (" + str + ")"); 
            }
        }
    }
    return true;
}

// 0 ~ 255 の範囲内かどうかチェックする
private static bool isInByteRange(string block) {
    byte result;
    return byte.TryParse(block, out result);
}

相当めんどくさいですね。