Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
jobs:
linux_tests:
name: PHP on ${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.composer-flags }}
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
php: ['8.1', '8.2', '8.3', '8.4']
Expand All @@ -32,7 +32,7 @@ jobs:
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.stability }}-${{ matrix.flags }}-${{ hashFiles('**/composer.lock') }}
Expand Down
15 changes: 7 additions & 8 deletions .php-cs-fixer.php
Original file line number Diff line number Diff line change
@@ -1,38 +1,36 @@
<?php

use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;

$finder = PhpCsFixer\Finder::create()
->in(__DIR__.'/src')
;

$config = new PhpCsFixer\Config();

return $config
->setParallelConfig(ParallelConfigFactory::detect())
->setRules([
'@PSR2' => true,
'@PSR12' => true,
'array_syntax' => ['syntax' => 'short'],
'concat_space' => ['spacing' => 'none'],
'global_namespace_import' => [
'import_classes' => true,
'import_constants' => true,
'import_functions' => true,
],
'list_syntax' => ['syntax' => 'short'],
'new_with_parentheses' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_phpdoc' => true,
'no_empty_comment' => true,
'no_leading_import_slash' => true,
'no_superfluous_phpdoc_tags' => [
'allow_mixed' => true,
'remove_inheritdoc' => true,
'allow_unused_params' => false,
],
'no_superfluous_phpdoc_tags' => true,
'no_trailing_comma_in_singleline' => true,
'no_unused_imports' => true,
'nullable_type_declaration_for_default_null_value' => true,
'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'],
'phpdoc_add_missing_param_annotation' => ['only_untyped' => true],
'phpdoc_align' => true,
'phpdoc_align' => ['align' => 'left'],
'phpdoc_no_empty_return' => true,
'phpdoc_order' => true,
'phpdoc_scalar' => true,
Expand All @@ -47,5 +45,6 @@
'trailing_comma_in_multiline' => true,
'trim_array_spaces' => true,
'whitespace_after_comma_in_array' => true,
'yoda_style' => true,
])
->setFinder($finder);
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

All Notable changes to `PHP Domain Parser` starting from the **5.x** series will be documented in this file

## [6.4.0] - 2025-04-22

### Added

- `DomainName::withRootLabel`, `DomainName::withoutRootLabel`, `DomainName::isAbsolute` methods to handle absolute domain names.

### Fixed

