Handle Domain::$name as lowercase punycode due to IDN complexity

This commit is contained in:
the-djmaze 2024-03-11 20:11:13 +01:00
parent 56ede16833
commit 7999894bda
7 changed files with 89 additions and 129 deletions

View file

@ -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);
} }

View file

@ -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);
} }

View file

@ -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');
} }

View file

@ -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;
} }

View file

@ -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()) {

View file

@ -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
); );

View file

@ -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);
} }
} }