unit uIpAddressHelpers;

{$mode objfpc}{$H+}

interface

uses
  SysUtils, RegExpr;

function IsValidPrivateIPv4(aIP: String): Boolean;
function IsValidIPv4(aIP: String): Boolean;
function IsValidIPv6(aIP: String): Boolean;

implementation

function IsValidPrivateIPv4(aIP: String): Boolean;
var
  Expr: String;
  r: TRegExpr;
begin
  Result := False;

  Expr := '^(';
  { *
    RFC 1918
    - 10.0.0.0 - 10.255.255.255
    - 172.16.0.0 - 172.31.255.255
    - 192.168.0.0 - 192.168.255.255
  * }
  Expr += '10(\.(25[0-5]|2[0-4][0-9]|1[0-9]{1,2}|[0-9]{1,2})){3}|';
  Expr += '((172\.(1[6-9]|2[0-9]|3[01]))|192\.168)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{1,2}|[0-9]{1,2})){2}|';
  { *
    RFC 5735
    - 169.254.0.0 - 169.254.255.255
  * }
  Expr += '169\.254(\.(25[0-5]|2[0-4][0-9]|1[0-9]{1,2}|[0-9]{1,2})){2}|';
  { *
    RFC 6598
    - 100.64.0.0 - 100.127.255.255
  * }
  Expr += '(100\.(6[4-9]|[7-9][0-9]|1[0-2][0-7]))(\.(25[0-5]|2[0-4][0-9]|1[0-9]{1,2}|[0-9]{1,2})){2}';
  Expr += ')$';

  r := TRegExpr.Create;
  try
    r.Expression := Expr;
    Result := r.Exec(Trim(aIP));
  finally
    r.Free;
  end;

end;

function IsValidIPv4(aIP: String): Boolean;
var
  Expr: String;
  r: TRegExpr;
begin
  Result := False;

  // With leading zeros, 000.000.000.000 - 255.255.255.255
  // Expr := '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}$';

  // Non leading zeros, 0.0.0.0 - 255.255.255.255
  Expr := '^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}';
  Expr += '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$';

  r := TRegExpr.Create;
  try
    r.Expression := Expr;
    Result := r.Exec(Trim(aIP));
  finally
    r.Free;
  end;
end;

function IsValidIPv6(aIP: String): Boolean;
var
  Expr: String;
  r: TRegExpr;
begin
  Result := False;

  // from here: https://stackoverflow.com/a/17871737
  Expr := '(';
  Expr += '([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|';          // 1:2:3:4:5:6:7:8
  Expr += '([0-9a-fA-F]{1,4}:){1,7}:|';                         // 1::                              1:2:3:4:5:6:7::
  Expr += '([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|';         // 1::8             1:2:3:4:5:6::8  1:2:3:4:5:6::8
  Expr += '([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|';  // 1::7:8           1:2:3:4:5::7:8  1:2:3:4:5::8
  Expr += '([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|';  // 1::6:7:8         1:2:3:4::6:7:8  1:2:3:4::8
  Expr += '([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|';  // 1::5:6:7:8       1:2:3::5:6:7:8  1:2:3::8
  Expr += '([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|';  // 1::4:5:6:7:8     1:2::4:5:6:7:8  1:2::8
  Expr += '[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|';       // 1::3:4:5:6:7:8   1::3:4:5:6:7:8  1::8
  Expr += ':((:[0-9a-fA-F]{1,4}){1,7}|:)|';                     // ::2:3:4:5:6:7:8  ::2:3:4:5:6:7:8 ::8       ::
  Expr += 'fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|';     // fe80::7:8%eth0   fe80::7:8%1     (link-local IPv6 addresses with zone index)
  Expr += '::(ffff(:0{1,4}){0,1}:){0,1}';
  Expr += '((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}';
  Expr += '(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|';          // ::255.255.255.255   ::ffff:255.255.255.255  ::ffff:0:255.255.255.255  (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
  Expr += '([0-9a-fA-F]{1,4}:){1,4}:';
  Expr += '((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}';
  Expr += '(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])';           // 2001:db8:3:4::192.0.2.33  64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address)
  Expr += ')';

  r := TRegExpr.Create;
  try
    r.Expression := Expr;
    Result := r.Exec(Trim(aIP));
  finally
    r.Free;
  end;
end;

end.

