ipアドレスが正しいかどうかを判定するには?


WAT  2008-01-07 18:16:26  No: 29185

標記を、外部dllを使わずに実現するにはどうすればよいのでしょうか。

なお、「正しい」とはIPv4ルールで正しく記述されているかどうかであり、当該ホストなりネットワークなりが存在しているかどうかは問いません。


ようするに...  2008-01-07 18:59:08  No: 29186

ipアドレスの文字列が
***.***.***.***
(↑***は 0〜255の数字文字列)
になってるかを判定したいってこと?


WAT  2008-01-07 21:08:04  No: 29187

はいそうです。
書き忘れましたが、delphi5 + indy10です。

いちいち「.」で分割してそれぞれのオクテットを検査しているのですが、

isSafeIP('192.168.0.256') → False

みたいな方法が無いのかなと探しています。


現状で出来ているのを関数にすれば  2008-01-07 22:29:52  No: 29188

いいだけだろ?


WinSock.pas  2008-01-07 23:04:37  No: 29189

inet_addr

こういうのでもいいのか?

・・・おっと、wsock32.dll を使うから、これもダメだな(笑)


inet_addrは  2008-01-07 23:58:49  No: 29190

inet_addr('192') としたときエラーにならないんだよ

別に .が3個あるか確認すればいいのか?

>標記を、外部dllを使わずに実現するにはどうすればよいのでしょうか。

APIは全滅じゃないか?


Fusa  URL  2008-01-08 00:41:14  No: 29191

こんな時こそ正規表現で....
と、自分がツカエもしないし好きでもない正規表現について言ってみました。

※Delphiの次Versionでは正規表現Libraryは標準で取り込まれるのかなあ…

さて、正規表現以外で自分で組むなら
ピリオドで区分したものが厳密に4つのものに分離できるか
そして、その4つがそれぞれ整数になり、0〜255であるかどうか、

を判断するといいという仕様になるのでしょうか。
うちのホームページの

http://delfusa.main.jp/delfusafloor/opensource/delfusalibrary/20070828160200/WordDecompose/WordDecompose.pas.txt
このあたりをみて、
WordCountやWordGetでピリオドとdmDelimiterExactly指定して
4つの要素を取り出してそれがTryIntToStrとかで数値かどうか判断して
それが0〜255かを、判断する。

と、上手く動きそうですよ。

人が考えるのと同じように
プログラムを組めるんじゃないかな。


KHE00221  2008-01-08 02:38:23  No: 29192

>いちいち「.」で分割してそれぞれのオクテットを検査しているのですが、

なのでできているんだろうけど一応・・・・・

function isSafeIP(IP:String):Boolean;
var
    I,J,J2,K : Integer;
begin
    Result := True;
    if Pos('$',IP) = 0 then
    begin
      J := 0;
      for I:=0 to 3 do
      begin
        J2 := PosEx('.',IP,J+1);
        if J2<>0 then
        begin
          K := StrToIntDef(Copy(IP,J+1,J2-J-1),256);
        end;
        if (J2=0) then
        begin
          if I=3 then
          begin
            K := StrToIntDef(Copy(IP,J+1,Length(IP)-J),256);
          end
          else
          begin
            Result := False;
          end;
        end;
        if (K<0) or (255<K) then Result := False;
        J := J2;
      end;
    end
    else
    begin
      Result := False;
    end;
end;


てんさく  2008-01-08 21:23:04  No: 29193

この↑関数では、こんな↓IPでも True になる?

  IP := '0001.0012.0123.000255';  // 数字の桁が3桁より多い
  IP := '12.34.56.78.90.123';     // 数字の数が4個より多い
  IP := '124.083.147.202...';     // 末尾に余計なドット


KHE000221  URL  2008-01-08 21:47:49  No: 29194

修正

function isSafeIP(IP:String):Boolean;
var
    I,J,J2,K : Integer;
    S : String;
begin
    Result := True;
    if Pos('$',IP) = 0 then
    begin
      J := 0;
      for I:=0 to 3 do
      begin
        J2 := PosEx('.',IP,J+1);
        if J2<>0 then
        begin
          S := Copy(IP,J+1,J2-J-1);
          if Length(S) < 4 then
          begin
            K := StrToIntDef(S,256);
          end
          else
          begin
            Result := False;
          end;
        end;
        if (J2=0) then
        begin
          if I=3 then
          begin
            S := Copy(IP,J+1,Length(IP)-J);
            if Length(S) < 4 then
            begin
              K := StrToIntDef(S,256);
            end
            else
            begin
              Result := False;
            end;
          end
          else
          begin
            Result := False;
          end;
        end;
        if (K<0) or (255<K) then Result := False;
        J := J2;
      end;
      if Copy(IP,J,1) = '.' then Result := False;
    end
    else
    begin
      Result := False;
    end;
