標記を、外部dllを使わずに実現するにはどうすればよいのでしょうか。
なお、「正しい」とはIPv4ルールで正しく記述されているかどうかであり、当該ホストなりネットワークなりが存在しているかどうかは問いません。
ipアドレスの文字列が
***.***.***.***
(↑***は 0〜255の数字文字列)
になってるかを判定したいってこと?
はいそうです。
書き忘れましたが、delphi5 + indy10です。
いちいち「.」で分割してそれぞれのオクテットを検査しているのですが、
isSafeIP('192.168.0.256') → False
みたいな方法が無いのかなと探しています。
いいだけだろ?
inet_addr
こういうのでもいいのか?
・・・おっと、wsock32.dll を使うから、これもダメだな(笑)
inet_addr('192') としたときエラーにならないんだよ
別に .が3個あるか確認すればいいのか?
>標記を、外部dllを使わずに実現するにはどうすればよいのでしょうか。
APIは全滅じゃないか?
こんな時こそ正規表現で....
と、自分がツカエもしないし好きでもない正規表現について言ってみました。
※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かを、判断する。
と、上手く動きそうですよ。
人が考えるのと同じように
プログラムを組めるんじゃないかな。
>いちいち「.」で分割してそれぞれのオクテットを検査しているのですが、
なのでできているんだろうけど一応・・・・・
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;
この↑関数では、こんな↓IPでも True になる?
IP := '0001.0012.0123.000255'; // 数字の桁が3桁より多い
IP := '12.34.56.78.90.123'; // 数字の数が4個より多い
IP := '124.083.147.202...'; // 末尾に余計なドット
修正
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;
そういうときには、テストファーストですな。
//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では正しく動作していることを確認しました。
「桁が余分」の所の微妙な仕様なんかは
人間としてはどっちでもいい気もするけど
厳密さはテストでチェックしておかないと、わからないな。
面白そうなので私もやってみました。
# 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;
おそらく、0x0Fは数値じゃない、と判断されているのではなくて
4文字以上かどうかの判断ではねられているのでしょう。
TryStrToIntでは0x0Fも$0FもTrueが返りますよ。
あとは、頭に0が付いていると、WindowsのPingなどでは
8進数だと誤解されるので、気をつけたほうがいいかもしれませんね。
私なら、頭に余分な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;
こんにちはー。
こういう話題は楽しいですね。
> # Check(False, isSafeIP('0.$0F.0.255'));
> # で引っ掛かりますが(^^;A
これに引っかかるというか
isSafeIP('0.$0F.0.255')
がFalseを返す、という仕様にするか
Trueを返す、という仕様にするかは
作成者の自由かと思います。
一応、テストコードとしてご参考ください。
StrToIntは結構不思議な仕様でして
0x0Fも$0Fも数値として返してくれるんですよね。
> 私なら、頭に余分な0がある場合はエラーにすることにして
私も、そうするっすね〜。
こんにちは。
私も、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;
なるほど、ResultをFalseにしておいてExitしていけばいれこが減るし
余計な判断ループもいらなくなりますね。 参考になります。
でも、ここまで来て、気が付きました。
> 書き忘れましたが、delphi5 + indy10です。
TryStrToInt関数とIf文のin演算子がないような気が…orz
> いちいち「.」で分割してそれぞれのオクテットを検査しているのですが、
分解して検査をしたくないということであれば、この文からも外れちゃってますね。
プログラムは短いけど、やってることはまさにこれ…orz
うちのテクニックサイトに書かせてもらいました。
やろうと思えば
TryStrToIntはCanStrToIntというのを検索でヒットさせて
実装を参考にしたり、
in演算子のところは、(0 <= Val) and (Val <= 255)にしてもらいましょう。
いまさらですが、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;
それぞれを、pingしてみるとどう変換されているかわかって面白いですよ。
ex)ping 192 -> ping 0.0.0.192
0192がだめなのは、8進数に9が存在しないからですね。(笑)
0162ならOKだったりするのがご愛嬌です。
もちろん0xを頭につければ、16進数で変換されます。
そういう意味では、(Windows上では)Winsockが正しいのかなぁ。
ツイート | ![]() |