diff --git a/src/PublicSuffixList.php b/src/PublicSuffixList.php index 360ecbf..00afdf3 100644 --- a/src/PublicSuffixList.php +++ b/src/PublicSuffixList.php @@ -1,4 +1,5 @@ -setURL($url); } @@ -34,10 +33,9 @@ public function __construct($url = null) * Set the URL, and clear any existing tree * * @param string|null $url URL for the PSL or null to use default - * * @return void */ - public function setURL($url) + public function setURL(?string $url): void { $this->url = $url; $this->tree = null; @@ -49,7 +47,7 @@ public function setURL($url) * * @return void */ - protected function setFallbackURL() + private function setFallbackURL(): void { $this->setLocalPSLName($this->url); if (null === $this->url) { @@ -61,15 +59,14 @@ protected function setFallbackURL() * Load the PSL tree, automatically handling caches * * @return void (results in $this->tree) - * * @throws \RuntimeException */ - protected function loadTree() + private function loadTree(): void { $this->setFallbackURL(); $this->tree = $this->readCachedPSL($this->url); - if (false !== $this->tree) { + if (null !== $this->tree) { return; } @@ -88,15 +85,14 @@ protected function loadTree() * Parse the PSL data * * @param string $fileData the PSL data - * * @return void (results in $this->tree) */ - protected function parsePSL($fileData) + private function parsePSL(string $fileData): void { $lines = explode("\n", $fileData); foreach ($lines as $line) { - if ($this->startsWith($line, '//') || '' == $line) { + if ('' === $line || $this->startsWith($line, '//')) { continue; } @@ -110,12 +106,11 @@ protected function parsePSL($fileData) /** * Does $search start with $startString? * - * @param string $search the string to test + * @param string $search the string to test * @param string $startString the starting string to match - * * @return bool */ - protected function startsWith($search, $startString) + private function startsWith(string $search, string $startString): bool { return (0 === strpos($search, $startString)); } @@ -123,12 +118,11 @@ protected function startsWith($search, $startString) /** * Add domains to tree * - * @param array $node tree array by reference + * @param array $node tree array by reference * @param string[] $tldParts array of domain parts - * * @return void - changes made to $node by reference */ - protected function buildSubDomain(&$node, $tldParts) + private function buildSubDomain(array &$node, array $tldParts): void { $dom = trim(array_pop($tldParts)); @@ -146,7 +140,7 @@ protected function buildSubDomain(&$node, $tldParts) } } - if (!$isNotDomain && count($tldParts) > 0) { + if (!$isNotDomain && 0 < count($tldParts)) { $this->buildSubDomain($node[$dom], $tldParts); } } @@ -157,7 +151,7 @@ protected function buildSubDomain(&$node, $tldParts) * @return array the PSL tree * @throws \RuntimeException if PSL cannot be loaded */ - public function getTree() + public function getTree(): array { if (null === $this->tree) { $this->loadTree(); @@ -171,7 +165,7 @@ public function getTree() * * @return string|false PSL file contents or false on error */ - protected function readPSL() + private function readPSL() { $parts = parse_url($this->url); $remote = isset($parts['scheme']) || isset($parts['host']); @@ -206,10 +200,9 @@ protected function readPSL() * Determine cache file name for a specified source * * @param string $url URL/filename of source PSL - * * @return string cache file name for given resource */ - protected function getCacheFileName($url) + private function getCacheFileName(string $url): string { return __DIR__ . $this->dataDir . $this->cachedPrefix . md5($url); } @@ -218,26 +211,23 @@ protected function getCacheFileName($url) * Attempt to load a cached Public Suffix List tree for a given source * * @param string $url URL/filename of source PSL - * * @return false|string[] PSL tree */ - protected function readCachedPSL($url) + private function readCachedPSL(string $url) { $cacheFile = $this->getCacheFileName($url); return file_exists($cacheFile) ? unserialize(file_get_contents($cacheFile), ['allowed_classes' => false]) - : false; + : null; } - /** * Cache the current Public Suffix List tree and associate with the specified source * * @param string $url URL/filename of source PSL - * * @return bool|int the number of bytes that were written to the file, or false on failure */ - protected function cachePSL($url) + private function cachePSL(string $url) { return file_put_contents($this->getCacheFileName($url), serialize($this->tree)); } @@ -246,10 +236,9 @@ protected function cachePSL($url) * Save a local copy of a retrieved Public Suffix List * * @param string $fileContents URL/filename of source PSL - * * @return bool|int the number of bytes that were written to the file, or false on failure */ - protected function saveLocalPSL($fileContents) + private function saveLocalPSL(string $fileContents) { return file_put_contents(__DIR__ . $this->localPSL, $fileContents); } @@ -257,11 +246,10 @@ protected function saveLocalPSL($fileContents) /** * Set localPSL name based on URL * - * @param null|string $url the URL for the PSL - * + * @param string|null $url the URL for the PSL * @return void (sets $this->localPSL) */ - protected function setLocalPSLName($url) + private function setLocalPSLName(?string $url): void { if (null === $url) { $url = $this->sourceURL; @@ -275,17 +263,16 @@ protected function setLocalPSLName($url) * Delete files in the data directory * * @param bool $cacheOnly true to limit clearing to cached serialized PSLs, false to clear all - * * @return void */ - public function clearDataDirectory($cacheOnly = false) + public function clearDataDirectory(bool $cacheOnly = false): void { $dir = __DIR__ . $this->dataDir; if (is_dir($dir)) { - if ($dirHandle = opendir($dir)) { + if (false !== ($dirHandle = opendir($dir))) { while (false !== ($file = readdir($dirHandle))) { if ('file' === filetype($dir . $file) - && (false === $cacheOnly || $this->startsWith($file, $this->cachedPrefix))) { + && (!$cacheOnly || $this->startsWith($file, $this->cachedPrefix))) { unlink($dir . $file); } } diff --git a/src/RegisteredDomain.php b/src/RegisteredDomain.php index ab6dd2e..a405ece 100644 --- a/src/RegisteredDomain.php +++ b/src/RegisteredDomain.php @@ -1,4 +1,5 @@ - * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ + class RegisteredDomain { - protected $tree; - protected $psl; + private ?array $tree = null; + private PublicSuffixList $psl; /** * RegisteredDomain constructor. * * @param PublicSuffixList|null $psl PublicSuffixList object, or null to use defaults */ - public function __construct(PublicSuffixList $psl = null) + public function __construct(?PublicSuffixList $psl = null) { - if (null === $psl) { - $psl = new PublicSuffixList(); - } - $this->psl = $psl; + $this->psl = $psl ?? new PublicSuffixList(); } /** @@ -38,7 +37,7 @@ public function __construct(PublicSuffixList $psl = null) * * @return string */ - protected function normalizeHost($url) + protected function normalizeHost(string $url): string { if (empty($url)) { return ''; @@ -59,20 +58,17 @@ protected function normalizeHost($url) } /** - * Convert a punycode string to UTF-8 if needed + * Convert a punycode string to UTF-8 if needed. * - * @param string $part host component + * @param string $part Host component * - * @return string host component as UTF-8 + * @return string Host component as UTF-8 */ - protected function convertPunycode($part) + protected function convertPunycode(string $part): string { if (0 === strpos($part, 'xn--')) { if (function_exists('idn_to_utf8')) { - if (defined('INTL_IDNA_VARIANT_UTS46')) { // PHP 7.2 - return idn_to_utf8($part, 0, INTL_IDNA_VARIANT_UTS46); - } - return idn_to_utf8($part); + return defined('INTL_IDNA_VARIANT_UTS46') ? idn_to_utf8($part, 0, INTL_IDNA_VARIANT_UTS46) : idn_to_utf8($part); } return $this->decodePunycode($part); } @@ -80,14 +76,14 @@ protected function convertPunycode($part) } /** - * convert punycode to UTF-8 (the hard way) Used only if idn_to_utf8() is not available + * Convert punycode to UTF-8 (the hard way) if idn_to_utf8() is not available. * * This fallback adapted from https://ckon.wordpress.com/2010/08/24/punycode-to-unicode-converter-php/ * * @param string $encoded * @return string */ - protected function decodePunycode($encoded) + protected function decodePunycode(string $encoded): string { $prefix = 'xn--'; $safe_char = 0xFFFC; @@ -97,7 +93,12 @@ protected function decodePunycode($encoded) $skew = 38; $damp = 700; - if (null === $encoded || 0 !== strpos($encoded, $prefix) || 0 == strlen(trim(str_replace($prefix, '', $encoded)))) { + if (0 !== strpos($encoded, $prefix)) { + return $encoded; + } + + $trimmed = trim(str_replace($prefix, '', $encoded)); + if ('' === $trimmed) { return $encoded; } @@ -171,13 +172,13 @@ protected function decodePunycode($encoded) } /** - * Determine the registered domain portion of the supplied host string + * Determine the registered domain portion of the supplied host string. * - * @param string $host a host name or URL containing a host name + * @param string $host A host name or URL containing a host name * - * @return string|null shortest registrable domain portion of the supplied host or null if invalid + * @return string|null Shortest registrable domain portion of the supplied host or null if invalid */ - public function getRegisteredDomain($host) + public function getRegisteredDomain(string $host): ?string { $this->tree = $this->psl->getTree(); @@ -194,7 +195,7 @@ public function getRegisteredDomain($host) // assure there is at least 1 TLD in the stripped signing domain if (!strpos($result, '.')) { $cnt = count($signingDomainParts); - if (1 == $cnt || '' == $signingDomainParts[$cnt-2]) { + if (1 === $cnt || '' === $signingDomainParts[$cnt-2]) { return null; } return $signingDomainParts[$cnt-2] . '.' . $signingDomainParts[$cnt-1]; @@ -203,23 +204,23 @@ public function getRegisteredDomain($host) } /** - * Recursive helper method to query the PSL tree + * Recursive helper method to query the PSL tree. * - * @param string[] $remainingSigningDomainParts parts of domain being queried - * @param string[] $treeNode subset of tree array by reference + * @param string[] $remainingSigningDomainParts Parts of domain being queried + * @param array $treeNode Subset of tree array by reference * - * @return null|string + * @return string|null */ - protected function findRegisteredDomain($remainingSigningDomainParts, &$treeNode) + protected function findRegisteredDomain(array $remainingSigningDomainParts, array &$treeNode): ?string { $sub = array_pop($remainingSigningDomainParts); $result = null; if (isset($treeNode['!'])) { return ''; - } elseif (is_array($treeNode) && array_key_exists($sub, $treeNode)) { + } elseif (array_key_exists($sub, $treeNode)) { $result = $this->findRegisteredDomain($remainingSigningDomainParts, $treeNode[$sub]); - } elseif (is_array($treeNode) && array_key_exists('*', $treeNode)) { + } elseif (array_key_exists('*', $treeNode)) { $result = $this->findRegisteredDomain($remainingSigningDomainParts, $treeNode['*']); } else { return $sub; diff --git a/tests/unit/PublicSuffixListTest.php b/tests/unit/PublicSuffixListTest.php index 37eec15..88fbd4a 100644 --- a/tests/unit/PublicSuffixListTest.php +++ b/tests/unit/PublicSuffixListTest.php @@ -1,4 +1,5 @@ -object->clearDataDirectory(); } public function testContracts() @@ -54,4 +56,39 @@ public function testClearDataDirectoryCacheOnly() $this->assertIsArray($tree); $this->assertArrayHasKey('com', $tree); } + + public function testSetURL() + { + $url = 'https://example.com'; + $this->object->setURL($url); + + // Use reflection to call the protected setFallbackURL method + $reflection = new \ReflectionClass($this->object); + // Check the URL property to verify the fallback URL is set correctly + $property = $reflection->getProperty('url'); + $property->setAccessible(true); + + $this->assertSame($url, $property->getValue($this->object)); + + } + + public function testFallbackURL() + { + // Set URL to null and trigger the fallback mechanism + $this->object->setURL(null); + + // Use reflection to call the protected setFallbackURL method + $reflection = new \ReflectionClass($this->object); + $method = $reflection->getMethod('setFallbackURL'); + $method->setAccessible(true); + $method->invoke($this->object); + + // Check the URL property to verify the fallback URL is set correctly + $property = $reflection->getProperty('url'); + $property->setAccessible(true); + +// $expectedUrl = file_exists(dirname(__DIR__, 2) . '/data/public_suffix_list.dat') ? dirname(__DIR__, 2) . '/data/public_suffix_list.dat' : 'https://publicsuffix.org/list/public_suffix_list.dat'; + $expectedUrl = file_exists(dirname(__DIR__, 2) . '/data/public_suffix_list.dat') ? '/../data/public_suffix_list.dat' : 'https://publicsuffix.org/list/public_suffix_list.dat'; + $this->assertSame($expectedUrl, $property->getValue($this->object)); + } } diff --git a/tests/unit/RegisteredDomainTest.php b/tests/unit/RegisteredDomainTest.php index acd1057..e207f05 100644 --- a/tests/unit/RegisteredDomainTest.php +++ b/tests/unit/RegisteredDomainTest.php @@ -1,4 +1,5 @@ -decodePunycode($string); } } - class RegisteredDomainTest extends TestCase { /** * @var RegisteredDomain */ - protected $object; + protected RegisteredDomain $object; /** * Sets up the fixture, for example, opens a network connection. @@ -33,7 +34,7 @@ class RegisteredDomainTest extends TestCase */ protected function setUp(): void { - //$this->object = new RegisteredDomain(); + $this->object = new RegisteredDomain(new PublicSuffixList()); } /** @@ -42,12 +43,12 @@ protected function setUp(): void */ protected function tearDown(): void { + // Clean up if necessary } - public function testContracts() + public function testContracts(): void { - $object = new RegisteredDomain(); - $this->assertInstanceOf(RegisteredDomain::class, $object); + $this->assertInstanceOf(RegisteredDomain::class, $this->object); } @@ -55,19 +56,18 @@ public function testContracts() /** * @dataProvider domainsProvider */ - public function testGetRegisteredDomain($url = '', $regdom = '') + public function testGetRegisteredDomain(string $url, ?string $regdom): void { - $object = new RegisteredDomain(); - $this->assertEquals($regdom, $object->getRegisteredDomain($url)); + $this->assertEquals($regdom, $this->object->getRegisteredDomain($url)); } /** - * @return array + * @return array */ - public static function domainsProvider() + public static function domainsProvider(): array { - $provider = [ - [null, null], + return [ + ['', ''], // Mixed case. ['COM', null], ['example.COM', 'example.com'], @@ -158,26 +158,25 @@ public static function domainsProvider() ['rfu.in.ua', 'rfu.in.ua'], ['in.ua', null], ]; - return $provider; } // #[\PHPUnit\Framework\Attributes\DataProvider('punycodeProvider')] //PHP 8 /** * @dataProvider punycodeProvider */ - public function testDecodePunycode($punycode, $decoded) + public function testDecodePunycode(string $punycode, string $decoded): void { - $object = new TestProtectedDecodePunycode(); + $object = new TestProtectedDecodePunycode(new PublicSuffixList()); $this->assertEquals($decoded, $object->doDecodePunycode($punycode)); } /** - * @return array + * @return array */ - public static function punycodeProvider() + public static function punycodeProvider(): array { - $provider = [ - [null, null], + return [ + ['', ''], // Mixed case. ['test', 'test'], // punycoded @@ -185,6 +184,5 @@ public static function punycodeProvider() ['xn--55qx5d', '公司'], ['xn--fiqs8s', '中国'], ]; - return $provider; - } +} }