- Absolute domain name can now also be resolved by the package see issue [#361](https://github.com/jeremykendall/php-domain-parser/issues/361) prior to this release an exception was thrown.
- Since we no longer support PHP7 type hint and return type are improved.

### Deprecated

- None

### Removed

- None

## [6.3.0] - 2023-02-25

### Added
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ composer require jeremykendall/php-domain-parser:^6.0

You need:

- **PHP >= 7.4** but the latest stable version of PHP is recommended
- the `intl` extension
- **PHP >= 8.1** but the latest stable version of PHP is recommended
- a copy of the [Public Suffix List](https://publicsuffix.org/) data and/or a copy of the [IANA Top Level Domain List](https://www.iana.org/domains/root/files). Please refer to the [Managing external data source section](#managing-the-package-external-resources) for more information when using this package in production.

Handling of an IDN host requires the presence of the `intl` extension or
a polyfill for the `intl` IDN functions like the `symfony/polyfill-intl-idn`
otherwise an exception will be thrown when attempting to validate or interact
with such a host.

## Usage

> [!WARNING]
Expand Down
10 changes: 6 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@
],
"require": {
"php": "^8.1",
"ext-filter": "*",
"ext-intl": "*"
"ext-filter": "*"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.65.0",
Expand All @@ -54,13 +53,16 @@
"phpunit/phpunit": "^10.5.15 || ^11.5.1",
"psr/http-factory": "^1.1.0",
"psr/simple-cache": "^1.0.1 || ^2.0.0",
"symfony/cache": "^v5.0.0 || ^6.4.16"
"symfony/cache": "^v5.0.0 || ^6.4.16",
"symfony/var-dumper": "^v6.4.18 || ^7.2"
},
"suggest": {
"psr/http-client-implementation": "To use the storage functionality which depends on PSR-18",
"psr/http-factory-implementation": "To use the storage functionality which depends on PSR-17",
"psr/simple-cache-implementation": "To use the storage functionality which depends on PSR-16",
"league/uri": "To parse URL and validate host"
"league/uri": "To parse and extract the host from an URL using a RFC3986/RFC3987 URI parser",
"rowbot/url": "To parse and extract the host from an URL using a WHATWG URL parser",
"symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
},
"autoload": {
"psr-4": {
Expand Down
24 changes: 24 additions & 0 deletions src/Domain.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Iterator;
use Stringable;

use const FILTER_FLAG_IPV4;
use const FILTER_VALIDATE_IP;

Expand Down Expand Up @@ -159,4 +160,27 @@ public function slice(int $offset, ?int $length = null): self
{
return $this->newInstance($this->registeredName->slice($offset, $length));
}

public function withRootLabel(): self
{
if ('' === $this->label(0)) {
return $this;
}

return $this->append('');
}

public function withoutRootLabel(): self
{
if ('' === $this->label(0)) {
return $this->slice(1);
}

return $this;
}

public function isAbsolute(): bool
{
return '' === $this->label(0);
}
}
6 changes: 6 additions & 0 deletions src/DomainName.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
* @see https://tools.ietf.org/html/rfc5890
*
* @extends IteratorAggregate<string>
*
* @method bool isAbsolute() tells whether the domain is absolute or not.
* @method self withRootLabel() returns an instance with its Root label. (see https://tools.ietf.org/html/rfc3986#section-3.2.2)
* @method self withoutRootLabel() returns an instance without its Root label. (see https://tools.ietf.org/html/rfc3986#section-3.2.2)
*/
interface DomainName extends Host, IteratorAggregate
{
Expand Down Expand Up @@ -120,4 +124,6 @@ public function clear(): self;
* If $length is null it returns all elements from $offset to the end of the Domain.
*/
public function slice(int $offset, ?int $length = null): self;


}
8 changes: 0 additions & 8 deletions src/DomainTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use stdClass;
use TypeError;

final class DomainTest extends TestCase
{
Expand Down Expand Up @@ -290,12 +288,6 @@ public static function withLabelWorksProvider(): iterable
];
}

public function testWithLabelFailsWithTypeError(): void
{
$this->expectException(TypeError::class);
Domain::fromIDNA2008('example.com')->withLabel(1, new stdClass()); /* @phpstan-ignore-line */
}

public function testWithLabelFailsWithInvalidKey(): void
{
$this->expectException(SyntaxError::class);
Expand Down
2 changes: 2 additions & 0 deletions src/Idna.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
namespace Pdp;

use UnexpectedValueException;

use function defined;
use function function_exists;
use function idn_to_ascii;
use function idn_to_utf8;
use function preg_match;
use function rawurldecode;
use function strtolower;

use const INTL_IDNA_VARIANT_UTS46;

/**
Expand Down
1 change: 1 addition & 0 deletions src/IdnaInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Pdp;

use function array_filter;

use const ARRAY_FILTER_USE_KEY;

/**
Expand Down
1 change: 1 addition & 0 deletions src/IdnaInfoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Pdp;

use PHPUnit\Framework\TestCase;

use function var_export;

final class IdnaInfoTest extends TestCase
Expand Down
6 changes: 3 additions & 3 deletions src/PublicSuffixList.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@ interface PublicSuffixList extends DomainNameResolver
/**
* Returns PSL info for a given domain against the PSL rules for cookie domain detection.
*
* @throws SyntaxError if the domain is invalid
* @throws SyntaxError if the domain is invalid
* @throws UnableToResolveDomain if the effective TLD can not be resolved
*/
public function getCookieDomain(Host $host): ResolvedDomainName;

/**
* Returns PSL info for a given domain against the PSL rules for ICANN domain detection.
*
* @throws SyntaxError if the domain is invalid
* @throws SyntaxError if the domain is invalid
* @throws UnableToResolveDomain if the domain does not contain a ICANN Effective TLD
*/
public function getICANNDomain(Host $host): ResolvedDomainName;

/**
* Returns PSL info for a given domain against the PSL rules for private domain detection.
*
* @throws SyntaxError if the domain is invalid
* @throws SyntaxError if the domain is invalid
* @throws UnableToResolveDomain if the domain does not contain a private Effective TLD
*/
public function getPrivateDomain(Host $host): ResolvedDomainName;
Expand Down
25 changes: 25 additions & 0 deletions src/RegisteredName.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Iterator;
use Stringable;

use function array_count_values;
use function array_keys;
use function array_reverse;
Expand All @@ -19,6 +20,7 @@
use function preg_match;
use function rawurldecode;
use function strtolower;

use const FILTER_FLAG_IPV4;
use const FILTER_VALIDATE_IP;

Expand Down Expand Up @@ -359,4 +361,27 @@ public function slice(int $offset, ?int $length = null): self

return new self($this->type, [] === $labels ? null : implode('.', array_reverse($labels)));
}

public function withRootLabel(): self
{
if ('' === $this->label(0)) {
return $this;
}

return $this->append('');
}

public function withoutRootLabel(): self
{
if ('' === $this->label(0)) {
return $this->slice(1);
}

return $this;
}

public function isAbsolute(): bool
{
return '' === $this->label(0);
}
}
17 changes: 11 additions & 6 deletions src/ResolvedDomain.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Pdp;

use Stringable;

use function count;

final class ResolvedDomain implements ResolvedDomainName
Expand Down Expand Up @@ -34,7 +35,7 @@ public static function fromICANN(DomainNameProvider|Host|Stringable|string|int|n
{
$domain = self::setDomainName($domain);

return new self($domain, Suffix::fromICANN($domain->slice(0, $suffixLength)));
return new self($domain, Suffix::fromICANN($domain->withoutRootLabel()->slice(0, $suffixLength)));
}

/**
Expand All @@ -44,7 +45,7 @@ public static function fromPrivate(DomainNameProvider|Host|Stringable|string|int
{
$domain = self::setDomainName($domain);

return new self($domain, Suffix::fromPrivate($domain->slice(0, $suffixLength)));
return new self($domain, Suffix::fromPrivate($domain->withoutRootLabel()->slice(0, $suffixLength)));
}

/**
Expand All @@ -54,7 +55,7 @@ public static function fromIANA(DomainNameProvider|Host|Stringable|string|int|nu
{
$domain = self::setDomainName($domain);

return new self($domain, Suffix::fromIANA($domain->label(0)));
return new self($domain, Suffix::fromIANA($domain->withoutRootLabel()->label(0)));
}

/**
Expand Down Expand Up @@ -118,11 +119,15 @@ private function parse(): array
}

$length = count($this->suffix);
$offset = 0;
if ($this->domain->isAbsolute()) {
$offset = 1;
}

return [
'registrableDomain' => $this->domain->slice(0, $length + 1),
'secondLevelDomain' => $this->domain->slice($length, 1),
'subDomain' => RegisteredName::fromIDNA2008($this->domain->value())->slice($length + 1),
'registrableDomain' => $this->domain->slice($offset, $length + 1),
'secondLevelDomain' => $this->domain->slice($length + $offset, 1),
'subDomain' => RegisteredName::fromIDNA2008($this->domain->value())->slice($length + 1 + $offset),
];
}

Expand Down
Loading