From 29d95cbf97a2d51dd0c7852af24593d3898e7955 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Mon, 19 Dec 2022 07:15:51 +0100 Subject: [PATCH 01/10] Improve install documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8e8589..219fb3d 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ PHP Domain Parser is compliant around: ### Composer ~~~ -$ composer require jeremykendall/php-domain-parser +composer require jeremykendall/php-domain-parser:^6.0 ~~~ ### System Requirements From 975fc664904fca14ee75613ce03faaabc300050f Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Mon, 19 Dec 2022 10:32:20 +0100 Subject: [PATCH 02/10] update shields URI in documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 219fb3d..8cd8756 100644 --- a/README.md +++ b/README.md @@ -594,7 +594,7 @@ Portions of the `Pdp\Rules` class are derivative works of the PHP [registered-domain-libs](https://github.com/usrflo/registered-domain-libs). I've included a copy of the Apache Software Foundation License 2.0 in this project. -[ico-github-actions-build]: https://img.shields.io/github/workflow/status/jeremykendall/php-domain-parser/Build?style=flat-square +[ico-github-actions-build]: https://img.shields.io/github/actions/workflow/status/jeremykendall/php-domain-parser/build.yaml?branch=develop&style=flat-square [ico-packagist]: https://img.shields.io/packagist/dt/jeremykendall/php-domain-parser.svg?style=flat-square [ico-release]: https://img.shields.io/github/release/jeremykendall/php-domain-parser.svg?style=flat-square [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square From fe22f702fd208753f998d1e7d2cf02a4d2658d61 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Wed, 4 Jan 2023 07:30:17 +0100 Subject: [PATCH 03/10] Update github actions and dependencies --- .github/workflows/build.yaml | 15 +++++---------- composer.json | 14 +++++++------- src/Storage/TimeToLive.php | 2 +- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1fb27c3..c5e55a2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,16 +10,11 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - php: ['8.1'] + php: ['8.1', '8.2'] stability: [prefer-lowest, prefer-stable] - include: - - php: '8.2' - flags: "--ignore-platform-req=php" - phpunit-flags: '--no-coverage' - stability: prefer-stable steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -36,8 +31,8 @@ jobs: - name: Get Composer Cache Directory id: composer-cache run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - uses: actions/cache@v2 + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ matrix.stability }}-${{ matrix.flags }}-${{ hashFiles('**/composer.lock') }} @@ -53,7 +48,7 @@ jobs: - name: Run static analysis run: composer phpstan - if: ${{ matrix.php == '8.1' && matrix.stability == 'prefer-stable'}} + if: ${{ matrix.php == '8.2' && matrix.stability == 'prefer-stable'}} - name: Run Coding style rules run: composer phpcs:fix diff --git a/composer.json b/composer.json index ba96958..43ef181 100644 --- a/composer.json +++ b/composer.json @@ -46,13 +46,13 @@ "ext-json": "*" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.8.0", + "friendsofphp/php-cs-fixer": "^v3.13.2", "guzzlehttp/guzzle": "^7.5", "guzzlehttp/psr7": "^1.6 || ^2.4.3", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan": "^1.9.6", + "phpstan/phpstan-phpunit": "^1.3.3", "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.26", + "phpunit/phpunit": "^9.5.27", "psr/http-factory": "^1.0.1", "psr/simple-cache": "^1.0.1", "symfony/cache": "^v5.0.0 || ^v6.0.0" @@ -69,14 +69,14 @@ } }, "scripts": { - "phpcs": "php-cs-fixer fix -vvv --diff --dry-run --allow-risky=yes --ansi", + "phpcs": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -vvv --diff --dry-run --allow-risky=yes --ansi", "phpcs:fix": "php-cs-fixer fix -vvv --allow-risky=yes --ansi", - "phpstan": "phpstan analyse -l max -c phpstan.neon src --memory-limit=256M --ansi", + "phpstan": "phpstan analyse -l max -c phpstan.neon src --xdebug --memory-limit=256M --ansi", "phpunit": "XDEBUG_MODE=coverage phpunit --coverage-text", "test": [ "@phpunit", "@phpstan", - "@phpcs:fix" + "@phpcs" ] }, "scripts-descriptions": { diff --git a/src/Storage/TimeToLive.php b/src/Storage/TimeToLive.php index 5d24cf2..5182f09 100644 --- a/src/Storage/TimeToLive.php +++ b/src/Storage/TimeToLive.php @@ -22,7 +22,7 @@ public static function fromDurationString(string $duration): DateInterval set_error_handler(fn () => true); $interval = DateInterval::createFromDateString($duration); restore_error_handler(); - if (!$interval instanceof DateInterval) { /* @phpstan-ignore-line */ + if (!$interval instanceof DateInterval) { throw new InvalidArgumentException( 'The ttl value "'.$duration.'" can not be parsable by `DateInterval::createFromDateString`.' ); From 87b2ca909f7937c1d03a86d2dfd18e2ae5316c17 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sat, 11 Feb 2023 22:15:37 +0100 Subject: [PATCH 04/10] Using PHPUnit 10 --- .gitignore | 2 +- composer.json | 8 +-- phpunit.xml.dist | 12 ++-- src/DomainTest.php | 60 +++++++------------ src/ResolvedDomainTest.php | 52 ++++++---------- src/RulesTest.php | 59 ++++-------------- .../PublicSuffixListPsr16CacheTest.php | 3 - .../PublicSuffixListPsr18ClientTest.php | 3 - src/Storage/RulesStorageTest.php | 3 - src/Storage/TimeToLiveTest.php | 9 ++- .../TopLevelDomainListPsr16CacheTest.php | 3 - .../TopLevelDomainListPsr18ClientTest.php | 3 - src/Storage/TopLevelDomainsStorageTest.php | 3 - src/SuffixTest.php | 23 +++---- src/TopLevelDomainsTest.php | 38 +++--------- 15 files changed, 79 insertions(+), 202 deletions(-) diff --git a/.gitignore b/.gitignore index fdae76c..61906c3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ docs build pdp .idea +.phpunit.cache .php_cs.cache .php-cs-fixer.cache -.phpunit.result.cache composer.lock diff --git a/composer.json b/composer.json index 43ef181..b3f7a19 100644 --- a/composer.json +++ b/composer.json @@ -49,10 +49,10 @@ "friendsofphp/php-cs-fixer": "^v3.13.2", "guzzlehttp/guzzle": "^7.5", "guzzlehttp/psr7": "^1.6 || ^2.4.3", - "phpstan/phpstan": "^1.9.6", - "phpstan/phpstan-phpunit": "^1.3.3", - "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.27", + "phpstan/phpstan": "^1.9.17", + "phpstan/phpstan-phpunit": "^1.3.4", + "phpstan/phpstan-strict-rules": "^1.4.5", + "phpunit/phpunit": "^10.0.7", "psr/http-factory": "^1.0.1", "psr/simple-cache": "^1.0.1", "symfony/cache": "^v5.0.0 || ^v6.0.0" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e0050e5..4a02ebf 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,17 +1,15 @@ + stopOnFailure="false" + cacheDirectory=".phpunit.cache" + backupStaticProperties="false" +> src diff --git a/src/DomainTest.php b/src/DomainTest.php index 066eef9..076baee 100644 --- a/src/DomainTest.php +++ b/src/DomainTest.php @@ -4,19 +4,14 @@ namespace Pdp; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use stdClass; use TypeError; -/** - * @coversDefaultClass \Pdp\Domain - */ final class DomainTest extends TestCase { - /** - * @covers \Pdp\SyntaxError - * @dataProvider invalidDomainProvider - */ + #[DataProvider('invalidDomainProvider')] public function testToAsciiThrowsException(string $domain): void { $this->expectException(SyntaxError::class); @@ -27,7 +22,7 @@ public function testToAsciiThrowsException(string $domain): void /** * @return iterable */ - public function invalidDomainProvider(): iterable + public static function invalidDomainProvider(): iterable { return [ 'invalid IDN domain' => ['a⒈com'], @@ -56,10 +51,9 @@ public function testDomainInternalPhpMethod(): void } /** - * @dataProvider countableProvider - * - * @param string[] $labels + * @param array $labels */ + #[DataProvider('countableProvider')] public function testCountable(?string $domain, int $nbLabels, array $labels): void { $domain = Domain::fromIDNA2008($domain); @@ -70,7 +64,7 @@ public function testCountable(?string $domain, int $nbLabels, array $labels): vo /** * @return iterable}> */ - public function countableProvider(): iterable + public static function countableProvider(): iterable { return [ 'null' => [null, 0, []], @@ -112,9 +106,7 @@ public function testLabels(): void self::assertSame([], Domain::fromIDNA2008(null)->labels()); } - /** - * @dataProvider toUnicodeProvider - */ + #[DataProvider('toUnicodeProvider')] public function testToIDN( ?string $domain, ?string $expectedDomain, @@ -131,7 +123,7 @@ public function testToIDN( /** * @return iterable */ - public function toUnicodeProvider(): iterable + public static function toUnicodeProvider(): iterable { return [ 'simple domain' => [ @@ -172,9 +164,7 @@ public function toUnicodeProvider(): iterable ]; } - /** - * @dataProvider toAsciiProvider - */ + #[DataProvider('toAsciiProvider')] public function testToAscii( ?string $domain, ?string $expectedDomain, @@ -191,7 +181,7 @@ public function testToAscii( /** * @return iterable */ - public function toAsciiProvider(): iterable + public static function toAsciiProvider(): iterable { return [ 'simple domain' => [ @@ -222,9 +212,7 @@ public function toAsciiProvider(): iterable ]; } - /** - * @dataProvider withLabelWorksProvider - */ + #[DataProvider('withLabelWorksProvider')] public function testWithLabelWorks(DomainName $domain, int $key, string $label, ?string $expected): void { $result = $domain->withLabel($key, $label); @@ -234,7 +222,7 @@ public function testWithLabelWorks(DomainName $domain, int $key, string $label, /** * @return iterable */ - public function withLabelWorksProvider(): iterable + public static function withLabelWorksProvider(): iterable { $base_domain = Domain::fromIDNA2008('www.example.com'); @@ -321,9 +309,7 @@ public function testWithLabelFailsWithInvalidLabel2(): void Domain::fromIDNA2008('example.com')->withLabel(-1, ''); } - /** - * @dataProvider validAppend - */ + #[DataProvider('validAppend')] public function testAppend(string $raw, string $append, string $expected): void { self::assertSame($expected, Domain::fromIDNA2008($raw)->append($append)->toString()); @@ -332,7 +318,7 @@ public function testAppend(string $raw, string $append, string $expected): void /** * @return iterable */ - public function validAppend(): iterable + public static function validAppend(): iterable { return [ ['secure.example.com', '8.8.8.8', 'secure.example.com.8.8.8.8'], @@ -342,9 +328,7 @@ public function validAppend(): iterable ]; } - /** - * @dataProvider validPrepend - */ + #[DataProvider('validPrepend')] public function testPrepend(string $raw, string $prepend, string $expected): void { self::assertSame($expected, Domain::fromIDNA2008($raw)->prepend($prepend)->toString()); @@ -353,7 +337,7 @@ public function testPrepend(string $raw, string $prepend, string $expected): voi /** * @return iterable */ - public function validPrepend(): iterable + public static function validPrepend(): iterable { return [ ['secure.example.com', 'master', 'master.secure.example.com'], @@ -362,9 +346,7 @@ public function validPrepend(): iterable ]; } - /** - * @dataProvider withoutLabelWorksProvider - */ + #[DataProvider('withoutLabelWorksProvider')] public function testwithoutLabelWorks(DomainName $domain, int $key, ?string $expected): void { $result = $domain->withoutLabel($key); @@ -374,7 +356,7 @@ public function testwithoutLabelWorks(DomainName $domain, int $key, ?string $exp /** * @return iterable */ - public function withoutLabelWorksProvider(): iterable + public static function withoutLabelWorksProvider(): iterable { $base_domain = Domain::fromIDNA2008('www.example.com'); @@ -413,9 +395,7 @@ public function testwithoutLabelWorksWithMultipleKeys(): void self::assertNull(Domain::fromIDNA2008('www.example.com')->withoutLabel(0, 1, 2)->value()); } - /** - * @dataProvider resolveCustomIDNAOptionsProvider - */ + #[DataProvider('resolveCustomIDNAOptionsProvider')] public function testResolveWorksWithCustomIDNAOptions( string $domainName, string $withLabel, @@ -434,7 +414,7 @@ public function testResolveWorksWithCustomIDNAOptions( /** * @return iterable> */ - public function resolveCustomIDNAOptionsProvider(): iterable + public static function resolveCustomIDNAOptionsProvider(): iterable { return [ 'without deviation characters' => [ diff --git a/src/ResolvedDomainTest.php b/src/ResolvedDomainTest.php index 75b798e..0fb4ca1 100644 --- a/src/ResolvedDomainTest.php +++ b/src/ResolvedDomainTest.php @@ -4,13 +4,11 @@ namespace Pdp; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use TypeError; use function date_create; -/** - * @coversDefaultClass \Pdp\ResolvedDomain - */ final class ResolvedDomainTest extends TestCase { public function testItCanBeCreatedWithAnotherResolvedDomain(): void @@ -31,9 +29,7 @@ public function testRegistrableDomainIsNullWithFoundDomain(): void self::assertNull($domain->secondLevelDomain()->value()); } - /** - * @dataProvider provideWrongConstructor - */ + #[DataProvider('provideWrongConstructor')] public function testItThrowsExceptionMisMatchPublicSuffixDomain(?string $domain, int $length): void { $this->expectException(UnableToResolveDomain::class); @@ -44,7 +40,7 @@ public function testItThrowsExceptionMisMatchPublicSuffixDomain(?string $domain, /** * @return iterable */ - public function provideWrongConstructor(): iterable + public static function provideWrongConstructor(): iterable { return [ 'domain and public suffix are the same' => [ @@ -68,9 +64,7 @@ public function testItCanBeUsedWithInternalPhpMethod(): void self::assertSame('www.ulb.ac.be', $domain->toString()); } - /** - * @dataProvider countableProvider - */ + #[DataProvider('countableProvider')] public function testItImplementsCountable(?string $domain, int $nbLabels): void { self::assertCount($nbLabels, ResolvedDomain::fromUnknown($domain)); @@ -79,7 +73,7 @@ public function testItImplementsCountable(?string $domain, int $nbLabels): void /** * @return iterable */ - public function countableProvider(): iterable + public static function countableProvider(): iterable { return [ 'null' => [null, 0], @@ -89,9 +83,7 @@ public function countableProvider(): iterable ]; } - /** - * @dataProvider toUnicodeProvider - */ + #[DataProvider('toUnicodeProvider')] public function testItCanBeConvertedToUnicode( ?string $domain, ?string $publicSuffix, @@ -113,7 +105,7 @@ public function testItCanBeConvertedToUnicode( /** * @return iterable */ - public function toUnicodeProvider(): iterable + public static function toUnicodeProvider(): iterable { return [ 'simple domain' => [ @@ -175,9 +167,7 @@ public function toUnicodeProvider(): iterable ]; } - /** - * @dataProvider toAsciiProvider - */ + #[DataProvider('toAsciiProvider')] public function testItCanBeConvertedToAscii( ?string $domain, ?string $publicSuffix, @@ -199,7 +189,7 @@ public function testItCanBeConvertedToAscii( /** * @return iterable */ - public function toAsciiProvider(): iterable + public static function toAsciiProvider(): iterable { return [ 'simple domain' => [ @@ -245,9 +235,7 @@ public function toAsciiProvider(): iterable ]; } - /** - * @dataProvider withSubDomainWorksProvider - */ + #[DataProvider('withSubDomainWorksProvider')] public function testItCanHaveItsSubDomainChanged(ResolvedDomain $domain, DomainName|string|null $subdomain, string $expected = null): void { $result = $domain->withSubDomain($subdomain); @@ -260,7 +248,7 @@ public function testItCanHaveItsSubDomainChanged(ResolvedDomain $domain, DomainN /** * @return iterable */ - public function withSubDomainWorksProvider(): iterable + public static function withSubDomainWorksProvider(): iterable { return [ 'simple addition' => [ @@ -319,9 +307,7 @@ public function testItCanThrowsDuringSubDomainChangesIfTheSubDomainIsNotStringab ResolvedDomain::fromICANN('www.example.com', 1)->withSubDomain(date_create()); /* @phpstan-ignore-line */ } - /** - * @dataProvider withPublicSuffixWorksProvider - */ + #[DataProvider('withPublicSuffixWorksProvider')] public function testItCanChangeItsSuffix( ResolvedDomain $domain, EffectiveTopLevelDomain|string|null $publicSuffix, @@ -342,7 +328,7 @@ public function testItCanChangeItsSuffix( /** * @return iterable */ - public function withPublicSuffixWorksProvider(): iterable + public static function withPublicSuffixWorksProvider(): iterable { $baseDomain = ResolvedDomain::fromICANN('example.com', 1); @@ -429,9 +415,7 @@ public function testItCanThrowsDuringSuffixChangesIfTheDomainHasNotSuffix(): voi ResolvedDomain::fromUnknown(null)->withSuffix('www'); } - /** - * @dataProvider resolveCustomIDNAOptionsProvider - */ + #[DataProvider('resolveCustomIDNAOptionsProvider')] public function testItCanWorksWithIDNAOptions( string $domainName, string $publicSuffix, @@ -453,7 +437,7 @@ public function testItCanWorksWithIDNAOptions( /** * @return iterable */ - public function resolveCustomIDNAOptionsProvider(): iterable + public static function resolveCustomIDNAOptionsProvider(): iterable { return [ 'without deviation characters' => [ @@ -495,9 +479,7 @@ public function resolveCustomIDNAOptionsProvider(): iterable ]; } - /** - * @dataProvider withSldWorksProvider - */ + #[DataProvider('withSldWorksProvider')] public function testWithSecondLevelDomain( ?string $host, ?string $publicSuffix, @@ -517,7 +499,7 @@ public function testWithSecondLevelDomain( /** * @return iterable */ - public function withSldWorksProvider(): iterable + public static function withSldWorksProvider(): iterable { return [ [ diff --git a/src/RulesTest.php b/src/RulesTest.php index cbcaf03..b8e4838 100644 --- a/src/RulesTest.php +++ b/src/RulesTest.php @@ -4,6 +4,7 @@ namespace Pdp; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use TypeError; use function array_fill; @@ -11,9 +12,6 @@ use function file_get_contents; use function implode; -/** - * @coversDefaultClass \Pdp\Rules - */ final class RulesTest extends TestCase { private static Rules $rules; @@ -203,25 +201,19 @@ public function testWithDomainInterfaceObject(): void ); } - /** - * @dataProvider parseDataProvider - */ + #[DataProvider('parseDataProvider')] public function testGetRegistrableDomain(?string $publicSuffix, ?string $registrableDomain, string $domain, ?string $expectedDomain): void { self::assertSame($registrableDomain, self::$rules->resolve($domain)->registrableDomain()->value()); } - /** - * @dataProvider parseDataProvider - */ + #[DataProvider('parseDataProvider')] public function testGetPublicSuffix(?string $publicSuffix, ?string $registrableDomain, string $domain, ?string $expectedDomain): void { self::assertSame($publicSuffix, self::$rules->resolve($domain)->suffix()->value()); } - /** - * @dataProvider parseDataProvider - */ + #[DataProvider('parseDataProvider')] public function testGetDomain(?string $publicSuffix, ?string $registrableDomain, string $domain, ?string $expectedDomain): void { self::assertSame($expectedDomain, self::$rules->resolve($domain)->value()); @@ -230,7 +222,7 @@ public function testGetDomain(?string $publicSuffix, ?string $registrableDomain, /** * @return iterable */ - public function parseDataProvider(): iterable + public static function parseDataProvider(): iterable { return [ // public suffix, registrable domain, domain @@ -324,11 +316,6 @@ public function parseDataProvider(): iterable ]; } - /** - * @covers ::validateDomain - * @covers \Pdp\Domain::parseDomain - * @covers \Pdp\Domain::parseValue - */ public function testGetPublicSuffixThrowsCouldNotResolvePublicSuffix(): void { $this->expectException(UnableToResolveDomain::class); @@ -336,13 +323,7 @@ public function testGetPublicSuffixThrowsCouldNotResolvePublicSuffix(): void self::$rules->getICANNDomain('localhost'); } - /** - * @covers ::getICANNDomain - * @covers \Pdp\Domain::parseDomain - * @covers \Pdp\Domain::parseValue - * - * @dataProvider invalidDomainParseProvider - */ + #[DataProvider('invalidDomainParseProvider')] public function testGetPublicSuffixThrowsInvalidDomainException(string $domain): void { $this->expectException(SyntaxError::class); @@ -353,7 +334,7 @@ public function testGetPublicSuffixThrowsInvalidDomainException(string $domain): /** * @return iterable */ - public function invalidDomainParseProvider(): iterable + public static function invalidDomainParseProvider(): iterable { return [ 'IPv6' => ['[::1]'], @@ -362,13 +343,7 @@ public function invalidDomainParseProvider(): iterable ]; } - /** - * @covers ::getICANNDomain - * @covers \Pdp\Domain::parseDomain - * @covers \Pdp\Domain::parseValue - * - * @dataProvider invalidHostParseProvider - */ + #[DataProvider('invalidHostParseProvider')] public function testGetPublicSuffixThrowsInvalidHostException(string $domain): void { $this->expectException(SyntaxError::class); @@ -379,7 +354,7 @@ public function testGetPublicSuffixThrowsInvalidHostException(string $domain): v /** * @return iterable */ - public function invalidHostParseProvider(): iterable + public static function invalidHostParseProvider(): iterable { $longLabel = implode('.', array_fill(0, 62, 'a')); @@ -391,13 +366,7 @@ public function invalidHostParseProvider(): iterable ]; } - /** - * @covers ::getCookieDomain - * @covers \Pdp\Domain::parseDomain - * @covers \Pdp\Domain::parseValue - * - * @dataProvider validPublicSectionProvider - */ + #[DataProvider('validPublicSectionProvider')] public function testPublicSuffixSection(?string $domain, ?string $expected): void { self::assertSame($expected, self::$rules->getCookieDomain($domain)->suffix()->value()); @@ -406,7 +375,7 @@ public function testPublicSuffixSection(?string $domain, ?string $expected): voi /** * @return iterable */ - public function validPublicSectionProvider(): iterable + public static function validPublicSectionProvider(): iterable { return [ 'idn domain' => [ @@ -548,9 +517,7 @@ public function testPublicSuffixSpec(): void $this->checkPublicSuffix('xn--fiqs8s', null); } - /** - * @dataProvider effectiveTLDProvider - */ + #[DataProvider('effectiveTLDProvider')] public function testEffectiveTLDResolution(string $host, string $cookieETLD, string $icannETLD, string $privateETLD): void { self::assertSame($cookieETLD, self::$rules->getCookieDomain($host)->suffix()->toString()); @@ -561,7 +528,7 @@ public function testEffectiveTLDResolution(string $host, string $cookieETLD, str /** * @return iterable */ - public function effectiveTLDProvider(): iterable + public static function effectiveTLDProvider(): iterable { return [ 'private domain effective TLD' => [ diff --git a/src/Storage/PublicSuffixListPsr16CacheTest.php b/src/Storage/PublicSuffixListPsr16CacheTest.php index 57212a7..c9c8989 100644 --- a/src/Storage/PublicSuffixListPsr16CacheTest.php +++ b/src/Storage/PublicSuffixListPsr16CacheTest.php @@ -14,9 +14,6 @@ use RuntimeException; use function dirname; -/** - * @coversDefaultClass \Pdp\Storage\PublicSuffixListPsr16Cache - */ final class PublicSuffixListPsr16CacheTest extends TestCase { public function testItReturnsNullIfTheCacheDoesNotExists(): void diff --git a/src/Storage/PublicSuffixListPsr18ClientTest.php b/src/Storage/PublicSuffixListPsr18ClientTest.php index c90dd9e..1117abb 100644 --- a/src/Storage/PublicSuffixListPsr18ClientTest.php +++ b/src/Storage/PublicSuffixListPsr18ClientTest.php @@ -17,9 +17,6 @@ use function dirname; use function file_get_contents; -/** - * @coversDefaultClass \Pdp\Storage\PublicSuffixListPsr18Client - */ final class PublicSuffixListPsr18ClientTest extends TestCase { public function testIsCanReturnAPublicSuffixListInstance(): void diff --git a/src/Storage/RulesStorageTest.php b/src/Storage/RulesStorageTest.php index 7671ffc..723628e 100644 --- a/src/Storage/RulesStorageTest.php +++ b/src/Storage/RulesStorageTest.php @@ -9,9 +9,6 @@ use PHPUnit\Framework\TestCase; use function dirname; -/** - * @coversDefaultClass \Pdp\Storage\RulesStorage - */ final class RulesStorageTest extends TestCase { public function testIsCanReturnAPublicSuffixListInstanceFromCache(): void diff --git a/src/Storage/TimeToLiveTest.php b/src/Storage/TimeToLiveTest.php index b8083d1..1604d3d 100644 --- a/src/Storage/TimeToLiveTest.php +++ b/src/Storage/TimeToLiveTest.php @@ -7,6 +7,7 @@ use DateInterval; use DateTimeImmutable; use DateTimeZone; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Stringable; @@ -34,7 +35,7 @@ public function testItCanBeInstantiatedFromDurationInput(string $input, DateInte /** * @return iterable */ - public function validDurationString(): iterable + public static function validDurationString(): iterable { $threeDays = new DateInterval('P3D'); @@ -54,9 +55,7 @@ public function testItCastToNullWithNull(): void self::assertNull(TimeToLive::convert(null)); } - /** - * @dataProvider validDurationInt - */ + #[DataProvider('validDurationInt')] public function testItCanBeInstantiatedFromSeconds(int|string|Stringable|DateInterval $input, DateInterval $expected): void { /** @var DateInterval $ttl */ @@ -69,7 +68,7 @@ public function testItCanBeInstantiatedFromSeconds(int|string|Stringable|DateInt /** * @return iterable */ - public function validDurationInt(): iterable + public static function validDurationInt(): iterable { $seconds = new DateInterval('PT2345S'); diff --git a/src/Storage/TopLevelDomainListPsr16CacheTest.php b/src/Storage/TopLevelDomainListPsr16CacheTest.php index 58af492..59a0a76 100644 --- a/src/Storage/TopLevelDomainListPsr16CacheTest.php +++ b/src/Storage/TopLevelDomainListPsr16CacheTest.php @@ -14,9 +14,6 @@ use RuntimeException; use function dirname; -/** - * @coversDefaultClass \Pdp\Storage\TopLevelDomainListPsr16Cache - */ final class TopLevelDomainListPsr16CacheTest extends TestCase { public function testItReturnsNullIfTheCacheDoesNotExists(): void diff --git a/src/Storage/TopLevelDomainListPsr18ClientTest.php b/src/Storage/TopLevelDomainListPsr18ClientTest.php index 1b4dd55..9f4b0b6 100644 --- a/src/Storage/TopLevelDomainListPsr18ClientTest.php +++ b/src/Storage/TopLevelDomainListPsr18ClientTest.php @@ -17,9 +17,6 @@ use function dirname; use function file_get_contents; -/** - * @coversDefaultClass \Pdp\Storage\TopLevelDomainListPsr18Client - */ final class TopLevelDomainListPsr18ClientTest extends TestCase { public function testIsCanReturnARootZoneDatabaseInstance(): void diff --git a/src/Storage/TopLevelDomainsStorageTest.php b/src/Storage/TopLevelDomainsStorageTest.php index 61ab96a..0e299e9 100644 --- a/src/Storage/TopLevelDomainsStorageTest.php +++ b/src/Storage/TopLevelDomainsStorageTest.php @@ -9,9 +9,6 @@ use PHPUnit\Framework\TestCase; use function dirname; -/** - * @coversDefaultClass \Pdp\Storage\TopLevelDomainsStorage - */ final class TopLevelDomainsStorageTest extends TestCase { public function testIsCanReturnARootZoneDatabaseInstanceFromCache(): void diff --git a/src/SuffixTest.php b/src/SuffixTest.php index 1bd1b82..ca728c8 100644 --- a/src/SuffixTest.php +++ b/src/SuffixTest.php @@ -4,12 +4,10 @@ namespace Pdp; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use function json_encode; -/** - * @coversDefaultClass \Pdp\Suffix - */ final class SuffixTest extends TestCase { public function testItCanBeCreatedWithAnotherResolvedDomain(): void @@ -36,9 +34,7 @@ public function testPSToUnicodeWithUrlEncode(): void self::assertSame('bébe', Suffix::fromUnknown('b%C3%A9be')->toUnicode()->value()); } - /** - * @dataProvider invalidPublicSuffixProvider - */ + #[DataProvider('invalidPublicSuffixProvider')] public function testConstructorThrowsException(string $publicSuffix): void { $this->expectException(SyntaxError::class); @@ -49,7 +45,7 @@ public function testConstructorThrowsException(string $publicSuffix): void /** * @return iterable> */ - public function invalidPublicSuffixProvider(): iterable + public static function invalidPublicSuffixProvider(): iterable { return [ 'empty string' => [''], @@ -104,9 +100,7 @@ public function testFromIANA(): void Suffix::fromIANA('ac.be'); } - /** - * @dataProvider conversionReturnsTheSameInstanceProvider - */ + #[DataProvider('conversionReturnsTheSameInstanceProvider')] public function testConversionReturnsTheSameInstance(?string $publicSuffix): void { $instance = Suffix::fromUnknown($publicSuffix); @@ -118,7 +112,7 @@ public function testConversionReturnsTheSameInstance(?string $publicSuffix): voi /** * @return iterable */ - public function conversionReturnsTheSameInstanceProvider(): iterable + public static function conversionReturnsTheSameInstanceProvider(): iterable { return [ 'ascii only domain' => ['ac.be'], @@ -133,10 +127,7 @@ public function testToUnicodeReturnsSameInstance(): void self::assertEquals($instance->toUnicode(), $instance); } - /** - * @dataProvider countableProvider - * @param ?string $domain - */ + #[DataProvider('countableProvider')] public function testCountable(?string $domain, int $nbLabels): void { $domain = Suffix::fromUnknown($domain); @@ -147,7 +138,7 @@ public function testCountable(?string $domain, int $nbLabels): void /** * @return iterable}> */ - public function countableProvider(): iterable + public static function countableProvider(): iterable { return [ 'null' => [null, 0, []], diff --git a/src/TopLevelDomainsTest.php b/src/TopLevelDomainsTest.php index 2b13f28..ca86e11 100644 --- a/src/TopLevelDomainsTest.php +++ b/src/TopLevelDomainsTest.php @@ -6,14 +6,12 @@ use DateTimeImmutable; use DateTimeZone; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Stringable; use TypeError; use function dirname; -/** - * @coversDefaultClass \Pdp\TopLevelDomains - */ final class TopLevelDomainsTest extends TestCase { private static TopLevelDomains $topLevelDomains; @@ -23,12 +21,6 @@ public static function setUpBeforeClass(): void self::$topLevelDomains = TopLevelDomains::fromPath(dirname(__DIR__).'/test_data/tlds-alpha-by-domain.txt'); } - /** - * @covers ::fromPath - * @covers ::fromString - * @covers \Pdp\Stream - * @covers ::__construct - */ public function testCreateFromPath(): void { $context = stream_context_create([ @@ -43,10 +35,6 @@ public function testCreateFromPath(): void self::assertEquals(self::$topLevelDomains, $topLevelDomains); } - /** - * @covers ::fromPath - * @covers \Pdp\UnableToLoadTopLevelDomainList - */ public function testCreateFromPathThrowsException(): void { $this->expectException(UnableToLoadResource::class); @@ -54,9 +42,7 @@ public function testCreateFromPathThrowsException(): void TopLevelDomains::fromPath('/foo/bar.dat'); } - /** - * @dataProvider invalidContentProvider - */ + #[DataProvider('invalidContentProvider')] public function testConverterThrowsException(string $content): void { $this->expectException(UnableToLoadTopLevelDomainList::class); @@ -67,7 +53,7 @@ public function testConverterThrowsException(string $content): void /** * @return iterable */ - public function invalidContentProvider(): iterable + public static function invalidContentProvider(): iterable { $doubleHeader = <<> */ - public function validDomainProvider(): iterable + public static function validDomainProvider(): iterable { $resolvedDomain = ResolvedDomain::fromICANN(Domain::fromIDNA2008('www.example.com'), 1); @@ -278,7 +256,7 @@ public function testGetTopLevelDomainWithUnregisteredTLD(): void /** * @return iterable> */ - public function validTldProvider(): iterable + public static function validTldProvider(): iterable { return [ 'simple TLD' => ['COM'], @@ -304,7 +282,7 @@ public function __toString(): string /** * @return iterable> */ - public function invalidTldProvider(): iterable + public static function invalidTldProvider(): iterable { return [ 'invalid TLD (1)' => ['COMM'], From e606fec0cba5731dff42c42cb8e1d068b06b41eb Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Mon, 20 Feb 2023 17:16:45 +0100 Subject: [PATCH 05/10] Introduce RegisteredName class to allow iplike subdomain --- CHANGELOG.md | 18 +++ src/Domain.php | 266 ++++--------------------------- src/Host.php | 2 + src/RegisteredName.php | 353 +++++++++++++++++++++++++++++++++++++++++ src/ResolvedDomain.php | 2 +- 5 files changed, 408 insertions(+), 233 deletions(-) create mode 100644 src/RegisteredName.php diff --git a/CHANGELOG.md b/CHANGELOG.md index e011fd6..f2681c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will be documented in this file +## Next - TBD + +### Added + +- `RegisteredName` class to allow subdomain with IP-like labels [#347](https://github.com/jeremykendall/php-domain-parser/issues/347) + +### Fixed + +- Using PHPUnit 10 + +### Deprecated + +- None + +### Removed + +- None + ## 6.2.0 - 2022-10-30 ### Added diff --git a/src/Domain.php b/src/Domain.php index fa8319c..46760da 100644 --- a/src/Domain.php +++ b/src/Domain.php @@ -6,134 +6,32 @@ use Iterator; use Stringable; -use function array_count_values; -use function array_keys; -use function array_reverse; -use function array_slice; -use function array_unshift; -use function count; -use function explode; -use function filter_var; -use function implode; -use function in_array; -use function ksort; -use function preg_match; -use function rawurldecode; -use function strtolower; -use const FILTER_FLAG_IPV4; -use const FILTER_VALIDATE_IP; final class Domain implements DomainName { - private const IDNA_2003 = 'IDNA_2003'; - private const IDNA_2008 = 'IDNA_2008'; - private const REGEXP_IDN_PATTERN = '/[^\x20-\x7f]/'; - // Note that unreserved is purposely missing . as it is used to separate labels. - private const REGEXP_REGISTERED_NAME = '/(?(DEFINE) - (?[a-z0-9_~\-]) - (?[!$&\'()*+,;=]) - (?%[A-F0-9]{2}) - (?(?:(?&unreserved)|(?&sub_delims)|(?&encoded)){1,63}) - ) - ^(?:(?®_name)\.){0,126}(?®_name)\.?$/ix'; - private const REGEXP_URI_DELIMITERS = '/[:\/?#\[\]@ ]/'; - - /** @var array */ - private readonly array $labels; - private readonly ?string $domain; - - private function __construct(private string $type, DomainNameProvider|Host|Stringable|string|int|null $domain) + private function __construct(private RegisteredName $registeredName) { - $this->domain = $this->parseDomain($domain); - $this->labels = null === $this->domain ? [] : array_reverse(explode('.', $this->domain)); + if ($this->registeredName->isIpv4()) { + throw SyntaxError::dueToUnsupportedType($this->registeredName->toString()); + } } /** - * @param array{domain:string|null, type:string} $properties + * @param array{registeredName: RegisteredName} $properties */ public static function __set_state(array $properties): self { - return new self($properties['type'], $properties['domain']); + return new self($properties['registeredName']); } public static function fromIDNA2003(DomainNameProvider|Host|Stringable|string|int|null $domain): self { - return new self(self::IDNA_2003, $domain); + return new self(RegisteredName::fromIDNA2003($domain)); } public static function fromIDNA2008(DomainNameProvider|Host|Stringable|string|int|null $domain): self { - return new self(self::IDNA_2008, $domain); - } - - private function parseDomain(DomainNameProvider|Host|Stringable|string|int|null $domain): ?string - { - if ($domain instanceof DomainNameProvider) { - $domain = $domain->domain(); - } - - if ($domain instanceof Host) { - return $this->parseValue($domain->toUnicode()->value()); - } - - return $this->parseValue($domain); - } - - /** - * Parse and format the domain to ensure it is valid. - * Returns an array containing the formatted domain name labels - * and the domain transitional information. - * - * For example: parse('wWw.uLb.Ac.be') should return ['www.ulb.ac.be', ['be', 'ac', 'ulb', 'www']];. - * - * @throws SyntaxError If the host is not a domain - * @throws SyntaxError If the domain is not a host - */ - private function parseValue(Stringable|string|int|null $domain): ?string - { - if (null === $domain) { - return null; - } - - if ($domain instanceof Stringable) { - $domain = (string) $domain; - } - - $domain = (string) $domain; - if ('' === $domain) { - return ''; - } - - $res = filter_var($domain, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); - if (false !== $res) { - throw SyntaxError::dueToUnsupportedType($domain); - } - - $formattedDomain = rawurldecode($domain); - return match (true) { - 1 === preg_match(self::REGEXP_REGISTERED_NAME, $formattedDomain) => strtolower($formattedDomain), - // a domain name can not contain URI delimiters or space - 1 === preg_match(self::REGEXP_URI_DELIMITERS, $formattedDomain) => throw SyntaxError::dueToInvalidCharacters($domain), - // if the domain name does not contain UTF-8 chars then it is malformed - 1 !== preg_match(self::REGEXP_IDN_PATTERN, $formattedDomain) => throw SyntaxError::dueToMalformedValue($domain), - default => $this->domainToUnicode($this->domainToAscii($formattedDomain)), - }; - } - - private function domainToAscii(string $domain): string - { - return Idna::toAscii( - $domain, - self::IDNA_2003 === $this->type ? Idna::IDNA2003_ASCII : Idna::IDNA2008_ASCII - )->result(); - } - - private function domainToUnicode(string $domain): string - { - return Idna::toUnicode( - $domain, - self::IDNA_2003 === $this->type ? Idna::IDNA2003_UNICODE : Idna::IDNA2008_UNICODE - )->result(); + return new self(RegisteredName::fromIDNA2008($domain)); } /** @@ -141,41 +39,37 @@ private function domainToUnicode(string $domain): string */ public function getIterator(): Iterator { - yield from $this->labels; + yield from $this->registeredName; } public function isAscii(): bool { - return null === $this->domain || 1 !== preg_match(self::REGEXP_IDN_PATTERN, $this->domain); + return $this->registeredName->isAscii(); } public function jsonSerialize(): ?string { - return $this->domain; + return $this->registeredName->jsonSerialize(); } public function count(): int { - return count($this->labels); + return count($this->registeredName); } public function value(): ?string { - return $this->domain; + return $this->registeredName->value(); } public function toString(): string { - return (string) $this->domain; + return $this->registeredName->toString(); } public function label(int $key): ?string { - if ($key < 0) { - $key += count($this->labels); - } - - return $this->labels[$key] ?? null; + return $this->registeredName->label($key); } /** @@ -183,11 +77,7 @@ public function label(int $key): ?string */ public function keys(string $label = null): array { - if (null === $label) { - return array_keys($this->labels); - } - - return array_keys($this->labels, $label, true); + return $this->registeredName->keys($label); } /** @@ -195,61 +85,31 @@ public function keys(string $label = null): array */ public function labels(): array { - return $this->labels; + return $this->registeredName->labels(); } - public function toAscii(): self + public function isIpv4(): bool { - if (null === $this->domain) { - return $this; - } - - $domain = $this->domainToAscii($this->domain); - if ($domain === $this->domain) { - return $this; - } - - return new self($this->type, $domain); + return $this->registeredName->isIpv4(); } - public function toUnicode(): self + private function newInstance(RegisteredName $registeredName): self { - if (null === $this->domain) { + if ($registeredName->value() === $this->registeredName->value()) { return $this; } - $domain = $this->domainToUnicode($this->domain); - if ($domain === $this->domain) { - return $this; - } - - return new self($this->type, $domain); + return new self($registeredName); } - /** - * Filter a subdomain to update the domain part. - */ - private function normalize(DomainNameProvider|Host|Stringable|string|null $domain): ?string + public function toAscii(): self { - if ($domain instanceof DomainNameProvider) { - $domain = $domain->domain(); - } - - if ($domain instanceof Host) { - $domain = $domain->value(); - } - - if (null === $domain) { - return $domain; - } - - $domain = (string) $domain; + return $this->newInstance($this->registeredName->toAscii()); + } - return match (true) { - null === $this->domain => $domain, - $this->isAscii() => $this->domainToAscii($domain), - default => $this->domainToUnicode($domain), - }; + public function toUnicode(): self + { + return $this->newInstance($this->registeredName->toUnicode()); } /** @@ -257,7 +117,7 @@ private function normalize(DomainNameProvider|Host|Stringable|string|null $domai */ public function prepend(DomainNameProvider|Host|string|Stringable|null $label): self { - return $this->withLabel(count($this->labels), $label); + return $this->newInstance($this->registeredName->prepend($label)); } /** @@ -265,84 +125,26 @@ public function prepend(DomainNameProvider|Host|string|Stringable|null $label): */ public function append(DomainNameProvider|Host|string|Stringable|null $label): self { - return $this->withLabel(- count($this->labels) - 1, $label); + return $this->newInstance($this->registeredName->append($label)); } public function withLabel(int $key, DomainNameProvider|Host|string|Stringable|null $label): self { - $nbLabels = count($this->labels); - if ($key < - $nbLabels - 1 || $key > $nbLabels) { - throw SyntaxError::dueToInvalidLabelKey($this, $key); - } - - if (0 > $key) { - $key = $nbLabels + $key; - } - - $label = $this->normalize($label); - - if (($this->labels[$key] ?? null) === $label) { - return $this; - } - - $labels = $this->labels; - $labels[$key] = $label; - ksort($labels); - - return new self($this->type, implode('.', array_reverse($labels))); + return $this->newInstance($this->registeredName->withLabel($key, $label)); } public function withoutLabel(int $key, int ...$keys): self { - array_unshift($keys, $key); - $nbLabels = count($this->labels); - foreach ($keys as &$offset) { - if (- $nbLabels > $offset || $nbLabels - 1 < $offset) { - throw SyntaxError::dueToInvalidLabelKey($this, $key); - } - - if (0 > $offset) { - $offset += $nbLabels; - } - } - unset($offset); - - $deletedKeys = array_keys(array_count_values($keys)); - $labels = []; - foreach ($this->labels as $offset => $label) { - if (!in_array($offset, $deletedKeys, true)) { - $labels[] = $label; - } - } - - if ($labels === $this->labels) { - return $this; - } - - return new self($this->type, [] === $labels ? null : implode('.', array_reverse($labels))); + return $this->newInstance($this->registeredName->withoutLabel($key, ...$keys)); } public function clear(): self { - if (null === $this->domain) { - return $this; - } - - return new self($this->type, null); + return $this->newInstance($this->registeredName->clear()); } public function slice(int $offset, int $length = null): self { - $nbLabels = count($this->labels); - if ($offset < - $nbLabels || $offset > $nbLabels) { - throw SyntaxError::dueToInvalidLabelKey($this, $offset); - } - - $labels = array_slice($this->labels, $offset, $length, true); - if ($labels === $this->labels) { - return $this; - } - - return new self($this->type, [] === $labels ? null : implode('.', array_reverse($labels))); + return $this->newInstance($this->registeredName->slice($offset, $length)); } } diff --git a/src/Host.php b/src/Host.php index 87945e9..2ca7048 100644 --- a/src/Host.php +++ b/src/Host.php @@ -11,6 +11,8 @@ * @see https://tools.ietf.org/html/rfc1034#section-3.5 * @see https://tools.ietf.org/html/rfc1123#section-2.1 * @see https://tools.ietf.org/html/rfc5890 + * + * @method bool isIpv4() */ interface Host extends Countable, JsonSerializable { diff --git a/src/RegisteredName.php b/src/RegisteredName.php new file mode 100644 index 0000000..145de07 --- /dev/null +++ b/src/RegisteredName.php @@ -0,0 +1,353 @@ +[a-z0-9_~\-]) + (?[!$&\'()*+,;=]) + (?%[A-F0-9]{2}) + (?(?:(?&unreserved)|(?&sub_delims)|(?&encoded)){1,63}) + ) + ^(?:(?®_name)\.){0,126}(?®_name)\.?$/ix'; + private const REGEXP_URI_DELIMITERS = '/[:\/?#\[\]@ ]/'; + + /** @var array */ + private readonly array $labels; + private readonly ?string $domain; + + private function __construct(private string $type, DomainNameProvider|Host|Stringable|string|int|null $domain) + { + $this->domain = $this->parseDomain($domain); + $this->labels = null === $this->domain ? [] : array_reverse(explode('.', $this->domain)); + } + + /** + * @param array{domain:string|null, type:string} $properties + */ + public static function __set_state(array $properties): self + { + return new self($properties['type'], $properties['domain']); + } + + public static function fromIDNA2003(DomainNameProvider|Host|Stringable|string|int|null $domain): self + { + return new self(self::IDNA_2003, $domain); + } + + public static function fromIDNA2008(DomainNameProvider|Host|Stringable|string|int|null $domain): self + { + return new self(self::IDNA_2008, $domain); + } + + private function parseDomain(DomainNameProvider|Host|Stringable|string|int|null $domain): ?string + { + if ($domain instanceof DomainNameProvider) { + $domain = $domain->domain(); + } + + if ($domain instanceof Host) { + return $this->parseValue($domain->toUnicode()->value()); + } + + return $this->parseValue($domain); + } + + /** + * Parse and format the domain to ensure it is valid. + * Returns an array containing the formatted domain name labels + * and the domain transitional information. + * + * For example: parse('wWw.uLb.Ac.be') should return ['www.ulb.ac.be', ['be', 'ac', 'ulb', 'www']];. + * + * @throws SyntaxError If the host is not a domain + * @throws SyntaxError If the domain is not a host + */ + private function parseValue(Stringable|string|int|null $domain): ?string + { + if (null === $domain) { + return null; + } + + if ($domain instanceof Stringable) { + $domain = (string) $domain; + } + + $domain = (string) $domain; + if ('' === $domain) { + return ''; + } + + $res = filter_var($domain, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + if (false !== $res) { + return $res; + } + + $formattedDomain = rawurldecode($domain); + return match (true) { + 1 === preg_match(self::REGEXP_REGISTERED_NAME, $formattedDomain) => strtolower($formattedDomain), + // a domain name can not contain URI delimiters or space + 1 === preg_match(self::REGEXP_URI_DELIMITERS, $formattedDomain) => throw SyntaxError::dueToInvalidCharacters($domain), + // if the domain name does not contain UTF-8 chars then it is malformed + 1 !== preg_match(self::REGEXP_IDN_PATTERN, $formattedDomain) => throw SyntaxError::dueToMalformedValue($domain), + default => $this->domainToUnicode($this->domainToAscii($formattedDomain)), + }; + } + + private function domainToAscii(string $domain): string + { + return Idna::toAscii( + $domain, + self::IDNA_2003 === $this->type ? Idna::IDNA2003_ASCII : Idna::IDNA2008_ASCII + )->result(); + } + + private function domainToUnicode(string $domain): string + { + return Idna::toUnicode( + $domain, + self::IDNA_2003 === $this->type ? Idna::IDNA2003_UNICODE : Idna::IDNA2008_UNICODE + )->result(); + } + + public function isIpv4(): bool + { + return false !== filter_var($this->domain, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + } + + /** + * @return Iterator + */ + public function getIterator(): Iterator + { + yield from $this->labels; + } + + public function isAscii(): bool + { + return null === $this->domain || 1 !== preg_match(self::REGEXP_IDN_PATTERN, $this->domain); + } + + public function jsonSerialize(): ?string + { + return $this->domain; + } + + public function count(): int + { + return count($this->labels); + } + + public function value(): ?string + { + return $this->domain; + } + + public function toString(): string + { + return (string) $this->domain; + } + + public function label(int $key): ?string + { + if ($key < 0) { + $key += count($this->labels); + } + + return $this->labels[$key] ?? null; + } + + /** + * @return list + */ + public function keys(string $label = null): array + { + if (null === $label) { + return array_keys($this->labels); + } + + return array_keys($this->labels, $label, true); + } + + /** + * @return array + */ + public function labels(): array + { + return $this->labels; + } + + public function toAscii(): self + { + if (null === $this->domain) { + return $this; + } + + $domain = $this->domainToAscii($this->domain); + if ($domain === $this->domain) { + return $this; + } + + return new self($this->type, $domain); + } + + public function toUnicode(): self + { + if (null === $this->domain) { + return $this; + } + + $domain = $this->domainToUnicode($this->domain); + if ($domain === $this->domain) { + return $this; + } + + return new self($this->type, $domain); + } + + /** + * Filter a subdomain to update the domain part. + */ + private function normalize(DomainNameProvider|Host|Stringable|string|null $domain): ?string + { + if ($domain instanceof DomainNameProvider) { + $domain = $domain->domain(); + } + + if ($domain instanceof Host) { + $domain = $domain->value(); + } + + if (null === $domain) { + return $domain; + } + + $domain = (string) $domain; + + return match (true) { + null === $this->domain => $domain, + $this->isAscii() => $this->domainToAscii($domain), + default => $this->domainToUnicode($domain), + }; + } + + /** + * @throws CannotProcessHost + */ + public function prepend(DomainNameProvider|Host|string|Stringable|null $label): self + { + return $this->withLabel(count($this->labels), $label); + } + + /** + * @throws CannotProcessHost + */ + public function append(DomainNameProvider|Host|string|Stringable|null $label): self + { + return $this->withLabel(- count($this->labels) - 1, $label); + } + + public function withLabel(int $key, DomainNameProvider|Host|string|Stringable|null $label): self + { + $nbLabels = count($this->labels); + if ($key < - $nbLabels - 1 || $key > $nbLabels) { + throw SyntaxError::dueToInvalidLabelKey($this, $key); + } + + if (0 > $key) { + $key = $nbLabels + $key; + } + + $label = $this->normalize($label); + + if (($this->labels[$key] ?? null) === $label) { + return $this; + } + + $labels = $this->labels; + $labels[$key] = $label; + ksort($labels); + + return new self($this->type, implode('.', array_reverse($labels))); + } + + public function withoutLabel(int $key, int ...$keys): self + { + array_unshift($keys, $key); + $nbLabels = count($this->labels); + foreach ($keys as &$offset) { + if (- $nbLabels > $offset || $nbLabels - 1 < $offset) { + throw SyntaxError::dueToInvalidLabelKey($this, $key); + } + + if (0 > $offset) { + $offset += $nbLabels; + } + } + unset($offset); + + $deletedKeys = array_keys(array_count_values($keys)); + $labels = []; + foreach ($this->labels as $offset => $label) { + if (!in_array($offset, $deletedKeys, true)) { + $labels[] = $label; + } + } + + if ($labels === $this->labels) { + return $this; + } + + return new self($this->type, [] === $labels ? null : implode('.', array_reverse($labels))); + } + + public function clear(): self + { + if (null === $this->domain) { + return $this; + } + + return new self($this->type, null); + } + + public function slice(int $offset, int $length = null): self + { + $nbLabels = count($this->labels); + if ($offset < - $nbLabels || $offset > $nbLabels) { + throw SyntaxError::dueToInvalidLabelKey($this, $offset); + } + + $labels = array_slice($this->labels, $offset, $length, true); + if ($labels === $this->labels) { + return $this; + } + + return new self($this->type, [] === $labels ? null : implode('.', array_reverse($labels))); + } +} diff --git a/src/ResolvedDomain.php b/src/ResolvedDomain.php index 870f62a..3849b6d 100644 --- a/src/ResolvedDomain.php +++ b/src/ResolvedDomain.php @@ -102,7 +102,7 @@ private function parse(): array return [ 'registrableDomain' => $this->domain->slice(0, $length + 1), 'secondLevelDomain' => $this->domain->slice($length, 1), - 'subDomain' => $this->domain->slice($length + 1), + 'subDomain' => RegisteredName::fromIDNA2008($this->domain->value())->slice($length + 1), ]; } From fd11f5783e0b23ed72b6c823d376d054542a0f7a Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Mon, 20 Feb 2023 21:54:09 +0100 Subject: [PATCH 06/10] remove iterator_to_array usage --- src/DomainTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DomainTest.php b/src/DomainTest.php index 076baee..1b3e357 100644 --- a/src/DomainTest.php +++ b/src/DomainTest.php @@ -45,7 +45,7 @@ public function testDomainInternalPhpMethod(): void /** @var Domain $generateDomain */ $generateDomain = eval('return '.var_export($domain, true).';'); self::assertEquals($domain, $generateDomain); - self::assertSame(['be', 'ac', 'ulb', 'www'], iterator_to_array($domain)); + self::assertSame(['be', 'ac', 'ulb', 'www'], [...$domain]); self::assertEquals('"www.ulb.ac.be"', json_encode($domain)); self::assertSame('www.ulb.ac.be', $domain->toString()); } @@ -58,7 +58,7 @@ public function testCountable(?string $domain, int $nbLabels, array $labels): vo { $domain = Domain::fromIDNA2008($domain); self::assertCount($nbLabels, $domain); - self::assertSame($labels, iterator_to_array($domain)); + self::assertSame($labels, [...$domain]); } /** From e9eb70ff3e9de356270bffbf147f72de6a6c417b Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Tue, 21 Feb 2023 06:17:52 +0100 Subject: [PATCH 07/10] Improve internal codebase --- CHANGELOG.md | 3 ++- composer.json | 4 ++-- src/Domain.php | 13 +++++-------- src/DomainName.php | 2 +- src/Host.php | 2 -- src/RegisteredName.php | 35 ++++++++++++++++++++++++----------- 6 files changed, 34 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2681c2..17449e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,12 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will ### Added -- `RegisteredName` class to allow subdomain with IP-like labels [#347](https://github.com/jeremykendall/php-domain-parser/issues/347) +- `RegisteredName` class to allow subdomain with IP-like labels ### Fixed - Using PHPUnit 10 +- `Domain` decorates the new `RegisteredName` class [#347](https://github.com/jeremykendall/php-domain-parser/issues/347) ### Deprecated diff --git a/composer.json b/composer.json index b3f7a19..27de40d 100644 --- a/composer.json +++ b/composer.json @@ -49,10 +49,10 @@ "friendsofphp/php-cs-fixer": "^v3.13.2", "guzzlehttp/guzzle": "^7.5", "guzzlehttp/psr7": "^1.6 || ^2.4.3", - "phpstan/phpstan": "^1.9.17", + "phpstan/phpstan": "^1.9.18", "phpstan/phpstan-phpunit": "^1.3.4", "phpstan/phpstan-strict-rules": "^1.4.5", - "phpunit/phpunit": "^10.0.7", + "phpunit/phpunit": "^10.0.11", "psr/http-factory": "^1.0.1", "psr/simple-cache": "^1.0.1", "symfony/cache": "^v5.0.0 || ^v6.0.0" diff --git a/src/Domain.php b/src/Domain.php index 46760da..cc7a5b7 100644 --- a/src/Domain.php +++ b/src/Domain.php @@ -6,12 +6,14 @@ use Iterator; use Stringable; +use const FILTER_FLAG_IPV4; +use const FILTER_VALIDATE_IP; final class Domain implements DomainName { private function __construct(private RegisteredName $registeredName) { - if ($this->registeredName->isIpv4()) { + if (false !== filter_var($this->registeredName->value(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { throw SyntaxError::dueToUnsupportedType($this->registeredName->toString()); } } @@ -88,11 +90,6 @@ public function labels(): array return $this->registeredName->labels(); } - public function isIpv4(): bool - { - return $this->registeredName->isIpv4(); - } - private function newInstance(RegisteredName $registeredName): self { if ($registeredName->value() === $this->registeredName->value()) { @@ -133,9 +130,9 @@ public function withLabel(int $key, DomainNameProvider|Host|string|Stringable|nu return $this->newInstance($this->registeredName->withLabel($key, $label)); } - public function withoutLabel(int $key, int ...$keys): self + public function withoutLabel(int ...$keys): self { - return $this->newInstance($this->registeredName->withoutLabel($key, ...$keys)); + return $this->newInstance($this->registeredName->withoutLabel(...$keys)); } public function clear(): self diff --git a/src/DomainName.php b/src/DomainName.php index fc407c3..c98c063 100644 --- a/src/DomainName.php +++ b/src/DomainName.php @@ -101,7 +101,7 @@ public function withLabel(int $key, DomainNameProvider|Host|string|Stringable|nu * * @throws CannotProcessHost If the key is out of bounds */ - public function withoutLabel(int $key, int ...$keys): self; + public function withoutLabel(int ...$keys): self; /** * Returns an instance with all labels removed. diff --git a/src/Host.php b/src/Host.php index 2ca7048..87945e9 100644 --- a/src/Host.php +++ b/src/Host.php @@ -11,8 +11,6 @@ * @see https://tools.ietf.org/html/rfc1034#section-3.5 * @see https://tools.ietf.org/html/rfc1123#section-2.1 * @see https://tools.ietf.org/html/rfc5890 - * - * @method bool isIpv4() */ interface Host extends Countable, JsonSerializable { diff --git a/src/RegisteredName.php b/src/RegisteredName.php index 145de07..3313519 100644 --- a/src/RegisteredName.php +++ b/src/RegisteredName.php @@ -10,7 +10,6 @@ use function array_keys; use function array_reverse; use function array_slice; -use function array_unshift; use function count; use function explode; use function filter_var; @@ -42,7 +41,10 @@ final class RegisteredName implements DomainName private readonly array $labels; private readonly ?string $domain; - private function __construct(private string $type, DomainNameProvider|Host|Stringable|string|int|null $domain) + /** + * @throws CannotProcessHost + */ + private function __construct(private readonly string $type, DomainNameProvider|Host|Stringable|string|int|null $domain) { $this->domain = $this->parseDomain($domain); $this->labels = null === $this->domain ? [] : array_reverse(explode('.', $this->domain)); @@ -50,22 +52,33 @@ private function __construct(private string $type, DomainNameProvider|Host|Strin /** * @param array{domain:string|null, type:string} $properties + * + * @throws CannotProcessHost */ public static function __set_state(array $properties): self { return new self($properties['type'], $properties['domain']); } + /** + * @throws CannotProcessHost + */ public static function fromIDNA2003(DomainNameProvider|Host|Stringable|string|int|null $domain): self { return new self(self::IDNA_2003, $domain); } + /** + * @throws CannotProcessHost + */ public static function fromIDNA2008(DomainNameProvider|Host|Stringable|string|int|null $domain): self { return new self(self::IDNA_2008, $domain); } + /** + * @throws CannotProcessHost + */ private function parseDomain(DomainNameProvider|Host|Stringable|string|int|null $domain): ?string { if ($domain instanceof DomainNameProvider) { @@ -136,11 +149,6 @@ private function domainToUnicode(string $domain): string )->result(); } - public function isIpv4(): bool - { - return false !== filter_var($this->domain, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); - } - /** * @return Iterator */ @@ -245,7 +253,7 @@ private function normalize(DomainNameProvider|Host|Stringable|string|null $domai } if (null === $domain) { - return $domain; + return null; } $domain = (string) $domain; @@ -297,13 +305,12 @@ public function withLabel(int $key, DomainNameProvider|Host|string|Stringable|nu return new self($this->type, implode('.', array_reverse($labels))); } - public function withoutLabel(int $key, int ...$keys): self + public function withoutLabel(int ...$keys): self { - array_unshift($keys, $key); $nbLabels = count($this->labels); foreach ($keys as &$offset) { if (- $nbLabels > $offset || $nbLabels - 1 < $offset) { - throw SyntaxError::dueToInvalidLabelKey($this, $key); + throw SyntaxError::dueToInvalidLabelKey($this, $offset); } if (0 > $offset) { @@ -327,6 +334,9 @@ public function withoutLabel(int $key, int ...$keys): self return new self($this->type, [] === $labels ? null : implode('.', array_reverse($labels))); } + /** + * @throws CannotProcessHost + */ public function clear(): self { if (null === $this->domain) { @@ -336,6 +346,9 @@ public function clear(): self return new self($this->type, null); } + /** + * @throws CannotProcessHost + */ public function slice(int $offset, int $length = null): self { $nbLabels = count($this->labels); From 313072375ae0238f4bf61e484a998e8076285f63 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Tue, 21 Feb 2023 06:26:34 +0100 Subject: [PATCH 08/10] Improve internal codebase --- CHANGELOG.md | 1 + src/DomainTest.php | 7 +++++++ src/RegisteredName.php | 14 +++++--------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17449e6..656cd74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - Using PHPUnit 10 - `Domain` decorates the new `RegisteredName` class [#347](https://github.com/jeremykendall/php-domain-parser/issues/347) +- `Host::withoutLabel` works without arguments. ### Deprecated diff --git a/src/DomainTest.php b/src/DomainTest.php index 1b3e357..3ea5315 100644 --- a/src/DomainTest.php +++ b/src/DomainTest.php @@ -395,6 +395,13 @@ public function testwithoutLabelWorksWithMultipleKeys(): void self::assertNull(Domain::fromIDNA2008('www.example.com')->withoutLabel(0, 1, 2)->value()); } + public function testWithoutLabelWorksWithoutArgument(): void + { + $domain = Domain::fromIDNA2008('www.example.com'); + + self::assertSame($domain, $domain->withoutLabel()); + } + #[DataProvider('resolveCustomIDNAOptionsProvider')] public function testResolveWorksWithCustomIDNAOptions( string $domainName, diff --git a/src/RegisteredName.php b/src/RegisteredName.php index 3313519..64602dd 100644 --- a/src/RegisteredName.php +++ b/src/RegisteredName.php @@ -81,15 +81,11 @@ public static function fromIDNA2008(DomainNameProvider|Host|Stringable|string|in */ private function parseDomain(DomainNameProvider|Host|Stringable|string|int|null $domain): ?string { - if ($domain instanceof DomainNameProvider) { - $domain = $domain->domain(); - } - - if ($domain instanceof Host) { - return $this->parseValue($domain->toUnicode()->value()); - } - - return $this->parseValue($domain); + return $this->parseValue(match (true) { + $domain instanceof DomainNameProvider => $domain->domain()->value(), + $domain instanceof Host => $domain->toUnicode()->value(), + default => $domain, + }); } /** From 1f28803b98e6a5f9d16c37502ae7dd6fc075a714 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Tue, 21 Feb 2023 17:40:01 +0100 Subject: [PATCH 09/10] Use RegisteredName to improve ResolvedDomain --- src/Domain.php | 8 ++--- src/DomainName.php | 12 +++---- src/RegisteredName.php | 12 +++---- src/ResolvedDomain.php | 70 ++++++++++++++++++++++---------------- src/ResolvedDomainName.php | 6 ++-- src/ResolvedDomainTest.php | 16 +++++++++ src/Rules.php | 23 +++++++------ src/Suffix.php | 17 ++++----- src/SuffixTest.php | 1 + 9 files changed, 99 insertions(+), 66 deletions(-) diff --git a/src/Domain.php b/src/Domain.php index cc7a5b7..5fc535e 100644 --- a/src/Domain.php +++ b/src/Domain.php @@ -83,7 +83,7 @@ public function keys(string $label = null): array } /** - * @return array + * @return list */ public function labels(): array { @@ -112,7 +112,7 @@ public function toUnicode(): self /** * @throws CannotProcessHost */ - public function prepend(DomainNameProvider|Host|string|Stringable|null $label): self + public function prepend(DomainNameProvider|Host|Stringable|string|int|null $label): self { return $this->newInstance($this->registeredName->prepend($label)); } @@ -120,12 +120,12 @@ public function prepend(DomainNameProvider|Host|string|Stringable|null $label): /** * @throws CannotProcessHost */ - public function append(DomainNameProvider|Host|string|Stringable|null $label): self + public function append(DomainNameProvider|Host|Stringable|string|int|null $label): self { return $this->newInstance($this->registeredName->append($label)); } - public function withLabel(int $key, DomainNameProvider|Host|string|Stringable|null $label): self + public function withLabel(int $key, DomainNameProvider|Host|Stringable|string|int|null $label): self { return $this->newInstance($this->registeredName->withLabel($key, $label)); } diff --git a/src/DomainName.php b/src/DomainName.php index c98c063..c4a4aaf 100644 --- a/src/DomainName.php +++ b/src/DomainName.php @@ -37,7 +37,7 @@ public function label(int $key): ?string; /** * Returns the object labels. * - * @return array + * @return list */ public function labels(): array; @@ -47,7 +47,7 @@ public function labels(): array; * If a value is specified only the keys associated with * the given value will be returned * - * @return array + * @return list */ public function keys(string $label = null): array; @@ -64,14 +64,14 @@ public function getIterator(): Iterator; * * @see ::withLabel */ - public function prepend(DomainNameProvider|Host|string|Stringable|null $label): self; + public function prepend(DomainNameProvider|Host|Stringable|string|int|null $label): self; /** * Appends a label to the domain. * * @see ::withLabel */ - public function append(DomainNameProvider|Host|string|Stringable|null $label): self; + public function append(DomainNameProvider|Host|Stringable|string|int|null $label): self; /** * Returns an instance with the specified label added at the specified key. @@ -82,12 +82,12 @@ public function append(DomainNameProvider|Host|string|Stringable|null $label): s * If $key is non-negative, the added label will be the label at $key position from the start. * If $key is negative, the added label will be the label at $key position from the end. * - * @param string|null|Stringable|Host|DomainNameProvider $label a domain label + * @param DomainNameProvider|Host|Stringable|string|int|null $label a domain label * * @throws CannotProcessHost If the key is out of bounds * @throws CannotProcessHost If the label is converted to the NULL value */ - public function withLabel(int $key, DomainNameProvider|Host|string|Stringable|null $label): self; + public function withLabel(int $key, DomainNameProvider|Host|Stringable|string|int|null $label): self; /** * Returns an instance with the label at the specified key removed. diff --git a/src/RegisteredName.php b/src/RegisteredName.php index 64602dd..4350932 100644 --- a/src/RegisteredName.php +++ b/src/RegisteredName.php @@ -37,7 +37,7 @@ final class RegisteredName implements DomainName ^(?:(?®_name)\.){0,126}(?®_name)\.?$/ix'; private const REGEXP_URI_DELIMITERS = '/[:\/?#\[\]@ ]/'; - /** @var array */ + /** @var list */ private readonly array $labels; private readonly ?string $domain; @@ -200,7 +200,7 @@ public function keys(string $label = null): array } /** - * @return array + * @return list */ public function labels(): array { @@ -238,7 +238,7 @@ public function toUnicode(): self /** * Filter a subdomain to update the domain part. */ - private function normalize(DomainNameProvider|Host|Stringable|string|null $domain): ?string + private function normalize(DomainNameProvider|Host|Stringable|string|int|null $domain): ?string { if ($domain instanceof DomainNameProvider) { $domain = $domain->domain(); @@ -264,7 +264,7 @@ private function normalize(DomainNameProvider|Host|Stringable|string|null $domai /** * @throws CannotProcessHost */ - public function prepend(DomainNameProvider|Host|string|Stringable|null $label): self + public function prepend(DomainNameProvider|Host|Stringable|string|int|null $label): self { return $this->withLabel(count($this->labels), $label); } @@ -272,12 +272,12 @@ public function prepend(DomainNameProvider|Host|string|Stringable|null $label): /** * @throws CannotProcessHost */ - public function append(DomainNameProvider|Host|string|Stringable|null $label): self + public function append(DomainNameProvider|Host|Stringable|string|int|null $label): self { return $this->withLabel(- count($this->labels) - 1, $label); } - public function withLabel(int $key, DomainNameProvider|Host|string|Stringable|null $label): self + public function withLabel(int $key, DomainNameProvider|Host|Stringable|string|int|null $label): self { $nbLabels = count($this->labels); if ($key < - $nbLabels - 1 || $key > $nbLabels) { diff --git a/src/ResolvedDomain.php b/src/ResolvedDomain.php index 3849b6d..1ead1f4 100644 --- a/src/ResolvedDomain.php +++ b/src/ResolvedDomain.php @@ -13,6 +13,9 @@ final class ResolvedDomain implements ResolvedDomainName private readonly DomainName $registrableDomain; private readonly DomainName $subDomain; + /** + * @throws CannotProcessHost + */ private function __construct( private readonly DomainName $domain, private readonly EffectiveTopLevelDomain $suffix @@ -24,28 +27,40 @@ private function __construct( ] = $this->parse(); } - public static function fromICANN(int|DomainNameProvider|Host|string|Stringable|null $domain, int $suffixLength): self + /** + * @throws CannotProcessHost + */ + public static function fromICANN(DomainNameProvider|Host|Stringable|string|int|null $domain, int $suffixLength): self { $domain = self::setDomainName($domain); return new self($domain, Suffix::fromICANN($domain->slice(0, $suffixLength))); } - public static function fromPrivate(int|DomainNameProvider|Host|string|Stringable|null $domain, int $suffixLength): self + /** + * @throws CannotProcessHost + */ + public static function fromPrivate(DomainNameProvider|Host|Stringable|string|int|null $domain, int $suffixLength): self { $domain = self::setDomainName($domain); return new self($domain, Suffix::fromPrivate($domain->slice(0, $suffixLength))); } - public static function fromIANA(int|DomainNameProvider|Host|string|Stringable|null $domain): self + /** + * @throws CannotProcessHost + */ + public static function fromIANA(DomainNameProvider|Host|Stringable|string|int|null $domain): self { $domain = self::setDomainName($domain); return new self($domain, Suffix::fromIANA($domain->label(0))); } - public static function fromUnknown(int|DomainNameProvider|Host|string|Stringable|null $domain, int $suffixLength = 0): self + /** + * @throws CannotProcessHost + */ + public static function fromUnknown(DomainNameProvider|Host|Stringable|string|int|null $domain, int $suffixLength = 0): self { $domain = self::setDomainName($domain); @@ -60,19 +75,19 @@ public static function __set_state(array $properties): self return new self($properties['domain'], $properties['suffix']); } - private static function setDomainName(int|DomainNameProvider|Host|string|Stringable|null $domain): DomainName + private static function setDomainName(DomainNameProvider|Host|Stringable|string|int|null $domain): DomainName { return match (true) { $domain instanceof DomainNameProvider => $domain->domain(), $domain instanceof DomainName => $domain, - default => Domain::fromIDNA2008($domain), + default => RegisteredName::fromIDNA2008($domain), }; } /** * Make sure the Value Object is always in a valid state. * - * @throws UnableToResolveDomain If the suffix can not be attached to the domain + * @throws UnableToResolveDomain|CannotProcessHost If the suffix can not be attached to the domain * * @return array{registrableDomain: DomainName, secondLevelDomain: DomainName, subDomain: DomainName} */ @@ -116,39 +131,39 @@ public function domain(): DomainName return $this->domain; } - public function jsonSerialize(): ?string + public function registrableDomain(): DomainName { - return $this->domain->jsonSerialize(); + return $this->registrableDomain; } - public function value(): ?string + public function secondLevelDomain(): DomainName { - return $this->domain->value(); + return $this->secondLevelDomain; } - public function toString(): string + public function subDomain(): DomainName { - return $this->domain->toString(); + return $this->subDomain; } - public function registrableDomain(): DomainName + public function suffix(): EffectiveTopLevelDomain { - return $this->registrableDomain; + return $this->suffix; } - public function secondLevelDomain(): DomainName + public function jsonSerialize(): ?string { - return $this->secondLevelDomain; + return $this->domain->jsonSerialize(); } - public function subDomain(): DomainName + public function value(): ?string { - return $this->subDomain; + return $this->domain->value(); } - public function suffix(): EffectiveTopLevelDomain + public function toString(): string { - return $this->suffix; + return $this->domain->toString(); } public function toAscii(): self @@ -161,7 +176,7 @@ public function toUnicode(): self return new self($this->domain->toUnicode(), $this->suffix->toUnicode()); } - public function withSuffix(int|DomainNameProvider|Host|string|Stringable|null $suffix): self + public function withSuffix(DomainNameProvider|Host|Stringable|string|int|null $suffix): self { if (!$suffix instanceof EffectiveTopLevelDomain) { $suffix = Suffix::fromUnknown($suffix); @@ -173,13 +188,13 @@ public function withSuffix(int|DomainNameProvider|Host|string|Stringable|null $s ); } - public function withSubDomain(int|DomainNameProvider|Host|string|Stringable|null $subDomain): self + public function withSubDomain(DomainNameProvider|Host|Stringable|string|int|null $subDomain): self { if (null === $this->suffix->value()) { throw UnableToResolveDomain::dueToMissingRegistrableDomain($this->domain); } - $subDomain = $this->domain->clear()->append(self::setDomainName($subDomain)); + $subDomain = RegisteredName::fromIDNA2008($subDomain); if ($this->subDomain->value() === $subDomain->value()) { return $this; } @@ -187,16 +202,13 @@ public function withSubDomain(int|DomainNameProvider|Host|string|Stringable|null return new self($this->registrableDomain->prepend($subDomain), $this->suffix); } - /** - * @param int|DomainNameProvider|Host|string|Stringable|null $label the second level domain - */ - public function withSecondLevelDomain($label): self + public function withSecondLevelDomain(DomainNameProvider|Host|Stringable|string|int|null $label): self { if (null === $this->suffix->value()) { throw UnableToResolveDomain::dueToMissingRegistrableDomain($this->domain); } - $label = self::setDomainName($label); + $label = RegisteredName::fromIDNA2008($label); if (1 !== count($label)) { throw UnableToResolveDomain::dueToInvalidSecondLevelDomain($label); } diff --git a/src/ResolvedDomainName.php b/src/ResolvedDomainName.php index 631d347..33d606f 100644 --- a/src/ResolvedDomainName.php +++ b/src/ResolvedDomainName.php @@ -22,15 +22,15 @@ public function secondLevelDomain(): DomainName; public function registrableDomain(): DomainName; /** - * Returns the sub domain component. + * Returns the subdomain component. */ public function subDomain(): DomainName; /** - * Returns an instance with the specified sub domain added. + * Returns an instance with the specified subdomain added. * * This method MUST retain the state of the current instance, and return - * an instance that contains the new sub domain + * an instance that contains the new subdomain * * @throws CannotProcessHost If the Sub domain can not be added to the current Domain */ diff --git a/src/ResolvedDomainTest.php b/src/ResolvedDomainTest.php index 0fb4ca1..e6f15f2 100644 --- a/src/ResolvedDomainTest.php +++ b/src/ResolvedDomainTest.php @@ -553,4 +553,20 @@ public function testItReturnsTheInstanceWhenTheSLDIsEqual(): void self::assertEquals($domain->withSecondLevelDomain('ulb'), $domain); } + + public function testSubDomainCanHandleIpLikeValue(): void + { + self::assertSame( + '1.1.1.1.cloudflare-dns.com', + ResolvedDomain::fromICANN('cloudflare-dns.com', 1)->withSubDomain('1.1.1.1')->toString() + ); + } + + public function testSuffixCanHandleIpLikeValue(): void + { + self::assertSame( + 'cloudflare-dns.com.1.1.1.1', + ResolvedDomain::fromICANN('cloudflare-dns.com.1.1.1.1', 4)->toString() + ); + } } diff --git a/src/Rules.php b/src/Rules.php index 7c49cd9..6c26b49 100644 --- a/src/Rules.php +++ b/src/Rules.php @@ -4,18 +4,19 @@ namespace Pdp; +use SplFileObject; use SplTempFileObject; use Stringable; use function array_pop; use function explode; use function preg_match; -use function strpos; use function substr; final class Rules implements PublicSuffixList { private const ICANN_DOMAINS = 'ICANN_DOMAINS'; private const PRIVATE_DOMAINS = 'PRIVATE_DOMAINS'; + private const UNKNOWN_DOMAINS = 'UNKNOWN_DOMAINS'; private const REGEX_PSL_SECTION = ',^// ===(?BEGIN|END) (?ICANN|PRIVATE) DOMAINS===,'; private const PSL_SECTION = [ @@ -42,7 +43,7 @@ private function __construct(private readonly array $rules) * @param null|resource $context * * @throws UnableToLoadResource If the rules can not be loaded from the path - * @throws UnableToLoadPublicSuffixList If the rules contains in the resource are invalid + * @throws UnableToLoadPublicSuffixList If the rules contain in the resource are invalid */ public static function fromPath(string $path, $context = null): self { @@ -52,7 +53,7 @@ public static function fromPath(string $path, $context = null): self /** * Returns a new instance from a string. * - * @throws UnableToLoadPublicSuffixList If the rules contains in the resource are invalid + * @throws UnableToLoadPublicSuffixList If the rules contain in the resource are invalid */ public static function fromString(Stringable|string $content): self { @@ -70,11 +71,11 @@ private static function parse(string $content): array $section = ''; $file = new SplTempFileObject(); $file->fwrite($content); - $file->setFlags(SplTempFileObject::DROP_NEW_LINE | SplTempFileObject::READ_AHEAD | SplTempFileObject::SKIP_EMPTY); + $file->setFlags(SplFileObject::DROP_NEW_LINE | SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY); /** @var string $line */ foreach ($file as $line) { $section = self::getSection($section, $line); - if (in_array($section, [self::PRIVATE_DOMAINS, self::ICANN_DOMAINS], true) && false === strpos($line, '//')) { + if (in_array($section, [self::PRIVATE_DOMAINS, self::ICANN_DOMAINS], true) && !str_contains($line, '//')) { $rules[$section] = self::addRule($rules[$section], explode('.', $line)); } } @@ -159,7 +160,7 @@ public function resolve($host): ResolvedDomainName return $this->getCookieDomain($host); } catch (UnableToResolveDomain $exception) { return ResolvedDomain::fromUnknown($exception->domain()); - } catch (SyntaxError $exception) { + } catch (SyntaxError) { return ResolvedDomain::fromUnknown(Domain::fromIDNA2008(null)); } } @@ -170,7 +171,7 @@ public function resolve($host): ResolvedDomainName public function getCookieDomain($host): ResolvedDomainName { $domain = $this->validateDomain($host); - [$suffixLength, $section] = $this->resolveSuffix($domain, ''); + [$suffixLength, $section] = $this->resolveSuffix($domain, self::UNKNOWN_DOMAINS); return match (true) { self::ICANN_DOMAINS === $section => ResolvedDomain::fromICANN($domain, $suffixLength), @@ -231,15 +232,17 @@ private function validateDomain(int|DomainNameProvider|Host|string|Stringable|nu } /** - * Returns the length and the section of thhe resolved effective top level domain. + * Returns the length and the section of the resolved effective top level domain. * - * @return array{0: int, 1:string} + * @param Rules::UNKNOWN_DOMAINS|Rules::ICANN_DOMAINS|Rules::PRIVATE_DOMAINS $section + * + * @return array{0: int, 1:Rules::UNKNOWN_DOMAINS|Rules::ICANN_DOMAINS|Rules::PRIVATE_DOMAINS} */ private function resolveSuffix(DomainName $domain, string $section): array { $icannSuffixLength = $this->getPublicSuffixLengthFromSection($domain, self::ICANN_DOMAINS); if (1 > $icannSuffixLength) { - return [1, '']; + return [1, self::UNKNOWN_DOMAINS]; } if (self::ICANN_DOMAINS === $section) { diff --git a/src/Suffix.php b/src/Suffix.php index 9f073b7..4e0b8d8 100644 --- a/src/Suffix.php +++ b/src/Suffix.php @@ -13,6 +13,7 @@ final class Suffix implements EffectiveTopLevelDomain private const ICANN = 'ICANN'; private const PRIVATE = 'PRIVATE'; private const IANA = 'IANA'; + private const UNKNOWN = 'UNKNOWN'; private function __construct( private readonly DomainName $domain, @@ -21,14 +22,14 @@ private function __construct( } /** - * @param array{domain:DomainName, section:string} $properties + * @param array{domain:DomainName, section:Suffix::ICANN|Suffix::PRIVATE|Suffix::IANA|Suffix::UNKNOWN} $properties */ public static function __set_state(array $properties): self { return new self($properties['domain'], $properties['section']); } - public static function fromICANN(int|DomainNameProvider|Host|string|Stringable|null $domain): self + public static function fromICANN(DomainNameProvider|Host|Stringable|string|int|null $domain): self { $domain = self::setDomainName($domain); if (1 > count($domain)) { @@ -38,7 +39,7 @@ public static function fromICANN(int|DomainNameProvider|Host|string|Stringable|n return new self($domain, self::ICANN); } - public static function fromPrivate(int|DomainNameProvider|Host|string|Stringable|null $domain): self + public static function fromPrivate(DomainNameProvider|Host|Stringable|string|int|null $domain): self { $domain = self::setDomainName($domain); if (1 > count($domain)) { @@ -48,7 +49,7 @@ public static function fromPrivate(int|DomainNameProvider|Host|string|Stringable return new self($domain, self::PRIVATE); } - public static function fromIANA(int|DomainNameProvider|Host|string|Stringable|null $domain): self + public static function fromIANA(DomainNameProvider|Host|Stringable|string|int|null $domain): self { $domain = self::setDomainName($domain); if (1 !== count($domain)) { @@ -58,9 +59,9 @@ public static function fromIANA(int|DomainNameProvider|Host|string|Stringable|nu return new self($domain, self::IANA); } - public static function fromUnknown(int|DomainNameProvider|Host|string|Stringable|null $domain): self + public static function fromUnknown(DomainNameProvider|Host|Stringable|string|int|null $domain): self { - return new self(self::setDomainName($domain), ''); + return new self(self::setDomainName($domain), self::UNKNOWN); } private static function setDomainName(int|DomainNameProvider|Host|string|Stringable|null $domain): DomainName @@ -70,7 +71,7 @@ private static function setDomainName(int|DomainNameProvider|Host|string|Stringa } if (!$domain instanceof DomainName) { - $domain = Domain::fromIDNA2008($domain); + $domain = RegisteredName::fromIDNA2008($domain); } if ('' === $domain->label(0)) { @@ -82,7 +83,7 @@ private static function setDomainName(int|DomainNameProvider|Host|string|Stringa public function isKnown(): bool { - return '' !== $this->section; + return self::UNKNOWN !== $this->section; } public function isIANA(): bool diff --git a/src/SuffixTest.php b/src/SuffixTest.php index ca728c8..b3e6d56 100644 --- a/src/SuffixTest.php +++ b/src/SuffixTest.php @@ -144,6 +144,7 @@ public static function countableProvider(): iterable 'null' => [null, 0, []], 'simple' => ['foo.bar.baz', 3, ['baz', 'bar', 'foo']], 'unicode' => ['www.食狮.公司.cn', 4, ['cn', '公司', '食狮', 'www']], + 'ipv4 like' => ['1.2.3.4', 4, ['1', '2', '3', '4']], ]; } } From df277d68c46f26b90c9e52dc4f25fe25b3e019d5 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sat, 25 Feb 2023 17:24:22 +0100 Subject: [PATCH 10/10] Prepare 6.3.0 release --- CHANGELOG.md | 52 ++++++++++++++++++++++++++++-------------- composer.json | 8 +++---- src/Domain.php | 17 +++++++++++++- src/ResolvedDomain.php | 14 ++++++++++++ src/Suffix.php | 18 +++++++++++++++ 5 files changed, 87 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 656cd74..965c392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,11 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will be documented in this file -## Next - TBD +## [6.3.0] - 2023-02-25 ### Added -- `RegisteredName` class to allow subdomain with IP-like labels +- `RegisteredName` class to allow domain with IP4-like labels ### Fixed @@ -22,7 +22,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 6.2.0 - 2022-10-30 +## [6.2.0] - 2022-10-30 ### Added @@ -40,7 +40,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - PHP7 and PHP8.0 support -## 6.1.2 - 2022-09-29 +## [6.1.2] - 2022-09-29 ### Added @@ -58,7 +58,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 6.1.1 - 2022-02-18 +## [6.1.1] - 2022-02-18 ### Added @@ -76,7 +76,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 6.1.0 - 2021-06-19 +## [6.1.0] - 2021-06-19 ### Added @@ -99,7 +99,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 6.0.0 - 2020-12-13 +## [6.0.0] - 2020-12-13 ### Added @@ -150,7 +150,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - `Pdp\TopLevelDomains::contains` without replacement - Internal Converter classes (implementation details are no longer exposed). -## 5.7.2 - 2020-10-25 +## [5.7.2] - 2020-10-25 ### Added @@ -168,7 +168,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 5.7.1 - 2020-08-24 +## [5.7.1] - 2020-08-24 ### Added @@ -186,7 +186,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 5.7.0 - 2020-08-02 +## [5.7.0] - 2020-08-02 ### Added @@ -210,7 +210,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - Support for PHP7.0 and PHP7.1 - The external data from IANA and mozilla is no longer part of the package and will be downloaded only on demand on composer update/install. -## 5.6.0 - 2019-12-29 +## [5.6.0] - 2019-12-29 ### Added @@ -229,7 +229,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 5.5.0 - 2019-04-14 +## [5.5.0] - 2019-04-14 ### Added @@ -252,7 +252,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 5.4.0 - 2018-11-22 +## [5.4.0] - 2018-11-22 ### Added @@ -279,7 +279,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 5.3.0 - 2018-05-22 +## [5.3.0] - 2018-05-22 ### Added @@ -314,7 +314,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 5.2.0 - 2018-02-23 +## [5.2.0] - 2018-02-23 ### Added @@ -340,7 +340,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 5.1.0 - 2017-12-18 +## [5.1.0] - 2017-12-18 ### Added @@ -359,7 +359,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - None -## 5.0.0 - 2017-12-13 +## [5.0.0] - 2017-12-13 ### Added @@ -389,3 +389,21 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - `Pdp\PublicSuffixManager` class replaced by the `Pdp\Manager` class - `Pdp\HttpAdapter\HttpAdapterInterface` interface replaced by the `Pdp\HttpClient` interface - `Pdp\HttpAdapter\CurlHttpAdapter` class replaced by the `Pdp\CurlHttpClient` class + +[6.3.0]: https://github.com/jeremykendall/php-domain-parser/compare/6.2.0...6.3.0 +[6.2.0]: https://github.com/jeremykendall/php-domain-parser/compare/6.1.2...6.2.0 +[6.1.2]: https://github.com/jeremykendall/php-domain-parser/compare/6.1.1...6.1.2 +[6.1.1]: https://github.com/jeremykendall/php-domain-parser/compare/6.1.0...6.1.1 +[6.1.0]: https://github.com/jeremykendall/php-domain-parser/compare/6.0.0...6.1.0 +[6.0.0]: https://github.com/jeremykendall/php-domain-parser/compare/5.7.2...6.0.0 +[6.0.0]: https://github.com/jeremykendall/php-domain-parser/compare/5.7.2...6.0.0 +[5.7.2]: https://github.com/jeremykendall/php-domain-parser/compare/5.7.1...5.7.2 +[5.7.1]: https://github.com/jeremykendall/php-domain-parser/compare/5.7.0...5.7.1 +[5.7.0]: https://github.com/jeremykendall/php-domain-parser/compare/5.6.0...5.7.0 +[5.6.0]: https://github.com/jeremykendall/php-domain-parser/compare/5.5.0...5.6.0 +[5.5.0]: https://github.com/jeremykendall/php-domain-parser/compare/5.4.0...5.5.0 +[5.4.0]: https://github.com/jeremykendall/php-domain-parser/compare/5.3.0...5.4.0 +[5.3.0]: https://github.com/jeremykendall/php-domain-parser/compare/5.2.0...5.3.0 +[5.2.0]: https://github.com/jeremykendall/php-domain-parser/compare/5.1.0...5.3.0 +[5.1.0]: https://github.com/jeremykendall/php-domain-parser/compare/5.0.0...5.1.0 +[5.0.0]: https://github.com/jeremykendall/php-domain-parser/compare/3.0.0...5.0.0 diff --git a/composer.json b/composer.json index 27de40d..66aafde 100644 --- a/composer.json +++ b/composer.json @@ -49,10 +49,10 @@ "friendsofphp/php-cs-fixer": "^v3.13.2", "guzzlehttp/guzzle": "^7.5", "guzzlehttp/psr7": "^1.6 || ^2.4.3", - "phpstan/phpstan": "^1.9.18", - "phpstan/phpstan-phpunit": "^1.3.4", - "phpstan/phpstan-strict-rules": "^1.4.5", - "phpunit/phpunit": "^10.0.11", + "phpstan/phpstan": "^1.10.3", + "phpstan/phpstan-phpunit": "^1.3.8", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^10.0.12", "psr/http-factory": "^1.0.1", "psr/simple-cache": "^1.0.1", "symfony/cache": "^v5.0.0 || ^v6.0.0" diff --git a/src/Domain.php b/src/Domain.php index 5fc535e..358ebdc 100644 --- a/src/Domain.php +++ b/src/Domain.php @@ -11,7 +11,10 @@ final class Domain implements DomainName { - private function __construct(private RegisteredName $registeredName) + /** + * @throws SyntaxError + */ + private function __construct(private readonly RegisteredName $registeredName) { if (false !== filter_var($this->registeredName->value(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { throw SyntaxError::dueToUnsupportedType($this->registeredName->toString()); @@ -26,11 +29,17 @@ public static function __set_state(array $properties): self return new self($properties['registeredName']); } + /** + * @throws CannotProcessHost + */ public static function fromIDNA2003(DomainNameProvider|Host|Stringable|string|int|null $domain): self { return new self(RegisteredName::fromIDNA2003($domain)); } + /** + * @throws CannotProcessHost + */ public static function fromIDNA2008(DomainNameProvider|Host|Stringable|string|int|null $domain): self { return new self(RegisteredName::fromIDNA2008($domain)); @@ -135,11 +144,17 @@ public function withoutLabel(int ...$keys): self return $this->newInstance($this->registeredName->withoutLabel(...$keys)); } + /** + * @throws CannotProcessHost + */ public function clear(): self { return $this->newInstance($this->registeredName->clear()); } + /** + * @throws CannotProcessHost + */ public function slice(int $offset, int $length = null): self { return $this->newInstance($this->registeredName->slice($offset, $length)); diff --git a/src/ResolvedDomain.php b/src/ResolvedDomain.php index 1ead1f4..f4fb87c 100644 --- a/src/ResolvedDomain.php +++ b/src/ResolvedDomain.php @@ -69,12 +69,17 @@ public static function fromUnknown(DomainNameProvider|Host|Stringable|string|int /** * @param array{domain:DomainName, suffix:EffectiveTopLevelDomain} $properties + * + * @throws CannotProcessHost */ public static function __set_state(array $properties): self { return new self($properties['domain'], $properties['suffix']); } + /** + * @throws CannotProcessHost + */ private static function setDomainName(DomainNameProvider|Host|Stringable|string|int|null $domain): DomainName { return match (true) { @@ -176,6 +181,9 @@ public function toUnicode(): self return new self($this->domain->toUnicode(), $this->suffix->toUnicode()); } + /** + * @throws CannotProcessHost + */ public function withSuffix(DomainNameProvider|Host|Stringable|string|int|null $suffix): self { if (!$suffix instanceof EffectiveTopLevelDomain) { @@ -188,6 +196,9 @@ public function withSuffix(DomainNameProvider|Host|Stringable|string|int|null $s ); } + /** + * @throws CannotProcessHost|UnableToResolveDomain + */ public function withSubDomain(DomainNameProvider|Host|Stringable|string|int|null $subDomain): self { if (null === $this->suffix->value()) { @@ -202,6 +213,9 @@ public function withSubDomain(DomainNameProvider|Host|Stringable|string|int|null return new self($this->registrableDomain->prepend($subDomain), $this->suffix); } + /** + * @throws CannotProcessHost|UnableToResolveDomain + */ public function withSecondLevelDomain(DomainNameProvider|Host|Stringable|string|int|null $label): self { if (null === $this->suffix->value()) { diff --git a/src/Suffix.php b/src/Suffix.php index 4e0b8d8..da39728 100644 --- a/src/Suffix.php +++ b/src/Suffix.php @@ -29,6 +29,9 @@ public static function __set_state(array $properties): self return new self($properties['domain'], $properties['section']); } + /** + * @throws CannotProcessHost + */ public static function fromICANN(DomainNameProvider|Host|Stringable|string|int|null $domain): self { $domain = self::setDomainName($domain); @@ -39,6 +42,9 @@ public static function fromICANN(DomainNameProvider|Host|Stringable|string|int|n return new self($domain, self::ICANN); } + /** + * @throws CannotProcessHost + */ public static function fromPrivate(DomainNameProvider|Host|Stringable|string|int|null $domain): self { $domain = self::setDomainName($domain); @@ -49,6 +55,9 @@ public static function fromPrivate(DomainNameProvider|Host|Stringable|string|int return new self($domain, self::PRIVATE); } + /** + * @throws CannotProcessHost + */ public static function fromIANA(DomainNameProvider|Host|Stringable|string|int|null $domain): self { $domain = self::setDomainName($domain); @@ -59,11 +68,17 @@ public static function fromIANA(DomainNameProvider|Host|Stringable|string|int|nu return new self($domain, self::IANA); } + /** + * @throws CannotProcessHost + */ public static function fromUnknown(DomainNameProvider|Host|Stringable|string|int|null $domain): self { return new self(self::setDomainName($domain), self::UNKNOWN); } + /** + * @throws CannotProcessHost + */ private static function setDomainName(int|DomainNameProvider|Host|string|Stringable|null $domain): DomainName { if ($domain instanceof DomainNameProvider) { @@ -141,6 +156,9 @@ public function toUnicode(): self return new self($this->domain->toUnicode(), $this->section); } + /** + * @throws CannotProcessHost + */ public function normalize(DomainName $domain): self { $newDomain = $domain->clear()->append($this->toUnicode());