Pattern.php 9.8 KB

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