Skip to content

Commit

Permalink
Cleanup phpdoc and readme, raise stability requirements
Browse files Browse the repository at this point in the history
  • Loading branch information
samdark committed Mar 4, 2021
1 parent ca26ac0 commit faedc0a
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 61 deletions.
16 changes: 8 additions & 8 deletions README.md
Expand Up @@ -42,24 +42,24 @@ composer require yiisoft/network-utilities --prefer-dist
```php
use Yiisoft\NetworkUtilities\IpHelper;

// checking IP version
// Check IP version.
$version = IpHelper::getIpVersion('192.168.1.1');
if ($version === IpHelper::IPV4) {
// ...
}

// checking if IP is in a certain range
// Check if IP is in a certain range.
if (!IpHelper::inRange('192.168.1.21/32', '192.168.1.0/24')) {
throw new \RuntimeException('Access denied!');
}

// expanding IP v6
// Expand IP v6.
echo IpHelper::expandIPv6('2001:db8::1');

// converting IP to bits representation
// Convert IP to bits representation.
echo IpHelper::ip2bin('192.168.1.1');

// gets bits from CIDR Notation
// Get bits from CIDR Notation.
echo IpHelper::getCidrBits('192.168.1.21/32');
```

Expand All @@ -68,9 +68,9 @@ echo IpHelper::getCidrBits('192.168.1.21/32');
```php
use Yiisoft\NetworkUtilities\DnsHelper;

// checking DNS record availability
if(!DnsHelper::existsA('yiiframework.com')) {
// record not found
// Chec DNS record availability.
if (!DnsHelper::existsA('yiiframework.com')) {
// Record not found.
}
```

Expand Down
4 changes: 1 addition & 3 deletions composer.json
Expand Up @@ -46,7 +46,5 @@
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}
}
15 changes: 9 additions & 6 deletions src/DnsHelper.php
Expand Up @@ -6,14 +6,17 @@

use RuntimeException;

