feat(domain-formatter): decode punycode hosts to Cyrillic with fallbacks
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Cf7stg;
|
||||||
|
|
||||||
|
final class DomainFormatter
|
||||||
|
{
|
||||||
|
public static function humanize(string $input): string
|
||||||
|
{
|
||||||
|
$input = trim($input);
|
||||||
|
if ($input === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize: strip scheme, userinfo, path, query, fragment
|
||||||
|
$host = $input;
|
||||||
|
if (preg_match('#^[a-z][a-z0-9+\-.]*://#i', $host)) {
|
||||||
|
$parsed = parse_url($host);
|
||||||
|
$host = $parsed['host'] ?? $host;
|
||||||
|
}
|
||||||
|
// Strip any remaining path/query
|
||||||
|
$host = preg_replace('~[/?#].*$~', '', $host) ?? $host;
|
||||||
|
// Strip leading www.
|
||||||
|
$host = preg_replace('/^www\./i', '', $host) ?? $host;
|
||||||
|
|
||||||
|
if ($host === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Punycode → Unicode per label (.xn-- pieces may appear on only one side)
|
||||||
|
if (function_exists('idn_to_utf8') && strpos($host, 'xn--') !== false) {
|
||||||
|
$variant = defined('INTL_IDNA_VARIANT_UTS46')
|
||||||
|
? INTL_IDNA_VARIANT_UTS46
|
||||||
|
: 0;
|
||||||
|
$decoded = @idn_to_utf8($host, IDNA_DEFAULT, $variant);
|
||||||
|
if (is_string($decoded) && $decoded !== '') {
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $host;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Cf7stg\Tests;
|
||||||
|
|
||||||
|
use Cf7stg\DomainFormatter;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class DomainFormatterTest extends TestCase
|
||||||
|
{
|
||||||
|
public function test_decodes_punycode_to_cyrillic(): void
|
||||||
|
{
|
||||||
|
self::assertSame(
|
||||||
|
'вашаняня.рф',
|
||||||
|
DomainFormatter::humanize('xn--80aae0ca7d8bb.xn--p1ai')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_latin_domain_unchanged(): void
|
||||||
|
{
|
||||||
|
self::assertSame(
|
||||||
|
'washanyanya.ru',
|
||||||
|
DomainFormatter::humanize('washanyanya.ru')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_strips_scheme_and_path(): void
|
||||||
|
{
|
||||||
|
self::assertSame(
|
||||||
|
'washanyanya.ru',
|
||||||
|
DomainFormatter::humanize('https://washanyanya.ru/contact/?a=1')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_strips_leading_www(): void
|
||||||
|
{
|
||||||
|
self::assertSame(
|
||||||
|
'washanyanya.ru',
|
||||||
|
DomainFormatter::humanize('www.washanyanya.ru')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_invalid_input_returns_empty_string(): void
|
||||||
|
{
|
||||||
|
self::assertSame('', DomainFormatter::humanize(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_broken_idn_returns_original(): void
|
||||||
|
{
|
||||||
|
// Looks like IDN but is garbage; idn_to_utf8 returns false → we fall back
|
||||||
|
self::assertSame('xn--', DomainFormatter::humanize('xn--'));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user