Pattern.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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. /**
  8. * Represents an address range in pattern format (only ending asterisks are supported).
  9. *
  10. * @example 127.0.*.*
  11. * @example ::/8
  12. */
  13. class Pattern extends AbstractRange
  14. {
  15. /**
  16. * Starting address of the range.
  17. *
  18. * @var \IPLib\Address\AddressInterface
  19. */
  20. protected $fromAddress;
  21. /**
  22. * Final address of the range.
  23. *
  24. * @var \IPLib\Address\AddressInterface
  25. */
  26. protected $toAddress;
  27. /**
  28. * Number of ending asterisks.
  29. *
  30. * @var int
  31. */
  32. protected $asterisksCount;
  33. /**
  34. * The type of the range of this IP range.
  35. *
  36. * @var int|false|null false if this range crosses multiple range types, null if yet to be determined
  37. */
  38. protected $rangeType;
  39. /**
  40. * Initializes the instance.
  41. *
  42. * @param \IPLib\Address\AddressInterface $fromAddress
  43. * @param \IPLib\Address\AddressInterface $toAddress
  44. * @param int $asterisksCount
  45. */
  46. public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $asterisksCount)
  47. {
  48. $this->fromAddress = $fromAddress;
  49. $this->toAddress = $toAddress;
  50. $this->asterisksCount = $asterisksCount;
  51. }
  52. /**
  53. * {@inheritdoc}
  54. *
  55. * @see \IPLib\Range\RangeInterface::__toString()
  56. */
  57. public function __toString()
  58. {
  59. return $this->toString();
  60. }
  61. /**
  62. * Try get the range instance starting from its string representation.
  63. *
  64. * @param string|mixed $range
  65. * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
  66. *
  67. * @return static|null
  68. */
  69. public static function fromString($range, $supportNonDecimalIPv4 = false)
  70. {
  71. if (!is_string($range) || strpos($range, '*') === false) {
  72. return null;
  73. }
  74. if ($range === '*.*.*.*') {
  75. return new static(IPv4::fromString('0.0.0.0'), IPv4::fromString('255.255.255.255'), 4);
  76. }
  77. if ($range === '*:*:*:*:*:*:*:*') {
  78. return new static(IPv6::fromString('::'), IPv6::fromString('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), 8);
  79. }
  80. $matches = null;
  81. if (strpos($range, '.') !== false && preg_match('/^[^*]+((?:\.\*)+)$/', $range, $matches)) {
  82. $asterisksCount = strlen($matches[1]) >> 1;
  83. if ($asterisksCount > 0) {
  84. $missingDots = 3 - substr_count($range, '.');
  85. if ($missingDots > 0) {
  86. $range .= str_repeat('.*', $missingDots);
  87. $asterisksCount += $missingDots;
  88. }
  89. }
  90. $fromAddress = IPv4::fromString(str_replace('*', '0', $range), true, $supportNonDecimalIPv4);
  91. if ($fromAddress === null) {
  92. return null;
  93. }
  94. $fixedBytes = array_slice($fromAddress->getBytes(), 0, -$asterisksCount);
  95. $otherBytes = array_fill(0, $asterisksCount, 255);
  96. $toAddress = IPv4::fromBytes(array_merge($fixedBytes, $otherBytes));
  97. return new static($fromAddress, $toAddress, $asterisksCount);
  98. }
  99. if (strpos($range, ':') !== false && preg_match('/^[^*]+((?::\*)+)$/', $range, $matches)) {
  100. $asterisksCount = strlen($matches[1]) >> 1;
  101. $fromAddress = IPv6::fromString(str_replace('*', '0', $range));
  102. if ($fromAddress === null) {
  103. return null;
  104. }
  105. $fixedWords = array_slice($fromAddress->getWords(), 0, -$asterisksCount);
  106. $otherWords = array_fill(0, $asterisksCount, 0xffff);
  107. $toAddress = IPv6::fromWords(array_merge($fixedWords, $otherWords));
  108. return new static($fromAddress, $toAddress, $asterisksCount);
  109. }
  110. return null;
  111. }
  112. /**
  113. * {@inheritdoc}
  114. *
  115. * @see \IPLib\Range\RangeInterface::toString()
  116. */
  117. public function toString($long = false)
  118. {
  119. if ($this->asterisksCount === 0) {
  120. return $this->fromAddress->toString($long);
  121. }
  122. switch (true) {
  123. case $this->fromAddress instanceof \IPLib\Address\IPv4:
  124. $chunks = explode('.', $this->fromAddress->toString());
  125. $chunks = array_slice($chunks, 0, -$this->asterisksCount);
  126. $chunks = array_pad($chunks, 4, '*');
  127. $result = implode('.', $chunks);
  128. break;
  129. case $this->fromAddress instanceof \IPLib\Address\IPv6:
  130. if ($long) {
  131. $chunks = explode(':', $this->fromAddress->toString(true));
  132. $chunks = array_slice($chunks, 0, -$this->asterisksCount);
  133. $chunks = array_pad($chunks, 8, '*');
  134. $result = implode(':', $chunks);
  135. } elseif ($this->asterisksCount === 8) {
  136. $result = '*:*:*:*:*:*:*:*';
  137. } else {
  138. $bytes = $this->toAddress->getBytes();
  139. $bytes = array_slice($bytes, 0, -$this->asterisksCount * 2);
  140. $bytes = array_pad($bytes, 16, 1);
  141. $address = IPv6::fromBytes($bytes);
  142. $before = substr($address->toString(false), 0, -strlen(':101') * $this->asterisksCount);
  143. $result = $before . str_repeat(':*', $this->asterisksCount);
  144. }
  145. break;
  146. default:
  147. throw new \Exception('@todo'); // @codeCoverageIgnore
  148. }
  149. return $result;
  150. }
  151. /**
  152. * {@inheritdoc}
  153. *
  154. * @see \IPLib\Range\RangeInterface::getAddressType()
  155. */
  156. public function getAddressType()
  157. {
  158. return $this->fromAddress->getAddressType();
  159. }
  160. /**
  161. * {@inheritdoc}
  162. *
  163. * @see \IPLib\Range\RangeInterface::getStartAddress()
  164. */
  165. public function getStartAddress()
  166. {
  167. return $this->fromAddress;
  168. }
  169. /**
  170. * {@inheritdoc}
  171. *
  172. * @see \IPLib\Range\RangeInterface::getEndAddress()
  173. */
  174. public function getEndAddress()
  175. {
  176. return $this->toAddress;
  177. }
  178. /**
  179. * {@inheritdoc}
  180. *
  181. * @see \IPLib\Range\RangeInterface::getComparableStartString()
  182. */
  183. public function getComparableStartString()
  184. {
  185. return $this->fromAddress->getComparableString();
  186. }
  187. /**
  188. * {@inheritdoc}
  189. *
  190. * @see \IPLib\Range\RangeInterface::getComparableEndString()
  191. */
  192. public function getComparableEndString()
  193. {
  194. return $this->toAddress->getComparableString();
  195. }
  196. /**
  197. * {@inheritdoc}
  198. *
  199. * @see \IPLib\Range\RangeInterface::asSubnet()
  200. */
  201. public function asSubnet()
  202. {
  203. switch ($this->getAddressType()) {
  204. case AddressType::T_IPv4:
  205. return new Subnet($this->getStartAddress(), $this->getEndAddress(), 8 * (4 - $this->asterisksCount));
  206. case AddressType::T_IPv6:
  207. return new Subnet($this->getStartAddress(), $this->getEndAddress(), 16 * (8 - $this->asterisksCount));
  208. }
  209. }
  210. /**
  211. * {@inheritdoc}
  212. *
  213. * @see \IPLib\Range\RangeInterface::asPattern()
  214. */
  215. public function asPattern()
  216. {
  217. return $this;
  218. }
  219. /**
  220. * {@inheritdoc}
  221. *
  222. * @see \IPLib\Range\RangeInterface::getSubnetMask()
  223. */
  224. public function getSubnetMask()
  225. {
  226. if ($this->getAddressType() !== AddressType::T_IPv4) {
  227. return null;
  228. }
  229. switch ($this->asterisksCount) {
  230. case 0:
  231. $bytes = array(255, 255, 255, 255);
  232. break;
  233. case 4:
  234. $bytes = array(0, 0, 0, 0);
  235. break;
  236. default:
  237. $bytes = array_pad(array_fill(0, 4 - $this->asterisksCount, 255), 4, 0);
  238. break;
  239. }
  240. return IPv4::fromBytes($bytes);
  241. }
  242. /**
  243. * {@inheritdoc}
  244. *
  245. * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
  246. */
  247. public function getReverseDNSLookupName()
  248. {
  249. return $this->asterisksCount === 0 ? array($this->getStartAddress()->getReverseDNSLookupName()) : $this->asSubnet()->getReverseDNSLookupName();
  250. }
  251. }