{==============================================================================|
| Project : Ararat Synapse                                       | 001.002.001 |
|==============================================================================|
| Content: IP address support procedures and functions                         |
|==============================================================================|
| Copyright (c)2006-2010, Lukas Gebauer                                        |
| All rights reserved.                                                         |
|                                                                              |
| Redistribution and use in source and binary forms, with or without           |
| modification, are permitted provided that the following conditions are met:  |
|                                                                              |
| Redistributions of source code must retain the above copyright notice, this  |
| list of conditions and the following disclaimer.                             |
|                                                                              |
| Redistributions in binary form must reproduce the above copyright notice,    |
| this list of conditions and the following disclaimer in the documentation    |
| and/or other materials provided with the distribution.                       |
|                                                                              |
| Neither the name of Lukas Gebauer nor the names of its contributors may      |
| be used to endorse or promote products derived from this software without    |
| specific prior written permission.                                           |
|                                                                              |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  |
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE    |
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE   |
| ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR  |
| ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL       |
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR   |
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER   |
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT           |
| LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY    |
| OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH  |
| DAMAGE.                                                                      |
|==============================================================================|
| The Initial Developer of the Original Code is Lukas Gebauer (Czech Republic).|
| Portions created by Lukas Gebauer are Copyright (c) 2006-2010.               |
| All Rights Reserved.                                                         |
|==============================================================================|
| Contributor(s):                                                              |
|==============================================================================|
| History: see HISTORY.HTM from distribution package                           |
|          (Found at URL: http://www.ararat.cz/synapse/)                       |
|==============================================================================}

{:@abstract(IP adress support procedures and functions)}

{$IFDEF FPC}
  {$MODE DELPHI}
{$ENDIF}
{$Q-}
{$R-}
{$H+}

{$IFDEF UNICODE}
  {$WARN IMPLICIT_STRING_CAST OFF}
  {$WARN IMPLICIT_STRING_CAST_LOSS OFF}
  {$WARN SUSPICIOUS_TYPECAST OFF}
{$ENDIF}

unit synaip;

interface

uses
  SysUtils, SynaUtil;

type
{:binary form of IPv6 adress (for string conversion routines)}
  TIp6Bytes = array [0..15] of Byte;
{:binary form of IPv6 adress (for string conversion routines)}
  TIp6Words = array [0..7] of Word;

{:Returns @TRUE, if "Value" is a valid IPv4 address. Cannot be a symbolic Name!}
function IsIP(const Value: string): Boolean;

{:Returns @TRUE, if "Value" is a valid IPv6 address. Cannot be a symbolic Name!}
function IsIP6(const Value: string): Boolean;

{:Returns a string with the "Host" ip address converted to binary form.}
function IPToID(Host: string): Ansistring;

{:Convert IPv6 address from their string form to binary byte array.}
function StrToIp6(value: string): TIp6Bytes;

{:Convert IPv6 address from binary byte array to string form.}
function Ip6ToStr(value: TIp6Bytes): string;

{:Convert IPv4 address from their string form to binary.}
function StrToIp(value: string): integer;

{:Convert IPv4 address from binary to string form.}
function IpToStr(value: integer): string;

{:Convert IPv4 address to reverse form.}
function ReverseIP(Value: AnsiString): AnsiString;

{:Convert IPv6 address to reverse form.}
function ReverseIP6(Value: AnsiString): AnsiString;

{:Expand short form of IPv6 address to long form.}
function ExpandIP6(Value: AnsiString): AnsiString;


implementation

{==============================================================================}

function IsIP(const Value: string): Boolean;
var
  TempIP: string;
  function ByteIsOk(const Value: string): Boolean;
  var
    x, n: integer;
  begin
    x := StrToIntDef(Value, -1);
    Result := (x >= 0) and (x < 256);
    // X may be in correct range, but value still may not be correct value!
    // i.e. "$80"
    if Result then
      for n := 1 to length(Value) do
        if not (AnsiChar(Value[n]) in ['0'..'9']) then
        begin
          Result := False;
          Break;
        end;
  end;
begin
  TempIP := Value;
  Result := False;
  if not ByteIsOk(Fetch(TempIP, '.')) then
    Exit;
  if not ByteIsOk(Fetch(TempIP, '.')) then
    Exit;
  if not ByteIsOk(Fetch(TempIP, '.')) then
    Exit;
  if ByteIsOk(TempIP) then
    Result := True;
end;

{==============================================================================}

function IsIP6(const Value: string): Boolean;
var
  TempIP: string;
  s,t: string;
  x: integer;
  partcount: integer;
  zerocount: integer;
  First: Boolean;
begin
  TempIP := Value;
  Result := False;
  if Value = '::' then
  begin
    Result := True;
    Exit;
  end;
  partcount := 0;
  zerocount := 0;
  First := True;
  while tempIP <> '' do
  begin
    s := fetch(TempIP, ':');
    if not(First) and (s = '') then
      Inc(zerocount);
    First := False;
    if zerocount > 1 then
      break;
    Inc(partCount);
    if s = '' then
      Continue;
    if partCount > 8 then
      break;
    if tempIP = '' then
    begin
      t := SeparateRight(s, '%');
      s := SeparateLeft(s, '%');
      x := StrToIntDef('$' + t, -1);
      if (x < 0) or (x > $ffff) then
        break;
    end;
    x := StrToIntDef('$' + s, -1);
    if (x < 0) or (x > $ffff) then
      break;
    if tempIP = '' then
      if not((PartCount = 1) and (ZeroCount = 0)) then
        Result := True;
  end;
end;

{==============================================================================}
function IPToID(Host: string): Ansistring;
var
  s: string;
  i, x: Integer;
begin
  Result := '';
  for x := 0 to 3 do
  begin
    s := Fetch(Host, '.');
    i := StrToIntDef(s, 0);
    Result := Result + AnsiChar(i);
  end;
end;

{==============================================================================}

function StrToIp(value: string): integer;
var
  s: string;
  i, x: Integer;
begin
  Result := 0;
  for x := 0 to 3 do
  begin
    s := Fetch(value, '.');
    i := StrToIntDef(s, 0);
    Result := (256 * Result) + i;
  end;
end;

{==============================================================================}

function IpToStr(value: integer): string;
var
  x1, x2: word;
  y1, y2: byte;
begin
  Result := '';
  x1 := value shr 16;
  x2 := value and $FFFF;
  y1 := x1 div $100;
  y2 := x1 mod $100;
  Result := inttostr(y1) + '.' + inttostr(y2) + '.';
  y1 := x2 div $100;
  y2 := x2 mod $100;
  Result := Result + inttostr(y1) + '.' + inttostr(y2);
end;

{==============================================================================}

function ExpandIP6(Value: AnsiString): AnsiString;
var
 n: integer;
 s: ansistring;
 x: integer;
begin
  Result := '';
  if value = '' then
    exit;
  x := countofchar(value, ':');
  if x > 7 then
    exit;
  if value[1] = ':' then
    value := '0' + value;
  if value[length(value)] = ':' then
    value := value + '0';
  x := 8 - x;
  s := '';
  for n := 1 to x do
    s := s + ':0';
  s := s + ':';
  Result := replacestring(value, '::', s);
end;
{==============================================================================}

function StrToIp6(Value: string): TIp6Bytes;
var
 IPv6: TIp6Words;
 Index: Integer;
 n: integer;
 b1, b2: byte;
 s: string;
 x: integer;
begin
  for n := 0 to 15 do
    Result[n] := 0;
  for n := 0 to 7 do
    Ipv6[n] := 0;
  Index := 0;
  Value := ExpandIP6(value);
  if value = '' then
    exit;
  while Value <> '' do
  begin
    if Index > 7 then
      Exit;
    s := fetch(value, ':');
    if s = '@' then
      break;
    if s = '' then
    begin
      IPv6[Index] := 0;
    end
    else
    begin
      x := StrToIntDef('$' + s, -1);
      if (x > 65535) or (x < 0) then
        Exit;
      IPv6[Index] := x;
    end;
    Inc(Index);
  end;
  for n := 0 to 7 do
  begin
    b1 := ipv6[n] div 256;
    b2 := ipv6[n] mod 256;
    Result[n * 2] := b1;
    Result[(n * 2) + 1] := b2;
  end;
end;

{==============================================================================}
//based on routine by the Free Pascal development team
function Ip6ToStr(value: TIp6Bytes): string;
var
  i, x: byte;
  zr1,zr2: set of byte;
  zc1,zc2: byte;
  have_skipped: boolean;
  ip6w: TIp6words;
begin
  zr1 := [];
  zr2 := [];
  zc1 := 0;
  zc2 := 0;
  for i := 0 to 7 do
  begin
    x := i * 2;
    ip6w[i] := value[x] * 256 + value[x + 1];
    if ip6w[i] = 0 then
    begin
      include(zr2, i);
      inc(zc2);
    end
    else
    begin
      if zc1 < zc2 then
      begin
        zc1 := zc2;
        zr1 := zr2;
        zc2 := 0;
        zr2 := [];
      end;
    end;
  end;
  if zc1 < zc2 then
  begin
    zr1 := zr2;
  end;
  SetLength(Result, 8*5-1);
  SetLength(Result, 0);
  have_skipped := false;
  for i := 0 to 7 do
  begin
    if not(i in zr1) then
    begin
      if have_skipped then
      begin
        if Result = '' then
          Result := '::'
        else
          Result := Result + ':';
        have_skipped := false;
      end;
      Result := Result + IntToHex(Ip6w[i], 1) + ':';
    end
    else
    begin
      have_skipped := true;
    end;
  end;
  if have_skipped then
    if Result = '' then
      Result := '::0'
    else
      Result := Result + ':';

  if Result = '' then
    Result := '::0';
  if not (7 in zr1) then
    SetLength(Result, Length(Result)-1);
  Result := LowerCase(result);
end;

{==============================================================================}
function ReverseIP(Value: AnsiString): AnsiString;
var
  x: Integer;
begin
  Result := '';
  repeat
    x := LastDelimiter('.', Value);
    Result := Result + '.' + Copy(Value, x + 1, Length(Value) - x);
    Delete(Value, x, Length(Value) - x + 1);
  until x < 1;
  if Length(Result) > 0 then
    if Result[1] = '.' then
      Delete(Result, 1, 1);
end;

{==============================================================================}
function ReverseIP6(Value: AnsiString): AnsiString;
var
  ip6: TIp6bytes;
  n: integer;
  x, y: integer;
begin
  ip6 := StrToIP6(Value);
  x := ip6[15] div 16;
  y := ip6[15] mod 16;
  Result := IntToHex(y, 1) + '.' + IntToHex(x, 1);
  for n := 14 downto 0 do
  begin
    x := ip6[n] div 16;
    y := ip6[n] mod 16;
    Result := Result + '.' + IntToHex(y, 1) + '.' + IntToHex(x, 1);
  end;
end;

{==============================================================================}
end.
