Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/3rdparty.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCôme Chilliet <come.chilliet@nextcloud.com>2022-09-20 13:06:02 +0300
committerCôme Chilliet <come.chilliet@nextcloud.com>2022-09-20 13:06:02 +0300
commit27cc868e57475a71b92cc037c869205765d36675 (patch)
treee06d90949ec1ff512abf7c6a6d7e0da44aa2a879
parentf143482ffb0b8dfdbc08cd848ce2e66f02a5d9b6 (diff)
Bump mlocati/ip-lib to 1.18
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
-rw-r--r--composer.json1
-rw-r--r--composer.lock73
-rw-r--r--composer/autoload_classmap.php16
-rw-r--r--composer/autoload_psr4.php1
-rw-r--r--composer/autoload_static.php21
-rw-r--r--composer/installed.json74
-rw-r--r--composer/installed.php13
-rw-r--r--mlocati/ip-lib/LICENSE.txt20
-rw-r--r--mlocati/ip-lib/README.md648
-rw-r--r--mlocati/ip-lib/composer.json60
-rw-r--r--mlocati/ip-lib/ip-lib.php13
-rw-r--r--mlocati/ip-lib/src/Address/AddressInterface.php156
-rw-r--r--mlocati/ip-lib/src/Address/AssignedRange.php140
-rw-r--r--mlocati/ip-lib/src/Address/IPv4.php515
-rw-r--r--mlocati/ip-lib/src/Address/IPv6.php608
-rw-r--r--mlocati/ip-lib/src/Address/Type.php44
-rw-r--r--mlocati/ip-lib/src/Factory.php298
-rw-r--r--mlocati/ip-lib/src/ParseStringFlag.php79
-rw-r--r--mlocati/ip-lib/src/Range/AbstractRange.php125
-rw-r--r--mlocati/ip-lib/src/Range/Pattern.php322
-rw-r--r--mlocati/ip-lib/src/Range/RangeInterface.php160
-rw-r--r--mlocati/ip-lib/src/Range/Single.php244
-rw-r--r--mlocati/ip-lib/src/Range/Subnet.php354
-rw-r--r--mlocati/ip-lib/src/Range/Type.php152
-rw-r--r--mlocati/ip-lib/src/Service/BinaryMath.php120
-rw-r--r--mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php168
-rw-r--r--mlocati/ip-lib/src/Service/UnsignedIntegerMath.php171
27 files changed, 4593 insertions, 3 deletions
diff --git a/composer.json b/composer.json
index 1dbdd417..7e1ed00f 100644
--- a/composer.json
+++ b/composer.json
@@ -33,6 +33,7 @@
"laravel/serializable-closure": "^1.2",
"mexitek/phpcolors": "^1.0",
"microsoft/azure-storage-blob": "^1.5",
+ "mlocati/ip-lib": "^1.18",
"nextcloud/lognormalizer": "^1.0",
"nikic/php-parser": "^4.2",
"opis/closure": "^3.6",
diff --git a/composer.lock b/composer.lock
index 2be33eee..9ceed2f2 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "682bfa95cb2df85d82d78f313aebd5fd",
+ "content-hash": "37d86728a17ad5fe98c1e8086a6c0c51",
"packages": [
{
"name": "aws/aws-sdk-php",
@@ -2239,6 +2239,77 @@
"time": "2020-12-28T07:59:51+00:00"
},
{
+ "name": "mlocati/ip-lib",
+ "version": "1.18.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mlocati/ip-lib.git",
+ "reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2",
+ "reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "ext-pdo_sqlite": "*",
+ "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.5 || ^9.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "IPLib\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michele Locati",
+ "email": "mlocati@gmail.com",
+ "homepage": "https://github.com/mlocati",
+ "role": "Author"
+ }
+ ],
+ "description": "Handle IPv4, IPv6 addresses and ranges",
+ "homepage": "https://github.com/mlocati/ip-lib",
+ "keywords": [
+ "IP",
+ "address",
+ "addresses",
+ "ipv4",
+ "ipv6",
+ "manage",
+ "managing",
+ "matching",
+ "network",
+ "networking",
+ "range",
+ "subnet"
+ ],
+ "support": {
+ "issues": "https://github.com/mlocati/ip-lib/issues",
+ "source": "https://github.com/mlocati/ip-lib/tree/1.18.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/mlocati",
+ "type": "github"
+ },
+ {
+ "url": "https://paypal.me/mlocati",
+ "type": "other"
+ }
+ ],
+ "time": "2022-01-13T18:05:33+00:00"
+ },
+ {
"name": "mtdowling/jmespath.php",
"version": "2.6.1",
"source": {
diff --git a/composer/autoload_classmap.php b/composer/autoload_classmap.php
index ad99ca64..31663882 100644
--- a/composer/autoload_classmap.php
+++ b/composer/autoload_classmap.php
@@ -1463,6 +1463,22 @@ return array(
'ID3Parser\\getID3\\getid3_exception' => $vendorDir . '/christophwurst/id3parser/src/getID3/getid3_exception.php',
'ID3Parser\\getID3\\getid3_handler' => $vendorDir . '/christophwurst/id3parser/src/getID3/getid3_handler.php',
'ID3Parser\\getID3\\getid3_lib' => $vendorDir . '/christophwurst/id3parser/src/getID3/getid3_lib.php',
+ 'IPLib\\Address\\AddressInterface' => $vendorDir . '/mlocati/ip-lib/src/Address/AddressInterface.php',
+ 'IPLib\\Address\\AssignedRange' => $vendorDir . '/mlocati/ip-lib/src/Address/AssignedRange.php',
+ 'IPLib\\Address\\IPv4' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv4.php',
+ 'IPLib\\Address\\IPv6' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv6.php',
+ 'IPLib\\Address\\Type' => $vendorDir . '/mlocati/ip-lib/src/Address/Type.php',
+ 'IPLib\\Factory' => $vendorDir . '/mlocati/ip-lib/src/Factory.php',
+ 'IPLib\\ParseStringFlag' => $vendorDir . '/mlocati/ip-lib/src/ParseStringFlag.php',
+ 'IPLib\\Range\\AbstractRange' => $vendorDir . '/mlocati/ip-lib/src/Range/AbstractRange.php',
+ 'IPLib\\Range\\Pattern' => $vendorDir . '/mlocati/ip-lib/src/Range/Pattern.php',
+ 'IPLib\\Range\\RangeInterface' => $vendorDir . '/mlocati/ip-lib/src/Range/RangeInterface.php',
+ 'IPLib\\Range\\Single' => $vendorDir . '/mlocati/ip-lib/src/Range/Single.php',
+ 'IPLib\\Range\\Subnet' => $vendorDir . '/mlocati/ip-lib/src/Range/Subnet.php',
+ 'IPLib\\Range\\Type' => $vendorDir . '/mlocati/ip-lib/src/Range/Type.php',
+ 'IPLib\\Service\\BinaryMath' => $vendorDir . '/mlocati/ip-lib/src/Service/BinaryMath.php',
+ 'IPLib\\Service\\RangesFromBoundaryCalculator' => $vendorDir . '/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php',
+ 'IPLib\\Service\\UnsignedIntegerMath' => $vendorDir . '/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php',
'Icewind\\Streams\\CallbackWrapper' => $vendorDir . '/icewind/streams/src/CallbackWrapper.php',
'Icewind\\Streams\\CountWrapper' => $vendorDir . '/icewind/streams/src/CountWrapper.php',
'Icewind\\Streams\\Directory' => $vendorDir . '/icewind/streams/src/Directory.php',
diff --git a/composer/autoload_psr4.php b/composer/autoload_psr4.php
index 4b572bd1..59242d02 100644
--- a/composer/autoload_psr4.php
+++ b/composer/autoload_psr4.php
@@ -67,6 +67,7 @@ return array(
'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'),
'JmesPath\\' => array($vendorDir . '/mtdowling/jmespath.php/src'),
'Icewind\\Streams\\' => array($vendorDir . '/icewind/streams/src'),
+ 'IPLib\\' => array($vendorDir . '/mlocati/ip-lib/src'),
'ID3Parser\\' => array($vendorDir . '/christophwurst/id3parser/src'),
'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'),
'Http\\Client\\' => array($vendorDir . '/php-http/httplug/src'),
diff --git a/composer/autoload_static.php b/composer/autoload_static.php
index 7efc962f..353fc312 100644
--- a/composer/autoload_static.php
+++ b/composer/autoload_static.php
@@ -235,6 +235,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652
'I' =>
array (
'Icewind\\Streams\\' => 16,
+ 'IPLib\\' => 6,
'ID3Parser\\' => 10,
),
'H' =>
@@ -535,6 +536,10 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652
array (
0 => __DIR__ . '/..' . '/icewind/streams/src',
),
+ 'IPLib\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/mlocati/ip-lib/src',
+ ),
'ID3Parser\\' =>
array (
0 => __DIR__ . '/..' . '/christophwurst/id3parser/src',
@@ -2126,6 +2131,22 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652
'ID3Parser\\getID3\\getid3_exception' => __DIR__ . '/..' . '/christophwurst/id3parser/src/getID3/getid3_exception.php',
'ID3Parser\\getID3\\getid3_handler' => __DIR__ . '/..' . '/christophwurst/id3parser/src/getID3/getid3_handler.php',
'ID3Parser\\getID3\\getid3_lib' => __DIR__ . '/..' . '/christophwurst/id3parser/src/getID3/getid3_lib.php',
+ 'IPLib\\Address\\AddressInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AddressInterface.php',
+ 'IPLib\\Address\\AssignedRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AssignedRange.php',
+ 'IPLib\\Address\\IPv4' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv4.php',
+ 'IPLib\\Address\\IPv6' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv6.php',
+ 'IPLib\\Address\\Type' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/Type.php',
+ 'IPLib\\Factory' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Factory.php',
+ 'IPLib\\ParseStringFlag' => __DIR__ . '/..' . '/mlocati/ip-lib/src/ParseStringFlag.php',
+ 'IPLib\\Range\\AbstractRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/AbstractRange.php',
+ 'IPLib\\Range\\Pattern' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Pattern.php',
+ 'IPLib\\Range\\RangeInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/RangeInterface.php',
+ 'IPLib\\Range\\Single' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Single.php',
+ 'IPLib\\Range\\Subnet' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Subnet.php',
+ 'IPLib\\Range\\Type' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Type.php',
+ 'IPLib\\Service\\BinaryMath' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/BinaryMath.php',
+ 'IPLib\\Service\\RangesFromBoundaryCalculator' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php',
+ 'IPLib\\Service\\UnsignedIntegerMath' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php',
'Icewind\\Streams\\CallbackWrapper' => __DIR__ . '/..' . '/icewind/streams/src/CallbackWrapper.php',
'Icewind\\Streams\\CountWrapper' => __DIR__ . '/..' . '/icewind/streams/src/CountWrapper.php',
'Icewind\\Streams\\Directory' => __DIR__ . '/..' . '/icewind/streams/src/Directory.php',
diff --git a/composer/installed.json b/composer/installed.json
index 1def2305..6dac20eb 100644
--- a/composer/installed.json
+++ b/composer/installed.json
@@ -2332,6 +2332,80 @@
"install-path": "../microsoft/azure-storage-common"
},
{
+ "name": "mlocati/ip-lib",
+ "version": "1.18.0",
+ "version_normalized": "1.18.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mlocati/ip-lib.git",
+ "reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2",
+ "reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "ext-pdo_sqlite": "*",
+ "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.5 || ^9.5"
+ },
+ "time": "2022-01-13T18:05:33+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "IPLib\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michele Locati",
+ "email": "mlocati@gmail.com",
+ "homepage": "https://github.com/mlocati",
+ "role": "Author"
+ }
+ ],
+ "description": "Handle IPv4, IPv6 addresses and ranges",
+ "homepage": "https://github.com/mlocati/ip-lib",
+ "keywords": [
+ "IP",
+ "address",
+ "addresses",
+ "ipv4",
+ "ipv6",
+ "manage",
+ "managing",
+ "matching",
+ "network",
+ "networking",
+ "range",
+ "subnet"
+ ],
+ "support": {
+ "issues": "https://github.com/mlocati/ip-lib/issues",
+ "source": "https://github.com/mlocati/ip-lib/tree/1.18.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/mlocati",
+ "type": "github"
+ },
+ {
+ "url": "https://paypal.me/mlocati",
+ "type": "other"
+ }
+ ],
+ "install-path": "../mlocati/ip-lib"
+ },
+ {
"name": "mtdowling/jmespath.php",
"version": "2.6.1",
"version_normalized": "2.6.1.0",
diff --git a/composer/installed.php b/composer/installed.php
index bd2d7725..677e6f99 100644
--- a/composer/installed.php
+++ b/composer/installed.php
@@ -3,7 +3,7 @@
'name' => 'nextcloud/3rdparty',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => 'd6a35b6d5759c08dd268618951f9e5b1c18aa939',
+ 'reference' => 'f143482ffb0b8dfdbc08cd848ce2e66f02a5d9b6',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
@@ -307,6 +307,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'mlocati/ip-lib' => array(
+ 'pretty_version' => '1.18.0',
+ 'version' => '1.18.0.0',
+ 'reference' => 'c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../mlocati/ip-lib',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'mtdowling/jmespath.php' => array(
'pretty_version' => '2.6.1',
'version' => '2.6.1.0',
@@ -319,7 +328,7 @@
'nextcloud/3rdparty' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => 'd6a35b6d5759c08dd268618951f9e5b1c18aa939',
+ 'reference' => 'f143482ffb0b8dfdbc08cd848ce2e66f02a5d9b6',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
diff --git a/mlocati/ip-lib/LICENSE.txt b/mlocati/ip-lib/LICENSE.txt
new file mode 100644
index 00000000..5c21e47c
--- /dev/null
+++ b/mlocati/ip-lib/LICENSE.txt
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+Copyright (c) 2016 Michele Locati
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. \ No newline at end of file
diff --git a/mlocati/ip-lib/README.md b/mlocati/ip-lib/README.md
new file mode 100644
index 00000000..762f0c3d
--- /dev/null
+++ b/mlocati/ip-lib/README.md
@@ -0,0 +1,648 @@
+[![Tests](https://github.com/mlocati/ip-lib/actions/workflows/tests.yml/badge.svg)](https://github.com/mlocati/ip-lib/actions?query=workflow%3A%22tests%22)
+[![Coverage Status](https://coveralls.io/repos/github/mlocati/ip-lib/badge.svg?branch=master)](https://coveralls.io/github/mlocati/ip-lib?branch=master)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/mlocati/ip-lib/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/mlocati/ip-lib/?branch=master)
+![Packagist Downloads](https://img.shields.io/packagist/dm/mlocati/ip-lib)
+
+# IPLib - Handle IPv4, IPv6 and IP ranges
+
+## Introduction
+
+IPLib is a modern, PSR-compliant, test-driven IP addresses and subnets manipulation library. It implements primitives to handle IPv4 and IPv6 addresses, as well as IP ranges (subnets), in CIDR format (like `::1/128` or `127.0.0.1/32`) and in pattern format (like `::*:*` or `127.0.*.*`).
+
+## Requirements
+
+IPLib has very basic requirements as:
+
+- Works with any PHP version greater than 5.3.3 (PHP **5.3.x**, **5.4.x**, **5.5.x**, **5.6.x**, **7.x**, and **8.x** are fully supported).
+- **No external dependencies**
+- **No special PHP configuration needed** (yes, it will __always work__ even if PHP has not been built with IPv6 support!).
+
+## Installation
+
+### Manual installation
+
+[Download](https://github.com/mlocati/ip-lib/releases) the latest version, unzip it and add these lines in our PHP files:
+
+```php
+require_once 'path/to/iplib/ip-lib.php';
+```
+
+### Installation with Composer
+
+Simply run
+
+```sh
+composer require mlocati/ip-lib
+```
+
+or add these lines to your `composer.json` file:
+
+```json
+"require": {
+ "mlocati/ip-lib": "^1"
+}
+```
+
+## Sample usage
+
+### Parse an address
+
+To parse an IPv4 address:
+
+```php
+$address = \IPLib\Address\IPv4::parseString('127.0.0.1');
+```
+
+To parse an IPv6 address:
+
+```php
+$address = \IPLib\Address\IPv6::parseString('::1');
+```
+
+To parse an address in any format (IPv4 or IPv6):
+
+```php
+$address = \IPLib\Factory::parseAddressString('::1');
+$address = \IPLib\Factory::parseAddressString('127.0.0.1');
+```
+
+### Get the next/previous addresses
+
+```php
+$address = \IPLib\Factory::parseAddressString('::1');
+
+// This will print ::
+echo (string) $address->getPreviousAddress();
+
+// This will print ::2
+echo (string) $address->getNextAddress();
+```
+
+### Get the addresses at a specified offset
+
+For addresses:
+
+```php
+$address = \IPLib\Factory::parseAddressString('::1');
+
+// This will print ::1
+echo (string) $address->getAddressAtOffset(0);
+
+// This will print ::2
+echo (string) $address->getAddressAtOffset(1);
+
+// This will print ::3
+echo (string) $address->getAddressAtOffset(2);
+
+// This will print ::3e9
+echo (string) $address->getAddressAtOffset(1000);
+
+// This will print ::
+echo (string) $address->getAddressAtOffset(-1);
+
+// This will print NULL
+echo var_dump($address->getAddressAtOffset(-2));
+```
+
+For ranges:
+
+```php
+$range = \IPLib\Factory::parseRangeString('::ff00/120');
+
+// This will print ::ff00
+echo (string) $range->getAddressAtOffset(0);
+
+// This will print ::ff10
+echo (string) $range->getAddressAtOffset(16);
+
+// This will print ::ff64
+echo (string) $range->getAddressAtOffset(100);
+
+// This will print NULL because the address ::1:0 is out of the range
+var_dump($range->getAddressAtOffset(256));
+
+// This will print ::ffff
+echo (string) $range->getAddressAtOffset(-1);
+
+// This will print ::fff0
+echo (string) $range->getAddressAtOffset(-16);
+
+// This will print ::ff00
+echo (string) $range->getAddressAtOffset(-256);
+
+// This will print NULL because the address ::feff is out of the range
+var_dump($range->getAddressAtOffset(-257));
+```
+
+### Parse an IP address range
+
+To parse a subnet (CIDR) range:
+
+```php
+$range = \IPLib\Range\Subnet::parseString('127.0.0.1/24');
+$range = \IPLib\Range\Subnet::parseString('::1/128');
+```
+
+To parse a pattern (asterisk notation) range:
+
+```php
+$range = \IPLib\Range\Pattern::parseString('127.0.0.*');
+$range = \IPLib\Range\Pattern::parseString('::*');
+```
+
+To parse an andress as a range:
+
+```php
+$range = \IPLib\Range\Single::parseString('127.0.0.1');
+$range = \IPLib\Range\Single::parseString('::1');
+```
+
+To parse a range in any format:
+
+```php
+$range = \IPLib\Factory::parseRangeString('127.0.0.*');
+$range = \IPLib\Factory::parseRangeString('::1/128');
+$range = \IPLib\Factory::parseRangeString('::');
+```
+
+### Retrieve a range from its boundaries
+
+You can calculate the smallest range that comprises two addresses:
+
+```php
+$range = \IPLib\Factory::getRangeFromBoundaries('192.168.0.1', '192.168.255.255');
+
+// This will print 192.168.0.0/16
+echo (string) $range;
+```
+
+You can also calculate a list of ranges that exactly describes all the addresses between two addresses:
+
+```php
+$ranges = \IPLib\Factory::getRangesFromBoundaries('192.168.0.0', '192.168.0.5');
+
+// This will print 192.168.0.0/30 192.168.0.4/31
+echo implode(' ', $ranges);
+```
+
+### Retrieve the boundaries of a range
+
+```php
+$range = \IPLib\Factory::parseRangeString('127.0.0.*');
+
+// This will print 127.0.0.0
+echo (string) $range->getStartAddress();
+
+// This will print 127.0.0.255
+echo (string) $range->getEndAddress();
+```
+
+### Format addresses and ranges
+
+Both IP addresses and ranges have a `toString` method that you can use to retrieve a textual representation:
+
+```php
+// This will print 127.0.0.1
+echo \IPLib\Factory::parseAddressString('127.0.0.1')->toString();
+
+// This will print 127.0.0.1
+echo \IPLib\Factory::parseAddressString('127.000.000.001')->toString();
+
+// This will print ::1
+echo \IPLib\Factory::parseAddressString('::1')->toString();
+
+// This will print ::1
+echo \IPLib\Factory::parseAddressString('0:0::1')->toString();
+
+// This will print ::1/64
+echo \IPLib\Factory::parseRangeString('0:0::1/64')->toString();
+```
+
+When working with IPv6, you may want the full (expanded) representation of the addresses. In this case, simply use a `true` parameter for the `toString` method:
+
+```php
+// This will print 0000:0000:0000:0000:0000:0000:0000:0000
+echo \IPLib\Factory::parseAddressString('::')->toString(true);
+
+// This will print 0000:0000:0000:0000:0000:0000:0000:0001
+echo \IPLib\Factory::parseAddressString('::1')->toString(true);
+
+// This will print 0fff:0000:0000:0000:0000:0000:0000:0000
+echo \IPLib\Factory::parseAddressString('fff::')->toString(true);
+
+// This will print 0000:0000:0000:0000:0000:0000:0000:0000
+echo \IPLib\Factory::parseAddressString('::0:0')->toString(true);
+
+// This will print 0001:0002:0003:0004:0005:0006:0007:0008
+echo \IPLib\Factory::parseAddressString('1:2:3:4:5:6:7:8')->toString(true);
+
+// This will print 0000:0000:0000:0000:0000:0000:0000:0001/64
+echo \IPLib\Factory::parseRangeString('0:0::1/64')->toString();
+```
+
+The address and range objects implements the `__toString()` method, which call the `toString()` method.
+So, if you want the string (short) representation of an object, you can do any of the following:
+
+```php
+$address = \IPLib\Address\IPv6::parseString('::1');
+
+// All these will print ::1
+echo $address->toString();
+echo $address->toString(false);
+echo (string) $address;
+```
+
+### Check if an address is contained in a range
+
+All the range types offer a `contains` method, and all the IP address types offer a `matches` method: you can call them to check if an address is contained in a range:
+
+```php
+$address = \IPLib\Factory::parseAddressString('1:2:3:4:5:6:7:8');
+$range = \IPLib\Factory::parseRangeString('0:0::1/64');
+
+$contained = $address->matches($range);
+// that's equivalent to
+$contained = $range->contains($address);
+```
+
+Please remark that if the address is IPv4 and the range is IPv6 (or vice-versa), the result will always be `false`.
+
+### Check if a range contains another range
+
+All the range types offer a `containsRange` method: you can call them to check if an address range fully contains another range:
+
+```php
+$range1 = \IPLib\Factory::parseRangeString('0:0::1/64');
+$range2 = \IPLib\Factory::parseRangeString('0:0::1/65');
+
+$contained = $range1->containsRange($range2);
+```
+
+### Getting the type of an IP address
+
+If you want to know if an address is within a private network, or if it's a public IP, or whatever you want, you can use the `getRangeType` method:
+
+```php
+$address = \IPLib\Factory::parseAddressString('::');
+
+$type = $address->getRangeType();
+
+$typeName = \IPLib\Range\Type::getName($type);
+```
+
+The most notable values of the range type are:
+
+- `\IPLib\Range\Type::T_UNSPECIFIED` if the address is all zeros (`0.0.0.0` or `::`)
+- `\IPLib\Range\Type::T_LOOPBACK` if the address is the localhost (usually `127.0.0.1` or `::1`)
+- `\IPLib\Range\Type::T_PRIVATENETWORK` if the address is in the local network (for instance `192.168.0.1` or `fc00::1`)
+- `\IPLib\Range\Type::T_PUBLIC` if the address is for public usage (for instance `104.25.25.33` or `2001:503:ba3e::2:30`)
+
+### Getting the type of an IP address range
+
+If you want to know the type of an address range, you can use the `getRangeType` method:
+
+```php
+$range = \IPLib\Factory::parseRangeString('2000:0::1/64');
+
+// $type will contain the value of \IPLib\Range\Type::T_PUBLIC
+$type = $range->getRangeType();
+
+// This will print Public address
+echo \IPLib\Range\Type::getName($type);
+```
+
+Please note that if a range spans across multiple range types, you'll get NULL as the range type:
+
+```php
+$range = \IPLib\Factory::parseRangeString('::/127');
+
+// $type will contain null
+$type = $range->getRangeType();
+
+// This will print Unknown type
+echo \IPLib\Range\Type::getName($type);
+```
+
+### Converting IP addresses
+
+This library supports converting IPv4 to/from IPv6 addresses using the [6to4 notation](https://tools.ietf.org/html/rfc3056) or the [IPv4-mapped notation](https://tools.ietf.org/html/rfc4291#section-2.5.5.2):
+
+```php
+$ipv4 = \IPLib\Factory::parseAddressString('1.2.3.4');
+
+// 6to4 notation
+$ipv6 = $ipv4->toIPv6();
+
+// This will print 2002:102:304::
+echo (string) $ipv6;
+
+// This will print 1.2.3.4
+echo $ipv6->toIPv4();
+
+// IPv4-mapped notation
+$ipv6 = $ipv4->toIPv6IPv4Mapped();
+
+// This will print ::ffff:1.2.3.4
+echo (string) $ipv6;
+
+// This will print 1.2.3.4
+echo $ipv6_6to4->toIPv4();
+```
+
+### Converting IP ranges
+
+This library supports IPv4/IPv6 ranges in pattern format (eg. `192.168.*.*`) and in CIDR/subnet format (eg. `192.168.0.0/16`), and it offers a way to convert between the two formats:
+
+```php
+// This will print ::*:*:*:*
+echo \IPLib\Factory::parseRangeString('::/64')->asPattern()->toString();
+
+// This will print 1:2::/96
+echo \IPLib\Factory::parseRangeString('1:2::*:*')->asSubnet()->toString();
+
+// This will print 192.168.0.0/24
+echo \IPLib\Factory::parseRangeString('192.168.0.*')->asSubnet()->toString();
+
+// This will print 10.*.*.*
+echo \IPLib\Factory::parseRangeString('10.0.0.0/8')->asPattern()->toString();
+```
+
+Please remark that all the range types implement the `asPattern()` and `asSubnet()` methods.
+
+### Getting the subnet mask for IPv4 ranges
+
+You can use the `getSubnetMask()` to get the subnet mask for IPv4 ranges:
+
+```php
+// This will print 255.255.255.0
+echo \IPLib\Factory::parseRangeString('192.168.0.*')->getSubnetMask()->toString();
+
+// This will print 255.255.255.252
+echo \IPLib\Factory::parseRangeString('192.168.0.12/30')->getSubnetMask()->toString();
+```
+
+### Getting the range size
+
+You can use the `getSize()` to get the count of addresses this IP range contains:
+
+```php
+// This will print 256
+echo \IPLib\Factory::parseRangeString('192.168.0.*')->getSize();
+
+// This will print 4
+echo \IPLib\Factory::parseRangeString('192.168.0.12/30')->getSize();
+
+// This will print 1
+echo \IPLib\Factory::parseRangeString('192.168.0.1')->getSize();
+```
+
+### Getting the reverse DNS lookup address
+
+To perform reverse DNS queries, you need to use a special format of the IP addresses.
+
+You can use the `getReverseDNSLookupName()` method of the IP address instances to retrieve it easily:
+
+```php
+$ipv4 = \IPLib\Factory::parseAddressString('1.2.3.255');
+$ipv6 = \IPLib\Factory::parseAddressString('1234:abcd::cafe:babe');
+
+// This will print 255.3.2.1.in-addr.arpa
+echo $ipv4->getReverseDNSLookupName();
+
+// This will print e.b.a.b.e.f.a.c.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.b.a.4.3.2.1.ip6.arpa
+echo $ipv6->getReverseDNSLookupName();
+```
+
+To parse addresses in reverse DNS lookup format you can use the `IPLib\ParseStringFlag::ADDRESS_MAYBE_RDNS` flag when parsing a string:
+
+```php
+$ipv4 = \IPLib\Factory::parseAddressString('255.3.2.1.in-addr.arpa', \IPLib\ParseStringFlag::ADDRESS_MAYBE_RDNS);
+$ipv6 = \IPLib\Factory::parseAddressString('e.b.a.b.e.f.a.c.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.b.a.4.3.2.1.ip6.arpa', \IPLib\ParseStringFlag::ADDRESS_MAYBE_RDNS);
+
+// This will print 1.2.3.255
+echo $ipv4->toString();
+
+// This will print 1234:abcd::cafe:babe
+echo (string) $ipv6->toString();
+```
+
+You can also use `getReverseDNSLookupName()` for IP ranges.
+In this case, the result is an array of strings:
+
+```php
+$range = \IPLib\Factory::parseRangeString('10.155.16.0/22');
+
+/*
+ * This will print:
+ * array (
+ * 0 => '16.155.10.in-addr.arpa',
+ * 1 => '17.155.10.in-addr.arpa',
+ * 2 => '18.155.10.in-addr.arpa',
+ * 3 => '19.155.10.in-addr.arpa',
+ * )
+*/
+var_export($range->getReverseDNSLookupName());
+```
+
+### Using a database
+
+This package offers a great feature: you can store address ranges in a database table, and check if an address is contained in one of the saved ranges with a simple query.
+
+To save a range, you need to store the address type (for IPv4 it's `4`, for IPv6 it's `6`), as well as two values representing the start and the end of the range.
+These methods are:
+```php
+$range->getAddressType();
+$range->getComparableStartString();
+$range->getComparableEndString();
+```
+
+Let's assume that you saved the type in a field called `addressType`, and the range boundaries in two fields called `rangeFrom` and `rangeTo`.
+
+When you want to check if an address is within a stored range, simply use the `getComparableString` method of the address and check if it's between the fields `rangeFrom` and `rangeTo`, and check if the stored `addressType` is the same as the one of the address instance you want to check.
+
+Here's a sample code:
+
+```php
+/*
+ * Let's assume that:
+ * - $pdo is a PDO instance
+ * - $range is a range object
+ * - $address is an address object
+ */
+
+// Save the $range object
+$insertQuery = $pdo->prepare('
+ insert into ranges (addressType, rangeFrom, rangeTo)
+ values (:addressType, :rangeFrom, :rangeTo)
+');
+
+$insertQuery->execute(array(
+ ':addressType' => $range->getAddressType(),
+ ':rangeFrom' => $range->getComparableStartString(),
+ ':rangeTo' => $range->getComparableEndString(),
+));
+
+// Retrieve the saved ranges where an address $address falls:
+$searchQuery = $pdo->prepare('
+ select * from ranges
+ where addressType = :addressType
+ and :address between rangeFrom and rangeTo
+');
+
+$searchQuery->execute(array(
+ ':addressType' => $address->getAddressType(),
+ ':address' => $address->getComparableString(),
+));
+
+$rows = $searchQuery->fetchAll();
+$searchQuery->closeCursor();
+```
+
+## Handling non-standard address and range strings
+
+### Accepting ports
+
+If you want to accept addresses that may include ports, you can specify the `IPLib\ParseStringFlag::MAY_INCLUDE_PORT` flag:
+
+```php
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+require_once __DIR__ . '/../ip-lib.php';
+
+// These will print NULL
+var_export(Factory::parseAddressString('127.0.0.1:80'));
+var_export(Factory::parseAddressString('[::]:80'));
+
+// This will print 127.0.0.1
+echo (string) Factory::parseAddressString('127.0.0.1:80', ParseStringFlag::MAY_INCLUDE_PORT);
+// This will print ::
+echo (string) Factory::parseAddressString('[::]:80', ParseStringFlag::MAY_INCLUDE_PORT);
+```
+
+### Accepting IPv6 zone IDs
+
+If you want to accept IPv6 addresses that may include a zone ID, you can specify the `IPLib\ParseStringFlag::MAY_INCLUDE_ZONEID` flag:
+
+```php
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+// This will print NULL
+var_export(Factory::parseAddressString('::%11'));
+
+// This will print ::
+echo (string) Factory::parseAddressString('::%11', ParseStringFlag::MAY_INCLUDE_ZONEID);
+```
+
+### Accepting non-decimal IPv4 addresses
+
+IPv4 addresses are usually expressed in decimal notation, for example as `192.168.0.1`.
+
+By the way, the GNU (used in many Linux distros), BSD (used in Mac) and Windows implementations of `inet_aton` and `inet_addr` accept IPv4 addresses with numbers in octal and/or hexadecimal format.
+Please remark that this does not apply to the `inet_pton` and `ip2long` functions, as well as to the Musl implementation (used in Alpine Linux) of `inet_aton` and `inet_addr`.
+
+So, for example, these addresses are all equivalent to `192.168.0.1`:
+
+- `0xC0.0xA8.0x0.0x01` (only hexadecimal)
+- `0300.0250.00.01` (only octal)
+- `192.0250.0.0x01` (decimal, octal and hexadecimal numbers)
+
+(try it: if you browse to [`http://0177.0.0.0x1`](http://0177.0.0.0x1), your browser will try to browse `http://127.0.0.1`).
+
+If you want to accept this non-decimal syntax, you may use the `IPLib\ParseStringFlag::IPV4_MAYBE_NON_DECIMAL` flag:
+
+```php
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+// This will print NULL
+var_export(Factory::parseAddressString('0177.0.0.0x1'));
+
+// This will print 127.0.0.1
+var_export((string) Factory::parseAddressString('0177.0.0.0x1', ParseStringFlag::IPV4_MAYBE_NON_DECIMAL));
+
+// This will print NULL
+var_export(Factory::parseRangeString('0177.0.0.0x1/32'));
+
+// This will print 127.0.0.1/32
+var_export((string) Factory::parseRangeString('0177.0.0.0x1/32', ParseStringFlag::IPV4_MAYBE_NON_DECIMAL));
+```
+
+Please be aware that the `IPV4_MAYBE_NON_DECIMAL` flag may also affect parsing decimal numbers:
+
+```php
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+// This will print 127.0.0.10 since the last digit is assumed to be decimal
+var_export((string) Factory::parseAddressString('127.0.0.010'));
+
+// This will print 127.0.0.8 since the last digit is assumed to be octal
+var_export((string) Factory::parseAddressString('127.0.0.010', ParseStringFlag::IPV4_MAYBE_NON_DECIMAL));
+```
+
+### Accepting IPv4 addresses in not-quad-dotted notation
+
+IPv4 addresses are usually expressed with 4 numbers, for example as `192.168.0.1`.
+
+By the way, the GNU (used in many Linux distros), BSD (used in Mac) and Windows implementations of `inet_aton` and `inet_addr` [accept IPv4 addresses with 1 to 4 numbers](https://man7.org/linux/man-pages/man3/inet_addr.3.html#DESCRIPTION).
+
+Please remark that this does not apply to the `inet_pton` and `ip2long` functions, as well as to the Musl implementation (used in Alpine Linux) of `inet_aton` and `inet_addr`.
+
+If you want to accept this non-decimal syntax, you may use the `IPLib\ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED` flag:
+
+```php
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+// This will print NULL
+var_export(Factory::parseAddressString('1.2.500'));
+
+// This will print 0.0.0.0
+var_export((string) Factory::parseAddressString('0', ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED));
+
+// This will print 0.0.0.1
+var_export((string) Factory::parseAddressString('1', ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED));
+
+// This will print 0.0.1.244
+var_export((string) Factory::parseAddressString('0.0.500', ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED));
+
+// This will print 255.255.255.255
+var_export((string) Factory::parseAddressString('4294967295', ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED));
+```
+
+### Accepting compact IPv4 subnet notation
+
+Even if there isn't an RFC that describe it, IPv4 subnet notation may also be written in a compact form, omitting extra digits (for example, `127.0.0.0/24` may be written as `127/24`).
+If you want to accept such format, you can specify the `IPLib\ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT` flag:
+
+```php
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+// This will print NULL
+var_export(Factory::parseRangeString('127/24'));
+
+// This will print 127.0.0.0/24
+echo (string) Factory::parseRangeString('127/24', ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT);
+```
+
+### Combining multiple flags
+
+Of course, you may use more than one `IPLib\ParseStringFlag` flag at once:
+
+```php
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+// This will print 127.0.0.255
+var_export((string) Factory::parseAddressString('127.0.0.0xff:80', ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::IPV4_MAYBE_NON_DECIMAL));
+
+// This will print ::
+var_export((string) Factory::parseAddressString('[::%11]:80', ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID));
+```
+
+## Do you really want to say thank you?
+
+You can offer me a [monthly coffee](https://github.com/sponsors/mlocati) or a [one-time coffee](https://paypal.me/mlocati) :wink:
diff --git a/mlocati/ip-lib/composer.json b/mlocati/ip-lib/composer.json
new file mode 100644
index 00000000..bb11be45
--- /dev/null
+++ b/mlocati/ip-lib/composer.json
@@ -0,0 +1,60 @@
+{
+ "name": "mlocati/ip-lib",
+ "description": "Handle IPv4, IPv6 addresses and ranges",
+ "type": "library",
+ "license": "MIT",
+ "homepage": "https://github.com/mlocati/ip-lib",
+ "authors": [
+ {
+ "name": "Michele Locati",
+ "homepage": "https://github.com/mlocati",
+ "email": "mlocati@gmail.com",
+ "role": "Author"
+ }
+ ],
+ "keywords": [
+ "ip",
+ "ipv4",
+ "ipv6",
+ "range",
+ "network",
+ "networking",
+ "address",
+ "addresses",
+ "subnet",
+ "matching",
+ "managing",
+ "manage"
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "autoload": {
+ "psr-4": {
+ "IPLib\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "IPLib\\Test\\": "test/tests/",
+ "IPLib\\Test\\Helpers\\": "test/helpers/"
+ }
+ },
+ "require-dev": {
+ "ext-pdo_sqlite": "*",
+ "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.5 || ^9.5"
+ },
+ "scripts": {
+ "test": "phpunit"
+ },
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/mlocati"
+ },
+ {
+ "type": "other",
+ "url": "https://paypal.me/mlocati"
+ }
+ ]
+}
diff --git a/mlocati/ip-lib/ip-lib.php b/mlocati/ip-lib/ip-lib.php
new file mode 100644
index 00000000..c34d0b49
--- /dev/null
+++ b/mlocati/ip-lib/ip-lib.php
@@ -0,0 +1,13 @@
+<?php
+
+spl_autoload_register(
+ function ($class) {
+ if (strpos($class, 'IPLib\\') !== 0) {
+ return;
+ }
+ $file = __DIR__ . DIRECTORY_SEPARATOR . 'src' . str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen('IPLib'))) . '.php';
+ if (is_file($file)) {
+ require_once $file;
+ }
+ }
+);
diff --git a/mlocati/ip-lib/src/Address/AddressInterface.php b/mlocati/ip-lib/src/Address/AddressInterface.php
new file mode 100644
index 00000000..f484c621
--- /dev/null
+++ b/mlocati/ip-lib/src/Address/AddressInterface.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace IPLib\Address;
+
+use IPLib\Range\RangeInterface;
+
+/**
+ * Interface of all the IP address types.
+ */
+interface AddressInterface
+{
+ /**
+ * Get the short string representation of this address.
+ *
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * Get the number of bits representing this address type.
+ *
+ * @return int
+ *
+ * @since 1.14.0
+ *
+ * @example 32 for IPv4
+ * @example 128 for IPv6
+ */
+ public static function getNumberOfBits();
+
+ /**
+ * Get the string representation of this address.
+ *
+ * @param bool $long set to true to have a long/full representation, false otherwise
+ *
+ * @return string
+ *
+ * @example If $long is true, you'll get '0000:0000:0000:0000:0000:0000:0000:0001', '::1' otherwise.
+ */
+ public function toString($long = false);
+
+ /**
+ * Get the byte list of the IP address.
+ *
+ * @return int[]
+ *
+ * @example For localhost: for IPv4 you'll get array(127, 0, 0, 1), for IPv6 array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
+ */
+ public function getBytes();
+
+ /**
+ * Get the full bit list the IP address.
+ *
+ * @return string
+ *
+ * @since 1.14.0
+ *
+ * @example For localhost: For IPv4 you'll get '01111111000000000000000000000001' (32 digits), for IPv6 '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001' (128 digits)
+ */
+ public function getBits();
+
+ /**
+ * Get the type of the IP address.
+ *
+ * @return int One of the \IPLib\Address\Type::T_... constants
+ */
+ public function getAddressType();
+
+ /**
+ * Get the default RFC reserved range type.
+ *
+ * @return int One of the \IPLib\Range\Type::T_... constants
+ *
+ * @since 1.5.0
+ */
+ public static function getDefaultReservedRangeType();
+
+ /**
+ * Get the RFC reserved ranges (except the ones of type getDefaultReservedRangeType).
+ *
+ * @return \IPLib\Address\AssignedRange[] ranges are sorted
+ *
+ * @since 1.5.0
+ */
+ public static function getReservedRanges();
+
+ /**
+ * Get the type of range of the IP address.
+ *
+ * @return int One of the \IPLib\Range\Type::T_... constants
+ */
+ public function getRangeType();
+
+ /**
+ * Get a string representation of this address than can be used when comparing addresses and ranges.
+ *
+ * @return string
+ */
+ public function getComparableString();
+
+ /**
+ * Check if this address is contained in an range.
+ *
+ * @param \IPLib\Range\RangeInterface $range
+ *
+ * @return bool
+ */
+ public function matches(RangeInterface $range);
+
+ /**
+ * Get the address at a certain distance from this address.
+ *
+ * @param int $n the distance of the address (can be negative)
+ *
+ * @return \IPLib\Address\AddressInterface|null return NULL if $n is not an integer or if the final address would be invalid
+ *
+ * @since 1.15.0
+ *
+ * @example passing 1 to the address 127.0.0.1 will result in 127.0.0.2
+ * @example passing -1 to the address 127.0.0.1 will result in 127.0.0.0
+ * @example passing -1 to the address 0.0.0.0 will result in NULL
+ */
+ public function getAddressAtOffset($n);
+
+ /**
+ * Get the address right after this IP address (if available).
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\Address\AddressInterface::getAddressAtOffset()
+ * @since 1.4.0
+ */
+ public function getNextAddress();
+
+ /**
+ * Get the address right before this IP address (if available).
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\Address\AddressInterface::getAddressAtOffset()
+ * @since 1.4.0
+ */
+ public function getPreviousAddress();
+
+ /**
+ * Get the Reverse DNS Lookup Address of this IP address.
+ *
+ * @return string
+ *
+ * @since 1.12.0
+ *
+ * @example for IPv4 it returns something like x.x.x.x.in-addr.arpa
+ * @example for IPv6 it returns something like x.x.x.x..x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa
+ */
+ public function getReverseDNSLookupName();
+}
diff --git a/mlocati/ip-lib/src/Address/AssignedRange.php b/mlocati/ip-lib/src/Address/AssignedRange.php
new file mode 100644
index 00000000..3d6fa532
--- /dev/null
+++ b/mlocati/ip-lib/src/Address/AssignedRange.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace IPLib\Address;
+
+use IPLib\Range\RangeInterface;
+
+/**
+ * Represents an IP address range with an assigned range type.
+ *
+ * @since 1.5.0
+ */
+class AssignedRange
+{
+ /**
+ * The range definition.
+ *
+ * @var \IPLib\Range\RangeInterface
+ */
+ protected $range;
+
+ /**
+ * The range type.
+ *
+ * @var int one of the \IPLib\Range\Type::T_ constants
+ */
+ protected $type;
+
+ /**
+ * The list of exceptions for this range type.
+ *
+ * @var \IPLib\Address\AssignedRange[]
+ */
+ protected $exceptions;
+
+ /**
+ * Initialize the instance.
+ *
+ * @param \IPLib\Range\RangeInterface $range the range definition
+ * @param int $type The range type (one of the \IPLib\Range\Type::T_ constants)
+ * @param \IPLib\Address\AssignedRange[] $exceptions the list of exceptions for this range type
+ */
+ public function __construct(RangeInterface $range, $type, array $exceptions = array())
+ {
+ $this->range = $range;
+ $this->type = $type;
+ $this->exceptions = $exceptions;
+ }
+
+ /**
+ * Get the range definition.
+ *
+ * @return \IPLib\Range\RangeInterface
+ */
+ public function getRange()
+ {
+ return $this->range;
+ }
+
+ /**
+ * Get the range type.
+ *
+ * @return int one of the \IPLib\Range\Type::T_ constants
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Get the list of exceptions for this range type.
+ *
+ * @return \IPLib\Address\AssignedRange[]
+ */
+ public function getExceptions()
+ {
+ return $this->exceptions;
+ }
+
+ /**
+ * Get the assigned type for a specific address.
+ *
+ * @param \IPLib\Address\AddressInterface $address
+ *
+ * @return int|null return NULL of the address is outside this address; a \IPLib\Range\Type::T_ constant otherwise
+ */
+ public function getAddressType(AddressInterface $address)
+ {
+ $result = null;
+ if ($this->range->contains($address)) {
+ foreach ($this->exceptions as $exception) {
+ $result = $exception->getAddressType($address);
+ if ($result !== null) {
+ break;
+ }
+ }
+ if ($result === null) {
+ $result = $this->type;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the assigned type for a specific address range.
+ *
+ * @param \IPLib\Range\RangeInterface $range
+ *
+ * @return int|false|null return NULL of the range is fully outside this range; false if it's partly crosses this range (or it contains mixed types); a \IPLib\Range\Type::T_ constant otherwise
+ */
+ public function getRangeType(RangeInterface $range)
+ {
+ $myStart = $this->range->getComparableStartString();
+ $rangeEnd = $range->getComparableEndString();
+ if ($myStart > $rangeEnd) {
+ $result = null;
+ } else {
+ $myEnd = $this->range->getComparableEndString();
+ $rangeStart = $range->getComparableStartString();
+ if ($myEnd < $rangeStart) {
+ $result = null;
+ } elseif ($rangeStart < $myStart || $rangeEnd > $myEnd) {
+ $result = false;
+ } else {
+ $result = null;
+ foreach ($this->exceptions as $exception) {
+ $result = $exception->getRangeType($range);
+ if ($result !== null) {
+ break;
+ }
+ }
+ if ($result === null) {
+ $result = $this->getType();
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/mlocati/ip-lib/src/Address/IPv4.php b/mlocati/ip-lib/src/Address/IPv4.php
new file mode 100644
index 00000000..cd2c416f
--- /dev/null
+++ b/mlocati/ip-lib/src/Address/IPv4.php
@@ -0,0 +1,515 @@
+<?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';
+ }
+}
diff --git a/mlocati/ip-lib/src/Address/IPv6.php b/mlocati/ip-lib/src/Address/IPv6.php
new file mode 100644
index 00000000..e094881b
--- /dev/null
+++ b/mlocati/ip-lib/src/Address/IPv6.php
@@ -0,0 +1,608 @@
+<?php
+
+namespace IPLib\Address;
+
+use IPLib\ParseStringFlag;
+use IPLib\Range\RangeInterface;
+use IPLib\Range\Subnet;
+use IPLib\Range\Type as RangeType;
+
+/**
+ * An IPv6 address.
+ */
+class IPv6 implements AddressInterface
+{
+ /**
+ * The long string representation of the address.
+ *
+ * @var string
+ *
+ * @example '0000:0000:0000:0000:0000:0000:0000:0001'
+ */
+ protected $longAddress;
+
+ /**
+ * The long string representation of the address.
+ *
+ * @var string|null
+ *
+ * @example '::1'
+ */
+ protected $shortAddress;
+
+ /**
+ * The byte list of the IP address.
+ *
+ * @var int[]|null
+ */
+ protected $bytes;
+
+ /**
+ * The word list of the IP address.
+ *
+ * @var int[]|null
+ */
+ protected $words;
+
+ /**
+ * The type of the range of this IP address.
+ *
+ * @var int|null
+ */
+ protected $rangeType;
+
+ /**
+ * An array containing RFC designated address ranges.
+ *
+ * @var array|null
+ */
+ private static $reservedRanges;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param string $longAddress
+ */
+ public function __construct($longAddress)
+ {
+ $this->longAddress = $longAddress;
+ $this->shortAddress = null;
+ $this->bytes = null;
+ $this->words = null;
+ $this->rangeType = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::__toString()
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getNumberOfBits()
+ */
+ public static function getNumberOfBits()
+ {
+ return 128;
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseString() method instead.
+ * For upgrading:
+ * - if $mayIncludePort is true, use the ParseStringFlag::MAY_INCLUDE_PORT flag
+ * - if $mayIncludeZoneID is true, use the ParseStringFlag::MAY_INCLUDE_ZONEID flag
+ *
+ * @param string|mixed $address
+ * @param bool $mayIncludePort
+ * @param bool $mayIncludeZoneID
+ *
+ * @return static|null
+ *
+ * @see \IPLib\Address\IPv6::parseString()
+ * @since 1.1.0 added the $mayIncludePort argument
+ * @since 1.3.0 added the $mayIncludeZoneID argument
+ */
+ public static function fromString($address, $mayIncludePort = true, $mayIncludeZoneID = true)
+ {
+ return static::parseString($address, 0 | ($mayIncludePort ? ParseStringFlag::MAY_INCLUDE_PORT : 0) | ($mayIncludeZoneID ? ParseStringFlag::MAY_INCLUDE_ZONEID : 0));
+ }
+
+ /**
+ * Parse a string and returns an IPv6 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;
+ }
+ $matches = null;
+ $flags = (int) $flags;
+ if ($flags & ParseStringFlag::ADDRESS_MAYBE_RDNS) {
+ if (preg_match('/^([0-9a-f](?:\.[0-9a-f]){31})\.ip6\.arpa\.?/i', $address, $matches)) {
+ $nibbles = array_reverse(explode('.', $matches[1]));
+ $quibbles = array();
+ foreach (array_chunk($nibbles, 4) as $n) {
+ $quibbles[] = implode('', $n);
+ }
+ $address = implode(':', $quibbles);
+ }
+ }
+ $result = null;
+ if (is_string($address) && strpos($address, ':') !== false && strpos($address, ':::') === false) {
+ if ($flags & ParseStringFlag::MAY_INCLUDE_PORT && $address[0] === '[' && preg_match('/^\[(.+)]:\d+$/', $address, $matches)) {
+ $address = $matches[1];
+ }
+ if ($flags & ParseStringFlag::MAY_INCLUDE_ZONEID) {
+ $percentagePos = strpos($address, '%');
+ if ($percentagePos > 0) {
+ $address = substr($address, 0, $percentagePos);
+ }
+ }
+ if (preg_match('/^((?:[0-9a-f]*:+)+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i', $address, $matches)) {
+ $address6 = static::parseString($matches[1] . '0:0');
+ if ($address6 !== null) {
+ $address4 = IPv4::parseString($matches[2]);
+ if ($address4 !== null) {
+ $bytes4 = $address4->getBytes();
+ $address6->longAddress = substr($address6->longAddress, 0, -9) . sprintf('%02x%02x:%02x%02x', $bytes4[0], $bytes4[1], $bytes4[2], $bytes4[3]);
+ $result = $address6;
+ }
+ }
+ } else {
+ if (strpos($address, '::') === false) {
+ $chunks = explode(':', $address);
+ } else {
+ $chunks = array();
+ $parts = explode('::', $address);
+ if (count($parts) === 2) {
+ $before = ($parts[0] === '') ? array() : explode(':', $parts[0]);
+ $after = ($parts[1] === '') ? array() : explode(':', $parts[1]);
+ $missing = 8 - count($before) - count($after);
+ if ($missing >= 0) {
+ $chunks = $before;
+ if ($missing !== 0) {
+ $chunks = array_merge($chunks, array_fill(0, $missing, '0'));
+ }
+ $chunks = array_merge($chunks, $after);
+ }
+ }
+ }
+ if (count($chunks) === 8) {
+ $nums = array_map(
+ function ($chunk) {
+ return preg_match('/^[0-9A-Fa-f]{1,4}$/', $chunk) ? hexdec($chunk) : false;
+ },
+ $chunks
+ );
+ if (!in_array(false, $nums, true)) {
+ $longAddress = implode(
+ ':',
+ array_map(
+ function ($num) {
+ return sprintf('%04x', $num);
+ },
+ $nums
+ )
+ );
+ $result = new static($longAddress);
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Parse an array of bytes and returns an IPv6 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) === 16) {
+ $address = '';
+ for ($i = 0; $i < 16; $i++) {
+ if ($i !== 0 && $i % 2 === 0) {
+ $address .= ':';
+ }
+ $byte = $bytes[$i];
+ if (is_int($byte) && $byte >= 0 && $byte <= 255) {
+ $address .= sprintf('%02x', $byte);
+ } else {
+ $address = null;
+ break;
+ }
+ }
+ if ($address !== null) {
+ $result = new static($address);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Parse an array of words and returns an IPv6 instance if the array is valid, or null otherwise.
+ *
+ * @param int[]|array $words
+ *
+ * @return static|null
+ */
+ public static function fromWords(array $words)
+ {
+ $result = null;
+ if (count($words) === 8) {
+ $chunks = array();
+ for ($i = 0; $i < 8; $i++) {
+ $word = $words[$i];
+ if (is_int($word) && $word >= 0 && $word <= 0xffff) {
+ $chunks[] = sprintf('%04x', $word);
+ } else {
+ $chunks = null;
+ break;
+ }
+ }
+ if ($chunks !== null) {
+ $result = new static(implode(':', $chunks));
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::toString()
+ */
+ public function toString($long = false)
+ {
+ if ($long) {
+ $result = $this->longAddress;
+ } else {
+ if ($this->shortAddress === null) {
+ if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) {
+ $lastBytes = array_slice($this->getBytes(), -4);
+ $this->shortAddress = '::ffff:' . implode('.', $lastBytes);
+ } else {
+ $chunks = array_map(
+ function ($word) {
+ return dechex($word);
+ },
+ $this->getWords()
+ );
+ $shortAddress = implode(':', $chunks);
+ $matches = null;
+ for ($i = 8; $i > 1; $i--) {
+ $search = '(?:^|:)' . rtrim(str_repeat('0:', $i), ':') . '(?:$|:)';
+ if (preg_match('/^(.*?)' . $search . '(.*)$/', $shortAddress, $matches)) {
+ $shortAddress = $matches[1] . '::' . $matches[2];
+ break;
+ }
+ }
+ $this->shortAddress = $shortAddress;
+ }
+ }
+ $result = $this->shortAddress;
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getBytes()
+ */
+ public function getBytes()
+ {
+ if ($this->bytes === null) {
+ $bytes = array();
+ foreach ($this->getWords() as $word) {
+ $bytes[] = $word >> 8;
+ $bytes[] = $word & 0xff;
+ }
+ $this->bytes = $bytes;
+ }
+
+ 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);
+ }
+
+ /**
+ * Get the word list of the IP address.
+ *
+ * @return int[]
+ */
+ public function getWords()
+ {
+ if ($this->words === null) {
+ $this->words = array_map(
+ function ($chunk) {
+ return hexdec($chunk);
+ },
+ explode(':', $this->longAddress)
+ );
+ }
+
+ return $this->words;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getAddressType()
+ */
+ public function getAddressType()
+ {
+ return Type::T_IPv6;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
+ */
+ public static function getDefaultReservedRangeType()
+ {
+ return RangeType::T_RESERVED;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getReservedRanges()
+ */
+ public static function getReservedRanges()
+ {
+ if (self::$reservedRanges === null) {
+ $reservedRanges = array();
+ foreach (array(
+ // RFC 4291
+ '::/128' => array(RangeType::T_UNSPECIFIED),
+ // RFC 4291
+ '::1/128' => array(RangeType::T_LOOPBACK),
+ // RFC 4291
+ '100::/8' => array(RangeType::T_DISCARD, array('100::/64' => RangeType::T_DISCARDONLY)),
+ //'2002::/16' => array(RangeType::),
+ // RFC 4291
+ '2000::/3' => array(RangeType::T_PUBLIC),
+ // RFC 4193
+ 'fc00::/7' => array(RangeType::T_PRIVATENETWORK),
+ // RFC 4291
+ 'fe80::/10' => array(RangeType::T_LINKLOCAL_UNICAST),
+ // RFC 4291
+ 'ff00::/8' => array(RangeType::T_MULTICAST),
+ // RFC 4291
+ //'::/8' => array(RangeType::T_RESERVED),
+ // RFC 4048
+ //'200::/7' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'400::/6' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'800::/5' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'1000::/4' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'4000::/3' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'6000::/3' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'8000::/3' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'a000::/3' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'c000::/3' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'e000::/4' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'f000::/5' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'f800::/6' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'fe00::/9' => array(RangeType::T_RESERVED),
+ // RFC 3879
+ //'fec0::/10' => array(RangeType::T_RESERVED),
+ ) 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) {
+ $ipv4 = $this->toIPv4();
+ if ($ipv4 !== null) {
+ $this->rangeType = $ipv4->getRangeType();
+ } else {
+ $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 IPv4 representation of this address (if possible, otherwise returns null).
+ *
+ * @return \IPLib\Address\IPv4|null
+ */
+ public function toIPv4()
+ {
+ if (strpos($this->longAddress, '2002:') === 0) {
+ // 6to4
+ return IPv4::fromBytes(array_slice($this->getBytes(), 2, 4));
+ }
+ if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) {
+ // IPv4-mapped IPv6 addresses
+ return IPv4::fromBytes(array_slice($this->getBytes(), -4));
+ }
+
+ return null;
+ }
+
+ /**
+ * Render this IPv6 address in the "mixed" IPv6 (first 12 bytes) + IPv4 (last 4 bytes) mixed syntax.
+ *
+ * @param bool $ipV6Long render the IPv6 part in "long" format?
+ * @param bool $ipV4Long render the IPv4 part in "long" format?
+ *
+ * @return string
+ *
+ * @example '::13.1.68.3'
+ * @example '0000:0000:0000:0000:0000:0000:13.1.68.3' when $ipV6Long is true
+ * @example '::013.001.068.003' when $ipV4Long is true
+ * @example '0000:0000:0000:0000:0000:0000:013.001.068.003' when $ipV6Long and $ipV4Long are true
+ *
+ * @see https://tools.ietf.org/html/rfc4291#section-2.2 point 3.
+ * @since 1.9.0
+ */
+ public function toMixedIPv6IPv4String($ipV6Long = false, $ipV4Long = false)
+ {
+ $myBytes = $this->getBytes();
+ $ipv6Bytes = array_merge(array_slice($myBytes, 0, 12), array(0xff, 0xff, 0xff, 0xff));
+ $ipv6String = static::fromBytes($ipv6Bytes)->toString($ipV6Long);
+ $ipv4Bytes = array_slice($myBytes, 12, 4);
+ $ipv4String = IPv4::fromBytes($ipv4Bytes)->toString($ipV4Long);
+
+ return preg_replace('/((ffff:ffff)|(\d+(\.\d+){3}))$/i', $ipv4String, $ipv6String);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getComparableString()
+ */
+ public function getComparableString()
+ {
+ return $this->longAddress;
+ }
+
+ /**
+ * {@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 = 0x10000;
+ $mod = $n;
+ $words = $this->getWords();
+ for ($i = count($words) - 1; $i >= 0; $i--) {
+ $tmp = ($words[$i] + $mod) % $boundary;
+ $mod = (int) floor(($words[$i] + $mod) / $boundary);
+ if ($tmp < 0) {
+ $tmp += $boundary;
+ }
+
+ $words[$i] = $tmp;
+ }
+
+ if ($mod !== 0) {
+ return null;
+ }
+
+ return static::fromWords($words);
+ }
+
+ /**
+ * {@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(str_split(str_replace(':', '', $this->toString(true)), 1))
+ ) . '.ip6.arpa';
+ }
+}
diff --git a/mlocati/ip-lib/src/Address/Type.php b/mlocati/ip-lib/src/Address/Type.php
new file mode 100644
index 00000000..17488f38
--- /dev/null
+++ b/mlocati/ip-lib/src/Address/Type.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace IPLib\Address;
+
+/**
+ * Types of IP addresses.
+ */
+class Type
+{
+ /**
+ * IPv4 address.
+ *
+ * @var int
+ */
+ const T_IPv4 = 4;
+
+ /**
+ * IPv6 address.
+ *
+ * @var int
+ */
+ const T_IPv6 = 6;
+
+ /**
+ * Get the name of a type.
+ *
+ * @param int $type
+ *
+ * @return string
+ *
+ * @since 1.1.0
+ */
+ public static function getName($type)
+ {
+ switch ($type) {
+ case static::T_IPv4:
+ return 'IP v4';
+ case static::T_IPv6:
+ return 'IP v6';
+ default:
+ return sprintf('Unknown type (%s)', $type);
+ }
+ }
+}
diff --git a/mlocati/ip-lib/src/Factory.php b/mlocati/ip-lib/src/Factory.php
new file mode 100644
index 00000000..31b21d41
--- /dev/null
+++ b/mlocati/ip-lib/src/Factory.php
@@ -0,0 +1,298 @@
+<?php
+
+namespace IPLib;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Range\Subnet;
+use IPLib\Service\RangesFromBoundaryCalculator;
+
+/**
+ * Factory methods to build class instances.
+ */
+class Factory
+{
+ /**
+ * @deprecated since 1.17.0: use the parseAddressString() method instead.
+ * For upgrading:
+ * - if $mayIncludePort is true, use the ParseStringFlag::MAY_INCLUDE_PORT flag
+ * - if $mayIncludeZoneID is true, use the ParseStringFlag::MAY_INCLUDE_ZONEID flag
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $address
+ * @param bool $mayIncludePort
+ * @param bool $mayIncludeZoneID
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\Factory::parseAddressString()
+ * @since 1.1.0 added the $mayIncludePort argument
+ * @since 1.3.0 added the $mayIncludeZoneID argument
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function addressFromString($address, $mayIncludePort = true, $mayIncludeZoneID = true, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseAddressString($address, 0 + ($mayIncludePort ? ParseStringFlag::MAY_INCLUDE_PORT : 0) + ($mayIncludeZoneID ? ParseStringFlag::MAY_INCLUDE_ZONEID : 0) + ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Parse an IP address string.
+ *
+ * @param string|mixed $address the address to parse
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function parseAddressString($address, $flags = 0)
+ {
+ $result = null;
+ if ($result === null) {
+ $result = Address\IPv4::parseString($address, $flags);
+ }
+ if ($result === null) {
+ $result = Address\IPv6::parseString($address, $flags);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Convert a byte array to an address instance.
+ *
+ * @param int[]|array $bytes
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ */
+ public static function addressFromBytes(array $bytes)
+ {
+ $result = null;
+ if ($result === null) {
+ $result = Address\IPv4::fromBytes($bytes);
+ }
+ if ($result === null) {
+ $result = Address\IPv6::fromBytes($bytes);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseRangeString() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $address
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\Factory::parseRangeString()
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function rangeFromString($address, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseRangeString($address, $supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0);
+ }
+
+ /**
+ * Parse an IP range string.
+ *
+ * @param string $range
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return \IPLib\Range\RangeInterface|null
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function parseRangeString($range, $flags = 0)
+ {
+ $result = null;
+ if ($result === null) {
+ $result = Range\Subnet::parseString($range, $flags);
+ }
+ if ($result === null) {
+ $result = Range\Pattern::parseString($range, $flags);
+ }
+ if ($result === null) {
+ $result = Range\Single::parseString($range, $flags);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the getRangeFromBoundaries() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|\IPLib\Address\AddressInterface|mixed $from
+ * @param string|\IPLib\Address\AddressInterface|mixed $to
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\Factory::getRangeFromBoundaries()
+ * @since 1.2.0
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function rangeFromBoundaries($from, $to, $supportNonDecimalIPv4 = false)
+ {
+ return static::getRangeFromBoundaries($from, $to, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Create the smallest address range that comprises two addresses.
+ *
+ * @param string|\IPLib\Address\AddressInterface|mixed $from
+ * @param string|\IPLib\Address\AddressInterface|mixed $to
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return \IPLib\Range\RangeInterface|null return NULL if $from and/or $to are invalid addresses, or if both are NULL or empty strings, or if they are addresses of different types
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function getRangeFromBoundaries($from, $to, $flags = 0)
+ {
+ list($from, $to) = self::parseBoundaries($from, $to, $flags);
+
+ return $from === false || $to === false ? null : static::rangeFromBoundaryAddresses($from, $to);
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the getRangesFromBoundaries() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|\IPLib\Address\AddressInterface|mixed $from
+ * @param string|\IPLib\Address\AddressInterface|mixed $to
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return \IPLib\Range\Subnet[]|null
+ *
+ * @see \IPLib\Factory::getRangesFromBoundaries()
+ * @since 1.14.0
+ */
+ public static function rangesFromBoundaries($from, $to, $supportNonDecimalIPv4 = false)
+ {
+ return static::getRangesFromBoundaries($from, $to, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Create a list of Range instances that exactly describes all the addresses between the two provided addresses.
+ *
+ * @param string|\IPLib\Address\AddressInterface $from
+ * @param string|\IPLib\Address\AddressInterface $to
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return \IPLib\Range\Subnet[]|null return NULL if $from and/or $to are invalid addresses, or if both are NULL or empty strings, or if they are addresses of different types
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function getRangesFromBoundaries($from, $to, $flags = 0)
+ {
+ list($from, $to) = self::parseBoundaries($from, $to, $flags);
+ if ($from === false || $to === false || ($from === null && $to === null)) {
+ return null;
+ }
+ if ($from === null || $to === null) {
+ $address = $from ? $from : $to;
+
+ return array(new Subnet($address, $address, $address->getNumberOfBits()));
+ }
+ $numberOfBits = $from->getNumberOfBits();
+ if ($to->getNumberOfBits() !== $numberOfBits) {
+ return null;
+ }
+ $calculator = new RangesFromBoundaryCalculator($numberOfBits);
+
+ return $calculator->getRanges($from, $to);
+ }
+
+ /**
+ * @param \IPLib\Address\AddressInterface $from
+ * @param \IPLib\Address\AddressInterface $to
+ *
+ * @return \IPLib\Range\RangeInterface|null
+ *
+ * @since 1.2.0
+ */
+ protected static function rangeFromBoundaryAddresses(AddressInterface $from = null, AddressInterface $to = null)
+ {
+ if ($from === null && $to === null) {
+ $result = null;
+ } elseif ($to === null) {
+ $result = Range\Single::fromAddress($from);
+ } elseif ($from === null) {
+ $result = Range\Single::fromAddress($to);
+ } else {
+ $result = null;
+ $addressType = $from->getAddressType();
+ if ($addressType === $to->getAddressType()) {
+ $cmp = strcmp($from->getComparableString(), $to->getComparableString());
+ if ($cmp === 0) {
+ $result = Range\Single::fromAddress($from);
+ } else {
+ if ($cmp > 0) {
+ list($from, $to) = array($to, $from);
+ }
+ $fromBytes = $from->getBytes();
+ $toBytes = $to->getBytes();
+ $numBytes = count($fromBytes);
+ $sameBits = 0;
+ for ($byteIndex = 0; $byteIndex < $numBytes; $byteIndex++) {
+ $fromByte = $fromBytes[$byteIndex];
+ $toByte = $toBytes[$byteIndex];
+ if ($fromByte === $toByte) {
+ $sameBits += 8;
+ } else {
+ $differentBitsInByte = decbin($fromByte ^ $toByte);
+ $sameBits += 8 - strlen($differentBitsInByte);
+ break;
+ }
+ }
+ $result = static::parseRangeString($from->toString() . '/' . (string) $sameBits);
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string|\IPLib\Address\AddressInterface $from
+ * @param string|\IPLib\Address\AddressInterface $to
+ * @param int $flags
+ *
+ * @return \IPLib\Address\AddressInterface[]|null[]|false[]
+ */
+ private static function parseBoundaries($from, $to, $flags = 0)
+ {
+ $result = array();
+ foreach (array('from', 'to') as $param) {
+ $value = $$param;
+ if (!($value instanceof AddressInterface)) {
+ $value = (string) $value;
+ if ($value === '') {
+ $value = null;
+ } else {
+ $value = static::parseAddressString($value, $flags);
+ if ($value === null) {
+ $value = false;
+ }
+ }
+ }
+ $result[] = $value;
+ }
+ if ($result[0] && $result[1] && strcmp($result[0]->getComparableString(), $result[1]->getComparableString()) > 0) {
+ $result = array($result[1], $result[0]);
+ }
+
+ return $result;
+ }
+}
diff --git a/mlocati/ip-lib/src/ParseStringFlag.php b/mlocati/ip-lib/src/ParseStringFlag.php
new file mode 100644
index 00000000..860256e7
--- /dev/null
+++ b/mlocati/ip-lib/src/ParseStringFlag.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace IPLib;
+
+/**
+ * Flags for the parseString() methods.
+ *
+ * @since 1.17.0
+ */
+class ParseStringFlag
+{
+ /**
+ * Use this flag if the input string may include the port.
+ *
+ * @var int
+ */
+ const MAY_INCLUDE_PORT = 1;
+
+ /**
+ * Use this flag if the input string may include a zone ID.
+ *
+ * @var int
+ */
+ const MAY_INCLUDE_ZONEID = 2;
+
+ /**
+ * Use this flag if IPv4 addresses may be in decimal/octal/hexadecimal format.
+ * This notation is accepted by the implementation of inet_aton and inet_addr of the libc implementation of GNU, Windows and Mac (but not Musl), but not by inet_pton and ip2long.
+ *
+ * @var int
+ *
+ * @example 1.08.0x10.0 => 5.0.0.1
+ * @example 5.256 => 5.0.1.0
+ * @example 5.0.256 => 5.0.1.0
+ * @example 123456789 => 7.91.205.21
+ */
+ const IPV4_MAYBE_NON_DECIMAL = 4;
+
+ /**
+ * Use this flag if IPv4 subnet ranges may be in compact form.
+ *
+ * @example 127/24 => 127.0.0.0/24
+ * @example 10/8 => 10.0.0.0/8
+ * @example 10/24 => 10.0.0.0/24
+ * @example 10.10.10/24 => 10.10.10.0/24
+ *
+ * @var int
+ */
+ const IPV4SUBNET_MAYBE_COMPACT = 8;
+
+ /**
+ * Use this flag if IPv4 addresses may be in non quad-dotted notation.
+ * This notation is accepted by the implementation of inet_aton and inet_addr of the libc implementation of GNU, Windows and Mac (but not Musl), but not by inet_pton and ip2long.
+ *
+ * @var int
+ *
+ * @example 5.1 => 5.0.0.1
+ * @example 5.256 => 5.0.1.0
+ * @example 5.0.256 => 5.0.1.0
+ * @example 123456789 => 7.91.205.21
+ *
+ * @see https://man7.org/linux/man-pages/man3/inet_addr.3.html#DESCRIPTION
+ * @see https://www.freebsd.org/cgi/man.cgi?query=inet_net&sektion=3&apropos=0&manpath=FreeBSD+12.2-RELEASE+and+Ports#end
+ * @see http://git.musl-libc.org/cgit/musl/tree/src/network/inet_aton.c?h=v1.2.2
+ */
+ const IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED = 16;
+
+ /**
+ * Use this flag if you want to accept parsing IPv4/IPv6 addresses in Reverse DNS Lookup Address format.
+ *
+ * @var int
+ *
+ * @since 1.18.0
+ *
+ * @example 140.13.12.10.in-addr.arpa => 10.12.13.140
+ * @example b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.ip6.arpa => 4321:0:1:2:3:4:567:89ab
+ */
+ const ADDRESS_MAYBE_RDNS = 32;
+}
diff --git a/mlocati/ip-lib/src/Range/AbstractRange.php b/mlocati/ip-lib/src/Range/AbstractRange.php
new file mode 100644
index 00000000..12204350
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/AbstractRange.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace IPLib\Range;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Address\IPv4;
+use IPLib\Address\IPv6;
+use IPLib\Address\Type as AddressType;
+use IPLib\Factory;
+
+/**
+ * Base class for range classes.
+ */
+abstract class AbstractRange implements RangeInterface
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getRangeType()
+ */
+ public function getRangeType()
+ {
+ if ($this->rangeType === null) {
+ $addressType = $this->getAddressType();
+ if ($addressType === AddressType::T_IPv6 && Subnet::get6to4()->containsRange($this)) {
+ $this->rangeType = Factory::getRangeFromBoundaries($this->fromAddress->toIPv4(), $this->toAddress->toIPv4())->getRangeType();
+ } else {
+ switch ($addressType) {
+ case AddressType::T_IPv4:
+ $defaultType = IPv4::getDefaultReservedRangeType();
+ $reservedRanges = IPv4::getReservedRanges();
+ break;
+ case AddressType::T_IPv6:
+ $defaultType = IPv6::getDefaultReservedRangeType();
+ $reservedRanges = IPv6::getReservedRanges();
+ break;
+ default:
+ throw new \Exception('@todo'); // @codeCoverageIgnore
+ }
+ $rangeType = null;
+ foreach ($reservedRanges as $reservedRange) {
+ $rangeType = $reservedRange->getRangeType($this);
+ if ($rangeType !== null) {
+ break;
+ }
+ }
+ $this->rangeType = $rangeType === null ? $defaultType : $rangeType;
+ }
+ }
+
+ return $this->rangeType === false ? null : $this->rangeType;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getAddressAtOffset()
+ */
+ public function getAddressAtOffset($n)
+ {
+ if (!is_int($n)) {
+ return null;
+ }
+
+ $address = null;
+ if ($n >= 0) {
+ $start = Factory::parseAddressString($this->getComparableStartString());
+ $address = $start->getAddressAtOffset($n);
+ } else {
+ $end = Factory::parseAddressString($this->getComparableEndString());
+ $address = $end->getAddressAtOffset($n + 1);
+ }
+
+ if ($address === null) {
+ return null;
+ }
+
+ return $this->contains($address) ? $address : null;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::contains()
+ */
+ public function contains(AddressInterface $address)
+ {
+ $result = false;
+ if ($address->getAddressType() === $this->getAddressType()) {
+ $cmp = $address->getComparableString();
+ $from = $this->getComparableStartString();
+ if ($cmp >= $from) {
+ $to = $this->getComparableEndString();
+ if ($cmp <= $to) {
+ $result = true;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::containsRange()
+ */
+ public function containsRange(RangeInterface $range)
+ {
+ $result = false;
+ if ($range->getAddressType() === $this->getAddressType()) {
+ $myStart = $this->getComparableStartString();
+ $itsStart = $range->getComparableStartString();
+ if ($itsStart >= $myStart) {
+ $myEnd = $this->getComparableEndString();
+ $itsEnd = $range->getComparableEndString();
+ if ($itsEnd <= $myEnd) {
+ $result = true;
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/mlocati/ip-lib/src/Range/Pattern.php b/mlocati/ip-lib/src/Range/Pattern.php
new file mode 100644
index 00000000..1e180ead
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/Pattern.php
@@ -0,0 +1,322 @@
+<?php
+
+namespace IPLib\Range;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Address\IPv4;
+use IPLib\Address\IPv6;
+use IPLib\Address\Type as AddressType;
+use IPLib\ParseStringFlag;
+
+/**
+ * Represents an address range in pattern format (only ending asterisks are supported).
+ *
+ * @example 127.0.*.*
+ * @example ::/8
+ */
+class Pattern extends AbstractRange
+{
+ /**
+ * Starting address of the range.
+ *
+ * @var \IPLib\Address\AddressInterface
+ */
+ protected $fromAddress;
+
+ /**
+ * Final address of the range.
+ *
+ * @var \IPLib\Address\AddressInterface
+ */
+ protected $toAddress;
+
+ /**
+ * Number of ending asterisks.
+ *
+ * @var int
+ */
+ protected $asterisksCount;
+
+ /**
+ * The type of the range of this IP range.
+ *
+ * @var int|false|null false if this range crosses multiple range types, null if yet to be determined
+ *
+ * @since 1.5.0
+ */
+ protected $rangeType;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param \IPLib\Address\AddressInterface $fromAddress
+ * @param \IPLib\Address\AddressInterface $toAddress
+ * @param int $asterisksCount
+ */
+ public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $asterisksCount)
+ {
+ $this->fromAddress = $fromAddress;
+ $this->toAddress = $toAddress;
+ $this->asterisksCount = $asterisksCount;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::__toString()
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseString() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $range
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return static|null
+ *
+ * @see \IPLib\Range\Pattern::parseString()
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function fromString($range, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Try get the range instance starting from its string representation.
+ *
+ * @param string|mixed $range
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return static|null
+ *
+ * @since 1.17.0
+ * @see \IPLib\ParseStringFlag
+ */
+ public static function parseString($range, $flags = 0)
+ {
+ if (!is_string($range) || strpos($range, '*') === false) {
+ return null;
+ }
+ if ($range === '*.*.*.*') {
+ return new static(IPv4::parseString('0.0.0.0'), IPv4::parseString('255.255.255.255'), 4);
+ }
+ if ($range === '*:*:*:*:*:*:*:*') {
+ return new static(IPv6::parseString('::'), IPv6::parseString('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), 8);
+ }
+ $matches = null;
+ if (strpos($range, '.') !== false && preg_match('/^[^*]+((?:\.\*)+)$/', $range, $matches)) {
+ $asterisksCount = strlen($matches[1]) >> 1;
+ if ($asterisksCount > 0) {
+ $missingDots = 3 - substr_count($range, '.');
+ if ($missingDots > 0) {
+ $range .= str_repeat('.*', $missingDots);
+ $asterisksCount += $missingDots;
+ }
+ }
+ $fromAddress = IPv4::parseString(str_replace('*', '0', $range), $flags);
+ if ($fromAddress === null) {
+ return null;
+ }
+ $fixedBytes = array_slice($fromAddress->getBytes(), 0, -$asterisksCount);
+ $otherBytes = array_fill(0, $asterisksCount, 255);
+ $toAddress = IPv4::fromBytes(array_merge($fixedBytes, $otherBytes));
+
+ return new static($fromAddress, $toAddress, $asterisksCount);
+ }
+ if (strpos($range, ':') !== false && preg_match('/^[^*]+((?::\*)+)$/', $range, $matches)) {
+ $asterisksCount = strlen($matches[1]) >> 1;
+ $fromAddress = IPv6::parseString(str_replace('*', '0', $range));
+ if ($fromAddress === null) {
+ return null;
+ }
+ $fixedWords = array_slice($fromAddress->getWords(), 0, -$asterisksCount);
+ $otherWords = array_fill(0, $asterisksCount, 0xffff);
+ $toAddress = IPv6::fromWords(array_merge($fixedWords, $otherWords));
+
+ return new static($fromAddress, $toAddress, $asterisksCount);
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::toString()
+ */
+ public function toString($long = false)
+ {
+ if ($this->asterisksCount === 0) {
+ return $this->fromAddress->toString($long);
+ }
+ switch (true) {
+ case $this->fromAddress instanceof \IPLib\Address\IPv4:
+ $chunks = explode('.', $this->fromAddress->toString());
+ $chunks = array_slice($chunks, 0, -$this->asterisksCount);
+ $chunks = array_pad($chunks, 4, '*');
+ $result = implode('.', $chunks);
+ break;
+ case $this->fromAddress instanceof \IPLib\Address\IPv6:
+ if ($long) {
+ $chunks = explode(':', $this->fromAddress->toString(true));
+ $chunks = array_slice($chunks, 0, -$this->asterisksCount);
+ $chunks = array_pad($chunks, 8, '*');
+ $result = implode(':', $chunks);
+ } elseif ($this->asterisksCount === 8) {
+ $result = '*:*:*:*:*:*:*:*';
+ } else {
+ $bytes = $this->toAddress->getBytes();
+ $bytes = array_slice($bytes, 0, -$this->asterisksCount * 2);
+ $bytes = array_pad($bytes, 16, 1);
+ $address = IPv6::fromBytes($bytes);
+ $before = substr($address->toString(false), 0, -strlen(':101') * $this->asterisksCount);
+ $result = $before . str_repeat(':*', $this->asterisksCount);
+ }
+ break;
+ default:
+ throw new \Exception('@todo'); // @codeCoverageIgnore
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getAddressType()
+ */
+ public function getAddressType()
+ {
+ return $this->fromAddress->getAddressType();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getStartAddress()
+ */
+ public function getStartAddress()
+ {
+ return $this->fromAddress;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getEndAddress()
+ */
+ public function getEndAddress()
+ {
+ return $this->toAddress;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableStartString()
+ */
+ public function getComparableStartString()
+ {
+ return $this->fromAddress->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableEndString()
+ */
+ public function getComparableEndString()
+ {
+ return $this->toAddress->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asSubnet()
+ * @since 1.8.0
+ */
+ public function asSubnet()
+ {
+ return new Subnet($this->getStartAddress(), $this->getEndAddress(), $this->getNetworkPrefix());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asPattern()
+ */
+ public function asPattern()
+ {
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSubnetMask()
+ */
+ public function getSubnetMask()
+ {
+ if ($this->getAddressType() !== AddressType::T_IPv4) {
+ return null;
+ }
+ switch ($this->asterisksCount) {
+ case 0:
+ $bytes = array(255, 255, 255, 255);
+ break;
+ case 4:
+ $bytes = array(0, 0, 0, 0);
+ break;
+ default:
+ $bytes = array_pad(array_fill(0, 4 - $this->asterisksCount, 255), 4, 0);
+ break;
+ }
+
+ return IPv4::fromBytes($bytes);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
+ */
+ public function getReverseDNSLookupName()
+ {
+ return $this->asterisksCount === 0 ? array($this->getStartAddress()->getReverseDNSLookupName()) : $this->asSubnet()->getReverseDNSLookupName();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSize()
+ */
+ public function getSize()
+ {
+ $fromAddress = $this->fromAddress;
+ $maxPrefix = $fromAddress::getNumberOfBits();
+ $prefix = $this->getNetworkPrefix();
+
+ return pow(2, ($maxPrefix - $prefix));
+ }
+
+ /**
+ * @return float|int
+ */
+ private function getNetworkPrefix()
+ {
+ switch ($this->getAddressType()) {
+ case AddressType::T_IPv4:
+ return 8 * (4 - $this->asterisksCount);
+ case AddressType::T_IPv6:
+ return 16 * (8 - $this->asterisksCount);
+ }
+ }
+}
diff --git a/mlocati/ip-lib/src/Range/RangeInterface.php b/mlocati/ip-lib/src/Range/RangeInterface.php
new file mode 100644
index 00000000..4d4d8347
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/RangeInterface.php
@@ -0,0 +1,160 @@
+<?php
+
+namespace IPLib\Range;
+
+use IPLib\Address\AddressInterface;
+
+/**
+ * Interface of all the range types.
+ */
+interface RangeInterface
+{
+ /**
+ * Get the short string representation of this address.
+ *
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * Get the string representation of this address.
+ *
+ * @param bool $long set to true to have a long/full representation, false otherwise
+ *
+ * @return string
+ *
+ * @example If $long is true, you'll get '0000:0000:0000:0000:0000:0000:0000:0001/128', '::1/128' otherwise.
+ */
+ public function toString($long = false);
+
+ /**
+ * Get the type of the IP addresses contained in this range.
+ *
+ * @return int One of the \IPLib\Address\Type::T_... constants
+ */
+ public function getAddressType();
+
+ /**
+ * Get the type of range of the IP address.
+ *
+ * @return int One of the \IPLib\Range\Type::T_... constants
+ *
+ * @since 1.5.0
+ */
+ public function getRangeType();
+
+ /**
+ * Get the address at a certain offset of this range.
+ *
+ * @param int $n the offset of the address (support negative offset)
+ *
+ * @return \IPLib\Address\AddressInterface|null return NULL if $n is not an integer or if the offset out of range
+ *
+ * @since 1.15.0
+ *
+ * @example passing 256 to the range 127.0.0.0/16 will result in 127.0.1.0
+ * @example passing -1 to the range 127.0.1.0/16 will result in 127.0.255.255
+ * @example passing 256 to the range 127.0.0.0/24 will result in NULL
+ */
+ public function getAddressAtOffset($n);
+
+ /**
+ * Check if this range contains an IP address.
+ *
+ * @param \IPLib\Address\AddressInterface $address
+ *
+ * @return bool
+ */
+ public function contains(AddressInterface $address);
+
+ /**
+ * Check if this range contains another range.
+ *
+ * @param \IPLib\Range\RangeInterface $range
+ *
+ * @return bool
+ *
+ * @since 1.5.0
+ */
+ public function containsRange(RangeInterface $range);
+
+ /**
+ * Get the initial address contained in this range.
+ *
+ * @return \IPLib\Address\AddressInterface
+ *
+ * @since 1.4.0
+ */
+ public function getStartAddress();
+
+ /**
+ * Get the final address contained in this range.
+ *
+ * @return \IPLib\Address\AddressInterface
+ *
+ * @since 1.4.0
+ */
+ public function getEndAddress();
+
+ /**
+ * Get a string representation of the starting address of this range than can be used when comparing addresses and ranges.
+ *
+ * @return string
+ */
+ public function getComparableStartString();
+
+ /**
+ * Get a string representation of the final address of this range than can be used when comparing addresses and ranges.
+ *
+ * @return string
+ */
+ public function getComparableEndString();
+
+ /**
+ * Get the subnet mask representing this range (only for IPv4 ranges).
+ *
+ * @return \IPLib\Address\IPv4|null return NULL if the range is an IPv6 range, the subnet mask otherwise
+ *
+ * @since 1.8.0
+ */
+ public function getSubnetMask();
+
+ /**
+ * Get the subnet/CIDR representation of this range.
+ *
+ * @return \IPLib\Range\Subnet
+ *
+ * @since 1.13.0
+ */
+ public function asSubnet();
+
+ /**
+ * Get the pattern/asterisk representation (if applicable) of this range.
+ *
+ * @return \IPLib\Range\Pattern|null return NULL if this range can't be represented by a pattern notation
+ *
+ * @since 1.13.0
+ */
+ public function asPattern();
+
+ /**
+ * Get the Reverse DNS Lookup Addresses of this IP range.
+ *
+ * @return string[]
+ *
+ * @since 1.13.0
+ *
+ * @example for IPv4 it returns something like array('x.x.x.x.in-addr.arpa', 'x.x.x.x.in-addr.arpa') (where the number of 'x.' ranges from 1 to 4)
+ * @example for IPv6 it returns something like array('x.x.x.x..x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa', 'x.x.x.x..x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa') (where the number of 'x.' ranges from 1 to 32)
+ */
+ public function getReverseDNSLookupName();
+
+ /**
+ * Get the count of addresses this IP range contains.
+ *
+ * @return int|float Return float as for huge IPv6 networks, int is not enough
+ *
+ * @since 1.16.0
+ */
+ public function getSize();
+}
diff --git a/mlocati/ip-lib/src/Range/Single.php b/mlocati/ip-lib/src/Range/Single.php
new file mode 100644
index 00000000..ec3531f6
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/Single.php
@@ -0,0 +1,244 @@
+<?php
+
+namespace IPLib\Range;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Address\IPv4;
+use IPLib\Address\Type as AddressType;
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+/**
+ * Represents a single address (eg a range that contains just one address).
+ *
+ * @example 127.0.0.1
+ * @example ::1
+ */
+class Single extends AbstractRange
+{
+ /**
+ * @var \IPLib\Address\AddressInterface
+ */
+ protected $address;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param \IPLib\Address\AddressInterface $address
+ */
+ protected function __construct(AddressInterface $address)
+ {
+ $this->address = $address;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::__toString()
+ */
+ public function __toString()
+ {
+ return $this->address->__toString();
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseString() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $range
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return static|null
+ *
+ * @see \IPLib\Range\Single::parseString()
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function fromString($range, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Try get the range instance starting from its string representation.
+ *
+ * @param string|mixed $range
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return static|null
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function parseString($range, $flags = 0)
+ {
+ $result = null;
+ $flags = (int) $flags;
+ $address = Factory::parseAddressString($range, $flags);
+ if ($address !== null) {
+ $result = new static($address);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Create the range instance starting from an address instance.
+ *
+ * @param \IPLib\Address\AddressInterface $address
+ *
+ * @return static
+ *
+ * @since 1.2.0
+ */
+ public static function fromAddress(AddressInterface $address)
+ {
+ return new static($address);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::toString()
+ */
+ public function toString($long = false)
+ {
+ return $this->address->toString($long);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getAddressType()
+ */
+ public function getAddressType()
+ {
+ return $this->address->getAddressType();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getRangeType()
+ */
+ public function getRangeType()
+ {
+ return $this->address->getRangeType();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::contains()
+ */
+ public function contains(AddressInterface $address)
+ {
+ $result = false;
+ if ($address->getAddressType() === $this->getAddressType()) {
+ if ($address->toString(false) === $this->address->toString(false)) {
+ $result = true;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getStartAddress()
+ */
+ public function getStartAddress()
+ {
+ return $this->address;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getEndAddress()
+ */
+ public function getEndAddress()
+ {
+ return $this->address;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableStartString()
+ */
+ public function getComparableStartString()
+ {
+ return $this->address->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableEndString()
+ */
+ public function getComparableEndString()
+ {
+ return $this->address->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asSubnet()
+ */
+ public function asSubnet()
+ {
+ $networkPrefixes = array(
+ AddressType::T_IPv4 => 32,
+ AddressType::T_IPv6 => 128,
+ );
+
+ return new Subnet($this->address, $this->address, $networkPrefixes[$this->address->getAddressType()]);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asPattern()
+ */
+ public function asPattern()
+ {
+ return new Pattern($this->address, $this->address, 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSubnetMask()
+ */
+ public function getSubnetMask()
+ {
+ if ($this->getAddressType() !== AddressType::T_IPv4) {
+ return null;
+ }
+
+ return IPv4::fromBytes(array(255, 255, 255, 255));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
+ */
+ public function getReverseDNSLookupName()
+ {
+ return array($this->getStartAddress()->getReverseDNSLookupName());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSize()
+ */
+ public function getSize()
+ {
+ return 1;
+ }
+}
diff --git a/mlocati/ip-lib/src/Range/Subnet.php b/mlocati/ip-lib/src/Range/Subnet.php
new file mode 100644
index 00000000..ea397622
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/Subnet.php
@@ -0,0 +1,354 @@
+<?php
+
+namespace IPLib\Range;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Address\IPv4;
+use IPLib\Address\Type as AddressType;
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+/**
+ * Represents an address range in subnet format (eg CIDR).
+ *
+ * @example 127.0.0.1/32
+ * @example ::/8
+ */
+class Subnet extends AbstractRange
+{
+ /**
+ * Starting address of the range.
+ *
+ * @var \IPLib\Address\AddressInterface
+ */
+ protected $fromAddress;
+
+ /**
+ * Final address of the range.
+ *
+ * @var \IPLib\Address\AddressInterface
+ */
+ protected $toAddress;
+
+ /**
+ * Number of the same bits of the range.
+ *
+ * @var int
+ */
+ protected $networkPrefix;
+
+ /**
+ * The type of the range of this IP range.
+ *
+ * @var int|null
+ *
+ * @since 1.5.0
+ */
+ protected $rangeType;
+
+ /**
+ * The 6to4 address IPv6 address range.
+ *
+ * @var self|null
+ */
+ private static $sixToFour;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param \IPLib\Address\AddressInterface $fromAddress
+ * @param \IPLib\Address\AddressInterface $toAddress
+ * @param int $networkPrefix
+ *
+ * @internal
+ */
+ public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $networkPrefix)
+ {
+ $this->fromAddress = $fromAddress;
+ $this->toAddress = $toAddress;
+ $this->networkPrefix = $networkPrefix;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::__toString()
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseString() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $range
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return static|null
+ *
+ * @see \IPLib\Range\Subnet::parseString()
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function fromString($range, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Try get the range instance starting from its string representation.
+ *
+ * @param string|mixed $range
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return static|null
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function parseString($range, $flags = 0)
+ {
+ if (!is_string($range)) {
+ return null;
+ }
+ $parts = explode('/', $range);
+ if (count($parts) !== 2) {
+ return null;
+ }
+ $flags = (int) $flags;
+ if (strpos($parts[0], ':') === false && $flags & ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT) {
+ $missingDots = 3 - substr_count($parts[0], '.');
+ if ($missingDots > 0) {
+ $parts[0] .= str_repeat('.0', $missingDots);
+ }
+ }
+ $address = Factory::parseAddressString($parts[0], $flags);
+ if ($address === null) {
+ return null;
+ }
+ if (!preg_match('/^[0-9]{1,9}$/', $parts[1])) {
+ return null;
+ }
+ $networkPrefix = (int) $parts[1];
+ $addressBytes = $address->getBytes();
+ $totalBytes = count($addressBytes);
+ $numDifferentBits = $totalBytes * 8 - $networkPrefix;
+ if ($numDifferentBits < 0) {
+ return null;
+ }
+ $numSameBytes = $networkPrefix >> 3;
+ $sameBytes = array_slice($addressBytes, 0, $numSameBytes);
+ $differentBytesStart = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 0);
+ $differentBytesEnd = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 255);
+ $startSameBits = $networkPrefix % 8;
+ if ($startSameBits !== 0) {
+ $varyingByte = $addressBytes[$numSameBytes];
+ $differentBytesStart[0] = $varyingByte & bindec(str_pad(str_repeat('1', $startSameBits), 8, '0', STR_PAD_RIGHT));
+ $differentBytesEnd[0] = $differentBytesStart[0] + bindec(str_repeat('1', 8 - $startSameBits));
+ }
+
+ return new static(
+ Factory::addressFromBytes(array_merge($sameBytes, $differentBytesStart)),
+ Factory::addressFromBytes(array_merge($sameBytes, $differentBytesEnd)),
+ $networkPrefix
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::toString()
+ */
+ public function toString($long = false)
+ {
+ return $this->fromAddress->toString($long) . '/' . $this->networkPrefix;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getAddressType()
+ */
+ public function getAddressType()
+ {
+ return $this->fromAddress->getAddressType();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getStartAddress()
+ */
+ public function getStartAddress()
+ {
+ return $this->fromAddress;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getEndAddress()
+ */
+ public function getEndAddress()
+ {
+ return $this->toAddress;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableStartString()
+ */
+ public function getComparableStartString()
+ {
+ return $this->fromAddress->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableEndString()
+ */
+ public function getComparableEndString()
+ {
+ return $this->toAddress->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asSubnet()
+ */
+ public function asSubnet()
+ {
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asPattern()
+ * @since 1.8.0
+ */
+ public function asPattern()
+ {
+ $address = $this->getStartAddress();
+ $networkPrefix = $this->getNetworkPrefix();
+ switch ($address->getAddressType()) {
+ case AddressType::T_IPv4:
+ return $networkPrefix % 8 === 0 ? new Pattern($address, $address, 4 - $networkPrefix / 8) : null;
+ case AddressType::T_IPv6:
+ return $networkPrefix % 16 === 0 ? new Pattern($address, $address, 8 - $networkPrefix / 16) : null;
+ }
+ }
+
+ /**
+ * Get the 6to4 address IPv6 address range.
+ *
+ * @return self
+ *
+ * @since 1.5.0
+ */
+ public static function get6to4()
+ {
+ if (self::$sixToFour === null) {
+ self::$sixToFour = self::parseString('2002::/16');
+ }
+
+ return self::$sixToFour;
+ }
+
+ /**
+ * Get subnet prefix.
+ *
+ * @return int
+ *
+ * @since 1.7.0
+ */
+ public function getNetworkPrefix()
+ {
+ return $this->networkPrefix;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSubnetMask()
+ */
+ public function getSubnetMask()
+ {
+ if ($this->getAddressType() !== AddressType::T_IPv4) {
+ return null;
+ }
+ $bytes = array();
+ $prefix = $this->getNetworkPrefix();
+ while ($prefix >= 8) {
+ $bytes[] = 255;
+ $prefix -= 8;
+ }
+ if ($prefix !== 0) {
+ $bytes[] = bindec(str_pad(str_repeat('1', $prefix), 8, '0'));
+ }
+ $bytes = array_pad($bytes, 4, 0);
+
+ return IPv4::fromBytes($bytes);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
+ */
+ public function getReverseDNSLookupName()
+ {
+ switch ($this->getAddressType()) {
+ case AddressType::T_IPv4:
+ $unitSize = 8; // bytes
+ $maxUnits = 4;
+ $isHex = false;
+ $rxUnit = '\d+';
+ break;
+ case AddressType::T_IPv6:
+ $unitSize = 4; // nibbles
+ $maxUnits = 32;
+ $isHex = true;
+ $rxUnit = '[0-9A-Fa-f]';
+ break;
+ }
+ $totBits = $unitSize * $maxUnits;
+ $prefixUnits = (int) ($this->networkPrefix / $unitSize);
+ $extraBits = ($totBits - $this->networkPrefix) % $unitSize;
+ if ($extraBits !== 0) {
+ $prefixUnits += 1;
+ }
+ $numVariants = 1 << $extraBits;
+ $result = array();
+ $unitsToRemove = $maxUnits - $prefixUnits;
+ $initialPointer = preg_replace("/^(({$rxUnit})\.){{$unitsToRemove}}/", '', $this->getStartAddress()->getReverseDNSLookupName());
+ $chunks = explode('.', $initialPointer, 2);
+ for ($index = 0; $index < $numVariants; $index++) {
+ if ($index !== 0) {
+ $chunks[0] = $isHex ? dechex(1 + hexdec($chunks[0])) : (string) (1 + (int) $chunks[0]);
+ }
+ $result[] = implode('.', $chunks);
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSize()
+ */
+ public function getSize()
+ {
+ $fromAddress = $this->fromAddress;
+ $maxPrefix = $fromAddress::getNumberOfBits();
+ $prefix = $this->getNetworkPrefix();
+
+ return pow(2, ($maxPrefix - $prefix));
+ }
+}
diff --git a/mlocati/ip-lib/src/Range/Type.php b/mlocati/ip-lib/src/Range/Type.php
new file mode 100644
index 00000000..b2ba8eb7
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/Type.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace IPLib\Range;
+
+/**
+ * Types of IP address classes.
+ */
+class Type
+{
+ /**
+ * Unspecified/unknown address.
+ *
+ * @var int
+ */
+ const T_UNSPECIFIED = 1;
+
+ /**
+ * Reserved/internal use only.
+ *
+ * @var int
+ */
+ const T_RESERVED = 2;
+
+ /**
+ * Refer to source hosts on "this" network.
+ *
+ * @var int
+ */
+ const T_THISNETWORK = 3;
+
+ /**
+ * Internet host loopback address.
+ *
+ * @var int
+ */
+ const T_LOOPBACK = 4;
+
+ /**
+ * Relay anycast address.
+ *
+ * @var int
+ */
+ const T_ANYCASTRELAY = 5;
+
+ /**
+ * "Limited broadcast" destination address.
+ *
+ * @var int
+ */
+ const T_LIMITEDBROADCAST = 6;
+
+ /**
+ * Multicast address assignments - Indentify a group of interfaces.
+ *
+ * @var int
+ */
+ const T_MULTICAST = 7;
+
+ /**
+ * "Link local" address, allocated for communication between hosts on a single link.
+ *
+ * @var int
+ */
+ const T_LINKLOCAL = 8;
+
+ /**
+ * Link local unicast / Linked-scoped unicast.
+ *
+ * @var int
+ */
+ const T_LINKLOCAL_UNICAST = 9;
+
+ /**
+ * Discard-Only address.
+ *
+ * @var int
+ */
+ const T_DISCARDONLY = 10;
+
+ /**
+ * Discard address.
+ *
+ * @var int
+ */
+ const T_DISCARD = 11;
+
+ /**
+ * For use in private networks.
+ *
+ * @var int
+ */
+ const T_PRIVATENETWORK = 12;
+
+ /**
+ * Public address.
+ *
+ * @var int
+ */
+ const T_PUBLIC = 13;
+
+ /**
+ * Carrier-grade NAT address.
+ *
+ * @var int
+ *
+ * @since 1.10.0
+ */
+ const T_CGNAT = 14;
+
+ /**
+ * Get the name of a type.
+ *
+ * @param int $type
+ *
+ * @return string
+ */
+ public static function getName($type)
+ {
+ switch ($type) {
+ case static::T_UNSPECIFIED:
+ return 'Unspecified/unknown address';
+ case static::T_RESERVED:
+ return 'Reserved/internal use only';
+ case static::T_THISNETWORK:
+ return 'Refer to source hosts on "this" network';
+ case static::T_LOOPBACK:
+ return 'Internet host loopback address';
+ case static::T_ANYCASTRELAY:
+ return 'Relay anycast address';
+ case static::T_LIMITEDBROADCAST:
+ return '"Limited broadcast" destination address';
+ case static::T_MULTICAST:
+ return 'Multicast address assignments - Indentify a group of interfaces';
+ case static::T_LINKLOCAL:
+ return '"Link local" address, allocated for communication between hosts on a single link';
+ case static::T_LINKLOCAL_UNICAST:
+ return 'Link local unicast / Linked-scoped unicast';
+ case static::T_DISCARDONLY:
+ return 'Discard only';
+ case static::T_DISCARD:
+ return 'Discard';
+ case static::T_PRIVATENETWORK:
+ return 'For use in private networks';
+ case static::T_PUBLIC:
+ return 'Public address';
+ case static::T_CGNAT:
+ return 'Carrier-grade NAT';
+ default:
+ return $type === null ? 'Unknown type' : sprintf('Unknown type (%s)', $type);
+ }
+ }
+}
diff --git a/mlocati/ip-lib/src/Service/BinaryMath.php b/mlocati/ip-lib/src/Service/BinaryMath.php
new file mode 100644
index 00000000..727fecf9
--- /dev/null
+++ b/mlocati/ip-lib/src/Service/BinaryMath.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace IPLib\Service;
+
+/**
+ * Helper class to work with unsigned binary integers.
+ *
+ * @internal
+ */
+class BinaryMath
+{
+ /**
+ * Trim the leading zeroes from a non-negative integer represented in binary form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function reduce($value)
+ {
+ $value = ltrim($value, '0');
+
+ return $value === '' ? '0' : $value;
+ }
+
+ /**
+ * Compare two non-negative integers represented in binary form.
+ *
+ * @param string $a
+ * @param string $b
+ *
+ * @return int 1 if $a is greater than $b, -1 if $b is greater than $b, 0 if they are the same
+ */
+ public function compare($a, $b)
+ {
+ list($a, $b) = $this->toSameLength($a, $b);
+
+ return $a < $b ? -1 : ($a > $b ? 1 : 0);
+ }
+
+ /**
+ * Add 1 to a non-negative integer represented in binary form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function increment($value)
+ {
+ $lastZeroIndex = strrpos($value, '0');
+ if ($lastZeroIndex === false) {
+ return '1' . str_repeat('0', strlen($value));
+ }
+
+ return ltrim(substr($value, 0, $lastZeroIndex), '0') . '1' . str_repeat('0', strlen($value) - $lastZeroIndex - 1);
+ }
+
+ /**
+ * Calculate the bitwise AND of two non-negative integers represented in binary form.
+ *
+ * @param string $operand1
+ * @param string $operand2
+ *
+ * @return string
+ */
+ public function andX($operand1, $operand2)
+ {
+ $operand1 = $this->reduce($operand1);
+ $operand2 = $this->reduce($operand2);
+ $numBits = min(strlen($operand1), strlen($operand2));
+ $operand1 = substr(str_pad($operand1, $numBits, '0', STR_PAD_LEFT), -$numBits);
+ $operand2 = substr(str_pad($operand2, $numBits, '0', STR_PAD_LEFT), -$numBits);
+ $result = '';
+ for ($index = 0; $index < $numBits; $index++) {
+ $result .= $operand1[$index] === '1' && $operand2[$index] === '1' ? '1' : '0';
+ }
+
+ return $this->reduce($result);
+ }
+
+ /**
+ * Calculate the bitwise OR of two non-negative integers represented in binary form.
+ *
+ * @param string $operand1
+ * @param string $operand2
+ *
+ * @return string
+ */
+ public function orX($operand1, $operand2)
+ {
+ list($operand1, $operand2, $numBits) = $this->toSameLength($operand1, $operand2);
+ $result = '';
+ for ($index = 0; $index < $numBits; $index++) {
+ $result .= $operand1[$index] === '1' || $operand2[$index] === '1' ? '1' : '0';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Zero-padding of two non-negative integers represented in binary form, so that they have the same length.
+ *
+ * @param string $num1
+ * @param string $num2
+ *
+ * @return string[],int[] The first array element is $num1 (padded), the first array element is $num2 (padded), the third array element is the number of bits
+ */
+ private function toSameLength($num1, $num2)
+ {
+ $num1 = $this->reduce($num1);
+ $num2 = $this->reduce($num2);
+ $numBits = max(strlen($num1), strlen($num2));
+
+ return array(
+ str_pad($num1, $numBits, '0', STR_PAD_LEFT),
+ str_pad($num2, $numBits, '0', STR_PAD_LEFT),
+ $numBits,
+ );
+ }
+}
diff --git a/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php b/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php
new file mode 100644
index 00000000..7a127e79
--- /dev/null
+++ b/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace IPLib\Service;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Factory;
+use IPLib\Range\Subnet;
+
+/**
+ * Helper class to calculate the subnets describing all (and only all) the addresses between two boundaries.
+ *
+ * @internal
+ */
+class RangesFromBoundaryCalculator
+{
+ /**
+ * The BinaryMath instance to be used to perform bitwise operations.
+ *
+ * @var \IPLib\Service\BinaryMath
+ */
+ private $math;
+
+ /**
+ * The number of bits used to represent addresses.
+ *
+ * @var int
+ *
+ * @example 32 for IPv4, 128 for IPv6
+ */
+ private $numBits;
+
+ /**
+ * The bit masks for every bit index.
+ *
+ * @var string[]
+ */
+ private $masks;
+
+ /**
+ * The bit unmasks for every bit index.
+ *
+ * @var string[]
+ */
+ private $unmasks;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param int $numBits the number of bits used to represent addresses (32 for IPv4, 128 for IPv6)
+ */
+ public function __construct($numBits)
+ {
+ $this->math = new BinaryMath();
+ $this->setNumBits($numBits);
+ }
+
+ /**
+ * Calculate the subnets describing all (and only all) the addresses between two boundaries.
+ *
+ * @param \IPLib\Address\AddressInterface $from
+ * @param \IPLib\Address\AddressInterface $to
+ *
+ * @return \IPLib\Range\Subnet[]|null return NULL if the two addresses have an invalid number of bits (that is, different from the one passed to the constructor of this class)
+ */
+ public function getRanges(AddressInterface $from, AddressInterface $to)
+ {
+ if ($from->getNumberOfBits() !== $this->numBits || $to->getNumberOfBits() !== $this->numBits) {
+ return null;
+ }
+ if ($from->getComparableString() > $to->getComparableString()) {
+ list($from, $to) = array($to, $from);
+ }
+ $result = array();
+ $this->calculate($this->math->reduce($from->getBits()), $this->math->reduce($to->getBits()), $this->numBits, $result);
+
+ return $result;
+ }
+
+ /**
+ * Set the number of bits used to represent addresses (32 for IPv4, 128 for IPv6).
+ *
+ * @param int $numBits
+ */
+ private function setNumBits($numBits)
+ {
+ $numBits = (int) $numBits;
+ $masks = array();
+ $unmasks = array();
+ for ($bit = 0; $bit < $numBits; $bit++) {
+ $masks[$bit] = str_repeat('1', $numBits - $bit) . str_repeat('0', $bit);
+ $unmasks[$bit] = $bit === 0 ? '0' : str_repeat('1', $bit);
+ }
+ $this->numBits = $numBits;
+ $this->masks = $masks;
+ $this->unmasks = $unmasks;
+ }
+
+ /**
+ * Calculate the subnets.
+ *
+ * @param string $start the start address (represented in reduced bit form)
+ * @param string $end the end address (represented in reduced bit form)
+ * @param int $position the number of bits in the mask we are comparing at this cycle
+ * @param \IPLib\Range\Subnet[] $result found ranges will be added to this variable
+ */
+ private function calculate($start, $end, $position, array &$result)
+ {
+ if ($start === $end) {
+ $result[] = $this->subnetFromBits($start, $this->numBits);
+
+ return;
+ }
+ for ($index = $position - 1; $index >= 0; $index--) {
+ $startMasked = $this->math->andX($start, $this->masks[$index]);
+ $endMasked = $this->math->andX($end, $this->masks[$index]);
+ if ($startMasked !== $endMasked) {
+ $position = $index;
+ break;
+ }
+ }
+ if ($startMasked === $start && $this->math->andX($this->math->increment($end), $this->unmasks[$position]) === '0') {
+ $result[] = $this->subnetFromBits($start, $this->numBits - 1 - $position);
+
+ return;
+ }
+ $middleAddress = $this->math->orX($start, $this->unmasks[$position]);
+ $this->calculate($start, $middleAddress, $position, $result);
+ $this->calculate($this->math->increment($middleAddress), $end, $position, $result);
+ }
+
+ /**
+ * Create an address instance starting from its bits.
+ *
+ * @param string $bits the bits of the address (represented in reduced bit form)
+ *
+ * @return \IPLib\Address\AddressInterface
+ */
+ private function addressFromBits($bits)
+ {
+ $bits = str_pad($bits, $this->numBits, '0', STR_PAD_LEFT);
+ $bytes = array();
+ foreach (explode("\n", trim(chunk_split($bits, 8, "\n"))) as $byteBits) {
+ $bytes[] = bindec($byteBits);
+ }
+
+ return Factory::addressFromBytes($bytes);
+ }
+
+ /**
+ * Create an range instance starting from the bits if the address and the length of the network prefix.
+ *
+ * @param string $bits the bits of the address (represented in reduced bit form)
+ * @param int $networkPrefix the length of the network prefix
+ *
+ * @return \IPLib\Range\Subnet
+ */
+ private function subnetFromBits($bits, $networkPrefix)
+ {
+ $startAddress = $this->addressFromBits($bits);
+ $numOnes = $this->numBits - $networkPrefix;
+ if ($numOnes === 0) {
+ return new Subnet($startAddress, $startAddress, $networkPrefix);
+ }
+ $endAddress = $this->addressFromBits(substr($bits, 0, -$numOnes) . str_repeat('1', $numOnes));
+
+ return new Subnet($startAddress, $endAddress, $networkPrefix);
+ }
+}
diff --git a/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php b/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php
new file mode 100644
index 00000000..3179f746
--- /dev/null
+++ b/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php
@@ -0,0 +1,171 @@
+<?php
+
+namespace IPLib\Service;
+
+/**
+ * Helper class to work with unsigned integers.
+ *
+ * @internal
+ */
+class UnsignedIntegerMath
+{
+ /**
+ * Convert a string containing a decimal, octal or hexadecimal number into its bytes.
+ *
+ * @param string $value
+ * @param int $numBytes the wanted number of bytes
+ * @param bool $onlyDecimal Only parse decimal numbers
+ *
+ * @return int[]|null
+ */
+ public function getBytes($value, $numBytes, $onlyDecimal = false)
+ {
+ $m = null;
+ if ($onlyDecimal) {
+ if (preg_match('/^0*(\d+)$/', $value, $m)) {
+ return $this->getBytesFromDecimal($m[1], $numBytes);
+ }
+ } else {
+ if (preg_match('/^0[Xx]0*([0-9A-Fa-f]+)$/', $value, $m)) {
+ return $this->getBytesFromHexadecimal($m[1], $numBytes);
+ }
+ if (preg_match('/^0+([0-7]*)$/', $value, $m)) {
+ return $this->getBytesFromOctal($m[1], $numBytes);
+ }
+ if (preg_match('/^[1-9][0-9]*$/', $value)) {
+ return $this->getBytesFromDecimal($value, $numBytes);
+ }
+ }
+
+ // Not a valid number
+ return null;
+ }
+
+ /**
+ * @return int
+ */
+ protected function getMaxSignedInt()
+ {
+ return PHP_INT_MAX;
+ }
+
+ /**
+ * @param string $value never zero-length, never extra leading zeroes
+ * @param int $numBytes
+ *
+ * @return int[]|null
+ */
+ private function getBytesFromBits($value, $numBytes)
+ {
+ $valueLength = strlen($value);
+ if ($valueLength > $numBytes << 3) {
+ // overflow
+ return null;
+ }
+ $remainderBits = $valueLength % 8;
+ if ($remainderBits !== 0) {
+ $value = str_pad($value, $valueLength + 8 - $remainderBits, '0', STR_PAD_LEFT);
+ }
+ $bytes = array_map('bindec', str_split($value, 8));
+
+ return array_pad($bytes, -$numBytes, 0);
+ }
+
+ /**
+ * @param string $value may be zero-length, never extra leading zeroes
+ * @param int $numBytes
+ *
+ * @return int[]|null
+ */
+ private function getBytesFromOctal($value, $numBytes)
+ {
+ if ($value === '') {
+ return array_fill(0, $numBytes, 0);
+ }
+ $bits = implode(
+ '',
+ array_map(
+ function ($octalDigit) {
+ return str_pad(decbin(octdec($octalDigit)), 3, '0', STR_PAD_LEFT);
+ },
+ str_split($value, 1)
+ )
+ );
+ $bits = ltrim($bits, '0');
+
+ return $bits === '' ? array_fill(0, $numBytes, 0) : static::getBytesFromBits($bits, $numBytes);
+ }
+
+ /**
+ * @param string $value never zero-length, never extra leading zeroes
+ * @param int $numBytes
+ *
+ * @return int[]|null
+ */
+ private function getBytesFromDecimal($value, $numBytes)
+ {
+ $valueLength = strlen($value);
+ $maxSignedIntLength = strlen((string) $this->getMaxSignedInt());
+ if ($valueLength < $maxSignedIntLength) {
+ return $this->getBytesFromBits(decbin((int) $value), $numBytes);
+ }
+ // Divide by two, so that we have 1 less bit
+ $carry = 0;
+ $halfValue = ltrim(
+ implode(
+ '',
+ array_map(
+ function ($digit) use (&$carry) {
+ $number = $carry + (int) $digit;
+ $carry = ($number % 2) * 10;
+
+ return (string) $number >> 1;
+ },
+ str_split($value, 1)
+ )
+ ),
+ '0'
+ );
+ $halfValueBytes = $this->getBytesFromDecimal($halfValue, $numBytes);
+ if ($halfValueBytes === null) {
+ return null;
+ }
+ $carry = $carry === 0 ? 0 : 1;
+ $result = array_fill(0, $numBytes, 0);
+ for ($index = $numBytes - 1; $index >= 0; $index--) {
+ $byte = $carry + ($halfValueBytes[$index] << 1);
+ if ($byte <= 0xFF) {
+ $carry = 0;
+ } else {
+ $carry = ($byte & ~0xFF) >> 8;
+ $byte -= 0x100;
+ }
+ $result[$index] = $byte;
+ }
+ if ($carry !== 0) {
+ // Overflow
+ return null;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $value never zero-length, never extra leading zeroes
+ * @param int $numBytes
+ *
+ * @return int[]|null
+ */
+ private function getBytesFromHexadecimal($value, $numBytes)
+ {
+ $valueLength = strlen($value);
+ if ($valueLength > $numBytes << 1) {
+ // overflow
+ return null;
+ }
+ $value = str_pad($value, $valueLength + $valueLength % 2, '0', STR_PAD_LEFT);
+ $bytes = array_map('hexdec', str_split($value, 2));
+
+ return array_pad($bytes, -$numBytes, 0);
+ }
+}