aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/league/uri-interfaces/IPv4/Converter.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/league/uri-interfaces/IPv4/Converter.php')
-rw-r--r--vendor/league/uri-interfaces/IPv4/Converter.php303
1 files changed, 303 insertions, 0 deletions
diff --git a/vendor/league/uri-interfaces/IPv4/Converter.php b/vendor/league/uri-interfaces/IPv4/Converter.php
new file mode 100644
index 000000000..71c0bb9fc
--- /dev/null
+++ b/vendor/league/uri-interfaces/IPv4/Converter.php
@@ -0,0 +1,303 @@
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\IPv4;
+
+use League\Uri\Exceptions\MissingFeature;
+use League\Uri\FeatureDetection;
+use Stringable;
+
+use function array_pop;
+use function count;
+use function explode;
+use function extension_loaded;
+use function ltrim;
+use function preg_match;
+use function str_ends_with;
+use function substr;
+
+use const FILTER_FLAG_IPV4;
+use const FILTER_FLAG_IPV6;
+use const FILTER_VALIDATE_IP;
+
+final class Converter
+{
+ private const REGEXP_IPV4_HOST = '/
+ (?(DEFINE) # . is missing as it is used to separate labels
+ (?<hexadecimal>0x[[:xdigit:]]*)
+ (?<octal>0[0-7]*)
+ (?<decimal>\d+)
+ (?<ipv4_part>(?:(?&hexadecimal)|(?&octal)|(?&decimal))*)
+ )
+ ^(?:(?&ipv4_part)\.){0,3}(?&ipv4_part)\.?$
+ /x';
+ private const REGEXP_IPV4_NUMBER_PER_BASE = [
+ '/^0x(?<number>[[:xdigit:]]*)$/' => 16,
+ '/^0(?<number>[0-7]*)$/' => 8,
+ '/^(?<number>\d+)$/' => 10,
+ ];
+
+ private const IPV6_6TO4_PREFIX = '2002:';
+ private const IPV4_MAPPED_PREFIX = '::ffff:';
+
+ private readonly mixed $maxIPv4Number;
+
+ public function __construct(
+ private readonly Calculator $calculator
+ ) {
+ $this->maxIPv4Number = $calculator->sub($calculator->pow(2, 32), 1);
+ }
+
+ /**
+ * Returns an instance using a GMP calculator.
+ */
+ public static function fromGMP(): self
+ {
+ return new self(new GMPCalculator());
+ }
+
+ /**
+ * Returns an instance using a Bcmath calculator.
+ */
+ public static function fromBCMath(): self
+ {
+ return new self(new BCMathCalculator());
+ }
+
+ /**
+ * Returns an instance using a PHP native calculator (requires 64bits PHP).
+ */
+ public static function fromNative(): self
+ {
+ return new self(new NativeCalculator());
+ }
+
+ /**
+ * Returns an instance using a detected calculator depending on the PHP environment.
+ *
+ * @throws MissingFeature If no Calculator implementing object can be used on the platform
+ *
+ * @codeCoverageIgnore
+ */
+ public static function fromEnvironment(): self
+ {
+ FeatureDetection::supportsIPv4Conversion();
+
+ return match (true) {
+ extension_loaded('gmp') => self::fromGMP(),
+ extension_loaded('bcmath') => self::fromBCMath(),
+ default => self::fromNative(),
+ };
+ }
+
+ public function isIpv4(Stringable|string|null $host): bool
+ {
+ if (null === $host) {
+ return false;
+ }
+
+ if (null !== $this->toDecimal($host)) {
+ return true;
+ }
+
+ $host = (string) $host;
+ if (false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+ return false;
+ }
+
+ $ipAddress = strtolower((string) inet_ntop((string) inet_pton($host)));
+ if (str_starts_with($ipAddress, self::IPV4_MAPPED_PREFIX)) {
+ return false !== filter_var(substr($ipAddress, 7), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
+ }
+
+ if (!str_starts_with($ipAddress, self::IPV6_6TO4_PREFIX)) {
+ return false;
+ }
+
+ $hexParts = explode(':', substr($ipAddress, 5, 9));
+
+ return count($hexParts) > 1
+ && false !== long2ip((int) hexdec($hexParts[0]) * 65536 + (int) hexdec($hexParts[1]));
+ }
+
+ public function toIPv6Using6to4(Stringable|string|null $host): ?string
+ {
+ $host = $this->toDecimal($host);
+ if (null === $host) {
+ return null;
+ }
+
+ /** @var array<string> $parts */
+ $parts = array_map(
+ fn (string $part): string => sprintf('%02x', $part),
+ explode('.', $host)
+ );
+
+ return '['.self::IPV6_6TO4_PREFIX.$parts[0].$parts[1].':'.$parts[2].$parts[3].'::]';
+ }
+
+ public function toIPv6UsingMapping(Stringable|string|null $host): ?string
+ {
+ $host = $this->toDecimal($host);
+ if (null === $host) {
+ return null;
+ }
+
+ return '['.self::IPV4_MAPPED_PREFIX.$host.']';
+ }
+
+ public function toOctal(Stringable|string|null $host): ?string
+ {
+ $host = $this->toDecimal($host);
+
+ return match (null) {
+ $host => null,
+ default => implode('.', array_map(
+ fn ($value) => str_pad(decoct((int) $value), 4, '0', STR_PAD_LEFT),
+ explode('.', $host)
+ )),
+ };
+ }
+
+ public function toHexadecimal(Stringable|string|null $host): ?string
+ {
+ $host = $this->toDecimal($host);
+
+ return match (null) {
+ $host => null,
+ default => '0x'.implode('', array_map(
+ fn ($value) => dechex((int) $value),
+ explode('.', $host)
+ )),
+ };
+ }
+
+ /**
+ * Tries to convert a IPv4 hexadecimal or a IPv4 octal notation into a IPv4 dot-decimal notation if possible
+ * otherwise returns null.
+ *
+ * @see https://url.spec.whatwg.org/#concept-ipv4-parser
+ */
+ public function toDecimal(Stringable|string|null $host): ?string
+ {
+ $host = (string) $host;
+ if (str_starts_with($host, '[') && str_ends_with($host, ']')) {
+ $host = substr($host, 1, -1);
+ if (false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+ return null;
+ }
+
+ $ipAddress = strtolower((string) inet_ntop((string) inet_pton($host)));
+ if (str_starts_with($ipAddress, self::IPV4_MAPPED_PREFIX)) {
+ return substr($ipAddress, 7);
+ }
+
+ if (!str_starts_with($ipAddress, self::IPV6_6TO4_PREFIX)) {
+ return null;
+ }
+
+ $hexParts = explode(':', substr($ipAddress, 5, 9));
+
+ return (string) match (true) {
+ count($hexParts) < 2 => null,
+ default => long2ip((int) hexdec($hexParts[0]) * 65536 + (int) hexdec($hexParts[1])),
+ };
+ }
+
+ if (1 !== preg_match(self::REGEXP_IPV4_HOST, $host)) {
+ return null;
+ }
+
+ if (str_ends_with($host, '.')) {
+ $host = substr($host, 0, -1);
+ }
+
+ $numbers = [];
+ foreach (explode('.', $host) as $label) {
+ $number = $this->labelToNumber($label);
+ if (null === $number) {
+ return null;
+ }
+
+ $numbers[] = $number;
+ }
+
+ $ipv4 = array_pop($numbers);
+ $max = $this->calculator->pow(256, 6 - count($numbers));
+ if ($this->calculator->compare($ipv4, $max) > 0) {
+ return null;
+ }
+
+ foreach ($numbers as $offset => $number) {
+ if ($this->calculator->compare($number, 255) > 0) {
+ return null;
+ }
+
+ $ipv4 = $this->calculator->add($ipv4, $this->calculator->multiply(
+ $number,
+ $this->calculator->pow(256, 3 - $offset)
+ ));
+ }
+
+ return $this->long2Ip($ipv4);
+ }
+
+ /**
+ * Converts a domain label into a IPv4 integer part.
+ *
+ * @see https://url.spec.whatwg.org/#ipv4-number-parser
+ *
+ * @return mixed returns null if it cannot correctly convert the label
+ */
+ private function labelToNumber(string $label): mixed
+ {
+ foreach (self::REGEXP_IPV4_NUMBER_PER_BASE as $regexp => $base) {
+ if (1 !== preg_match($regexp, $label, $matches)) {
+ continue;
+ }
+
+ $number = ltrim($matches['number'], '0');
+ if ('' === $number) {
+ return 0;
+ }
+
+ $number = $this->calculator->baseConvert($number, $base);
+ if (0 <= $this->calculator->compare($number, 0) && 0 >= $this->calculator->compare($number, $this->maxIPv4Number)) {
+ return $number;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Generates the dot-decimal notation for IPv4.
+ *
+ * @see https://url.spec.whatwg.org/#concept-ipv4-parser
+ *
+ * @param mixed $ipAddress the number representation of the IPV4address
+ */
+ private function long2Ip(mixed $ipAddress): string
+ {
+ $output = '';
+ for ($offset = 0; $offset < 4; $offset++) {
+ $output = $this->calculator->mod($ipAddress, 256).$output;
+ if ($offset < 3) {
+ $output = '.'.$output;
+ }
+ $ipAddress = $this->calculator->div($ipAddress, 256);
+ }
+
+ return $output;
+ }
+}