| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 |
- <?php
- namespace IPLib\Address;
- use IPLib\ParseStringFlag;
- use IPLib\Range\RangeInterface;
- use IPLib\Range\Subnet;
- use IPLib\Range\Type as RangeType;
- /**
- * An IPv4 address.
- */
- class IPv4 implements AddressInterface
- {
- /**
- * The string representation of the address.
- *
- * @var string
- *
- * @example '127.0.0.1'
- */
- protected $address;
- /**
- * The byte list of the IP address.
- *
- * @var int[]|null
- */
- protected $bytes;
- /**
- * The type of the range of this IP address.
- *
- * @var int|null
- */
- protected $rangeType;
- /**
- * A string representation of this address than can be used when comparing addresses and ranges.
- *
- * @var string
- */
- protected $comparableString;
- /**
- * An array containing RFC designated address ranges.
- *
- * @var array|null
- */
- private static $reservedRanges;
- /**
- * Initializes the instance.
- *
- * @param string $address
- */
- protected function __construct($address)
- {
- $this->address = $address;
- $this->bytes = null;
- $this->rangeType = null;
- $this->comparableString = null;
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::__toString()
- */
- public function __toString()
- {
- return $this->address;
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getNumberOfBits()
- */
- public static function getNumberOfBits()
- {
- return 32;
- }
- /**
- * @deprecated since 1.17.0: use the parseString() method instead.
- * For upgrading:
- * - if $mayIncludePort is true, use the ParseStringFlag::MAY_INCLUDE_PORT flag
- * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
- *
- * @param string|mixed $address the address to parse
- * @param bool $mayIncludePort
- * @param bool $supportNonDecimalIPv4
- *
- * @return static|null
- *
- * @see \IPLib\Address\IPv4::parseString()
- * @since 1.1.0 added the $mayIncludePort argument
- * @since 1.10.0 added the $supportNonDecimalIPv4 argument
- */
- public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false)
- {
- return static::parseString($address, 0 | ($mayIncludePort ? ParseStringFlag::MAY_INCLUDE_PORT : 0) | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
- }
- /**
- * Parse a string and returns an IPv4 instance if the string is valid, or null otherwise.
- *
- * @param string|mixed $address the address to parse
- * @param int $flags A combination or zero or more flags
- *
- * @return static|null
- *
- * @see \IPLib\ParseStringFlag
- * @since 1.17.0
- */
- public static function parseString($address, $flags = 0)
- {
- if (!is_string($address)) {
- return null;
- }
- $flags = (int) $flags;
- $matches = null;
- if ($flags & ParseStringFlag::ADDRESS_MAYBE_RDNS) {
- if (preg_match('/^([12]?[0-9]{1,2}\.[12]?[0-9]{1,2}\.[12]?[0-9]{1,2}\.[12]?[0-9]{1,2})\.in-addr\.arpa\.?$/i', $address, $matches)) {
- $address = implode('.', array_reverse(explode('.', $matches[1])));
- $flags = $flags & ~(ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED);
- }
- }
- if ($flags & ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED) {
- if (strpos($address, '.') === 0) {
- return null;
- }
- $lengthNonHex = '{1,11}';
- $lengthHex = '{1,8}';
- $chunk234Optional = true;
- } else {
- if (!strpos($address, '.')) {
- return null;
- }
- $lengthNonHex = '{1,3}';
- $lengthHex = '{1,2}';
- $chunk234Optional = false;
- }
- $rxChunk1 = "0?[0-9]{$lengthNonHex}";
- if ($flags & ParseStringFlag::IPV4_MAYBE_NON_DECIMAL) {
- $rxChunk1 = "(?:0[Xx]0*[0-9A-Fa-f]{$lengthHex})|(?:{$rxChunk1})";
- $onlyDecimal = false;
- } else {
- $onlyDecimal = true;
- }
- $rxChunk1 = "0*?({$rxChunk1})";
- $rxChunk234 = "\.{$rxChunk1}";
- if ($chunk234Optional) {
- $rxChunk234 = "(?:{$rxChunk234})?";
- }
- $rx = "{$rxChunk1}{$rxChunk234}{$rxChunk234}{$rxChunk234}";
- if ($flags & ParseStringFlag::MAY_INCLUDE_PORT) {
- $rx .= '(?::\d+)?';
- }
- if (!preg_match('/^' . $rx . '$/', $address, $matches)) {
- return null;
- }
- $math = new \IPLib\Service\UnsignedIntegerMath();
- $nums = array();
- $maxChunkIndex = count($matches) - 1;
- for ($i = 1; $i <= $maxChunkIndex; $i++) {
- $numBytes = $i === $maxChunkIndex ? 5 - $i : 1;
- $chunkBytes = $math->getBytes($matches[$i], $numBytes, $onlyDecimal);
- if ($chunkBytes === null) {
- return null;
- }
- $nums = array_merge($nums, $chunkBytes);
- }
- return new static(implode('.', $nums));
- }
- /**
- * Parse an array of bytes and returns an IPv4 instance if the array is valid, or null otherwise.
- *
- * @param int[]|array $bytes
- *
- * @return static|null
- */
- public static function fromBytes(array $bytes)
- {
- $result = null;
- if (count($bytes) === 4) {
- $chunks = array_map(
- function ($byte) {
- return (is_int($byte) && $byte >= 0 && $byte <= 255) ? (string) $byte : false;
- },
- $bytes
- );
- if (in_array(false, $chunks, true) === false) {
- $result = new static(implode('.', $chunks));
- }
- }
- return $result;
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::toString()
- */
- public function toString($long = false)
- {
- if ($long) {
- return $this->getComparableString();
- }
- return $this->address;
- }
- /**
- * Get the octal representation of this IP address.
- *
- * @param bool $long
- *
- * @return string
- *
- * @since 1.10.0
- *
- * @example if $long == false: if the decimal representation is '0.7.8.255': '0.7.010.0377'
- * @example if $long == true: if the decimal representation is '0.7.8.255': '0000.0007.0010.0377'
- */
- public function toOctal($long = false)
- {
- $chunks = array();
- foreach ($this->getBytes() as $byte) {
- if ($long) {
- $chunks[] = sprintf('%04o', $byte);
- } else {
- $chunks[] = '0' . decoct($byte);
- }
- }
- return implode('.', $chunks);
- }
- /**
- * Get the hexadecimal representation of this IP address.
- *
- * @param bool $long
- *
- * @return string
- *
- * @since 1.10.0
- *
- * @example if $long == false: if the decimal representation is '0.9.10.255': '0.9.0xa.0xff'
- * @example if $long == true: if the decimal representation is '0.9.10.255': '0x00.0x09.0x0a.0xff'
- */
- public function toHexadecimal($long = false)
- {
- $chunks = array();
- foreach ($this->getBytes() as $byte) {
- if ($long) {
- $chunks[] = sprintf('0x%02x', $byte);
- } else {
- $chunks[] = '0x' . dechex($byte);
- }
- }
- return implode('.', $chunks);
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getBytes()
- */
- public function getBytes()
- {
- if ($this->bytes === null) {
- $this->bytes = array_map(
- function ($chunk) {
- return (int) $chunk;
- },
- explode('.', $this->address)
- );
- }
- return $this->bytes;
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getBits()
- */
- public function getBits()
- {
- $parts = array();
- foreach ($this->getBytes() as $byte) {
- $parts[] = sprintf('%08b', $byte);
- }
- return implode('', $parts);
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getAddressType()
- */
- public function getAddressType()
- {
- return Type::T_IPv4;
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
- */
- public static function getDefaultReservedRangeType()
- {
- return RangeType::T_PUBLIC;
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getReservedRanges()
- */
- public static function getReservedRanges()
- {
- if (self::$reservedRanges === null) {
- $reservedRanges = array();
- foreach (array(
- // RFC 5735
- '0.0.0.0/8' => array(RangeType::T_THISNETWORK, array('0.0.0.0/32' => RangeType::T_UNSPECIFIED)),
- // RFC 5735
- '10.0.0.0/8' => array(RangeType::T_PRIVATENETWORK),
- // RFC 6598
- '100.64.0.0/10' => array(RangeType::T_CGNAT),
- // RFC 5735
- '127.0.0.0/8' => array(RangeType::T_LOOPBACK),
- // RFC 5735
- '169.254.0.0/16' => array(RangeType::T_LINKLOCAL),
- // RFC 5735
- '172.16.0.0/12' => array(RangeType::T_PRIVATENETWORK),
- // RFC 5735
- '192.0.0.0/24' => array(RangeType::T_RESERVED),
- // RFC 5735
- '192.0.2.0/24' => array(RangeType::T_RESERVED),
- // RFC 5735
- '192.88.99.0/24' => array(RangeType::T_ANYCASTRELAY),
- // RFC 5735
- '192.168.0.0/16' => array(RangeType::T_PRIVATENETWORK),
- // RFC 5735
- '198.18.0.0/15' => array(RangeType::T_RESERVED),
- // RFC 5735
- '198.51.100.0/24' => array(RangeType::T_RESERVED),
- // RFC 5735
- '203.0.113.0/24' => array(RangeType::T_RESERVED),
- // RFC 5735
- '224.0.0.0/4' => array(RangeType::T_MULTICAST),
- // RFC 5735
- '240.0.0.0/4' => array(RangeType::T_RESERVED, array('255.255.255.255/32' => RangeType::T_LIMITEDBROADCAST)),
- ) as $range => $data) {
- $exceptions = array();
- if (isset($data[1])) {
- foreach ($data[1] as $exceptionRange => $exceptionType) {
- $exceptions[] = new AssignedRange(Subnet::parseString($exceptionRange), $exceptionType);
- }
- }
- $reservedRanges[] = new AssignedRange(Subnet::parseString($range), $data[0], $exceptions);
- }
- self::$reservedRanges = $reservedRanges;
- }
- return self::$reservedRanges;
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getRangeType()
- */
- public function getRangeType()
- {
- if ($this->rangeType === null) {
- $rangeType = null;
- foreach (static::getReservedRanges() as $reservedRange) {
- $rangeType = $reservedRange->getAddressType($this);
- if ($rangeType !== null) {
- break;
- }
- }
- $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
- }
- return $this->rangeType;
- }
- /**
- * Create an IPv6 representation of this address (in 6to4 notation).
- *
- * @return \IPLib\Address\IPv6
- */
- public function toIPv6()
- {
- $myBytes = $this->getBytes();
- return IPv6::parseString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::');
- }
- /**
- * Create an IPv6 representation of this address (in IPv6 IPv4-mapped notation).
- *
- * @return \IPLib\Address\IPv6
- *
- * @since 1.11.0
- */
- public function toIPv6IPv4Mapped()
- {
- return IPv6::fromBytes(array_merge(array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff), $this->getBytes()));
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getComparableString()
- */
- public function getComparableString()
- {
- if ($this->comparableString === null) {
- $chunks = array();
- foreach ($this->getBytes() as $byte) {
- $chunks[] = sprintf('%03d', $byte);
- }
- $this->comparableString = implode('.', $chunks);
- }
- return $this->comparableString;
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::matches()
- */
- public function matches(RangeInterface $range)
- {
- return $range->contains($this);
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getAddressAtOffset()
- */
- public function getAddressAtOffset($n)
- {
- if (!is_int($n)) {
- return null;
- }
- $boundary = 256;
- $mod = $n;
- $bytes = $this->getBytes();
- for ($i = count($bytes) - 1; $i >= 0; $i--) {
- $tmp = ($bytes[$i] + $mod) % $boundary;
- $mod = (int) floor(($bytes[$i] + $mod) / $boundary);
- if ($tmp < 0) {
- $tmp += $boundary;
- }
- $bytes[$i] = $tmp;
- }
- if ($mod !== 0) {
- return null;
- }
- return static::fromBytes($bytes);
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getNextAddress()
- */
- public function getNextAddress()
- {
- return $this->getAddressAtOffset(1);
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getPreviousAddress()
- */
- public function getPreviousAddress()
- {
- return $this->getAddressAtOffset(-1);
- }
- /**
- * {@inheritdoc}
- *
- * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
- */
- public function getReverseDNSLookupName()
- {
- return implode(
- '.',
- array_reverse($this->getBytes())
- ) . '.in-addr.arpa';
- }
- }
|