Pattern.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <?php
  2. namespace IPLib\Range;
  3. use IPLib\Address\AddressInterface;
  4. use IPLib\Address\IPv4;
  5. use IPLib\Address\IPv6;
  6. use IPLib\Address\Type as AddressType;
  7. use IPLib\ParseStringFlag;
  8. use IPLib\Service\BinaryMath;
  9. /**
  10. * Represents an address range in pattern format (only ending asterisks are supported).
  11. *
  12. * @example 127.0.*.*
  13. * @example ::/8
  14. *
  15. * @phpstan-consistent-constructor
  16. */
  17. class Pattern extends AbstractRange
  18. {
  19. /**
  20. * Starting address of the range.
  21. *
  22. * @var \IPLib\Address\AddressInterface
  23. */
  24. protected $fromAddress;
  25. /**
  26. * Final address of the range.
  27. *
  28. * @var \IPLib\Address\AddressInterface
  29. */
  30. protected $toAddress;
  31. /**
  32. * Number of ending asterisks.
  33. *
  34. * @var int
  35. */
  36. protected $asterisksCount;
  37. /**
  38. * The type of the range of this IP range.
  39. *
  40. * @var int|false|null false if this range crosses multiple range types, null if yet to be determined
  41. *
  42. * @since 1.5.0
  43. */
  44. protected $rangeType;
  45. /**
  46. * Initializes the instance.
  47. *
  48. * @param \IPLib\Address\AddressInterface $fromAddress
  49. * @param \IPLib\Address\AddressInterface $toAddress
  50. * @param int $asterisksCount
  51. */
  52. public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $asterisksCount)
  53. {
  54. $this->fromAddress = $fromAddress;
  55. $this->toAddress = $toAddress;
  56. $this->asterisksCount = $asterisksCount;
  57. }
  58. /**
  59. * {@inheritdoc}
  60. *
  61. * @see \IPLib\Range\RangeInterface::__toString()
  62. */
  63. public function __toString()
  64. {
  65. return $this->toString();
  66. }
  67. /**
  68. * @deprecated since 1.17.0: use the parseString() method instead.
  69. * For upgrading:
  70. * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
  71. *
  72. * @param string|mixed $range
  73. * @param bool $supportNonDecimalIPv4
  74. *
  75. * @return static|null
  76. *
  77. * @see \IPLib\Range\Pattern::parseString()
  78. * @since 1.10.0 added the $supportNonDecimalIPv4 argument
  79. */
  80. public static function fromString($range, $supportNonDecimalIPv4 = false)
  81. {
  82. return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
  83. }
  84. /**
  85. * Try get the range instance starting from its string representation.
  86. *
  87. * @param string|mixed $range
  88. * @param int $flags A combination or zero or more flags
  89. *
  90. * @return static|null
  91. *
  92. * @since 1.17.0
  93. * @see \IPLib\ParseStringFlag
  94. */
  95. public static function parseString($range, $flags = 0)
  96. {
  97. if (!is_string($range) || strpos($range, '*') === false) {
  98. return null;
  99. }
  100. if ($range === '*.*.*.*') {
  101. $fromAddress = IPv4::parseString('0.0.0.0');
  102. /** @var \IPLib\Address\IPv4 $fromAddress */
  103. $toAddress = IPv4::parseString('255.255.255.255');
  104. /** @var \IPLib\Address\IPv4 $toAddress */
  105. return new static($fromAddress, $toAddress, 4);
  106. }
  107. if ($range === '*:*:*:*:*:*:*:*') {
  108. $fromAddress = IPv6::parseString('::');
  109. /** @var \IPLib\Address\IPv6 $fromAddress */
  110. $toAddress = IPv6::parseString('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff');
  111. /** @var \IPLib\Address\IPv6 $toAddress */
  112. return new static($fromAddress, $toAddress, 8);
  113. }
  114. $matches = null;
  115. if (strpos($range, '.') !== false && preg_match('/^[^*]+((?:\.\*)+)$/D', $range, $matches)) {
  116. $asterisksCount = strlen($matches[1]) >> 1;
  117. if ($asterisksCount > 0) {
  118. $missingDots = 3 - substr_count($range, '.');
  119. if ($missingDots > 0) {
  120. $range .= str_repeat('.*', $missingDots);
  121. $asterisksCount += $missingDots;
  122. }
  123. }
  124. $fromAddress = IPv4::parseString(str_replace('*', '0', $range), $flags);
  125. if ($fromAddress === null) {
  126. return null;
  127. }
  128. $fixedBytes = array_slice($fromAddress->getBytes(), 0, -$asterisksCount);
  129. $otherBytes = array_fill(0, $asterisksCount, 255);
  130. $toAddress = IPv4::fromBytes(array_merge($fixedBytes, $otherBytes));
  131. /** @var \IPLib\Address\IPv4 $toAddress */
  132. return new static($fromAddress, $toAddress, $asterisksCount);
  133. }
  134. if (strpos($range, ':') !== false && preg_match('/^[^*]+((?::\*)+)$/D', $range, $matches)) {
  135. $asterisksCount = strlen($matches[1]) >> 1;
  136. $fromAddress = IPv6::parseString(str_replace('*', '0', $range), $flags);
  137. if ($fromAddress === null) {
  138. return null;
  139. }
  140. $fixedWords = array_slice($fromAddress->getWords(), 0, -$asterisksCount);
  141. $otherWords = array_fill(0, $asterisksCount, 0xffff);
  142. $toAddress = IPv6::fromWords(array_merge($fixedWords, $otherWords));
  143. /** @var \IPLib\Address\IPv6 $toAddress */
  144. return new static($fromAddress, $toAddress, $asterisksCount);
  145. }
  146. return null;
  147. }
  148. /**
  149. * {@inheritdoc}
  150. *
  151. * @see \IPLib\Range\RangeInterface::toString()
  152. */
  153. public function toString($long = false)
  154. {
  155. if ($this->asterisksCount === 0) {
  156. return $this->fromAddress->toString($long);
  157. }
  158. switch (true) {
  159. case $this->fromAddress instanceof \IPLib\Address\IPv4:
  160. $chunks = explode('.', $this->fromAddress->toString());
  161. $chunks = array_slice($chunks, 0, -$this->asterisksCount);
  162. $chunks = array_pad($chunks, 4, '*');
  163. $result = implode('.', $chunks);
  164. break;
  165. case $this->fromAddress instanceof \IPLib\Address\IPv6:
  166. if ($long) {
  167. $chunks = explode(':', $this->fromAddress->toString(true));
  168. $chunks = array_slice($chunks, 0, -$this->asterisksCount);
  169. $chunks = array_pad($chunks, 8, '*');
  170. $result = implode(':', $chunks);
  171. } elseif ($this->asterisksCount === 8) {
  172. $result = '*:*:*:*:*:*:*:*';
  173. } else {
  174. $numFixedWords = 8 - $this->asterisksCount;
  175. $chunks = array_map('dechex', array_slice($this->fromAddress->getWords(), 0, $numFixedWords));
  176. $result = implode(':', array_pad($chunks, 8, '*'));
  177. $matches = null;
  178. for ($i = $numFixedWords; $i > 1; $i--) {
  179. $search = '(?:^|:)' . rtrim(str_repeat('0:', $i), ':') . ':';
  180. if (preg_match('/^(.*?)' . $search . '(.*)$/', $result, $matches)) {
  181. $result = $matches[1] . '::' . $matches[2];
  182. break;
  183. }
  184. }
  185. }
  186. break;
  187. default:
  188. throw new \RuntimeException('Unsupported address type: ' . get_class($this->fromAddress)); // @codeCoverageIgnore
  189. }
  190. return $result;
  191. }
  192. /**
  193. * {@inheritdoc}
  194. *
  195. * @see \IPLib\Range\RangeInterface::getAddressType()
  196. */
  197. public function getAddressType()
  198. {
  199. return $this->fromAddress->getAddressType();
  200. }
  201. /**
  202. * {@inheritdoc}
  203. *
  204. * @see \IPLib\Range\RangeInterface::getStartAddress()
  205. */
  206. public function getStartAddress()
  207. {
  208. return $this->fromAddress;
  209. }
  210. /**
  211. * {@inheritdoc}
  212. *
  213. * @see \IPLib\Range\RangeInterface::getEndAddress()
  214. */
  215. public function getEndAddress()
  216. {
  217. return $this->toAddress;
  218. }
  219. /**
  220. * {@inheritdoc}
  221. *
  222. * @see \IPLib\Range\RangeInterface::getComparableStartString()
  223. */
  224. public function getComparableStartString()
  225. {
  226. return $this->fromAddress->getComparableString();
  227. }
  228. /**
  229. * {@inheritdoc}
  230. *
  231. * @see \IPLib\Range\RangeInterface::getComparableEndString()
  232. */
  233. public function getComparableEndString()
  234. {
  235. return $this->toAddress->getComparableString();
  236. }
  237. /**
  238. * {@inheritdoc}
  239. *
  240. * @see \IPLib\Range\RangeInterface::asSubnet()
  241. * @since 1.8.0
  242. */
  243. public function asSubnet()
  244. {
  245. return new Subnet($this->getStartAddress(), $this->getEndAddress(), $this->getNetworkPrefix());
  246. }
  247. /**
  248. * {@inheritdoc}
  249. *
  250. * @see \IPLib\Range\RangeInterface::asPattern()
  251. */
  252. public function asPattern()
  253. {
  254. return $this;
  255. }
  256. /**
  257. * {@inheritdoc}
  258. *
  259. * @see \IPLib\Range\RangeInterface::getSubnetMask()
  260. */
  261. public function getSubnetMask()
  262. {
  263. if ($this->getAddressType() !== AddressType::T_IPv4) {
  264. return null;
  265. }
  266. switch ($this->asterisksCount) {
  267. case 0:
  268. $bytes = array(255, 255, 255, 255);
  269. break;
  270. case 4:
  271. $bytes = array(0, 0, 0, 0);
  272. break;
  273. default:
  274. $bytes = array_pad(array_fill(0, 4 - $this->asterisksCount, 255), 4, 0);
  275. break;
  276. }
  277. return IPv4::fromBytes($bytes);
  278. }
  279. /**
  280. * {@inheritdoc}
  281. *
  282. * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
  283. */
  284. public function getReverseDNSLookupName()
  285. {
  286. return $this->asterisksCount === 0 ? array($this->getStartAddress()->getReverseDNSLookupName()) : $this->asSubnet()->getReverseDNSLookupName();
  287. }
  288. /**
  289. * {@inheritdoc}
  290. *
  291. * @see \IPLib\Range\RangeInterface::getSize()
  292. */
  293. public function getSize()
  294. {
  295. $fromAddress = $this->fromAddress;
  296. $maxPrefix = $fromAddress::getNumberOfBits();
  297. $prefix = $this->getNetworkPrefix();
  298. return pow(2, $maxPrefix - $prefix);
  299. }
  300. /**
  301. * {@inheritdoc}
  302. *
  303. * @see \IPLib\Range\RangeInterface::getExactSize()
  304. */
  305. public function getExactSize()
  306. {
  307. $fromAddress = $this->fromAddress;
  308. $maxPrefix = $fromAddress::getNumberOfBits();
  309. $prefix = $this->getNetworkPrefix();
  310. return BinaryMath::getInstance()->pow2string($maxPrefix - $prefix);
  311. }
  312. /**
  313. * {@inheritdoc}
  314. *
  315. * @see \IPLib\Range\RangeInterface::getNetworkPrefix()
  316. */
  317. public function getNetworkPrefix()
  318. {
  319. switch ($this->getAddressType()) {
  320. case AddressType::T_IPv4:
  321. return 8 * (4 - $this->asterisksCount);
  322. case AddressType::T_IPv6:
  323. return 16 * (8 - $this->asterisksCount);
  324. }
  325. }
  326. }