From f6b405c795568361dbfa8e533057dca0046ecd22 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Mon, 14 Dec 2020 09:53:30 +0100 Subject: [PATCH 01/13] Improve gitattributes --- .gitattributes | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index 37141d4d..7dd10a26 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,9 +5,10 @@ /.github export-ignore /.gitignore export-ignore /.php_cs export-ignore -/phpstan.neon export-ignore -/.travis.yml export-ignore -/README.md export-ignore +/phpstan.neon export-ignore /phpunit.xml.dist export-ignore +/psalm.xml export-ignore /CHANGELOG.md export-ignore +/UPGRADING.md export-ignore +/README.md export-ignore /**/*Test.php export-ignore From f1e5823b090bfc99f66102d0a5b8053b67603124 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Mon, 14 Dec 2020 10:07:45 +0100 Subject: [PATCH 02/13] Improve TimeToLive class --- CHANGELOG.md | 21 +++++ README.md | 5 +- src/Storage/PsrStorageFactory.php | 6 +- src/Storage/PublicSuffixListPsr16Cache.php | 24 ++++- .../PublicSuffixListStorageFactory.php | 5 +- src/Storage/TimeToLive.php | 92 ++++++++++++++----- src/Storage/TopLevelDomainListPsr16Cache.php | 24 ++++- .../TopLevelDomainListStorageFactory.php | 5 +- 8 files changed, 147 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa99827a..40542acd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will be documented in this file +## Next - TBD + +### Added + +- `TimeToLive::fromNow` +- `TimeToLive::fromDurationString` + +### Fixed + +- `.gitattributes` files to be filter out. + +### Deprecated + +- `TimeToLive::fromDateTimeInterface` use `TimeToLive::fromNow` +- `TimeToLive::fromScalar` use `TimeToLive::fromDurationString` +- `TimeToLive::convert` without replacement + +### Removed + +- None + ## 6.0.0 - 2020-12-13 ### Added diff --git a/README.md b/README.md index 9ebb6932..7afb63c9 100644 --- a/README.md +++ b/README.md @@ -540,8 +540,9 @@ Testing `pdp-domain-parser` has: - a [PHPUnit](https://phpunit.de) test suite -- a coding style compliance test suite using [PHP CS Fixer](http://cs.sensiolabs.org/). -- a code analysis compliance test suite using [PHPStan](https://github.com/phpstan/phpstan). +- a code analysis compliance test suite using [PHPStan](https://phpstan.org). +- a code analysis compliance test suite using [Psalm](https://psalm.dev). +- a coding style compliance test suite using [PHP CS Fixer](https://cs.symfony.com). To run the tests, run the following command from the project folder. diff --git a/src/Storage/PsrStorageFactory.php b/src/Storage/PsrStorageFactory.php index 253f7a96..365e811d 100644 --- a/src/Storage/PsrStorageFactory.php +++ b/src/Storage/PsrStorageFactory.php @@ -4,6 +4,8 @@ namespace Pdp\Storage; +use DateInterval; +use DateTimeInterface; use Pdp\ResourceUri; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; @@ -28,7 +30,7 @@ public function __construct(CacheInterface $cache, ClientInterface $client, Requ } /** - * @param mixed $cacheTtl The cache TTL + * @param DateInterval|DateTimeInterface|object|int|string|null $cacheTtl storage TTL object should implement the __toString method */ public function createPublicSuffixListStorage(string $cachePrefix = '', $cacheTtl = null): PublicSuffixListStorage { @@ -39,7 +41,7 @@ public function createPublicSuffixListStorage(string $cachePrefix = '', $cacheTt } /** - * @param mixed $cacheTtl The cache TTL + * @param DateInterval|DateTimeInterface|object|int|string|null $cacheTtl storage TTL object should implement the __toString method */ public function createTopLevelDomainListStorage(string $cachePrefix = '', $cacheTtl = null): TopLevelDomainListStorage { diff --git a/src/Storage/PublicSuffixListPsr16Cache.php b/src/Storage/PublicSuffixListPsr16Cache.php index 3d1bd90f..f0190c5f 100644 --- a/src/Storage/PublicSuffixListPsr16Cache.php +++ b/src/Storage/PublicSuffixListPsr16Cache.php @@ -5,6 +5,8 @@ namespace Pdp\Storage; use DateInterval; +use DateTimeInterface; +use InvalidArgumentException; use Pdp\PublicSuffixList; use Psr\SimpleCache\CacheException; use Psr\SimpleCache\CacheInterface; @@ -21,13 +23,31 @@ final class PublicSuffixListPsr16Cache implements PublicSuffixListCache private ?DateInterval $cacheTtl; /** - * @param mixed $cacheTtl cache TTL + * @param DateInterval|DateTimeInterface|object|int|string|null $cacheTtl storage TTL object should implement the __toString method */ public function __construct(CacheInterface $cache, string $cachePrefix = '', $cacheTtl = null) { $this->cache = $cache; $this->cachePrefix = $cachePrefix; - $this->cacheTtl = TimeToLive::convert($cacheTtl); + $this->cacheTtl = $this->setCacheTtl($cacheTtl); + } + + /** + * @param DateInterval|DateTimeInterface|object|int|string|null $cacheTtl storage TTL object should implement the __toString method + * + * @throws InvalidArgumentException if the value can not be computed + */ + private function setCacheTtl($cacheTtl): ?DateInterval + { + if ($cacheTtl instanceof DateInterval || null === $cacheTtl) { + return $cacheTtl; + } + + if ($cacheTtl instanceof DateTimeInterface) { + return TimeToLive::fromNow($cacheTtl); + } + + return TimeToLive::fromDurationString($cacheTtl); } public function fetch(string $uri): ?PublicSuffixList diff --git a/src/Storage/PublicSuffixListStorageFactory.php b/src/Storage/PublicSuffixListStorageFactory.php index b2e978bf..0bebfe0c 100644 --- a/src/Storage/PublicSuffixListStorageFactory.php +++ b/src/Storage/PublicSuffixListStorageFactory.php @@ -4,10 +4,13 @@ namespace Pdp\Storage; +use DateInterval; +use DateTimeInterface; + interface PublicSuffixListStorageFactory { /** - * @param mixed $cacheTtl The cache TTL + * @param DateInterval|DateTimeInterface|object|int|string|null $cacheTtl storage TTL object should implement the __toString method */ public function createPublicSuffixListStorage(string $cachePrefix = '', $cacheTtl = null): PublicSuffixListStorage; } diff --git a/src/Storage/TimeToLive.php b/src/Storage/TimeToLive.php index aa70977a..673a8f78 100644 --- a/src/Storage/TimeToLive.php +++ b/src/Storage/TimeToLive.php @@ -18,41 +18,30 @@ final class TimeToLive { - public static function fromDateTimeInterface(DateTimeInterface $ttl): DateInterval - { - /** @var DateTimeZone $timezone */ - $timezone = $ttl->getTimezone(); - - $now = new DateTimeImmutable('NOW', $timezone); - - /** @var DateInterval $diff */ - $diff = $now->diff($ttl, false); - - return $diff; - } - /** - * @param object|string|int $ttl the cache TTL the object must implement the __toString method + * Returns a DateInterval from string parsing. + * + * @param object|int|string $duration storage TTL object should implement the __toString method */ - public static function fromScalar($ttl): DateInterval + public static function fromDurationString($duration): DateInterval { - if (is_object($ttl) && method_exists($ttl, '__toString')) { - $ttl = (string) $ttl; + if (is_object($duration) && method_exists($duration, '__toString')) { + $duration = (string) $duration; } - if (false !== ($res = filter_var($ttl, FILTER_VALIDATE_INT))) { + if (false !== ($res = filter_var($duration, FILTER_VALIDATE_INT))) { return new DateInterval('PT'.$res.'S'); } - if (!is_string($ttl)) { + if (!is_string($duration)) { throw new TypeError('The ttl must null, an integer, a string, a DateTimeInterface or a DateInterval object.'); } /** @var DateInterval|false $date */ - $date = @DateInterval::createFromDateString($ttl); + $date = @DateInterval::createFromDateString($duration); if (!$date instanceof DateInterval) { throw new InvalidArgumentException( - 'The ttl value "'.$ttl.'" can not be parsable by `DateInterval::createFromDateString`.' + 'The ttl value "'.$duration.'" can not be parsable by `DateInterval::createFromDateString`.' ); } @@ -60,12 +49,65 @@ public static function fromScalar($ttl): DateInterval } /** - * Set the cache TTL. + * Returns a DateInterval relative to the current date and time. + */ + public static function fromNow(DateTimeInterface $date): DateInterval + { + /** @var DateTimeZone $timezone */ + $timezone = $date->getTimezone(); + + $now = new DateTimeImmutable('NOW', $timezone); + + /** @var DateInterval $diff */ + $diff = $now->diff($date, false); + + return $diff; + } + + /** + * Returns a DateInterval relative to the current date and time. + * + * DEPRECATION WARNING! This method will be removed in the next major point release + * + * @deprecated 6.1.0 deprecated + * @codeCoverageIgnore + * @see TimeToLive::fromNow + */ + public static function fromDateTimeInterface(DateTimeInterface $date): DateInterval + { + return self::fromNow($date); + } + + /** + * Returns a DateInterval from string parsing. + * + * @param object|int|string $duration storage TTL object should implement the __toString method * - * @param mixed $ttl the cache TTL + * @throws InvalidArgumentException if the value can not be parsable + * + * DEPRECATION WARNING! This method will be removed in the next major point release + * + * @deprecated 6.1.0 deprecated + * @codeCoverageIgnore + * @see TimeToLive::fromDurationString + */ + public static function fromScalar($duration): DateInterval + { + return self::fromDurationString($duration); + } + + /** + * Convert the input data into a DateInterval object or null. + * + * @param DateInterval|DateTimeInterface|object|int|string|null $ttl the object should implement the __toString method * * @throws InvalidArgumentException if the value can not be computed * @throws TypeError if the value type is not recognized + * + * DEPRECATION WARNING! This method will be removed in the next major point release + * + * @deprecated 6.1.0 deprecated + * @codeCoverageIgnore */ public static function convert($ttl): ?DateInterval { @@ -74,9 +116,9 @@ public static function convert($ttl): ?DateInterval } if ($ttl instanceof DateTimeInterface) { - return self::fromDateTimeInterface($ttl); + return self::fromNow($ttl); } - return self::fromScalar($ttl); + return self::fromDurationString($ttl); } } diff --git a/src/Storage/TopLevelDomainListPsr16Cache.php b/src/Storage/TopLevelDomainListPsr16Cache.php index 0c24755f..183bc348 100644 --- a/src/Storage/TopLevelDomainListPsr16Cache.php +++ b/src/Storage/TopLevelDomainListPsr16Cache.php @@ -5,6 +5,8 @@ namespace Pdp\Storage; use DateInterval; +use DateTimeInterface; +use InvalidArgumentException; use Pdp\TopLevelDomainList; use Psr\SimpleCache\CacheException; use Psr\SimpleCache\CacheInterface; @@ -21,13 +23,31 @@ final class TopLevelDomainListPsr16Cache implements TopLevelDomainListCache private ?DateInterval $cacheTtl; /** - * @param mixed $cacheTtl storage TTL + * @param DateInterval|DateTimeInterface|object|int|string|null $cacheTtl storage TTL object should implement the __toString method */ public function __construct(CacheInterface $cache, string $cachePrefix = '', $cacheTtl = null) { $this->cache = $cache; $this->cachePrefix = $cachePrefix; - $this->cacheTtl = TimeToLive::convert($cacheTtl); + $this->cacheTtl = $this->setCacheTtl($cacheTtl); + } + + /** + * @param DateInterval|DateTimeInterface|object|int|string|null $cacheTtl storage TTL object should implement the __toString method + * + * @throws InvalidArgumentException if the value can not be computed + */ + private function setCacheTtl($cacheTtl): ?DateInterval + { + if ($cacheTtl instanceof DateInterval || null === $cacheTtl) { + return $cacheTtl; + } + + if ($cacheTtl instanceof DateTimeInterface) { + return TimeToLive::fromNow($cacheTtl); + } + + return TimeToLive::fromDurationString($cacheTtl); } public function fetch(string $uri): ?TopLevelDomainList diff --git a/src/Storage/TopLevelDomainListStorageFactory.php b/src/Storage/TopLevelDomainListStorageFactory.php index 60b26fe1..e66fba78 100644 --- a/src/Storage/TopLevelDomainListStorageFactory.php +++ b/src/Storage/TopLevelDomainListStorageFactory.php @@ -5,10 +5,13 @@ namespace Pdp\Storage; +use DateInterval; +use DateTimeInterface; + interface TopLevelDomainListStorageFactory { /** - * @param mixed $cacheTtl The cache TTL + * @param DateInterval|DateTimeInterface|object|int|string|null $cacheTtl storage TTL object should implement the __toString method */ public function createTopLevelDomainListStorage(string $cachePrefix = '', $cacheTtl = null): TopLevelDomainListStorage; } From d7ddb2adba4d6a766be86ef971605e46e425cfec Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Mon, 5 Apr 2021 09:07:40 +0200 Subject: [PATCH 03/13] Improve package documentation --- .github/workflows/build.yaml | 6 ++-- README.md | 55 +++++++++++++++++++++++------------- composer.json | 5 +++- phpstan.neon | 1 - src/Domain.php | 8 ++++++ src/Idna.php | 22 --------------- src/IdnaInfo.php | 48 +++++++++++++++++++++---------- src/IdnaInfoTest.php | 6 ++-- src/ResolvedDomain.php | 8 ++++++ src/Suffix.php | 8 ++++++ 10 files changed, 102 insertions(+), 65 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a1908249..bc5da59a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -23,8 +23,8 @@ jobs: - run: composer update --no-progress ${{ matrix.composer-flags }} - run: composer phpunit - run: composer phpstan - if: ${{ matrix.php == '7.4' }} + if: ${{ matrix.php == '8.0' }} - run: composer psalm - if: ${{ matrix.php == '7.4' }} + if: ${{ matrix.php == '8.0' }} - run: composer phpcs - if: ${{ matrix.php == '7.4' }} + if: ${{ matrix.php == '8.0' }} diff --git a/README.md b/README.md index 7afb63c9..60ee17f1 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,12 @@ For the [Public Suffix List](http://publicsuffix.org/) you need to use the ~~~php resolve('www.PreF.OkiNawA.jP'); +$result = $publicSuffixList->resolve($domain); echo $result->domain()->toString(); //display 'www.pref.okinawa.jp'; echo $result->subDomain()->toString(); //display 'www'; echo $result->secondLevelDomain()->toString(); //display 'pref'; @@ -79,8 +81,9 @@ the `Pdp\TopLevelDomains` class is use instead: use Pdp\TopLevelDomains; $topLevelDomains = TopLevelDomains::fromPath('/path/to/cache/tlds-alpha-by-domain.txt'); +$domain = Domain::fromIDNA2008('www.PreF.OkiNawA.jP'); -$result = $topLevelDomains->resolve('www.PreF.OkiNawA.jP'); +$result = $topLevelDomains->resolve($domain); echo $result->domain()->toString(); //display 'www.pref.okinawa.jp'; echo $result->suffix()->toString(); //display 'jp'; echo $result->secondLevelDomain()->toString(); //display 'okinawa'; @@ -109,31 +112,38 @@ These methods resolve the domain against their respective data source using the same rules as the `resolve` method but will instead throw an exception if no valid effective TLD is found or if the submitted domain is invalid. +**All these methods expect as their sole argument a `Pdp\Host` implementing +object, but other types (ie: `string`, `null` and stringable objects) are +supported with predefined conditions as explained in the remaining document.** + ~~~php -getICANNDomain('qfdsf.unknownTLD'); +$publicSuffixList->getICANNDomain($domain); // will throw because `.unknownTLD` is not part of the ICANN section -$result = $publicSuffixList->getCookieDomain('qfdsf.unknownTLD'); +$result = $publicSuffixList->getCookieDomain($domain); $result->suffix()->value(); // returns 'unknownTLD' $result->suffix()->isKnown(); // returns false // will not throw because the domain syntax is correct. -$publicSuffixList->getCookieDomain('com'); +$publicSuffixList->getCookieDomain(Domain::fromIDNA2008('com')); // will not throw because the domain syntax is invalid (ie: does not support public suffix) -$result = $publicSuffixList->resolve('com'); +$result = $publicSuffixList->resolve(Domain::fromIDNA2008('com')); $result->suffix()->value(); // returns null $result->suffix()->isKnown(); // returns false // will not throw but its public suffix value equal to NULL $topLevelDomains = TopLevelDomains::fromPath('/path/to/cache/public-suffix-list.dat'); -$topLevelDomains->getIANADomain('com'); +$topLevelDomains->getIANADomain(Domain::fromIDNA2008('com')); // will not throw because the domain syntax is invalid (ie: does not support public suffix) ~~~ @@ -171,10 +181,12 @@ The `Pdp\ResolvedDomain` decorates the `Pdp\Domain` class resolved but also gives access as separate methods to the domain different components. ~~~php +use Pdp\Domain; use Pdp\TopLevelDomains; +$domain = Domain::fromIDNA2008('www.PreF.OkiNawA.jP'); /** @var TopLevelDomains $topLevelDomains */ -$result = $topLevelDomains->resolve('www.PreF.OkiNawA.jP'); +$result = $topLevelDomains->resolve($domain); echo $result->domain()->toString(); //display 'www.pref.okinawa.jp'; echo $result->suffix()->toString(); //display 'jp'; echo $result->secondLevelDomain()->toString(); //display 'okinawa'; @@ -188,14 +200,15 @@ You can modify the returned `Pdp\ResolvedDomain` instance using the following me ~~~php resolve('shop.example.com'); +$result = $publicSuffixList->resolve(Domain::fromIDNA2008('shop.example.com')); $altResult = $result - ->withSubDomain('foo.bar') - ->withSecondLevelDomain('test') - ->withSuffix('example'); + ->withSubDomain(Domain::fromIDNA2008('foo.bar')) + ->withSecondLevelDomain(Domain::fromIDNA2008('test')) + ->withSuffix(Domain::fromIDNA2008('example')); echo $result->domain()->toString(); //display 'shop.example.com'; $result->suffix()->isKnown(); //return true; @@ -217,10 +230,11 @@ origin. ~~~php resolve('example.github.io')->suffix(); +$suffix = $publicSuffixList->resolve(Domain::fromIDNA2008('example.github.io'))->suffix(); echo $suffix->domain()->toString(); //display 'github.io'; $suffix->isICANN(); //will return false @@ -274,11 +288,12 @@ manipulating domain labels. You can access the object using the following method `Domain` objects usage are explain in the next section. ~~~php -resolve('www.bbc.co.uk'); +$result = $publicSuffixList->resolve(Domain::from2008('www.bbc.co.uk')); $domain = $result->domain(); echo $domain->toString(); // display 'www.bbc.co.uk' count($domain); // returns 4 @@ -303,10 +318,11 @@ following methods: ~~~php resolve('www.ExAmpLE.cOM')->domain(); +$domain = $publicSuffixList->resolve(Domain::from2008('www.ExAmpLE.cOM'))->domain(); $newDomain = $domain ->withLabel(1, 'com') //replace 'example' by 'com' @@ -571,10 +587,9 @@ The MIT License (MIT). Please see [License File](LICENSE) for more information. Attribution ------- -Portions of the `Pdp\Converter` and `Pdp\Rules` are derivative works of the PHP +Portions of the `Pdp\Rules` class are derivative works of the PHP [registered-domain-libs](https://github.com/usrflo/registered-domain-libs). -Those parts of this codebase are heavily commented, and I've included a copy of -the Apache Software Foundation License 2.0 in this project. +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-packagist]: https://img.shields.io/packagist/dt/jeremykendall/php-domain-parser.svg?style=flat-square diff --git a/composer.json b/composer.json index 21134687..6d8667fb 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ ], "require": { "php": "^7.4 || ^8.0", + "ext-filter": "*", "ext-intl": "*", "ext-json": "*" }, @@ -71,7 +72,8 @@ }, "scripts": { "phpcs": "php-cs-fixer fix -vvv --diff --dry-run --allow-risky=yes --ansi", - "phpstan": "phpstan analyse -l max -c phpstan.neon src --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", "psalm": "psalm --show-info=true", "phpunit": "phpunit --coverage-text", "test": [ @@ -83,6 +85,7 @@ }, "scripts-descriptions": { "phpcs": "Runs coding style test suite", + "phpcs:fix": "Fix the package coding style", "phpstan": "Runs complete codebase static analysis", "psalm": "Runs complete codebase static analysis", "phpunit": "Runs unit and functional testing", diff --git a/phpstan.neon b/phpstan.neon index 6db39376..d4747672 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,5 +4,4 @@ includes: - vendor/phpstan/phpstan-phpunit/rules.neon parameters: ignoreErrors: - - '#should be covariant with return type#' reportUnmatchedIgnoredErrors: true diff --git a/src/Domain.php b/src/Domain.php index 9e6ada35..b3a6ef84 100644 --- a/src/Domain.php +++ b/src/Domain.php @@ -235,6 +235,10 @@ public function labels(): array return $this->labels; } + /** + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ public function toAscii(): self { if (null === $this->domain) { @@ -249,6 +253,10 @@ public function toAscii(): self return new self($this->type, $domain); } + /** + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ public function toUnicode(): self { if (null === $this->domain) { diff --git a/src/Idna.php b/src/Idna.php index 20bef739..30d859b5 100644 --- a/src/Idna.php +++ b/src/Idna.php @@ -20,25 +20,6 @@ */ final class Idna { - /** - * IDNA errors. - */ - public const ERROR_EMPTY_LABEL = 1; - public const ERROR_LABEL_TOO_LONG = 2; - public const ERROR_DOMAIN_NAME_TOO_LONG = 4; - public const ERROR_LEADING_HYPHEN = 8; - public const ERROR_TRAILING_HYPHEN = 0x10; - public const ERROR_HYPHEN_3_4 = 0x20; - public const ERROR_LEADING_COMBINING_MARK = 0x40; - public const ERROR_DISALLOWED = 0x80; - public const ERROR_PUNYCODE = 0x100; - public const ERROR_LABEL_HAS_DOT = 0x200; - public const ERROR_INVALID_ACE_LABEL = 0x400; - public const ERROR_BIDI = 0x800; - public const ERROR_CONTEXTJ = 0x1000; - public const ERROR_CONTEXTO_PUNCTUATION = 0x2000; - public const ERROR_CONTEXTO_DIGITS = 0x4000; - /** * IDNA options. */ @@ -55,15 +36,12 @@ final class Idna | self::IDNA_CHECK_BIDI | self::IDNA_USE_STD3_RULES | self::IDNA_CHECK_CONTEXTJ; - public const IDNA2008_UNICODE = self::IDNA_NONTRANSITIONAL_TO_UNICODE | self::IDNA_CHECK_BIDI | self::IDNA_USE_STD3_RULES | self::IDNA_CHECK_CONTEXTJ; - public const IDNA2003_ASCII = self::IDNA_DEFAULT; public const IDNA2003_UNICODE = self::IDNA_DEFAULT; - private const REGEXP_IDNA_PATTERN = '/[^\x20-\x7f]/'; /** diff --git a/src/IdnaInfo.php b/src/IdnaInfo.php index 9147b86a..51a84bd2 100644 --- a/src/IdnaInfo.php +++ b/src/IdnaInfo.php @@ -12,22 +12,40 @@ */ final class IdnaInfo { + /** + * IDNA errors. + */ + public const ERROR_EMPTY_LABEL = 1; + public const ERROR_LABEL_TOO_LONG = 2; + public const ERROR_DOMAIN_NAME_TOO_LONG = 4; + public const ERROR_LEADING_HYPHEN = 8; + public const ERROR_TRAILING_HYPHEN = 0x10; + public const ERROR_HYPHEN_3_4 = 0x20; + public const ERROR_LEADING_COMBINING_MARK = 0x40; + public const ERROR_DISALLOWED = 0x80; + public const ERROR_PUNYCODE = 0x100; + public const ERROR_LABEL_HAS_DOT = 0x200; + public const ERROR_INVALID_ACE_LABEL = 0x400; + public const ERROR_BIDI = 0x800; + public const ERROR_CONTEXTJ = 0x1000; + public const ERROR_CONTEXTO_PUNCTUATION = 0x2000; + public const ERROR_CONTEXTO_DIGITS = 0x4000; private const ERRORS = [ - Idna::ERROR_EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty', - Idna::ERROR_LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes', - Idna::ERROR_DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form', - Idna::ERROR_LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")', - Idna::ERROR_TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")', - Idna::ERROR_HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions', - Idna::ERROR_LEADING_COMBINING_MARK => 'a label starts with a combining mark', - Idna::ERROR_DISALLOWED => 'a label or domain name contains disallowed characters', - Idna::ERROR_PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode', - Idna::ERROR_LABEL_HAS_DOT => 'a label contains a dot=full stop', - Idna::ERROR_INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string', - Idna::ERROR_BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)', - Idna::ERROR_CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements', - Idna::ERROR_CONTEXTO_DIGITS => 'a label does not meet the IDNA CONTEXTO requirements for digits', - Idna::ERROR_CONTEXTO_PUNCTUATION => 'a label does not meet the IDNA CONTEXTO requirements for punctuation characters. Some punctuation characters "Would otherwise have been DISALLOWED" but are allowed in certain contexts', + self::ERROR_EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty', + self::ERROR_LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes', + self::ERROR_DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form', + self::ERROR_LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")', + self::ERROR_TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")', + self::ERROR_HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions', + self::ERROR_LEADING_COMBINING_MARK => 'a label starts with a combining mark', + self::ERROR_DISALLOWED => 'a label or domain name contains disallowed characters', + self::ERROR_PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode', + self::ERROR_LABEL_HAS_DOT => 'a label contains a dot=full stop', + self::ERROR_INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string', + self::ERROR_BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)', + self::ERROR_CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements', + self::ERROR_CONTEXTO_DIGITS => 'a label does not meet the IDNA CONTEXTO requirements for digits', + self::ERROR_CONTEXTO_PUNCTUATION => 'a label does not meet the IDNA CONTEXTO requirements for punctuation characters. Some punctuation characters "Would otherwise have been DISALLOWED" but are allowed in certain contexts', ]; private string $result; diff --git a/src/IdnaInfoTest.php b/src/IdnaInfoTest.php index 8dd39356..bb62e782 100644 --- a/src/IdnaInfoTest.php +++ b/src/IdnaInfoTest.php @@ -27,7 +27,7 @@ public function testItCanBeInstantiatedFromArray(): void self::assertSame('', $result->result()); self::assertFalse($result->isTransitionalDifferent()); self::assertSame(0, $result->errors()); - self::assertNull($result->error(Idna::ERROR_BIDI)); + self::assertNull($result->error(IdnaInfo::ERROR_BIDI)); self::assertCount(0, $result->errorList()); } @@ -38,8 +38,8 @@ public function testInvalidSyntaxAfterIDNConversion(): void } catch (SyntaxError $exception) { $result = $exception->idnaInfo(); self::assertInstanceOf(IdnaInfo::class, $result); - self::assertSame(Idna::ERROR_DISALLOWED, $result->errors()); - self::assertIsString($result->error(Idna::ERROR_DISALLOWED)); + self::assertSame(IdnaInfo::ERROR_DISALLOWED, $result->errors()); + self::assertIsString($result->error(IdnaInfo::ERROR_DISALLOWED)); self::assertCount(1, $result->errorList()); } } diff --git a/src/ResolvedDomain.php b/src/ResolvedDomain.php index 6d4adcdb..06eca8d7 100644 --- a/src/ResolvedDomain.php +++ b/src/ResolvedDomain.php @@ -165,11 +165,19 @@ public function suffix(): EffectiveTopLevelDomain return $this->suffix; } + /** + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ public function toAscii(): self { return new self($this->domain->toAscii(), $this->suffix->toAscii()); } + /** + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ public function toUnicode(): self { return new self($this->domain->toUnicode(), $this->suffix->toUnicode()); diff --git a/src/Suffix.php b/src/Suffix.php index c613f4fd..2fd68bbe 100644 --- a/src/Suffix.php +++ b/src/Suffix.php @@ -148,6 +148,10 @@ public function toString(): string return $this->domain->toString(); } + /** + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ public function toAscii(): self { $clone = clone $this; @@ -156,6 +160,10 @@ public function toAscii(): self return $clone; } + /** + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ public function toUnicode(): self { $clone = clone $this; From 283e458fd207f540cea736661e03f7675d93df33 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Wed, 7 Apr 2021 20:11:21 +0200 Subject: [PATCH 04/13] Improve PHP static analysis fix --- .php_cs | 2 +- psalm.xml | 2 +- src/Domain.php | 10 +++++----- src/DomainName.php | 8 ++++---- src/DomainTest.php | 4 ++-- src/Idna.php | 14 ++++++++++++-- src/ResolvedDomainTest.php | 2 +- src/Rules.php | 5 ++++- src/Storage/TimeToLive.php | 5 +---- src/TopLevelDomains.php | 2 +- 10 files changed, 32 insertions(+), 22 deletions(-) diff --git a/.php_cs b/.php_cs index ff29a34d..56145cef 100644 --- a/.php_cs +++ b/.php_cs @@ -24,7 +24,7 @@ return PhpCsFixer\Config::create() 'phpdoc_no_empty_return' => true, 'phpdoc_order' => true, 'phpdoc_scalar' => true, - 'phpdoc_to_comment' => true, + 'phpdoc_to_comment' => false, 'phpdoc_summary' => true, 'psr0' => true, 'psr4' => true, diff --git a/psalm.xml b/psalm.xml index b22c0d57..ad6be53d 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ + * @var array */ private array $labels; @@ -176,7 +176,7 @@ private function domainToUnicode(string $domain): string */ public function getIterator(): Iterator { - foreach ($this->labels as $offset => $label) { + foreach ($this->labels as $label) { yield $label; } } @@ -216,7 +216,7 @@ public function label(int $key): ?string } /** - * @return array + * @return list */ public function keys(string $label = null): array { @@ -228,7 +228,7 @@ public function keys(string $label = null): array } /** - * @return array + * @return array */ public function labels(): array { @@ -274,7 +274,7 @@ public function toUnicode(): self /** * Filter a subdomain to update the domain part. * - * @param string|object $domain a domain + * @param string|object|null $domain a domain * * @throws TypeError if the domain can not be converted */ diff --git a/src/DomainName.php b/src/DomainName.php index 2f458454..00496c2d 100644 --- a/src/DomainName.php +++ b/src/DomainName.php @@ -36,7 +36,7 @@ public function label(int $key): ?string; /** * Returns the object labels. * - * @return array + * @return array */ public function labels(): array; @@ -63,7 +63,7 @@ public function getIterator(): Iterator; * * @see ::withLabel * - * @param mixed $label a domain label + * @param string|null|object $label a domain label */ public function prepend($label): self; @@ -72,7 +72,7 @@ public function prepend($label): self; * * @see ::withLabel * - * @param mixed $label a domain label + * @param string|null|object $label a domain label */ public function append($label): self; @@ -85,7 +85,7 @@ public function append($label): self; * 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 mixed $label a domain label + * @param string|null|object $label a domain label * * @throws CannotProcessHost If the key is out of bounds * @throws CannotProcessHost If the label is converted to the NULL value diff --git a/src/DomainTest.php b/src/DomainTest.php index e8151dbe..1c32a146 100644 --- a/src/DomainTest.php +++ b/src/DomainTest.php @@ -338,7 +338,7 @@ public function testAppend(string $raw, string $append, string $expected): void } /** - * @return iterable + * @return iterable */ public function validAppend(): iterable { @@ -359,7 +359,7 @@ public function testPrepend(string $raw, string $prepend, string $expected): voi } /** - * @return iterable + * @return iterable */ public function validPrepend(): iterable { diff --git a/src/Idna.php b/src/Idna.php index 30d859b5..b7096c99 100644 --- a/src/Idna.php +++ b/src/Idna.php @@ -75,9 +75,14 @@ public static function toAscii(string $domain, int $options): IdnaInfo self::supportsIdna(); + /** + * @param-out array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo + */ idn_to_ascii($domain, $options, INTL_IDNA_VARIANT_UTS46, $idnaInfo); - /* @var array{result:string, isTransitionalDifferent:bool, errors:int} $idnaInfo */ + /** + * @var array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo + */ return self::createIdnaInfo($domain, $idnaInfo); } @@ -96,9 +101,14 @@ public static function toUnicode(string $domain, int $options): IdnaInfo self::supportsIdna(); + /** + * @param-out array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo + */ idn_to_utf8($domain, $options, INTL_IDNA_VARIANT_UTS46, $idnaInfo); - /* @var array{result:string, isTransitionalDifferent:bool, errors:int} $idnaInfo */ + /** + * @var array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo + */ return self::createIdnaInfo($domain, $idnaInfo); } diff --git a/src/ResolvedDomainTest.php b/src/ResolvedDomainTest.php index 45f1a59f..5a120fd3 100644 --- a/src/ResolvedDomainTest.php +++ b/src/ResolvedDomainTest.php @@ -540,7 +540,7 @@ public function testWithSecondLevelDomain( } /** - * @return iterable + * @return iterable */ public function withSldWorksProvider(): iterable { diff --git a/src/Rules.php b/src/Rules.php index 81b3ca25..6186f5ab 100644 --- a/src/Rules.php +++ b/src/Rules.php @@ -157,7 +157,9 @@ private static function addRule(array $list, array $ruleParts): array $list[$rule] = $list[$rule] ?? ($isDomain ? [] : ['!' => '']); if ($isDomain && [] !== $ruleParts) { - $list[$rule] = self::addRule($list[$rule], $ruleParts); + /** @var array $tmpList */ + $tmpList = $list[$rule]; + $list[$rule] = self::addRule($tmpList, $ruleParts); } return $list; @@ -307,6 +309,7 @@ private function getEffectiveTopLevelDomainFromSection(DomainName $domain, strin } $matches[] = $label; + /** @var array $rules */ $rules = $rules[$label]; } diff --git a/src/Storage/TimeToLive.php b/src/Storage/TimeToLive.php index 673a8f78..41b4682f 100644 --- a/src/Storage/TimeToLive.php +++ b/src/Storage/TimeToLive.php @@ -58,10 +58,7 @@ public static function fromNow(DateTimeInterface $date): DateInterval $now = new DateTimeImmutable('NOW', $timezone); - /** @var DateInterval $diff */ - $diff = $now->diff($date, false); - - return $diff; + return $now->diff($date, false); } /** diff --git a/src/TopLevelDomains.php b/src/TopLevelDomains.php index 99740889..4623ad07 100644 --- a/src/TopLevelDomains.php +++ b/src/TopLevelDomains.php @@ -186,7 +186,7 @@ public function isEmpty(): bool */ public function getIterator(): Iterator { - foreach ($this->records as $tld => $int) { + foreach ($this->records as $tld => $_) { yield $tld; } } From 52a1b3b4742f43bdd195d149426508ada6b53178 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Wed, 7 Apr 2021 20:56:19 +0200 Subject: [PATCH 05/13] Improve TimeToLive codebase --- CHANGELOG.md | 2 +- src/Storage/PublicSuffixListPsr16Cache.php | 2 +- src/Storage/TimeToLive.php | 29 ++++---- src/Storage/TimeToLiveTest.php | 74 ++++++++++++++++++++ src/Storage/TopLevelDomainListPsr16Cache.php | 2 +- 5 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 src/Storage/TimeToLiveTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 40542acd..abf87027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will ### Added -- `TimeToLive::fromNow` +- `TimeToLive::until` - `TimeToLive::fromDurationString` ### Fixed diff --git a/src/Storage/PublicSuffixListPsr16Cache.php b/src/Storage/PublicSuffixListPsr16Cache.php index f0190c5f..03a5a3c9 100644 --- a/src/Storage/PublicSuffixListPsr16Cache.php +++ b/src/Storage/PublicSuffixListPsr16Cache.php @@ -44,7 +44,7 @@ private function setCacheTtl($cacheTtl): ?DateInterval } if ($cacheTtl instanceof DateTimeInterface) { - return TimeToLive::fromNow($cacheTtl); + return TimeToLive::until($cacheTtl); } return TimeToLive::fromDurationString($cacheTtl); diff --git a/src/Storage/TimeToLive.php b/src/Storage/TimeToLive.php index 41b4682f..3dadaef4 100644 --- a/src/Storage/TimeToLive.php +++ b/src/Storage/TimeToLive.php @@ -7,7 +7,6 @@ use DateInterval; use DateTimeImmutable; use DateTimeInterface; -use DateTimeZone; use InvalidArgumentException; use TypeError; use function filter_var; @@ -29,12 +28,19 @@ public static function fromDurationString($duration): DateInterval $duration = (string) $duration; } - if (false !== ($res = filter_var($duration, FILTER_VALIDATE_INT))) { - return new DateInterval('PT'.$res.'S'); + if (false !== ($seconds = filter_var($duration, FILTER_VALIDATE_INT))) { + if (-1 < $seconds) { + return new DateInterval('PT'.$seconds.'S'); + } + + $interval = new DateInterval('PT'.($seconds * -1).'S'); + $interval->invert = 1; + + return $interval; } if (!is_string($duration)) { - throw new TypeError('The ttl must null, an integer, a string, a DateTimeInterface or a DateInterval object.'); + throw new TypeError('The ttl must null, an integer, a string, or a stringable object.'); } /** @var DateInterval|false $date */ @@ -51,14 +57,9 @@ public static function fromDurationString($duration): DateInterval /** * Returns a DateInterval relative to the current date and time. */ - public static function fromNow(DateTimeInterface $date): DateInterval + public static function until(DateTimeInterface $date): DateInterval { - /** @var DateTimeZone $timezone */ - $timezone = $date->getTimezone(); - - $now = new DateTimeImmutable('NOW', $timezone); - - return $now->diff($date, false); + return (new DateTimeImmutable('NOW', $date->getTimezone()))->diff($date, false); } /** @@ -68,11 +69,11 @@ public static function fromNow(DateTimeInterface $date): DateInterval * * @deprecated 6.1.0 deprecated * @codeCoverageIgnore - * @see TimeToLive::fromNow + * @see TimeToLive::until */ public static function fromDateTimeInterface(DateTimeInterface $date): DateInterval { - return self::fromNow($date); + return self::until($date); } /** @@ -113,7 +114,7 @@ public static function convert($ttl): ?DateInterval } if ($ttl instanceof DateTimeInterface) { - return self::fromNow($ttl); + return self::until($ttl); } return self::fromDurationString($ttl); diff --git a/src/Storage/TimeToLiveTest.php b/src/Storage/TimeToLiveTest.php new file mode 100644 index 00000000..8e2ed4c2 --- /dev/null +++ b/src/Storage/TimeToLiveTest.php @@ -0,0 +1,74 @@ +invert); + self::assertSame(0, TimeToLive::until($tomorrow)->invert); + } + + /** + * @param object|string|int $input + * @dataProvider validDurationString + */ + public function testItCanBeInstantiatedFromDurationInput($input, DateInterval $expected): void + { + self::assertEquals($expected, TimeToLive::fromDurationString($input)); + } + + /** + * @return iterable + */ + public function validDurationString(): iterable + { + $threeDays = new DateInterval('P3D'); + + yield 'stringable object' => [ + 'input' => new class() { + public function __toString(): string + { + return '3 days'; + } + }, + 'expected' => $threeDays, + ]; + + yield 'string' => [ + 'input' => '3 days', + 'expected' => $threeDays, + ]; + + $seconds = new DateInterval('PT2345S'); + + yield 'stringable seconds' => [ + 'input' => '2345', + 'expected' => $seconds, + ]; + + yield 'seconds' => [ + 'input' => 2345, + 'expected' => $seconds, + ]; + + $negativeInterval = clone $seconds; + $negativeInterval->invert = 1; + + yield 'negative seconds' => [ + 'input' => '-2345', + 'expected' => $negativeInterval, + ]; + } +} diff --git a/src/Storage/TopLevelDomainListPsr16Cache.php b/src/Storage/TopLevelDomainListPsr16Cache.php index 183bc348..36e2195f 100644 --- a/src/Storage/TopLevelDomainListPsr16Cache.php +++ b/src/Storage/TopLevelDomainListPsr16Cache.php @@ -44,7 +44,7 @@ private function setCacheTtl($cacheTtl): ?DateInterval } if ($cacheTtl instanceof DateTimeInterface) { - return TimeToLive::fromNow($cacheTtl); + return TimeToLive::until($cacheTtl); } return TimeToLive::fromDurationString($cacheTtl); From 488fd66020bb7f19d522303b4df604a95fcbde7b Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Thu, 8 Apr 2021 23:43:21 +0200 Subject: [PATCH 06/13] Improve Rules internal implementation --- README.md | 3 +++ src/Idna.php | 16 ++++---------- src/Rules.php | 59 +++++++++++++++++++++++---------------------------- 3 files changed, 33 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 60ee17f1..e8e8589a 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,9 @@ For the [IANA Top Level Domain List](https://www.iana.org/domains/root/files), the `Pdp\TopLevelDomains` class is use instead: ~~~php +validateDomain($host); - [$length, $section] = $this->getEffectiveTopLevelDomain($domain, ''); + [$suffixLength, $section] = $this->resolveSuffix($domain, ''); if (self::ICANN_DOMAINS === $section) { - return ResolvedDomain::fromICANN($domain, $length); + return ResolvedDomain::fromICANN($domain, $suffixLength); } if (self::PRIVATE_DOMAINS === $section) { - return ResolvedDomain::fromPrivate($domain, $length); + return ResolvedDomain::fromPrivate($domain, $suffixLength); } - return ResolvedDomain::fromUnknown($domain, $length); + return ResolvedDomain::fromUnknown($domain, $suffixLength); } /** @@ -211,12 +210,12 @@ public function getCookieDomain($host): ResolvedDomainName public function getICANNDomain($host): ResolvedDomainName { $domain = $this->validateDomain($host); - [$length, $section] = $this->getEffectiveTopLevelDomain($domain, self::ICANN_DOMAINS); + [$suffixLength, $section] = $this->resolveSuffix($domain, self::ICANN_DOMAINS); if (self::ICANN_DOMAINS !== $section) { throw UnableToResolveDomain::dueToMissingSuffix($domain, 'ICANN'); } - return ResolvedDomain::fromICANN($domain, $length); + return ResolvedDomain::fromICANN($domain, $suffixLength); } /** @@ -225,12 +224,12 @@ public function getICANNDomain($host): ResolvedDomainName public function getPrivateDomain($host): ResolvedDomainName { $domain = $this->validateDomain($host); - [$length, $section] = $this->getEffectiveTopLevelDomain($domain, self::PRIVATE_DOMAINS); + [$suffixLength, $section] = $this->resolveSuffix($domain, self::PRIVATE_DOMAINS); if (self::PRIVATE_DOMAINS !== $section) { throw UnableToResolveDomain::dueToMissingSuffix($domain, 'private'); } - return ResolvedDomain::fromPrivate($domain, $length); + return ResolvedDomain::fromPrivate($domain, $suffixLength); } /** @@ -259,38 +258,36 @@ private function validateDomain($domain): DomainName } /** - * Returns the matched public suffix. + * Returns the length and the section of thhe resolved effective top level domain. * - * @return array{0:int, 1:string} + * @return array{0: int, 1:string} */ - private function getEffectiveTopLevelDomain(DomainName $domain, string $section): array + private function resolveSuffix(DomainName $domain, string $section): array { - $icann = $this->getEffectiveTopLevelDomainFromSection($domain, self::ICANN_DOMAINS); - if (self::ICANN_DOMAINS === $section) { - return $icann; + $icannSuffixLength = $this->getPublicSuffixLengthFromSection($domain, self::ICANN_DOMAINS); + if (1 > $icannSuffixLength) { + return [1, '']; } - $private = $this->getEffectiveTopLevelDomainFromSection($domain, self::PRIVATE_DOMAINS); - if ($private[0] > $icann[0]) { - return $private; + if (self::ICANN_DOMAINS === $section) { + return [$icannSuffixLength, self::ICANN_DOMAINS]; } - if ('' === $section) { - return $icann; + $privateSuffixLength = $this->getPublicSuffixLengthFromSection($domain, self::PRIVATE_DOMAINS); + if ($privateSuffixLength > $icannSuffixLength) { + return [$privateSuffixLength, self::PRIVATE_DOMAINS]; } - return [1, '']; + return [$icannSuffixLength, self::ICANN_DOMAINS]; } /** - * Returns the public suffix matched against a given PSL section. - * - * @return array{0:int, 1:string} + * Returns the public suffix label count for a domain name according to a PSL section. */ - private function getEffectiveTopLevelDomainFromSection(DomainName $domain, string $section): array + private function getPublicSuffixLengthFromSection(DomainName $domain, string $section): int { $rules = $this->rules[$section]; - $matches = []; + $labelCount = 0; foreach ($domain->toAscii() as $label) { //match exception rule if (isset($rules[$label], $rules[$label]['!'])) { @@ -299,7 +296,7 @@ private function getEffectiveTopLevelDomainFromSection(DomainName $domain, strin //match wildcard rule if (isset($rules['*'])) { - $matches[] = $label; + ++$labelCount; break; } @@ -308,15 +305,11 @@ private function getEffectiveTopLevelDomainFromSection(DomainName $domain, strin break; } - $matches[] = $label; + ++$labelCount; /** @var array $rules */ $rules = $rules[$label]; } - if ([] === $matches) { - return [1, '']; - } - - return [count($matches), $section]; + return $labelCount; } } From 5b513340dc0dc1bad4600baa35a56b08d27d8a67 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Sat, 10 Apr 2021 14:44:09 +0200 Subject: [PATCH 07/13] Improve TimeToLive --- CHANGELOG.md | 4 +- src/Idna.php | 26 ++++++- src/IdnaInfo.php | 49 ++++-------- src/IdnaInfoTest.php | 6 +- src/Storage/PublicSuffixListPsr16Cache.php | 21 +----- src/Storage/TimeToLive.php | 79 +++++++++----------- src/Storage/TimeToLiveTest.php | 61 ++++++++++----- src/Storage/TopLevelDomainListPsr16Cache.php | 21 +----- 8 files changed, 127 insertions(+), 140 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abf87027..3dca7eee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,12 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will ### Fixed - `.gitattributes` files to be filter out. +- `TimeToLive` marked as internal ### Deprecated - `TimeToLive::fromDateTimeInterface` use `TimeToLive::fromNow` -- `TimeToLive::fromScalar` use `TimeToLive::fromDurationString` -- `TimeToLive::convert` without replacement +- `TimeToLive::fromScalar` use `TimeToLive::convert` ### Removed diff --git a/src/Idna.php b/src/Idna.php index 61c0dd9c..d68e6795 100644 --- a/src/Idna.php +++ b/src/Idna.php @@ -16,10 +16,13 @@ use const INTL_IDNA_VARIANT_UTS46; /** + * @internal * @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html */ final class Idna { + private const REGEXP_IDNA_PATTERN = '/[^\x20-\x7f]/'; + /** * IDNA options. */ @@ -32,6 +35,28 @@ final class Idna public const IDNA_NONTRANSITIONAL_TO_UNICODE = 0x20; public const IDNA_CHECK_CONTEXTO = 0x40; + /** + * IDNA errors. + */ + public const ERROR_EMPTY_LABEL = 1; + public const ERROR_LABEL_TOO_LONG = 2; + public const ERROR_DOMAIN_NAME_TOO_LONG = 4; + public const ERROR_LEADING_HYPHEN = 8; + public const ERROR_TRAILING_HYPHEN = 0x10; + public const ERROR_HYPHEN_3_4 = 0x20; + public const ERROR_LEADING_COMBINING_MARK = 0x40; + public const ERROR_DISALLOWED = 0x80; + public const ERROR_PUNYCODE = 0x100; + public const ERROR_LABEL_HAS_DOT = 0x200; + public const ERROR_INVALID_ACE_LABEL = 0x400; + public const ERROR_BIDI = 0x800; + public const ERROR_CONTEXTJ = 0x1000; + public const ERROR_CONTEXTO_PUNCTUATION = 0x2000; + public const ERROR_CONTEXTO_DIGITS = 0x4000; + + /** + * IDNA default options. + */ public const IDNA2008_ASCII = self::IDNA_NONTRANSITIONAL_TO_ASCII | self::IDNA_CHECK_BIDI | self::IDNA_USE_STD3_RULES @@ -42,7 +67,6 @@ final class Idna | self::IDNA_CHECK_CONTEXTJ; public const IDNA2003_ASCII = self::IDNA_DEFAULT; public const IDNA2003_UNICODE = self::IDNA_DEFAULT; - private const REGEXP_IDNA_PATTERN = '/[^\x20-\x7f]/'; /** * @codeCoverageIgnore diff --git a/src/IdnaInfo.php b/src/IdnaInfo.php index 51a84bd2..e35bc159 100644 --- a/src/IdnaInfo.php +++ b/src/IdnaInfo.php @@ -8,44 +8,27 @@ use const ARRAY_FILTER_USE_KEY; /** + * @internal * @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html */ final class IdnaInfo { - /** - * IDNA errors. - */ - public const ERROR_EMPTY_LABEL = 1; - public const ERROR_LABEL_TOO_LONG = 2; - public const ERROR_DOMAIN_NAME_TOO_LONG = 4; - public const ERROR_LEADING_HYPHEN = 8; - public const ERROR_TRAILING_HYPHEN = 0x10; - public const ERROR_HYPHEN_3_4 = 0x20; - public const ERROR_LEADING_COMBINING_MARK = 0x40; - public const ERROR_DISALLOWED = 0x80; - public const ERROR_PUNYCODE = 0x100; - public const ERROR_LABEL_HAS_DOT = 0x200; - public const ERROR_INVALID_ACE_LABEL = 0x400; - public const ERROR_BIDI = 0x800; - public const ERROR_CONTEXTJ = 0x1000; - public const ERROR_CONTEXTO_PUNCTUATION = 0x2000; - public const ERROR_CONTEXTO_DIGITS = 0x4000; private const ERRORS = [ - self::ERROR_EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty', - self::ERROR_LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes', - self::ERROR_DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form', - self::ERROR_LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")', - self::ERROR_TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")', - self::ERROR_HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions', - self::ERROR_LEADING_COMBINING_MARK => 'a label starts with a combining mark', - self::ERROR_DISALLOWED => 'a label or domain name contains disallowed characters', - self::ERROR_PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode', - self::ERROR_LABEL_HAS_DOT => 'a label contains a dot=full stop', - self::ERROR_INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string', - self::ERROR_BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)', - self::ERROR_CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements', - self::ERROR_CONTEXTO_DIGITS => 'a label does not meet the IDNA CONTEXTO requirements for digits', - self::ERROR_CONTEXTO_PUNCTUATION => 'a label does not meet the IDNA CONTEXTO requirements for punctuation characters. Some punctuation characters "Would otherwise have been DISALLOWED" but are allowed in certain contexts', + Idna::ERROR_EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty', + Idna::ERROR_LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes', + Idna::ERROR_DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form', + Idna::ERROR_LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")', + Idna::ERROR_TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")', + Idna::ERROR_HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions', + Idna::ERROR_LEADING_COMBINING_MARK => 'a label starts with a combining mark', + Idna::ERROR_DISALLOWED => 'a label or domain name contains disallowed characters', + Idna::ERROR_PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode', + Idna::ERROR_LABEL_HAS_DOT => 'a label contains a dot=full stop', + Idna::ERROR_INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string', + Idna::ERROR_BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)', + Idna::ERROR_CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements', + Idna::ERROR_CONTEXTO_DIGITS => 'a label does not meet the IDNA CONTEXTO requirements for digits', + Idna::ERROR_CONTEXTO_PUNCTUATION => 'a label does not meet the IDNA CONTEXTO requirements for punctuation characters. Some punctuation characters "Would otherwise have been DISALLOWED" but are allowed in certain contexts', ]; private string $result; diff --git a/src/IdnaInfoTest.php b/src/IdnaInfoTest.php index bb62e782..8dd39356 100644 --- a/src/IdnaInfoTest.php +++ b/src/IdnaInfoTest.php @@ -27,7 +27,7 @@ public function testItCanBeInstantiatedFromArray(): void self::assertSame('', $result->result()); self::assertFalse($result->isTransitionalDifferent()); self::assertSame(0, $result->errors()); - self::assertNull($result->error(IdnaInfo::ERROR_BIDI)); + self::assertNull($result->error(Idna::ERROR_BIDI)); self::assertCount(0, $result->errorList()); } @@ -38,8 +38,8 @@ public function testInvalidSyntaxAfterIDNConversion(): void } catch (SyntaxError $exception) { $result = $exception->idnaInfo(); self::assertInstanceOf(IdnaInfo::class, $result); - self::assertSame(IdnaInfo::ERROR_DISALLOWED, $result->errors()); - self::assertIsString($result->error(IdnaInfo::ERROR_DISALLOWED)); + self::assertSame(Idna::ERROR_DISALLOWED, $result->errors()); + self::assertIsString($result->error(Idna::ERROR_DISALLOWED)); self::assertCount(1, $result->errorList()); } } diff --git a/src/Storage/PublicSuffixListPsr16Cache.php b/src/Storage/PublicSuffixListPsr16Cache.php index 03a5a3c9..a49445fc 100644 --- a/src/Storage/PublicSuffixListPsr16Cache.php +++ b/src/Storage/PublicSuffixListPsr16Cache.php @@ -6,7 +6,6 @@ use DateInterval; use DateTimeInterface; -use InvalidArgumentException; use Pdp\PublicSuffixList; use Psr\SimpleCache\CacheException; use Psr\SimpleCache\CacheInterface; @@ -29,25 +28,7 @@ public function __construct(CacheInterface $cache, string $cachePrefix = '', $ca { $this->cache = $cache; $this->cachePrefix = $cachePrefix; - $this->cacheTtl = $this->setCacheTtl($cacheTtl); - } - - /** - * @param DateInterval|DateTimeInterface|object|int|string|null $cacheTtl storage TTL object should implement the __toString method - * - * @throws InvalidArgumentException if the value can not be computed - */ - private function setCacheTtl($cacheTtl): ?DateInterval - { - if ($cacheTtl instanceof DateInterval || null === $cacheTtl) { - return $cacheTtl; - } - - if ($cacheTtl instanceof DateTimeInterface) { - return TimeToLive::until($cacheTtl); - } - - return TimeToLive::fromDurationString($cacheTtl); + $this->cacheTtl = TimeToLive::convert($cacheTtl); } public function fetch(string $uri): ?PublicSuffixList diff --git a/src/Storage/TimeToLive.php b/src/Storage/TimeToLive.php index 3dadaef4..d4155fe7 100644 --- a/src/Storage/TimeToLive.php +++ b/src/Storage/TimeToLive.php @@ -11,52 +11,27 @@ use TypeError; use function filter_var; use function is_object; -use function is_string; use function method_exists; use const FILTER_VALIDATE_INT; +/** + * @internal + */ final class TimeToLive { - /** - * Returns a DateInterval from string parsing. - * - * @param object|int|string $duration storage TTL object should implement the __toString method - */ - public static function fromDurationString($duration): DateInterval + public static function fromDurationString(string $duration): DateInterval { - if (is_object($duration) && method_exists($duration, '__toString')) { - $duration = (string) $duration; - } - - if (false !== ($seconds = filter_var($duration, FILTER_VALIDATE_INT))) { - if (-1 < $seconds) { - return new DateInterval('PT'.$seconds.'S'); - } - - $interval = new DateInterval('PT'.($seconds * -1).'S'); - $interval->invert = 1; - - return $interval; - } - - if (!is_string($duration)) { - throw new TypeError('The ttl must null, an integer, a string, or a stringable object.'); - } - - /** @var DateInterval|false $date */ - $date = @DateInterval::createFromDateString($duration); - if (!$date instanceof DateInterval) { + /** @var DateInterval|false $interval */ + $interval = @DateInterval::createFromDateString($duration); + if (!$interval instanceof DateInterval) { throw new InvalidArgumentException( 'The ttl value "'.$duration.'" can not be parsable by `DateInterval::createFromDateString`.' ); } - return $date; + return $interval; } - /** - * Returns a DateInterval relative to the current date and time. - */ public static function until(DateTimeInterface $date): DateInterval { return (new DateTimeImmutable('NOW', $date->getTimezone()))->diff($date, false); @@ -79,19 +54,28 @@ public static function fromDateTimeInterface(DateTimeInterface $date): DateInter /** * Returns a DateInterval from string parsing. * - * @param object|int|string $duration storage TTL object should implement the __toString method - * - * @throws InvalidArgumentException if the value can not be parsable - * * DEPRECATION WARNING! This method will be removed in the next major point release * * @deprecated 6.1.0 deprecated - * @codeCoverageIgnore * @see TimeToLive::fromDurationString + * @codeCoverageIgnore + * + * @param object|int|string $duration storage TTL object should implement the __toString method + * + * @throws InvalidArgumentException if the value can not be parsable + * */ public static function fromScalar($duration): DateInterval { - return self::fromDurationString($duration); + if (is_object($duration) && method_exists($duration, '__toString')) { + $duration = (string) $duration; + } + + if (!is_scalar($duration)) { + throw new TypeError('The duration type is unsupported or is an non stringable object.'); + } + + return self::fromDurationString((string) $duration); } /** @@ -101,11 +85,6 @@ public static function fromScalar($duration): DateInterval * * @throws InvalidArgumentException if the value can not be computed * @throws TypeError if the value type is not recognized - * - * DEPRECATION WARNING! This method will be removed in the next major point release - * - * @deprecated 6.1.0 deprecated - * @codeCoverageIgnore */ public static function convert($ttl): ?DateInterval { @@ -117,6 +96,18 @@ public static function convert($ttl): ?DateInterval return self::until($ttl); } + if (is_object($ttl) && method_exists($ttl, '__toString')) { + $ttl = (string) $ttl; + } + + if (false !== ($seconds = filter_var($ttl, FILTER_VALIDATE_INT))) { + return self::fromDurationString($seconds.' seconds'); + } + + if (!is_string($ttl)) { + throw new TypeError('The duration type is unsupported or is an non stringable object.'); + } + return self::fromDurationString($ttl); } } diff --git a/src/Storage/TimeToLiveTest.php b/src/Storage/TimeToLiveTest.php index 8e2ed4c2..febb0fde 100644 --- a/src/Storage/TimeToLiveTest.php +++ b/src/Storage/TimeToLiveTest.php @@ -21,39 +21,69 @@ public function testItDoesNotReturnTheAbsoluteInterval(): void } /** - * @param object|string|int $input * @dataProvider validDurationString */ - public function testItCanBeInstantiatedFromDurationInput($input, DateInterval $expected): void + public function testItCanBeInstantiatedFromDurationInput(string $input, DateInterval $expected): void { self::assertEquals($expected, TimeToLive::fromDurationString($input)); } /** - * @return iterable + * @return iterable */ public function validDurationString(): iterable { $threeDays = new DateInterval('P3D'); + yield 'string' => [ + 'input' => '3 days', + 'expected' => $threeDays, + ]; + + yield 'stringable negative days' => [ + 'input' => '-3 days', + 'expected' => DateInterval::createFromDateString('-3 DAYS'), + ]; + } + + /** + * @param int|string|object|null|DateInterval $input + * @param ?DateInterval $expected + * @dataProvider validDurationInt + */ + public function testItCanBeInstantiatedFromSeconds($input, ?DateInterval $expected): void + { + self::assertEquals($expected, TimeToLive::convert($input)); + } + + /** + * @return iterable + */ + public function validDurationInt(): iterable + { + $seconds = new DateInterval('PT2345S'); + + yield 'DateInterval' => [ + 'input' => $seconds, + 'expected' => $seconds, + ]; + + yield 'null' => [ + 'input' => null, + 'expected' => null, + ]; + yield 'stringable object' => [ 'input' => new class() { public function __toString(): string { - return '3 days'; + return '2345'; } }, - 'expected' => $threeDays, - ]; - - yield 'string' => [ - 'input' => '3 days', - 'expected' => $threeDays, + 'expected' => $seconds, ]; - $seconds = new DateInterval('PT2345S'); - - yield 'stringable seconds' => [ + yield 'numeric string' => [ 'input' => '2345', 'expected' => $seconds, ]; @@ -63,12 +93,9 @@ public function __toString(): string 'expected' => $seconds, ]; - $negativeInterval = clone $seconds; - $negativeInterval->invert = 1; - yield 'negative seconds' => [ 'input' => '-2345', - 'expected' => $negativeInterval, + 'expected' => DateInterval::createFromDateString('-2345 seconds'), ]; } } diff --git a/src/Storage/TopLevelDomainListPsr16Cache.php b/src/Storage/TopLevelDomainListPsr16Cache.php index 36e2195f..e0838fac 100644 --- a/src/Storage/TopLevelDomainListPsr16Cache.php +++ b/src/Storage/TopLevelDomainListPsr16Cache.php @@ -6,7 +6,6 @@ use DateInterval; use DateTimeInterface; -use InvalidArgumentException; use Pdp\TopLevelDomainList; use Psr\SimpleCache\CacheException; use Psr\SimpleCache\CacheInterface; @@ -29,25 +28,7 @@ public function __construct(CacheInterface $cache, string $cachePrefix = '', $ca { $this->cache = $cache; $this->cachePrefix = $cachePrefix; - $this->cacheTtl = $this->setCacheTtl($cacheTtl); - } - - /** - * @param DateInterval|DateTimeInterface|object|int|string|null $cacheTtl storage TTL object should implement the __toString method - * - * @throws InvalidArgumentException if the value can not be computed - */ - private function setCacheTtl($cacheTtl): ?DateInterval - { - if ($cacheTtl instanceof DateInterval || null === $cacheTtl) { - return $cacheTtl; - } - - if ($cacheTtl instanceof DateTimeInterface) { - return TimeToLive::until($cacheTtl); - } - - return TimeToLive::fromDurationString($cacheTtl); + $this->cacheTtl = TimeToLive::convert($cacheTtl); } public function fetch(string $uri): ?TopLevelDomainList From 753cae23f7518e053217f3d410de14b405de6457 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Fri, 18 Jun 2021 08:06:23 +0200 Subject: [PATCH 08/13] Fix UnableToResolveDomain exception messqge --- src/UnableToResolveDomain.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnableToResolveDomain.php b/src/UnableToResolveDomain.php index 496e052e..dd5278a9 100644 --- a/src/UnableToResolveDomain.php +++ b/src/UnableToResolveDomain.php @@ -23,7 +23,7 @@ public static function dueToInvalidSecondLevelDomain(DomainName $domain): self public static function dueToIdenticalValue(DomainName $domain): self { - return new self('The public suffix and the domain name are is identical `'.$domain->toString().'`.', $domain); + return new self('The public suffix and the domain name are identical `'.$domain->toString().'`.', $domain); } public static function dueToMissingSuffix(DomainName $domain, string $type): self From 421f199361f81ce098cf154841653aac1ce97edc Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Fri, 18 Jun 2021 08:07:10 +0200 Subject: [PATCH 09/13] Host::toUnicode must never throws according to RFC3490 --- src/DomainTest.php | 9 +++++---- src/Idna.php | 12 ++++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/DomainTest.php b/src/DomainTest.php index 1c32a146..aece2374 100644 --- a/src/DomainTest.php +++ b/src/DomainTest.php @@ -35,11 +35,12 @@ public function invalidDomainProvider(): iterable ]; } - public function testToUnicodeThrowsException(): void + public function testToUnicodeNeverThrowsException(): void { - $this->expectException(SyntaxError::class); - - Domain::fromIDNA2008('xn--a-ecp.ru')->toUnicode(); + self::assertSame( + 'xn--a-ecp.ru', + Domain::fromIDNA2008('xN--a-eCp.rU')->toUnicode()->toString() + ); } public function testDomainInternalPhpMethod(): void diff --git a/src/Idna.php b/src/Idna.php index d68e6795..b3b03f27 100644 --- a/src/Idna.php +++ b/src/Idna.php @@ -124,8 +124,16 @@ public static function toUnicode(string $domain, int $options): IdnaInfo /** @param-out array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */ idn_to_utf8($domain, $options, INTL_IDNA_VARIANT_UTS46, $idnaInfo); - /** @var array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */ - return self::createIdnaInfo($domain, $idnaInfo); + try { + /** @var array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */ + return self::createIdnaInfo($domain, $idnaInfo); + } catch (SyntaxError $exception) { + if ($exception->idnaInfo() instanceof IdnaInfo) { + return IdnaInfo::fromIntl(['result' => $domain, 'isTransitionalDifferent' => false, 'errors' => 0]); + } + + throw $exception; + } } /** From 7e6eae561a20491c30afd6a864e2c5a0267126c8 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Fri, 18 Jun 2021 08:07:25 +0200 Subject: [PATCH 10/13] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dca7eee..bdd83675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will - `.gitattributes` files to be filter out. - `TimeToLive` marked as internal +- `Host::toUnicode` method MUST never throw exceptions on conversion according to RFC3490. +- `UnableToResolveDomain` typo in the exception message ### Deprecated From d9b5435ca3db7f545e6612d21ee8316324af92d2 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Sat, 19 Jun 2021 08:28:03 +0200 Subject: [PATCH 11/13] Bugfix named parameter mismatch --- src/Idna.php | 31 +++++++++---------------------- src/TopLevelDomains.php | 6 +++--- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/Idna.php b/src/Idna.php index b3b03f27..3867eb10 100644 --- a/src/Idna.php +++ b/src/Idna.php @@ -102,16 +102,19 @@ public static function toAscii(string $domain, int $options): IdnaInfo /** @param-out array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */ idn_to_ascii($domain, $options, INTL_IDNA_VARIANT_UTS46, $idnaInfo); - /** @var array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */ - return self::createIdnaInfo($domain, $idnaInfo); + /** @var array{result:string, isTransitionalDifferent:bool, errors:int} $idnaInfo */ + $info = IdnaInfo::fromIntl($idnaInfo); + if (0 !== $info->errors()) { + throw SyntaxError::dueToIDNAError($domain, $info); + } + + return $info; } /** * Converts the input to its IDNA UNICODE form. * * This method returns the string converted to IDN UNICODE form - * - * @throws SyntaxError if the string can not be converted to UNICODE using IDN UTS46 algorithm */ public static function toUnicode(string $domain, int $options): IdnaInfo { @@ -124,26 +127,10 @@ public static function toUnicode(string $domain, int $options): IdnaInfo /** @param-out array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */ idn_to_utf8($domain, $options, INTL_IDNA_VARIANT_UTS46, $idnaInfo); - try { - /** @var array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */ - return self::createIdnaInfo($domain, $idnaInfo); - } catch (SyntaxError $exception) { - if ($exception->idnaInfo() instanceof IdnaInfo) { - return IdnaInfo::fromIntl(['result' => $domain, 'isTransitionalDifferent' => false, 'errors' => 0]); - } - - throw $exception; - } - } - - /** - * @param array{result:string, isTransitionalDifferent:bool, errors:int} $idnaInfo - */ - private static function createIdnaInfo(string $domain, array $idnaInfo): IdnaInfo - { + /** @var array{result:string, isTransitionalDifferent:bool, errors:int} $idnaInfo */ $info = IdnaInfo::fromIntl($idnaInfo); if (0 !== $info->errors()) { - throw SyntaxError::dueToIDNAError($domain, $info); + return IdnaInfo::fromIntl(['result' => $domain, 'isTransitionalDifferent' => false, 'errors' => 0]); } return $info; diff --git a/src/TopLevelDomains.php b/src/TopLevelDomains.php index 4623ad07..d97d0065 100644 --- a/src/TopLevelDomains.php +++ b/src/TopLevelDomains.php @@ -241,11 +241,11 @@ private function containsTopLevelDomain(DomainName $domain): bool } /** - * @param mixed $domain a domain in a type that can be converted into a DomainInterface instance + * @param mixed $host a domain in a type that can be converted into a DomainInterface instance */ - public function getIANADomain($domain): ResolvedDomainName + public function getIANADomain($host): ResolvedDomainName { - $domain = $this->validateDomain($domain); + $domain = $this->validateDomain($host); if (!$this->containsTopLevelDomain($domain)) { throw UnableToResolveDomain::dueToMissingSuffix($domain, 'IANA'); } From 2df74130b307284cc8266e78ec114b811d6c67c6 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Sat, 19 Jun 2021 08:28:29 +0200 Subject: [PATCH 12/13] Update package php-cs-fixer settings --- .php_cs => .php-cs-fixer.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename .php_cs => .php-cs-fixer.php (87%) diff --git a/.php_cs b/.php-cs-fixer.php similarity index 87% rename from .php_cs rename to .php-cs-fixer.php index 56145cef..5560b22f 100644 --- a/.php_cs +++ b/.php-cs-fixer.php @@ -4,7 +4,9 @@ ->in(__DIR__.'/src') ; -return PhpCsFixer\Config::create() +$config = new PhpCsFixer\Config(); + +return $config ->setRules([ '@PSR2' => true, 'array_syntax' => ['syntax' => 'short'], @@ -16,7 +18,6 @@ 'no_leading_import_slash' => true, 'no_superfluous_phpdoc_tags' => true, 'no_trailing_comma_in_singleline_array' => true, - 'no_superfluous_phpdoc_tags' => true, 'no_unused_imports' => true, 'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'], 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], @@ -26,14 +27,13 @@ 'phpdoc_scalar' => true, 'phpdoc_to_comment' => false, 'phpdoc_summary' => true, - 'psr0' => true, - 'psr4' => true, + 'psr_autoloading' => true, 'return_type_declaration' => ['space_before' => 'none'], 'single_blank_line_before_namespace' => true, 'single_quote' => true, 'space_after_semicolon' => true, 'ternary_operator_spaces' => true, - 'trailing_comma_in_multiline_array' => true, + 'trailing_comma_in_multiline' => true, 'trim_array_spaces' => true, 'whitespace_after_comma_in_array' => true, 'yoda_style' => true, From 8607e36e1e39ce9ceb4398dda69567beff724453 Mon Sep 17 00:00:00 2001 From: Ignace Nyamagana Butera Date: Sat, 19 Jun 2021 09:47:13 +0200 Subject: [PATCH 13/13] Prepare 6.1.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdd83675..84076d5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All Notable changes to `PHP Domain Parser` starting from the **5.x** series will be documented in this file -## Next - TBD +## 6.1.0 - 2021-06-19 ### Added