ClpIPAddressUtilities.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. { *********************************************************************************** }
  2. { * CryptoLib Library * }
  3. { * Copyright (c) 2018 - 20XX Ugochukwu Mmaduekwe * }
  4. { * Github Repository <https://github.com/Xor-el> * }
  5. { * Distributed under the MIT software license, see the accompanying file LICENSE * }
  6. { * or visit http://www.opensource.org/licenses/mit-license.php. * }
  7. { * Acknowledgements: * }
  8. { * * }
  9. { * Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring * }
  10. { * development of this library * }
  11. { * ******************************************************************************* * }
  12. (* &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& *)
  13. unit ClpIPAddressUtilities;
  14. {$I ..\Include\CryptoLib.inc}
  15. interface
  16. uses
  17. SysUtils,
  18. ClpStringUtilities;
  19. type
  20. /// <summary>
  21. /// IP Address utility class for validating IPv4 and IPv6 addresses.
  22. /// </summary>
  23. TIPAddressUtilities = class sealed(TObject)
  24. strict private
  25. class function IsParseableIPv4Octet(const AStr: String; APos, AEnd: Int32): Boolean; static;
  26. class function IsParseableIPv4Mask(const AStr: String): Boolean; static;
  27. class function IsParseableIPv6Segment(const AStr: String; APos, AEnd: Int32): Boolean; static;
  28. class function IsParseableIPv6Mask(const AStr: String): Boolean; static;
  29. class function IsParseableDecimal(const AStr: String; APos, AEnd, AMaxLength: Int32;
  30. AAllowLeadingZero: Boolean; AMinValue, AMaxValue: Int32): Boolean; static;
  31. class function IsParseableHexadecimal(const AStr: String; APos, AEnd, AMaxLength: Int32;
  32. AAllowLeadingZero: Boolean; AMinValue, AMaxValue: Int32): Boolean; static;
  33. class function GetDigitDecimal(const AStr: String; APos: Int32): Int32; static;
  34. class function GetDigitHexadecimal(const AStr: String; APos: Int32): Int32; static;
  35. public
  36. /// <summary>
  37. /// Validate the given IPv4 or IPv6 address.
  38. /// </summary>
  39. class function IsValid(const AAddress: String): Boolean; static;
  40. /// <summary>
  41. /// Validate the given IPv4 or IPv6 address and netmask.
  42. /// </summary>
  43. class function IsValidWithNetMask(const AAddress: String): Boolean; static;
  44. /// <summary>
  45. /// Validate the given IPv4 address.
  46. /// </summary>
  47. class function IsValidIPv4(const AAddress: String): Boolean; static;
  48. /// <summary>
  49. /// Validate the given IPv4 address with netmask.
  50. /// </summary>
  51. class function IsValidIPv4WithNetmask(const AAddress: String): Boolean; static;
  52. /// <summary>
  53. /// Validate the given IPv6 address.
  54. /// </summary>
  55. class function IsValidIPv6(const AAddress: String): Boolean; static;
  56. /// <summary>
  57. /// Validate the given IPv6 address with netmask.
  58. /// </summary>
  59. class function IsValidIPv6WithNetmask(const AAddress: String): Boolean; static;
  60. end;
  61. implementation
  62. { TIPAddressUtilities }
  63. class function TIPAddressUtilities.IsValid(const AAddress: String): Boolean;
  64. begin
  65. Result := IsValidIPv4(AAddress) or IsValidIPv6(AAddress);
  66. end;
  67. class function TIPAddressUtilities.IsValidWithNetMask(const AAddress: String): Boolean;
  68. begin
  69. Result := IsValidIPv4WithNetmask(AAddress) or IsValidIPv6WithNetmask(AAddress);
  70. end;
  71. class function TIPAddressUtilities.IsValidIPv4(const AAddress: String): Boolean;
  72. var
  73. LLength, LPos, LEnd, LOctetIndex: Int32;
  74. begin
  75. LLength := System.Length(AAddress);
  76. if (LLength < 7) or (LLength > 15) then
  77. begin
  78. Result := False;
  79. Exit;
  80. end;
  81. LPos := 1; // 1-based position
  82. for LOctetIndex := 0 to 2 do
  83. begin
  84. // TStringUtilities.IndexOf returns 1-based index (0 if not found)
  85. LEnd := TStringUtilities.IndexOf(AAddress, '.', LPos);
  86. if LEnd = 0 then
  87. begin
  88. Result := False;
  89. Exit;
  90. end;
  91. // IsParseableIPv4Octet expects 1-based positions
  92. if not IsParseableIPv4Octet(AAddress, LPos, LEnd) then
  93. begin
  94. Result := False;
  95. Exit;
  96. end;
  97. LPos := LEnd + 1; // Skip the '.' character
  98. end;
  99. // Check last octet
  100. Result := IsParseableIPv4Octet(AAddress, LPos, LLength + 1);
  101. end;
  102. class function TIPAddressUtilities.IsValidIPv4WithNetmask(const AAddress: String): Boolean;
  103. var
  104. LIndex: Int32;
  105. LBefore, LAfter: String;
  106. begin
  107. LIndex := TStringUtilities.IndexOf(AAddress, '/');
  108. if LIndex = 0 then
  109. begin
  110. Result := False;
  111. Exit;
  112. end;
  113. // LIndex is 1-based position of '/'
  114. LBefore := System.Copy(AAddress, 1, LIndex - 1);
  115. LAfter := System.Copy(AAddress, LIndex + 1, System.Length(AAddress) - LIndex);
  116. Result := IsValidIPv4(LBefore) and (IsValidIPv4(LAfter) or IsParseableIPv4Mask(LAfter));
  117. end;
  118. class function TIPAddressUtilities.IsValidIPv6(const AAddress: String): Boolean;
  119. var
  120. LLength, LPos, LEnd, LSegmentCount: Int32;
  121. LTemp, LValue: String;
  122. LDoubleColonFound: Boolean;
  123. begin
  124. LLength := System.Length(AAddress);
  125. if LLength = 0 then
  126. begin
  127. Result := False;
  128. Exit;
  129. end;
  130. // Check first character
  131. if (AAddress[1] <> ':') and (GetDigitHexadecimal(AAddress, 1) < 0) then
  132. begin
  133. Result := False;
  134. Exit;
  135. end;
  136. LSegmentCount := 0;
  137. LTemp := AAddress + ':';
  138. LDoubleColonFound := False;
  139. LPos := 1; // 1-based position
  140. while LPos <= System.Length(LTemp) do
  141. begin
  142. LEnd := TStringUtilities.IndexOf(LTemp, ':', LPos);
  143. if LEnd = 0 then
  144. Break;
  145. if LSegmentCount = 8 then
  146. begin
  147. Result := False;
  148. Exit;
  149. end;
  150. if LPos <> LEnd then
  151. begin
  152. // Extract segment (1-based positions)
  153. LValue := System.Copy(LTemp, LPos, LEnd - LPos);
  154. // Check if this is the last segment and contains IPv4 notation
  155. if (LEnd = System.Length(LTemp)) and (TStringUtilities.IndexOf(LValue, '.') > 0) then
  156. begin
  157. // Add an extra one as address covers 2 words
  158. System.Inc(LSegmentCount);
  159. if LSegmentCount = 8 then
  160. begin
  161. Result := False;
  162. Exit;
  163. end;
  164. if not IsValidIPv4(LValue) then
  165. begin
  166. Result := False;
  167. Exit;
  168. end;
  169. end
  170. else if not IsParseableIPv6Segment(LTemp, LPos, LEnd) then
  171. begin
  172. Result := False;
  173. Exit;
  174. end;
  175. end
  176. else
  177. begin
  178. // Empty segment (double colon)
  179. if (LEnd <> 2) and (LEnd <> System.Length(LTemp)) and LDoubleColonFound then
  180. begin
  181. Result := False;
  182. Exit;
  183. end;
  184. LDoubleColonFound := True;
  185. end;
  186. LPos := LEnd + 1; // Skip the ':' character
  187. System.Inc(LSegmentCount);
  188. end;
  189. Result := (LSegmentCount = 8) or LDoubleColonFound;
  190. end;
  191. class function TIPAddressUtilities.IsValidIPv6WithNetmask(const AAddress: String): Boolean;
  192. var
  193. LIndex: Int32;
  194. LBefore, LAfter: String;
  195. begin
  196. LIndex := TStringUtilities.IndexOf(AAddress, '/');
  197. if LIndex = 0 then
  198. begin
  199. Result := False;
  200. Exit;
  201. end;
  202. // LIndex is 1-based position of '/'
  203. LBefore := System.Copy(AAddress, 1, LIndex - 1);
  204. LAfter := System.Copy(AAddress, LIndex + 1, System.Length(AAddress) - LIndex);
  205. Result := IsValidIPv6(LBefore) and (IsValidIPv6(LAfter) or IsParseableIPv6Mask(LAfter));
  206. end;
  207. class function TIPAddressUtilities.IsParseableIPv4Mask(const AStr: String): Boolean;
  208. begin
  209. Result := IsParseableDecimal(AStr, 1, System.Length(AStr) + 1, 2, False, 0, 32);
  210. end;
  211. class function TIPAddressUtilities.IsParseableIPv4Octet(const AStr: String; APos, AEnd: Int32): Boolean;
  212. begin
  213. // APos and AEnd are 1-based
  214. Result := IsParseableDecimal(AStr, APos, AEnd, 3, True, 0, 255);
  215. end;
  216. class function TIPAddressUtilities.IsParseableIPv6Mask(const AStr: String): Boolean;
  217. begin
  218. Result := IsParseableDecimal(AStr, 1, System.Length(AStr) + 1, 3, False, 1, 128);
  219. end;
  220. class function TIPAddressUtilities.IsParseableIPv6Segment(const AStr: String; APos, AEnd: Int32): Boolean;
  221. begin
  222. // APos and AEnd are 1-based
  223. Result := IsParseableHexadecimal(AStr, APos, AEnd, 4, True, $0000, $FFFF);
  224. end;
  225. class function TIPAddressUtilities.IsParseableDecimal(const AStr: String; APos, AEnd, AMaxLength: Int32;
  226. AAllowLeadingZero: Boolean; AMinValue, AMaxValue: Int32): Boolean;
  227. var
  228. LLength, LValue: Int32;
  229. LD: Int32;
  230. begin
  231. // APos and AEnd are 1-based
  232. LLength := AEnd - APos;
  233. if (LLength < 1) or (LLength > AMaxLength) then
  234. begin
  235. Result := False;
  236. Exit;
  237. end;
  238. // Check for leading zero
  239. if (LLength > 1) and (not AAllowLeadingZero) and (AStr[APos] = '0') then
  240. begin
  241. Result := False;
  242. Exit;
  243. end;
  244. LValue := 0;
  245. while APos < AEnd do
  246. begin
  247. LD := GetDigitDecimal(AStr, APos);
  248. if LD < 0 then
  249. begin
  250. Result := False;
  251. Exit;
  252. end;
  253. LValue := LValue * 10;
  254. LValue := LValue + LD;
  255. System.Inc(APos);
  256. end;
  257. Result := (LValue >= AMinValue) and (LValue <= AMaxValue);
  258. end;
  259. class function TIPAddressUtilities.IsParseableHexadecimal(const AStr: String; APos, AEnd, AMaxLength: Int32;
  260. AAllowLeadingZero: Boolean; AMinValue, AMaxValue: Int32): Boolean;
  261. var
  262. LLength, LValue: Int32;
  263. LD: Int32;
  264. begin
  265. // APos and AEnd are 1-based
  266. LLength := AEnd - APos;
  267. if (LLength < 1) or (LLength > AMaxLength) then
  268. begin
  269. Result := False;
  270. Exit;
  271. end;
  272. // Check for leading zero
  273. if (LLength > 1) and (not AAllowLeadingZero) and (AStr[APos] = '0') then
  274. begin
  275. Result := False;
  276. Exit;
  277. end;
  278. LValue := 0;
  279. while APos < AEnd do
  280. begin
  281. LD := GetDigitHexadecimal(AStr, APos);
  282. if LD < 0 then
  283. begin
  284. Result := False;
  285. Exit;
  286. end;
  287. LValue := LValue * 16;
  288. LValue := LValue + LD;
  289. System.Inc(APos);
  290. end;
  291. Result := (LValue >= AMinValue) and (LValue <= AMaxValue);
  292. end;
  293. class function TIPAddressUtilities.GetDigitDecimal(const AStr: String; APos: Int32): Int32;
  294. var
  295. LC: Char;
  296. LD: UInt32;
  297. begin
  298. // APos is 1-based
  299. LC := AStr[APos];
  300. LD := UInt32(Ord(LC) - Ord('0'));
  301. if LD <= 9 then
  302. Result := Int32(LD)
  303. else
  304. Result := -1;
  305. end;
  306. class function TIPAddressUtilities.GetDigitHexadecimal(const AStr: String; APos: Int32): Int32;
  307. var
  308. LC: Char;
  309. LD: UInt32;
  310. begin
  311. // APos is 1-based
  312. LC := AStr[APos];
  313. // Convert to lowercase for comparison
  314. LD := UInt32(Ord(LC)) or $20;
  315. if LD >= UInt32(Ord('a')) then
  316. LD := LD - (UInt32(Ord('a')) - 10)
  317. else
  318. LD := LD - UInt32(Ord('0'));
  319. if LD <= 16 then
  320. Result := Int32(LD)
  321. else
  322. Result := -1;
  323. end;
  324. end.