diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 7d7471e..66c19bf 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -5,29 +5,39 @@ on: - push jobs: - coding-standards: + continuous-integration-php-82: name: "Coding Standards" - runs-on: ubuntu-latest - steps: - name: "Checkout" uses: actions/checkout@master - - - name: "Install Php 8.3" + - name: "Install Php 8.2" uses: shivammathur/setup-php@v2 with: php-version: '8.2' tools: composer:v2 extensions: openssl, json, pdo, pdo_mysql, fileinfo, curl - - name: "Validate composer.json" run: php $(which composer) validate --strict - - name: "Install dependencies with composer" run: php $(which composer) install --no-interaction --no-progress --no-suggest - - name: "Run PHP CodeSniffer" run: php vendor/bin/phpcs --standard=phpcs.xml - - + continuous-integration-php-83: + name: "Coding Standards" + runs-on: ubuntu-latest + steps: + - name: "Checkout" + uses: actions/checkout@master + - name: "Install Php 8.3" + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + tools: composer:v2 + extensions: openssl, json, pdo, pdo_mysql, fileinfo, curl + - name: "Validate composer.json" + run: php $(which composer) validate --strict + - name: "Install dependencies with composer" + run: php $(which composer) install --no-interaction --no-progress --no-suggest + - name: "Run PHP CodeSniffer" + run: php vendor/bin/phpcs --standard=phpcs.xml diff --git a/composer.json b/composer.json index 597099d..6d4df02 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,7 @@ }, "require-dev": { "squizlabs/php_codesniffer": "3.7.2", - "slevomat/coding-standard": "^8.13" + "slevomat/coding-standard": "^8.14" }, "scripts": { "post-create-project-cmd": [ diff --git a/src/Auth/Roles/Interfaces/IdentityInterface.php b/src/Auth/Roles/Interfaces/IdentityInterface.php index a544cd9..56215e9 100644 --- a/src/Auth/Roles/Interfaces/IdentityInterface.php +++ b/src/Auth/Roles/Interfaces/IdentityInterface.php @@ -5,5 +5,9 @@ interface IdentityInterface { - public function getId() : int; + /** + * @return int + * @noinspection PhpMissingReturnTypeInspection + */ + public function getId(); } diff --git a/src/Console/Command/BuiltInWebServer.php b/src/Console/Command/BuiltInWebServer.php index b84980b..536d9f7 100644 --- a/src/Console/Command/BuiltInWebServer.php +++ b/src/Console/Command/BuiltInWebServer.php @@ -24,7 +24,6 @@ use function chdir; use function defined; use function dirname; -use function error_clear_last; use function escapeshellcmd; use function exec; use function fclose; @@ -248,7 +247,7 @@ protected function execute(InputInterface $input, OutputInterface $output) : int $host = $input->getOption('host'); $host = strtolower(trim($host??'')?:'127.0.0.1'); $isIp = Ip::isValidIpv4($host); - $isLocal = $isIp && Ip::isLocalIP($host); + $isLocal = $isIp && Ip::isLocalIP4($host); if (!$isIp && $host !== 'localhost') { $output->writeln(''); $output->writeln(sprintf( @@ -397,7 +396,7 @@ protected function doProcess( ); return self::FAILURE; } - set_error_handler(fn () => error_clear_last()); + set_error_handler(static fn () => null); $ports = $port === 'auto' ? range(8000, 9000) : [$port]; shuffle($ports); array_unshift( diff --git a/src/Console/Command/ChecksumGenerator.php b/src/Console/Command/ChecksumGenerator.php index 26cf881..cf61c0d 100644 --- a/src/Console/Command/ChecksumGenerator.php +++ b/src/Console/Command/ChecksumGenerator.php @@ -24,7 +24,6 @@ use function clearstatcache; use function date; use function dirname; -use function error_clear_last; use function fclose; use function filemtime; use function fopen; @@ -166,7 +165,7 @@ static function ($e) { } - set_error_handler(static fn() => error_clear_last()); + set_error_handler(static fn() => null); try { if (!$printOnly) { if (!is_dir($checksumDirectory)) { diff --git a/src/Container/ContainerResolver.php b/src/Container/ContainerResolver.php index 638785d..a76527a 100644 --- a/src/Container/ContainerResolver.php +++ b/src/Container/ContainerResolver.php @@ -115,16 +115,16 @@ public function resolveCallable( } } - if (empty($arguments) && is_callable($callable)) { + if (is_callable($callable)) { $arguments = $this ->resolveArguments( (is_string($callable) || $callable instanceof Closure) ? new ReflectionFunction($callable) : new ReflectionMethod(...$callable), + $arguments, $arguments ); } - $value = is_callable($callable) ? $callable(...$arguments) : $callable; } $this->allocateService($value); diff --git a/src/Container/Factory/ContainerFactory.php b/src/Container/Factory/ContainerFactory.php index eb3b3aa..14dc6ea 100644 --- a/src/Container/Factory/ContainerFactory.php +++ b/src/Container/Factory/ContainerFactory.php @@ -18,7 +18,7 @@ use ArrayAccess\TrayDigita\Benchmark\Interfaces\ProfilerInterface; use ArrayAccess\TrayDigita\Benchmark\Profiler; use ArrayAccess\TrayDigita\Benchmark\Waterfall; -use ArrayAccess\TrayDigita\Cache\Entities; +use ArrayAccess\TrayDigita\Cache\Cache; use ArrayAccess\TrayDigita\Collection\Config; use ArrayAccess\TrayDigita\Console\Application; use ArrayAccess\TrayDigita\Container\Container; diff --git a/src/Event/Interfaces/ManagerInterface.php b/src/Event/Interfaces/ManagerInterface.php index e9197da..20550c8 100644 --- a/src/Event/Interfaces/ManagerInterface.php +++ b/src/Event/Interfaces/ManagerInterface.php @@ -23,6 +23,23 @@ public function getDispatchListener(): ?EventDispatchListenerInterface; */ public function attach(string $eventName, $eventCallback, int $priority = 10) : string; + /** + * Attach the event & then remove after dispatched + * + * @param string $eventName + * @param $eventCallback + * @param int $priority + * @return string + */ + public function attachOnce(string $eventName, $eventCallback, int $priority = 10) : string; + + /** + * Check if has events + * + * @param string $eventName + * @param $eventCallback + * @return bool + */ public function has(string $eventName, $eventCallback = null) : bool; /** diff --git a/src/Event/Manager.php b/src/Event/Manager.php index 4c3fd9b..dfd4845 100644 --- a/src/Event/Manager.php +++ b/src/Event/Manager.php @@ -34,6 +34,11 @@ class Manager implements ManagerInterface protected array $events = []; + /** + * @var array>> + */ + private array $eventOnce = []; + protected array $currents = []; /** @@ -159,8 +164,11 @@ public static function generateCallableId($eventCallback): array|null ]; } - public function attach(string $eventName, $eventCallback, int $priority = 10): string - { + public function attach( + string $eventName, + $eventCallback, + int $priority = 10 + ): string { $callable = $this->generateCallableId($eventCallback); if ($callable === null) { throw new InvalidCallbackException( @@ -174,6 +182,14 @@ public function attach(string $eventName, $eventCallback, int $priority = 10): s return $id; } + public function attachOnce(string $eventName, $eventCallback, int $priority = 10): string + { + $id = $this->attach(...func_get_args()); + $this->eventOnce[$eventName][$priority][$id] ??= 0; + $this->eventOnce[$eventName][$priority][$id]++; + return $id; + } + public function has( string $eventName, $eventCallback = null @@ -422,6 +438,7 @@ public function dispatch( } $this->currents[$eventName] ??= []; + // sorting ksort($this->events[$eventName]); // make temporary to prevent remove $originalParam = $param; @@ -433,21 +450,12 @@ public function dispatch( } foreach ($callableList as $id => $eventCallback) { unset($eventCallback[$id]); - - // prevent loops to call dispatch same - //if (isset($this->currents[$eventName][$priority][$id])) { - // continue; - //} - if (!isset($this->records[$eventName][$id][$priority])) { $this->records[$eventName][$id][$priority] = 0; } foreach ($eventCallback as $inc => $callable) { // prevent loops to call dispatch same increment - //if (isset($this->currents[$eventName][$priority][$id][$inc])) { - // continue; - //} if (!isset($this->currents[$eventName][$priority][$id][$inc])) { $this->currents[$eventName][$priority][$id][$inc] = 0; } @@ -482,7 +490,6 @@ public function dispatch( && is_object($callable[0]) && is_string($callable[1] ?? null) && str_contains($callable[1], '::') - // && !is_callable($callable) ) { $obj = $callable[0]; $methods = explode('::', $callable[1], 2); @@ -509,7 +516,23 @@ public function dispatch( $this->currentEvent = null; $this->currentEventId = null; $this->currentEventName = null; + // unset unset($this->currents[$eventName][$priority][$id][$inc]); + if ($this->currents[$eventName][$priority][$id] === []) { + unset($this->currents[$eventName][$priority][$id]); + } + if (isset($this->eventOnce[$eventName][$priority][$id])) { + $this->eventOnce[$eventName][$priority][$id]--; + if ($this->eventOnce[$eventName][$priority][$id] <= 0) { + unset($this->eventOnce[$eventName][$priority][$id]); + } + if ([] === $this->eventOnce[$eventName][$priority]) { + unset($this->eventOnce[$eventName][$priority]); + } + if ([] === $this->eventOnce[$eventName]) { + unset($this->eventOnce[$eventName]); + } + } // @onDispatched $this->dispatchListener?->onFinishDispatch( diff --git a/src/HttpKernel/Helper/AbstractHelper.php b/src/HttpKernel/Helper/AbstractHelper.php index 05cd58e..b1dc47a 100644 --- a/src/HttpKernel/Helper/AbstractHelper.php +++ b/src/HttpKernel/Helper/AbstractHelper.php @@ -3,6 +3,7 @@ namespace ArrayAccess\TrayDigita\HttpKernel\Helper; +use ArrayAccess\TrayDigita\Cache\Cache; use ArrayAccess\TrayDigita\Cache\Entities; use ArrayAccess\TrayDigita\Container\Interfaces\ContainerAllocatorInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerAllocatorInterface; diff --git a/src/Responder/FileResponder.php b/src/Responder/FileResponder.php index b5f9590..629e8b6 100644 --- a/src/Responder/FileResponder.php +++ b/src/Responder/FileResponder.php @@ -95,7 +95,7 @@ public function __construct(SplFileInfo|string $file) } $this->file = $file; $this->attachmentFileName = $this->file->getBasename(); - $this->size = $this->valid() ? $this->file->getSize() : 0; + $this->size = $this->valid() ? ($this->file->getSize()?:0) : 0; } public function getFile(): SplFileInfo @@ -409,9 +409,22 @@ private function sendRequestData(ServerRequestInterface $request) : never $ranges = []; // header for multi-bytes $headers = []; - $total = 0; + $total = $fileSize; $rangeTotal = 0; - + // if empty + if ($total === 0) { + // set content length + $this->sendHeaderContentLength($total); + // send mimetype header + $this->sendHeaderMimeType(); + // send etag + $this->sendHeaderEtag(); + // send last modifier + $this->sendHeaderLastModified(); + // send attachment header + $this->sendHeaderAttachment(); + $this->stopRequest(); + } /** * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests */ @@ -425,6 +438,7 @@ private function sendRequestData(ServerRequestInterface $request) : never $maxRangeRequest = $maxRange; $minRangeRequest = null; if ($maxRanges > 0 && $rangeHeader && preg_match('~^bytes=(.+)$~i', $rangeHeader, $match)) { + $total = 0; $rangeHeader = array_map('trim', explode(',', trim($match[1]))); foreach ($rangeHeader as $range) { $range = trim($range); @@ -524,6 +538,7 @@ private function sendRequestData(ServerRequestInterface $request) : never $this->sendHeader('Content-Range', "bytes $startingPoint-$end/$fileSize"); } } + // set content length $this->sendHeaderContentLength($total); // send mimetype header @@ -543,12 +558,16 @@ private function sendRequestData(ServerRequestInterface $request) : never $sock = $this->getSock(); $sock->fseek($startingPoint); - while (!$sock->eof()) { + while (!$sock->eof() && $total > 0) { $read = 4096; if ($total < $read) { $read = $total; $total = 0; } + // stop + if ($read < 1) { + break; + } echo $sock->fread($read); } $this->stopRequest(); diff --git a/src/Routing/Router.php b/src/Routing/Router.php index 92c336c..dd5fb3d 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -763,17 +763,13 @@ public function matchRouteParamByRequest(Route $route, ServerRequestInterface $r $pattern = "#$pattern#"; } - set_error_handler(static function () { - error_clear_last(); - }); - + set_error_handler(static fn () => null); preg_match( $pattern, $path, $match, PREG_NO_ERROR ); - // restore restore_error_handler(); if (empty($match)) { diff --git a/src/Util/Filter/Consolidation.php b/src/Util/Filter/Consolidation.php index 5f48486..c98772c 100644 --- a/src/Util/Filter/Consolidation.php +++ b/src/Util/Filter/Consolidation.php @@ -410,6 +410,22 @@ public static function callbackReduceError( return $result; } + /** + * Call the callble with hide the error + * + * @param callable $callback + * @param ...$args + */ + public static function callNoError(callable $callback, ...$args) + { + set_error_handler(static fn () => null); + try { + return $callback(...$args); + } finally { + restore_error_handler(); + } + } + public static function namespace(string|object $fullClassName) : string|false { if (is_object($fullClassName)) { diff --git a/src/Util/Filter/ContainerHelper.php b/src/Util/Filter/ContainerHelper.php index 24c5222..620e2c6 100644 --- a/src/Util/Filter/ContainerHelper.php +++ b/src/Util/Filter/ContainerHelper.php @@ -81,7 +81,6 @@ public static function service( )??Decorator::service($expect); } - /** * Get object instance or create if not available until interfaces * beware using this method, it will check interface tree. diff --git a/src/Util/Filter/DataNormalizer.php b/src/Util/Filter/DataNormalizer.php index 9f05eb4..35332f7 100644 --- a/src/Util/Filter/DataNormalizer.php +++ b/src/Util/Filter/DataNormalizer.php @@ -3,6 +3,7 @@ namespace ArrayAccess\TrayDigita\Util\Filter; +use ArrayAccess\TrayDigita\Exceptions\Runtime\MaximumCallstackExceeded; use function array_keys; use function array_merge_recursive; use function array_pop; @@ -12,6 +13,7 @@ use function clearstatcache; use function explode; use function file_exists; +use function idn_to_ascii; use function implode; use function in_array; use function is_dir; @@ -19,7 +21,6 @@ use function is_string; use function iterator_to_array; use function mt_rand; -use function parse_url; use function preg_match; use function preg_quote; use function preg_replace; @@ -27,6 +28,7 @@ use function realpath; use function rtrim; use function sprintf; +use function str_contains; use function str_replace; use function strlen; use function strpos; @@ -220,9 +222,9 @@ public static function removeJSContent(string $data): string * @version 1.1 * * Modified by Scott Reilly (coffee2code) 02 Aug 2004 - * 1.1 Fixed handling of append/stack pop order of end text - * Added Cleaning Hooks - * 1.0 First Version + * 1.1 Fixed handling append/stack pop order of end text + * Added Cleaning Hooks + * 1.0 First Version * * @author Leonard Lin * @license GPL @@ -333,7 +335,8 @@ public static function forceBalanceTags(string $text): string } else { // Else it's not a single-entity tag // --------- - // If the top of the stack is the same as the tag we want to push, close previous tag + // If the top of the stack is the same as the tag we want to push, + // close the previous tag if ($stackSize > 0 && !in_array($tag, $nestable_tags) && $tagStack[$stackSize - 1] === $tag ) { @@ -383,33 +386,36 @@ public static function forceBalanceTags(string $text): string public static function splitCrossDomain(string $domain): ?string { // make it domain lower - $domain = strtolower($domain); - $domain = preg_replace('~^\s*(?:(http|ftp)s?|sftp|xmp)://~i', '', $domain); - $domain = preg_replace('~/.*$~', '', $domain); - - if ($domain === '127.0.0.1' || $domain === 'localhost') { - return $domain; + $domain = strtolower(trim($domain)); + if ($domain === '') { + return null; } - if (Ip::version($domain) !== false) { - return $domain; + preg_match( + '~^(?:(?:[a-z]*:)?//)?([^?#/]+)(?:[#?/]|$)~', + $domain, + $match + ); + $host = $match[1]??null; + if (!$host) { + return null; } - $parse = parse_url('https://' . $domain . '/'); - $domain = $parse['host'] ?? null; - if ($domain === null) { + if ($host === '127.0.0.1' || $host === 'localhost') { + return $host; + } + if (Ip::version($host) !== null) { + return $host; + } + // ascii domain + if (idn_to_ascii($host) === false) { return null; } - - $domain = preg_replace('~[\~!@#$%^&*()+`{}\]\[/\';<>,\"?=|\\\]~', '', $domain); - if (str_contains($domain, '.')) { - if (preg_match('~(.*\.)+(.*\.)+(.*)~', $domain)) { - $return = '.' . preg_replace('~(.*\.)+(.*\.)+(.*)~', '$2$3', $domain); - } else { - $return = '.' . $domain; - } - } else { - $return = $domain; + if (!str_contains($host, '.')) { + return $host; + } + if (preg_match('~^[^.]+\.([^.]+\..+)$~', $host, $match)) { + $host = $match[2].$match[3]; } - return $return; + return '.' . $host; } /** @@ -452,14 +458,25 @@ public static function uniqueSlug(string $slug, array $slugCollections): string * @param callable $callable must be returning true for valid * @return string */ - public static function uniqueSlugCallback(string $slug, callable $callable): string - { + public static function uniqueSlugCallback( + string $slug, + callable $callable, + int $maxIteration = 4096 + ): string { + $maxIteration = min(128, $maxIteration); + // maximum set to 8192 + $maxIteration = max($maxIteration, 8192); $separator = '-'; $inc = 1; $slug = self::normalizeSlug($slug); $baseSlug = $slug; while (!$callable($slug)) { - $slug = $baseSlug . $separator . $inc++; + if ($inc++ > $maxIteration) { + throw new MaximumCallstackExceeded( + 'Unique slug iteration exceeded' + ); + } + $slug = $baseSlug . $separator . $inc; } return $slug; @@ -486,7 +503,7 @@ public static function normalizeUnixDirectorySeparator( * @param string $directory * @param bool $allowedSpace * @return ?string null if directory does not exist, - * returning full path for save. + * returning a full path for save. */ public static function resolveFileDuplication( string $fileName, @@ -497,8 +514,8 @@ public static function resolveFileDuplication( return null; } - $directory = self::normalizeDirectorySeparator($directory); - $directory = realpath($directory) ?: rtrim($directory, DIRECTORY_SEPARATOR); + $directory = self::normalizeDirectorySeparator($directory, true); + $directory = realpath($directory) ?: $directory; $directory .= DIRECTORY_SEPARATOR; $paths = explode('.', $fileName); $extension = null; @@ -514,7 +531,8 @@ public static function resolveFileDuplication( $c = 1; $filePath = $extension ? "$fileName.$extension" : $fileName; while (file_exists($directory . $filePath)) { - $newFile = "$fileName-" . $c++; + $c++; + $newFile = "$fileName-$c"; $filePath = $extension ? "$newFile.$extension" : $newFile; } diff --git a/src/Util/Filter/DataType.php b/src/Util/Filter/DataType.php index 0b8610b..bd70af1 100644 --- a/src/Util/Filter/DataType.php +++ b/src/Util/Filter/DataType.php @@ -5,7 +5,6 @@ use Psr\Http\Message\ResponseInterface; use Throwable; -use function error_clear_last; use function is_array; use function is_int; use function is_numeric; @@ -76,9 +75,7 @@ public static function isHtmlContentType( */ public static function isValidRegExP(string $regexP): bool { - set_error_handler(static function () { - error_clear_last(); - }); + set_error_handler(static fn () => null); $result = preg_match($regexP, '', flags: PREG_NO_ERROR) !== false; restore_error_handler(); diff --git a/src/Util/Filter/Ip.php b/src/Util/Filter/Ip.php index 6ccbdc9..158c6f5 100644 --- a/src/Util/Filter/Ip.php +++ b/src/Util/Filter/Ip.php @@ -3,22 +3,33 @@ namespace ArrayAccess\TrayDigita\Util\Filter; +use function bin2hex; use function bindec; +use function dechex; use function explode; +use function hex2bin; use function hexdec; use function implode; +use function inet_ntop; +use function inet_pton; use function ip2long; use function is_numeric; use function is_string; use function long2ip; use function ltrim; +use function min; +use function pack; use function pow; use function preg_match; +use function reset; +use function str_contains; use function str_replace; +use function str_split; use function strlen; use function strrpos; use function substr; use function substr_count; +use function substr_replace; use function trim; class Ip @@ -28,23 +39,17 @@ class Ip const IPV4_LOCAL_REGEX = '~^ (?: - (?: - 1?0 | # start with 0. or 10. - 127 # start with 127. - )\.(?:0|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?|1[0-9]{0,2}|[1-9][0-9]?) # next 0 to 255 + (?:0?[01]?0|127|255)\.(?:[01]?[0-9]{1,2}|2[0-4][0-9]|25[0-5]) | 192\.168 | 172\.16 ) - # next 0. to 255. twice - (?: - \.(?:0|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?|1[0-9]{0,2}|[1-9][0-9]?) - ){2} + (?:\.(?:[01]?[0-9]{1,2}|2[0-4][0-9]|25[0-5])){2} $~x'; - public static function filterIpv4(string $ip): false|string + public static function filterIpv4(string $ip): ?string { if (preg_match('/^([01]{8}\.){3}[01]{8}\z/i', $ip)) { - // binary format 00000000.00000000.00000000.00000000 + // binary format 00000000.00000000.00000000.00000000 $ip = bindec(substr($ip, 0, 8)) . '.' . bindec(substr($ip, 9, 8)) @@ -61,12 +66,11 @@ public static function filterIpv4(string $ip): false|string $ip = hexdec(substr($ip, 0, 2)) . '.' . hexdec(substr($ip, 3, 2)) . '.' . hexdec(substr($ip, 6, 2)) . '.' . hexdec(substr($ip, 9, 2)); } - $ip2long = ip2long($ip); - if ($ip2long === false) { - return false; + if (($ip2long = ip2long($ip)) === false) { + return null; } - return $ip === long2ip($ip2long) ? $ip : false; + return $ip === long2ip($ip2long) ? $ip : null; } /** @@ -80,7 +84,7 @@ public static function isValidIpv4(string $ip): bool return self::filterIpv4($ip) !== false; } - public static function isLocalIP(string $ip): bool + public static function isLocalIP4(string $ip): bool { $ip = self::filterIpv4($ip); return $ip && preg_match(self::IPV4_LOCAL_REGEX, $ip); @@ -133,68 +137,114 @@ public static function isValidIpv6(string $value): bool */ public static function ipv4CIDRToRange(string $cidr): ?array { - $cidr = str_replace(' ', '', $cidr); - $cidr = explode('/', $cidr); - if (count($cidr) !== 2) { + if (count(($cidr = explode('/', $cidr))) !== 2) { return null; } - - $ip = $cidr[0]; - $range = $cidr[1]; - if (!is_numeric($range) - || strlen($range) > 2 + if (($ip = trim($cidr[0])) === '' + || ($range = trim($cidr[1])) === '' || str_contains($range, '.') - || self::isValidIpv4($ip) + || !is_numeric($range) + || $range > 32 + || $range < 0 + || !self::isValidIpv4($ip) ) { return null; } - $range = (int) $range; - if ($range < 1 || $range > 32) { - return null; - } $ips = explode('.', $ip); if (count($ips) !== 4) { return null; } - $ip_temp = []; foreach ($ips as $ip_address) { - $ip_address = trim($ip_address); if ($ip_address === '') { return null; } - $ip_address = ltrim($ip_address, '0'); - if ($ip_address === '') { - $ip_address = '0'; - } - if (!is_numeric($ip_address) - || str_contains('.', $ip_address) + if (str_contains('.', $ip_address) + || ! is_numeric($ip_address) + || $ip_address > 255 + || $ip_address < 0 ) { return null; } - $ip_address = (int) $ip_address; - if ($ip_address < 0 || $ip_address > 255) { - return null; - } - $ip_temp[] = $ip_address; } - $ip = implode('.', $ip_temp); + $range = (int) $range; return [ long2ip((ip2long($ip)) & ((-1 << (32 - $range)))), long2ip((ip2long($ip)) + pow(2, (32 - $range)) - 1) ]; } + /** + * Convert ipv6 cidr to range + * + * @param string $cidr 2001:100::/24 + * @return ?array{0: string, 1: string} start & end ip address + */ + public static function ipv6CIDRToRange(string $cidr) : ?array + { + if (count(($cidr = explode('/', trim($cidr)))) !== 2) { + return null; + } + if (($ip= trim($cidr[0])) === '' + || ($range = trim($cidr[1])) === '' + || str_contains($range, '.') + || !is_numeric($range) + || $range < 0 + || $range > 128 + || !self::isValidIpv6($ip) + ) { + return null; + } + + $firstAddrBin = inet_pton($ip); + // fail return null + if ($firstAddrBin === false + || !($firstAddr = inet_ntop($firstAddrBin)) + ) { + return null; + } + $flexBits = 128 - ((int) $range); + // Build the hexadecimal string of the last address + $lastAddrHex = bin2hex($firstAddrBin); + // start at the end of the string (which is always 32 characters long) + $pos = 31; + while ($flexBits > 0) { + // Get the character at this position + $orig = substr($lastAddrHex, $pos, 1); + // Convert it to an integer + $originalVal = hexdec($orig); + // OR it with (2^flexBits)-1, with flexBits limited to 4 at a time + $newVal = $originalVal | (pow(2, min(4, $flexBits)) - 1); + // Convert it back to a hexadecimal character + $new = dechex($newVal); + // And put that character back in the string + $lastAddrHex = substr_replace($lastAddrHex, $new, $pos, 1); + // process one nibble, move to previous position + $flexBits -= 4; + $pos -= 1; + } + $lastAddrBin = inet_pton($lastAddrHex); + if (!$lastAddrBin) { + return null; + } + $lastAddr = inet_ntop($lastAddrBin); + return !$lastAddr ? null : [$firstAddr, $lastAddr]; + } + /** * @param string|mixed $ip * - * @return int|false + * @return ?int */ - public static function version(mixed $ip) : int|false + public static function version(mixed $ip) : ?int { - return !is_string($ip) ? false : ( - self::isValidIpv4($ip) + if (!is_string($ip)) { + return null; + } + if (str_contains($ip, ':')) { + return self::isValidIpv6($ip) ? self::IP6 : null; + } + return str_contains($ip, '.') && self::isValidIpv4($ip) ? self::IP4 - : (self::isValidIpv6($ip) ? self::IP6 : false) - ); + : null; } } diff --git a/src/Util/Network/Dns.php b/src/Util/Network/Dns.php deleted file mode 100644 index 8ab9d31..0000000 --- a/src/Util/Network/Dns.php +++ /dev/null @@ -1,503 +0,0 @@ - "A", // RFC1035 - 2 => "NS", // RFC1035 - 5 => "CNAME", // RFC1035 - 6 => "SOA", // RFC1035 RFC2308 - 12 => "PTR", // RFC1035 - 13 => "HINFO", - 14 => "MINFO", - 15 => "MX", // RFC1035 RFC7505 - 16 => "TXT", // RFC1035 - 17 => "RP", // RFC1183 - 18 => "AFSDB", // RFC1183 RFC5864 - 19 => "X25", // RFC1183 - 20 => "ISDN", // RFC1183 - 21 => "RT", // RFC1183 - 22 => "NSAP", // RFC1706 - 23 => "NSAP-PTR", // RFC1348 RFC1637 RFC1706 - 24 => "SIG", // RFC4034 RFC3755 RFC2535 RFC2536 RFC2537 RFC3008 RFC3110 - 25 => "KEY", // RFC2930 RFC4034 RFC2535 RFC2536 RFC2537 RFC3008 RFC3110 - 26 => "PX", // RFC2136 - 27 => "GPOS", // RFC1712 - 28 => "AAAA", // RFC3596 - 29 => "LOC", // RFC1876 - 31 => "EID", - 32 => "NIMLOC", - 33 => "SRV", // RFC2782 - 34 => "ATMA", - 35 => "NAPTR", // RFC3403 - 36 => "KX", // RFC2230 - 37 => "CERT", // RFC4398 - 39 => "DNAME", // RFC2672 - 40 => "SINK", - 41 => "OPT", // RFC6891 RFC3658 - 42 => "APL", - 43 => "DS", // RFC4034 RFC3658 - 44 => "SSHFP", // RFC4255 - 45 => "IPSECKEY", // RFC4025 - 46 => "RRSIG", // RFC4034 RFC3755 - 47 => "NSEC", // RFC4034 RFC3755 - 48 => "DNSKEY", // RFC4034 RFC3755 - 49 => "DHCID", // RFC4701 - 50 => "NSEC3", // RFC5155 - 51 => "NSEC3PARAM", // RFC5155 - 52 => "TLSA", // RFC6698 - 55 => "HIP", // RFC5205 - 56 => "NINFO", - 57 => "RKEY", - 58 => "TALINK", - 59 => "CDS", // RFC7344 - 60 => "CDNSKEY", // RFC7344 - 61 => "OPENPGPKEY", // internet draft - 62 => "CSYNC", // RFC7477 - 99 => "SPF", // RFC4408 RFC7208 - 100 => "UNIFO", // IANA Reserved - 101 => "UID", // IANA Reserved - 102 => "GID", // IANA Reserved - 103 => "UNSPEC", // IANA Reserved - 104 => "NID", // RFC6742 - 105 => "L32", // RFC6742 - 106 => "L64", // RFC6742 - 107 => "LP", // RFC6742 - 108 => "EUI48", // RFC7043 - 109 => "EUI64", // RFC7043 - 249 => "TKEY", // RFC2930 - 250 => "TSIG", // RFC2845 - 251 => "IXFR", // RFC1995 - 252 => "AXFR", // RFC1035 RFC5936 - 253 => "MAILB", // RFC1035 - 254 => "MAILA", // RFC1035 - 255 => "ANY", // RFC1035 RFC6895 - 256 => "URI", // RFC7553 - 257 => "CAA", // RFC6844 - 32768 => "TA", - 32769 => "DLV", - 65534 => "TYPE65534", // Eurid uses this one? - ]; - - final const DEFAULT_NAMESERVER = [ - '1.1.1.1', - '8.8.8.8', - '8.8.4.4', - '1.1.0.0' - ]; - - public static function record( - string $type, - string $host, - string|array|null $server = null, - int $timeout = 5, - &$is_authoritative = null - ): array { - $type = strtoupper(trim($type)); - if (!($idType = array_search($type, self::TYPE))) { - throw new InvalidArgumentException( - sprintf('Dns type %s is not valid', $type) - ); - } - - $parsed = parse_url(trim($host)); - $host = $parsed['host']??$parsed['path']??null; - if (is_string($host) && !isset($parsed['scheme'])) { - $host = str_replace('\\', '/', $host); - $host = trim($host, '/'); - $host = explode('/', $host, 2); - $host = array_shift($host); - } - if (!$host) { - throw new EmptyArgumentException( - 'Hostname is invalid' - ); - } - - $socket = self::createSocket($server, $timeout, $useServer); - $labels = explode('.', $host); - $question_binary = ''; - foreach ($labels as $label) { - $question_binary .= pack("C", strlen($label)); // size byte first - $question_binary .= $label; // add label - } - $question_binary .= pack("C", 0); // end it off - $id = rand(1, 255)|(rand(0, 255)<<8); // generate the ID - // Set standard codes and flags - $flags = (0x0100 & 0x0300) | 0x0020; // recursion & queryspecmask | authenticated data - - $opcode = 0x0000; // opcode - // Build the header - $domainLabel = pack("n", $id); - $domainLabel .= pack("n", $opcode | $flags); - $domainLabel .= pack("nnnn", 1, 0, 0, 0); - $domainLabel .= $question_binary; - $domainLabel .= pack("n", $idType); - $domainLabel .= pack("n", 0x0001); // internet class - $headerSize = strlen($domainLabel); - // $headersizebin = pack("n", $headerSize); - $written = fwrite($socket, $domainLabel, $headerSize); - if ($written === false) { - fclose($socket); - throw new RuntimeException( - "Could not communicate DNS query" - ); - } - $rawBuffer = fread($socket, 4096); - fclose($socket); - if (strlen($rawBuffer) < 12) { - throw new OutOfRangeException( - "DNS query return buffer too small" - ); - } - $pos = 0; - $domainLabel = unpack( - "nid/nflags/nqdcount/nancount/nnscount/narcount", - self::readBufferLength(12, $rawBuffer, $pos) - ); - if (!isset($domainLabel['flags'])) { - throw new RuntimeException( - "Could read dns flags" - ); - } - $flags = sprintf("%016b\n", $domainLabel['flags']); - // No answers - if (!$domainLabel['ancount']) { - return []; - } - - $is_authoritative = $flags[5] === 1; - - // Question section - if ($domainLabel['qdcount']) { - // Skip name - self::readName($rawBuffer, $pos); - // skip question part - $pos += 4; // 4 => QTYPE + QCLASS - } - - $responses = []; - - for ($a = 0; $a < $domainLabel['ancount']; $a++) { - $host = self::readName($rawBuffer, $pos); // Skip name - $ans_header = unpack("ntype/nclass/Nttl/nlength", self::readBufferLength(10, $rawBuffer, $pos)); - $ans_header = !is_array($ans_header) ? [] : $ans_header; - $resType = self::TYPE[$ans_header['type']] ?? null; - if ($type !== 'ANY' && $resType !== $type) { - // Skip type that was not requested - $resType = null; - } - // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml - $ans = match ($ans_header['class']) { - 0x0000, - 0xFFFF => 'RESERVED', - 0x0001 => 'IN', - 0x0002 => 'UNASSIGNED', - 0x0003 => 'CH', - 0x0004 => 'HS', - 0x00FE => 'NONE', - 0x00FF => 'ANY', - default => null - }; - if ($ans === null) { - $ans = 'UNKNOWN'; - if ($ans_header['class'] >= 0x0005 - && $ans_header['class'] <= 0x00FD - ) { - $ans = 'UNASSIGNED'; - } elseif ($ans_header['class'] >= 0xFF00 - && $ans_header['class'] <= 0xFFFE - ) { - $ans = 'RESERVED'; - } - } - switch ($resType) { - case 'A': - $responses[$resType][] = [ - 'host' => $host, - 'ttl' => $ans_header['ttl'], - 'class' => $ans, - 'length' => $ans_header['length'], - 'ip' => implode( - ".", - unpack( - "Ca/Cb/Cc/Cd", - self::readBufferLength(4, $rawBuffer, $pos) - ) - ) - ]; - break; - case 'AAAA': - $responses[$resType][] = [ - 'host' => $host, - 'ttl' => $ans_header['ttl'], - 'class' => $ans, - 'length' => $ans_header['length'], - 'ip' => implode( - ':', - unpack( - "H4a/H4b/H4c/H4d/H4e/H4f/H4g/H4h", - self::readBufferLength(16, $rawBuffer, $pos) - ) - ) - ]; - break; - case 'MX': - $priority = unpack('nprio', self::readBufferLength(2, $rawBuffer, $pos)); // priority - $responses[$resType][] = [ - 'host' => self::readName($rawBuffer, $pos), - 'type' => $type, - 'ttl' => $ans_header['ttl'], - 'class' => $ans, - 'length' => $ans_header['length'], - 'priority' => $priority['prio'], - ]; - break; - case 'NS': - case 'CNAME': - case 'PTR': - $responses[$resType][] = [ - 'host' => self::readName($rawBuffer, $pos), - 'type' => $type, - 'ttl' => $ans_header['ttl'], - 'class' => $ans, - 'length' => $ans_header['length'], - ]; - break; - case 'TXT': - $responses[$resType][] = [ - 'host' => $host, - 'type' => $type, - 'ttl' => $ans_header['ttl'], - 'class' => $ans, - 'length' => $ans_header['length'], - 'value' => self::readBufferLength( - (int) $ans_header['length'], - $rawBuffer, - $pos - ) - ]; - break; - case 'SOA': - $mname = self::readName($rawBuffer, $pos); - $rname = self::readName($rawBuffer, $pos); - $extras = unpack( - "Nserial/Nrefresh/Nretry/Nexpiry/Nminttl", - self::readBufferLength(20, $rawBuffer, $pos) - ); - $responses[$resType][] = [ - 'host' => $host, - 'type' => $type, - 'class' => $ans, - 'ttl' => $ans_header['ttl'], - 'mname' => $mname, - 'rname' => $rname, - 'serial' => $extras['serial'], - 'refresh' => $extras['refresh'], - 'expire' => $extras['expiry'], - 'minimum_ttl' => $extras['minttl'], - ]; - break; - case 'DNSKEY': - $stuff = self::readBufferLength( - (int) $ans_header['length'], - $rawBuffer, - $pos - ); - $extras = unpack("nflags/Cprotocol/Calgorithm/a*pubkey", $stuff); - $flags = sprintf("%016b\n", $extras['flags']); - $ac = 0; - for ($i = 0; $i < $ans_header['length']; $i++) { - $keyPack = unpack("C", $stuff[$i]); - $ac += (($i & 1) ? $keyPack[1] : $keyPack[1] << 8); - } - $ac += ($ac >> 16) & 0xFFFF; - $keyTag = $ac & 0xFFFF; - - $zoneKey = (int) $flags[7] === 1; - $zoneSep = (int) $flags[15] === 1; - $responses[$resType][] = [ - 'host' => $host, - 'ttl' => $ans_header['ttl'], - 'class' => $ans_header['class'], - 'key' => $zoneKey, - 'sep' => $zoneSep, - 'protocol' => $extras['protocol'], - 'algorithm' => $extras['algorithm'], - 'public_key' => base64_encode($extras['pubkey']), - 'key_id' => $keyTag, - 'flags' => $extras['flags'], - ]; - break; - case 'DS': - $stuff = self::readBufferLength( - (int) $ans_header['length'], - $rawBuffer, - $pos - ); - $length = (($ans_header['length'] - 4) * 2) - 8; - $stuff = unpack("nkeytag/Calgo/Cdigest/H" . $length . "string/H*rest", $stuff); - $responses[$resType][] = [ - 'host' => $host, - 'ttl' => $ans_header['ttl'], - 'class' => $ans_header['class'], - 'key_tag' => $stuff['keytag'], - 'algorithm' => $stuff['algo'], - 'digest' => $stuff['digest'], - 'rest' => strtoupper($stuff['rest']), - 'string' => strtoupper($stuff['string']), - ]; - break; - case 'RRSIG': - $stuff = self::readBufferLength(18, $rawBuffer, $pos); - //$length = $ans_header['length'] - 18; - $test = unpack("ntype/calgorithm/clabels/Noriginalttl/Nexpiration/Ninception/nkeytag", $stuff); - $name = self::readName($rawBuffer, $pos); - $sig = self::readBufferLength($ans_header['length'] - (strlen($name) + 2) - 18, $rawBuffer, $pos); - $responses[$resType][] = [ - 'host' => $host, - 'ttl' => $ans_header['ttl'], - 'class' => $ans_header['class'], - 'type' => $test['type'], - 'labels' => $test['labels'], - 'originalttl' => $test['originalttl'], - 'expiration' => $test['expiration'], - 'inception' => $test['inception'], - 'keytag' => $test['keytag'], - 'algorithm' => $test['algorithm'], - 'signer' => $name, - 'signature' => base64_encode($sig), - ]; - break; - default: - // Skip - $responses[$resType][] = [ - 'host' => $host, - 'ttl' => $ans_header['ttl'], - 'class' => $ans_header['class'], - 'length' => $ans_header['length'], - 'value' => self::readBufferLength( - (int) $ans_header['length'], - $rawBuffer, - $pos - ) - ]; - break; - } - } - - return $responses[$type]; - } - - protected static function readNamingPosition(int $offset, string $rawBuffer): array - { - $out = []; - while (($len = ord(substr($rawBuffer, $offset, 1))) && $len > 0) { - $out[] = substr($rawBuffer, $offset + 1, $len); - $offset += $len + 1; - } - return $out; - } - - protected static function readName(string $rawBuffer, int &$pos): string - { - $out = []; - - while (($len = ord(self::readBufferLength(1, $rawBuffer, $pos))) && $len > 0) { - if ($len >= 64) { - $offset = (($len & 0x3f) << 8) + ord(self::readBufferLength(1, $rawBuffer, $pos)); - $out = array_merge($out, self::readNamingPosition($offset, $rawBuffer)); - break; - } else { - $out[] = self::readBufferLength($len, $rawBuffer, $pos); - } - } - - return implode('.', $out); - } - - protected static function readBufferLength(int $length, string $rawBuffer, int &$pos): string - { - $out = substr($rawBuffer, $pos, $length); - $pos += $length; - return $out; - } - - protected static function createSocket(string|array|null $servers, int $timeout, &$useServer) - { - if (is_string($servers)) { - $servers = [trim($servers)]; - } - if (is_array($servers)) { - $servers = array_map('trim', array_filter($servers, 'is_string')); - } - if (!is_array($servers) || empty($servers)) { - $servers = self::DEFAULT_NAMESERVER; - } - $socket = null; - $errorCode = null; - $errorMessage = null; - $useServer = null; - foreach ($servers as $server) { - $host = "udp://$server"; - $errCode = 0; - $errMessage = null; - set_error_handler(static function ($errorCode, $errorMessage) use (&$errCode, &$errMessage) { - $errCode = $errorCode; - $errMessage = $errorMessage; - error_clear_last(); - }); - $socket = fsockopen($host, 53, $errno, $errstr, $timeout); - restore_error_handler(); - if ($socket) { - $useServer = $server; - break; - } - $errorCode = $errno === 0 && $errCode !== 0 ? $errCode : $errno; - $errorMessage = !$errstr && $errMessage !== null ? $errMessage : $errno; - } - if (!$socket) { - throw new RuntimeException( - $errorCode??0, - $errorMessage??'There was an error while creating socket' - ); - } - return $socket; - } -} diff --git a/src/Util/Whois/Abstracts/AddressStorage.php b/src/Util/Whois/Abstracts/AddressStorage.php deleted file mode 100644 index f92c8e9..0000000 --- a/src/Util/Whois/Abstracts/AddressStorage.php +++ /dev/null @@ -1,48 +0,0 @@ -address = trim($address); - } - - public function getAddress(): string - { - return $this->address; - } - - abstract public function isValid(): bool; - - abstract public function isLocal() : bool; - - public function serialize(): ?string - { - return serialize($this->__serialize()); - } - - public function unserialize(string $data): void - { - $this->__unserialize(unserialize($data)); - } - - abstract public function __serialize(): array; - - abstract public function __unserialize(array $data): void; - - public function __toString(): string - { - return $this->getAddress(); - } -} diff --git a/src/Util/Whois/Checker.php b/src/Util/Whois/Checker.php deleted file mode 100644 index c2bb1c9..0000000 --- a/src/Util/Whois/Checker.php +++ /dev/null @@ -1,245 +0,0 @@ -cache; - } - - public function getConversionFactory(): WhoisDataConversionFactory - { - return $this->conversionFactory ??= new WhoisDataConversionFactory(); - } - - public function setConversionFactory(WhoisDataConversionFactory $conversionFactory): void - { - $this->conversionFactory = $conversionFactory; - } - - public function getRequest(): SocketRequest - { - return $this->request ??= new SocketRequest(); - } - - public function setRequest(SocketRequest $request): void - { - $this->request = $request; - } - - protected function fromCacheDomain( - Domain|Ip $domain, - string $server, - ?string $extraCommand - ) : ?WhoisResult { - $isDomain = $domain instanceof Domain; - $domain = $isDomain - ? $domain->getAsciiName() - : $domain->getAddress(); - $pool = $this->getCache(); - if (!$domain || !$pool) { - return null; - } - $cacheName = self::CACHE_NAME_PREFIX . sha1( - "$domain:$server:$extraCommand" - ); - try { - $item = $pool->getItem($cacheName); - $result = $item->get(); - if (!$result instanceof WhoisResult - || !$result->isValid() - ) { - $pool->deleteItem($cacheName); - return null; - } - } catch (Throwable) { - return null; - } - - return !$isDomain || $domain === $result->getAddress()->getAsciiName() - ? $result - : null; - } - - public function getDomainIpServer(Domain|Ip $address): DomainIpServer - { - return new DomainIpServer( - $this->getRequest(), - $address, - $this->getCache() - ); - } - - protected function saveCache(WhoisResult $data) : string|false - { - $pool = $this->getCache(); - if (!$pool) { - return false; - } - $domain = $data->getAddress(); - $ascii = $data->isIp() ? $domain->getAddress() : $domain->getAsciiName(); - if (!$ascii) { - return false; - } - $server = $data->getServer(); - $extraCommand = $data->getExtraCommand(); - $cacheName = self::CACHE_NAME_PREFIX - . sha1("$ascii:$server:$extraCommand"); - try { - $item = $pool->getItem($cacheName); - $item->set($data); - $expired = static::DEFAULT_EXPIRED; - /** @noinspection PhpConditionAlreadyCheckedInspection */ - $expired = !is_int($expired) ? self::DEFAULT_EXPIRED : $expired; - $item->expiresAfter($expired); - return $pool->save($item) ? $cacheName : false; - } catch (Throwable) { - return false; - } - } - - public function whois( - string|Domain|Ip $address, - bool $useCache = true, - ?string $server = null, - int $timeout = 15 - ): WhoisResult { - $address = is_string($address) ? trim($address) : $address; - if ($address === '' || is_object($address) && $address->getAddress() === '') { - throw new EmptyArgumentException( - 'Domain name or IP could not be empty' - ); - } - - if (is_string($address)) { - // filter ip - $ipAddress = IpValidator::filterIpv4($address); - // if not ip4 -> check ipv6 - $ipAddress = $ipAddress && str_contains($ipAddress, ':') - ? (IpValidator::isValidIpv6($address) ? $address : false) - : $ipAddress; - if ($ipAddress) { - $address = new Ip($ipAddress); - } else { - $address = new Domain($address); - } - } - - if ($address instanceof Domain) { - $asciiName = $address->getAsciiName(); - if (!$asciiName) { - throw new InvalidArgumentException( - sprintf( - 'Domain name "%s" is invalid', - $address->getAddress() - ) - ); - } - if ($address->isLocal()) { - return new WhoisResult( - $address, - '[Local Domain]', - DomainIpServer::DEFAULT_SERVER, - null - ); - } - } else { - if (!$address->isValid()) { - throw new InvalidArgumentException( - sprintf( - 'IP address "%s" is invalid', - $address->getAddress() - ) - ); - } - if ($address->isLocal()) { - return new WhoisResult( - $address, - '[Local IP]', - DomainIpServer::DEFAULT_SERVER, - null - ); - } - } - - $isIp = $address instanceof Ip; - $target = $isIp ? $address->getAddress() : $address->getAsciiName(); - // check - $server = is_string($server) ? trim($server) : null; - $server = $server?:null; - $extraCommand = null; - if (!$server) { - $extension = $this->getDomainIpServer($address); - $server = $extension->getServer(); - $extraCommand = $extension->getExtraCommand(); - } - if (!$server) { - throw new RuntimeException( - sprintf( - 'Could not determine server from %s: %s', - $isIp ? 'IP' : 'domain', - $address->getAddress() - ) - ); - } - - if ($useCache - && ($result = $this->fromCacheDomain($address, $server, $extraCommand)) - ) { - return $result; - } - - $response = $this->getRequest()->doRequest( - $server, - $target, - $timeout, - $extraCommand - ); - if ($response['error']['code'] && $response['error']['message']) { - throw new RuntimeException( - sprintf('Whois Request Error: %s', $response['error']['message']) - ); - } - $result = new WhoisResult( - $address, - trim($response['result']??''), - $server, - $extraCommand - ); - $this->saveCache($result); - return $result; - } -} diff --git a/src/Util/Whois/Domain.php b/src/Util/Whois/Domain.php deleted file mode 100644 index cb07bc6..0000000 --- a/src/Util/Whois/Domain.php +++ /dev/null @@ -1,127 +0,0 @@ -getAsciiName() !== false; - } - - public function isLocal() : bool - { - $address = strtolower($this->address); - if ($address === '.local' || $address === 'localhost') { - return true; - } - $utf8 = $this->getUtf8Name(); - if (!$utf8) { - return false; - } - return $utf8 === 'localhost' || str_ends_with($utf8, '.local'); - } - - public function getUtf8Name(): false|string - { - return $this->getAsciiName() !== false ? $this->utf8DomainName : false; - } - - /** - * Filer Domain Name - * @return false|string return ascii domain name of false if failure - */ - public function getAsciiName(): false|string - { - if ($this->asciiDomain !== null) { - return $this->asciiDomain; - } - $this->asciiDomain = false; - $this->utf8DomainName = false; - if ($this->address === '' - || strlen($this->address) > 255 - || strlen($this->address) < 3 // a.b < 3 - || ! str_contains($this->address, '.') - || preg_match(self::REGEXP_IDNA_PATTERN, $this->address) - ) { - return false; - } - - $domainName = strtolower($this->address); - $arrayDomain = explode('.', $domainName); - foreach ($arrayDomain as $subName) { - // The maximum length of each label is 63 characters - if (strlen($subName) > 63) { - return false; - } - } - $extension = array_pop($arrayDomain); - array_pop($arrayDomain); - if (in_array($extension, self::GLOBAL_IDN) - && preg_match(self::REGEXP_GLOBAL_IDNA, $domainName) - ) { - return false; - } - - $domainName = idn_to_ascii($domainName); - if (!is_string($domainName) - || strlen($domainName) > 255 - || !preg_match(self::REGEXP_VALID_ASCII_PATTERN, $domainName) - ) { - return false; - } - $this->asciiDomain = $domainName; - $this->utf8DomainName = idn_to_utf8($this->asciiDomain); - return $this->asciiDomain; - } - - public function __serialize(): array - { - return [ - 'domainName' => $this->getAddress() - ]; - } - - public function __unserialize(array $data): void - { - $this->address = $data['domainName']; - $this->asciiDomain = null; - $this->utf8DomainName = null; - } -} diff --git a/src/Util/Whois/DomainIpServer.php b/src/Util/Whois/DomainIpServer.php deleted file mode 100644 index dc2a388..0000000 --- a/src/Util/Whois/DomainIpServer.php +++ /dev/null @@ -1,205 +0,0 @@ - self::APNIC, - 'whois.arin.net' => self::ARIN, - 'whois.ripe.net' => self::RIPE, - 'whois.lacnic.net' => self::LACNIC, - 'whois.afrinic.net' => self::AFRINIC, - ]; - - final const EXTRA_COMMANDS = [ - 'whois.jprs.jp' => '/e' - ]; - - final const PREDEFINED_SERVER = [ - 'jp' => 'whois.jprs.jp', - 'com' => 'whois.verisign-grs.com', - 'net' => 'whois.verisign-grs.com', - 'org' => 'whois.publicinterestregistry.org', - // za domain - 'za' => 'whois.nic.za', - ]; - - /** - * @var Domain|Ip - */ - protected Domain|Ip $address; - - protected string|false|null $extension = null; - - protected ?string $server = null; - - public function __construct( - protected SocketRequest $request, - Domain|Ip $address, - protected ?CacheItemPoolInterface $cache = null - ) { - $this->address = $address; - } - - public function getAddress(): Domain|Ip - { - return $this->address; - } - - protected function getDomainNameExtension() - { - if (!$this->address instanceof Domain) { - return null; - } - if (null !== $this->extension) { - return $this->extension?:null; - } - $this->extension = false; - $asciiDomain = $this->address->getAsciiName(); - if (!$asciiDomain) { - return null; - } - $asciiDomain = explode('.', $asciiDomain); - $this->extension = array_pop($asciiDomain); - return $this->extension; - } - - /** - * Get extra command - * - * @return string|null - */ - public function getExtraCommand(): ?string - { - $server = $this->getServer(); - return $server && isset(self::EXTRA_COMMANDS[$server]) - ? self::EXTRA_COMMANDS[$server] - : null; - } - - /** - * @return ?string - */ - public function getServer(): ?string - { - if ($this->server !== null) { - return $this->server?:null; - } - - $this->server = ''; - // if contains extension only - if ($this->address instanceof Domain - && !$this->address->isLocal() - && strlen($this->address->getAddress()) > 1 - && str_starts_with($this->address->getAddress(), '.') - ) { - $extension = idn_to_ascii(substr($this->address->getAddress(), 1)); - if (!$extension) { - return null; - } - } else { - if (!$this->address->isValid()) { - return null; - } - if ($this->address instanceof Domain) { - $extension = $this->getDomainNameExtension(); - $this->server = ''; - if (!$extension) { - return null; - } - } else { - if ($this->address->isLocal()) { - return null; - } - $extension = $this->address->getAddress(); - } - } - - // use predefined - if (isset(self::PREDEFINED_SERVER[$extension])) { - return $this->server = self::PREDEFINED_SERVER[$extension]; - } - if (isset(self::$extensionServers[$extension])) { - $this->server = self::$extensionServers[$extension]; - return $this->server?:null; - } - - $cacheName = self::CACHE_NAME_PREFIX.sha1($extension); - $item = null; - try { - $item = $this->cache?->getItem($cacheName); - $cache = $item?->get(); - if ($cache instanceof WhoisExtensionCache - && $cache->isValid() - ) { - $this->server = $cache->getServer(); - self::$extensionServers[$extension] = $this->server; - return $this->server; - } - } catch (Throwable) { - } - $response = $this->request->doRequest( - self::DEFAULT_SERVER, - $extension - ); - - if ($response['error']['code'] && $response['error']['message']) { - throw new RuntimeException( - sprintf('Server Error: %s', $response['error']['message']) - ); - } - - preg_match( - '~^\s*whois\s*:\s*(\S+)\s*$~mi', - $response['result']??'', - $match - ); - $this->server = trim($match[1]??''); - if ($this->cache && $item) { - $cache = new WhoisExtensionCache( - $extension, - $this->server?:false - ); - /** @noinspection PhpUnnecessaryStaticReferenceInspection */ - $expired = static::DEFAULT_EXPIRED; - /** @noinspection PhpConditionAlreadyCheckedInspection */ - $expired = !is_int($expired) ? self::DEFAULT_EXPIRED : $expired; - $item->set($cache); - $item->expiresAfter($expired); - $this->cache->save($item); - } - - return $this->server; - } -} diff --git a/src/Util/Whois/Ip.php b/src/Util/Whois/Ip.php deleted file mode 100644 index b34e1b8..0000000 --- a/src/Util/Whois/Ip.php +++ /dev/null @@ -1,44 +0,0 @@ -ipVersion ??= IpValidator::version($this->getAddress())?:false; - return $this->ipVersion?:null; - } - - public function isLocal() : bool - { - $this->isLocal ??= IpValidator::isLocalIP($this->getAddress()); - return $this->isLocal; - } - - public function isValid(): bool - { - $version = $this->getVersion(); - return $version === IpValidator::IP4 || $version === IpValidator::IP6; - } - - public function __serialize(): array - { - return [ - 'ip' => $this->getAddress(), - ]; - } - - public function __unserialize(array $data): void - { - $this->address = $data['ip']; - } -} diff --git a/src/Util/Whois/SocketRequest.php b/src/Util/Whois/SocketRequest.php deleted file mode 100644 index bf62931..0000000 --- a/src/Util/Whois/SocketRequest.php +++ /dev/null @@ -1,239 +0,0 @@ -acceptedProtocol = curl_version()['protocols']; - } - - /** - * @param string $server - * @return array{scheme: string, port: int, host: string} - */ - public function determineServer(string $server): array - { - $parsed = parse_url($server); - $host = $parsed['host']??$parsed['path']; - $scheme = $parsed['scheme']??null; - if (!isset($parsed['port'])) { - $port = $scheme === 'https' ? 443 : ( - $scheme === 'http' ? 80 : 43 - ); - } else { - $port = (int)$parsed['port']; - } - return [ - 'scheme' => $scheme??'telnet', - 'port' => $port, - 'host' => $host - ]; - } - - - /** - * @param string $server - * @param string $domain - * @param int $timeout - * @param ?string $extraCommand extra command, commonly in whois.jprs.jp to print english - * @return array{error: array{code: int, message: ?string}, info: array, result: ?string} - */ - public function socketRequest( - string $server, - string $domain, - int $timeout = 15, - ?string $extraCommand = null - ) : array { - $server = $this->determineServer($server); - $alternateErrorCode = null; - $alternateMessage = null; - set_error_handler(static function ( - $errCode, - $errMessage - ) use ( - &$alternateErrorCode, - &$alternateMessage - ) { - $alternateErrorCode = $errCode; - $alternateMessage = $errMessage; - error_clear_last(); - }); - $fp = fsockopen( - $server['host'], - $server['port'], - $errorCode, - $errorMessage, - $timeout - ); - // restore - restore_error_handler(); - $errorCode = $alternateErrorCode !== null && $errorCode === 0 - ? $alternateErrorCode - : $errorCode; - $errorMessage = $alternateMessage !== null && !$errorMessage - ? $alternateMessage - : $errorMessage; - $result = false; - $info = false; - if ($fp && $errorCode === 0) { - fputs($fp, "$domain$extraCommand\r\n"); - $result = ''; - while (!feof($fp)) { - $result .= fread($fp, 1024); - } - $info = stream_get_meta_data($fp); - } - if (is_resource($fp)) { - fclose($fp); - } - return [ - 'error' => [ - 'code' => $errorCode, - 'message' => $errorMessage?:null, - ], - 'info' => $info?:[], - 'result' => $result === false ? null : $result, - ]; - } - - /** - * @param string $server - * @param string $domain - * @param int $timeout - * @param ?string $extraCommand extra command, commonly in whois.jprs.jp to print english - * @return array{error: array{code: int, message: ?string}, info: array, result: ?string} - */ - public function curlRequest( - string $server, - string $domain, - int $timeout = 15, - ?string $extraCommand = null - ) : array { - $server = $this->determineServer($server); - if (!in_array($server['scheme'], $this->acceptedProtocol)) { - throw new RuntimeException( - sprintf('Protocol "%s" is not supported.', $server['scheme']) - ); - } - - $fp = fopen("php://temp", 'r+'); - $domain = strtolower(trim($domain)); - fwrite($fp, "$domain$extraCommand\r\n"); - fseek($fp, 0); - $size = fstat($fp)['size']; - $ch = curl_init(); - $host = "{$server['scheme']}://{$server['host']}"; - if ($server['port'] === 43) { - $host .= ":43"; - } - curl_setopt_array($ch, [ - CURLOPT_URL => $host, - CURLOPT_PORT => $server['port'], - CURLOPT_CONNECTTIMEOUT => 5, - CURLOPT_TIMEOUT => $timeout, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_PROTOCOLS => match ($server['scheme']) { - 'https' => CURLPROTO_HTTPS, - 'http' => CURLPROTO_HTTP, - default => CURLPROTO_TELNET - }, - CURLOPT_NOPROGRESS => true, - CURLOPT_INFILE => $fp, - CURLOPT_INFILESIZE => $size, - CURLOPT_VERBOSE => false, - CURLOPT_READFUNCTION => function ($ch, $fh, $length = false) { - return fread($fh, $length?:0); - } - ]); - $result = curl_exec($ch); - - fclose($fp); - - $error = curl_error($ch); - $errorCode = curl_errno($ch); - $info = curl_getinfo($ch); - curl_close($ch); - return [ - 'error' => [ - 'code' => $errorCode, - 'message' => $error?:null, - ], - 'info' => $info?:[], - 'result' => $result === false ? null : $result, - ]; - } - - /** - * @param string $server - * @param string $domain - * @param int $timeout - * @param ?string $extraCommand extra command, commonly in whois.jprs.jp to print english - * @return array{error: array{code: int, message: ?string}, info: array, result: ?string} - */ - public function doRequest( - string $server, - string $domain, - int $timeout = 15, - ?string $extraCommand = null - ): array { - return self::$useSocket - ? $this->socketRequest($server, $domain, $timeout, $extraCommand) - : $this->curlRequest($server, $domain, $timeout, $extraCommand); - } -} diff --git a/src/Util/Whois/Util/Abstracts/AbstractIPResultData.php b/src/Util/Whois/Util/Abstracts/AbstractIPResultData.php deleted file mode 100644 index abaf561..0000000 --- a/src/Util/Whois/Util/Abstracts/AbstractIPResultData.php +++ /dev/null @@ -1,122 +0,0 @@ -reparse($result) - ); - } - - /** - * @param string $name - * @return string - */ - protected function normalizeKeyName(string $name): string - { - $name = strtolower(trim($name)); - return preg_replace('~[^a-z0-9]+|[_\-\s]+~', '_', $name); - } - - protected function getAliasDataName(string $key): string - { - $key = $this->normalizeKeyName($key); - $key = match ($key) { - 'irt' => 'incident_response_team', - 'inetnum', - 'netrange' => 'network_range', - 'aut_num' => 'autonomous_system_number', - 'tech_c' => 'technical_contact', - 'admin_c' => 'administrative_contact', - 'owner_c' => 'owner_contact', - 'ownerid' => 'owner_id', - 'abuse_c' => 'abuse_contact', - 'netname' => 'network_name', - 'descr' => 'description', - 'mnt_ref' => 'maintainer_reference', - 'mnt_by' => 'maintainer', - 'mnt_lower' => 'sub_maintainer', - 'mnt_irt' => 'maintainer_incident_response_team', - 'mnt_routes' => 'maintainer_routes', - 'nic_hdl' => 'nic_handle', - 'org_name' => 'organization_name', - 'org_type' => 'organization_type', - 'nserver' => 'name_server', - 'nsstat' => 'name_server_status', - 'nslastaa' => 'name_server_last_a_address', - 'changed' => 'updated_at', - 'created' => 'created_at', - // cidr - 'inetrev' => 'cidr', - default => $key - }; - if (str_starts_with($key, 'tech_')) { - $key = substr_replace( - $key, - 'technical_', - 0, - 5 - ); - } - if (str_starts_with($key, '_c')) { - $key = substr_replace( - $key, - '_contact', - 0, - -2 - ); - } - - return $key; - } - - protected function reparse(WhoisResult $result) : array - { - $data = trim($result->getData()); - $result = []; - $start = true; - $increment = 0; - foreach (explode("\n", $data) as $item) { - $item = trim($item); - if (str_starts_with($item, '%')) { - continue; - } - if (!$start && $item === '') { - $start = true; - $increment++; - continue; - } - $item = explode(':', $item, 2); - if (count($item) !== 2) { - continue; - } - $key = array_shift($item); - $key = $this->getAliasDataName($key); - $item = trim(array_shift($item) ??''); - $item = trim(preg_replace('~(?:[\s]+|#.+$)~', ' ', $item)); - if ($key === 'phone') { - $item = preg_replace('~^tel\s*:\s*~', '', $item); - } - $result[$increment][$key][] = $item; - $start = false; - } - - return $result; - } -} diff --git a/src/Util/Whois/Util/Abstracts/AbstractResultData.php b/src/Util/Whois/Util/Abstracts/AbstractResultData.php deleted file mode 100644 index ff11b65..0000000 --- a/src/Util/Whois/Util/Abstracts/AbstractResultData.php +++ /dev/null @@ -1,70 +0,0 @@ -address = $result->getAddress()->getAsciiName(); - $this->type = $result->isIp() ? self::TYPE_IP : self::TYPE_DOMAIN; - $this->data = $data; - $this->server = $result->getServer(); - $this->provider = $registrar; - if (!$registrar || $registrar === self::REGISTRAR) { - if ($result->isIp()) { - $providers = DomainIpServer::IP_WHOIS_SERVER_PROVIDER; - $this->provider = $providers[strtolower($this->server)]??$this->provider; - } else { - preg_match( - '~^Registrar\s*:\s*(.*)$~m', - $result->getData(), - $match - ); - if (!empty($match[1])) { - $this->provider = trim($match[1]); - } - } - } - } - - public function getType(): string - { - return $this->type; - } - - public function getData(): array - { - return $this->data; - } - - public function getProvider(): ?string - { - return $this->provider; - } -} diff --git a/src/Util/Whois/Util/Abstracts/DomainResultAbstract.php b/src/Util/Whois/Util/Abstracts/DomainResultAbstract.php deleted file mode 100644 index 95b1881..0000000 --- a/src/Util/Whois/Util/Abstracts/DomainResultAbstract.php +++ /dev/null @@ -1,289 +0,0 @@ - null, - 'domain_id' => null, - 'domain_name' => null, - 'domain_whois_server' => null, - 'domain_creation_date' => null, - 'domain_updated_date' => null, - 'domain_expiration_date' => null, - 'domain_whois_last_update' => null, - 'domain_dnssec' => null, - 'domain_status' => [], - 'domain_name_server' => [], - - 'registrar_id' => null, - 'registrar_name' => null, - 'registrar_url' => null, - 'registrar_abuse_contact_email' => null, - 'registrar_abuse_contact_phone' => null, - - 'registrant_id' => null, - 'registrant_name' => null, - 'registrant_url' => null, - 'registrant_organization' => null, - 'registrant_street' => [], - 'registrant_city' => null, - 'registrant_state_province' => null, - 'registrant_postal_code' => null, - 'registrant_country' => null, - 'registrant_phone' => [], - 'registrant_fax' => [], - 'registrant_email' => null, - - // admin - 'admin_id' => null, - 'admin_name' => null, - 'admin_url' => null, - 'admin_organization' => null, - 'admin_street' => [], - 'admin_city' => null, - 'admin_state_province' => null, - 'admin_postal_code' => null, - 'admin_country' => null, - 'admin_phone' => [], - 'admin_fax' => [], - 'admin_email' => null, - - // technical - 'technical_id' => null, - 'technical_name' => null, - 'technical_url' => null, - 'technical_organization' => null, - 'technical_street' => [], - 'technical_city' => null, - 'technical_state_province' => null, - 'technical_postal_code' => null, - 'technical_country' => null, - 'technical_phone' => [], - 'technical_fax' => [], - 'technical_email' => null, - - // technical - 'billing_id' => null, - 'billing_name' => null, - 'billing_url' => null, - 'billing_organization' => null, - 'billing_street' => [], - 'billing_city' => null, - 'billing_state_province' => null, - 'billing_postal_code' => null, - 'billing_country' => null, - 'billing_phone' => [], - 'billing_fax' => [], - 'billing_email' => null, - - 'url_icann_data_report' => null, - ]; - - protected ?DateTimeZone $timeZone = null; - - public function __construct(WhoisResult $result) - { - parent::__construct( - $result, - null, - $this->parseData($result) - ); - } - - /** - * @param string $name - * @return string - */ - protected function normalizeKeyName(string $name): string - { - $name = strtolower(trim($name)); - return preg_replace('~[^a-z0-9]+|[_\-\s]+~', '_', $name); - } - - protected function getAliasDataName(string $key): string - { - $key = $this->normalizeKeyName($key); - $key = preg_replace_callback( - '~(^|_)(tech|admn?|contact|org\.?)(_|$)~', - static function ($e) { - return $e[1] - . match ($e[2]) { - 'tech' => 'technical', - 'adm', - 'contact', - 'admn' => 'admin', - 'org', - 'org.'=> 'organization', - default => $e[2], - } - . $e[3]; - }, - $key - ); - return match ($key) { - 'sponsoring_registrar', - 'registrar' => 'registrar_name', - - 'registrant', - 'domain_registrant' => 'registrant_name', - - 'whois', - 'whois_server', - 'registrar_whois_server' => 'domain_whois_server', - 'creation_date', - 'domain_created_on' => 'domain_creation_date', - 'update_date', - 'domain_last_updated', - 'last_updated', - 'updated_date'=> 'domain_updated_date', - 'last_update' => 'domain_whois_last_update', - 'expiration_date', - 'domain_expires_on', - 'expires_on', - 'expires_at', - 'expiry_date', - 'registry_expiry_date', - 'registrar_registration_expiration_date' => 'domain_expiration_date', - 'domain_ns', - 'name_server' => 'domain_name_server', - 'dnssec', - 'dns_sec', - 'domain_signing_key', - 'signing_key', - 'dnssecurity', - 'dns_security' => 'domain_dnssec', - 'domain', - 'name' => 'domain_name', - - 'registrant_phone_ext' => 'registrant_phone', - 'registrant_fax_ext' => 'registrant_fax', - 'registrar_phone_ext' => 'registrar_phone', - 'registrar_fax_ext' => 'registrar_fax', - 'admin_phone_ext' => 'admin_phone', - 'admin_fax_ext' => 'admin_fax', - 'billing_phone_ext' => 'billing_phone', - 'billing_fax_ext' => 'billing_fax', - 'technical_phone_ext' => 'technical_phone', - 'technical_fax_ext' => 'technical_fax', - - 'registry_domain_id' => 'domain_id', - 'registry_admin_id' => 'admin_id', - 'registry_registrant_id' => 'registrant_id', - 'registrar_iana_id' => 'registrar_id', - 'registry_tech_id', - 'registry_technical_id' => 'technical_id', - - 'url_of_the_icann_whois_data_problem_reporting_system' => 'url_icann_data_report', - - 'contact_name' => 'admin_name', - 'admin_web', - 'admin_page', - 'admin_web_page' => 'admin_url', - 'technical_web', - 'technical_page', - 'technical_web_page' => 'technical_url', - 'registrar_web', - 'registrar_page', - 'registrar_web_page' => 'registrar_url', - 'registrant_web', - 'registrant_page', - 'registrant_web_page' => 'registrant_url', - default => $key - }; - } - - protected function filterValue(string $key, string $item): ?string - { - if (str_ends_with($key, '_date') && preg_match('~^[1-9][0-9]{3}~', $item)) { - try { - $this->timeZone ??= new DateTimeZone('UTC'); - $date = (new DateTimeImmutable($item))->setTimezone($this->timeZone); - $item = $date->format(DateTimeInterface::RFC3339); - } catch (Throwable) { - } - } - return match ($key) { - 'domain_name' => idn_to_utf8(strtolower($item))?:strtolower($item), - default => $item?:null - }; - } - - protected function parseData(WhoisResult $result) : array - { - $array = self::DEFAULT_METADATA; - $array['whois_server'] = $result->getServer(); - $stop = false; - $result = strip_tags($result->getData()); - foreach (explode("\n", $result) as $result) { - $result = trim($result); - if (str_starts_with($result, '#')) { - continue; - } - // stop end of whois - if (str_contains($result, '>>>')) { - if (!str_contains($result, ':')) { - break; - } - $result = trim($result, '<>'); - if (!preg_match('~Last\s*Update[^:]*:\s*(.+)$~i', $result, $match)) { - break; - } - $result = 'last_update: '.$match[1]; - $stop = true; - } - - $result = explode(':', $result, 2); - // no info key - if (count($result) !== 2) { - continue; - } - - /** - * @var array $result - */ - $key = $this->getAliasDataName(array_shift($result)); - $result = trim(array_shift($result)); - $result = $this->filterValue($key, $result); - if (isset($array[$key])) { - if (is_array($array[$key])) { - if ($result) { - $array[$key][] = $result; - } - } else { - $array[$key] .= ', '. $result; - } - } else { - $array[$key] = $result; - } - if ($stop) { - break; - } - } - return $array; - } -} diff --git a/src/Util/Whois/Util/InternetProtocolRegistrar/Afrinic.php b/src/Util/Whois/Util/InternetProtocolRegistrar/Afrinic.php deleted file mode 100644 index 8b18f2b..0000000 --- a/src/Util/Whois/Util/InternetProtocolRegistrar/Afrinic.php +++ /dev/null @@ -1,13 +0,0 @@ -isIp() - ? $this->parseDomain($result) - : $this->parseIP($result); - } - - protected function parseIP(WhoisResult $result) : AbstractResultData - { - if ($result->isLocalIP()) { - return new LocalIpData($result); - } - $server = strtolower($result->getServer()); - $type = DomainIpServer::IP_WHOIS_SERVER_PROVIDER[$server]??null; - - return match ($type) { - DomainIpServer::APNIC => new Apnic($result), - DomainIpServer::ARIN => new Arin($result), - DomainIpServer::LACNIC => new Lacnic($result), - DomainIpServer::AFRINIC => new Afrinic($result), - DomainIpServer::RIPE => new Ripe($result), - default => new CommonIP($result), - }; - } - - protected function parseDomain(WhoisResult $result) : AbstractResultData - { - if ($result->isLocalDomain()) { - return new LocalDomainData($result); - } - $server = strtolower($result->getServer()); - return match ($server) { - 'whois.jprs.jp' => new Jprs($result), - default => new CommonDomain($result) - }; - } -} diff --git a/src/Util/Whois/Util/WhoisRegistrar/Common.php b/src/Util/Whois/Util/WhoisRegistrar/Common.php deleted file mode 100644 index 8b14a22..0000000 --- a/src/Util/Whois/Util/WhoisRegistrar/Common.php +++ /dev/null @@ -1,10 +0,0 @@ -timeZone = new DateTimeZone('JST'); - parent::__construct($result); - } - - protected function getAliasDataName(string $key, ?string $section = null): string - { - $key = parent::getAliasDataName($key); - return match ($section) { - 'domain' => match ($key) { - 'domain_domain_name', - 'domain_name' => 'domain_name', - default => $key - }, - default => $key, - }; - } - - protected function parseData(WhoisResult $result): array - { - $array = self::DEFAULT_METADATA; - $current_section = null; - $previousKey = null; - foreach (explode("\n", $result->getData()) as $result) { - $trimmedResult = trim($result); - if ($trimmedResult === '') { - $previousKey = null; - continue; - } - if (str_ends_with($trimmedResult, 'Information:')) { - preg_match('~^(.+\S)\s*Information:~', $trimmedResult, $match); - if (!empty($match)) { - $current_section = $this->normalizeKeyName(trim($match[1])); - } - continue; - } - if (!$current_section) { - continue; - } - $key = null; - $item = null; - if (str_starts_with($trimmedResult, '[') && str_contains($trimmedResult, '[')) { - preg_match('~^\[\s*([^]]+)\s*]\s*(.*)$~', $trimmedResult, $match); - if (!empty($match)) { - $key = $match[1]; - $previousKey = $key; - $item = $match[2]; - } - } - - if (!$key) { - if (!$previousKey) { - continue; - } - $key = $previousKey; - $item = $trimmedResult; - } - $key = $this->getAliasDataName("{$current_section}_$key", $current_section); - $item = $item ? $this->filterValue($key, $item) : $item; - if (isset($array[$key])) { - if (is_array($array[$key])) { - if ($item) { - $array[$key][] = $item; - } - } else { - $array[$key] .= ', '. $item; - } - } else { - $array[$key] = $item; - } - } - - $address = $array['admin_postal_address']??[]; - unset($array['admin_postal_address']); - $array['admin_city'] ??= array_pop($address)?:null; - $array['admin_street'] = array_values($address); - foreach ($array as $k => $i) { - if (!str_starts_with($k, 'admin_')) { - continue; - } - $key = substr_replace($k, 'registrant_', 0, 6); - if (array_key_exists($key, $array) && empty($array[$key])) { - $array[$key] = $i; - } - } - return $array; - } -} diff --git a/src/Util/Whois/Util/WhoisRegistrar/LocalData.php b/src/Util/Whois/Util/WhoisRegistrar/LocalData.php deleted file mode 100644 index 32c68e9..0000000 --- a/src/Util/Whois/Util/WhoisRegistrar/LocalData.php +++ /dev/null @@ -1,11 +0,0 @@ -extension = strtolower(trim($extension)); - $this->server = $server; - $this->hash = sha1($extension . ($server?:'')); - } - - public function getExtension(): string - { - return $this->extension; - } - - public function getHash(): string - { - return $this->hash; - } - - public function getServer(): false|string - { - return $this->server; - } - - public function isValid() : bool - { - if (!$this->currentHash) { - $this->currentHash = sha1($this->extension . ($this->server?:'')); - } - return $this->hash === $this->currentHash; - } - - public function serialize() : string - { - return serialize($this->__serialize()); - } - - public function unserialize(string $data): void - { - $this->__unserialize(unserialize($data)); - } - - public function __serialize(): array - { - return [ - 'extension' => $this->extension, - 'server' => $this->server, - 'hash' => $this->hash - ]; - } - - public function __unserialize(array $data): void - { - $this->extension = $data['extension']; - $this->server = $data['server']; - $this->hash = $data['hash']; - } -} diff --git a/src/Util/Whois/WhoisResult.php b/src/Util/Whois/WhoisResult.php deleted file mode 100644 index c8fc0e7..0000000 --- a/src/Util/Whois/WhoisResult.php +++ /dev/null @@ -1,229 +0,0 @@ -extraCommand = $extraCommand; - $this->data = $data; - $this->server = strtolower(trim($server)); - $this->hash = sha1(serialize([ - $this->server, - $extraCommand, - $data, - $address - ])); - } - - public function getExtraCommand(): ?string - { - return $this->extraCommand; - } - - public function getServer(): string - { - return $this->server; - } - - public function getAlternativeWhoisServer() : ?string - { - if ($this->alternativeWhoisServer !== null) { - return $this->alternativeWhoisServer?:null; - } - $this->alternativeWhoisServer = ''; - if ($this->address instanceof Ip) { - return null; - } - preg_match( - '~^\s*(?:Registrar\s*)?Whois(?:[\s-]*Server)?\s*:\s*(\S+)\s*$~mi', - $this->getData(), - $match - ); - $this->alternativeWhoisServer = strtolower(trim($match[1]??'')); - return $this->alternativeWhoisServer?:null; - } - - - public function getAlternativeData( - Checker $checker, - bool $useCache = true - ): ?WhoisResult { - if ($this->alternativeResult !== null) { - return $this->alternativeResult; - } - $this->alternativeResult = false; - $alternateServer = $this->getAlternativeWhoisServer(); - if (!$alternateServer || $alternateServer === $this->server) { - return $this->alternativeResult?:null; - } - try { - $cacheKey = self::CACHE_IP_PREFIX . sha1($alternateServer); - $cache = $checker->getCache(); - $cacheItem = $cache?->getItem($cacheKey); - $cachedIp = $cacheItem?->get(); - if ($useCache && $cacheItem && (is_string($cachedIp) || ($bool = is_bool($cachedIp)))) { - if (!empty($bool)) { - return null; - } - $ip = $cachedIp; - } else { - $record = Dns::record('A', $alternateServer, timeout: 2)??[]; - $record = reset($record) ?? []; - $ip = $record['ip'] ?? null; - if ($cacheItem) { - $cacheItem->set($ip??false); - $cacheItem->expiresAfter(3600); - $cache->save($cacheItem); - } - } - if (!$ip || (new Ip($ip))->isLocal()) { - return null; - } - } catch (Throwable) { - return null; - } - return $this->alternativeResult = $checker - ->whois( - $this->address, - $useCache, - $alternateServer - ); - } - - public function getType(): string - { - return $this->address instanceof Domain - ? self::TYPE_DOMAIN - : self::TYPE_IP; - } - public function isIp() : bool - { - return $this->getType() === self::TYPE_IP; - } - - public function isDomain() : bool - { - return $this->getType() === self::TYPE_DOMAIN; - } - - public function isLocalIP(): bool - { - return $this->isIp() && $this->getAddress()->isLocal(); - } - - public function isLocalDomain(): bool - { - return $this->isDomain() && $this->getAddress()->isLocal(); - } - - public function getData(): string - { - return $this->data; - } - - public function getHash(): string - { - return $this->hash; - } - - public function getAddress(): Domain|Ip - { - return $this->address; - } - - public function isValid() : bool - { - if (!$this->currentHash) { - $this->currentHash = sha1(serialize([ - $this->server, - $this->extraCommand, - $this->data, - $this->address - ])); - } - return $this->hash === $this->currentHash; - } - - public function serialize() : string - { - return serialize($this->__serialize()); - } - - public function unserialize(string $data): void - { - $this->__unserialize(unserialize($data)); - } - - public function __serialize(): array - { - return [ - 'domain' => $this->getAddress(), - 'hash' => $this->getHash(), - 'data' => $this->getData(), - 'server' => $this->getServer(), - 'extra_command' => $this->getExtraCommand(), - 'alternative_result' => $this->alternativeResult - ]; - } - - public function __unserialize(array $data): void - { - $this->data = $data['data']; - $this->address = $data['domain']; - $this->hash = $data['hash']; - $this->server = $data['server']; - $this->extraCommand = $data['extra_command']??null; - $this->alternativeResult = $data['alternative_result']; - $this->alternativeWhoisServer = null; - } - - public function __toString(): string - { - return $this->data; - } - - public function jsonSerialize(): array - { - return $this->__serialize(); - } -}