end;


Fusa  URL  2008-01-08 22:31:53  No: 29195

そういうときには、テストファーストですな。

//KHE000221さんのisSafeIPに必要なのは
//このPosEx関数かな?
function PosEx(Substr: string; S: string; P: Integer): Integer;
begin
  if P <= 0 then Result := 0
  else Result := Pos(Substr, Copy(S, P, Length(S)));
  if Result <> 0 then Result := Result + P - 1;
end;

//これがチェックコード
procedure Check(A, B: Boolean);
begin
  if not(A = B) then
  begin
    Assert(False,
      'エラーです:A=' + BoolToStr(A) + ':B=' + BoolToStr(B));
  end;
end;

//ここでテストをおこなって
//テストを通過するようにisSafeIPを実装できていればよい。
procedure TForm1.Button1Click(Sender: TObject);
begin
  Check(True, isSafeIP('0.0.0.0'));
  Check(True, isSafeIP('255.255.255.0'));
  Check(True, isSafeIP('255.255.0.0'));
  Check(True, isSafeIP('0.0.0.255'));
  Check(True, isSafeIP('192.168.0.1'));
  Check(True, isSafeIP('192.168.0.01'));

  Check(False, isSafeIP('192.168.0.-1'));

  // 数字の桁が3桁より多い
  Check(False, isSafeIP('0192.168.0.1'));
  Check(False, isSafeIP('192.168.0000.1'));
  Check(False, isSafeIP('192.168.0.0001'));
  Check(False, isSafeIP('0001.0012.0123.000255'));

  //桁が余分
  Check(True, isSafeIP('192.02.03.04'));
  Check(False, isSafeIP('0192.02.03.04'));
  Check(True, isSafeIP('01.02.03.04'));

  // 数字の数が4個より多い
  Check(False, isSafeIP('12.34.56.78.90'));
  Check(False, isSafeIP('12.34.56.78.90.123'));

  // 末尾に余計なドット
  Check(False, isSafeIP('124.083.147.202..'));
  Check(False, isSafeIP('124.083.147.202...'));

  //先頭に余計なドット
  Check(False, isSafeIP('.124.083.147.202'));

  //数値がない部分がある
  Check(False, isSafeIP('.0.0.1'));
  Check(False, isSafeIP('0.0..1'));

  //数値が範囲外
  Check(False, isSafeIP('256.255.255.0'));
  Check(False, isSafeIP('255.255.0.256'));

  //数値じゃない値が入っている
  Check(False, isSafeIP('0.0F.0.255'));
  Check(False, isSafeIP('0.0x0F.0.255'));
  Check(False, isSafeIP('0.$0F.0.255'));
end;

このテストを通過していることが、
isSafeIPの関数仕様を現しているので
どういう文字がisSafeIPで許容されるか否かがすぐわかるし
isSafeIPをリファクタリングして内部実装が変更になっても
このテストが通れば、仕様をみたしている関数ということになります。

上のisSafeIPではエラーが起きるし
下のisSafeIPでは正しく動作していることを確認しました。

「桁が余分」の所の微妙な仕様なんかは
人間としてはどっちでもいい気もするけど
厳密さはテストでチェックしておかないと、わからないな。


DEKO  2008-01-09 00:00:17  No: 29196

面白そうなので私もやってみました。
# Check(False, isSafeIP('0.$0F.0.255'));
# で引っ掛かりますが(^^;A

function isSafeIP(IP: String): Boolean;
var
  i: Integer;
  Stream: TStringStream;
  Parser: TParser;
  iValue: Int64;
  Source: String;