/**
* DnsHelper contains static methods to work with DNS.
*/
final class DnsHelper
{
/**
* Checks DNS MX record availability.
*
* @param string $hostname hostname without dot at end
* @param string $hostname Hostname without dot at end.
*
* @return bool
* @return bool Whether MX record exists.
*/
public static function existsMx(string $hostname): bool
{
Expand All @@ -37,9 +40,9 @@ static function (int $errorNumber, string $errorString) use ($hostname): ?bool {
/**
* Checks DNS A record availability.
*
* @param string $hostname
* @param string $hostname Hostname without dot at end.
*
* @return bool
* @return bool Whether A records exists.
*/
public static function existsA(string $hostname): bool
{
Expand All @@ -65,9 +68,9 @@ static function (int $errorNumber, string $errorString) use ($hostname): ?bool {
*
* @link https://tools.ietf.org/html/rfc5321#section-5
*
* @param string $hostnameOrEmail
* @param string $hostnameOrEmail Hostname without dot at end or an email.
*
* @return bool
* @return bool Whether email domain is available.
*/
public static function acceptsEmails(string $hostnameOrEmail): bool
{
Expand Down
63 changes: 36 additions & 27 deletions src/IpHelper.php
Expand Up @@ -4,14 +4,23 @@

namespace Yiisoft\NetworkUtilities;

use InvalidArgumentException;
use RuntimeException;
use function assert;
use function is_string;
use function strlen;

/**
* DnsHelper contains static methods to work with IPs.
*/
final class IpHelper
{
public const IPV4 = 4;
public const IPV6 = 6;

/**
* IPv4 address pattern. This pattern is PHP and JavaScript compatible.
* Allows to define your own IP regexp eg. `'/^'.IpHelper::IPV4_PATTERN.'/(\d+)$/'`
* Allows to define your own IP regexp eg. `'/^'.IpHelper::IPV4_PATTERN.'/(\d+)$/'`.
*/
public const IPV4_PATTERN = '((2(5[0-5]|[0-4]\d)|1\d{2}|[1-9]?\d)\.){3}(2(5[0-5]|[0-4]\d)|1\d{2}|[1-9]?\d)';
/**
Expand All @@ -20,39 +29,39 @@ final class IpHelper
public const IPV4_REGEXP = '/^' . self::IPV4_PATTERN . '$/';
/**
* IPv6 address pattern. This pattern is PHP and Javascript compatible.
* Allows to define your own IP regexp eg. `'/^'.IpHelper::IPV6_PATTERN.'/(\d+)$/'`
* Allows to define your own IP regexp eg. `'/^'.IpHelper::IPV6_PATTERN.'/(\d+)$/'`.
*/
public const IPV6_PATTERN = '(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:' . self::IPV4_PATTERN . ')';
/**
* IPv6 address regexp. This regexp is PHP and JavaScript compatible.
*/
public const IPV6_REGEXP = '/^' . self::IPV6_PATTERN . '$/';
/**
* The length of IPv6 address in bits
* The length of IPv6 address in bits.
*/
public const IPV6_ADDRESS_LENGTH = 128;
/**
* The length of IPv4 address in bits
* The length of IPv4 address in bits.
*/
public const IPV4_ADDRESS_LENGTH = 32;

/**
* Gets the IP version.
*
* @param string $ip the valid IPv4 or IPv6 address.
* @param bool $validate enable perform IP address validation. False is best practice if the data comes from a trusted source.
* @param string $ip The valid IPv4 or IPv6 address.
* @param bool $validate Enable perform IP address validation. False is best practice if the data comes from a trusted source.
*
* @return int {@see IPV4} or {@see IPV6}
* @return int {@see IPV4} or {@see IPV6}.
*/
public static function getIpVersion(string $ip, bool $validate = true): int
{
$ipStringLength = strlen($ip);
if ($ipStringLength < 2) {
throw new \InvalidArgumentException("Unrecognized address $ip", 10);
throw new InvalidArgumentException("Unrecognized address $ip", 10);
}
$preIpVersion = strpos($ip, ':') === false ? self::IPV4 : self::IPV6;
if ($preIpVersion === self::IPV4 && $ipStringLength < 7) {
throw new \InvalidArgumentException("Unrecognized address $ip", 11);
throw new InvalidArgumentException("Unrecognized address $ip", 11);
}
if (!$validate) {
return $preIpVersion;
Expand All @@ -64,7 +73,7 @@ public static function getIpVersion(string $ip, bool $validate = true): int
if ($preIpVersion === self::IPV6 && preg_match(self::IPV6_REGEXP, $ip) === 1) {
return self::IPV6;
}
throw new \InvalidArgumentException("Unrecognized address $ip", 12);
throw new InvalidArgumentException("Unrecognized address $ip.", 12);
}

/**
Expand All @@ -84,10 +93,10 @@ public static function getIpVersion(string $ip, bool $validate = true): int
* IpHelper::inRange('192.168.1.21/32', '192.168.1.0/24'); // true
* ```
*
* @param string $subnet the valid IPv4 or IPv6 address or CIDR range, e.g.: `10.0.0.0/8` or `2001:af::/64`
* @param string $range the valid IPv4 or IPv6 CIDR range, e.g. `10.0.0.0/8` or `2001:af::/64`
* @param string $subnet The valid IPv4 or IPv6 address or CIDR range, e.g.: `10.0.0.0/8` or `2001:af::/64`.
* @param string $range The valid IPv4 or IPv6 CIDR range, e.g. `10.0.0.0/8` or `2001:af::/64`.
*
* @return bool whether $subnet is contained by $range
* @return bool Whether $subnet is contained by $range.
*
* @see https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
*/
Expand Down Expand Up @@ -119,20 +128,20 @@ public static function inRange(string $subnet, string $range): bool
/**
* Expands an IPv6 address to it's full notation.
*
* For example `2001:db8::1` will be expanded to `2001:0db8:0000:0000:0000:0000:0000:0001`
* For example `2001:db8::1` will be expanded to `2001:0db8:0000:0000:0000:0000:0000:0001`.
*
* @param string $ip the original valid IPv6 address
* @param string $ip The original valid IPv6 address.
*
* @return string the expanded IPv6 address
* @return string The expanded IPv6 address.
*/
public static function expandIPv6(string $ip): string
{
$ipRaw = @inet_pton($ip);
if ($ipRaw === false) {
if (@inet_pton('::1') === false) {
throw new \RuntimeException('IPv6 is not supported by inet_pton()!');
throw new RuntimeException('IPv6 is not supported by inet_pton()!');
}
throw new \InvalidArgumentException("Unrecognized address $ip");
throw new InvalidArgumentException("Unrecognized address $ip.");
}
$hex = unpack('H*hex', $ipRaw);
return substr(preg_replace('/([a-f0-9]{4})/i', '$1:', $hex['hex']), 0, -1);
Expand All @@ -141,16 +150,16 @@ public static function expandIPv6(string $ip): string
/**
* Converts IP address to bits representation.
*
* @param string $ip the valid IPv4 or IPv6 address
* @param string $ip The valid IPv4 or IPv6 address.
*
* @return string bits as a string
* @return string Bits as a string.
*/
public static function ip2bin(string $ip): string
{
if (self::getIpVersion($ip) === self::IPV4) {
$ipBinary = pack('N', ip2long($ip));
} elseif (@inet_pton('::1') === false) {
throw new \RuntimeException('IPv6 is not supported by inet_pton()!');
throw new RuntimeException('IPv6 is not supported by inet_pton()!');
} else {
$ipBinary = inet_pton($ip);
}
Expand All @@ -161,7 +170,7 @@ public static function ip2bin(string $ip): string
for ($i = 0, $iMax = strlen($ipBinary); $i < $iMax; $i += 4) {
$data = substr($ipBinary, $i, 4);
if (!is_string($data)) {
throw new \RuntimeException('An error occurred while converting IP address to bits representation.');
throw new RuntimeException('An error occurred while converting IP address to bits representation.');
}
$result .= str_pad(decbin(unpack('N', $data)[1]), 32, '0', STR_PAD_LEFT);
}
Expand All @@ -171,14 +180,14 @@ public static function ip2bin(string $ip): string
/**
* Gets the bits from CIDR Notation.
*
* @param string $ip IP or IP with CIDR Notation (`127.0.0.1`, `2001:db8:a::123/64`)
* @param string $ip IP or IP with CIDR Notation (`127.0.0.1`, `2001:db8:a::123/64`).
*
* @return int
* @return int Bits.
*/
public static function getCidrBits(string $ip): int
{
if (preg_match('/^(?<ip>.{2,}?)(?:\/(?<bits>-?\d+))?$/', $ip, $matches) === 0) {
throw new \InvalidArgumentException("Unrecognized address $ip", 1);
throw new InvalidArgumentException("Unrecognized address $ip.", 1);
}
$ipVersion = self::getIpVersion($matches['ip']);
$maxBits = $ipVersion === self::IPV6 ? self::IPV6_ADDRESS_LENGTH : self::IPV4_ADDRESS_LENGTH;
Expand All @@ -188,10 +197,10 @@ public static function getCidrBits(string $ip): int
}
$bits = (int)$bits;
if ($bits < 0) {
throw new \InvalidArgumentException('The number of CIDR bits cannot be negative', 2);
throw new InvalidArgumentException('The number of CIDR bits cannot be negative.', 2);
}
if ($bits > $maxBits) {
throw new \InvalidArgumentException("CIDR bits is greater than $bits", 3);
throw new InvalidArgumentException("CIDR bits is greater than $bits.", 3);
}
return $bits;
}
Expand Down
35 changes: 18 additions & 17 deletions tests/IpHelperTest.php
Expand Up @@ -4,6 +4,7 @@

namespace Yiisoft\NetworkUtilities\Tests;

use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use Yiisoft\NetworkUtilities\IpHelper;

Expand All @@ -26,21 +27,21 @@ public function testGetIpVersion(string $value, bool $validation, ?int $expected
public function getIpVersionProvider(): array
{
return [
'emptyString' => ['', false, null, \InvalidArgumentException::class],
'emptyStringValidate' => ['', true, null, \InvalidArgumentException::class],
'tooShort' => ['1', false, null, \InvalidArgumentException::class],
'tooShortValidate' => ['1', true, null, \InvalidArgumentException::class],
'emptyString' => ['', false, null, InvalidArgumentException::class],
'emptyStringValidate' => ['', true, null, InvalidArgumentException::class],
'tooShort' => ['1', false, null, InvalidArgumentException::class],
'tooShortValidate' => ['1', true, null, InvalidArgumentException::class],
'ipv4Minimal' => ['0.0.0.0', false, IpHelper::IPV4],
'ipv4TooShort' => ['0.0.0.', false, null, \InvalidArgumentException::class],
'ipv4TooShort' => ['0.0.0.', false, null, InvalidArgumentException::class],
'ipv4' => ['192.168.0.1', false, IpHelper::IPV4],
'ipv4Max' => ['255.255.255.255', false, IpHelper::IPV4],
'ipv4MaxValidation' => ['255.255.255.255', true, IpHelper::IPV4],
'ipv4OverMax' => ['255.255.255.256', true, null, \InvalidArgumentException::class],
'ipv4OverMax' => ['255.255.255.256', true, null, InvalidArgumentException::class],
'ipv4Cidr' => ['192.168.0.1/24', false, IpHelper::IPV4, null, 'IPv4 with CIDR is resolved correctly'],
'ipv4CidrValidation' => ['192.168.0.1/24', true, null, \InvalidArgumentException::class],
'ipv4CidrValidation' => ['192.168.0.1/24', true, null, InvalidArgumentException::class],
'ipv6' => ['fb01::1', false, IpHelper::IPV6],
'ipv6Cidr' => ['fb01::1/24', false, IpHelper::IPV6, null, 'IPv6 with CIDR is resolved correctly'],
'ipv6CidrValidation' => ['fb01::1/24', true, null, \InvalidArgumentException::class],
'ipv6CidrValidation' => ['fb01::1/24', true, null, InvalidArgumentException::class],
'ipv6Minimal' => ['::', false, IpHelper::IPV6],
'ipv6MinimalValidation' => ['::', true, IpHelper::IPV6],
'ipv6MappedIpv4' => ['::ffff:192.168.0.2', false, IpHelper::IPV6],
Expand Down Expand Up @@ -69,7 +70,7 @@ public function expandIpv6Provider(): array

public function testIpv6ExpandingWithInvalidValue(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectException(InvalidArgumentException::class);
IpHelper::expandIPv6('fa01::1/64');
}

Expand Down Expand Up @@ -121,25 +122,25 @@ public function inRangeProvider(): array
public function getCidrBitsDataProvider(): array
{
return [
'invalidEmpty' => ['', null, \InvalidArgumentException::class],
'invalidTooShort' => ['1', null, \InvalidArgumentException::class],
'invalidIp' => ['999.999.999.999', null, \InvalidArgumentException::class],
'invalidIpCidr' => ['999.999.999.999/22', null, \InvalidArgumentException::class],
'invalidEmpty' => ['', null, InvalidArgumentException::class],
'invalidTooShort' => ['1', null, InvalidArgumentException::class],
'invalidIp' => ['999.999.999.999', null, InvalidArgumentException::class],
'invalidIpCidr' => ['999.999.999.999/22', null, InvalidArgumentException::class],
'shortestIp' => ['::', 128],
'ipv4' => ['127.0.0.1', 32],
'ipv6' => ['::1', 128],
'ipv4-negative' => ['127.0.0.1/-1', null, \InvalidArgumentException::class],
'ipv4-negative' => ['127.0.0.1/-1', null, InvalidArgumentException::class],
'ipv4-min' => ['127.0.0.1/0', 0],
'ipv4-normal' => ['127.0.0.1/13', 13],
'ipv4-max' => ['127.0.0.1/32', 32],
'ipv4-overflow' => ['127.0.0.1/33', null, \InvalidArgumentException::class],
'ipv6-negative' => ['::1/-1', null, \InvalidArgumentException::class],
'ipv4-overflow' => ['127.0.0.1/33', null, InvalidArgumentException::class],
'ipv6-negative' => ['::1/-1', null, InvalidArgumentException::class],
'ipv6-min' => ['::1/0', 0],
'ipv6-normal' => ['::1/72', 72],
'ipv6-normalExpanded' => ['2001:0db8:85a3:0000:0000:8a2e:0370:7334/23', 23],
'ipv6-normalIpv4Mapped' => ['::ffff:192.0.2.128/109', 109],
'ipv6-max' => ['::1/128', 128],
'ipv6-overflow' => ['::1/129', null, \InvalidArgumentException::class],
'ipv6-overflow' => ['::1/129', null, InvalidArgumentException::class],
];
}

Expand Down

0 comments on commit faedc0a

Please sign in to comment.