mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-08 08:57:03 +03:00
Handle Domain::$name as lowercase punycode due to IDN complexity
This commit is contained in:
parent
56ede16833
commit
7999894bda
7 changed files with 89 additions and 129 deletions
|
|
@ -115,11 +115,10 @@ class Http
|
||||||
if (!\strlen($sHost)) {
|
if (!\strlen($sHost)) {
|
||||||
$sName = static::GetServer('SERVER_NAME', '');
|
$sName = static::GetServer('SERVER_NAME', '');
|
||||||
$iPort = (int) static::GetServer('SERVER_PORT', 80);
|
$iPort = (int) static::GetServer('SERVER_PORT', 80);
|
||||||
|
|
||||||
$sHost = (\in_array($iPort, array(80, 433))) ? $sName : $sName.':'.$iPort;
|
$sHost = (\in_array($iPort, array(80, 433))) ? $sName : $sName.':'.$iPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($bWithoutWWW && 'www.' === \substr(\strtolower($sHost), 0, 4)) {
|
if ($bWithoutWWW && \str_starts_with($sHost, 'www.')) {
|
||||||
$sHost = \substr($sHost, 4);
|
$sHost = \substr($sHost, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -685,19 +685,6 @@ abstract class Utils
|
||||||
return \sha1($sAdditionalSalt . \random_bytes(16));
|
return \sha1($sAdditionalSalt . \random_bytes(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function ValidateDomain(string $sDomain, bool $bSimple = false) : bool
|
|
||||||
{
|
|
||||||
$aMatch = array();
|
|
||||||
if ($bSimple) {
|
|
||||||
return \preg_match('/.+(\.[a-zA-Z]+)$/', $sDomain, $aMatch) && !empty($aMatch[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return \preg_match('/.+(\.[a-zA-Z]+)$/', $sDomain, $aMatch) && !empty($aMatch[1]) && \in_array($aMatch[1], \explode(' ',
|
|
||||||
'.academy .actor .agency .audio .bar .beer .bike .blue .boutique .cab .camera .camp .capital .cards .careers .cash .catering .center .cheap .city .cleaning .clinic .clothing .club .coffee .community .company .computer .construction .consulting .contractors .cool .credit .dance .dating .democrat .dental .diamonds .digital .direct .directory .discount .domains .education .email .energy .equipment .estate .events .expert .exposed .fail .farm .fish .fitness .florist .fund .futbol .gallery .gift .glass .graphics .guru .help .holdings .holiday .host .hosting .house .institute .international .kitchen .land .life .lighting .limo .link .management .market .marketing .media .menu .moda .partners .parts .photo .photography .photos .pics .pink .press .productions .pub .red .rentals .repair .report .rest .sexy .shoes .social .solar .solutions .space .support .systems .tattoo .tax .technology .tips .today .tools .town .toys .trade .training .university .uno .vacations .vision .vodka .voyage .watch .webcam .wiki .work .works .wtf .zone .aero .asia .biz .cat .com .coop .edu .gov .info .int .jobs .mil .mobi .museum .name .net .org .pro .tel .travel .xxx .xyz '.
|
|
||||||
'.ac .ad .ae .af .ag .ai .al .am .an .ao .aq .ar .as .at .au .aw .ax .az .ba .bb .bd .be .bf .bg .bh .bi .bj .bm .bn .bo .br .bs .bt .bv .bw .by .bz .ca .cc .cd .cf .cg .ch .ci .ck .cl .cm .cn .co .cr .cs .cu .cv .cx .cy .cz .dd .de .dj .dk .dm .do .dz .ec .ee .eg .er .es .et .eu .fi .fj .fk .fm .fo .fr .ga .gb .gd .ge .gf .gg .gh .gi .gl .gm .gn .gp .gq .gr .gs .gt .gu .gw .gy .hk .hm .hn .hr .ht .hu .id .ie .il .im .in .io .iq .ir .is .it .je .jm .jo .jp .ke .kg .kh .ki .km .kn .kp .kr .kw .ky .kz .la .lb .lc .li .lk .lr .ls .lt .lu .lv .ly .ma .mc .md .me .mg .mh .mk .ml .mm .mn .mo .mp .mq .mr .ms .mt .mu .mv .mw .mx .my .mz .na .nc .ne .nf .ng .ni .nl .no .np .nr .nu .nz .om .pa .pe .pf .pg .ph .pk .pl .pm .pn .pr .ps .pt .pw .py .qa .re .ro .rs .ru . .rw .sa .sb .sc .sd .se .sg .sh .si .sj .sk .sl .sm .sn .so .sr .st .su .sv .sy .sz .tc .td .tf .tg .th .tj .tk .tl .tm .tn .to .tp .tr .tt .tv .tw .tz .ua .ug .uk .us .uy .uz .va .vc .ve .vg .vi .vn .vu .wf .ws .ye .yt .za .zm .zw'
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function ValidateIP(string $sIp) : bool
|
public static function ValidateIP(string $sIp) : bool
|
||||||
{
|
{
|
||||||
return !empty($sIp) && $sIp === \filter_var($sIp, FILTER_VALIDATE_IP);
|
return !empty($sIp) && $sIp === \filter_var($sIp, FILTER_VALIDATE_IP);
|
||||||
|
|
@ -726,9 +713,6 @@ abstract class Utils
|
||||||
{
|
{
|
||||||
$aParts = \explode('@', $sStr);
|
$aParts = \explode('@', $sStr);
|
||||||
$sDomain = \array_pop($aParts);
|
$sDomain = \array_pop($aParts);
|
||||||
if ($bLowerCase) {
|
|
||||||
$sDomain = \mb_strtolower($sDomain);
|
|
||||||
}
|
|
||||||
if (\strlen($sDomain) && \preg_match('/[^\x20-\x7E]/', $sDomain)) {
|
if (\strlen($sDomain) && \preg_match('/[^\x20-\x7E]/', $sDomain)) {
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -736,6 +720,9 @@ abstract class Utils
|
||||||
}
|
}
|
||||||
catch (\Throwable $oException) {}
|
catch (\Throwable $oException) {}
|
||||||
}
|
}
|
||||||
|
if ($bLowerCase) {
|
||||||
|
$sDomain = \strtolower($sDomain);
|
||||||
|
}
|
||||||
$aParts[] = $sDomain;
|
$aParts[] = $sDomain;
|
||||||
return \implode('@', $aParts);
|
return \implode('@', $aParts);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,55 +33,44 @@ trait UserAuth
|
||||||
$this->Plugins()->RunHook('login.credentials.step-1', array(&$sEmail));
|
$this->Plugins()->RunHook('login.credentials.step-1', array(&$sEmail));
|
||||||
|
|
||||||
if (!\str_contains($sEmail, '@')) {
|
if (!\str_contains($sEmail, '@')) {
|
||||||
$this->logWrite('The email address "' . $sEmail . '" is not complete', \LOG_INFO, 'LOGIN');
|
$this->logWrite("The email address '{$sEmail}' is incomplete", \LOG_INFO, 'LOGIN');
|
||||||
|
$oDomain = null;
|
||||||
$bAdded = false;
|
|
||||||
|
|
||||||
if ($this->Config()->Get('login', 'determine_user_domain', false)) {
|
if ($this->Config()->Get('login', 'determine_user_domain', false)) {
|
||||||
$sUserHost = \trim($this->Http()->GetHost(true, true));
|
$sUserHost = \strtolower(\idn_to_ascii($this->Http()->GetHost(false, true)));
|
||||||
$this->logWrite('Determined user domain: ' . $sUserHost, \LOG_INFO, 'LOGIN');
|
$this->logWrite("Determined user domain: {$sUserHost}", \LOG_INFO, 'LOGIN');
|
||||||
|
|
||||||
$aDomainParts = \explode('.', $sUserHost);
|
$aDomainParts = \explode('.', $sUserHost);
|
||||||
$iLimit = \min(\count($aDomainParts), 14);
|
$iLimit = \min(\count($aDomainParts), 14);
|
||||||
|
|
||||||
$oDomainProvider = $this->DomainProvider();
|
$oDomainProvider = $this->DomainProvider();
|
||||||
while (0 < $iLimit--) {
|
while (0 < $iLimit--) {
|
||||||
$sLine = \implode('.', $aDomainParts);
|
$sLine = \implode('.', $aDomainParts);
|
||||||
|
|
||||||
$oDomain = $oDomainProvider->Load($sLine, false);
|
$oDomain = $oDomainProvider->Load($sLine, false);
|
||||||
if ($oDomain) {
|
if ($oDomain) {
|
||||||
$bAdded = true;
|
|
||||||
$this->logWrite('Check "' . $sLine . '": OK (' . $sEmail . ' > ' . $sEmail . '@' . $sLine . ')',
|
|
||||||
\LOG_INFO, 'LOGIN');
|
|
||||||
|
|
||||||
$sEmail .= '@' . $sLine;
|
$sEmail .= '@' . $sLine;
|
||||||
|
$this->logWrite("Check '{$sLine}': OK", \LOG_INFO, 'LOGIN');
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
$this->logWrite('Check "' . $sLine . '": NO', \LOG_INFO, 'LOGIN');
|
$this->logWrite("Check '{$sLine}': NO", \LOG_INFO, 'LOGIN');
|
||||||
}
|
}
|
||||||
|
|
||||||
\array_shift($aDomainParts);
|
\array_shift($aDomainParts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$bAdded) {
|
if (!$oDomain) {
|
||||||
$oDomain = $oDomainProvider->Load($sUserHost, true);
|
$oDomain = $oDomainProvider->Load($sUserHost, true);
|
||||||
if ($oDomain) {
|
if ($oDomain) {
|
||||||
$bAdded = true;
|
|
||||||
$this->logWrite('Check "' . $sUserHost . '" with wildcard: OK (' . $sEmail . ' > ' . $sEmail . '@' . $sUserHost . ')',
|
|
||||||
\LOG_INFO, 'LOGIN');
|
|
||||||
|
|
||||||
$sEmail .= '@' . $sUserHost;
|
$sEmail .= '@' . $sUserHost;
|
||||||
|
$this->logWrite("Check '{$sUserHost}' with wildcard: OK", \LOG_INFO, 'LOGIN');
|
||||||
} else {
|
} else {
|
||||||
$this->logWrite('Check "' . $sUserHost . '" with wildcard: NO', \LOG_INFO, 'LOGIN');
|
$this->logWrite("Check '{$sUserHost}' with wildcard: NO", \LOG_INFO, 'LOGIN');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$bAdded) {
|
if (!$oDomain) {
|
||||||
$this->logWrite('Domain was not found!', \LOG_INFO, 'LOGIN');
|
$this->logWrite("Domain '{$sUserHost}' was not determined!", \LOG_INFO, 'LOGIN');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$bAdded) {
|
if (!$oDomain) {
|
||||||
$sDefDomain = \trim($this->Config()->Get('login', 'default_domain', ''));
|
$sDefDomain = \trim($this->Config()->Get('login', 'default_domain', ''));
|
||||||
if (\strlen($sDefDomain)) {
|
if (\strlen($sDefDomain)) {
|
||||||
if ('HTTP_HOST' === $sDefDomain || 'SERVER_NAME' === $sDefDomain) {
|
if ('HTTP_HOST' === $sDefDomain || 'SERVER_NAME' === $sDefDomain) {
|
||||||
|
|
@ -89,10 +78,8 @@ trait UserAuth
|
||||||
} else if ('gethostname' === $sDefDomain) {
|
} else if ('gethostname' === $sDefDomain) {
|
||||||
$sDefDomain = \gethostname();
|
$sDefDomain = \gethostname();
|
||||||
}
|
}
|
||||||
$this->logWrite('Default domain "' . $sDefDomain . '" was used. (' . $sEmail . ' > ' . $sEmail . '@' . $sDefDomain . ')',
|
|
||||||
\LOG_INFO, 'LOGIN');
|
|
||||||
|
|
||||||
$sEmail .= '@' . $sDefDomain;
|
$sEmail .= '@' . $sDefDomain;
|
||||||
|
$this->logWrite("Default domain '{$sDefDomain}' will be used.", \LOG_INFO, 'LOGIN');
|
||||||
} else {
|
} else {
|
||||||
$this->logWrite('Default domain not configured.', \LOG_INFO, 'LOGIN');
|
$this->logWrite('Default domain not configured.', \LOG_INFO, 'LOGIN');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
/**
|
||||||
|
* Domain name is handled in lowercase punycode.
|
||||||
|
* Because internationalized domain names can have uppercase or titlecase characters.
|
||||||
|
*/
|
||||||
|
|
||||||
namespace RainLoop\Model;
|
namespace RainLoop\Model;
|
||||||
|
|
||||||
|
|
@ -20,8 +24,7 @@ class Domain implements \JsonSerializable
|
||||||
|
|
||||||
function __construct(string $sName)
|
function __construct(string $sName)
|
||||||
{
|
{
|
||||||
$this->Name = $sName;
|
$this->Name = \strtolower(\idn_to_ascii($sName));
|
||||||
|
|
||||||
$this->IMAP = new \MailSo\Imap\Settings;
|
$this->IMAP = new \MailSo\Imap\Settings;
|
||||||
$this->SMTP = new \MailSo\Smtp\Settings;
|
$this->SMTP = new \MailSo\Smtp\Settings;
|
||||||
$this->Sieve = new \MailSo\Sieve\Settings;
|
$this->Sieve = new \MailSo\Sieve\Settings;
|
||||||
|
|
@ -96,7 +99,7 @@ class Domain implements \JsonSerializable
|
||||||
|
|
||||||
public function SetAliasName(string $sAliasName) : void
|
public function SetAliasName(string $sAliasName) : void
|
||||||
{
|
{
|
||||||
$this->aliasName = $sAliasName;
|
$this->aliasName = \strtolower(\idn_to_ascii($sAliasName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ValidateWhiteList(string $sEmail, string $sLogin = '') : bool
|
public function ValidateWhiteList(string $sEmail, string $sLogin = '') : bool
|
||||||
|
|
@ -232,14 +235,14 @@ class Domain implements \JsonSerializable
|
||||||
{
|
{
|
||||||
$aResult = array(
|
$aResult = array(
|
||||||
// '@Object' => 'Object/Domain',
|
// '@Object' => 'Object/Domain',
|
||||||
'name' => $this->Name,
|
'name' => \idn_to_utf8($this->Name),
|
||||||
'IMAP' => $this->IMAP,
|
'IMAP' => $this->IMAP,
|
||||||
'SMTP' => $this->SMTP,
|
'SMTP' => $this->SMTP,
|
||||||
'Sieve' => $this->Sieve,
|
'Sieve' => $this->Sieve,
|
||||||
'whiteList' => $this->whiteList
|
'whiteList' => $this->whiteList
|
||||||
);
|
);
|
||||||
if ($this->aliasName) {
|
if ($this->aliasName) {
|
||||||
$aResult['aliasName'] = $this->aliasName;
|
$aResult['aliasName'] = \idn_to_utf8($this->aliasName);
|
||||||
}
|
}
|
||||||
return $aResult;
|
return $aResult;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ abstract class Autoconfig
|
||||||
public static function discover(string $emailaddress) : ?array
|
public static function discover(string $emailaddress) : ?array
|
||||||
{
|
{
|
||||||
$domain = \MailSo\Base\Utils::GetDomainFromEmail($emailaddress);
|
$domain = \MailSo\Base\Utils::GetDomainFromEmail($emailaddress);
|
||||||
|
// $domain = \strtolower(\idn_to_ascii($domain));
|
||||||
// First try
|
// First try
|
||||||
$autoconfig = static::resolve($domain, $emailaddress);
|
$autoconfig = static::resolve($domain, $emailaddress);
|
||||||
if ($autoconfig) {
|
if ($autoconfig) {
|
||||||
|
|
@ -89,7 +90,7 @@ abstract class Autoconfig
|
||||||
private static function publicsuffixes() : array
|
private static function publicsuffixes() : array
|
||||||
{
|
{
|
||||||
$oCache = \RainLoop\Api::Actions()->Cacher();
|
$oCache = \RainLoop\Api::Actions()->Cacher();
|
||||||
$list = $oCache->Get('public_suffix_list') ?: null;
|
$list = $oCache->Get('public_suffix_list');
|
||||||
if ($list) {
|
if ($list) {
|
||||||
$list = \json_decode($list, true);
|
$list = \json_decode($list, true);
|
||||||
if ($list[1] < \time()) {
|
if ($list[1] < \time()) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
/**
|
||||||
|
* Domain names are handled in lowercase punycode.
|
||||||
|
* Because internationalized domain names can have uppercase or titlecase characters.
|
||||||
|
*/
|
||||||
|
|
||||||
namespace RainLoop\Providers\Domain;
|
namespace RainLoop\Providers\Domain;
|
||||||
|
|
||||||
|
|
@ -6,7 +10,7 @@ use MailSo\Cache\CacheClient;
|
||||||
|
|
||||||
class DefaultDomain implements DomainInterface
|
class DefaultDomain implements DomainInterface
|
||||||
{
|
{
|
||||||
const CACHE_KEY = '/WildCard/DomainCache/';
|
const CACHE_KEY = '/WildCardDomainsCache/';
|
||||||
|
|
||||||
protected string $sDomainPath;
|
protected string $sDomainPath;
|
||||||
|
|
||||||
|
|
@ -20,30 +24,26 @@ class DefaultDomain implements DomainInterface
|
||||||
|
|
||||||
private static function decodeFileName(string $sName) : string
|
private static function decodeFileName(string $sName) : string
|
||||||
{
|
{
|
||||||
return ('default' === $sName)
|
return ('default' === $sName) ? '*' : \str_replace('_wildcard_', '*', $sName);
|
||||||
? '*'
|
|
||||||
: \str_replace('_wildcard_', '*', \MailSo\Base\Utils::IdnToUtf8($sName, true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function encodeFileName(string $sName) : string
|
private static function encodeFileName(string $sName) : string
|
||||||
{
|
{
|
||||||
return ('*' === $sName)
|
return ('*' === $sName) ? 'default' : \str_replace('*', '_wildcard_', \strtolower(\idn_to_ascii($sName)));
|
||||||
? 'default'
|
|
||||||
: \str_replace('*', '_wildcard_', \MailSo\Base\Utils::IdnToAscii($sName, true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getWildcardDomainsLine() : string
|
private function getWildcardDomainsLine() : string
|
||||||
{
|
{
|
||||||
if ($this->oCacher) {
|
if ($this->oCacher) {
|
||||||
$sResult = $this->oCacher->Get(static::CACHE_KEY);
|
$sResult = $this->oCacher->Get(static::CACHE_KEY);
|
||||||
if (\strlen($sResult)) {
|
if (\is_string($sResult)) {
|
||||||
return $sResult;
|
return $sResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$aNames = array();
|
$aNames = array();
|
||||||
|
|
||||||
// $aList = \glob($this->sDomainPath.'/*.{ini,json}', GLOB_BRACE);
|
// $aList = \glob($this->sDomainPath.'/*.{ini,json,alias}', GLOB_BRACE);
|
||||||
$aList = \glob($this->sDomainPath.'/*.json');
|
$aList = \glob($this->sDomainPath.'/*.json');
|
||||||
foreach ($aList as $sFile) {
|
foreach ($aList as $sFile) {
|
||||||
$sName = \substr(\basename($sFile), 0, -5);
|
$sName = \substr(\basename($sFile), 0, -5);
|
||||||
|
|
@ -72,16 +72,14 @@ class DefaultDomain implements DomainInterface
|
||||||
$sResult = \implode(' ', \array_unique($aNames));
|
$sResult = \implode(' ', \array_unique($aNames));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->oCacher) {
|
$this->oCacher && $this->oCacher->Set(static::CACHE_KEY, $sResult);
|
||||||
$this->oCacher->Set(static::CACHE_KEY, $sResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sResult;
|
return $sResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Load(string $sName, bool $bFindWithWildCard = false, bool $bCheckDisabled = true, bool $bCheckAliases = true) : ?\RainLoop\Model\Domain
|
public function Load(string $sName, bool $bFindWithWildCard = false, bool $bCheckDisabled = true, bool $bCheckAliases = true) : ?\RainLoop\Model\Domain
|
||||||
{
|
{
|
||||||
$sName = \MailSo\Base\Utils::IdnToUtf8($sName, true);
|
$sName = \strtolower(\idn_to_ascii($sName));
|
||||||
if ($bCheckDisabled && \in_array($sName, $this->getDisabled())) {
|
if ($bCheckDisabled && \in_array($sName, $this->getDisabled())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +128,7 @@ class DefaultDomain implements DomainInterface
|
||||||
|
|
||||||
public function SaveAlias(string $sName, string $sAlias) : bool
|
public function SaveAlias(string $sName, string $sAlias) : bool
|
||||||
{
|
{
|
||||||
$sAlias = \MailSo\Base\Utils::IdnToUtf8($sAlias, true);
|
$sAlias = \strtolower(\idn_to_ascii($sAlias));
|
||||||
$sRealFileName = static::encodeFileName($sName);
|
$sRealFileName = static::encodeFileName($sName);
|
||||||
\RainLoop\Utils::saveFile($this->sDomainPath.'/'.$sRealFileName.'.alias', $sAlias);
|
\RainLoop\Utils::saveFile($this->sDomainPath.'/'.$sRealFileName.'.alias', $sAlias);
|
||||||
$this->oCacher && $this->oCacher->Delete(static::CACHE_KEY);
|
$this->oCacher && $this->oCacher->Delete(static::CACHE_KEY);
|
||||||
|
|
@ -147,7 +145,7 @@ class DefaultDomain implements DomainInterface
|
||||||
// RainLoop use comma, we use newline
|
// RainLoop use comma, we use newline
|
||||||
$sItem = \strtok($sFile, ",\n");
|
$sItem = \strtok($sFile, ",\n");
|
||||||
while (false !== $sItem) {
|
while (false !== $sItem) {
|
||||||
$aDisabled[] = \MailSo\Base\Utils::IdnToUtf8($sItem, true);
|
$aDisabled[] = \strtolower(\idn_to_ascii($sItem));
|
||||||
$sItem = \strtok(",\n");
|
$sItem = \strtok(",\n");
|
||||||
}
|
}
|
||||||
return $aDisabled;
|
return $aDisabled;
|
||||||
|
|
@ -156,7 +154,7 @@ class DefaultDomain implements DomainInterface
|
||||||
|
|
||||||
public function Disable(string $sName, bool $bDisable) : bool
|
public function Disable(string $sName, bool $bDisable) : bool
|
||||||
{
|
{
|
||||||
$sName = \MailSo\Base\Utils::IdnToUtf8($sName, true);
|
$sName = \strtolower(\idn_to_ascii($sName));
|
||||||
if ($sName) {
|
if ($sName) {
|
||||||
$aResult = $this->getDisabled();
|
$aResult = $this->getDisabled();
|
||||||
if ($bDisable) {
|
if ($bDisable) {
|
||||||
|
|
@ -209,20 +207,23 @@ class DefaultDomain implements DomainInterface
|
||||||
if ($bAlias) {
|
if ($bAlias) {
|
||||||
if ($bIncludeAliases) {
|
if ($bIncludeAliases) {
|
||||||
$aAliases[$sName] = array(
|
$aAliases[$sName] = array(
|
||||||
'name' => $sName,
|
'name' => \idn_to_utf8($sName),
|
||||||
|
'punycode' => $sName,
|
||||||
'disabled' => \in_array($sName, $aDisabledNames),
|
'disabled' => \in_array($sName, $aDisabledNames),
|
||||||
'alias' => true
|
'alias' => true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (false !== \strpos($sName, '*')) {
|
} else if (false !== \strpos($sName, '*')) {
|
||||||
$aWildCards[$sName] = array(
|
$aWildCards[$sName] = array(
|
||||||
'name' => $sName,
|
'name' => \idn_to_utf8($sName),
|
||||||
|
'punycode' => $sName,
|
||||||
'disabled' => \in_array($sName, $aDisabledNames),
|
'disabled' => \in_array($sName, $aDisabledNames),
|
||||||
'alias' => false
|
'alias' => false
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$aResult[$sName] = array(
|
$aResult[$sName] = array(
|
||||||
'name' => $sName,
|
'name' => \idn_to_utf8($sName),
|
||||||
|
'punycode' => $sName,
|
||||||
'disabled' => \in_array($sName, $aDisabledNames),
|
'disabled' => \in_array($sName, $aDisabledNames),
|
||||||
'alias' => false
|
'alias' => false
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ abstract class IDN
|
||||||
{
|
{
|
||||||
public static function anyToAscii(string $string) : string
|
public static function anyToAscii(string $string) : string
|
||||||
{
|
{
|
||||||
if (\preg_match('#[:/]#', $string)) {
|
if (\strspn($string, ':/')) {
|
||||||
return static::uri($string, true);
|
return static::uri($string, true);
|
||||||
}
|
}
|
||||||
if (\strpos($string, '@')) {
|
if (\str_contains($string, '@')) {
|
||||||
return static::emailAddress($string, true);
|
return static::emailAddress($string, true);
|
||||||
}
|
}
|
||||||
return \idn_to_ascii($string);
|
return \idn_to_ascii($string);
|
||||||
|
|
@ -20,91 +20,73 @@ abstract class IDN
|
||||||
|
|
||||||
public static function anyToUtf8(string $string) : string
|
public static function anyToUtf8(string $string) : string
|
||||||
{
|
{
|
||||||
if (\preg_match('#[:/]#', $string)) {
|
if (\strspn($string, ':/')) {
|
||||||
return static::uri($string, false);
|
return static::uri($string, false);
|
||||||
}
|
}
|
||||||
if (\strpos($string, '@')) {
|
if (\str_contains($string, '@')) {
|
||||||
return static::emailAddress($string, false);
|
return static::emailAddress($string, false);
|
||||||
}
|
}
|
||||||
return \idn_to_utf8($string);
|
return \idn_to_utf8($string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts IDN domain part to lowercased punycode
|
||||||
|
*/
|
||||||
public static function emailToAscii(string $address) : string
|
public static function emailToAscii(string $address) : string
|
||||||
{
|
{
|
||||||
return static::emailAddress($address, true);
|
return static::emailAddress($address, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function emailToUtf8(string $address) : string
|
private static function domain(string $domain, bool $toAscii) : string
|
||||||
{
|
{
|
||||||
return static::emailAddress($address, false);
|
return $toAscii ? \strtolower(\idn_to_ascii($domain)) : \idn_to_utf8($domain);
|
||||||
}
|
/*
|
||||||
|
$domain = \explode('.', $domain);
|
||||||
public static function uriToAscii(string $address) : string
|
foreach ($domain as $k => $v) {
|
||||||
{
|
$conv = $toAscii ? \idn_to_ascii($v) : \idn_to_utf8($v);
|
||||||
return static::uri($address, true);
|
if ($conv) $domain[$k] = $conv;
|
||||||
}
|
}
|
||||||
|
return \implode('.', $domain);
|
||||||
public static function uriToUtf8(string $address) : string
|
*/
|
||||||
{
|
|
||||||
return static::uri($address, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function emailAddress(string $address, bool $toAscii) : string
|
private static function emailAddress(string $address, bool $toAscii) : string
|
||||||
{
|
{
|
||||||
if (\str_contains($address, '@')) {
|
if (!\str_contains($address, '@')) {
|
||||||
$local = \explode('@', $address);
|
throw new \RuntimeException("Invalid email address: {$address}");
|
||||||
$domain = \array_pop($local);
|
|
||||||
$local = \implode('@', $local);
|
|
||||||
$arr = \explode('.', $domain);
|
|
||||||
foreach ($arr as $k => $v) {
|
|
||||||
$conv = $toAscii ? \idn_to_ascii($v) : \idn_to_utf8($v);
|
|
||||||
if ($conv) $arr[$k] = $conv;
|
|
||||||
}
|
|
||||||
return $local . '@' . \implode('.', $arr);
|
|
||||||
}
|
}
|
||||||
return $toAscii ? \idn_to_ascii($address) : \idn_to_utf8($address);
|
$local = \explode('@', $address);
|
||||||
|
$domain = static::domain(\array_pop($local), $toAscii);
|
||||||
|
return \implode('@', $local) . '@' . $domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function uri(string $address, bool $toAscii) : string
|
private static function uri(string $address, bool $toAscii) : string
|
||||||
{
|
{
|
||||||
if (\preg_match('#[:\./]#', $address)) {
|
$parsed = \parse_url($address);
|
||||||
$parsed = \parse_url($address);
|
if (isset($parsed['host'])) {
|
||||||
if (isset($parsed['host'])) {
|
$parsed['host'] = static::domain($parsed['host'], $toAscii);
|
||||||
$arr = \explode('.', $parsed['host']);
|
|
||||||
foreach ($arr as $k => $v) {
|
|
||||||
$conv = $toAscii ? \idn_to_ascii($v) : \idn_to_utf8($v);
|
|
||||||
if ($conv) $arr[$k] = $conv;
|
|
||||||
}
|
|
||||||
$parsed['host'] = \implode('.', $arr);
|
|
||||||
|
|
||||||
$url = '//';
|
$url = empty($parsed['scheme']) ? '//'
|
||||||
if (!empty($parsed['scheme'])) { $url = $parsed['scheme'] . (\strtolower($parsed['scheme']) === 'mailto' ? ':' : '://'); }
|
: $parsed['scheme'] . (\strtolower($parsed['scheme']) === 'mailto' ? ':' : '://');
|
||||||
/*
|
/*
|
||||||
if (!empty($parsed['user']) || !empty($parsed['pass'])) {
|
if (!empty($parsed['user']) || !empty($parsed['pass'])) {
|
||||||
$ret_url .= $parts_arr['user'];
|
$ret_url .= $parts_arr['user'];
|
||||||
if (!empty($parts_arr['pass'])) {
|
if (!empty($parts_arr['pass'])) {
|
||||||
$ret_url .= ':' . $parts_arr['pass'];
|
$ret_url .= ':' . $parts_arr['pass'];
|
||||||
}
|
|
||||||
$ret_url .= '@';
|
|
||||||
}
|
}
|
||||||
|
$ret_url .= '@';
|
||||||
|
}
|
||||||
*/
|
*/
|
||||||
if (!empty($parsed['host'])) { $url .= $parsed['host']; }
|
if (!empty($parsed['host'])) { $url .= $parsed['host']; }
|
||||||
if (!empty($parsed['port'])) { $url .= ':'.$parsed['port']; }
|
if (!empty($parsed['port'])) { $url .= ':'.$parsed['port']; }
|
||||||
if (!empty($parsed['path'])) { $url .= $parsed['path']; }
|
if (!empty($parsed['path'])) { $url .= $parsed['path']; }
|
||||||
if (!empty($parsed['query'])) { $url .= '?'.$parsed['query']; }
|
if (!empty($parsed['query'])) { $url .= '?'.$parsed['query']; }
|
||||||
if (!empty($parsed['fragment'])) { $url .= '#'.$parsed['fragment']; }
|
if (!empty($parsed['fragment'])) { $url .= '#'.$parsed['fragment']; }
|
||||||
return $url;
|
return $url;
|
||||||
}
|
|
||||||
|
|
||||||
// parse_url seems to have failed, try without it
|
|
||||||
$arr = \explode('.', $address);
|
|
||||||
foreach ($arr as $k => $v) {
|
|
||||||
$conv = $toAscii ? \idn_to_ascii($v) : \idn_to_utf8($v);
|
|
||||||
if ($conv) $arr[$k] = $conv;
|
|
||||||
}
|
|
||||||
return \implode('.', $arr);
|
|
||||||
}
|
}
|
||||||
return $toAscii ? \idn_to_ascii($address) : \idn_to_utf8($address);
|
|
||||||
|
// parse_url seems to have failed, try without it
|
||||||
|
return static::domain($address, $toAscii);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue