Factory.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <?php
  2. namespace IPLib;
  3. use IPLib\Address\AddressInterface;
  4. use IPLib\Range\Subnet;
  5. use IPLib\Service\RangesFromBoundaryCalculator;
  6. /**
  7. * Factory methods to build class instances.
  8. */
  9. class Factory
  10. {
  11. /**
  12. * @deprecated since 1.17.0: use the parseAddressString() method instead.
  13. * For upgrading:
  14. * - if $mayIncludePort is true, use the ParseStringFlag::MAY_INCLUDE_PORT flag
  15. * - if $mayIncludeZoneID is true, use the ParseStringFlag::MAY_INCLUDE_ZONEID flag
  16. * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
  17. *
  18. * @param string|mixed $address
  19. * @param bool $mayIncludePort
  20. * @param bool $mayIncludeZoneID
  21. * @param bool $supportNonDecimalIPv4
  22. *
  23. * @return \IPLib\Address\AddressInterface|null
  24. *
  25. * @see \IPLib\Factory::parseAddressString()
  26. * @since 1.1.0 added the $mayIncludePort argument
  27. * @since 1.3.0 added the $mayIncludeZoneID argument
  28. * @since 1.10.0 added the $supportNonDecimalIPv4 argument
  29. */
  30. public static function addressFromString($address, $mayIncludePort = true, $mayIncludeZoneID = true, $supportNonDecimalIPv4 = false)
  31. {
  32. return static::parseAddressString($address, 0 + ($mayIncludePort ? ParseStringFlag::MAY_INCLUDE_PORT : 0) + ($mayIncludeZoneID ? ParseStringFlag::MAY_INCLUDE_ZONEID : 0) + ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
  33. }
  34. /**
  35. * Parse an IP address string.
  36. *
  37. * @param string|mixed $address the address to parse
  38. * @param int $flags A combination or zero or more flags
  39. *
  40. * @return \IPLib\Address\AddressInterface|null
  41. *
  42. * @see \IPLib\ParseStringFlag
  43. * @since 1.17.0
  44. */
  45. public static function parseAddressString($address, $flags = 0)
  46. {
  47. if (($result = Address\IPv4::parseString($address, $flags)) !== null) {
  48. return $result;
  49. }
  50. if (($result = Address\IPv6::parseString($address, $flags)) !== null) {
  51. return $result;
  52. }
  53. return null;
  54. }
  55. /**
  56. * Convert a byte array to an address instance.
  57. *
  58. * @param array<int|mixed> $bytes
  59. *
  60. * @return \IPLib\Address\AddressInterface|null
  61. */
  62. public static function addressFromBytes(array $bytes)
  63. {
  64. if (($result = Address\IPv4::fromBytes($bytes)) !== null) {
  65. return $result;
  66. }
  67. if (($result = Address\IPv6::fromBytes($bytes)) !== null) {
  68. return $result;
  69. }
  70. return null;
  71. }
  72. /**
  73. * @deprecated since 1.17.0: use the parseRangeString() method instead.
  74. * For upgrading:
  75. * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
  76. *
  77. * @param string|mixed $range
  78. * @param bool $supportNonDecimalIPv4
  79. *
  80. * @return \IPLib\Range\RangeInterface|null
  81. *
  82. * @see \IPLib\Factory::parseRangeString()
  83. * @since 1.10.0 added the $supportNonDecimalIPv4 argument
  84. */
  85. public static function rangeFromString($range, $supportNonDecimalIPv4 = false)
  86. {
  87. return static::parseRangeString($range, $supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0);
  88. }
  89. /**
  90. * Parse an IP range string.
  91. *
  92. * @param string|mixed $range
  93. * @param int $flags A combination or zero or more flags
  94. *
  95. * @return \IPLib\Range\RangeInterface|null
  96. *
  97. * @see \IPLib\ParseStringFlag
  98. * @since 1.17.0
  99. */
  100. public static function parseRangeString($range, $flags = 0)
  101. {
  102. $result = Range\Subnet::parseString($range, $flags);
  103. if ($result === null) {
  104. $result = Range\Pattern::parseString($range, $flags);
  105. }
  106. if ($result === null) {
  107. $result = Range\Single::parseString($range, $flags);
  108. }
  109. return $result;
  110. }
  111. /**
  112. * @deprecated since 1.17.0: use the getRangeFromBoundaries() method instead.
  113. * For upgrading:
  114. * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
  115. *
  116. * @param string|\IPLib\Address\AddressInterface|mixed $from
  117. * @param string|\IPLib\Address\AddressInterface|mixed $to
  118. * @param bool $supportNonDecimalIPv4
  119. *
  120. * @return \IPLib\Range\RangeInterface|null
  121. *
  122. * @see \IPLib\Factory::getRangeFromBoundaries()
  123. * @since 1.2.0
  124. * @since 1.10.0 added the $supportNonDecimalIPv4 argument
  125. */
  126. public static function rangeFromBoundaries($from, $to, $supportNonDecimalIPv4 = false)
  127. {
  128. return static::getRangeFromBoundaries($from, $to, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
  129. }
  130. /**
  131. * Create the smallest address range that comprises two addresses.
  132. *
  133. * @param string|\IPLib\Address\AddressInterface|mixed $from
  134. * @param string|\IPLib\Address\AddressInterface|mixed $to
  135. * @param int $flags A combination or zero or more flags
  136. *
  137. * @return \IPLib\Range\RangeInterface|null return NULL if $from and/or $to are invalid addresses, or if both are NULL or empty strings, or if they are addresses of different types
  138. *
  139. * @see \IPLib\ParseStringFlag
  140. * @since 1.17.0
  141. */
  142. public static function getRangeFromBoundaries($from, $to, $flags = 0)
  143. {
  144. list($from, $to) = self::parseBoundaries($from, $to, $flags);
  145. return $from === false || $to === false ? null : static::rangeFromBoundaryAddresses($from, $to);
  146. }
  147. /**
  148. * Calculate the minimal range that contains all the specified addresses.
  149. *
  150. * @param array<non-empty-string|\IPLib\Address\AddressInterface|mixed> $addresses
  151. * @param int $flags
  152. *
  153. * @return \IPLib\Range\RangeInterface|null Returns NULL if $addresses is empty, if it contains invalid addresses, or if the addresses aren't compatible (for example, both IPv4 and IPv6 addresses)
  154. *
  155. * @since 1.22.0
  156. */
  157. public static function getRangeFromAddresses(array $addresses, $flags = 0)
  158. {
  159. $min = null;
  160. $max = null;
  161. $numberOfBits = null;
  162. foreach ($addresses as $address) {
  163. if (!$address instanceof AddressInterface) {
  164. $address = Factory::parseAddressString($address, $flags);
  165. if ($address === null) {
  166. return null;
  167. }
  168. }
  169. if ($numberOfBits === null) {
  170. $min = $address;
  171. $max = $address;
  172. $numberOfBits = $address->getNumberOfBits();
  173. } elseif ($numberOfBits !== $address->getNumberOfBits()) {
  174. return null;
  175. } else {
  176. /** @var AddressInterface $min */
  177. /** @var AddressInterface $max */
  178. $comparable = $address->getComparableString();
  179. if ($min->getComparableString() > $comparable) {
  180. $min = $address;
  181. }
  182. if ($max->getComparableString() < $comparable) {
  183. $max = $address;
  184. }
  185. }
  186. }
  187. if ($numberOfBits === null) {
  188. return null;
  189. }
  190. return static::rangeFromBoundaryAddresses($min, $max);
  191. }
  192. /**
  193. * @deprecated since 1.17.0: use the getRangesFromBoundaries() method instead.
  194. * For upgrading:
  195. * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
  196. *
  197. * @param string|\IPLib\Address\AddressInterface|mixed $from
  198. * @param string|\IPLib\Address\AddressInterface|mixed $to
  199. * @param bool $supportNonDecimalIPv4
  200. *
  201. * @return \IPLib\Range\Subnet[]|null
  202. *
  203. * @see \IPLib\Factory::getRangesFromBoundaries()
  204. * @since 1.14.0
  205. */
  206. public static function rangesFromBoundaries($from, $to, $supportNonDecimalIPv4 = false)
  207. {
  208. return static::getRangesFromBoundaries($from, $to, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
  209. }
  210. /**
  211. * Create a list of Range instances that exactly describes all the addresses between the two provided addresses.
  212. *
  213. * @param string|\IPLib\Address\AddressInterface|mixed $from
  214. * @param string|\IPLib\Address\AddressInterface|mixed $to
  215. * @param int $flags A combination or zero or more flags
  216. *
  217. * @return \IPLib\Range\Subnet[]|null return NULL if $from and/or $to are invalid addresses, or if both are NULL or empty strings, or if they are addresses of different types
  218. *
  219. * @see \IPLib\ParseStringFlag
  220. * @since 1.17.0
  221. */
  222. public static function getRangesFromBoundaries($from, $to, $flags = 0)
  223. {
  224. list($from, $to) = self::parseBoundaries($from, $to, $flags);
  225. if ($from === false || $to === false || ($from === null && $to === null)) {
  226. return null;
  227. }
  228. if ($from === null || $to === null) {
  229. $address = $from ? $from : $to;
  230. /** @var AddressInterface $address */
  231. return array(new Subnet($address, $address, $address->getNumberOfBits()));
  232. }
  233. $numberOfBits = $from->getNumberOfBits();
  234. if ($to->getNumberOfBits() !== $numberOfBits) {
  235. return null;
  236. }
  237. $calculator = new RangesFromBoundaryCalculator($numberOfBits);
  238. return $calculator->getRanges($from, $to);
  239. }
  240. /**
  241. * @param \IPLib\Address\AddressInterface|null $from
  242. * @param \IPLib\Address\AddressInterface|null $to
  243. *
  244. * @return \IPLib\Range\RangeInterface|null
  245. *
  246. * @since 1.2.0
  247. */
  248. protected static function rangeFromBoundaryAddresses($from = null, $to = null)
  249. {
  250. if (!$from instanceof AddressInterface && !$to instanceof AddressInterface) {
  251. $result = null;
  252. } elseif (!$to instanceof AddressInterface) {
  253. $result = Range\Single::fromAddress($from);
  254. } elseif (!$from instanceof AddressInterface) {
  255. $result = Range\Single::fromAddress($to);
  256. } else {
  257. $result = null;
  258. $addressType = $from->getAddressType();
  259. if ($addressType === $to->getAddressType()) {
  260. $cmp = strcmp($from->getComparableString(), $to->getComparableString());
  261. if ($cmp === 0) {
  262. $result = Range\Single::fromAddress($from);
  263. } else {
  264. if ($cmp > 0) {
  265. list($from, $to) = array($to, $from);
  266. }
  267. $fromBytes = $from->getBytes();
  268. $toBytes = $to->getBytes();
  269. $numBytes = count($fromBytes);
  270. $sameBits = 0;
  271. for ($byteIndex = 0; $byteIndex < $numBytes; $byteIndex++) {
  272. $fromByte = $fromBytes[$byteIndex];
  273. $toByte = $toBytes[$byteIndex];
  274. if ($fromByte === $toByte) {
  275. $sameBits += 8;
  276. } else {
  277. $differentBitsInByte = decbin($fromByte ^ $toByte);
  278. $sameBits += 8 - strlen($differentBitsInByte);
  279. break;
  280. }
  281. }
  282. $result = static::parseRangeString($from->toString() . '/' . (string) $sameBits);
  283. }
  284. }
  285. }
  286. return $result;
  287. }
  288. /**
  289. * @param string|\IPLib\Address\AddressInterface|mixed $from
  290. * @param string|\IPLib\Address\AddressInterface|mixed $to
  291. * @param int $flags
  292. *
  293. * @return array{\IPLib\Address\AddressInterface|false|null, \IPLib\Address\AddressInterface|false|null}
  294. */
  295. private static function parseBoundaries($from, $to, $flags = 0)
  296. {
  297. $result = array();
  298. foreach (array('from', 'to') as $param) {
  299. $value = $$param;
  300. if (!($value instanceof AddressInterface)) {
  301. $value = is_object($value) && method_exists($value, '__toString') || is_scalar($value) ? (string) $value : '';
  302. if ($value === '') {
  303. $value = null;
  304. } else {
  305. $value = static::parseAddressString($value, $flags);
  306. if ($value === null) {
  307. $value = false;
  308. }
  309. }
  310. }
  311. $result[] = $value;
  312. }
  313. /** @var array{\IPLib\Address\AddressInterface|false|null, \IPLib\Address\AddressInterface|false|null} $result */
  314. if ($result[0] && $result[1] && strcmp($result[0]->getComparableString(), $result[1]->getComparableString()) > 0) {
  315. $result = array($result[1], $result[0]);
  316. }
  317. return $result;
  318. }
  319. }