begin
  result := False;
  Source := StringReplace(IP,'.',#$FF,[rfReplaceAll]);
  Stream := TStringStream.Create(Source);
  try
    Parser := TParser.Create(Stream);
    try
      for i:=0 to 6 do
        begin
          if ODD(i) then
            begin
              if Parser.TokenString <> #$FF then
                Exit;
            end
          else
            begin
              if Parser.Token <> toInteger then
                Exit;
              if Length(Parser.TokenString) > 3 then
                Exit;
              iValue := Parser.TokenInt;
              if not (iValue in [0..255]) then
                Exit;
            end;
          Parser.NextToken;
        end;
      if Parser.Token <> toEOF then
        Exit;
      result := True;
    finally
      Parser.Free;
    end;
  finally
    Stream.Free;
  end;
end;


まちだ  2008-01-09 02:46:12  No: 29197

おそらく、0x0Fは数値じゃない、と判断されているのではなくて
4文字以上かどうかの判断ではねられているのでしょう。
TryStrToIntでは0x0Fも$0FもTrueが返りますよ。

あとは、頭に0が付いていると、WindowsのPingなどでは
8進数だと誤解されるので、気をつけたほうがいいかもしれませんね。


まちだ  2008-01-09 02:55:05  No: 29198

私なら、頭に余分な0がある場合はエラーにすることにして
こうするかなぁ

function isSafeIP(IP:String):Boolean;
var
  slBuffer  : TStringList;
  i,j       : Integer;
begin
  Result  := True;
  slBuffer  := TStringList.Create;
  try
    slBuffer.Delimiter      := '.';
    slBuffer.DelimitedText  := IP;
    // 数のチェック
    if Not(slBuffer.Count = 4) then
      Result := False
    else
      // 数字かどうかのチェック
      for i:=0 to 3 do
        if Not(TryStrToInt(slBuffer.Strings[i],j)) then
          Result := False
        else
          // 数値範囲のチェック
          if Not(j in [0..255]) then
            Result := False
          else
            // 数値と文字列の整合チェック
            if Not(IntToStr(j)=slBuffer.Strings[i]) then
              Result := False;
  finally
    slBuffer.Free;
  end;
end;


Fusa  2008-01-11 10:11:15  No: 29199

こんにちはー。
こういう話題は楽しいですね。

> # Check(False, isSafeIP('0.$0F.0.255'));
> # で引っ掛かりますが(^^;A

これに引っかかるというか
isSafeIP('0.$0F.0.255')
がFalseを返す、という仕様にするか
Trueを返す、という仕様にするかは
作成者の自由かと思います。

一応、テストコードとしてご参考ください。

StrToIntは結構不思議な仕様でして
0x0Fも$0Fも数値として返してくれるんですよね。

> 私なら、頭に余分な0がある場合はエラーにすることにして
私も、そうするっすね〜。


Fusa  URL  2008-01-15 18:47:58  No: 29200

こんにちは。
私も、isSafeIPを作ってみました。

function isSafeIP(IP:String):Boolean;
var
  i, Val: Integer;
begin
  Result := False;
  with TWordDecompose.Create('.', IP, dmDelimiterExactly) do try
  //↓分割要素数が厳密に4であること
  if Count <> 4 then Exit;
  for i := 0 to 3 do
  begin
    //↓要素が空文字じゃないこと
    if Words[i] = '' then Exit;
    //↓要素が数字(0〜9)で出来ていること
    if CheckStrInTable(Words[i], hanNumberTbl) <> itAllInclude then Exit;
    //↓2文字以上あって先頭が0じゃないこと
    if (2 <= Length(Words[i])) and (AnsiPos('0', Words[i])=1) then Exit;
    //↓StrToIntで数値変換できること
    if not TryStrToInt(Words[i], Val) then Exit;
    //↓範囲が0〜255であること
    if not CheckRange(0, Val, 255) then Exit
  end;
  Result := True;
  finally Free; end;
end;

TWordDecomposeは、文字列を区分するクラス、Split関数みたいなもの
CheckStrInTableは文字列がテーブルに含まれているかどうか判断する関数
CheckRangeは範囲を調べる関数

DelFusa Library - Delphi OpenSource - DelFusa Floor
http://delfusa.main.jp/delfusafloor/opensource/delfusa_library_f.html

以下、次のようなテストコード通過してます。

procedure TForm1.Button1Click(Sender: TObject);
begin
  Check(True, isSafeIP('0.0.0.0'));
  Check(True, isSafeIP('1.2.3.4'));
  Check(True, isSafeIP('255.255.255.0'));
  Check(True, isSafeIP('255.255.0.0'));
  Check(True, isSafeIP('0.0.0.255'));
  Check(True, isSafeIP('192.168.0.1'));

  Check(False, isSafeIP('192.168.0.-1'));

  // 数字の桁が3桁より多い
  Check(False, isSafeIP('0192.168.0.1'));
  Check(False, isSafeIP('192.168.0000.1'));
  Check(False, isSafeIP('192.168.0.0001'));
  Check(False, isSafeIP('0001.0012.0123.000255'));

  //桁が余分※仕様によって変更あり
  Check(False, isSafeIP('192.168.0.01'));
  Check(False, isSafeIP('192.02.03.04'));
  Check(False, isSafeIP('0192.02.03.04'));
  Check(False, isSafeIP('01.02.03.04'));

  // 数字の数が4個より多い
  Check(False, isSafeIP('12.34.56.78.90'));
  Check(False, isSafeIP('12.34.56.78.90.123'));

  // 末尾に余計なドット
  Check(False, isSafeIP('124.083.147.202..'));
  Check(False, isSafeIP('124.083.147.202...'));

  //先頭に余計なドット
  Check(False, isSafeIP('.124.083.147.202'));

  //数値がない部分がある
  Check(False, isSafeIP('.0.0.1'));
  Check(False, isSafeIP('0.0..1'));

  //数値が範囲外
  Check(False, isSafeIP('256.255.255.0'));
  Check(False, isSafeIP('255.255.0.256'));

  //数値じゃない値が入っている
  Check(False, isSafeIP('0.0F.0.255'));
  Check(False, isSafeIP('0.0x0F.0.255'));
  Check(False, isSafeIP('0.$0F.0.255'));
end;

作ってみると
みなさんの実装、参考になりますね。

StrToIntを実行してから、
IntToStrを実行して同じかどうか見るってのは
先頭文字がゼロとか、全部の文字が0〜9になっているとかを
判断する必要がなくなるので
わかりやすい整合性チェックできますね。

リファクタリング、
つまり、関数の定義は変えずに内部実装をもっと効率よくわかりやすく
工夫してみました。
下記でもテストコードを通過しています。

function isSafeIP(IP:String):Boolean;
var
  i, Val: Integer;
begin
  Result := False;
  with TWordDecompose.Create('.', IP, dmDelimiterExactly) do try
  if Count <> 4 then Exit;
  for i := 0 to 3 do
  begin
    if Words[i] = '' then Exit;
    if not TryStrToInt(Words[i], Val) then Exit;
    if not (Val in [0..255]) then Exit;
    if not (Words[i]=IntToStr(Val)) then Exit;
  end;
  Result := True;
  finally Free; end;
end;


まちだ  2008-01-16 03:02:37  No: 29201

なるほど、ResultをFalseにしておいてExitしていけばいれこが減るし
余計な判断ループもいらなくなりますね。  参考になります。

でも、ここまで来て、気が付きました。

> 書き忘れましたが、delphi5 + indy10です。

TryStrToInt関数とIf文のin演算子がないような気が…orz

> いちいち「.」で分割してそれぞれのオクテットを検査しているのですが、

分解して検査をしたくないということであれば、この文からも外れちゃってますね。
プログラムは短いけど、やってることはまさにこれ…orz


Fusa  URL  2008-01-16 09:30:10  No: 29202

うちのテクニックサイトに書かせてもらいました。

やろうと思えば
TryStrToIntはCanStrToIntというのを検索でヒットさせて
実装を参考にしたり、
in演算子のところは、(0 <= Val) and (Val <= 255)にしてもらいましょう。


Fusa  2008-02-06 22:37:25  No: 29203

いまさらですが、wisockのinet_addrを
IsSafeIP関数でラップして調査してみました。

  Check(False, IsSafeIP('0192.168.0.1'));
なぜか、これがIPとして認められてないのに、

  Check(True, IsSafeIP('0001.0012.0123.000255'));
これはIPとして認められる。

  Check(True, IsSafeIP('0.0x0F.0.255'));
まさか、これまでIPアドレスとして許容するとは…

[WinSock.pas]さんのおっしゃるように

  Check(True, IsSafeIP('192'));

…ひどい…

----------
//uses  Winsockを追加

function IsSafeIP(IP:String):Boolean;
var
  InAddr: TInAddr;
begin
  InAddr.S_addr := inet_addr(PChar(IP));
  if InAddr.S_addr = INADDR_NONE then
  begin
    Result := False;
  end else
  begin
    Result := True;
  end;
end;


まちだ  2008-02-06 23:50:36  No: 29204

それぞれを、pingしてみるとどう変換されているかわかって面白いですよ。
ex)ping 192 -> ping 0.0.0.192

0192がだめなのは、8進数に9が存在しないからですね。(笑)
0162ならOKだったりするのがご愛嬌です。
もちろん0xを頭につければ、16進数で変換されます。

そういう意味では、(Windows上では)Winsockが正しいのかなぁ。


※返信する前に利用規約をご確認ください。








  このエントリーをはてなブックマークに追加